pyrat.src.PygameRenderingEngine
This file is part of the PyRat library. It is meant to be used as a library, and not to be executed directly.
Please import necessary elements using the following syntax:
from pyrat import
1##################################################################################################################################################### 2######################################################################## INFO ####################################################################### 3##################################################################################################################################################### 4 5""" 6 This file is part of the PyRat library. 7 It is meant to be used as a library, and not to be executed directly. 8 Please import necessary elements using the following syntax: 9 from pyrat import <element_name> 10""" 11 12##################################################################################################################################################### 13###################################################################### IMPORTS ###################################################################### 14##################################################################################################################################################### 15 16# External imports 17from typing import * 18from typing_extensions import * 19from numbers import * 20import copy 21import multiprocessing 22import os 23import glob 24import distinctipy 25import math 26import random 27import time 28import queue 29 30# PyRat imports 31from pyrat.src.RenderingEngine import RenderingEngine 32from pyrat.src.Player import Player 33from pyrat.src.Maze import Maze 34from pyrat.src.GameState import GameState 35 36##################################################################################################################################################### 37###################################################################### CLASSES ###################################################################### 38##################################################################################################################################################### 39 40class PygameRenderingEngine (RenderingEngine): 41 42 """ 43 This class inherits from the RenderingEngine class. 44 Therefore, it has the attributes and methods defined in the RenderingEngine class in addition to the ones defined below. 45 46 This rendering engine uses the pygame library to render the game. 47 It will create a window and display the game in it. 48 The window will run in a different process than the one running the game. 49 """ 50 51 ############################################################################################################################################# 52 # MAGIC METHODS # 53 ############################################################################################################################################# 54 55 def __init__ ( self: Self, 56 fullscreen: bool = False, 57 trace_length: Integral = 0, 58 gui_speed: Number = 1.0, 59 *args: Any, 60 **kwargs: Any 61 ) -> Self: 62 63 """ 64 This function is the constructor of the class. 65 When an object is instantiated, this method is called to initialize the object. 66 This is where you should define the attributes of the object and set their initial values. 67 Arguments *args and **kwargs are used to pass arguments to the parent constructor. 68 This is useful not to declare again all the parent's attributes in the child class. 69 In: 70 * self: Reference to the current object. 71 * fullscreen: Indicates if the GUI should be fullscreen. 72 * trace_length: Length of the trace to display. 73 * gui_speed: Speed of the GUI. 74 * args: Arguments to pass to the parent constructor. 75 * kwargs: Keyword arguments to pass to the parent constructor. 76 Out: 77 * A new instance of the class. 78 """ 79 80 # Inherit from parent class 81 super().__init__(*args, **kwargs) 82 83 # Debug 84 assert isinstance(fullscreen, bool) # Type check for fullscreen 85 assert isinstance(trace_length, Integral) # Type check for trace_length 86 assert isinstance(gui_speed, Number) # Type check for gui_speed 87 assert trace_length >= 0 # trace_length must be positive 88 assert gui_speed > 0 # gui_speed must be positive 89 90 # Private attributes 91 self.__fullscreen = fullscreen 92 self.__trace_length = trace_length 93 self.__gui_speed = gui_speed 94 self.__gui_process = None 95 self.__gui_queue = None 96 97 ############################################################################################################################################# 98 # PUBLIC METHODS # 99 ############################################################################################################################################# 100 101 @override 102 def render ( self: Self, 103 players: List[Player], 104 maze: Maze, 105 game_state: GameState, 106 ) -> None: 107 108 """ 109 This method redefines the method of the parent class. 110 This function renders the game to a Pyame window. 111 The window is created in a different process than the one running the game. 112 In: 113 * self: Reference to the current object. 114 * players: Players of the game. 115 * maze: Maze of the game. 116 * game_state: State of the game. 117 Out: 118 * None. 119 """ 120 121 # Debug 122 assert isinstance(players, list) # Type check for players 123 assert all(isinstance(player, Player) for player in players) # Type check for players 124 assert isinstance(maze, Maze) # Type check for maze 125 assert isinstance(game_state, GameState) # Type check for game_state 126 127 # Initialize the GUI in a different process at turn 0 128 if game_state.turn == 0: 129 130 # Initialize the GUI process 131 gui_initialized_synchronizer = multiprocessing.Manager().Barrier(2) 132 self.__gui_queue = multiprocessing.Manager().Queue() 133 self.__gui_process = multiprocessing.Process(target=_gui_process_function, args=(gui_initialized_synchronizer, self.__gui_queue, maze, game_state, players, self.__fullscreen, self._render_simplified, self.__trace_length, self.__gui_speed)) 134 self.__gui_process.start() 135 gui_initialized_synchronizer.wait() 136 137 # At each turn, send current info to the process 138 else: 139 self.__gui_queue.put(game_state) 140 141 ############################################################################################################################################# 142 143 @override 144 def end ( self: Self, 145 ) -> None: 146 147 """ 148 This method redefines the method of the parent class. 149 It waits for the window to be closed before exiting. 150 In: 151 * self: Reference to the current object. 152 Out: 153 * None. 154 """ 155 156 # Wait for GUI to be exited to quit if there is one 157 if self.__gui_process is not None: 158 self.__gui_process.join() 159 160##################################################################################################################################################### 161##################################################################### FUNCTIONS ##################################################################### 162##################################################################################################################################################### 163 164def _gui_process_function ( gui_initialized_synchronizer: multiprocessing.Barrier, 165 gui_queue: multiprocessing.Queue, 166 maze: Maze, 167 initial_game_state: GameState, 168 players: List[Player], 169 fullscreen: bool, 170 render_simplified: bool, 171 trace_length: Integral, 172 gui_speed: Number 173 ) -> None: 174 175 """ 176 This function is executed in a separate process for the GUI. 177 It handles rendering in a Pygame environment. 178 It is defined outside of the class due to multiprocessing limitations. 179 In: 180 * gui_initialized_synchronizer: Barrier to synchronize the initialization of the GUI. 181 * gui_queue: Queue to receive the game state. 182 * maze: Maze of the game. 183 * initial_game_state: Initial game state. 184 * players: Players of the game. 185 * fullscreen: Indicates if the GUI should be fullscreen. 186 * render_simplified: Indicates if the GUI should be simplified. 187 * trace_length: Length of the trace to display. 188 * gui_speed: Speed of the GUI. 189 Out: 190 * None. 191 """ 192 193 # Debug 194 assert isinstance(gui_initialized_synchronizer, multiprocessing.managers.BarrierProxy) # Type check for gui_initialized_synchronizer 195 assert isinstance(gui_queue, multiprocessing.managers.BaseProxy) # Type check for gui_queue 196 assert isinstance(maze, Maze) # Type check for maze 197 assert isinstance(initial_game_state, GameState) # Type check for initial_game_state 198 assert isinstance(players, list) # Type check for players 199 assert all(isinstance(player, Player) for player in players) # Type check for players 200 assert isinstance(fullscreen, bool) # Type check for fullscreen 201 assert isinstance(render_simplified, bool) # Type check for render_simplified 202 assert isinstance(trace_length, Integral) # Type check for trace_length 203 assert isinstance(gui_speed, Number) # Type check for gui_speed 204 assert trace_length >= 0 # trace_length must be positive 205 assert gui_speed > 0.0 # gui_speed must be positive 206 207 # We catch exceptions that may happen during the game 208 try: 209 210 # Initialize PyGame 211 # Imports are done here to avoid multiple initializations in multiprocessing 212 os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" 213 import pygame 214 import pygame.locals as pglocals 215 pygame.init() 216 pygame.mixer.init() 217 218 # Random number generator 219 rng = random.Random() 220 221 # Start screen 222 if fullscreen: 223 gui_screen = pygame.display.set_mode((0, 0), pygame.NOFRAME) 224 pygame.display.toggle_fullscreen() 225 else: 226 gui_screen = pygame.display.set_mode((int(pygame.display.Info().current_w * 0.8), int(pygame.display.Info().current_h * 0.8)), pygame.SCALED) 227 228 # We will store elements to display 229 maze_elements = [] 230 avatar_elements = [] 231 player_elements = {} 232 cheese_elements = {} 233 234 # Parameters of the GUI 235 window_width, window_height = pygame.display.get_surface().get_size() 236 cell_size = int(min(window_width / maze.width, window_height / maze.height) * 0.9) 237 background_color = (0, 0, 0) 238 cell_text_color = (50, 50, 50) 239 cell_text_offset = int(cell_size * 0.1) 240 wall_size = cell_size // 7 241 mud_text_color = (185, 155, 60) 242 corner_wall_ratio = 1.2 243 flag_size = int(cell_size * 0.4) 244 flag_x_offset = int(cell_size * 0.2) 245 flag_x_next_offset = int(cell_size * 0.07) 246 flag_y_offset = int(cell_size * 0.3) 247 game_area_width = cell_size * maze.width 248 game_area_height = cell_size * maze.height 249 maze_x_offset = int((window_width - game_area_width) * 0.9) 250 maze_y_offset = (window_height - game_area_height) // 2 251 avatars_x_offset = window_width - maze_x_offset - game_area_width 252 avatars_area_width = maze_x_offset - 2 * avatars_x_offset 253 avatars_area_height = min(game_area_height // 2, (game_area_height - (len(initial_game_state.teams) - 1) * maze_y_offset) // len(initial_game_state.teams)) 254 avatars_area_border = 2 255 avatars_area_angle = 10 256 avatars_area_color = (255, 255, 255) 257 teams_enabled = len(initial_game_state.teams) > 1 or len(list(initial_game_state.teams.keys())[0]) > 0 258 if teams_enabled: 259 avatars_area_padding = avatars_area_height // 13 260 team_text_size = avatars_area_padding * 3 261 colors = distinctipy.distinctipy.get_colors(len(initial_game_state.teams)) 262 team_colors = {list(initial_game_state.teams.keys())[i]: tuple([int(c * 255) for c in colors[i]]) for i in range(len(initial_game_state.teams))} 263 else: 264 avatars_area_padding = avatars_area_height // 12 265 team_text_size = 0 266 avatars_area_height -= avatars_area_padding * 3 267 team_colors = {list(initial_game_state.teams.keys())[i]: avatars_area_color for i in range(len(initial_game_state.teams))} 268 player_avatar_size = avatars_area_padding * 3 269 player_avatar_horizontal_padding = avatars_area_padding * 4 270 player_name_text_size = avatars_area_padding 271 cheese_score_size = avatars_area_padding 272 text_size = int(cell_size * 0.17) 273 cheese_size = int(cell_size * 0.4) 274 player_size = int(cell_size * 0.5) 275 flag_border_color = (255, 255, 255) 276 flag_border_width = 1 277 player_border_width = 2 278 cheese_border_color = (255, 255, 0) 279 cheese_border_width = 1 280 cheese_score_border_color = (100, 100, 100) 281 cheese_score_border_width = 1 282 trace_size = wall_size // 2 283 animation_steps = int(max(cell_size / gui_speed, 1)) 284 animation_time = 0.01 285 medal_size = min(avatars_x_offset, maze_y_offset) * 2 286 icon_size = 50 287 main_image_factor = 0.8 288 main_image_border_color = (0, 0, 0) 289 main_image_border_size = 1 290 go_image_duration = 0.5 291 292 # Function to load an image with some scaling 293 # If only 2 arguments are provided, scales keeping ratio specifying the maximum size 294 # If first argument is a directory, returns a random image from it 295 already_loaded_images = {} 296 def ___surface_from_image (file_or_dir_name, target_width_or_max_size, target_height=None): 297 full_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), file_or_dir_name) 298 if os.path.isdir(full_path): 299 full_path = rng.choice(glob.glob(os.path.join(full_path, "*"))) 300 loaded_image_key = str(full_path) + "_" + str(target_width_or_max_size) + "_" + str(target_height) 301 if loaded_image_key in already_loaded_images: 302 return already_loaded_images[loaded_image_key] 303 surface = pygame.image.load(full_path).convert_alpha() 304 if target_height is None: 305 max_surface_size = max(surface.get_width(), surface.get_height()) 306 surface = pygame.transform.scale(surface, (surface.get_width() * target_width_or_max_size // max_surface_size, surface.get_height() * target_width_or_max_size // max_surface_size)) 307 else: 308 surface = pygame.transform.scale(surface, (target_width_or_max_size, target_height)) 309 already_loaded_images[loaded_image_key] = surface 310 return surface 311 312 # Same function for text 313 def ___surface_from_text (text, target_height, text_color, original_font_size=50): 314 surface = pygame.font.SysFont(None, original_font_size).render(text, True, text_color) 315 surface = pygame.transform.scale(surface, (surface.get_width() * target_height // surface.get_height(), target_height)) 316 return surface 317 318 # Function to colorize an object 319 def ___colorize (surface, color): 320 final_surface = surface.copy() 321 color_surface = pygame.Surface(final_surface.get_size()).convert_alpha() 322 color_surface.fill(color) 323 final_surface.blit(color_surface, (0, 0), special_flags=pygame.BLEND_MULT) 324 return final_surface 325 326 # Function to add a colored border around an object 327 def ___add_color_border (surface, border_color, border_size, final_rescale=True): 328 final_surface = pygame.Surface((surface.get_width() + 2 * border_size, surface.get_height() + 2 * border_size)).convert_alpha() 329 final_surface.fill((0, 0, 0, 0)) 330 mask_surface = surface.copy() 331 color_surface = pygame.Surface(mask_surface.get_size()) 332 color_surface.fill((0, 0, 0, 0)) 333 mask_surface.blit(color_surface, (0, 0), special_flags=pygame.BLEND_MIN) 334 color_surface.fill(border_color) 335 mask_surface.blit(color_surface, (0, 0), special_flags=pygame.BLEND_MAX) 336 for offset_x in range(-border_size, border_size + 1): 337 for offset_y in range(-border_size, border_size + 1): 338 if math.dist([0, 0], [offset_x, offset_y]) <= border_size: 339 final_surface.blit(mask_surface, (border_size // 2 + offset_x, border_size // 2 + offset_y)) 340 final_surface.blit(surface, (border_size // 2, border_size // 2)) 341 if final_rescale: 342 final_surface = pygame.transform.scale(final_surface, surface.get_size()) 343 return final_surface 344 345 # Function to load the surfaces of a player 346 def ___load_player_surfaces (player_skin, scale, border_color=None, border_width=None, add_border=teams_enabled): 347 try: 348 player_neutral = ___surface_from_image(os.path.join("..", "gui", "players", player_skin.value, "neutral.png"), scale) 349 player_north = ___surface_from_image(os.path.join("..", "gui", "players", player_skin.value, "north.png"), scale) 350 player_south = ___surface_from_image(os.path.join("..", "gui", "players", player_skin.value, "south.png"), scale) 351 player_west = ___surface_from_image(os.path.join("..", "gui", "players", player_skin.value, "west.png"), scale) 352 player_east = ___surface_from_image(os.path.join("..", "gui", "players", player_skin.value, "east.png"), scale) 353 if add_border: 354 player_neutral = ___add_color_border(player_neutral, border_color, border_width) 355 player_north = ___add_color_border(player_north, border_color, border_width) 356 player_south = ___add_color_border(player_south, border_color, border_width) 357 player_west = ___add_color_border(player_west, border_color, border_width) 358 player_east = ___add_color_border(player_east, border_color, border_width) 359 return player_neutral, player_north, player_south, player_west, player_east 360 except: 361 return ___load_player_surfaces("default", scale, border_color, border_width, add_border) 362 363 # Function to play a sound 364 def ___play_sound (file_name, alternate_file_name=None): 365 sound_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), file_name) 366 if not os.path.exists(sound_file): 367 sound_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), alternate_file_name) 368 sound = pygame.mixer.Sound(sound_file) 369 channel = pygame.mixer.find_channel() 370 channel.play(sound) 371 372 # Function to load the avatar of a player 373 def ___load_player_avatar (player_skin, scale): 374 try: 375 return ___surface_from_image(os.path.join("..", "gui", "players", player_skin.value, "avatar.png"), scale) 376 except: 377 return ___load_player_avatar("default", scale) 378 379 # Function to get the main color of a surface 380 def ___get_main_color (surface): 381 colors = pygame.surfarray.array2d(surface) 382 counts = {color: 0 for color in set(colors.flatten())} 383 for color in colors.flatten(): 384 counts[color] += 1 385 max_occurrences = sorted(counts, key=lambda x: counts[x], reverse=True)[:2] 386 main_color = surface.unmap_rgb(max_occurrences[0]) 387 if main_color == (0, 0, 0, 0): 388 main_color = surface.unmap_rgb(max_occurrences[1]) 389 return main_color 390 391 # Set window icon and title 392 icon = ___surface_from_image(os.path.join("..", "gui", "icon", "pyrat.png"), icon_size) 393 pygame.display.set_icon(icon) 394 pygame.display.set_caption("PyRat") 395 396 # Set background color 397 pygame.draw.rect(gui_screen, background_color, pygame.Rect(0, 0, window_width, window_height)) 398 399 # Add cells 400 for row in range(maze.height): 401 for col in range(maze.width): 402 if maze.rc_exists(row, col): 403 cell = ___surface_from_image(os.path.join("..", "gui", "ground"), cell_size, cell_size) 404 cell = pygame.transform.rotate(cell, rng.randint(0, 3) * 90) 405 cell = pygame.transform.flip(cell, bool(rng.randint(0, 1)), bool(rng.randint(0, 1))) 406 cell_x = maze_x_offset + col * cell_size 407 cell_y = maze_y_offset + row * cell_size 408 maze_elements.append((cell_x, cell_y, cell)) 409 410 # Add mud 411 mud = ___surface_from_image(os.path.join("..", "gui", "mud", "mud.png"), cell_size) 412 for row in range(maze.height): 413 for col in range(maze.width): 414 if maze.rc_exists(row, col): 415 if maze.rc_exists(row, col - 1): 416 if maze.has_edge(maze.rc_to_i(row, col), maze.rc_to_i(row, col - 1)): 417 if maze.get_weight(maze.rc_to_i(row, col), maze.rc_to_i(row, col - 1)) > 1: 418 mud_x = maze_x_offset + col * cell_size - mud.get_width() // 2 419 mud_y = maze_y_offset + row * cell_size 420 maze_elements.append((mud_x, mud_y, mud)) 421 if not render_simplified: 422 weight_text = ___surface_from_text(str(maze.get_weight(maze.rc_to_i(row, col), maze.rc_to_i(row, col - 1))), text_size, mud_text_color) 423 weight_text_x = maze_x_offset + col * cell_size - weight_text.get_width() // 2 424 weight_text_y = maze_y_offset + row * cell_size + (cell_size - weight_text.get_height()) // 2 425 maze_elements.append((weight_text_x, weight_text_y, weight_text)) 426 if maze.rc_exists(row - 1, col): 427 if maze.has_edge(maze.rc_to_i(row, col), maze.rc_to_i(row - 1, col)): 428 if maze.get_weight(maze.rc_to_i(row, col), maze.rc_to_i(row - 1, col)) > 1: 429 mud_horizontal = pygame.transform.rotate(mud, 90) 430 mud_x = maze_x_offset + col * cell_size 431 mud_y = maze_y_offset + row * cell_size - mud.get_width() // 2 432 maze_elements.append((mud_x, mud_y, mud_horizontal)) 433 if not render_simplified: 434 weight_text = ___surface_from_text(str(maze.get_weight(maze.rc_to_i(row, col), maze.rc_to_i(row - 1, col))), text_size, mud_text_color) 435 weight_text_x = maze_x_offset + col * cell_size + (cell_size - weight_text.get_width()) // 2 436 weight_text_y = maze_y_offset + row * cell_size - weight_text.get_height() // 2 437 maze_elements.append((weight_text_x, weight_text_y, weight_text)) 438 439 # Add cell numbers 440 if not render_simplified: 441 for row in range(maze.height): 442 for col in range(maze.width): 443 if maze.rc_exists(row, col): 444 cell_text = ___surface_from_text(str(maze.rc_to_i(row, col)), text_size, cell_text_color) 445 cell_text_x = maze_x_offset + col * cell_size + cell_text_offset 446 cell_text_y = maze_y_offset + row * cell_size + cell_text_offset 447 maze_elements.append((cell_text_x, cell_text_y, cell_text)) 448 449 # Add walls 450 walls = [] 451 wall = ___surface_from_image(os.path.join("..", "gui", "wall", "wall.png"), cell_size) 452 for row in range(maze.height + 1): 453 for col in range(maze.width + 1): 454 case_outside_to_inside = not maze.rc_exists(row, col) and maze.rc_exists(row, col - 1) 455 case_inside_to_outside = maze.rc_exists(row, col) and not maze.rc_exists(row, col - 1) 456 case_inside_to_inside = maze.rc_exists(row, col) and maze.rc_exists(row, col - 1) and not maze.has_edge(maze.rc_to_i(row, col), maze.rc_to_i(row, col - 1)) 457 if case_outside_to_inside or case_inside_to_outside or case_inside_to_inside: 458 wall_x = maze_x_offset + col * cell_size - wall.get_width() // 2 459 wall_y = maze_y_offset + row * cell_size 460 maze_elements.append((wall_x, wall_y, wall)) 461 walls.append((row, col, row, col - 1)) 462 case_outside_to_inside = not maze.rc_exists(row, col) and maze.rc_exists(row - 1, col) 463 case_inside_to_outside = maze.rc_exists(row, col) and not maze.rc_exists(row - 1, col) 464 case_inside_to_inside = maze.rc_exists(row, col) and maze.rc_exists(row - 1, col) and not maze.has_edge(maze.rc_to_i(row, col), maze.rc_to_i(row - 1, col)) 465 if case_outside_to_inside or case_inside_to_outside or case_inside_to_inside: 466 wall_horizontal = pygame.transform.rotate(wall, 90) 467 wall_x = maze_x_offset + col * cell_size 468 wall_y = maze_y_offset + row * cell_size - wall.get_width() // 2 469 maze_elements.append((wall_x, wall_y, wall_horizontal)) 470 walls.append((row, col, row - 1, col)) 471 472 # Add corners 473 corner = ___surface_from_image(os.path.join("..", "gui", "wall", "corner.png"), int(wall.get_width() * corner_wall_ratio), int(wall.get_width() * corner_wall_ratio)) 474 for row, col, neighbor_row, neighbor_col in walls: 475 if col != neighbor_col: 476 corner_x = maze_x_offset + col * cell_size - corner.get_width() // 2 477 if (row - 1, col, neighbor_row - 1, neighbor_col) not in walls or ((neighbor_row, neighbor_col, neighbor_row - 1, neighbor_col) in walls and (row, col, row - 1, col) in walls and (row - 1, col, neighbor_row - 1, neighbor_col) in walls): 478 corner_y = maze_y_offset + row * cell_size - corner.get_width() // 2 479 maze_elements.append((corner_x, corner_y, corner)) 480 if (row + 1, col, neighbor_row + 1, neighbor_col) not in walls: 481 corner_y = maze_y_offset + (row + 1) * cell_size - corner.get_width() // 2 482 maze_elements.append((corner_x, corner_y, corner)) 483 if row != neighbor_row: 484 corner_y = maze_y_offset + row * cell_size - corner.get_width() // 2 485 if (row, col - 1, neighbor_row, neighbor_col - 1) not in walls: 486 corner_x = maze_x_offset + col * cell_size - corner.get_width() // 2 487 maze_elements.append((corner_x, corner_y, corner)) 488 if (row, col + 1, neighbor_row, neighbor_col + 1) not in walls: 489 corner_x = maze_x_offset + (col + 1) * cell_size - corner.get_width() // 2 490 maze_elements.append((corner_x, corner_y, corner)) 491 492 # Add flags 493 if not render_simplified: 494 cells_with_flags = {cell: {} for cell in initial_game_state.player_locations.values()} 495 for player in players: 496 team = [team for team in initial_game_state.teams if player.name in initial_game_state.teams[team]][0] 497 if team not in cells_with_flags[initial_game_state.player_locations[player.name]]: 498 cells_with_flags[initial_game_state.player_locations[player.name]][team] = 0 499 cells_with_flags[initial_game_state.player_locations[player.name]][team] += 1 500 flag = ___surface_from_image(os.path.join("..", "gui", "flag", "flag.png"), flag_size) 501 max_teams_in_cells = max([len(team) for team in cells_with_flags.values()]) 502 max_players_in_cells = max([cells_with_flags[cell][team] for cell in cells_with_flags for team in cells_with_flags[cell]]) 503 for cell in cells_with_flags: 504 row, col = maze.i_to_rc(cell) 505 for i_team in range(len(cells_with_flags[cell])): 506 team = list(cells_with_flags[cell].keys())[i_team] 507 flag_colored = ___colorize(flag, team_colors[team]) 508 flag_colored = ___add_color_border(flag_colored, flag_border_color, flag_border_width) 509 for i_player in range(cells_with_flags[cell][team]): 510 flag_x = maze_x_offset + (col + 1) * cell_size - flag_x_offset - i_player * min(flag_x_next_offset, (cell_size - flag_x_offset) / (max_players_in_cells + 1)) 511 flag_y = maze_y_offset + row * cell_size - flag.get_height() + flag_y_offset + i_team * min(flag_y_offset, (cell_size - flag_y_offset) / (max_teams_in_cells + 1)) 512 maze_elements.append((flag_x, flag_y, flag_colored)) 513 514 # Add cheese 515 cheese = ___surface_from_image(os.path.join("..", "gui", "cheese", "cheese.png"), cheese_size) 516 cheese = ___add_color_border(cheese, cheese_border_color, cheese_border_width) 517 for c in initial_game_state.cheese: 518 row, col = maze.i_to_rc(c) 519 cheese_x = maze_x_offset + col * cell_size + (cell_size - cheese.get_width()) // 2 520 cheese_y = maze_y_offset + row * cell_size + (cell_size - cheese.get_height()) // 2 521 cheese_elements[c] = (cheese_x, cheese_y, cheese) 522 523 # Add players 524 for player in players: 525 team = [team for team in initial_game_state.teams if player.name in initial_game_state.teams[team]][0] 526 player_neutral, player_north, player_south, player_west, player_east = ___load_player_surfaces(player.skin, player_size, team_colors[team], player_border_width) 527 row, col = maze.i_to_rc(initial_game_state.player_locations[player.name]) 528 player_x = maze_x_offset + col * cell_size + (cell_size - player_neutral.get_width()) // 2 529 player_y = maze_y_offset + row * cell_size + (cell_size - player_neutral.get_height()) // 2 530 player_elements[player.name] = (player_x, player_y, player_neutral, player_north, player_south, player_west, player_east) 531 532 # Add avatars area 533 score_locations = {} 534 medal_locations = {} 535 for i in range(len(initial_game_state.teams)): 536 537 # Box 538 team = list(initial_game_state.teams.keys())[i] 539 team_background = pygame.Surface((avatars_area_width, avatars_area_height)) 540 pygame.draw.rect(team_background, background_color, pygame.Rect(0, 0, avatars_area_width, avatars_area_height)) 541 pygame.draw.rect(team_background, team_colors[team], pygame.Rect(0, 0, avatars_area_width, avatars_area_height), avatars_area_border, avatars_area_angle) 542 team_background_x = avatars_x_offset 543 team_background_y = (1 + i) * maze_y_offset + i * avatars_area_height if len(initial_game_state.teams) > 1 else (window_height - avatars_area_height) // 2 544 avatar_elements.append((team_background_x, team_background_y, team_background)) 545 medal_locations[team] = (team_background_x + avatars_area_width, team_background_y) 546 547 # Team name 548 team_text = ___surface_from_text(team, team_text_size, team_colors[team]) 549 if team_text.get_width() > avatars_area_width - 2 * avatars_area_padding: 550 ratio = (avatars_area_width - 2 * avatars_area_padding) / team_text.get_width() 551 team_text = pygame.transform.scale(team_text, (int(team_text.get_width() * ratio), int(team_text.get_height() * ratio))) 552 team_text_x = avatars_x_offset + (avatars_area_width - team_text.get_width()) // 2 553 team_text_y = team_background_y + avatars_area_padding + (team_text_size - team_text.get_height()) // 2 554 if not teams_enabled: 555 team_text_size = -avatars_area_padding 556 avatar_elements.append((team_text_x, team_text_y, team_text)) 557 558 # Players avatars 559 player_images = [] 560 for j in range(len(initial_game_state.teams[team])): 561 player = [player for player in players if player.name == initial_game_state.teams[team][j]][0] 562 player_avatar = ___load_player_avatar(player.skin, player_avatar_size) 563 player_images.append(player_avatar) 564 avatar_area = pygame.Surface((2 * avatars_area_padding + sum([player_image.get_width() for player_image in player_images]) + player_avatar_horizontal_padding * (len(initial_game_state.teams[team]) - 1), player_avatar_size)) 565 pygame.draw.rect(avatar_area, background_color, pygame.Rect(0, 0, avatar_area.get_width(), avatar_area.get_height())) 566 player_x = avatars_area_padding 567 centers = [] 568 for player_avatar in player_images: 569 avatar_area.blit(player_avatar, (player_x, 0)) 570 centers.append(player_x + player_avatar.get_width() // 2) 571 player_x += player_avatar.get_width() + player_avatar_horizontal_padding 572 if avatar_area.get_width() > avatars_area_width - 2 * avatars_area_padding: 573 ratio = (avatars_area_width - 2 * avatars_area_padding) / avatar_area.get_width() 574 centers = [center * ratio for center in centers] 575 avatar_area = pygame.transform.scale(avatar_area, (int(avatar_area.get_width() * ratio), int(avatar_area.get_height() * ratio))) 576 avatar_area_x = avatars_x_offset + (avatars_area_width - avatar_area.get_width()) // 2 577 avatar_area_y = team_background_y + 2 * avatars_area_padding + team_text_size + (player_avatar_size - avatar_area.get_height()) // 2 578 avatar_elements.append((avatar_area_x, avatar_area_y, avatar_area)) 579 580 # Players names 581 for j in range(len(initial_game_state.teams[team])): 582 player_name = initial_game_state.teams[team][j] 583 while True: 584 player_name_text = ___surface_from_text(player_name, player_name_text_size, avatars_area_color) 585 if player_name_text.get_width() > (avatars_area_width - 2 * avatars_area_padding) / len(initial_game_state.teams[team]): 586 player_name = player_name[:-2] + "." 587 else: 588 break 589 player_name_text_x = avatar_area_x + centers[j] - player_name_text.get_width() // 2 590 player_name_text_y = team_background_y + 3 * avatars_area_padding + team_text_size + player_avatar_size + (player_name_text_size - player_name_text.get_height()) // 2 591 avatar_elements.append((player_name_text_x, player_name_text_y, player_name_text)) 592 593 # Score locations 594 cheese_missing = ___surface_from_image(os.path.join("..", "gui", "cheese", "cheese_missing.png"), cheese_score_size) 595 score_x_offset = avatars_x_offset + avatars_area_padding 596 score_margin = avatars_area_width - 2 * avatars_area_padding - cheese_missing.get_width() 597 if len(initial_game_state.cheese) > 1: 598 score_margin /= (len(initial_game_state.cheese) - 1) 599 score_margin = min(score_margin, cheese_missing.get_width() * 2) 600 estimated_width = cheese_missing.get_width() + (len(initial_game_state.cheese) - 1) * score_margin 601 if estimated_width < avatars_area_width - 2 * avatars_area_padding: 602 score_x_offset += (avatars_area_width - 2 * avatars_area_padding - estimated_width) / 2 603 score_y_offset = team_background_y + 4 * avatars_area_padding + team_text_size + player_avatar_size + player_name_text_size 604 score_locations[team] = (score_x_offset, score_margin, score_y_offset) 605 606 # Show maze 607 def ___show_maze (): 608 pygame.draw.rect(gui_screen, background_color, pygame.Rect(maze_x_offset, maze_y_offset, game_area_width, game_area_height)) 609 for surface_x, surface_y, surface in maze_elements: 610 gui_screen.blit(surface, (surface_x, surface_y)) 611 ___show_maze() 612 613 # Show cheese 614 def ___show_cheese (cheese): 615 for c in cheese: 616 cheese_x, cheese_y, surface = cheese_elements[c] 617 gui_screen.blit(surface, (cheese_x, cheese_y)) 618 ___show_cheese(initial_game_state.cheese) 619 620 # Show_players at initial locations 621 def ___show_initial_players (): 622 for p in player_elements: 623 player_x, player_y, player_neutral, _, _ , _, _ = player_elements[p] 624 gui_screen.blit(player_neutral, (player_x, player_y)) 625 ___show_initial_players() 626 627 # Show avatars 628 def ___show_avatars (): 629 for surface_x, surface_y, surface in avatar_elements: 630 gui_screen.blit(surface, (surface_x, surface_y)) 631 ___show_avatars() 632 633 # Show scores 634 def ___show_scores (team_scores): 635 cheese_missing = ___surface_from_image(os.path.join("..", "gui", "cheese", "cheese_missing.png"), cheese_score_size) 636 cheese_missing = ___add_color_border(cheese_missing, cheese_score_border_color, cheese_score_border_width) 637 cheese_eaten = ___surface_from_image(os.path.join("..", "gui", "cheese", "cheese_eaten.png"), cheese_score_size) 638 cheese_eaten = ___add_color_border(cheese_eaten, cheese_score_border_color, cheese_score_border_width) 639 for team in score_locations: 640 score_x_offset, score_margin, score_y_offset = score_locations[team] 641 for i in range(int(team_scores[team])): 642 gui_screen.blit(cheese_eaten, (score_x_offset + i * score_margin, score_y_offset)) 643 if int(team_scores[team]) != team_scores[team]: 644 cheese_partial = ___surface_from_image(os.path.join("..", "gui", "cheese", "cheese_eaten.png"), cheese_score_size) 645 cheese_partial = ___colorize(cheese_partial, [(team_scores[team] - int(team_scores[team])) * 255] * 3) 646 cheese_partial = ___add_color_border(cheese_partial, cheese_score_border_color, cheese_score_border_width) 647 gui_screen.blit(cheese_partial, (score_x_offset + int(team_scores[team]) * score_margin, score_y_offset)) 648 for j in range(math.ceil(team_scores[team]), len(initial_game_state.cheese)): 649 gui_screen.blit(cheese_missing, (score_x_offset + j * score_margin, score_y_offset)) 650 initial_scores = {team: 0 for team in initial_game_state.teams} 651 ___show_scores(initial_scores) 652 653 # Show preprocessing message 654 preprocessing_image = ___surface_from_image(os.path.join("..", "gui", "drawings", "pyrat_preprocessing.png"), int(min(game_area_width, game_area_height) * main_image_factor)) 655 preprocessing_image = ___add_color_border(preprocessing_image, main_image_border_color, main_image_border_size) 656 go_image = ___surface_from_image(os.path.join("..", "gui", "drawings", "pyrat_go.png"), int(min(game_area_width, game_area_height) * main_image_factor)) 657 go_image = ___add_color_border(go_image, main_image_border_color, main_image_border_size) 658 main_image_x = maze_x_offset + (game_area_width - preprocessing_image.get_width()) / 2 659 main_image_y = maze_y_offset + (game_area_height - preprocessing_image.get_height()) / 2 660 gui_screen.blit(preprocessing_image, (main_image_x, main_image_y)) 661 662 # Prepare useful variables 663 current_state = copy.deepcopy(initial_game_state) 664 mud_being_crossed = {player.name: 0 for player in players} 665 traces = {player.name: [(player_elements[player.name][0] + player_elements[player.name][2].get_width() / 2, player_elements[player.name][1] + player_elements[player.name][2].get_height() / 2)] for player in players} 666 trace_colors = {player.name: ___get_main_color(player_elements[player.name][2]) for player in players} 667 player_surfaces = {player.name: player_elements[player.name][2] for player in players} 668 669 # Show and indicate when ready 670 gui_running = True 671 pygame.display.flip() 672 time.sleep(0.1) 673 pygame.display.update() 674 gui_initialized_synchronizer.wait() 675 676 # Run until the user asks to quit 677 while gui_running: 678 try: 679 680 # We check for termination 681 for event in pygame.event.get(): 682 if event.type == pygame.QUIT or (event.type == pglocals.KEYDOWN and event.key == pglocals.K_ESCAPE): 683 gui_running = False 684 if not gui_running: 685 break 686 687 # Get turn info 688 new_state = gui_queue.get(False) 689 690 # Indicate when preprocessing is over for a little time 691 if new_state.turn == 1: 692 ___show_maze() 693 ___show_cheese(current_state.cheese if i != animation_steps - 1 else new_state.cheese) 694 ___show_initial_players() 695 gui_screen.blit(go_image, (main_image_x, main_image_y)) 696 pygame.display.update((maze_x_offset, maze_y_offset, maze.width * cell_size, maze.height * cell_size)) 697 time.sleep(go_image_duration) 698 699 # Enter mud? 700 for player in players: 701 if new_state.muds[player.name]["count"] > 0 and mud_being_crossed[player.name] == 0: 702 mud_being_crossed[player.name] = new_state.muds[player.name]["count"] + 1 703 704 # Choose the correct player surface 705 for player in players: 706 player_x, player_y, player_neutral, player_north, player_south, player_west, player_east = player_elements[player.name] 707 row, col = maze.i_to_rc(current_state.player_locations[player.name]) 708 adjusted_new_location = new_state.player_locations[player.name] if not new_state.is_in_mud(player.name) else new_state.muds[player.name]["target"] 709 new_row, new_col = maze.i_to_rc(adjusted_new_location) 710 player_x += player_surfaces[player.name].get_width() / 2 711 player_y += player_surfaces[player.name].get_height() / 2 712 if new_col > col: 713 player_surfaces[player.name] = player_east 714 elif new_col < col: 715 player_surfaces[player.name] = player_west 716 elif new_row > row: 717 player_surfaces[player.name] = player_south 718 elif new_row < row: 719 player_surfaces[player.name] = player_north 720 else: 721 player_surfaces[player.name] = player_neutral 722 player_x -= player_surfaces[player.name].get_width() / 2 723 player_y -= player_surfaces[player.name].get_height() / 2 724 player_elements[player.name] = (player_x, player_y, player_neutral, player_north, player_south, player_west, player_east) 725 726 # Move players 727 for i in range(animation_steps): 728 729 # Reset background & cheese 730 ___show_maze() 731 ___show_cheese(current_state.cheese if i != animation_steps - 1 else new_state.cheese) 732 733 # Move player with trace 734 for player in players: 735 player_x, player_y, player_neutral, player_north, player_south, player_west, player_east = player_elements[player.name] 736 row, col = maze.i_to_rc(current_state.player_locations[player.name]) 737 adjusted_new_location = new_state.player_locations[player.name] if not new_state.is_in_mud(player.name) else new_state.muds[player.name]["target"] 738 new_row, new_col = maze.i_to_rc(adjusted_new_location) 739 shift = (i + 1) * cell_size / animation_steps 740 if mud_being_crossed[player.name] > 0: 741 shift /= mud_being_crossed[player.name] 742 shift += (mud_being_crossed[player.name] - new_state.muds[player.name]["count"] - 1) * cell_size / mud_being_crossed[player.name] 743 next_x = player_x if col == new_col else player_x + shift if new_col > col else player_x - shift 744 next_y = player_y if row == new_row else player_y + shift if new_row > row else player_y - shift 745 if i == animation_steps - 1 and new_state.muds[player.name]["count"] == 0: 746 player_elements[player.name] = (next_x, next_y, player_neutral, player_north, player_south, player_west, player_east) 747 if trace_length > 0: 748 pygame.draw.line(gui_screen, trace_colors[player.name], (next_x + player_surfaces[player.name].get_width() / 2, next_y + player_surfaces[player.name].get_height() / 2), traces[player.name][-1], width=trace_size) 749 for j in range(1, trace_length): 750 if len(traces[player.name]) > j: 751 pygame.draw.line(gui_screen, trace_colors[player.name], traces[player.name][-j-1], traces[player.name][-j], width=trace_size) 752 if len(traces[player.name]) == trace_length + 1: 753 final_segment_length = math.sqrt((traces[player.name][-1][0] - (next_x + player_surfaces[player.name].get_width() / 2))**2 + (traces[player.name][-1][1] - (next_y + player_surfaces[player.name].get_height() / 2))**2) 754 ratio = 1 - final_segment_length / cell_size 755 pygame.draw.line(gui_screen, trace_colors[player.name], traces[player.name][1], (traces[player.name][1][0] + ratio * (traces[player.name][0][0] - traces[player.name][1][0]), traces[player.name][1][1] + ratio * (traces[player.name][0][1] - traces[player.name][1][1])), width=trace_size) 756 gui_screen.blit(player_surfaces[player.name], (next_x, next_y)) 757 758 # Update maze & wait for animation 759 pygame.display.update((maze_x_offset, maze_y_offset, maze.width * cell_size, maze.height * cell_size)) 760 time.sleep(animation_time / animation_steps) 761 762 # Exit mud? 763 for player in players: 764 if new_state.muds[player.name]["count"] == 0: 765 mud_being_crossed[player.name] = 0 766 if mud_being_crossed[player.name] == 0: 767 player_x, player_y, _, _, _, _, _ = player_elements[player.name] 768 if traces[player.name][-1] != (player_x + player_surfaces[player.name].get_width() / 2, player_y + player_surfaces[player.name].get_height() / 2): 769 traces[player.name].append((player_x + player_surfaces[player.name].get_width() / 2, player_y + player_surfaces[player.name].get_height() / 2)) 770 traces[player.name] = traces[player.name][-trace_length-1:] 771 772 # Play a sound is a cheese is eaten 773 for player in players: 774 if new_state.player_locations[player.name] in current_state.cheese and mud_being_crossed[player.name] == 0: 775 ___play_sound(os.path.join("..", "gui", "players", player.skin.value, "cheese_eaten.wav"), os.path.join("..", "gui", "players", "default", "cheese_eaten.wav")) 776 777 # Update score 778 ___show_avatars() 779 new_scores = new_state.get_score_per_team() 780 ___show_scores(new_scores) 781 current_state = new_state 782 783 # Indicate if the game is over 784 if new_state.game_over(): 785 sorted_results = sorted([(new_scores[team], team) for team in new_scores], reverse=True) 786 medals = [___surface_from_image(os.path.join("..", "gui", "endgame", medal_name), medal_size) for medal_name in ["first.png", "second.png", "third.png", "others.png"]] 787 for i in range(len(sorted_results)): 788 if i > 0 and sorted_results[i][0] != sorted_results[i-1][0] and len(medals) > 1: 789 del medals[0] 790 team = sorted_results[i][1] 791 gui_screen.blit(medals[0], (medal_locations[team][0] - medals[0].get_width() / 2, medal_locations[team][1] - medals[0].get_height() / 3)) 792 ___play_sound(os.path.join("..", "gui", "endgame", "game_over.wav")) 793 pygame.display.update((0, 0, maze_x_offset, window_height)) 794 795 # Ignore exceptions raised due to emtpy queue 796 except queue.Empty: 797 pass 798 799 # Quit PyGame 800 pygame.quit() 801 802 # Ignore 803 except: 804 pass 805 806##################################################################################################################################################### 807#####################################################################################################################################################
41class PygameRenderingEngine (RenderingEngine): 42 43 """ 44 This class inherits from the RenderingEngine class. 45 Therefore, it has the attributes and methods defined in the RenderingEngine class in addition to the ones defined below. 46 47 This rendering engine uses the pygame library to render the game. 48 It will create a window and display the game in it. 49 The window will run in a different process than the one running the game. 50 """ 51 52 ############################################################################################################################################# 53 # MAGIC METHODS # 54 ############################################################################################################################################# 55 56 def __init__ ( self: Self, 57 fullscreen: bool = False, 58 trace_length: Integral = 0, 59 gui_speed: Number = 1.0, 60 *args: Any, 61 **kwargs: Any 62 ) -> Self: 63 64 """ 65 This function is the constructor of the class. 66 When an object is instantiated, this method is called to initialize the object. 67 This is where you should define the attributes of the object and set their initial values. 68 Arguments *args and **kwargs are used to pass arguments to the parent constructor. 69 This is useful not to declare again all the parent's attributes in the child class. 70 In: 71 * self: Reference to the current object. 72 * fullscreen: Indicates if the GUI should be fullscreen. 73 * trace_length: Length of the trace to display. 74 * gui_speed: Speed of the GUI. 75 * args: Arguments to pass to the parent constructor. 76 * kwargs: Keyword arguments to pass to the parent constructor. 77 Out: 78 * A new instance of the class. 79 """ 80 81 # Inherit from parent class 82 super().__init__(*args, **kwargs) 83 84 # Debug 85 assert isinstance(fullscreen, bool) # Type check for fullscreen 86 assert isinstance(trace_length, Integral) # Type check for trace_length 87 assert isinstance(gui_speed, Number) # Type check for gui_speed 88 assert trace_length >= 0 # trace_length must be positive 89 assert gui_speed > 0 # gui_speed must be positive 90 91 # Private attributes 92 self.__fullscreen = fullscreen 93 self.__trace_length = trace_length 94 self.__gui_speed = gui_speed 95 self.__gui_process = None 96 self.__gui_queue = None 97 98 ############################################################################################################################################# 99 # PUBLIC METHODS # 100 ############################################################################################################################################# 101 102 @override 103 def render ( self: Self, 104 players: List[Player], 105 maze: Maze, 106 game_state: GameState, 107 ) -> None: 108 109 """ 110 This method redefines the method of the parent class. 111 This function renders the game to a Pyame window. 112 The window is created in a different process than the one running the game. 113 In: 114 * self: Reference to the current object. 115 * players: Players of the game. 116 * maze: Maze of the game. 117 * game_state: State of the game. 118 Out: 119 * None. 120 """ 121 122 # Debug 123 assert isinstance(players, list) # Type check for players 124 assert all(isinstance(player, Player) for player in players) # Type check for players 125 assert isinstance(maze, Maze) # Type check for maze 126 assert isinstance(game_state, GameState) # Type check for game_state 127 128 # Initialize the GUI in a different process at turn 0 129 if game_state.turn == 0: 130 131 # Initialize the GUI process 132 gui_initialized_synchronizer = multiprocessing.Manager().Barrier(2) 133 self.__gui_queue = multiprocessing.Manager().Queue() 134 self.__gui_process = multiprocessing.Process(target=_gui_process_function, args=(gui_initialized_synchronizer, self.__gui_queue, maze, game_state, players, self.__fullscreen, self._render_simplified, self.__trace_length, self.__gui_speed)) 135 self.__gui_process.start() 136 gui_initialized_synchronizer.wait() 137 138 # At each turn, send current info to the process 139 else: 140 self.__gui_queue.put(game_state) 141 142 ############################################################################################################################################# 143 144 @override 145 def end ( self: Self, 146 ) -> None: 147 148 """ 149 This method redefines the method of the parent class. 150 It waits for the window to be closed before exiting. 151 In: 152 * self: Reference to the current object. 153 Out: 154 * None. 155 """ 156 157 # Wait for GUI to be exited to quit if there is one 158 if self.__gui_process is not None: 159 self.__gui_process.join()
This class inherits from the RenderingEngine class. Therefore, it has the attributes and methods defined in the RenderingEngine class in addition to the ones defined below.
This rendering engine uses the pygame library to render the game. It will create a window and display the game in it. The window will run in a different process than the one running the game.
56 def __init__ ( self: Self, 57 fullscreen: bool = False, 58 trace_length: Integral = 0, 59 gui_speed: Number = 1.0, 60 *args: Any, 61 **kwargs: Any 62 ) -> Self: 63 64 """ 65 This function is the constructor of the class. 66 When an object is instantiated, this method is called to initialize the object. 67 This is where you should define the attributes of the object and set their initial values. 68 Arguments *args and **kwargs are used to pass arguments to the parent constructor. 69 This is useful not to declare again all the parent's attributes in the child class. 70 In: 71 * self: Reference to the current object. 72 * fullscreen: Indicates if the GUI should be fullscreen. 73 * trace_length: Length of the trace to display. 74 * gui_speed: Speed of the GUI. 75 * args: Arguments to pass to the parent constructor. 76 * kwargs: Keyword arguments to pass to the parent constructor. 77 Out: 78 * A new instance of the class. 79 """ 80 81 # Inherit from parent class 82 super().__init__(*args, **kwargs) 83 84 # Debug 85 assert isinstance(fullscreen, bool) # Type check for fullscreen 86 assert isinstance(trace_length, Integral) # Type check for trace_length 87 assert isinstance(gui_speed, Number) # Type check for gui_speed 88 assert trace_length >= 0 # trace_length must be positive 89 assert gui_speed > 0 # gui_speed must be positive 90 91 # Private attributes 92 self.__fullscreen = fullscreen 93 self.__trace_length = trace_length 94 self.__gui_speed = gui_speed 95 self.__gui_process = None 96 self.__gui_queue = None
This function is the constructor of the class. When an object is instantiated, this method is called to initialize the object. This is where you should define the attributes of the object and set their initial values. Arguments args and *kwargs are used to pass arguments to the parent constructor. This is useful not to declare again all the parent's attributes in the child class.
In:
- self: Reference to the current object.
- fullscreen: Indicates if the GUI should be fullscreen.
- trace_length: Length of the trace to display.
- gui_speed: Speed of the GUI.
- args: Arguments to pass to the parent constructor.
- kwargs: Keyword arguments to pass to the parent constructor.
Out:
- A new instance of the class.
102 @override 103 def render ( self: Self, 104 players: List[Player], 105 maze: Maze, 106 game_state: GameState, 107 ) -> None: 108 109 """ 110 This method redefines the method of the parent class. 111 This function renders the game to a Pyame window. 112 The window is created in a different process than the one running the game. 113 In: 114 * self: Reference to the current object. 115 * players: Players of the game. 116 * maze: Maze of the game. 117 * game_state: State of the game. 118 Out: 119 * None. 120 """ 121 122 # Debug 123 assert isinstance(players, list) # Type check for players 124 assert all(isinstance(player, Player) for player in players) # Type check for players 125 assert isinstance(maze, Maze) # Type check for maze 126 assert isinstance(game_state, GameState) # Type check for game_state 127 128 # Initialize the GUI in a different process at turn 0 129 if game_state.turn == 0: 130 131 # Initialize the GUI process 132 gui_initialized_synchronizer = multiprocessing.Manager().Barrier(2) 133 self.__gui_queue = multiprocessing.Manager().Queue() 134 self.__gui_process = multiprocessing.Process(target=_gui_process_function, args=(gui_initialized_synchronizer, self.__gui_queue, maze, game_state, players, self.__fullscreen, self._render_simplified, self.__trace_length, self.__gui_speed)) 135 self.__gui_process.start() 136 gui_initialized_synchronizer.wait() 137 138 # At each turn, send current info to the process 139 else: 140 self.__gui_queue.put(game_state)
This method redefines the method of the parent class. This function renders the game to a Pyame window. The window is created in a different process than the one running the game.
In:
- self: Reference to the current object.
- players: Players of the game.
- maze: Maze of the game.
- game_state: State of the game.
Out:
- None.
144 @override 145 def end ( self: Self, 146 ) -> None: 147 148 """ 149 This method redefines the method of the parent class. 150 It waits for the window to be closed before exiting. 151 In: 152 * self: Reference to the current object. 153 Out: 154 * None. 155 """ 156 157 # Wait for GUI to be exited to quit if there is one 158 if self.__gui_process is not None: 159 self.__gui_process.join()
This method redefines the method of the parent class. It waits for the window to be closed before exiting.
In:
- self: Reference to the current object.
Out:
- None.