Compare commits

...

3 Commits

Author SHA1 Message Date
Ninluc 14a39b7154 Added ap min_moves, gameStop callback and optimizations 2026-10-10 15:29:20 +02:00
Ninluc 10a3d93821 Added coins 2026-10-10 15:28:42 +02:00
Ninluc 9030146ec8 Optimise physic 2026-10-10 14:53:04 +02:00
11 changed files with 106 additions and 39 deletions
+2
View File
@@ -0,0 +1,2 @@
# show extra spaces after certains emojis to align them in the terminal
SHOW_EXTRA_EMOJI_SPACES=true
+2
View File
@@ -0,0 +1,2 @@
# show extra spaces after certains emojis to align them in the terminal
SHOW_EXTRA_EMOJI_SPACES=true
+2
View File
@@ -1,9 +1,11 @@
from .stoneBlock import StoneBlock from .stoneBlock import StoneBlock
from .spawnPointBlock import SpawnPointBlock from .spawnPointBlock import SpawnPointBlock
from .finishBlock import FinishBlock from .finishBlock import FinishBlock
from .coinBlock import CoinBlock
BLOCKS = [ BLOCKS = [
StoneBlock, StoneBlock,
SpawnPointBlock, SpawnPointBlock,
FinishBlock, FinishBlock,
CoinBlock
] ]
+28
View File
@@ -0,0 +1,28 @@
import os
from game.blocks.block import Block
from game.gameState import GameState
class CoinBlock(Block):
def __init__(self):
self.collected = False
@property
def character(self):
return '°'
@property
def type(self):
return 'coin'
@property
def solid(self):
return False
def on_collision(self, gameState: GameState):
if not self.collected:
gameState.add_coin()
self.collected = True
def to_string(self):
return " " if self.collected else "🪙" + (" " if os.getenv("SHOW_EXTRA_EMOJI_SPACES", "false").lower() == "true" else "")
+38 -30
View File
@@ -4,7 +4,7 @@ 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 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 gameState object to access the current state of the game.
It provides the Map object to access the map. 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. TODO: It provides some stopGame conditions to stop the game like the max number of moves.
It provides public methods to start, stop and reset the game. It provides public methods to start, stop and reset the game.
It provides public methods to change the current map. It provides public methods to change the current map.
""" """
@@ -20,17 +20,22 @@ class Game:
self.map = map self.map = map
self.gameState = gameState self.gameState = gameState
self.waitingForInputCallback = None self.waitingForInputCallback = None
self.positionUpdateCallback = None
self.gameStopCallback = None
def set_waiting_for_input_callback(self, callback): def set_waiting_for_input_callback(self, callback: callable):
self.waitingForInputCallback = callback self.waitingForInputCallback = callback
def set_position_update_callback(self, callback): def set_position_update_callback(self, callback: callable):
self.positionUpdateCallback = callback self.positionUpdateCallback = callback
def _position_update(self): def _position_update(self):
if self.positionUpdateCallback: if self.positionUpdateCallback:
self.positionUpdateCallback() self.positionUpdateCallback()
def set_game_stop_callback(self, callback: callable):
self.gameStopCallback = callback
def start_game(self): def start_game(self):
self.gameState.set_stop_game(False) self.gameState.set_stop_game(False)
@@ -48,13 +53,15 @@ class Game:
self.gameState.add_move_count() self.gameState.add_move_count()
# Try to move the player according to his velocity # Try to move the player according to his velocity
block_at_velocity = self._get_block_at_velocity() velocity_position = self.get_position_at_velocity()
if not block_at_velocity or not block_at_velocity.solid: block_at_velocity = None
velocity_position = self.get_position_at_velocity() # TODO : optimize 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: if velocity_position:
self.gameState.set_player_position(velocity_position) self.gameState.set_player_position(velocity_position)
if block_at_velocity: 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 block_at_velocity.on_collision(self.gameState) # Do something when the player is on the block, like add coins or stop the game
# Check if the player is on a solid block # Check if the player is on a solid block
player_position = self.gameState.get_player_position() player_position = self.gameState.get_player_position()
block_at_player_position = self.map.get_block_at(player_position[0], player_position[1] + 1) block_at_player_position = self.map.get_block_at(player_position[0], player_position[1] + 1)
@@ -63,6 +70,9 @@ class Game:
self._update_player_velocity() self._update_player_velocity()
if self.gameStopCallback and self.gameStopCallback(self.gameState):
self.gameState.set_stop_game(True)
break
def _update_player_velocity(self): def _update_player_velocity(self):
current_velocity = self.gameState.playerVelocity current_velocity = self.gameState.playerVelocity
@@ -100,29 +110,27 @@ class Game:
player_position = self.gameState.get_player_position() player_position = self.gameState.get_player_position()
current_velocity = self.gameState.playerVelocity current_velocity = self.gameState.playerVelocity
if current_velocity == -1: match current_velocity:
return None case -1:
return None
if current_velocity == "down": case "down":
return (player_position[0], player_position[1] + 1) return (player_position[0], player_position[1] + 1)
case "up":
if current_velocity == "left_down": return (player_position[0], player_position[1] - 1)
return (player_position[0] - 1, player_position[1] + 1) case "left_down":
return (player_position[0] - 1, player_position[1] + 1)
if current_velocity == "right_down": case "right_down":
return (player_position[0] + 1, player_position[1] + 1) return (player_position[0] + 1, player_position[1] + 1)
case "left":
if current_velocity == "left": return (player_position[0] - 1, player_position[1])
return (player_position[0] - 1, player_position[1]) case "right":
return (player_position[0] + 1, player_position[1])
if current_velocity == "right": case "left_up":
return (player_position[0] + 1, player_position[1]) return (player_position[0] - 1, player_position[1] - 1)
case "right_up":
if current_velocity == "left_up": return (player_position[0] + 1, player_position[1] - 1)
return (player_position[0] - 1, player_position[1] - 1) case _:
return None
if current_velocity == "right_up":
return (player_position[0] + 1, player_position[1] - 1)
return None return None
+5 -5
View File
@@ -1,7 +1,7 @@
""" """
Represents the current state of the game. Represents the current state of the game.
Contains : Contains :
- Player score - Player coins
- stopGame flag - stopGame flag
- Player position - Player position
""" """
@@ -10,15 +10,12 @@ class GameState:
PLAYER_VELOCITIES = [*PLAYER_MOVEMENTS, "down"] PLAYER_VELOCITIES = [*PLAYER_MOVEMENTS, "down"]
def __init__(self): def __init__(self):
self.score = 0
self.playerPosition = (0, 0) self.playerPosition = (0, 0)
self.stopGame = False self.stopGame = False
self.movesCount = 0 self.movesCount = 0
self.playerVelocity = -1 # -1 means no velocity self.playerVelocity = -1 # -1 means no velocity
self.gameWon = False self.gameWon = False
self.coins = 0
def add_score(self, points):
self.score += points
def set_player_position(self, x, y): def set_player_position(self, x, y):
self.playerPosition = (x, y) self.playerPosition = (x, y)
@@ -44,3 +41,6 @@ class GameState:
def set_game_won(self, won): def set_game_won(self, won):
self.gameWon = won self.gameWon = won
def add_coin(self):
self.coins += 1
+14 -2
View File
@@ -14,11 +14,23 @@ class UserPlayerGameHandler(GameHandler):
""" """
Refresh the screen Refresh the screen
""" """
print("\033[2J\033[H" + self.game.to_string()) print("\n\033[2J") # Add margin for stupid vscode terminal
game_string = self.game.to_string()
for i, line in enumerate(game_string.splitlines()):
if i == 0:
line += "\tCoins: " + str(self.game.gameState.coins)
elif i == 1:
line += "\tMoves: " + str(self.game.gameState.movesCount)
print(line)
sleep(0.1) sleep(0.1)
def on_input_request(self) -> str: def on_input_request(self) -> str:
""" """
Listen for keyboard input, and map it to a gameState PLAYER_MOVEMENTS Listen for keyboard input, and map it to a gameState PLAYER_MOVEMENTS
""" """
return self.game.gameState.PLAYER_MOVEMENTS[self.KEYBOARD_INPUTS.index(input(f"Enter a movement ({', '.join(self.KEYBOARD_INPUTS)}): "))] user_input = input(f"Enter a movement ({', '.join(self.KEYBOARD_INPUTS)}): ")
if user_input not in self.KEYBOARD_INPUTS:
print(f"Invalid input: {user_input}. Please enter one of {', '.join(self.KEYBOARD_INPUTS)}.")
return self.on_input_request()
return self.game.gameState.PLAYER_MOVEMENTS[self.KEYBOARD_INPUTS.index(user_input)]
+4
View File
@@ -16,6 +16,7 @@ class Map:
self.width = int(map_data.get('width', 0)) self.width = int(map_data.get('width', 0))
self.height = int(map_data.get('height', 0)) self.height = int(map_data.get('height', 0))
self.difficulty = float(map_data.get('difficulty', 0.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.layout = map_data.get('layout', [])
def get_block_at(self, x, y) -> Block | None: def get_block_at(self, x, y) -> Block | None:
@@ -38,6 +39,9 @@ class Map:
return block.type return block.type
return None return None
def get_min_moves(self):
return self.min_moves
def to_string_dict(self): def to_string_dict(self):
""" """
Returns a string matrix representation of the map layout. Returns a string matrix representation of the map layout.
+3
View File
@@ -36,6 +36,9 @@ class MapReader:
'layout': self._parse_layout(config.get('layout', '')) 'layout': self._parse_layout(config.get('layout', ''))
} }
if config.get('min_moves') is not None:
map_data['min_moves'] = config.get('min_moves')
return Map(map_data) return Map(map_data)
def _get_block_characters_mapping(self): def _get_block_characters_mapping(self):
+5
View File
@@ -1,3 +1,5 @@
from dotenv import load_dotenv
from game.game import Game from game.game import Game
from game.gameState import GameState from game.gameState import GameState
from game.handlers import GAME_HANDLERS from game.handlers import GAME_HANDLERS
@@ -5,6 +7,9 @@ from game.map import Map
from game.mapReader import MapReader from game.mapReader import MapReader
from game.mapSet import MapSet from game.mapSet import MapSet
# loading variables from .env file
load_dotenv()
if __name__ == "__main__": if __name__ == "__main__":
mapReader = MapReader() mapReader = MapReader()
mapSet = MapSet(mapReader, mapReader.get_map_names()) mapSet = MapSet(mapReader, mapReader.get_map_names())
+1
View File
@@ -1,6 +1,7 @@
width = 12 width = 12
height = 6 height = 6
difficulty = 10 difficulty = 10
min_moves = 6
layout = """\ layout = """\
############ ############
# E# # E#