27 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
JailDoctor's Dilemma - Codebase Architecture Guide
Overview
JailDoctor's Dilemma is a retro-style 2D puzzle platformer game built in C++20 using SDL3 for graphics and audio. The game features 60+ rooms, collectible items, enemies, and an achievement system. It targets multiple platforms (Windows, macOS, Linux) with a focus on retro aesthetics and precise collision detection.
Language: C++20 Graphics: SDL3, OpenGL Audio: SDL3 + custom jail_audio library Build: CMake 3.10+ Game Canvas: 256x192 pixels (retro resolution)
Build Commands
Initial Setup & Build
# From project root
mkdir -p build
cd build
cmake ..
cmake --build .
Rebuild After Changes
# From build directory
cmake --build .
# Or from project root
cmake --build build
Clean Build
# From build directory
cmake --build . --clean-first
# Or from project root
cmake --build build --clean-first
Run the Game
# Executable is placed in project root
./jaildoctors_dilemma
Important: The build directory is /Users/sergio/Gitea/jaildoctors_dilemma/build and the output executable is placed in the project root directory.
Linter (clang-tidy)
IMPORTANT: Always run the linter on specific files, NOT on the entire project, to avoid long execution times and errors in unrelated files.
# Analyze a specific file (RECOMMENDED)
tools/linter/run_clang-tidy.sh source/game/entities/player.cpp
# Analyze multiple specific files
tools/linter/run_clang-tidy.sh source/game/entities/player.cpp source/game/entities/enemy.cpp
# Apply fixes automatically to a specific file
tools/linter/run_clang-tidy.sh --fix source/game/entities/player.cpp
# Analyze entire project (SLOW, may have errors in defaults.hpp)
tools/linter/run_clang-tidy.sh
Note: Running the linter on the entire project can produce errors in files like defaults.hpp that are unrelated to your changes. Always target specific files you're working on.
1. High-Level Architecture Overview
The architecture follows a layered, modular design with clear separation of concerns:
┌─────────────────────────────────────┐
│ Application Layer │
│ (Director, Scene Manager) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Game Logic Layer │
│ (Game Scene, Entities, Gameplay) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Core Systems Layer │
│ (Rendering, Audio, Input, Resources)│
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ SDL3 & External Libraries │
│ (jail_audio, stb_image, stb_vorbis) │
└─────────────────────────────────────┘
Key Design Principles
- Singleton Pattern: Core systems (Director, Screen, Audio, Input, Resource, Asset) use singleton pattern for global access
- Scene-Based Architecture: Game flow managed through discrete scenes (Logo, Title, Loading, Game, Ending, etc.)
- Component-Based Entities: Player, enemies, and items are discrete entities with render/update patterns
- Resource Management: All assets (textures, sounds, animations, tilemaps) cached through Resource singleton
- Delta Time: Frame-rate independent physics using DeltaTimer class
2. Directory Structure
source/
├── core/ # Core engine systems
│ ├── audio/ # Audio management
│ │ └── audio.hpp/cpp # Audio singleton (music, sounds, volumes)
│ ├── input/ # Input handling
│ │ ├── input.hpp/cpp # Input manager (keyboard, gamepad)
│ │ ├── global_inputs.hpp # Global input state
│ │ └── mouse.hpp # Mouse input
│ ├── rendering/ # Graphics rendering
│ │ ├── screen.hpp/cpp # Screen/window singleton, SDL renderer
│ │ ├── surface.hpp/cpp # 8-bit indexed color surface abstraction
│ │ ├── surface_sprite.hpp # Static sprite rendering
│ │ ├── surface_animated_sprite.hpp # Animated sprite with frame data
│ │ ├── surface_moving_sprite.hpp # Moving sprite with velocity
│ │ ├── texture.hpp/cpp # SDL texture wrapper
│ │ ├── text.hpp/cpp # Text rendering system
│ │ ├── gif.hpp/cpp # GIF image loader
│ │ ├── opengl/ # OpenGL shader backend
│ │ │ └── opengl_shader.hpp/cpp # CRT shader effects
│ │ └── shader_backend.hpp # Abstract shader interface
│ ├── resources/ # Asset & Resource management
│ │ ├── asset.hpp/cpp # Asset registry (file path mapping)
│ │ └── resource.hpp/cpp # Resource singleton (loads/caches assets)
│ └── system/ # System management
│ ├── director.hpp/cpp # Main application controller
│ ├── debug.hpp/cpp # Debug info overlay
│ └── global_events.hpp/cpp # Global event broadcasting
├── game/ # Game-specific logic
│ ├── entities/ # Game objects
│ │ ├── player.hpp/cpp # Player entity with physics
│ │ ├── enemy.hpp/cpp # Enemy entities
│ │ └── item.hpp/cpp # Collectible items
│ ├── gameplay/ # Core gameplay systems
│ │ ├── room.hpp/cpp # Room/level logic, tilemap, collision
│ │ ├── room_tracker.hpp/cpp # Tracks visited rooms
│ │ ├── scoreboard.hpp/cpp # Score display & data
│ │ ├── item_tracker.hpp/cpp # Tracks collected items
│ │ ├── stats.hpp/cpp # Game statistics
│ │ └── cheevos.hpp/cpp # Achievement system
│ ├── scenes/ # Game scenes (flow states)
│ │ ├── logo.hpp/cpp # JailGames logo screen
│ │ ├── loading_screen.hpp/cpp # Resource loading progress
│ │ ├── title.hpp/cpp # Title screen & menus
│ │ ├── game.hpp/cpp # Main gameplay loop
│ │ ├── game_over.hpp/cpp # Game over screen
│ │ ├── ending.hpp/cpp # Ending sequence 1
│ │ ├── ending2.hpp/cpp # Ending sequence 2
│ │ └── credits.hpp/cpp # Credits screen
│ ├── ui/ # User interface
│ │ └── notifier.hpp/cpp # Achievement/notification display
│ ├── options.hpp/cpp # Game configuration/options
│ ├── scene_manager.hpp # Scene flow state machine
│ ├── defaults.hpp # Game defaults constants
│ └── gameplay.hpp # Gameplay constants
├── external/ # Third-party libraries
│ ├── jail_audio.hpp/cpp # Custom audio library
│ ├── jail_audio.h # C interface for jail_audio
│ ├── stb_image.h # Image loading library
│ └── stb_vorbis.h # OGG Vorbis audio decoding
├── utils/ # Utility code
│ ├── delta_timer.hpp/cpp # Frame-rate independent timing
│ ├── defines.hpp # Game constants (resolutions, block sizes)
│ └── utils.hpp/cpp # Helper functions (colors, math)
└── main.cpp # Application entry point
data/ # Game assets
├── font/ # Bitmap fonts + descriptors
├── palette/ # Color palettes (.pal files)
├── shaders/ # GLSL vertex/fragment shaders
├── room/ # Tilemaps & room definitions (01.tmx-60.tmx)
├── tilesets/ # Tileset graphics
├── enemies/ # Enemy sprites & animations
├── player/ # Player sprites & animations
├── items/ # Item sprite sheets
├── music/ # Background music (OGG)
├── sound/ # Sound effects (WAV)
├── logo/ # Logo images
├── loading/ # Loading screen graphics
├── title/ # Title screen graphics
├── ending/ # Ending sequence images
└── credits/ # Credits screen assets
3. Key Architectural Patterns
3.1 Singleton Pattern (Core Systems)
Most core systems use thread-safe singleton pattern:
class Screen {
private:
static Screen* screen_; // Singleton instance
Screen(); // Private constructor
~Screen();
public:
static void init(); // Creates singleton
static void destroy(); // Destroys singleton
static Screen* get(); // Accesses singleton
};
Singleton Systems:
Screen- Rendering, window management, palette/shader effectsInput- Keyboard & gamepad input binding and checkingAudio- Music and sound effect playbackResource- Asset loading, caching, streamingAsset- Asset path registry and verificationDirector- Main application controllerCheevos- Achievement state managementDebug- Debug information overlay
3.2 Scene-Based State Machine
The game uses a scene manager to control application flow:
// namespace SceneManager
enum class Scene {
LOGO, LOADING_SCREEN, TITLE, CREDITS, GAME, DEMO,
GAME_OVER, ENDING, ENDING2, QUIT
};
inline Scene current = Scene::LOGO; // Global scene state
inline Options options = ...; // Transition options
Scene Hierarchy:
- Each scene runs its own update/render loop
Directorswitches between scenes based onSceneManager::current- Scenes can request transitions via
SceneManager::currentassignment
3.3 Entity-Component Pattern (Simplified)
Entities (Player, Enemies, Items) have:
- Update - Logic, physics, animation
- Render - Draw to screen surface
- Collision - Hit detection (player collides with room, enemies, items)
3.4 Surface-Based Rendering Pipeline
The rendering system uses a 8-bit indexed color pipeline:
SurfaceData (pixel buffer)
↓
Surface (palette + rendering operations)
↓
SDL_Texture (GPU texture)
↓
SDL_Renderer (to SDL_Window)
↓
Display
Key Components:
Surface- 8-bit indexed pixel buffer with palette supportSurfaceSprite- Renders a fixed region of a surfaceSurfaceAnimatedSprite- Frame-based animation on top of spriteSurfaceMovingSprite- Adds velocity/position to animated sprite- Supports color replacement, palette swapping, and shader effects (CRT)
3.5 Tile-Based Collision System
Rooms use a sophisticated collision detection system:
class Room {
std::vector<LineHorizontal> bottom_floors_; // Ground surfaces
std::vector<LineHorizontal> top_floors_; // Ceiling surfaces
std::vector<LineVertical> left_walls_; // Left walls
std::vector<LineVertical> right_walls_; // Right walls
std::vector<LineDiagonal> left_slopes_; // Ramps going left
std::vector<LineDiagonal> right_slopes_; // Ramps going right
std::vector<LineHorizontal> conveyor_belt_floors_; // Moving platforms
};
Collision Detection:
- Player has collision points and "feet" points for fine-grained detection
- Room provides
check*Surfaces()methods for collision resolution - Supports ramps, slopes, conveyor belts, and auto-surfaces
- Enemies have axis-aligned bounding boxes
3.6 Sprite Animation System
Animation is data-driven:
struct AnimationData {
std::string name;
std::vector<SDL_FRect> frames; // Frame rectangles
int speed; // Milliseconds per frame
int loop; // Frame to return to (-1 = no loop)
bool completed;
int current_frame;
int counter;
};
// Loaded from .ani files (list of animation names)
// Rendered with SurfaceAnimatedSprite
4. System Interactions & Data Flow
4.1 Application Lifecycle
main()
↓
Director::Director() [Initialization]
├─ Options::init() - Load game configuration
├─ Asset::init() - Register asset paths
├─ Screen::init() - Create window, SDL renderer
├─ Audio::init() - Initialize SDL audio
├─ Input::init() - Bind keyboard/gamepad controls
├─ Resource::init() - Load all game resources
└─ Cheevos::init() - Load achievement state
Director::run() [Main loop]
├─ while (SceneManager::current != QUIT)
│ ├─ Logo::run()
│ ├─ LoadingScreen::run()
│ ├─ Title::run()
│ ├─ Game::run()
│ ├─ Ending::run()
│ └─ ...
└─ return 0
Director::~Director() [Cleanup]
├─ Options::saveToFile() - Save game settings
├─ Resource::destroy()
├─ Audio::destroy()
├─ Input::destroy()
├─ Screen::destroy()
└─ Asset::destroy()
4.2 Game Scene Flow (Core Gameplay Loop)
Game::run() {
while (game is running) {
// Update
Input::checkInput() - Get player commands
Player::update() - Physics, animation, collision
Room::update() - Enemy AI, animated tiles
checkCollisions() - Player vs enemies, items, room
checkGameOver() - Win/lose conditions
// Render
Screen::start() - Prepare for drawing
Room::renderMap() - Draw tilemap
Room::renderEnemies() - Draw enemy sprites
Room::renderItems() - Draw item sprites
Player::render() - Draw player sprite
renderScoreboard() - Draw HUD
Screen::render() - Flush to GPU
}
}
4.3 Resource Loading & Caching
Director::setFileList()
├─ Asset::add(file, type) - Register all assets
└─ Asset::check() - Verify files exist
Game Scene initialization
└─ Resource::init() - Loads all resources
├─ loadSounds() - WAV files
├─ loadMusics() - OGG files
├─ loadSurfaces() - GIF/PNG images
├─ loadAnimations() - .ani animation definitions
├─ loadTileMaps() - .room tilemap data
└─ loadRooms() - Room metadata
During gameplay
└─ Resource::get*(name) - Return cached resource
4.4 Input Flow
SDL_Event (from OS)
↓
Input::checkInput(action)
├─ Check keyboard bindings - SDL_Scancode → InputAction
├─ Check gamepad bindings - SDL_GamepadButton → InputAction
└─ Return true if action active
Game logic uses checked inputs
└─ Player responds to actions
4.5 Audio Control Flow
Game code
├─ Audio::playMusic(name, loop)
├─ Audio::playSound(name, group)
└─ Audio::stopMusic()
↓
Audio [wrapper/manager]
├─ Resource::getMusic(name)
├─ Resource::getSound(name)
└─ jail_audio C library
└─ SDL3 Audio device
5. Important Design Patterns & Conventions
5.1 Naming Conventions
Classes:
PascalCasefor classes:Player,Room,Screen,Director- Suffix
Spritefor sprite classes:SurfaceSprite,SurfaceAnimatedSprite
Methods:
get*()for getters:getWidth(),getRect()set*()for setters:setColor(),setPos()check*()for boolean checks:checkInput(),checkCollision()update()for game logic updatesrender()for drawing
Variables:
snake_casefor member variables with_suffix:x_,y_,sprite_UPPER_CASEfor constants:BLOCK,MAX_VY,WIDTH- Private members:
private_member_
Structs for Data:
XxxDatasuffix:PlayerData,RoomData,AnimationData- Used as initialization structures passed to constructors
5.2 Memory Management
- Smart Pointers: Uses
std::shared_ptrfor shared ownership (Surfaces, Sprites, Rooms) - Unique Pointers: Uses
std::unique_ptrfor sole ownership (Director, local objects) - Raw Pointers: Minimal use, mainly for singleton access or SDL objects
- Static Allocation: Singletons use static pointer pattern for RAII
5.3 Frame-Independent Physics
Uses DeltaTimer for delta time calculation:
// In game loop
float delta = deltaTimer.tick(); // Get seconds since last frame
// Apply to physics
player.vy_ += gravity * delta;
player.y_ += player.vy_ * delta;
Provides time scaling for slow-motion effects.
5.4 Palette System
- 8-bit indexed color (256 colors per palette)
- Multiple palettes can be loaded and swapped
Surface::setPalette()changes rendering colors- Supports color replacement per-render:
renderWithColorReplace() - CRT shader effects can modify colors in real-time
5.5 Configuration System
Game settings stored in configuration file:
namespace Options {
inline Video video{}; // Screen resolution, fullscreen, etc.
inline Audio audio{}; // Music/sound volumes
inline Notification notifications{};
inline Cheat cheats{}; // Cheat codes
inline ControlScheme keys{}; // Control mapping
inline Stats stats{}; // Game statistics
}
Options::loadFromFile(path); // Load from config.txt
Options::saveToFile(path); // Save on exit
5.6 Achievement System
struct Achievement {
int id;
std::string caption;
std::string description;
int icon;
bool completed;
bool obtainable;
};
Cheevos::unlock(id); // Unlock achievement
Cheevos::setUnobtainable(id); // Lock achievement
Cheevos::saveToFile(); // Persist state
Achievements trigger notifications on unlock.
6. Technology Stack
Core Technologies
| Component | Technology | Version | Role |
|---|---|---|---|
| Graphics | SDL3 | Latest | Window, rendering, input |
| GPU Rendering | OpenGL | 3.2+ | Shader effects (CRT) |
| Audio | SDL3 Audio | Latest | Audio device, mixing |
| Audio Decoding | jail_audio (custom) | 1.x | OGG/WAV playback |
| Image Loading | stb_image | v2.x | PNG/GIF image loading |
| Audio Decoding | stb_vorbis | v1.x | OGG Vorbis support |
| Language | C++ | C++20 | Standard library features |
| Build System | CMake | 3.10+ | Cross-platform building |
C++ Features Used
- Smart Pointers:
std::shared_ptr,std::unique_ptr - Standard Containers:
std::vector,std::array - Modern Features:
std::move, lambda functions, constexpr - Namespaces: Extensive use for organization
- Inline Variables: C++17
inlinefor global game state
Platform Support
- Windows: MinGW/MSVC with SDL3
- macOS: Apple Clang, arm64 architecture, OpenGL
- Linux: GCC with SDL3 and OpenGL
7. Key Classes & Their Responsibilities
Core System Classes
| Class | Purpose | Pattern |
|---|---|---|
Director |
Main application controller, scene manager | Singleton |
Screen |
Rendering, window, palette management | Singleton |
Input |
Keyboard & gamepad input | Singleton |
Audio |
Music and SFX playback | Singleton |
Resource |
Asset caching and loading | Singleton |
Asset |
Asset path registry | Singleton |
Debug |
Debug overlay information | Singleton |
globalEvents |
Global SDL event handling (quit, device reset, mouse) | Namespace |
Game Logic Classes
| Class | Purpose |
|---|---|
Game |
Main gameplay scene, orchestrates update/render |
Player |
Player entity with physics and animation |
Room |
Level data, collision detection, tilemap rendering |
Enemy |
Enemy entity behavior and rendering |
Item |
Collectible items |
Scoreboard |
HUD display data |
Cheevos |
Achievement unlock and state |
ItemTracker |
Tracks collected items |
RoomTracker |
Tracks visited rooms |
Rendering Classes
| Class | Purpose |
|---|---|
Surface |
8-bit indexed color pixel buffer with palette |
SurfaceSprite |
Renders a sprite region |
SurfaceAnimatedSprite |
Frame-based animation rendering |
SurfaceMovingSprite |
Sprite with velocity/position |
Text |
Text rendering system |
OpenGLShader |
Shader compilation and effects |
Utility Classes
| Class | Purpose |
|---|---|
DeltaTimer |
Frame-rate independent timing |
8. Common Development Workflows
Adding a New Input Action
- Add to
InputActionenum incore/input/input.hpp - Bind in
Director::initInput():Input::get()->bindKey(InputAction::NEW_ACTION, SDL_SCANCODE_X); - Check in game code:
if (Input::get()->checkInput(InputAction::NEW_ACTION)) { // Handle action }
Adding a New Scene
- Create class inheriting appropriate base (usually standalone
run()method) - Add to
SceneManager::Sceneenum - Implement in
Director::run()switch statement - Set
SceneManager::currentto transition
Adding Game Assets
- Place file in
data/directory - Register in
Director::setFileList():Asset::get()->add("/data/path/file.ext", AssetType::TYPE); - Resource loads automatically during
Resource::init() - Access via:
Resource::get()->getSurface("name")
Modifying Collision Detection
- Update
Room::setBottomSurfaces(),setLeftSlopes(), etc. - Modify Player collision point generation in
Player::updateColliderPoints() - Adjust tile classification in
TileTypeenum - Test with debug visualization (F12 key)
9. Debug Features
Available at Runtime
- F12 - Toggle debug info overlay (FPS, player position, collision points)
- F1/F2 - Decrease/increase window zoom
- F3 - Toggle fullscreen mode
- F4 - Toggle shader effects
- F5/F6 - Next/previous palette
- F7 - Toggle integer scaling
- M - Toggle music
- B - Toggle border display
- P - Pause game
Debug Mode Compilation
In debug builds (#ifdef DEBUG), renders:
- Player collision boxes
- Collision point visualization
- Debug information overlay
- Special debug event handling
10. Performance Considerations
Optimization Points
- Rendering: Uses indexed color (8-bit) to reduce memory bandwidth
- Surfaces: Shared smart pointers reduce copying
- Collision: Pre-computed tile surface lists avoid per-frame searches
- Animation: Frame-based animation reduces computation vs. bone systems
- Audio: Cached music and sound effects, no runtime decoding
- Delta Time: Frame-rate independent logic for smooth gameplay
Known Limitations
- Single-threaded architecture (SDL3 requires single-thread rendering)
- Surfaces stored entirely in CPU memory (not GPU-side textures)
- Palette system requires full surface redraw when changing colors
11. File Format Reference
Animation Files (.ani)
List of animation names, one per line:
default
jump
run
fall
Room Data Files (.room)
Key-value pairs defining room properties:
number=01
name=Starting Room
bg_color=0x000000
border_color=0xFF00FF
...
Tilemap Files (.tmx)
Tiled map format with tileset and collision data.
Palette Files (.pal)
Binary 256-color palette format (256 × 4 bytes RGBA).
12. Quick Reference: Main Entry Points
For Graphics Issues
Screen::render()- Main rendering methodScreen::setPalete()- Palette applicationSurfaceclass - Pixel buffer operations
For Input Issues
Input::checkInput()- Input state checkingDirector::initInput()- Binding configuration
For Audio Issues
Audio::playMusic()- Music playbackAudio::playSound()- SFX playbackjail_audiolibrary - Low-level audio operations
For Game Logic
Game::run()- Main game loopRoomclass - Collision and level logicPlayer::update()- Physics and movement
For Asset Management
Asset::add()- Asset registrationResource::load()- Asset loadingDirector::setFileList()- Complete asset registry
13. Future Enhancement Points
Identified Areas for Expansion
- Network Play: Stats upload to online service
- More Scenes: Additional game modes
- Custom Rooms: Level editor integration
- Audio Streaming: Background music without loading entire file
- Particle System: Visual effects for pickups/collisions
- Controller Feedback: Haptic feedback for game events
- Accessibility: Font scaling, color-blind modes, key remapping UI
Development Notes
- Language: All code comments and some variable names are in Spanish (maintaining original author style)
- Compilation: Use CMake - automatically handles platform differences
- Performance Profiling: Use debug overlay (F12) for basic metrics; consider external profilers for deeper analysis
- Common Warnings: The codebase has been cleaned of the
struct/classforward declaration inconsistency warnings that previously appeared
Important Code Consistency Rules
-
Forward Declarations: Always use
classfor forward declarations of classes defined withclass(notstruct). TheSurfaceclass is defined asclass Surfaceinsurface.hpp:57, so all forward declarations must useclass Surface; -
Singleton Pattern: When creating new singletons, follow the existing pattern:
- Private static instance pointer
- Private constructor/destructor
- Public
init(),destroy(), andget()methods
-
Delta Time: Always use
DeltaTimerfor frame-rate independent physics calculations -
Memory Management: Prefer
std::shared_ptrfor shared resources (Surfaces, Sprites) andstd::unique_ptrfor sole ownership
Last Updated: November 2022 (per README) Original Author: JailDesigner Repository: Gitea (internal)