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 .spawnPointBlock import SpawnPointBlock
from .finishBlock import FinishBlock
from .coinBlock import CoinBlock
BLOCKS = [
StoneBlock,
SpawnPointBlock,
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 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.
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 change the current map.
"""
@@ -20,17 +20,22 @@ class Game:
self.map = map
self.gameState = gameState
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
def set_position_update_callback(self, callback):
def set_position_update_callback(self, callback: callable):
self.positionUpdateCallback = callback
def _position_update(self):
if self.positionUpdateCallback:
self.positionUpdateCallback()
def set_game_stop_callback(self, callback: callable):
self.gameStopCallback = callback
def start_game(self):
self.gameState.set_stop_game(False)
@@ -48,13 +53,15 @@ class Game:
self.gameState.add_move_count()
# Try to move the player according to his velocity
block_at_velocity = self._get_block_at_velocity()
if not block_at_velocity or not block_at_velocity.solid:
velocity_position = self.get_position_at_velocity() # TODO : optimize
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
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
player_position = self.gameState.get_player_position()
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()
if self.gameStopCallback and self.gameStopCallback(self.gameState):
self.gameState.set_stop_game(True)
break
def _update_player_velocity(self):
current_velocity = self.gameState.playerVelocity
@@ -100,29 +110,27 @@ class Game:
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)
match current_velocity:
case -1:
return None
case "down":
return (player_position[0], player_position[1] + 1)
case "up":
return (player_position[0], player_position[1] - 1)
case "left_down":
return (player_position[0] - 1, player_position[1] + 1)
case "right_down":
return (player_position[0] + 1, player_position[1] + 1)
case "left":
return (player_position[0] - 1, player_position[1])
case "right":
return (player_position[0] + 1, player_position[1])
case "left_up":
return (player_position[0] - 1, player_position[1] - 1)
case "right_up":
return (player_position[0] + 1, player_position[1] - 1)
case _:
return None
return None
+5 -5
View File
@@ -1,7 +1,7 @@
"""
Represents the current state of the game.
Contains :
- Player score
- Player coins
- stopGame flag
- Player position
"""
@@ -10,15 +10,12 @@ class GameState:
PLAYER_VELOCITIES = [*PLAYER_MOVEMENTS, "down"]
def __init__(self):
self.score = 0
self.playerPosition = (0, 0)
self.stopGame = False
self.movesCount = 0
self.playerVelocity = -1 # -1 means no velocity
self.gameWon = False
def add_score(self, points):
self.score += points
self.coins = 0
def set_player_position(self, x, y):
self.playerPosition = (x, y)
@@ -44,3 +41,6 @@ class GameState:
def set_game_won(self, 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
"""
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)
def on_input_request(self) -> str:
"""
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.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', [])
def get_block_at(self, x, y) -> Block | None:
@@ -38,6 +39,9 @@ class Map:
return block.type
return None
def get_min_moves(self):
return self.min_moves
def to_string_dict(self):
"""
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', ''))
}
if config.get('min_moves') is not None:
map_data['min_moves'] = config.get('min_moves')
return Map(map_data)
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.gameState import GameState
from game.handlers import GAME_HANDLERS
@@ -5,6 +7,9 @@ from game.map import Map
from game.mapReader import MapReader
from game.mapSet import MapSet
# loading variables from .env file
load_dotenv()
if __name__ == "__main__":
mapReader = MapReader()
mapSet = MapSet(mapReader, mapReader.get_map_names())
+1
View File
@@ -1,6 +1,7 @@
width = 12
height = 6
difficulty = 10
min_moves = 6
layout = """\
############
# E#