Help with my 1st Tkinter program

Bill Dandreta wjdandreta at att.net
Tue Oct 19 22:53:43 EDT 2004


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()



More information about the Python-list mailing list