_ ___ _
| | __ ___ ____ _ ( _ ) / \ __ _ _ _ __ _
| | / _` \ \ / / _` | / _ \/\ / _ \ / _` | | | |/ _` |
| |__| (_| |\ V / (_| | | (_> < / ___ \ (_| | |_| | (_| |
|_____\__,_| \_/ \__,_| \___/\/ /_/ \_\__, |\__,_|\__,_|
|_|
A Python-based puzzle game engine featuring classic Lava & Aqua mechanics. Navigate a grid-based board, collect portal orbs, reach goal positions, and outsmart spreading lava and water hazards.
- Python 3.13+
- UV (Python package manager)
# navigate to the project
cd lava-and-aqua
# Install dependencies using UV
uv sync
# Run the game
uv run src/lava_and_aqua/main.py| Key | Action |
|---|---|
w |
Move up |
a |
Move left |
s |
Move down |
d |
Move right |
r |
Reset current level |
u |
Undo last move |
q |
Quit game |
lava-and-aqua/
โโโ src/lava_and_aqua/
โ โโโ core/ # Game engine and state management
โ โ โโโ state.py # Immutable GameState container
โ โ โโโ board.py # Board representation and entity management
โ โ โโโ entities.py # Game entity definitions
โ โ โโโ actions.py # Movement action system
โ โ โโโ engine.py # GameEngine state transitions
โ โ โโโ evaluator.py # Win/loss condition evaluation
โ โ โโโ observers.py # Observer pattern for game mechanics
โ โ โโโ game_manager.py # Game session and history management
โ โโโ utils/
โ โ โโโ types.py # Type definitions and enums
โ โ โโโ constants.py # Game constants and obstacle definitions
โ โ โโโ level_loader.py # JSON level file loading
โ โ โโโ rendering.py # ASCII emoji board rendering
โ โโโ config.py # Global configuration
โ โโโ main.py # Interactive demo entry point
โโโ levels/ # JSON-based level definitions
โ โโโ level_1.json
โ โโโ level_4.json
โ โโโ level_10.json
โ โโโ level_15.json
โ โโโ test_level.json
โโโ pyproject.toml # UV project configuration
โโโ uv.lock # Dependency lock file
โโโ README.md
1. Immutability-First Approach
- All core game objects (
GameState,Board,Entity) are frozen dataclasses - State transitions create new objects rather than mutating existing ones
- Enables reliable undo/redo functionality and state history
2. Separation of Concerns
- State Layer:
GameStateandBoardmanage data representation - Logic Layer:
GameEngine,GameEvaluator,Observerhandle rules and mechanics - Presentation Layer:
rendering.pyandmain.pyhandle UI - Utility Layer:
types.py,constants.pyprovide shared definitions
3. Design Patterns Used
| Pattern | Implementation | Purpose |
|---|---|---|
| Observer | Observer class |
Encapsulates game mechanics (spread, collision, movement validation) |
| State | GameState, GamePhase |
Represents distinct game states (PLAYING, WON, LOST) |
| Factory | Board.from_dict(), create_entity_class() |
Creates objects from configuration and generates entity types dynamically |
| Facade | GameState and GameEngine |
Provide unified interface to complex subsystems |
| Strategy | MoveAction, pathfinding algorithms |
Support different action types and search strategies |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GameState (Frozen) โ
โ โโ Immutable game snapshot โ
โ โโ board: Board โ
โ โโ phase: GamePhase โ
โ โโ move_history: list[MoveAction] โ
โ โโ move_count: int โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโ
โ โ
โโโโโโโโโผโโโโโโโ โโโโผโโโโโโโโโ
โ Board โ โ GamePhase โ
โ (Frozen) โ โ (Enum) โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโผโโโโโโโโโ โโโโผโโโโโโโโโ โโโโผโโโโโโโโโ
โ Entities โ โ Position โ โPosition โ
โ dict โ โ Mapping โ โ Map โ
โโโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Entity Hierarchy โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Entity (Base Class - frozen dataclass) โ
โ โโ Player โ
โ โ โโ collected_orbs: frozenset[EntityId] โ
โ โโ MetalBox (created via factory) โ
โ โโ Wall (created via factory) โ
โ โโ Goal (created via factory) โ
โ โโ Lava (created via factory) โ
โ โโ Water (created via factory) โ
โ โโ Orb (PortalOrb) (created via factory) โ
โ โโ CrackedWall (created via factory) โ
โ โโ TimedDoor โ
โ โโ remaining_time: int โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Game Logic Components โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ GameEngine โ
โ โโ apply_action(board, phase, move_count, action) โ
โ โโ apply_move(board, player, direction) โ
โ โโ get_available_actions(board, phase) โ
โ โ
โ GameEvaluator โ
โ โโ is_won(board, phase) โ
โ โโ is_lost(board, phase) โ
โ โโ is_terminal(phase) โ
โ โ
โ Observer โ
โ โโ can_move(board, player, direction) โ
โ โโ spread_lava_and_water(board) โ
โ โโ player_is_on_lava(board, player) โ
โ โโ has_collected_all_orbs(board, player) โ
โ โ
โ GameManager โ
โ โโ game_states: deque[GameState] (for undo/redo) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
main.py
โ
โโ LevelLoader.load_level() โโโ JSON file
โ
โโ GameState.from_level_data() โโโ Board
โ
โโ GameManager.add_state()
โ
โโ Game Loop:
โ
โโ User Input โโโ MoveAction
โ
โโ GameState.is_valid_action()
โ โโ GameEngine.is_valid_action()
โ โโ Observer.can_move()
โ
โโ GameState.update_state()
โ โโ GameEngine.apply_action()
โ โโ board.apply_move()
โ โ โโ Collision detection
โ โ โโ Orb collection
โ โ โโ Metal box pushing
โ โโ board.spread_lava_and_water()
โ โโ boardtick_TIMED_DOORs()
โ โโ GameEvaluator.is_terminal
โ
โโ GameManager.add_state() (history)
โ
โโ print_board() โโโ Render to console
โ
โโ Check win/loss conditions
- Player Movement: 4-directional (UP, DOWN, LEFT, RIGHT)
- Boundary Checking: Actions constrained within board bounds
- Collision Detection: Observer pattern validates valid moves
- Player can push metal boxes in the direction of movement
- Boxes can push other boxes (chain reaction)
- Boxes block lava and water spread
- Boxes can be pushed into fluids (fluid is destroyed)
Two-phase fluid spreading that occurs after each player move:
Phase 1: Water Spread
- Water spreads to adjacent empty cells (4 directions)
- Water + Lava = Wall (collision creates obstacle)
- Water spreads through cracked walls and portal orbs
Phase 2: Lava Spread
- Lava spreads to adjacent empty cells (4 directions)
- Lava + Water = Wall (same collision rule)
- Lava spreads through cracked walls and portal orbs
- Collect all portal orbs on the board
- Reach the goal position
- Both conditions must be met simultaneously
- Player touches lava during their position update
- Player occupies same cell as wall after move
- Must be collected for winning
- Can be in any cell
- Automatically collected when player moves to their position
- Can have water/lava on top (layering)
- Green blocks that appear for a set duration
- Count down each turn after player moves
- Disappear when timer reaches 0
- Passable when gone
- Blue passable obstacles
- Lava and water can spread through them
- Block direct movement (solid)
- Can stack with fluids
Levels are defined in JSON format with entity positioning:
{
"width": 14,
"height": 6,
"entities": {
"players": [
{ "position": [1, 1] }
],
"metal_boxes": [
{ "position": [5, 3] },
{ "position": [7, 2] }
],
"walls": [
{ "position": [13, 0] },
{ "position": [0, 5] }
],
"goals": [
{ "position": [13, 1] }
],
"lavas": [
{ "position": [3, 5] },
{ "position": [11, 4] }
],
"waters": [
{ "position": [2, 2] }
],
"portal_orbs": [
{ "position": [6, 2] },
{ "position": [10, 3] }
],
"cracked_walls": [
{ "position": [5, 5] }
],
"timed_doors": [
{ "position": [8, 4], "timer": 10 }
]
}
}Immutable frozen dataclass representing a game snapshot
GameState(
board: Board,
phase: GamePhase,
move_history: list[MoveAction],
move_count: int
)Key Methods:
from_level_data(): Create initial state from JSONis_valid_action(): Validate move possibilityupdate_state(): Apply action and return new GameStateget_available_actions(): List all valid movesis_won(),is_lost(): Check terminal conditions
Manages entity grid with efficient position lookup
Board(
width: int,
height: int,
entities: dict[EntityId, GameEntity],
position_map: dict[Coordinate, list[EntityId]],
player_id: EntityId
)Key Methods:
from_dict(): Create from level JSONget_entities_at(position): O(1) position lookupupdate_entity(),remove_entity(),add_entity(): Immutable updatesget_player(): Retrieve player entityspread_lava_and_water(): Trigger fluid mechanicstick_TIMED_DOORs(): Update door timers
# Frozen base class
Entity(
entity_id: EntityId,
entity_type: EntityType,
position: Position
)
# Special subclasses
Player(collected_orbs: frozenset[EntityId])
TimedDoor(remaining_time: int)
# Dynamically generated via factory
MetalBox, Wall, Goal, Lava, Water, Orb, CrackedWallOrchestrates state transitions and applies actions
Responsibilities:
- Validate actions against board state
- Apply player movement with collision handling
- Manage metal box pushing
- Trigger spread mechanics
- Check terminal conditions
Implements game mechanics and collision logic
Responsibilities:
- Movement validation (
can_move,can_push_box) - Fluid spread algorithm
- Orb collection detection
- Lava collision detection
- Timed door countdown
Determines win/loss conditions
is_won(board, phase) โ bool # All orbs + at goal
is_lost(board, phase) โ bool # On lava or wall
is_terminal(phase) โ bool # Game endedMaintains game session history for undo functionality
game_states: deque[GameState]
add_state(state)
remove_last_state() โ GameState| Entity | Symbol | Details |
|---|---|---|
| Player | ๐คฃ | ๐ญ if on lava, ๐ฅถ if on water |
| Goal | ๐ | Purple portal exit |
| Portal Orb | ๐ | Collectible on empty cell |
| Lava | ๐ฅ | Red hazard (spreads) |
| Water | ๐ง | Blue hazard (spreads) |
| Metal Box | ๐๏ธ | Pushable container |
| Wall | ๐งฑ | Solid obstacle |
| Cracked Wall | ๐ง | Passable to fluids |
| Timed Door | โณ 1๏ธโฃ-๐ | Green timer (10โ1 shows countdown) |
This project is open source and available under the MIT License.
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.