python tkinter showing X's and O's and alternating them

93 Views Asked by At
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import font as tkFont


#defines the function to switch to the game screen
def play_game():

    #creates a top level window (appears above all other windows when pressed)
    game_window = tk.Toplevel(win)

    #sets the name of the new window to "Tic-Tac-Toe Game"
    game_window.title("Tic-Tac-Toe Game")

    #sets the size of the game window when opened to 600x600
    game_window.geometry("600x600")

    #doesn't allow the main game window to be resized
    game_window.resizable(False, False)


    #creates a canvas to draw lines
    canvas = tk.Canvas(game_window, width=600, height=600)
    canvas.pack()

    #creates horizontal lines
    for i in range(1, 3):
        canvas.create_line(0, i * 200, 600, i * 200, fill="black")

    #creates vertical lines
    for i in range(1, 3):
        canvas.create_line(i * 200, 0, i * 200, 600, fill="black")


    global buttons[0][0]
    global buttons[0][1]
    
    buttons[0][0] = ttk.Button(game_window, text=" ", command=button_click, style='tmrn.TButton')
    buttons[0][1] = ttk.Button(game_window, text=" ", command=button_click, style='tmrn.TButton')



#creates a 3x3 grid of buttons
    buttons = [[None] * 3 for _ in range(3)] #creates a 3x3 grid of nothing so far
    for i in range(3):
        for j in range(3):
            #calculates the coordinates for each button
            x1 = j * 200
            y1 = i * 200
            x2 = (j + 1) * 200
            y2 = (i + 1) * 200
            #creates a button at position (i, j)
            buttons[i][j] = ttk.Button(game_window, command=lambda row=i, col=j: button_click(row, col))
            buttons[i][j].place(x=x1, y=y1, width=200, height=200)




def button_click(row, col):
    print(f"Button clicked at row {row}, column {col}")





    pass


#make another function which refers to button click event - draw X in that square
#create a flag for X's turn or O's turn and changes when a button is clicked
#on button press display X or O
#defines what to do when a button is clicked



#defines the function to open the user guide
def open_user_guide():
    #creates a top level window (appears above all other windows when pressed)
    guide_window = tk.Toplevel(win)

    #sets the name of the new window to "Tic-Tac-Toe Game"
    guide_window.title("User Guide")

    #sets the size of the game window when opened to 600x700
    guide_window.geometry("800x300")

    #doesn't allow the user guide window to be resized
    guide_window.resizable(False, False)

    #creates a frame to contain the labels
    frame = ttk.Frame(guide_window)
    frame.pack(expand=True, fill='both', padx=20, pady=20)

    #creates a new label with the text "How to play:" on the top of the window
    guide_label = ttk.Label(frame, text="User Guide", font=('Times New Roman', 20, 'bold'))
    guide_label.pack(pady=(0, 10), anchor='center')  #centres the label horizontally

    #creates a new text section 
    instructions_label = ttk.Label(frame, text="""
    Hi there! And welcome to my Tic Tac Toe game, made with tkinter.
    
    Here's how to play Tic-Tac-Toe! As you can see, the game is played on a grid that is 3 squares by 3 squares.

    1. The player that starts is X, and the other person is O. You and another player take turns placing X or O into a square.
    2. The first player to get all 3 marks in a row (horizontally, vertically or diagonally) wins the game.
    3. When all the squares are filled and no one has won, the game ends in a tie. AND THAT'S IT! Have fun playing!

    And press reset to play again!""",
    font=('Times New Roman', 12))
    instructions_label.pack(anchor='w')





win = tk.Tk()

#sets window size to 1000 x 600
win.geometry("1000x600")


#sets background image to a tictac
bg = tk.PhotoImage(file = "tictac.png")

#shows image using a label
backgroundimage = tk.Label(win, image=bg) 
backgroundimage.place(x = 0, y = 0)


#sets minimum window resizable size to 500 x 300
win.minsize(500, 300)

#doesn't allow the launcher window to be resized
win.resizable(False, False)

#sets title name of window to Tic-Tac-Toe (name of game)
win.title("Tic-Tac-Toe")

#integrates a new font into tkinter application
tmrn = tkFont.Font(family='Times New Roman', size=15, weight='bold')

#creates a custom style for buttons
style = ttk.Style()
style.configure('tmrn.TButton', font=('Times New Roman', 15, 'bold'), padding=(200, 10))  # Increase left and right padding to make the buttons longer


#creates a label with big black letters at the top of the window
title_label = ttk.Label(win, text="Tic-Tac-Toe Game!", font=('Times New Roman', 40, 'bold'), foreground='black')
title_label.pack(side=tk.TOP, pady=20)  # Add some padding at the top


#creates a button called 'Play' - takes the user to the actual playable game
play_button = ttk.Button(win, text="Play", command=play_game, style='tmrn.TButton')
play_button.place(relx=0.5, rely=0.6, anchor=tk.CENTER)  #positions the button just below the center

#creates a button called 'User Guide' - supposed to take the user to another window with how to play
guide_button = ttk.Button(win, text="User Guide", command=open_user_guide, style='tmrn.TButton')
guide_button.place(relx=0.5, rely=0.7, anchor=tk.CENTER)  #positions the button just below the "Play" button with some padding


win.mainloop()


#add a restart button to reset the game

This is my code so far, I'm trying to figure out how to display X's and O's when a button in the play_game window is clicked. For example, for the top left button, 'buttons[0][0]' should display an X if it is pressed first, and the next one whatever the user presses should be O, and so on and so forth but if a user has already clicked a square, the value of X or O cannot be changed. Right now I would just like to display an X and then an O when a button is clicked, but i'm running into a lot of errors.

i tried adding 2 buttons so far as an example, the top left and the top middle, that show blank spaces and it gives local error so i tried to define it, but i'm getting more errors.

2

There are 2 best solutions below

10
NULL On

So first the errors to get started, the globals aka global buttons[0][0] & global buttons[0][1], to keep it simple you use global to get a variable/function not so much to define it at least not on that line. It's a list of variables/functions in the global scope. Plus you don't need to define the buttons like this:

buttons[0][0] = ttk.Button(game_window, text=" ", command=button_click, style='tmrn.TButton')
buttons[0][1] = ttk.Button(game_window, text=" ", command=button_click, style='tmrn.TButton')

because you are already doing this in the for-loops. Anyways for the solution:

I pass the button through the command to later destroy the button. I also pass the parent object and I am using configure so I can access the button after it's defined.

button_tile.configure(command=lambda row=i, col=j, btn=button_tile, parent=game_window: button_click(row, col, btn, parent))

We will also need a bool to keep track of the turns but also switch between X/O

o_Turn = not o_Turn

make the bool into an X or an O:

Sign = ''
if o_Turn:
    Sign = '◯'

and you have something that looks like this:

    for i in range(3):
        for j in range(3):
            x1 = i * 200
            y1 = j * 200
            button_tile = ttk.Button(game_window)
            button_tile.place(x=x1, y=y1, width=200, height=200)
            button_tile.configure(command=lambda row=i, col=j, btn=button_tile, parent=game_window: button_click(row, col, btn, parent))

o_Turn = True
def button_click(row, col, button, parent):
   #print(f"Button clicked at row {row}, column {col}")
    
    global o_Turn
    Sign = ''
    if o_Turn:
        Sign = '◯'
    
    o_Turn = not o_Turn
    
    ttk.Label(parent, text=Sign, font=('Corbel', 50, tkFont.BOLD)).place(x=(row*200)+75, y=(col*200)+55)
    
    button.destroy()

hope it helps :)

0
acw1668 On

First the following lines inside play_game() will raise exception, so remove them:

def play_game():
    ...

    global buttons[0][0]
    global buttons[0][1]

    buttons[0][0] = ttk.Button(game_window, text=" ", command=button_click, style='tmrn.TButton')
    buttons[0][1] = ttk.Button(game_window, text=" ", command=button_click, style='tmrn.TButton')

    ...

Also suggest to initialize buttons once outside the function and create a variable player for the current player and initialize it to "X".

#creates a 3x3 grid of buttons
buttons = [[None] * 3 for _ in range(3)] #creates a 3x3 grid of nothing so far
# first player is X
player = "X"

#defines the function to switch to the game screen
def play_game():
    ...

Then update button_click() as below:

def button_click(row, col):
    global player
    print(f"Button clicked at row {row}, column {col}")
    # update button text for current player and then disable the button
    buttons[row][col].config(text=player, state="disabled")
    # toggle player
    player = "O" if player == "X" else "X"