Thanks for sharing your knowledge, our dear brother wooeee (even you do not accept friendship requests).
However I do not like those \
line continuations. They are not needed when you have parenthesis available, which have automatic continuation. Don't like those separarator comments so much either.
Also I think that two quit buttons is little too much. So here is result of my experimenting of your code and making it pyTonyc. I took out some index based looping and used set operations for two in row. I used global list of sets for winners, same for two in line.
I googled on the wait_variable hang up. Finally I ended up try...except way with extranous stop. I added random start with instruction toplevel at beginning. But this version leaves ghosts processes behind from IDLE, so better use it from command line or double click. The algorithm should consider empty line preferable over one with opponents piece, but with correct play that winning chance is anyway blocked with opponent. Basically game will always end with tie with correct play.
The timer quit was fun novelty, but I took it out. User can close the window by himself at end of game.
This is little too trivial game, extend game to gomoku, and things would get interesting (http://flibuste.net/libre/morpyon/)
""" pyTonyc version of wooeee's code for Daniweb """
from Tkinter import *
import tkMessageBox
import random
from functools import partial
textsize = 24
class TicTacToe(object):
winners = ({1, 2, 3}, {4, 5, 6}, {7, 8, 9},
{1, 4, 7}, {2, 5, 8}, {3, 6, 9},
{1, 5, 9}, {3, 5, 7})
def __init__(self, top):
self.player = bool(random.randint(0,1)) ## True = "X", False = "O"
self.display(('I start with X.\nYou have O.' if self.player
else 'You start with O.\nI have X.') +
'\nClose this window to start the game!\n')
self.button_dic = {} ## pointer to buttons and StringVar()
self.top = top
self.top.geometry("240x240+20+20")
self.top.wm_attributes('-topmost', True)
self.X_O_dict = {"X":[], "O":[]} ## list of "X" and "O" moves
self.top.title('Buttons TicTacToe Test')
self.top_frame = Frame(self.top, width=500, height=500)
self.top_frame.grid(row=0, column=1)
self.next_player=BooleanVar()
self.buttons()
# tie if moves finish
self.tie = True
self.moves = 0
self.next_player.set(self.player)
def play(self):
while self.moves < 9 and self.tie:
self.selection()
try:
self.stop()
except TclError:
pass
def buttons(self):
""" create 9 buttons, a 3x3 grid
"""
b_row=1
b_col=0
for j in range(1, 10):
sv=StringVar()
sv.set(j)
b = Button(self.top_frame, textvariable=sv, font=('Arial', textsize),
command=partial(self.cb_handler, j), bg='white')
b.grid(row=b_row, column=b_col)
self.button_dic[j]=[sv, b]
b_col += 1
if b_col > 2:
b_col = 0
b_row += 1
# don't know how to separate from play field using grid
Button(self.top, text='Exit', font=('Arial', textsize*3/4),
command=self.stop).grid(row=2,column=1, columnspan=3)
def stop(self):
if self.moves == 99:
# second stop ignored
return
if not self.tie:
#self.X_O_dict['X'] = range(10)
self.display("Winner is O" if self.player else "Winner is X")
elif self.moves == 9:
self.display("...It's A TIE....." )
else:
self.display("Quitter!")
self.moves = 99
self.tie = False
# unsetting wait events
self.next_player.set(self.player)
self.top.destroy()
#raise SystemExit('Bye, bye')
def cb_handler(self, square_number):
this_player = "X" if self.player else "O"
##--- square not already occupied
if self.legal_move(square_number):
## change button's text to "X" or "O"
self.button_dic[square_number][0].set(this_player)
self.X_O_dict[this_player].append(square_number)
## set background to occupied color
self.button_dic[square_number][1].config(bg="lightgray")
self.check_for_winner(self.X_O_dict[this_player])
self.player = not self.player
self.next_player.set(self.player)
else:
print "Occupied, pick another", square_number
def check_for_winner( self, list_in):
set_in = set(list_in)
if any(winner.issubset(set_in) for winner in self.winners):
self.tie = False
def check_two_in_a_row(self):
""" check for two, but not three, in a row
if player="X", move to win
if player="O", move to block
one check for either strategy
"""
## check to win first and then to block
for player in ["X", "O"]:
this = set(self.X_O_dict[player])
for sub_set in self.winners:
if len(this & sub_set) == 2:
one_to_return = next(iter(sub_set - this))
# all one of them legal, then return
if self.legal_move(one_to_return):
return one_to_return
def display(self, msg, title='Instructions'):
tl=Toplevel()
tl.geometry("350x140+30+60")
tl.title(title)
tl.wm_attributes('-topmost', True)
lb=Label(tl, text=msg, font=('Arial', 16))
# sorry, but I love pack compared to grid (pyTony)
lb.pack(fill='both', expand=True)
tl.lift()
tl.wait_window()
def legal_move(self, square_number):
return (square_number not in self.X_O_dict["X"] and
square_number not in self.X_O_dict["O"])
def selection(self):
## computer moves
if self.player:
## don't accept button clicks when it is computer's (X) turn
for but in self.button_dic:
self.button_dic[but][1].state=DISABLED
move_to_take = self.check_two_in_a_row()
if move_to_take is not None:
self.cb_handler(move_to_take)
else:
## sequence = middle square, and then each corner as the
## 2 middle rows are elmininated by the middle square
for chosen in (5, 1, 3, 7, 9, 2, 4, 6, 8):
if self.legal_move(chosen):
self.cb_handler(chosen)
break
else:
## person moves, set buttons back to normal
for but in self.button_dic:
self.button_dic[but][1].state=NORMAL
# we can wait variable change, because they are BooleanVars
self.top.wait_variable(self.next_player)
self.moves += 1
game = TicTacToe(Tk())
game.play()
print('Finished')