283 lines
8.6 KiB
Markdown
283 lines
8.6 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Build Commands
|
|
|
|
```bash
|
|
# Build the project (compiles to ./build directory)
|
|
make
|
|
|
|
# Or use the build script directly
|
|
./build.sh
|
|
|
|
# Run the game
|
|
make run
|
|
|
|
# Clean build artifacts
|
|
make clean
|
|
|
|
# The executable and all assets are in ./build/
|
|
cd build
|
|
./mcrogueface
|
|
```
|
|
|
|
## Project Architecture
|
|
|
|
McRogueFace is a C++ game engine with Python scripting support, designed for creating roguelike games. The architecture consists of:
|
|
|
|
### Core Engine (C++)
|
|
- **Entry Point**: `src/main.cpp` initializes the game engine
|
|
- **Scene System**: `Scene.h/cpp` manages game states
|
|
- **Entity System**: `UIEntity.h/cpp` provides game objects
|
|
- **Python Integration**: `McRFPy_API.h/cpp` exposes engine functionality to Python
|
|
- **UI Components**: `UIFrame`, `UICaption`, `UISprite`, `UIGrid` for rendering
|
|
|
|
### Game Logic (Python)
|
|
- **Main Script**: `src/scripts/game.py` contains game initialization and scene setup
|
|
- **Entity System**: `src/scripts/cos_entities.py` implements game entities (Player, Enemy, Boulder, etc.)
|
|
- **Level Generation**: `src/scripts/cos_level.py` uses BSP for procedural dungeon generation
|
|
- **Tile System**: `src/scripts/cos_tiles.py` implements Wave Function Collapse for tile placement
|
|
|
|
### Key Python API (`mcrfpy` module)
|
|
The C++ engine exposes these primary functions to Python:
|
|
- Scene Management: `createScene()`, `setScene()`, `sceneUI()`
|
|
- Entity Creation: `Entity()` with position and sprite properties
|
|
- Grid Management: `Grid()` for tilemap rendering
|
|
- Input Handling: `keypressScene()` for keyboard events
|
|
- Audio: `createSoundBuffer()`, `playSound()`, `setVolume()`
|
|
- Timers: `setTimer()`, `delTimer()` for event scheduling
|
|
|
|
## Development Workflow
|
|
|
|
### Running the Game
|
|
After building, the executable expects:
|
|
- `assets/` directory with sprites, fonts, and audio
|
|
- `scripts/` directory with Python game files
|
|
- Python 3.12 shared libraries in `./lib/`
|
|
|
|
### Modifying Game Logic
|
|
- Game scripts are in `src/scripts/`
|
|
- Main game entry is `game.py`
|
|
- Entity behavior in `cos_entities.py`
|
|
- Level generation in `cos_level.py`
|
|
|
|
### Adding New Features
|
|
1. C++ API additions go in `src/McRFPy_API.cpp`
|
|
2. Expose to Python using the existing binding pattern
|
|
3. Update Python scripts to use new functionality
|
|
|
|
## Testing Game Changes
|
|
|
|
Currently no automated test suite. Manual testing workflow:
|
|
1. Build with `make`
|
|
2. Run `make run` or `cd build && ./mcrogueface`
|
|
3. Test specific features through gameplay
|
|
4. Check console output for Python errors
|
|
|
|
### Quick Testing Commands
|
|
```bash
|
|
# Test basic functionality
|
|
make test
|
|
|
|
# Run in Python interactive mode
|
|
make python
|
|
|
|
# Test headless mode
|
|
cd build
|
|
./mcrogueface --headless -c "import mcrfpy; print('Headless test')"
|
|
```
|
|
|
|
## Common Development Tasks
|
|
|
|
### Compiling McRogueFace
|
|
```bash
|
|
# Standard build (to ./build directory)
|
|
make
|
|
|
|
# Full rebuild
|
|
make clean && make
|
|
|
|
# Manual CMake build
|
|
mkdir build && cd build
|
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
|
make -j$(nproc)
|
|
|
|
# The library path issue: if linking fails, check that libraries are in __lib/
|
|
# CMakeLists.txt expects: link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
|
```
|
|
|
|
### Running and Capturing Output
|
|
```bash
|
|
# Run with timeout and capture output
|
|
cd build
|
|
timeout 5 ./mcrogueface 2>&1 | tee output.log
|
|
|
|
# Run in background and kill after delay
|
|
./mcrogueface > output.txt 2>&1 & PID=$!; sleep 3; kill $PID 2>/dev/null
|
|
|
|
# Just capture first N lines (useful for crashes)
|
|
./mcrogueface 2>&1 | head -50
|
|
```
|
|
|
|
### Debugging with GDB
|
|
```bash
|
|
# Interactive debugging
|
|
gdb ./mcrogueface
|
|
(gdb) run
|
|
(gdb) bt # backtrace after crash
|
|
|
|
# Batch mode debugging (non-interactive)
|
|
gdb -batch -ex run -ex where -ex quit ./mcrogueface 2>&1
|
|
|
|
# Get just the backtrace after a crash
|
|
gdb -batch -ex "run" -ex "bt" ./mcrogueface 2>&1 | head -50
|
|
|
|
# Debug with specific commands
|
|
echo -e "run\nbt 5\nquit\ny" | gdb ./mcrogueface 2>&1
|
|
```
|
|
|
|
### Testing Different Python Scripts
|
|
```bash
|
|
# The game automatically runs build/scripts/game.py on startup
|
|
# To test different behavior:
|
|
|
|
# Option 1: Replace game.py temporarily
|
|
cd build
|
|
cp scripts/my_test_script.py scripts/game.py
|
|
./mcrogueface
|
|
|
|
# Option 2: Backup original and test
|
|
mv scripts/game.py scripts/game.py.bak
|
|
cp my_test.py scripts/game.py
|
|
./mcrogueface
|
|
mv scripts/game.py.bak scripts/game.py
|
|
|
|
# Option 3: For quick tests, create minimal game.py
|
|
echo 'import mcrfpy; print("Test"); mcrfpy.createScene("test")' > scripts/game.py
|
|
```
|
|
|
|
### Understanding Key Macros and Patterns
|
|
|
|
#### RET_PY_INSTANCE Macro (UIDrawable.h)
|
|
This macro handles converting C++ UI objects to their Python equivalents:
|
|
```cpp
|
|
RET_PY_INSTANCE(target);
|
|
// Expands to a switch on target->derived_type() that:
|
|
// 1. Allocates the correct Python object type (Frame, Caption, Sprite, Grid)
|
|
// 2. Sets the shared_ptr data member
|
|
// 3. Returns the PyObject*
|
|
```
|
|
|
|
#### Collection Patterns
|
|
- `UICollection` wraps `std::vector<std::shared_ptr<UIDrawable>>`
|
|
- `UIEntityCollection` wraps `std::list<std::shared_ptr<UIEntity>>`
|
|
- Different containers require different iteration code (vector vs list)
|
|
|
|
#### Python Object Creation Patterns
|
|
```cpp
|
|
// Pattern 1: Using tp_alloc (most common)
|
|
auto o = (PyUIFrameObject*)type->tp_alloc(type, 0);
|
|
o->data = std::make_shared<UIFrame>();
|
|
|
|
// Pattern 2: Getting type from module
|
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
|
auto o = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
|
|
|
// Pattern 3: Direct shared_ptr assignment
|
|
iterObj->data = self->data; // Shares the C++ object
|
|
```
|
|
|
|
### Working Directory Structure
|
|
```
|
|
build/
|
|
├── mcrogueface # The executable
|
|
├── scripts/
|
|
│ └── game.py # Auto-loaded Python script
|
|
├── assets/ # Copied from source during build
|
|
└── lib/ # Python libraries (copied from __lib/)
|
|
```
|
|
|
|
### Quick Iteration Tips
|
|
- Keep a test script ready for quick experiments
|
|
- Use `timeout` to auto-kill hanging processes
|
|
- The game expects a window manager; use Xvfb for headless testing
|
|
- Python errors go to stderr, game output to stdout
|
|
- Segfaults usually mean Python type initialization issues
|
|
|
|
## Important Notes
|
|
|
|
- The project uses SFML for graphics/audio and libtcod for roguelike utilities
|
|
- Python scripts are loaded at runtime from the `scripts/` directory
|
|
- Asset loading expects specific paths relative to the executable
|
|
- The game was created for 7DRL 2025 as "Crypt of Sokoban"
|
|
- Iterator implementations require careful handling of C++/Python boundaries
|
|
|
|
## Testing Guidelines
|
|
|
|
### Test-Driven Development
|
|
- **Always write tests first**: Create automation tests in `./tests/` for all bugs and new features
|
|
- **Practice TDD**: Write tests that fail to demonstrate the issue, then pass after the fix is applied
|
|
- **Close the loop**: Reproduce issue → change code → recompile → verify behavior change
|
|
|
|
### Two Types of Tests
|
|
|
|
#### 1. Direct Execution Tests (No Game Loop)
|
|
For tests that only need class initialization or direct code execution:
|
|
```python
|
|
# These tests can treat McRogueFace like a Python interpreter
|
|
import mcrfpy
|
|
|
|
# Test code here
|
|
result = mcrfpy.some_function()
|
|
assert result == expected_value
|
|
print("PASS" if condition else "FAIL")
|
|
```
|
|
|
|
#### 2. Game Loop Tests (Timer-Based)
|
|
For tests requiring rendering, game state, or elapsed time:
|
|
```python
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import sys
|
|
|
|
def run_test(runtime):
|
|
"""Timer callback - runs after game loop starts"""
|
|
# Now rendering is active, screenshots will work
|
|
automation.screenshot("test_result.png")
|
|
|
|
# Run your tests here
|
|
automation.click(100, 100)
|
|
|
|
# Always exit at the end
|
|
print("PASS" if success else "FAIL")
|
|
sys.exit(0)
|
|
|
|
# Set up the test scene
|
|
mcrfpy.createScene("test")
|
|
# ... add UI elements ...
|
|
|
|
# Schedule test to run after game loop starts
|
|
mcrfpy.setTimer("test", run_test, 100) # 0.1 seconds
|
|
```
|
|
|
|
### Key Testing Principles
|
|
- **Timer callbacks are essential**: Screenshots and UI interactions only work after the render loop starts
|
|
- **Use automation API**: Always create and examine screenshots when visual feedback is required
|
|
- **Exit properly**: Call `sys.exit()` at the end of timer-based tests to prevent hanging
|
|
- **Headless mode**: Use `--exec` flag for automated testing: `./mcrogueface --headless --exec tests/my_test.py`
|
|
|
|
### Example Test Pattern
|
|
```bash
|
|
# Run a test that requires game loop
|
|
./build/mcrogueface --headless --exec tests/issue_78_middle_click_test.py
|
|
|
|
# The test will:
|
|
# 1. Set up the scene during script execution
|
|
# 2. Register a timer callback
|
|
# 3. Game loop starts
|
|
# 4. Timer fires after 100ms
|
|
# 5. Test runs with full rendering available
|
|
# 6. Test takes screenshots and validates behavior
|
|
# 7. Test calls sys.exit() to terminate
|
|
``` |