tic-tac-toe.py 6,59 ko
Newer Older
Apurv Bajaj's avatar
Apurv Bajaj a validé
from tkinter import *
import sys
import os.path
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from games import minimax_decision, alphabeta_player, random_player, TicTacToe
# "gen_state" can be used to generate a game state to apply the algorithm
from tests.test_games import gen_state

ttt = TicTacToe()
root = None
buttons = []
frames = []
x_pos = []
o_pos = []
count = 0
sym = ""
result = None
choices = None


def create_frames(root):
    """
    This function creates the necessary structure of the game.
    """
    frame1 = Frame(root)
    frame2 = Frame(root)
    frame3 = Frame(root)
    frame4 = Frame(root)
    create_buttons(frame1)
    create_buttons(frame2)
    create_buttons(frame3)
    buttonExit = Button(
        frame4, height=1, width=2,
        text="Exit",
        command=lambda: exit_game(root))
    buttonExit.pack(side=LEFT)
    frame4.pack(side=BOTTOM)
    frame3.pack(side=BOTTOM)
    frame2.pack(side=BOTTOM)
    frame1.pack(side=BOTTOM)
    frames.append(frame1)
    frames.append(frame2)
    frames.append(frame3)
    for x in frames:
        buttons_in_frame = []
        for y in x.winfo_children():
            buttons_in_frame.append(y)
        buttons.append(buttons_in_frame)
    buttonReset = Button(frame4, height=1, width=2,
                         text="Reset", command=lambda: reset_game())
    buttonReset.pack(side=LEFT)


def create_buttons(frame):
    """
    This function creates the buttons to be pressed/clicked during the game.
    """
    button0 = Button(frame, height=2, width=2, text=" ",
                     command=lambda: on_click(button0))
    button0.pack(side=LEFT)
    button1 = Button(frame, height=2, width=2, text=" ",
                     command=lambda: on_click(button1))
    button1.pack(side=LEFT)
    button2 = Button(frame, height=2, width=2, text=" ",
                     command=lambda: on_click(button2))
    button2.pack(side=LEFT)


# TODO: Add a choice option for the user.
def on_click(button):
    """
    This function determines the action of any button.
    """
    global ttt, choices, count, sym, result, x_pos, o_pos

    if count % 2 == 0:
        sym = "X"
    else:
        sym = "O"
    count += 1

    button.config(
        text=sym,
        state='disabled',
        disabledforeground="red")  # For cross

    x, y = get_coordinates(button)
    x += 1
    y += 1
    x_pos.append((x, y))
    state = gen_state(to_move='O', x_positions=x_pos,
                      o_positions=o_pos)
    try:
        choice = choices.get()
        if "Random" in choice:
            a, b = random_player(ttt, state)
        elif "Pro" in choice:
            a, b = minimax_decision(state, ttt)
        else:
            a, b = alphabeta_player(ttt, state)
    except (ValueError, IndexError, TypeError) as e:
        disable_game()
        result.set("It's a draw :|")
        return
    if 1 <= a <= 3 and 1 <= b <= 3:
        o_pos.append((a, b))
        button_to_change = get_button(a - 1, b - 1)
        if count % 2 == 0:  # Used again, will become handy when user is given the choice of turn.
            sym = "X"
        else:
            sym = "O"
        count += 1

        if check_victory(button):
            result.set("You win :)")
            disable_game()
        else:
            button_to_change.config(text=sym, state='disabled',
                                    disabledforeground="black")
            if check_victory(button_to_change):
                result.set("You lose :(")
                disable_game()


# TODO: Replace "check_victory" by "k_in_row" function.
def check_victory(button):
    """
    This function checks various winning conditions of the game.
    """
    # check if previous move caused a win on vertical line
    global buttons
    x, y = get_coordinates(button)
    tt = button['text']
    if buttons[0][y]['text'] == buttons[1][y]['text'] == buttons[2][y]['text'] != " ":
        buttons[0][y].config(text="|" + tt + "|")
        buttons[1][y].config(text="|" + tt + "|")
        buttons[2][y].config(text="|" + tt + "|")
        return True

    # check if previous move caused a win on horizontal line
    if buttons[x][0]['text'] == buttons[x][1]['text'] == buttons[x][2]['text'] != " ":
        buttons[x][0].config(text="--" + tt + "--")
        buttons[x][1].config(text="--" + tt + "--")
        buttons[x][2].config(text="--" + tt + "--")
        return True

    # check if previous move was on the main diagonal and caused a win
    if x == y and buttons[0][0]['text'] == buttons[1][1]['text'] == buttons[2][2]['text'] != " ":
        buttons[0][0].config(text="\\" + tt + "\\")
        buttons[1][1].config(text="\\" + tt + "\\")
        buttons[2][2].config(text="\\" + tt + "\\")
        return True

    # check if previous move was on the secondary diagonal and caused a win
    if x + \
            y == 2 and buttons[0][2]['text'] == buttons[1][1]['text'] == buttons[2][0]['text'] != " ":
        buttons[0][2].config(text="/" + tt + "/")
        buttons[1][1].config(text="/" + tt + "/")
        buttons[2][0].config(text="/" + tt + "/")
        return True

    return False


def get_coordinates(button):
    """
    This function returns the coordinates of the button clicked.
    """
    global buttons
    for x in range(len(buttons)):
        for y in range(len(buttons[x])):
            if buttons[x][y] == button:
                return x, y


def get_button(x, y):
    """
    This function returns the button memory location corresponding to a coordinate.
    """
    global buttons
    return buttons[x][y]


def reset_game():
    """
    This function will reset all the tiles to the initial null value.
    """
    global x_pos, o_pos, frames, count

    count = 0
    x_pos = []
    o_pos = []
    result.set("Your Turn!")
    for x in frames:
        for y in x.winfo_children():
            y.config(text=" ", state='normal')


def disable_game():
    """
    This function deactivates the game after a win, loss or draw.
    """
    global frames
    for x in frames:
        for y in x.winfo_children():
            y.config(state='disabled')


def exit_game(root):
    """
    This function will exit the game by killing the root.
    """
    root.destroy()


def main():
    global result, choices

    root = Tk()
    root.title("TicTacToe")
    root.resizable(0, 0)  # To remove the maximize window option
    result = StringVar()
    result.set("Your Turn!")
    w = Label(root, textvariable=result)
    w.pack(side=BOTTOM)
    create_frames(root)
    choices = StringVar(root)
    choices.set("Vs Pro")
    menu = OptionMenu(root, choices, "Vs Random", "Vs Pro", "Vs Legend")
    menu.pack()
    root.mainloop()


if __name__ == "__main__":
    main()