Help with my 1st Tkinter program

B

Bill Dandreta

Below is my 1st Tkinter program.

I would like some constructive criticism, what did I do poorly and how
can it be improved. My goal was to write the program using only what
comes with Python so it will be cross platform without having to
maintain various versions of the code for different platforms and easy
for users to run (i.e., not having to download and install various modules).

I failed on only one count, sound. To my great disappointment, Python
has no native sound support of any kind. All I needed was the ability to
play a sound file. I am using tkSnack for sound.

Bill
=====================================

#!/usr/bin/env python
#
# wordsearch.py - A simple worsearch game. Written October 2004. A list of
# words is hidden in a square grid of letters. The words are placed
vertically,
# horizontally and diagonally. Game play consists of finding the words
in the
# grid. You identify the words by clicking the left button while the
cursor is
# over the 1st or last letter of the word and dragging to the opposite
end of
# the word at which point the word becomes hilighted and is deleted from the
# word list. You release the button and repeat with another word in the
list.
#
from Tkinter import *
import tkFont
from math import sqrt
from os import stat
from stat import ST_SIZE
from random import randrange
from sys import argv
from sets import Set
import tkSnack
from tkMessageBox import showinfo

class MsgDialog(Toplevel):

def __init__(self, f, parent=None, title=None, msg="\n\nHello!\n\n",
buttonLabel="Dismiss"):
Toplevel.__init__(self, parent)
self.transient(parent)
self.parent = parent
if title:
self.title(title)
F = Frame(self)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.dismiss)
self.geometry("+%d+%d" %
(parent.winfo_rootx()+100,parent.winfo_rooty()+100))
F.grid()
L = Label(self, text=msg, font=f)
L.grid(column=0,row=0)
B = Button(self, text=buttonLabel, command=self.dismiss,
default=ACTIVE, font=f)
B.grid(column=0,row=1)
self.initial_focus = B
self.initial_focus.focus_set()
self.wait_window(self)

def dismiss(self, event=None):

"""Returns focus to parent and closes dialog window."""

self.parent.focus_set()
self.destroy()

class Application(Frame):

def __init__(self, master=None):
Frame.__init__(self, master)
self.MOUSE_DOWN = False
self.found_word = False
self.game_won = False
global f14
f14 = tkFont.Font(family="Courier New", size=14, weight="bold")
self.grid()
self.createWidgets()
self.populate_listbox(self.wordlist)
self.populate_matrix(self.letterMatrix,self.wordlist)

def createWidgets(self):

"""Creates the wordlist listbox, letterMatrix canvas, newGame button
and Quit button """

self.wordlist = Listbox(self, height=20, font=f14)
L=self.wordlist
L.grid(column=1, row=0)
self.letterMatrix = Canvas(self, bg="white", width=640, height=640)
C = self.letterMatrix
C.grid(column=0,row=0)
C.bind("<ButtonPress-1>", self.press)
C.bind("<ButtonRelease-1>", self.release)
self.quit = Button(self,text="Quit", command=self.quit, font=f14)
Q=self.quit
Q.grid(column=0, row=1)
self.new = Button(self,text="New Game", command=self.new_game,
font=f14)
N=self.new
N.grid(column=1, row=1)

def populate_listbox(self, L):

"""Selects a wordlist at random and chooses 20 words (5 to 13 bytes
long)
from the list at random and puts them in the listbox"""

word_files = ['bible']*15+['birds']*45+['dogs']+['names']*30
self.word_file = word_files[randrange(len(word_files))]
f=self.word_file
self.master.title("WordSearch "+f)
all_words = open(f, 'rb')
size = stat(f)[ST_SIZE]
self.words = []
words = self.words
while len(words)<20:
pos = randrange(size)
all_words.seek(pos)
bytes = all_words.read(256)
lines = bytes.split('\n')
for w in lines[1:-1]:
w = w[:-1]
ws = len(w)
if ws>4 and ws<14:
if w not in words:
words.append(w)
break
words.sort()
for w in words:
L.insert(END,w)
self.words = [w.replace(' ','') for w in words]

def select_random_start(self,word_length):

"""Select a random starting point and direction in the letterMatrix
for a word"""

max_len = [0]
direction = 0
while max_len[direction]<word_length:
row,col = randrange(26),randrange(26)
direction = randrange(8)
max_len = {0:26-col,1:min(row,26-col),2:row,3:min(row,col),
4:col,5:min(26-row,col),6:26-row,7:min(26-row,26-col)}
deltas = {0:(24,0),1:(24,-24),2:(0,-24),3:(-24,-24),
4:(-24,0),5:(-24,24),6:(0,24),7:(24,24)}
x,y = 24+24*col,24+24*row
dx,dy = deltas[direction]
return x,y,dx,dy

def remove_letters(self, word,M,C):

"""Remove letters for a word that did not fit."""

for obj in C.find_withtag(word):
tags = [tag for tag in C.gettags(obj) if tag!='current' and
tag!=word]
if tags:
C.dtag(obj,word)
else:
(x,y) = C.coords(obj)
del M[x,y]
C.delete(obj)

def print_word(self,C,M,word):

"""Place a word in the letterMatrix."""

x,y,dx,dy = self.select_random_start(len(word))
for letter in word:
if (x,y) not in M:
M[x,y] = C.create_text(x,y,text=letter,font=f14,tags=word,
fill=solution_color)
elif C.itemcget(M[x,y],'text')==letter:
C.addtag_withtag(word,M[x,y])
else:
self.remove_letters(word,M,C)
return False
x,y = x+dx,y+dy
return True

def populate_matrix(self,C,L):

"""Put all words in the matrix trying to place each word up to 1000
times then fill in all remaining empty positions in letter'Matrix
with random letters."""

words = self.words
self.matrix = {}
M = self.matrix
for word in words:
for i in range(1000):
if self.print_word(C,M,word):
break
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
col,row=0,0
for i in range(26*26):
x = 24+24*col
y = 24+24*row
if (x,y) not in M:
letter = alpha[randrange(26)]
M[x,y] = C.create_text(x, y, text=letter, font=f14, tags="t")
col = (col + 1) % 26
if col==0:
row += 1

def press(self, event):

"""Marks the anchor point for letter selection during game play."""

C = event.widget
if 't' in C.itemcget('current', "tags"):
(self.anchorx,self.anchory) = C.coords('current')
self.last = self.matrix[(self.anchorx,self.anchory)]
self.MOUSE_DOWN = True
self.found_word = False
C.bind("<Motion>",self.motion)

def draw_enclosure(self,x,y,ax,ay,slope,C):

"""Draws a loop around selected letters, i.e., anchor letter to letter
under current mouse position."""

delta = 5*sqrt(2)
lines = {-1:[delta,delta], 0:[0,10], 1:[-delta,delta],'undef':[10,0]}
[dx,dy] = lines[slope]
dy = lines[slope][1]
self.line1 = C.create_line(x+dx,y+dy,ax+dx, ay+dy)
self.line2 = C.create_line(x-dx,y-dy,ax-dx, ay-dy)
arcs = {-1:[135,360*int(y>ay)-180], 0:[90,-360*int(x>ax)+180],
1:[45,-360*int(x>ax)+180],'undef':[0,360*int(y<ay)-180]}
startangle=arcs[slope][0]
extentangle =arcs[slope][1]
self.arc1 =
C.create_arc(x-10,y+10,x+10,y-10,style="arc",start=startangle,extent =
extentangle)
self.arc2 =
C.create_arc(ax-10,ay+10,ax+10,ay-10,style="arc",start=startangle,extent
=-extentangle)

def getword(self,x,y,ax,ay,slope,C,M):

"""Checks selected letters to see if they form a word, if so hilights
the letters and returns the word."""

ltrs = [C.itemcget(M[ax,ay],'text'),C.itemcget(M[x,y],'text')]
potential_words1 = Set(C.gettags(M[ax,ay]))
potential_words2 = Set(C.gettags(M[x,y]))
potential_words = potential_words1 & potential_words2
word_len = 1+max(abs(ax-x),abs(ay-y))/24
for word in potential_words:
if word[0] in ltrs and word[-1] in ltrs and len(word)==word_len:
for i in C.find_withtag(word):
C.itemconfigure(i,fill='red')
self.MOUSE_DOWN = False
return word
else:
return None

def calc_slope(self,x,y,ax,ay):

"""Calculate the slope of the line connecting the anchor letter to the
letter at the current mouse position."""

try:
slope = (y-ay)/(x-ax)
except:
slope = 'undef'
return slope

def delete_enclosure(self,C):

"""Remove the loop around the last selection of letters."""

try:
for obj in [self.line1, self.line2, self.arc1, self.arc2]:
C.delete(obj)
except:
pass

def motion (self, event):

"""Checks whether selected letters form a word from the wordlist and if
so hilights the word and removes it from the listbox."""

if self.MOUSE_DOWN:
C = event.widget
M = self.matrix
pts = C.coords(C.find_closest(event.x,event.y))
if len(pts)==2:
(x,y) = pts
if M[(x,y)] != self.last:
self.last = M[(x,y)]
(ax,ay) = (self.anchorx,self.anchory)
slope = self.calc_slope(x,y,ax,ay)
if slope in [-1, 0 ,1, 'undef']:
self.delete_enclosure(C)
self.draw_enclosure(x,y,ax,ay,slope,C)
word = self.getword(x,y,ax,ay,slope,C,M)
if word:
self.found_word = True
self.MOUSE_DOWN = False
indx = self.words.index(word)
L = self.wordlist
L.delete(indx)
if L.size()==0:
self.game_won = True
self.words.remove(word)

def release(self, event):

"""Does any needed cleanup when mouse button is released after
letter selection."""

C = event.widget
C.unbind("<Motion>")
self.MOUSE_DOWN = False
if self.found_word:
self.found_word = False
try:
self.line1=self.line2=self.arc1=self.arc2=None
except:
pass
else:
self.delete_enclosure(C)
if self.game_won:
self.show_game_won_window()

def new_game(self):

"""Clear the game board and reinitialize."""

self.MOUSE_DOWN = False
self.found_word = False
self.game_won = False
C = self.letterMatrix
for obj in C.find_all():
C.delete(obj)
L = self.wordlist
while L.size() != 0:
L.delete(0)
self.populate_listbox(L)
self.populate_matrix(C,L)

def show_game_won_window(self):

"""Play a sound and display winning dialog."""

tkSnack.initializeSnack(self)
tada = tkSnack.Sound(load='tada.wav')
tada.play()
MsgDialog(f14, app, "Winner","\n\n Congratulations you won! \n\n")

f14 = None
solution_color = 'black'

try:
if argv[1] == '-test':
solution_color = 'blue'
except:
pass
app = Application()
app.mainloop()
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top