learning / Python · June 25, 2023 0

Python Tic Tac Toe Using Tkinter

# import tkinter to create GUI
import tkinter as tk
# import random for robot moves
import random
# import messagebox for when someone wins
from tkinter import messagebox

# create game window
window = tk.Tk()
# the game is tic tac toe
window.title("Tic Tac Toe")

# status of the game
# X always starts
current_player = "X"
# create the board for tic tac toe
# using a 2-dimensional list would prevent a lot of confusing variables in the future.
board = [["" for _ in range(3)] for _ in range(3)]
game_mode = ""  # save the mode of the game. there are 2 modes, player vs player and player vs robot.

# scorboard for player x and player o. There are multi-dimensional dictionaries
player_stats = {"X": {"wins": 0, "losses": 0, "ties": 0}, "O": {"wins": 0, "losses": 0, "ties": 0}}


# function that handles button clicks for the board
def button_click(row, col):
    # current_player has to be global because it could be changed in this function.
    global current_player

    # check if position was occupied
    # if it isn't, then the click will be registered.
    if board[row][col] == "":
        # update the list board
        board[row][col] = current_player
        # update the actual display board, and show the colour as well.
        buttons[row][col].config(text=current_player, fg=get_player_color(current_player))
        # if the player is red, then it is blue's turn, and vice versa.
        if get_player_color(current_player) == "red":
            next_move_label.config(text=f"Next Move: O", font = ("Arial", 20), fg="blue")
        else:
            next_move_label.config(text=f"Next Move: X", font = ("Arial", 20), fg="red")

        # check if game was won
        if check_win(current_player):
            # show dialogue who won
            messagebox.showinfo("Game Over", "Player {} Won!".format(current_player))
            # update scoreboard
            player_stats[current_player]["wins"] += 1
            player_stats[get_other_player(current_player)]["losses"] += 1
            update_scoreboard()
            # reset the game for another round
            reset_game()
        # check if there is a tie to end the game if the board is full and nobody won
        elif check_tie():
            messagebox.showinfo("Game Over", "Tie!Let's play another round!")
            # update the stats
            player_stats[current_player]["ties"] += 1
            player_stats[get_other_player(current_player)]["ties"] += 1
            update_scoreboard()
            # reset the game for another round
            reset_game()
        else:
            # switch to next move. if it is x, it needs to be changed to o, and vice versa.
            # if it is against the robot and the current player is o, then the robot needs to move.
            current_player = get_other_player(current_player)
            if game_mode == "Player X Vs Robot O" and current_player == "O":
                play_robot_move()


# function to check if player/robot won
def check_win(player):
    # check rows
    for row in range(3):
        # check every row to see if everything on a row is the same
        if all(board[row][col] == player for col in range(3)):
            return True

    # check columns
    for col in range(3):
        # check every column to see if everything on a column is the same
        if all(board[row][col] == player for row in range(3)):
            return True
    # check every diagonal to see if everything on a diagonal is the same
    if board[0][0] == board[1][1] == board[2][2] == player:
        return True
    if board[0][2] == board[1][1] == board[2][0] == player:
        return True
    # nothing else is met, nobody won yet. the game continues
    return False


# check if tie
def check_tie():
    # see if every cell isn't empty and has either x or o.
    return all(all(cell != "" for cell in row) for row in board)


# function to reset the board
def reset_game():
    # the following variables will be changed in the function, so they need to be global
    global current_player, board
    
    # default player is x
    current_player = "X"
    # the initital status of the board has to be empty
    board = [["" for _ in range(3)] for _ in range(3)]

    # make game board blank
    for row in range(3):
        for col in range(3):
            # the default text needs to have a certain colour, even though it doesn't matter.
            # the default has no text
            buttons[row][col].config(text="", fg="black")
    next_move_label.config(text=f"Next Move: X", font = ("Arial", 20), fg="red")


# player vs robot mode,make robot move
def play_robot_move():
    # the robot randomly moves based off of remaining spaces
    # it moves randomly to provide people a chance to win
    
    # find a list of available moves
    available_moves = [(row, col) for row in range(3) for col in range(3) if board[row][col] == ""]
    # the robot instantly moves, so it is always the player's turn.
    next_move_label.config(text=f"Next Move: X", font = ("Arial", 20), fg="red")
    if available_moves:
        # randomly select an empty cell
        row, col = random.choice(available_moves)
        button_click(row, col)


# switch player
def get_other_player(player):
    if player == "X":
        return "O"
    return "X"

# get the player's colour
# I made each type to make the game easier to play and make it more fun
def get_player_color(player):
    if player == "X":
        return "red"  # player x is red
    elif player == "O":
        if game_mode == "Player X Vs Robot O":
            return "blue"  # the robot is blue
        else:
            return "blue"  # player o is blue


# function to update the scoreboard
def update_scoreboard():
    x_wins = player_stats["X"]["wins"]
    x_losses = player_stats["X"]["losses"]
    x_ties = player_stats["X"]["ties"]
    o_wins = player_stats["O"]["wins"]
    o_losses = player_stats["O"]["losses"]
    o_ties = player_stats["O"]["ties"]
    x_score_label.config(text=f"Player X: Wins {x_wins},Loses {x_losses},Ties {x_ties}")
    o_score_label.config(text=f"Player O: Wins {o_wins},Loses {o_losses},Ties {o_ties}")


# function to reset the scoreboard
def reset_scoreboard():
    player_stats["X"]["wins"] = 0
    player_stats["X"]["losses"] = 0
    player_stats["X"]["ties"] = 0
    player_stats["O"]["wins"] = 0
    player_stats["O"]["losses"] = 0
    player_stats["O"]["ties"] = 0
    update_scoreboard()


# function to select player vs player mode
def select_pvp_mode():
    # game_mode has to be changed, so it needs to be global
    global game_mode
    # change the mode to player vs player
    game_mode = "Player X Vs Player O"
    # change the text to say player vs player
    game_mode_label.config(text="Current Mode:Player X Vs Player O")
    # the game has to be reset
    reset_game()


# select player vs robot mode
def select_pvr_mode():
    # game_mode has to be changed, so it needs to be global
    global game_mode
    # change the mode to player vs robot
    game_mode = "Player X Vs Robot O"    
    # change the text to say player vs robot
    game_mode_label.config(text="Current Mode:Player X Vs Robot O")
    # the game has to be reset
    reset_game()


# create buttons
buttons = []
for row in range(3):
    row_buttons = []
    for col in range(3):
        # initially create the buttons
        # the font, width, height
        button = tk.Button(window, text="", font=("Arial", 40), width=6, height=3,
                           command=lambda r=row, c=col: button_click(r, c))
        # the space between the buttons
        button.grid(row=row, column=col, padx=5, pady=5)
        # add buttons
        row_buttons.append(button)
    # add buttons
    buttons.append(row_buttons)

# create the scoreboard
scoreboard_frame = tk.Frame(window)
scoreboard_frame.grid(row=0, column=3, rowspan=3, padx=10, pady=10)
# add the scoreboard title
game_mode_label = tk.Label(scoreboard_frame, text="Scoreboard", font=("Arial", 20))
game_mode_label.pack(pady=5)
# add the scoreboard
# player x is red to keep the colours the same
x_score_label = tk.Label(scoreboard_frame, text="Player X: Wins 0,Loses 0,Ties 0", font=("Arial", 12), fg="red")
x_score_label.pack(pady=5)
# player o is green to keep the colours the same
o_score_label = tk.Label(scoreboard_frame, text="Player O: Wins 0,Loses 0,Ties 0", font=("Arial", 12), fg="blue")
o_score_label.pack(pady=5)
# add a button that resets the scoreboard
reset_button = tk.Button(scoreboard_frame, text="Reset Scoreboard", font=("Arial", 12), command=reset_scoreboard)
reset_button.pack(pady=10)

# add a button to select game mode
game_mode_label = tk.Label(scoreboard_frame, text="Current Mode:Player X Vs Player O", font=("Arial", 12))
game_mode_label.pack(pady=5)
# add a button for player vs player mode
pvp_button = tk.Button(scoreboard_frame, text="Player X Vs Player O", font=("Arial", 12), command=select_pvp_mode)
pvp_button.pack(pady=5)
# add a button for player vs robot mode
pvr_button = tk.Button(scoreboard_frame, text="Player X Vs Robot O", font=("Arial", 12), command=select_pvr_mode)
pvr_button.pack(pady=5)

# Show who is supposed to move next
next_move_label = tk.Label(scoreboard_frame, text="Next Move: X", font=("Arial", 20), fg="red")
next_move_label.pack(pady=30)

# run the window
window.mainloop()

Copy and paste this code into a python IDE, and you got tic tac toe!