# Simple Tic-Tac-Toe example # CS 1803 - Fall 2010 # Copyright, Jay Summet # #Note, the code below plays TTT, but not terribly well, because #it does not assume the human will make the human's best move! from tkinter import * #We define a TTT class here: class TTT(): #It has an object variable called "board" that remembers #who has made what moves. We use a 9 element long 1D data structure # to make calculations easier. On-Screen, it's represented with a 3x3 grid. board = [ " ", " ", " ", " ", " ", " ", " ", " ", " "] #This function tries to find the "best" move. "best" is defined as # the move least likely to result in a loss (for O - the computer) # It will return the number (position) of the best move for the # computer. ("O") # # However, note that it gets lost in the global "best" and the player # typically finds a "local" best that wins the game! # # This happens because this function does not take into account the # actions of the player (who is presumably trying to win) def findBestMove(self, board): possMoves = self.generateMoves(board) print("PossMoves=", possMoves) moveScores = [] for move in possMoves: nb = board[:] nb[move] = "O" #Make our move! result = self.countWinLoseDraw(nb, "X") moveScores.append(result) print("moveScores:", moveScores) #Try to minimize X's wins! lowestScore = moveScores[0][2] #x's wins! bestMove = possMoves[0] for index in range( len( possMoves) ): if moveScores[index][2] < lowestScore: bestMove = possMoves[index] highestScore = moveScores[index][2] print("our best move = ", bestMove) return bestMove #This function will take a starting board position, and # generate all possible game states moving forward for each # possible move from this board position. It will return a # tuple of (Owins, draw, Xwins) counts for this board. # It calls itself recursively, swapping the player whoGoesNext # each time. def countWinLoseDraw(self, board, whoGoesNext): xWins = 0 draws = 0 oWins = 0 #First, our terminating condition. If the last move #resulted in a win, lose, or draw, we report that! winner = self.checkWin(board) if winner == "X": return (0,0,1) elif winner == "O": return (1,0,0) elif self.checkDraw(board) == True: return(0,1,0) #We have at least one possible move from here! #Generate a list of all valid moves! possibleMoves = self.generateMoves(board) #try out each move, keeping track of our total Xwin,draw,Owin #count... for move in possibleMoves: nb = board[:] nb[move] = whoGoesNext if whoGoesNext == "X": wgn = "O" else: wgn = "X" #Recursive call! results = self.countWinLoseDraw(nb,wgn) oWins = oWins + results[0] draws = draws + results[1] xWins = xWins + results[2] return (oWins, draws, xWins) #Given a board, this function returns a list of the positions # that are possible moves def generateMoves(self, board): possMoves = [] for index in range( len(board)): if board[index] == " ": possMoves.append(index) return(possMoves) #This function returns TRUE if the board has no open moves and #nobody has won. (i.e. it checks for a draw...) def checkDraw(self, board): if self.checkWin(board) == None and len( list(filter(lambda x: x==" ", board)) ) == 0: return True else: return False #This function accepts a "board" which is a list of 9 elements. #Each element is a character (X) (O) or (space). #It returns X, O or None if nobody has won. def checkWin(self, board): if board[0] == board[1] and board[1] == board[2] and board[0] != " ": return(board[0]) if board[3] == board[4] and board[4] == board[5] and board[3] != " ": return(board[3]) if board[6] == board[7] and board[7] == board[8] and board[6] != " ": return(board[6]) if board[0] == board[3] and board[3] == board[6] and board[0] != " ": return(board[0]) if board[1] == board[4] and board[4] == board[7] and board[1] != " ": return(board[1]) if board[2] == board[5] and board[5] == board[8] and board[2] != " ": return(board[2]) if board[0] == board[4] and board[4] == board[8] and board[0] != " ": return(board[0]) if board[2] == board[4] and board[4] == board[6] and board[2] != " ": return(board[2]) #If none of the above are true, nobody has won! return None #This is the constructor. It draws the window with the 9 buttons. def __init__(self, tkMainWin): frame = Frame(tkMainWin) frame.pack() self.B00 = Button(frame) self.B00.bind("", self.clicked) self.B00.grid(row=0,column=0) self.B01 = Button(frame) self.B01.bind("", self.clicked) self.B01.grid(row=0, column=1) self.B02 = Button(frame) self.B02.bind("", self.clicked) self.B02.grid(row=0, column=2) self.B10 = Button(frame) self.B10.bind("", self.clicked) self.B10.grid(row=1,column=0) self.B11 = Button(frame) self.B11.bind("", self.clicked) self.B11.grid(row=1, column=1) self.B12 = Button(frame) self.B12.bind("", self.clicked) self.B12.grid(row=1, column=2) self.B20 = Button(frame) self.B20.bind("", self.clicked) self.B20.grid(row=2,column=0) self.B21 = Button(frame) self.B21.bind("", self.clicked) self.B21.grid(row=2,column=1) self.B22 = Button(frame) self.B22.bind("", self.clicked) self.B22.grid(row=2,column=2) #Set the text for each of the 9 buttons. (Initially, to all Blanks!) self.redrawBoard() #This event handler (callback) will figure out which of the 9 buttons #were clicked, and call the "userMove" method with that move position. def clicked(self, event): if event.widget == self.B00: self.userMove(0) if event.widget == self.B01 : self.userMove(1) if event.widget == self.B02 : self.userMove(2) if event.widget == self.B10 : self.userMove(3) if event.widget == self.B11: self.userMove(4) if event.widget == self.B12 : self.userMove(5) if event.widget == self.B20 : self.userMove(6) if event.widget == self.B21 : self.userMove(7) if event.widget == self.B22 : self.userMove(8) #When a button signals that the user has tried to make a move by # clicking, we check to see if that move is valid. If it is, we # need to check to see if the user has won. If they have not, we # need to make our move, and check to see if the computer has won. # We also redraw the board after each move. def userMove(self, pos): #Is this a valid move? if self.board[pos] == " ": #Record the players move... self.board[pos] = "X" #Then redraw the board! self.redrawBoard() result = self.checkWin(self.board) if result == "X": print("You won!") elif result == "O": print ("The computer won!") elif self.checkDraw(self.board) : print("It's a Draw!") else: #Make our move! self.computerMove() #TODO: Make our move smarter! #Check to see if the computer won with this move! result = self.checkWin(self.board) if result == "O": print("Computer won!") #Then redraw the board! self.redrawBoard() else: #Move is NOT valid! Don't do anything! messagebox.showinfo("Invalid Move", "I'm sorry, that move is not valid!" ) # TODO: Make our move smarter! # This method will make a move for the computer. def computerMove(self): #Method one: just pick the first valid move from an # ordered list of preferred moves. #for move in [4, 0, 2, 6, 8, 1, 3, 5, 7]: # if self.board[move] == " ": # self.board[move] = "O" # return #Method two: Try to find the move that will result #in the most "possible" wins for us, or the least "possible" # wins for the player... ourMove = self.findBestMove(self.board) self.board[ourMove] = "O" return #This method will update the text displayed by # each of the 9 buttons to reflect the "board" # object variable. def redrawBoard(self): self.B00.config( text = self.board[0]) self.B01.config( text = self.board[1]) self.B02.config( text = self.board[2]) self.B10.config( text = self.board[3]) self.B11.config( text = self.board[4]) self.B12.config( text = self.board[5]) self.B20.config( text = self.board[6]) self.B21.config( text = self.board[7]) self.B22.config( text = self.board[8]) #This code starts up TK and creates a main window. mainWin = Tk() #This code creates an instance of the TTT object. ttt = TTT( mainWin) #This line starts the main event handling loop and sets us on our way... mainWin.mainloop()