""" Represents the main game class. This class is responsible for managing the game's logic. It offers to set a callback function that will be called when waiting for a player input. It provides the gameState object to access the current state of the game. It provides the Map object to access the map. It provides some stopGame conditions to stop the game like the max number of moves or the player score. It provides public methods to start, stop and reset the game. It provides public methods to change the current map. """ from game.blocks.block import Block from game.gameState import GameState from game.map import Map from colorama import Style class Game: def __init__(self, map: Map, gameState: GameState): self.map = map self.gameState = gameState self.waitingForInputCallback = None def set_waiting_for_input_callback(self, callback): self.waitingForInputCallback = callback def set_position_update_callback(self, callback): self.positionUpdateCallback = callback def _position_update(self): if self.positionUpdateCallback: self.positionUpdateCallback() def start_game(self): self.gameState.set_stop_game(False) # Spawn the player at the spawn point spawn_point_position = self.map.get_first_block_type_position('spawn_point') if spawn_point_position is None: raise ValueError("No spawn point found in the map.") self.gameState.set_player_position(spawn_point_position) while not self.gameState.stopGame: self._position_update() # Update the screen for example if self.gameState.playerVelocity == -1 and self.waitingForInputCallback: self.gameState.set_player_velocity(self.waitingForInputCallback()) self.gameState.add_move_count() # Try to move the player according to his velocity velocity_position = self.get_position_at_velocity() block_at_velocity = None if velocity_position is not None: block_at_velocity = self.map.get_block_at(velocity_position[0], velocity_position[1]) if not block_at_velocity or not block_at_velocity.solid: # Empty space or non-solid block → Move the player to the new position if velocity_position: self.gameState.set_player_position(velocity_position) if block_at_velocity: block_at_velocity.on_collision(self.gameState) # Do something when the player is on the block, like add score or stop the game # Check if the player is on a solid block player_position = self.gameState.get_player_position() block_at_player_position = self.map.get_block_at(player_position[0], player_position[1] + 1) if block_at_player_position and block_at_player_position.solid: self.gameState.set_player_velocity(-1) # Stop the player from falling self._update_player_velocity() def _update_player_velocity(self): current_velocity = self.gameState.playerVelocity if current_velocity == -1: # If no velocity, then the player is not moving, so we don't need to update the velocity return if current_velocity == "down": # Falling straight down → Still falling except if the player is on a block if not self._get_block_at_velocity() == None and self._get_block_at_velocity().solid: # If block at where the player is going is solid, then the player will stop falling self.gameState.set_player_velocity(-1) return if current_velocity is "up": # Going up → Going down self.gameState.set_player_velocity("down") return if current_velocity in ["left_down", "right_down"]: self.gameState.set_player_velocity("down") return if current_velocity in ["left", "right"]: self.gameState.set_player_velocity(current_velocity + "_down") block_at_velocity = self._get_block_at_velocity() if not block_at_velocity == None and block_at_velocity.solid: # If block at where the player is going is solid, then the player will fall down self.gameState.set_player_velocity("down") return if current_velocity in ["left_up", "right_up"]: self.gameState.set_player_velocity(current_velocity.replace("_up", "_down")) block_at_velocity = self._get_block_at_velocity() if not block_at_velocity == None and block_at_velocity.solid: # If block at where the player is going is solid, then the player will fall down self.gameState.set_player_velocity("down") return def get_position_at_velocity(self) -> tuple | None: player_position = self.gameState.get_player_position() current_velocity = self.gameState.playerVelocity if current_velocity == -1: return None if current_velocity == "down": return (player_position[0], player_position[1] + 1) if current_velocity == "left_down": return (player_position[0] - 1, player_position[1] + 1) if current_velocity == "right_down": return (player_position[0] + 1, player_position[1] + 1) if current_velocity == "left": return (player_position[0] - 1, player_position[1]) if current_velocity == "right": return (player_position[0] + 1, player_position[1]) if current_velocity == "left_up": return (player_position[0] - 1, player_position[1] - 1) if current_velocity == "right_up": return (player_position[0] + 1, player_position[1] - 1) return None def _get_block_at_velocity(self) -> Block | None: position_at_velocity = self.get_position_at_velocity() if position_at_velocity is None: return None return self.map.get_block_at(position_at_velocity[0], position_at_velocity[1]) def stop_game(self): self.gameState.set_stop_game(True) def to_string(self): """ Returns a string representation of the game state and map with player position. """ player_position = self.gameState.get_player_position() map_string_dict = self.map.to_string_dict() map_string_dict[player_position[1]][player_position[0]] = f'{Style.BRIGHT}🦧' return '\n'.join([''.join(character + Style.RESET_ALL for character in row) for row in map_string_dict])