From a thread on another forum, I wondered how difficult it would be to create a Tic-Tac-Toe game that could not be beaten. Since it is easiest if the computer goes first and chooses the center square, this code implements that portion. The logic is fairly simple; occupy the center square as that removes both diagonals and both middle options for the opponent. Then choose a corner square since it removes two of the remaining options, leaving two only. Before every move, check for two in a row that can lead to a win or a block of the opponent. I suppose it could be improved a little and pick a square where there are 3 that are not occupied, and so a possible win, but this is all the time I have for now.
Unbeatable Tic-Tac_Toe
TrustyTony commented: Thanks for demonstration of tkinter wait_variable +13
from Tkinter import *
import tkMessageBox
from functools import partial
class TicTacToe:
def __init__(self, top):
self.top = top
self.top.geometry("108x117+20+20")
self.button_dic = {} ## pointer to buttons and StringVar()
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.moves=0 ## incremented for each move
self.buttons()
exit = Button(self.top_frame, text='Exit', \
command=self.stop).grid(row=10,column=0, columnspan=5)
self.player = True ## True = "X", False = "O"
self.next_player.set(self.player) ## tk boolean to signal move has been made
ctr=0
while (len(self.X_O_dict["X"]) + len(self.X_O_dict["O"]) < 9):
self.moves += 1
## "ret" is not used and is just to signal a return from the function
ret=self.selection()
if self.moves: ## set to zero if there is a winner
self.is_tie()
##-------------------------------------------------------------------
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, \
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
exitb = Button(self.top_frame, text='Exit', \
command=self.stop).grid(row=10,column=0, columnspan=5)
##----------------------------------------------------------------
def cb_handler(self, square_number):
if self.player: ## True = "X", False = "O"
this_player = "X"
else:
this_player = "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"
##----------------------------------------------------------------
def check_for_winner( self, list_in):
winner_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9], \
[1, 4, 7], [2, 5, 8], [3, 6, 9], \
[1, 5, 9], [3, 5, 7]]
for winner in winner_list:
if set(winner).issubset(set(list_in)):
if self.player: ## True = "X", False = "O"
self.display_msg("Winner is X")
else:
self.display_msg("Winner is O")
##----------------------------------------------------------------
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
"""
two_row=(set([1,2,3]), set([4,5,6]), set([7,8,9]), \
set([1,4,7]), set([2,5,8]), set([3,6,9]), \
set([1,5,9]), set([3,5,7]), \
set([1,2,4]), set([2,3,6]), set([4,7,8]), set([6,8,9]))
## check to win first and then to block
for player in ["X", "O"]:
this_list = self.X_O_dict[player]
for sub_set in two_row:
ctr=0
not_in = []
for square in sub_set:
if square in this_list:
ctr += 1 ## number occupied
else:
not_in.append(square)
## if two squares only are occupied, and the third is not
## occupied by the opponent
if (ctr == 2) and (self.legal_move(not_in[0])):
return not_in ## = move to be made
return []
##----------------------------------------------------------------
def display_msg(self, msg):
tl=Toplevel(self.top, takefocus=True)
tl.geometry("75x50+30+60")
lb=Label(tl, text=msg)
lb.grid()
self.top.after(3000, self.stop)
##----------------------------------------------------------------
def is_tie(self):
self.display_msg("...It's A TIE.....")
##-------------------------------------------------------------------
def legal_move(self, square_number):
if square_number not in self.X_O_dict["X"] and \
square_number not in self.X_O_dict["O"]:
return True
return False
##----------------------------------------------------------------
def selection(self):
if self.player: ## computer moves
## 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_list=self.check_two_in_a_row()
if len(move_list): ## move to win or block
self.cb_handler(move_list[0])
return 1
## sequence = middle square, and then each corner as the
## 2 middle rows are elmininated by the middle square
start_set=(5, 1, 3, 7, 9, 2, 4, 6, 8)
ret=False
ctr = 0
while not ret and ctr < 9:
ret=self.legal_move(start_set[ctr])
if ret:
self.cb_handler(start_set[ctr])
ctr+=1
else: ## person moves
## set buttons back to normal
for but in self.button_dic:
self.button_dic[but][1].state=NORMAL
self.top.wait_variable(self.next_player)
return 1
##----------------------------------------------------------------
def stop(self):
## clear any wait_variable
self.next_player.set(False)
self.next_player.set(True)
self.moves=0
## a hack to get around the "len(self.X_O_dict" while() loop continuing
for ctr in range(10):
self.X_O_dict["X"].append(ctr)
self.top.destroy()
self.top.quit()
##===================================================================
root = Tk()
BT=TicTacToe(root)
root.mainloop()
TrustyTony 888 pyMod Team Colleague Featured Poster
TrustyTony 888 pyMod Team Colleague Featured Poster
TrustyTony 888 pyMod Team Colleague Featured Poster
TrustyTony 888 pyMod Team Colleague Featured Poster
Be a part of the DaniWeb community
We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.