diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md index 7f29c26..befad05 100644 --- a/docs/USER_MANUAL.md +++ b/docs/USER_MANUAL.md @@ -11,12 +11,12 @@ - - - - - - + + + + + + > [!TIP] @@ -166,6 +166,9 @@ Pour jouer en réseau : 5. Entrez un nom de partie identique pour les deux joueurs 6. Le serveur attribuera automatiquement les rôles (Joueur 1 ou 2) +> [!NOTE] +> Serveur déployé sur `potel.dev` sur le port `5000` + > [!IMPORTANT] > Pour jouer en réseau sur Internet, assurez-vous que le port utilisé (par défaut 5000) est ouvert dans votre pare-feu et correctement redirigé si vous utilisez un routeur. diff --git a/src/isolation/bot.py b/src/isolation/bot.py index 0435838..537d989 100644 --- a/src/isolation/bot.py +++ b/src/isolation/bot.py @@ -1,6 +1,7 @@ import random -from typing import List, Tuple, Optional -from src.captures import is_threatened +from typing import List, Tuple, Optional, Dict, Set +import time +from src.captures import is_threatened, has_valid_move class IsolationBot: """ @@ -12,46 +13,75 @@ def __init__(self, player_id: int): params : player_id - l'identifiant du joueur (1 ou 2) """ - self.player_id = player_id - 1 + self.player_id = player_id - 1 self.opponent_id = 1 - self.player_id + self.time_limit = 1.0 - def get_valid_moves(self, board: List[List[List]], player: int) -> List[Tuple[int, int]]: + def get_valid_moves(self, board: List[List[List]]) -> List[Tuple[int, int]]: """ - fonction : retourne tous les coups valides pour un joueur + fonction : trouve tous les coups valides pour un joueur params : - board - l'état actuel du plateau - player - l'identifiant du joueur (0 ou 1) - retour : liste des positions (ligne, colonne) valides + board - plateau de jeu + retour : liste des positions valides """ valid_moves = [] - for row in range(len(board)): - for column in range(len(board[0])): - if board[row][column][0] is None and not is_threatened(board, row, column, 1 - player, check_all_pieces=True): - valid_moves.append((row, column)) + rows = len(board) + cols = len(board[0]) if rows > 0 else 0 + + for i in range(rows): + for j in range(cols): + if board[i][j][0] is None and not is_threatened(board, i, j, self.player_id, check_all_pieces=True): + valid_moves.append((i, j)) + return valid_moves + def evaluate_move(self, board: List[List[List]], move: Tuple[int, int]) -> int: + """ + fonction : évalue un coup en calculant combien de coups seront disponibles pour l'adversaire + params : + board - plateau de jeu + move - coup à évaluer (row, col) + retour : score du coup (plus petit = meilleur) + """ + row, col = move + board[row][col][0] = self.player_id + + opponent_moves = 0 + for i in range(len(board)): + for j in range(len(board[0])): + if board[i][j][0] is None and not is_threatened(board, i, j, self.opponent_id, check_all_pieces=True): + opponent_moves += 1 + + board[row][col][0] = None + + return -opponent_moves + def get_move(self, board: List[List[List]]) -> Optional[Tuple[int, int]]: """ fonction : détermine le meilleur coup à jouer params : - board - l'état actuel du plateau - retour : Position (ligne, colonne) du meilleur coup + board - plateau de jeu + retour : position (row, col) du meilleur coup, ou None s'il n'y a pas de coup valide """ - valid_moves = self.get_valid_moves(board, self.player_id) + start_time = time.time() + + valid_moves = self.get_valid_moves(board) if not valid_moves: return None - + if len(valid_moves) == 1: return valid_moves[0] - + + evaluated_moves = [] for move in valid_moves: - row, column = move - board[row][column][0] = self.player_id - opponent_moves = self.get_valid_moves(board, self.opponent_id) - board[row][column][0] = None + score = self.evaluate_move(board, move) + evaluated_moves.append((move, score)) - if len(opponent_moves) == 0: - return move - - return random.choice(valid_moves) \ No newline at end of file + if time.time() - start_time > self.time_limit: + break + + evaluated_moves.sort(key=lambda x: x[1], reverse=True) + + top_moves = [move for move, _ in evaluated_moves[:min(3, len(evaluated_moves))]] + return random.choice(top_moves) \ No newline at end of file diff --git a/src/isolation/game.py b/src/isolation/game.py index 8d27cff..e0d0020 100644 --- a/src/isolation/game.py +++ b/src/isolation/game.py @@ -170,7 +170,7 @@ def on_click(self, row: int, col: int) -> bool: # gestion du tour du bot if self.round_turn == 1 and self.game_mode == "Bot": self.render.edit_info_label("Bot is thinking...") - pygame.time.set_timer(pygame.USEREVENT, 500) # délai pour l'action du bot + pygame.time.set_timer(pygame.USEREVENT, 10) # délai pour l'action du bot self._bot_timer_set = True else: self.render.edit_info_label(f"Player {self.round_turn + 1}'s turn - Place your tower") @@ -221,7 +221,7 @@ def load_game(self) -> None: self.render.edit_info_label(f"Player {self.round_turn + 1}'s turn - Place your tower") self.render.run_game_loop() if getattr(self.render, "end_popup_action", None) == "play_again": - from src.windows.selector.selector import Selector + from src.windows.selector import Selector Selector() self.cleanup() diff --git a/src/windows/components/navbar/navbar.py b/src/windows/components/navbar.py similarity index 100% rename from src/windows/components/navbar/navbar.py rename to src/windows/components/navbar.py diff --git a/src/windows/screens/base_screen.py b/src/windows/screens/base_screen.py index be95e36..b9a408c 100644 --- a/src/windows/screens/base_screen.py +++ b/src/windows/screens/base_screen.py @@ -1,5 +1,5 @@ import pygame -from src.windows.components.navbar.navbar import NavBar +from src.windows.components.navbar import NavBar from src.utils.logger import Logger from src.utils.theme_manager import ThemeManager from src.windows.font_manager import FontManager diff --git a/src/windows/screens/game_config/game_config.py b/src/windows/screens/game_config/game_config.py index f296eed..3c3aea3 100644 --- a/src/windows/screens/game_config/game_config.py +++ b/src/windows/screens/game_config/game_config.py @@ -86,13 +86,11 @@ def setup_ui(self): """ procédure : configure l'interface utilisateur de l'écran. """ - padding = 20 element_spacing = 20 label_spacing = 10 current_y = self.navbar_height + 200 button_font = self.font_manager.get_font(30) - title_font = self.font_manager.get_font(24) self.font = self.font_manager.get_font(20) self.labels = [] diff --git a/src/windows/screens/network/create_game.py b/src/windows/screens/network/create_game.py index 6bc2dd8..a8675e8 100644 --- a/src/windows/screens/network/create_game.py +++ b/src/windows/screens/network/create_game.py @@ -60,13 +60,11 @@ def __init__(self): Logger.error("CreateGameScreen", "Failed to load quadrant configurations.") def setup_ui(self): - padding = 20 element_spacing = 20 label_spacing = 10 current_y = self.navbar_height + 200 button_font = self.font_manager.get_font(30) - title_font = self.font_manager.get_font(24) self.font = self.font_manager.get_font(20) self.labels = [] diff --git a/src/windows/screens/network/join_game.py b/src/windows/screens/network/join_game.py index 077533c..50c474b 100644 --- a/src/windows/screens/network/join_game.py +++ b/src/windows/screens/network/join_game.py @@ -40,7 +40,10 @@ def __init__(self): Logger.error("JoinGameScreen", "Failed to load quadrant configurations.") self.selected_quadrants = None - self.background = pygame.image.load('assets/pirate/background.png').convert() + theme = self.theme_manager.current_theme + self.background = pygame.image.load(f'assets/{theme}/background.png').convert() + Logger.info("JoinGameScreen", f"Using theme: {theme} for background") + self.icon_green_circle = pygame.image.load('assets/Basic_GUI_Bundle/ButtonsIcons/IconButton_Large_Green_Circle.png').convert_alpha() self.icon_red_circle = pygame.image.load('assets/Basic_GUI_Bundle/ButtonsIcons/IconButton_Large_Red_Circle.png').convert_alpha() @@ -69,25 +72,17 @@ def on_game_list_received(self, games): Logger.info("JoinGameScreen", f"Received {len(games)} games from server") def setup_ui(self): - try: - self.title_font = pygame.font.SysFont('Arial', 48, bold=True) - self.status_font = pygame.font.SysFont('Arial', 20, bold=True) - self.host_font = pygame.font.SysFont('Arial', 18) - self.button_font = pygame.font.SysFont('Arial', 18, bold=True) - self.footer_font = pygame.font.SysFont('Arial', 14) - except Exception as e: - Logger.error("JoinGameScreen", f"Font loading error: {str(e)}") - self.title_font = pygame.font.Font(None, 48) - self.status_font = pygame.font.Font(None, 20) - self.host_font = pygame.font.Font(None, 18) - self.button_font = pygame.font.Font(None, 18) - self.footer_font = pygame.font.Font(None, 14) + self.title_font = self.font_manager.get_font(48) + self.status_font = self.font_manager.get_font(20) + self.host_font = self.font_manager.get_font(18) + self.button_font = self.font_manager.get_font(18) + self.footer_font = self.font_manager.get_font(14) panel_width = int(self.width * 0.7) panel_height = int(self.height * 0.6) self.panel_x = (self.width - panel_width) // 2 - self.panel_y = self.navbar_height + 60 + self.panel_y = self.navbar_height + 120 self.panel_width = panel_width self.panel_height = panel_height @@ -298,8 +293,8 @@ def draw_screen(self): title_join = self.title_font.render("JOIN A GAME", True, (255, 255, 255)) title_network_x = (self.width - title_network.get_width()) // 2 title_join_x = (self.width - title_join.get_width()) // 2 - self.screen.blit(title_network, (title_network_x, self.panel_y - 90)) - self.screen.blit(title_join, (title_join_x, self.panel_y - 40)) + self.screen.blit(title_network, (title_network_x, self.panel_y - 110)) + self.screen.blit(title_join, (title_join_x, self.panel_y - 60)) self.refresh_button.draw(self.screen) diff --git a/src/windows/selector/quadrant_handler.py b/src/windows/selector/quadrant_handler.py index 0308a3f..62bedce 100644 --- a/src/windows/selector/quadrant_handler.py +++ b/src/windows/selector/quadrant_handler.py @@ -61,11 +61,7 @@ def draw_quadrant(self, screen: pygame.Surface, quadrant_data: List[List[List[Op x_offset (int): coordonnée x du coin supérieur gauche de la zone de dessin du quadrant. y_offset (int): coordonnée y du coin supérieur gauche de la zone de dessin du quadrant. cell_size (int): taille en pixels d'une cellule dans la prévisualisation. - """ - if quadrant_data is None: - Logger.error("QuadrantHandler", "Call draw_quadrant with quadrant_data to none.") - return - + """ for row_i, row in enumerate(quadrant_data): for col_i, cell_state in enumerate(row): x1 = x_offset + col_i * cell_size