Skip to content

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.

Notifications You must be signed in to change notification settings

Zaid-Al-Habbal/lava-and-aqua

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

27 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

                             _                        ___        _                     
                            | |    __ ___   ____ _   ( _ )      / \   __ _ _   _  __ _ 
                            | |   / _` \ \ / / _` |  / _ \/\   / _ \ / _` | | | |/ _` |
                            | |__| (_| |\ 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.

Quick Start

Prerequisites

  • Python 3.13+
  • UV (Python package manager)

Installation & Running

# 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

Game Controls

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

๐Ÿ“ Project Structure

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

๐Ÿ—๏ธ Architecture & Design Patterns

Core Design Principles

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: GameState and Board manage data representation
  • Logic Layer: GameEngine, GameEvaluator, Observer handle rules and mechanics
  • Presentation Layer: rendering.py and main.py handle UI
  • Utility Layer: types.py, constants.py provide 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

๐Ÿ”— Class Relationships & Architecture Diagram

Class Hierarchy

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     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)           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Interaction Flow

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

๐ŸŽฎ Key Game Mechanics

1. Movement & Collision

  • Player Movement: 4-directional (UP, DOWN, LEFT, RIGHT)
  • Boundary Checking: Actions constrained within board bounds
  • Collision Detection: Observer pattern validates valid moves

2. Metal Box Mechanics

  • 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)

3. Spread System

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

4. Win Condition

  • Collect all portal orbs on the board
  • Reach the goal position
  • Both conditions must be met simultaneously

5. Loss Condition

  • Player touches lava during their position update
  • Player occupies same cell as wall after move

6. Portal Orbs (Collectibles)

  • 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)

7. Timed Doors

  • Green blocks that appear for a set duration
  • Count down each turn after player moves
  • Disappear when timer reaches 0
  • Passable when gone

8. Cracked Walls

  • Blue passable obstacles
  • Lava and water can spread through them
  • Block direct movement (solid)
  • Can stack with fluids

๐Ÿ“‹ Level File Format

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 }
    ]
  }
}

๐Ÿ”ง Core Components Reference

GameState (core/state.py)

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 JSON
  • is_valid_action(): Validate move possibility
  • update_state(): Apply action and return new GameState
  • get_available_actions(): List all valid moves
  • is_won(), is_lost(): Check terminal conditions

Board (core/board.py)

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 JSON
  • get_entities_at(position): O(1) position lookup
  • update_entity(), remove_entity(), add_entity(): Immutable updates
  • get_player(): Retrieve player entity
  • spread_lava_and_water(): Trigger fluid mechanics
  • tick_TIMED_DOORs(): Update door timers

Entity & Subclasses (core/entities.py)

# 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, CrackedWall

GameEngine (core/engine.py)

Orchestrates 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

Observer (core/observer.py)

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

GameEvaluator (core/evaluator.py)

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 ended

GameManager (core/game_manager.py)

Maintains game session history for undo functionality

game_states: deque[GameState]
add_state(state)
remove_last_state() โ†’ GameState

๐ŸŽจ Rendering System

Emoji Board Visualization

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)

๐Ÿ“ License

This project is open source and available under the MIT License.


๐Ÿค Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.

About

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.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages