import tkinter as tk from tkinter import messagebox # We can make a "virtual" game board as a list of lists. The list contains 3 other lists (rows). # Each row list contains 3 string values representing spaces. The spaces will be populated with # either "X" or "O", but we start with empty strings to denote an empty space. # # We can access a space on the board using row and column indexes. For example, the top-left space # is `board[0][0]`, the center space is `board[1][1]`, and the bottom-right space is `board[2][2]`. board = [ ["", "", ""], ["", "", ""], ["", "", ""] ] # We'll also store the buttons in a similiar way. This list will be auto-populated by make_button() buttons = [] # # Game Window Setup # # Now we create our window and give it a title. We'll store this window in a variable called # "game", so we can access it later. game = tk.Tk() game.title("Tic Tac Toe") # Add a message area (to display text to the user). Use `message.set(text)` to change the message. message = tk.StringVar() message_area = tk.Label(game, textvariable=message, font="Times 20 bold", pady=20) message_area.grid(row=0) # Add a "holder" for our buttons button_holder = tk.Frame(game) button_holder.grid(row=1) # # Function Definitions: # def check_victory(row, col): """ Checks to see if there's a winner based on the last-played postion """ # check if previous move caused a win on vertical line if board[0][col] == board[1][col] == board[2][col]: return True # check if previous move caused a win on horizontal line if board[row][0] == board[row][1] == board[row][2]: return True # check if previous move was on the main diagonal and caused a win if row == col and board[0][0] == board[1][1] == board[2][2]: return True # check if previous move was on the secondary diagonal and caused a win if row + col == 2 and board[0][2] == board[1][1] == board[2][0]: return True return False def make_move(row, col): """ Makes a move for the current player (whether human or computer). This function returns `True` if the move results in a win, otherwise it returns `False`. """ # If the clicked space is not already occupied, set it to the current player if not board[row][col]: # Set "X" or "O" on our virtual game board board[row][col] = game.current_player # Change the button text to the current player (X or O) button = buttons[row][col] button.configure(text=game.current_player) # Check if there's a winner, otherwise, switch the current player if check_victory(row, col): messagebox.showinfo("Game over!", "The winner is: " + game.current_player + "!") new_game() return True else: if game.current_player == "X": game.current_player = "O" else: game.current_player = "X" message.set(game.current_player + "'s move") return False def on_click(event): """ This function gets called any time a button is clicked. Each button has a `row` and `col` attribute, which we can pass to the `make_move()` function. """ clicked_button = event.widget row = clicked_button.row col = clicked_button.col # The `make_move()` function returns `True` if it results in a win. # We store that result in a variable so we can use it in the next step. is_winning_move = make_move(row, col) if not is_winning_move: # Wait 1 second before calling `ai_move()` to make it look like the computer is "thinking". game.after(1000, ai_move) def new_game(): """ Clears the game board and starts a new game """ # re-set the buttons list buttons.clear() # Remove existing button widgets for widget in button_holder.winfo_children(): widget.destroy() # Fill the window with new buttons for row in range(0, 3): buttons.append([]) for col in range(0, 3): button = make_button(row, col) buttons[row].append(button) board[row][col] = "" # X player goes first game.current_player = "X" message.set(game.current_player + "'s move") def make_button(row, col): """ Creates a button in the given space """ button = tk.Button(button_holder, text=" ", font="Times 20 bold", bg="gray", fg="white", height=4, width=8) button.row = row button.col = col button.grid(row=row, column=col, stick=tk.S + tk.N + tk.E + tk.W) # Call on_click() when the button is clicked button.bind("<Button-1>", on_click) # return the button object for use later on return button def ai_move(): """ This function contains the AI for the computer to make a move on behalf of the current player. """ # "DUMB" ALGORITHM: for row in range(0, 3): for col in range(0, 3): if board[row][col] == "": make_move(row, col) # by returning here, we stop the function entirely (no more looping) return # The rest of the code is called immediately when the script starts: new_game() # Start the app game.mainloop()