commit c8414ae30424a58218e30f7d393532cb0f8ba01b Author: leo.tuaillon Date: Mon Apr 24 23:13:35 2023 +0200 v1 ai morpion diff --git a/TicTacToe.py b/TicTacToe.py new file mode 100644 index 0000000..14f56e2 --- /dev/null +++ b/TicTacToe.py @@ -0,0 +1,259 @@ +import sys +import pygame +import numpy as np +import random +import copy +import time + +from constants import * + +# Initialize pygame +pygame.init() +screen = pygame.display.set_mode((WIDTH, LENGHT)) +pygame.display.set_caption("Tic Tac Toe Game - AI ") +screen.fill( BACKGROUND_COLOR ) + +#Class : Matrix +class Matrix: + def __init__(self): + self.cases = np.zeros((GRID_ROWS, GRID_COLS)) + self.empty_cases = self.cases + self.marked_case = 0 + + + def winer(self): + ''' + Check if there is a winner + return 0 if there is no winner yet + return 1 if player 1 win + return 2 if player 2 win + ''' + + # Check vertical lines + for column in range(GRID_COLS): + if self.cases[0][column] == self.cases[1][column] == self.cases[2][column] != 0: + return self.cases[0][column] + # Check horizontal lines + for row in range(GRID_ROWS): + if self.cases[row][0] == self.cases[row][1] == self.cases[row][2] != 0: + return self.cases[row][0] + + # Check diagonals + if self.cases[0][0] == self.cases[1][1] == self.cases[2][2] != 0: + return self.cases[0][0] + if self.cases[0][2] == self.cases[1][1] == self.cases[2][0] != 0: + return self.cases[0][2] + # Check if there is no winner yet + return 0 + + def empty_case(self, row,column): + return self.cases[row][column] == 0 + + def mark_case(self, row, column, player): + self.cases[row][column] = player + self.marked_case += 1 + + def get_empty_cases(self): + empty_cases = [] + for row in range(GRID_ROWS): + for column in range(GRID_COLS): + if self.empty_case(row, column): + empty_cases.append((row, column)) + return empty_cases + + def isfull(self): + return self.marked_case == 9 + + def isempty(self): + return self.marked_case == 0 + +#Class : AI +class AI: + + def __init__(self, level= 5, player=2): + self.level = level + self.player = player + + def randomM(self,matrix): + empty_cases = matrix.get_empty_cases() + a = random.randrange(0,len(empty_cases)) + + return empty_cases[a] # (row, column) + + def minimax(self, matrix, maximizing): + + #Check if terminal case + case = matrix.winer() + #player 1 win + if case == 1: + return 1, None # eval, move + #player 2 win + if case == 2: + return -1, None # eval, move + #draw + elif matrix.isfull(): + return 0, None # eval, move + + if maximizing: + max_eval = -100 + best_move = None + empty_cases = matrix.get_empty_cases() + + for (row,column) in empty_cases: + temp_matrix = copy.deepcopy(matrix) + temp_matrix.mark_case(row, column, 1) + eval = self.minimax(temp_matrix, False)[0] + if eval > max_eval: + max_eval = eval + best_move = (row, column) + + return max_eval, best_move # eval, move + + elif not maximizing: + min_eval = 100 + best_move = None + empty_cases = matrix.get_empty_cases() + + for (row,column) in empty_cases: + temp_matrix = copy.deepcopy(matrix) + temp_matrix.mark_case(row, column, self.player) + eval = self.minimax(temp_matrix, True)[0] + if eval < min_eval: + min_eval = eval + best_move = (row, column) + + return min_eval, best_move # eval, move + + + def evaluate(self, main_matrix): + if self.level == 0: + #random choice + eval = 'random' + move = self.randomM(main_matrix) + else: + #minimax algo choice + print("AI is thinking...") + time.sleep(1) + eval, move = self.minimax(main_matrix, False) + + print(f"AI has chosen the pos {move} with an eval of {eval}") + + return move # (row, column) + +#Class : Game +class Game: + def __init__(self): + self.matrix = Matrix() + self.ai = AI() + self.player = 1 #1 = X, 2 = O + self.gamemode = 'ai' #1v1 or 1vAI + self.running = True + self.lines() + + def draw_figures(self, row, column): + if self.player == 1: + #draw X + start_desc = (column * CASE_SIZE + CASE_SIZE // 4, row * CASE_SIZE + CASE_SIZE // 4) + end_desc = (column * CASE_SIZE + CASE_SIZE * 3 // 4, row * CASE_SIZE + CASE_SIZE * 3 // 4) + pygame.draw.line(screen,PLAYER1_COLOR, start_desc, end_desc, CROSS_WIDTH) + start_asc = (column * CASE_SIZE + CASE_SIZE // 4, row * CASE_SIZE + CASE_SIZE * 3 // 4) + end_asc = (column * CASE_SIZE + CASE_SIZE * 3 // 4, row * CASE_SIZE + CASE_SIZE // 4) + pygame.draw.line(screen,PLAYER1_COLOR, start_asc, end_asc, CROSS_WIDTH) + elif self.player == 2: + #draw O + center = ( column * CASE_SIZE + CASE_SIZE // 2, row * CASE_SIZE + CASE_SIZE // 2) + pygame.draw.circle(screen, PLAYER2_COLOR,center, CIRCLE_RADIUS, CIRCLE_WIDTH) + + def make_move(self, row, col): + self.matrix.mark_case(row, col, self.player) + self.draw_figures(row, col) + self.player_turn() + + def player_turn(self): + self.player = self.player % 2 + 1 + + def lines(self): + # Horizontal lines + pygame.draw.line(screen, LINE_COLOR, (0, CASE_SIZE), (WIDTH,CASE_SIZE), LINE_WIDTH) + pygame.draw.line(screen, LINE_COLOR, (0, LENGHT - CASE_SIZE), (WIDTH, LENGHT - CASE_SIZE), LINE_WIDTH) + + #Vertical lines + pygame.draw.line(screen, LINE_COLOR, (CASE_SIZE, 0), (CASE_SIZE, LENGHT), LINE_WIDTH) + pygame.draw.line(screen, LINE_COLOR, (WIDTH - CASE_SIZE, 0), (WIDTH - CASE_SIZE, LENGHT), LINE_WIDTH) + + def change_gamemode(self): + if self.gamemode == 'ai': + self.gamemode = '1v1' + else: + self.gamemode = 'ai' + + def isover(self): + return self.matrix.winer() != 0 or self.matrix.isfull() + + def reset(self): + self.__init__() + screen.fill(BACKGROUND_COLOR) + self.lines() + +def main(): + + # Object of Game class + game = Game() + matrix = game.matrix + ai = game.ai + + # Main loop + while True: + + for event in pygame.event.get(): + + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + if event.type == pygame.KEYDOWN: + + # g-gamemode + if event.key == pygame.K_g: + game.change_gamemode() + + #r-restart + if event.key == pygame.K_r: + game.reset() + matrix = game.matrix + ai = game.ai + + # 0-random ai + if event.key == pygame.K_0: + ai.level = 0 + + # 1-easy ai + if event.key == pygame.K_1: + ai.level = 1 + + #click event + if event.type == pygame.MOUSEBUTTONDOWN: + pos = event.pos + row = pos[1] // CASE_SIZE + column = pos[0] // CASE_SIZE + + if matrix.empty_case(row,column) and game.running: + game.make_move(row, column) + + if game.isover(): + game.running = False + + if game.gamemode == 'ai' and game.player == ai.player and game.running: + # update the screen + pygame.display.update() + + # AI turn + row, column = ai.evaluate(matrix) + game.make_move(row, column) + + if game.isover(): + game.running = False + + pygame.display.update() +main() + diff --git a/__pycache__/constants.cpython-310.pyc b/__pycache__/constants.cpython-310.pyc new file mode 100644 index 0000000..598e2be Binary files /dev/null and b/__pycache__/constants.cpython-310.pyc differ diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..0b44428 --- /dev/null +++ b/constants.py @@ -0,0 +1,25 @@ + +#Window +WIDTH = 800 +LENGHT = 800 + +GRID_ROWS = 3 +GRID_COLS = 3 + +CASE_SIZE = WIDTH // GRID_COLS + +LINE_WIDTH = 15 + +# Circle +CIRCLE_RADIUS = CASE_SIZE // 4 +CIRCLE_WIDTH = 15 + +# Cross +CROSS_WIDTH = 25 + +# Colors (RGB) + +BACKGROUND_COLOR = (46, 98, 163) +LINE_COLOR = (255,205, 0) +PLAYER2_COLOR = (98,195,248) +PLAYER1_COLOR = (215, 0, 0) \ No newline at end of file