From 8d1ce8e27e5449c9e787c507392844c3e241e933 Mon Sep 17 00:00:00 2001 From: Krasto Date: Sun, 7 Jan 2024 12:20:27 +0000 Subject: [PATCH] wip minmax --- ci_quixo/custom_game.py | 4 ++- ci_quixo/minmax.py | 72 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 ci_quixo/minmax.py diff --git a/ci_quixo/custom_game.py b/ci_quixo/custom_game.py index 934a086..9d3bd71 100644 --- a/ci_quixo/custom_game.py +++ b/ci_quixo/custom_game.py @@ -101,6 +101,8 @@ def from_canon(canon: "CustomGame", idx: int) -> "CustomGame": symmetries = canon.symmetries() return CustomGame.from_str(symmetries[idx]) + def from_game(game: "Game") -> "CustomGame": + return CustomGame.from_board(game.get_board(), game.get_current_player()) def __hash__(self) -> str: return str(self).__hash__() @@ -109,7 +111,7 @@ def __eq__(self, other: "CustomGame") -> bool: return self.__hash__() == other.__hash__() def valid_moves(self, player: int) -> tuple[CompleteMove]: - return [it for it in POSSIBLE_MOVES if self._board[it.position] == -1 or self._board[it.position] == player] + return [it for it in POSSIBLE_MOVES if self._board[it.position[::-1]] == -1 or self._board[it.position[::-1]] == player] if __name__ == "__main__": diff --git a/ci_quixo/minmax.py b/ci_quixo/minmax.py new file mode 100644 index 0000000..032d298 --- /dev/null +++ b/ci_quixo/minmax.py @@ -0,0 +1,72 @@ +from game import Player, Game +from custom_game import CustomGame +from typing import TYPE_CHECKING +import numpy as np +from copy import deepcopy +import random + +if TYPE_CHECKING: + from custom_game import CompleteMove + +class MinMaxPlayer(Player): + + def __init__(self, max_depth: int = None, use_alpha_beta_pruning: bool = False) -> None: + super().__init__() + + self.max_depth = max_depth + self.use_alpha_beta_pruning = use_alpha_beta_pruning + self._init_ab() + + def _init_ab(self): + self._alpha, self._beta = -np.inf, np.inf + + + def make_move(self, game: Game) -> "CompleteMove": + cg = CustomGame.from_game(game) + best_move = self._minmax(0, cg, True)[1] + if best_move is None: + best_move = random.choice(cg.valid_moves()) + print("I made a move...") + return best_move + + def _minmax(self, depth: int, game: "CustomGame", maximixe: bool) -> tuple[float, "CompleteMove"]: + winner = game.check_winner() + if winner != -1: + return 25 * winner, None + if self.max_depth is not None and depth >= self.max_depth: + return 0, None + + if depth == 0: + self._init_ab() + + best_move = None + if maximixe: + for move in game.valid_moves(game.get_current_player()): + copied = deepcopy(game) + assert copied._Game__move(*move, copied.current_player_idx), f"Somehow got an invalid move while iterating from valid moves, {copied}, {move}" + score, _ = self._minmax(depth+1, copied, False) + if score > self._alpha: + self._alpha = score + best_move = move + + if self.use_alpha_beta_pruning and self._alpha > self._beta: + break + return self._alpha, best_move + else: + for move in game.valid_moves(game.get_current_player()): + copied = deepcopy(game) + assert copied._Game__move(*move, copied.current_player_idx), "Somehow got an invalid move while iterating from valid moves" + score, _ = self._minmax(depth+1, copied, True) + if score < self._beta: + self._beta = score + best_move = move + + if self.use_alpha_beta_pruning and self._alpha > self._beta: + break + return self._beta, best_move + +if __name__ == "__main__": + from main import RandomPlayer + mm = MinMaxPlayer(20, True) + rp = RandomPlayer() + game = Game() \ No newline at end of file