From 17f00ebca2fd80e4fc7d5c584cc67968d8073a94 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Sat, 10 Oct 2026 16:21:11 +0200 Subject: [PATCH] Spikes, better gameHandler, map width and height computed automatically & big peanut hard map --- game/blocks/__init__.py | 4 ++- game/blocks/finishBlock.py | 2 +- game/blocks/spikesBlock.py | 24 ++++++++++++++ game/game.py | 5 +-- game/handlers/gameHandler.py | 16 ++++++---- game/handlers/userPlayerGameHandler.py | 44 +++++++++++++++++++++++--- game/map.py | 4 +-- game/mapReader.py | 22 +++++++------ game/mapSet.py | 6 ++++ main.py | 17 +++++----- maps/big_peanut.toml | 22 +++++++++++++ maps/default.toml | 2 -- 12 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 game/blocks/spikesBlock.py create mode 100644 maps/big_peanut.toml diff --git a/game/blocks/__init__.py b/game/blocks/__init__.py index 2381260..434146e 100644 --- a/game/blocks/__init__.py +++ b/game/blocks/__init__.py @@ -2,10 +2,12 @@ from .stoneBlock import StoneBlock from .spawnPointBlock import SpawnPointBlock from .finishBlock import FinishBlock from .coinBlock import CoinBlock +from .spikesBlock import SpikesBlock BLOCKS = [ StoneBlock, SpawnPointBlock, FinishBlock, - CoinBlock + CoinBlock, + SpikesBlock, ] \ No newline at end of file diff --git a/game/blocks/finishBlock.py b/game/blocks/finishBlock.py index 5f97ce8..dfeafe6 100644 --- a/game/blocks/finishBlock.py +++ b/game/blocks/finishBlock.py @@ -16,8 +16,8 @@ class FinishBlock(Block): return False def on_collision(self, gameState: GameState): - gameState.set_stop_game(True) gameState.set_game_won(True) + gameState.set_stop_game(True) def to_string(self): return "🏁" \ No newline at end of file diff --git a/game/blocks/spikesBlock.py b/game/blocks/spikesBlock.py new file mode 100644 index 0000000..ec2b924 --- /dev/null +++ b/game/blocks/spikesBlock.py @@ -0,0 +1,24 @@ +import os + +from game.blocks.block import Block +from game.gameState import GameState + +class SpikesBlock(Block): + @property + def character(self): + return '^' + + @property + def type(self): + return 'spikes' + + @property + def solid(self): + return False + + def on_collision(self, gameState: GameState): + gameState.set_game_won(False) + gameState.set_stop_game(True) + + def to_string(self): + return "🔥" \ No newline at end of file diff --git a/game/game.py b/game/game.py index b2d4c97..24c1b24 100644 --- a/game/game.py +++ b/game/game.py @@ -44,10 +44,9 @@ class Game: if spawn_point_position is None: raise ValueError("No spawn point found in the map.") self.gameState.set_player_position(spawn_point_position) + self._position_update() 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() @@ -70,6 +69,8 @@ class Game: self._update_player_velocity() + self._position_update() # Update the screen for example + if self.gameStopCallback and self.gameStopCallback(self.gameState): self.gameState.set_stop_game(True) break diff --git a/game/handlers/gameHandler.py b/game/handlers/gameHandler.py index 622fd54..89d8cf0 100644 --- a/game/handlers/gameHandler.py +++ b/game/handlers/gameHandler.py @@ -1,12 +1,14 @@ from abc import abstractmethod -from game.game import Game + class GameHandler: - def __init__(self, game: Game): - self.game = game - self.game.set_waiting_for_input_callback(self.on_input_request) - self.game.set_position_update_callback(self.on_position_update) + def __init__(self): + pass + + @abstractmethod + def run(self) -> None: + pass @abstractmethod def on_position_update(self) -> None: @@ -14,4 +16,6 @@ class GameHandler: @abstractmethod def on_input_request(self) -> str: - pass \ No newline at end of file + pass + + \ No newline at end of file diff --git a/game/handlers/userPlayerGameHandler.py b/game/handlers/userPlayerGameHandler.py index 328d99a..28cb856 100644 --- a/game/handlers/userPlayerGameHandler.py +++ b/game/handlers/userPlayerGameHandler.py @@ -1,14 +1,48 @@ - from time import sleep -from game.game import Game -from game.handlers.gameHandler import GameHandler +from game.handlers.gameHandler import GameHandler +from game.game import Game +from game.gameState import GameState +from game.mapReader import MapReader +from game.mapSet import MapSet class UserPlayerGameHandler(GameHandler): KEYBOARD_INPUTS = ["w", "q", "a", "z", "e", "d", "c"] - def __init__(self, game: Game): - super().__init__(game) + def __init__(self): + super().__init__() + self.mapReader = MapReader() + self.mapSet = MapSet(self.mapReader, self.mapReader.get_map_names()) + self.map = self._choose_map() + self.gameState = GameState() + self.game = Game(self.map, self.gameState) + self.game.set_waiting_for_input_callback(self.on_input_request) + self.game.set_position_update_callback(self.on_position_update) + + def _choose_map(self): + """ + Prompt the user to choose a map from the available maps + """ + print("\nAvailable maps:") + for i, map_name in enumerate(self.mapReader.get_map_names()): + print(f"{i + 1}. {map_name.replace("_", " ").title()}") + + while True: + try: + choice = int(input("Choose a map by number: ")) - 1 + if 0 <= choice < len(self.mapReader.get_map_names()): + return self.mapSet.get_map(choice) + else: + print("Invalid choice. Please try again.") + except ValueError: + print("Invalid input. Please enter a number.") + + def run(self) -> None: + """ + Run the game loop + """ + self.game.start_game() + print("Game over : You " + ("Won!" if self.game.gameState.gameWon else "Lost!")) def on_position_update(self) -> None: """ diff --git a/game/map.py b/game/map.py index 8c9b5e5..6bb41f7 100644 --- a/game/map.py +++ b/game/map.py @@ -13,11 +13,11 @@ from game.blocks.block import Block class Map: def __init__(self, map_data: dict): self.name = map_data.get('name', 'Unknown') - self.width = int(map_data.get('width', 0)) - self.height = int(map_data.get('height', 0)) self.difficulty = float(map_data.get('difficulty', 0.0)) self.min_moves = map_data.get('min_moves', None) self.layout = map_data.get('layout', []) + self.width = max(len(row) for row in self.layout) if self.layout else 0 + self.height = len(self.layout) if self.layout else 0 def get_block_at(self, x, y) -> Block | None: if 0 <= x < self.width and 0 <= y < self.height: diff --git a/game/mapReader.py b/game/mapReader.py index b936a6f..126d571 100644 --- a/game/mapReader.py +++ b/game/mapReader.py @@ -27,13 +27,13 @@ class MapReader: with open(map_path, 'rb') as f: config = tomllib.load(f) + + layout = config.get('layout', '') map_data = { 'name': map_name, - 'width': config.get('width', 0), - 'height': config.get('height', 0), 'difficulty': config.get('difficulty', 0.0), - 'layout': self._parse_layout(config.get('layout', '')) + 'layout': self._parse_layout(layout) } if config.get('min_moves') is not None: @@ -48,16 +48,20 @@ class MapReader: def _parse_layout(self, mapString): # Takes the blocks and translates their characters into the corresponding block objects. - block_characters_mapping = self._get_block_characters_mapping() + # Makes sure tthe width is consistent across all rows by padding with None if necessary. + block_mapping = self._get_block_characters_mapping() layout = [] - for line in mapString.strip().splitlines(): + for line in mapString.splitlines(): row = [] for char in line: - block_class = block_characters_mapping.get(char) + block_class = block_mapping.get(char) if block_class: row.append(block_class()) else: - row.append(None) + row.append(None) # Unknown character, treat as empty space layout.append(row) - return layout - \ No newline at end of file + max_width = max(len(row) for row in layout) + for row in layout: + while len(row) < max_width: + row.append(None) # Pad with None to ensure consistent width + return layout \ No newline at end of file diff --git a/game/mapSet.py b/game/mapSet.py index 5f80664..ca30efe 100644 --- a/game/mapSet.py +++ b/game/mapSet.py @@ -10,6 +10,12 @@ class MapSet: self.mapReader = mapReader self.maps = [self.mapReader.read_map(name) for name in mapNames] + def get_map_by_name(self, name: str): + for map in self.maps: + if map.name == name: + return map + raise ValueError(f"Map with name '{name}' not found in the MapSet.") + def get_map(self, index: int): if index < 0 or index >= len(self.maps): raise IndexError("Map index out of range.") diff --git a/main.py b/main.py index c05c609..e1515a1 100644 --- a/main.py +++ b/main.py @@ -11,12 +11,11 @@ from game.mapSet import MapSet load_dotenv() if __name__ == "__main__": - mapReader = MapReader() - mapSet = MapSet(mapReader, mapReader.get_map_names()) - map = mapSet.get_maps_by_difficulty(0, 1)[0] - gameState = GameState() - game = Game(map, gameState) - gameHandler = GAME_HANDLERS["user player"](game) - game.start_game() - - # run_game() + # Make the user choose the game handler + print("Choose a game handler:") + for i, handler_name in enumerate(GAME_HANDLERS.keys()): + print(f"[{i + 1}] {handler_name}") + choice = int(input("Enter the number of the game handler: ")) - 1 + handler_name = list(GAME_HANDLERS.keys())[choice] + game_handler_class = GAME_HANDLERS[handler_name]() + game_handler_class.run() \ No newline at end of file diff --git a/maps/big_peanut.toml b/maps/big_peanut.toml new file mode 100644 index 0000000..9068843 --- /dev/null +++ b/maps/big_peanut.toml @@ -0,0 +1,22 @@ +difficulty = 90 +min_moves = 42 +layout = """ + ##### + ###### ######## + ## #### + ### E## + ### °##### + ## ° ## ###### + #°####### ## # # ### ###### + # # ## + ## ##° ##° ^### + # ### #### ## + # #####^##### ## + # ##### ####° # + # # # + # °# #####^^## + ## ° ###^######## + ## # ########## + #S ##^##### + ######### +""" \ No newline at end of file diff --git a/maps/default.toml b/maps/default.toml index 2b0146e..7770722 100644 --- a/maps/default.toml +++ b/maps/default.toml @@ -1,5 +1,3 @@ -width = 12 -height = 6 difficulty = 10 min_moves = 6 layout = """\