From 88fa0dea62b862d0bd73fe39b4a0b8722aa95a50 Mon Sep 17 00:00:00 2001 From: Rutuparn Date: Mon, 2 Nov 2020 18:23:38 +0530 Subject: [PATCH 1/3] Python GUI added --- py_version/compute.py | 256 +++++++++++++++++++++++++++++++++++ py_version/gui.py | 307 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 563 insertions(+) create mode 100644 py_version/compute.py create mode 100644 py_version/gui.py diff --git a/py_version/compute.py b/py_version/compute.py new file mode 100644 index 0000000..f51b970 --- /dev/null +++ b/py_version/compute.py @@ -0,0 +1,256 @@ +# File Name: compute.py +# Description: Provides game algorithm for Cross & Nut game +# +# Written by Rutuparn Pawar (InputBlackBoxOutput) +# Created on 5 Sept 2019 +# Last modified on 2 Nov 2020 + +import random, time, minimax + +class Tic_Tac_Toe: + + def __init__(self): + # self.grid_map = [['O', 'X', 'X'], ['O', 'X', 'O'], ['O', 'O', 'O']] # Use for testing purpose + self.grid_map = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']] + self.cross_nut_map = {'cross': 'X', 'empty': ' ', 'nut': 'O'} + self.winner_string = None + + """Function to place cross/nut in grid if place is empty""" + + def place_cross_nut(self, x, y, place): + if place == 'cross': + if self.grid_map[x][y] == ' ': + self.grid_map[x][y] = self.cross_nut_map[place] + # Cross placed at ({x},{y}) + return True + else: + # Cross cannot be placed at ({x},{y}) since place is not empty + return False + elif place == 'nut': + if self.grid_map[x][y] == ' ': + self.grid_map[x][y] = self.cross_nut_map[place] + # Nut placed at ({x},{y}) + return True + else: + # Nut cannot be placed at ({x},{y}) since place is not empty + return False + else: + self.grid_map[x][y] = self.cross_nut_map['empty'] + + """Function to check if the player/computer has won""" + + def winner_check(self, player): + + # Check for 3 cross/nut in a row + for x in range(0, 3): + win_count = 0 + for y in range(0, 3): + if self.cross_nut_map[player] == self.grid_map[x][y]: + win_count = win_count + 1 + if win_count == 3: + # print('Complete Row', end='\n\n') + return 'Winner is ' + player + + # Check for 3 cross/nut in a column + for y in range(0, 3): + win_count = 0 + for x in range(0, 3): + if self.cross_nut_map[player] == self.grid_map[x][y]: + win_count = win_count + 1 + if win_count == 3: + # print('Complete column', end='\n\n') + return 'Winner is ' + player + + # Check for 3 cross/nut across diagonals + win_count = 0 + for i in range(0, 3): + if self.cross_nut_map[player] == self.grid_map[i][i]: + win_count = win_count + 1 + if win_count == 3: + # print('Complete Diagonal \\', end='\n\n') + return 'Winner is ' + player + + win_count = 0 + + if self.cross_nut_map[player] == self.grid_map[0][2]: + win_count = win_count + 1 + if self.cross_nut_map[player] == self.grid_map[1][1]: + win_count = win_count + 1 + if self.cross_nut_map[player] == self.grid_map[2][0]: + win_count = win_count + 1 + + if win_count == 3: + # print('Complete Diagonal /', end='\n\n') + return 'Winner is ' + player + + return None + + '''Function to check if ''' + + def game_tied(self): + if self.winner_string is None: + for row in range(0, 3): + if self.cross_nut_map['empty'] in self.grid_map[row]: + return False + return True + + '''Function to clear grid_map''' + + def clear_cross_nut(self): + for each_r in range(0, 3): + for each_c in range(0, 3): + self.grid_map[each_r][each_c] = self.cross_nut_map['empty'] + + """ Function to display grid on console for testing """ + + def display_for_testing(self): + index = [0, 1, 2] + for x in index: + for y in index: + print(self.grid_map[x][y], end=' ') + print() + + """ Function to run gameloop in console for testing """ + + def gameloop_for_testing(self): + # print("Tic Tac Toe") + # print('@Attention - !!!!! GRID MAY BEEN PREDEFINED !!!!!') + + while self.winner_string is None: + + self.display_for_testing() + # print("Player X") + x = input("Enter x coordinate :") + y = input("Enter y coordinate :") + self.place_cross_nut(int(x), int(y), 'cross') + self.winner_string = self.winner_check('cross') + + if self.winner_string is not None: + break + + self.display_for_testing() + + # print("Player O") + x = input("Enter x coordinate :") + y = input("Enter y coordinate :") + self.place_cross_nut(int(x), int(y), 'nut') + self.winner_string = self.winner_check('nut') + + self.display_for_testing() + # print(self.winner_string) + + return + + # Player is always cross hence begins the game + + """"Function to generate random unoccupied position""" + def random_position(self): + position = [-1, -1] + found_position = True + + retries = 0 + while found_position: + x = random.randint(0, 2) + y = random.randint(0, 2) + if self.grid_map[x][y] == self.cross_nut_map['empty']: + position[0] = x + position[1] = y + found_position = False + + retries = retries + 1 + if retries == 25: + print("Error: random_position tries exceeded") + break + + return position + + """Function to get vacant grid positions""" + def get_vacant_pos(self): + vacant = [] + for r in range(0,3): + for c in range(0,3): + if self.grid_map[r][c] == self.cross_nut_map['empty']: + vacant.append((r, c)) + return vacant + + """Function to get computer's move in a game vs computer""" + # Uses hand-crafted rules for AI + def bot_move(self): + if not self.game_tied() and self.winner_string is None: + # Wait for some time + time.sleep(0.1) + random.seed(random.randint(20, 120)) + # 75% chance of best move + chance = random.randint(0, 3) + + if chance > 0 : + best_move = False + # Check if winning move possible + for each in self.get_vacant_pos(): + self.grid_map[each[0]][each[1]] = self.cross_nut_map['nut'] + + if self.winner_check('nut') is None: + self.grid_map[each[0]][each[1]] = self.cross_nut_map['empty'] + else: + best_move = True + break + + if best_move: + return + + # Check if blocking move possible + for each in self.get_vacant_pos(): + self.grid_map[each[0]][each[1]] = self.cross_nut_map['cross'] + + if self.winner_check('cross') is None: + self.grid_map[each[0]][each[1]] = self.cross_nut_map['empty'] + else: + self.grid_map[each[0]][each[1]] = self.cross_nut_map['nut'] + best_move = True + break + if best_move: + return + else: + position = self.random_position() + self.place_cross_nut(position[0], position[1], 'nut') + + # Play random move + if chance == 0: + position = self.random_position() + self.place_cross_nut(position[0], position[1], 'nut') + + def bot_move_minimax(self): + for i in range(3): + for j in range(3): + if self.grid_map[i][j] == self.cross_nut_map['cross']: + minimax.board[i][j] = -1 + + elif self.grid_map[i][j] == self.cross_nut_map['nut']: + minimax.board[i][j] = 1 + + else: + minimax.board[i][j] = 0 + + + depth = len(minimax.empty_cells(minimax.board)) + if depth == 0 or minimax.game_over(minimax.board): + return + + if depth == 9: + x = minimax.choice([0, 1, 2]) + y = minimax.choice([0, 1, 2]) + else: + move = minimax.minimax(minimax.board, depth, minimax.COMP) + x, y = move[0], move[1] + + self.place_cross_nut(x, y, 'nut') + + +# For testing game vs person +# game=Tic_Tac_Toe() +# game.gameloop_for_testing() + +# For testing game vs computer +# game.display_for_testing() +# game.bot_move() +# game.display_for_testing() \ No newline at end of file diff --git a/py_version/gui.py b/py_version/gui.py new file mode 100644 index 0000000..99d8fe8 --- /dev/null +++ b/py_version/gui.py @@ -0,0 +1,307 @@ +# File Name:Tic_Tac_Toe.py +# Description:Provides graphical user interface (GUI) for Cross & Nut game. +# Built using python's inbuilt tkinter module +# +# Written by Rutuparn Pawar (InputBlackBoxOutput) +# Created on 29 Sept 2019 +# Last modified 1 Nov 2020 + +import os + +from tkinter import * +import tkinter.messagebox as msgbox + +from compute import * + +class GUI(Tk): + def __init__(self, background, font_size): + super().__init__() + self.board = Tic_Tac_Toe() + + self.title("Cross & Nut") + self.geometry("410x420") + self.wm_resizable(width=False, height=False) + # self.wm_iconbitmap('path') Add .ico file to path + + self.bkgnd = background + self.configure(bg=background) + self.font_size = font_size + + self.move = 'cross' # Cross plays first + self.bot = True # Default: Game vs computer + + # Binding functions for menu bar + def Vs_computer(self): + self.board.clear_cross_nut() + self.update_board() + self.remove_mark() + self.bot = True + self.move = 'cross' + self.board.winner_string = None + self.status.configure(text="Match versus computer") + # print('Beginning game vs computer') + + def Vs_player(self): + self.board.clear_cross_nut() + self.update_board() + self.remove_mark() + self.bot = False + self.board.winner_string = None + self.move = 'cross' + self.status.configure(text="") + # print('Beginning game vs person') + + def how_to_play(self): + try: + with open(os.path.join(sys.path[0], "help.txt"), "rt") as help_file: + msgbox.showinfo('HOW TO PLAY', help_file.read()) + except FileNotFoundError: + print("File not found!") + + def about(self): + try: + with open(os.path.join(sys.path[0], "about.txt"), "r") as about_file: + msgbox.showinfo('ABOUT', about_file.read()) + except FileNotFoundError: + print("File not found!") + + '''Fxn to build menu bar''' + def menu_bar(self): + self.menu = Menu(self) + self.menu.add_command(label='One player', command=self.Vs_computer) + self.menu.add_command(label='Two player', command=self.Vs_player) + self.menu.add_command(label='How to play', command=self.how_to_play) + self.menu.add_command(label='About', command=self.about) + self.menu.add_command(label='Close', command=quit) + self.config(menu=self.menu) + + '''Fxn to build label to display winner''' + def heading_label(self, padding): + self.win_label = Label(self, text="Let's play Tic Tac Toe!", font="lucida 16 bold", padx=padding, + pady=round(padding / 5)) + self.win_label.pack(side=TOP, fill=X) + Label(self, bg=self.bkgnd).pack(side=TOP) + + # Helper functions for playing grid + def update_board(self): + each_button = 0 + for each_r in range(0, 3): + for each_c in range(0, 3): + new_text = self.board.grid_map[each_r][each_c] + self.b_list[each_button].configure(text=new_text, font=f"calibri {self.font_size - 1} bold") + each_button = each_button + 1 + + ''' If there is no winner notify the user and begin the game again ''' + def no_winner(self): + if self.board.game_tied(): + msgbox.showinfo('No winner', "Looks like there is no winner.") + self.board.clear_cross_nut() + self.remove_mark() + self.update_board() + + + '''Helper function for button_pressed function''' + def toggle_move(self): + if self.move == 'cross': + self.move = 'nut' + else: + self.move = 'cross' + + def whose_move(self): + if self.move == 'cross': + return 'Nut' + else: + return'Cross' + + def button_pressed(self, button, x, y): + # Don't do anything if there is a winner + if self.board.winner_string is not None : + return + + # Do something if there is no winner + # print(f'Button {button} pressed') + + stat = self.board.place_cross_nut(x, y, self.move) + + if self.bot is False: + if stat: + self.update_board() + self.board.winner_string = self.board.winner_check(self.move) + self.status.configure(text=f"{self.whose_move()}'s turn ") + self.toggle_move() + + if self.board.winner_string is not None: + self.status.configure(text=self.board.winner_string) + self.mark() + return + self.no_winner() + else: + if stat: + self.board.bot_move_minimax() + self.update_board() + + self.board.winner_string = self.board.winner_check('nut') or self.board.winner_check('cross') + if self.board.winner_string is not None: + self.status.configure(text=self.board.winner_string) + self.mark() + + # Binding functions for playing grid + def b0(self): + self.button_pressed(0, 0, 0) + def b1(self): + self.button_pressed(1, 0, 1) + def b2(self): + self.button_pressed(2, 0, 2) + def b3(self): + self.button_pressed(3, 1, 0) + def b4(self): + self.button_pressed(4, 1, 1) + def b5(self): + self.button_pressed(5, 1, 2) + def b6(self): + self.button_pressed(6, 2, 0) + def b7(self): + self.button_pressed(7, 2, 1) + def b8(self): + self.button_pressed(8, 2, 2) + + '''Fxn to build playing grid''' + def play_grid(self, padding): + self.canvas = Canvas(self, bg=self.bkgnd) + self.grid_map = Frame(self.canvas, bg='grey') + self.canvas.pack(fill=BOTH) + + # Draw line after game has a winner (Not working!) + # Looks like drawing on canvas does not get superimposed on button widget + #self.canvas.create_line(0, 0, 1000, 1000, fill="red") + + # Generate 9 button widgets + self.b_list = [] + for each in range(0, 9): + self.b_list.append( + Button(self.grid_map, text=' ', font=f"calibri {self.font_size} bold", padx=padding, pady=padding)) + + # Place 9 button widgets in a grid + each = 0 + for r in range(0, 3): + for c in range(0, 3): + self.b_list[each].grid(row=r, column=c) + each = each + 1 + + # Mapping buttons in grid to functions + self.b_list[0].configure(command=self.b0) + self.b_list[1].configure(command=self.b1) + self.b_list[2].configure(command=self.b2) + + self.b_list[3].configure(command=self.b3) + self.b_list[4].configure(command=self.b4) + self.b_list[5].configure(command=self.b5) + + self.b_list[6].configure(command=self.b6) + self.b_list[7].configure(command=self.b7) + self.b_list[8].configure(command=self.b8) + + self.grid_map.pack(side=BOTTOM) + + self.update_board() + + '''Fxn to build status bar''' + def status_bar(self, padding): + self.status = Label(self, text="Developed by Rutuparn Pawar", font='calibri 12 normal', borderwidth=1, + relief=SUNKEN, anchor='s', pady=padding) + self.status.pack(side=BOTTOM, fill=X) + Label(self, bg=self.bkgnd).pack(side=BOTTOM) + + '''Fxn to remove mark row/column/diagonal''' + def remove_mark(self): + for each_button in self.b_list: + each_button.configure(bg='#F0FF0FF0F') + + '''Fxn to mark row/column/diagonal''' + def mark(self): + self.remove_mark() + + for player in ['cross', 'nut']: + # Check for 3 cross/nut in a row + for x in range(0, 3): + win_count = 0 + for y in range(0, 3): + if self.board.cross_nut_map[player] == self.board.grid_map[x][y]: + win_count = win_count + 1 + if win_count == 3: + if x == 0: + self.b_list[0].configure(bg='#AFFFAF') + self.b_list[1].configure(bg='#AFFFAF') + self.b_list[2].configure(bg='#AFFFAF') + break + elif x == 1: + self.b_list[3].configure(bg='#AFFFAF') + self.b_list[4].configure(bg='#AFFFAF') + self.b_list[5].configure(bg='#AFFFAF') + break + elif x == 2: + self.b_list[6].configure(bg='#AFFFAF') + self.b_list[7].configure(bg='#AFFFAF') + self.b_list[8].configure(bg='#AFFFAF') + break + + # Check for 3 cross/nut in a column + for y in range(0, 3): + win_count = 0 + for x in range(0, 3): + if self.board.cross_nut_map[player] == self.board.grid_map[x][y]: + win_count = win_count + 1 + if win_count == 3: + if y == 0: + self.b_list[0].configure(bg='#AFFFAF') + self.b_list[3].configure(bg='#AFFFAF') + self.b_list[6].configure(bg='#AFFFAF') + break + elif y == 1: + self.b_list[1].configure(bg='#AFFFAF') + self.b_list[4].configure(bg='#AFFFAF') + self.b_list[7].configure(bg='#AFFFAF') + break + elif y == 2: + self.b_list[2].configure(bg='#AFFFAF') + self.b_list[5].configure(bg='#AFFFAF') + self.b_list[8].configure(bg='#AFFFAF') + break + + # Check for 3 cross/nut across diagonals + win_count = 0 + for i in range(0, 3): + if self.board.cross_nut_map[player] == self.board.grid_map[i][i]: + win_count = win_count + 1 + if win_count == 3: + self.b_list[0].configure(bg='#AFFFAF') + self.b_list[4].configure(bg='#AFFFAF') + self.b_list[8].configure(bg='#AFFFAF') + return + + win_count = 0 + if self.board.cross_nut_map[player] == self.board.grid_map[0][2]: + win_count = win_count + 1 + if self.board.cross_nut_map[player] == self.board.grid_map[1][1]: + win_count = win_count + 1 + if self.board.cross_nut_map[player] == self.board.grid_map[2][0]: + win_count = win_count + 1 + + if win_count == 3: + self.b_list[2].configure(bg='#AFFFAF') + self.b_list[4].configure(bg='#AFFFAF') + self.b_list[6].configure(bg='#AFFFAF') + return + + + +# //////////////////////////////////////////////////////////////////////////////////////////// +if __name__ == "__main__": + print("Please minimize this window.") + window = GUI(background='grey', font_size=10) + window.menu_bar() + window.heading_label(padding=2) + window.status_bar(padding=2) + window.play_grid(padding=40) + window.remove_mark() + window.mainloop() \ No newline at end of file From d7742a56440f30587e3d542af0f0f31e422836a4 Mon Sep 17 00:00:00 2001 From: Rutuparn Date: Mon, 2 Nov 2020 18:27:05 +0530 Subject: [PATCH 2/3] Screenshots added --- preview/tkinter-gui1.png | Bin 0 -> 17742 bytes preview/tkinter-gui2.png | Bin 0 -> 17496 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 preview/tkinter-gui1.png create mode 100644 preview/tkinter-gui2.png diff --git a/preview/tkinter-gui1.png b/preview/tkinter-gui1.png new file mode 100644 index 0000000000000000000000000000000000000000..53a739512cd21c65ded0eb7e46dee501352ffa64 GIT binary patch literal 17742 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 Date: Mon, 2 Nov 2020 18:46:26 +0530 Subject: [PATCH 3/3] Screenshot added to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 036f112..daa6ad8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # tic-tac-toe-minimax An implementation of Minimax AI Algorithm on Tic-Tac-Toe (or Noughts and Crosses) game. Try it: [Tic-tac-toe - Minimax](https://cledersonbc.github.io/tic-tac-toe-minimax/) +Alternatively, if you have python interpreter, get the repo and run **gui.py** from the py_version directory +

@@ -150,6 +152,8 @@ def minimax(state, depth, player): return best ``` +## GUI Screenshot +![GUI Screenshot 2](preview/tkinter-gui2.png) ## Game Tree Below, the best move is on the middle because the max value is on 2nd node on left.