Files
snake/.github/copilot-instructions.md

307 lines
10 KiB
Markdown

# Snake CLI Game - Development Guidelines
## Overview
This project demonstrates clean Python architecture with separation of concerns, comprehensive type hints, robust error handling, and user-friendly design. These guidelines capture the principles and patterns established during the refactor.
## Architecture & Design
### Modular Structure
Separate concerns into dedicated modules:
- **Game Logic** (`snake.py`) - Core game loop and UI interaction
- **Constants** (`constants.py`) - Game configuration and magic numbers
- **Themes** (`theme.py`) - Color and UI styling
- **Sound** (`sound_manager.py`) - Audio effects and music (runs in separate process)
- **Persistence** (`high_score.py`, `game_config.py`) - Data storage and retrieval
**Pattern**: Each module has a single responsibility. Avoid mixing config, logic, and I/O.
**Example**: Don't hardcode speed values in the game loop—define them in `constants.py` and import them.
### Configuration Management
Use JSON files in the home directory for user preferences and data:
- `~/.snake_config.json` - User settings (speed, length, theme, effects)
- `~/.snake_scores.json` - High scores and game history
**Pattern**: Create dedicated manager classes (`GameConfig`, `HighScoreManager`) to handle persistence.
**Benefits**: Settings persist across sessions, users can manually edit configs, no directory pollution.
## Code Style & Quality
### Type Hints
Use comprehensive type hints on all functions:
```python
def ask_question(
self,
question: str,
value_type: type,
value_default,
value_min: int = 0,
value_max: int = 0
):
"""Ask user a question and validate the response."""
```
**When**: Every function parameter and return type.
**Why**: Enables better IDE support, catches bugs early, improves readability.
### Docstrings
Add docstrings to all modules, classes, and methods:
```python
def _handle_input(self, current_key: int, next_key: int) -> Optional[int]:
"""Handle player input and return new direction, or None if quitting."""
```
**Pattern**: One-liner for simple functions; paragraph for complex logic explaining intent and side effects.
### Error Handling & Graceful Degradation
When optional features fail (missing files, audio init), gracefully continue:
```python
def _load_effects(self) -> None:
"""Load all sound effects."""
for effect_name, filename in effect_files.items():
effect_path = self.effects_dir / filename
try:
if effect_path.exists():
self.sounds[effect_name] = pygame.mixer.Sound(str(effect_path))
except pygame.error as e:
print(f"Warning: Could not load effect {filename}: {e}")
```
**Pattern**: Try to load optional features; warn but don't crash if they're missing.
### File Paths
Always use absolute paths with `pathlib.Path`:
```python
from pathlib import Path
self.effects_dir = Path(__file__).parent / 'effects'
self.music_dir = Path(__file__).parent / 'music'
```
**Why**: Works regardless of where the script is invoked from. Relative paths break when run from different directories.
### Input Validation
Use loops instead of recursion to avoid stack overflow:
```python
while True:
# ... get input ...
if value_type == int:
try:
answer = int(answer)
if value_min <= answer <= value_max:
return answer
except ValueError:
pass
# Show error and retry (not recursive call)
self.print_center(f"ERROR: Must be {value_min}-{value_max}", 1, color)
time.sleep(2)
```
**Why**: Recursive validation can stack overflow on repeated bad input.
### Code Cleanliness
- Remove unnecessary semicolons (Python style)
- Remove commented-out code—if needed, use version control
- Use f-strings instead of `.format()` or `%` formatting
- Avoid `Any` type hints—be specific: `List[int]`, `Optional[str]`, etc.
## CLI Arguments & Configuration
When adding user-facing options, use `argparse`:
```python
def parse_args() -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description='Snake CLI Game')
parser.add_argument(
'--music',
action='store_true',
help='Enable background music'
)
return parser.parse_args()
```
**Pattern**: Arguments override saved preferences; save user choices back to config.
## Process Management
When spawning background processes (audio, workers):
1. Make processes **daemons** (`p.daemon = True`) so they don't block shutdown
2. Always **clean up** on exit:
```python
queue.put('quit')
p.join(timeout=2)
if p.is_alive():
p.terminate()
```
3. Use **queues** for inter-process communication (not globals)
## Testing & Documentation
### Integration Testing
For features involving file I/O, configuration, or multi-component interaction, write integration tests:
```python
def test_high_score_persistence():
"""Test that high scores are saved and loaded correctly."""
manager = HighScoreManager()
manager.save_game(score=100, length=15, speed=7)
# Reload from disk
manager2 = HighScoreManager()
assert manager2.high_score == 100
```
**When**: Testing config loader, score persistence, sound initialization
**Why**: Ensures data survives across restarts and different processes load correctly
**Pattern**:
1. Create temporary test data or use fixtures
2. Verify both save and load operations
3. Test error cases (missing files, corrupted JSON)
4. Clean up test files
Run with: `pytest tests/` or `python -m pytest`
### Manual Testing Checklist
For CLI games, manual testing covers behavior integration better than unit tests:
- [ ] Start game, verify colors/characters render
- [ ] Change speed at menu, verify timing in-game
- [ ] Hit pause (ESC), verify time display, resume works
- [ ] Lose game, verify high score shows and persists
- [ ] Check `~/.snake_config.json` was created with settings
- [ ] Test with terminal <20x10, verify error message
- [ ] Test with missing audio files, verify graceful fallback
### README
Always include a comprehensive `README.md` with:
- **Features**: What the project does
- **Installation**: Step-by-step setup
- **Usage**: Examples with common options
- **Controls/API**: Reference for users
- **Configuration**: How to customize
- **Troubleshooting**: Common issues and solutions
- **Architecture**: Project structure and design decisions
- **Changelog**: Version history
See [README.md](../README.md) for the pattern used here.
### Game Statistics & Telemetry
Track meaningful stats for debugging and user feedback:
- Game duration (tracks performance)
- Scores and history (user engagement)
- High scores (replayability metric)
Store in JSON with timestamps for later analysis.
## Performance Considerations
### Avoid Blocking
- Don't run long operations on the main game loop
- Move audio processing to separate process with queues
- Use `time.sleep()` for delays, not busy-wait loops
### Efficient Rendering
- Clear screen once per frame, not multiple times
- Update only changed elements when possible
- In curses, use `refresh()` once per iteration
## Refactoring & Maintenance
When improving existing code:
1. **Identify code smells**: Magic numbers, repeated patterns, unclear responsibility
2. **Extract modules**: One class/function per file when it exceeds 20-30 lines
3. **Add type hints**: Progressively add even to old code
4. **Fix bugs first**: Don't refactor and add features simultaneously
5. **Test manually**: For CLI games, run through gameplay scenarios
6. **Document changes**: Add to CHANGELOG, update README
## Example: Adding a New Feature
If adding a difficulty mode:
1. Add constants to `constants.py`
2. Create `DifficultyManager` class in new `difficulty.py`
3. Add config options to `GameConfig`
4. Update `Game` class to use the manager
5. Add CLI argument in `parse_args()`
6. Update README with new feature
7. Test with: `python snake.py --difficulty hard`
### Future Feature Architecture Patterns
When implementing planned features, follow these patterns:
**Obstacles On Map**: Create `obstacles.py` with `ObstacleManager` handling collision detection and serialization to config
**Power-ups**: Add `PowerUpManager` class, integrate with `game_config.py` for user selection and persistence
**Game Modes**: Add mode enum to `constants.py`, create mode-specific logic in separate modules (not inline)
**Leaderboard** (online): Create `leaderboard.py` with API integration, keep local `high_score.py` as fallback
**Custom Themes**: Extend `theme.py` with `ThemeLoader` that reads themes from `~/.snake_themes/` directory
**Replay System**: Add `GameRecorder` class that logs moves to JSON for playback/analysis
**Key principle**: Never inline new feature code into existing modules. Always create a dedicated module + manager class for substantial features (>50 lines of code).
## Common Patterns in This Project
| Pattern | Use Case | Example |
|---------|----------|---------|
| Manager Classes | Persistence and state | `HighScoreManager`, `GameConfig` |
| Dataclasses | Structured data | `GameRecord`, `ColorPair` |
| Type hints | All functions | `def load() -> None:` |
| Queues | Inter-process comms | Sound process queue |
| Graceful degradation | Optional features | Missing audio files |
| JSON config | User preferences | `~/.snake_config.json` |
| Absolute paths | Cross-directory execution | `Path(__file__).parent` |
| CLI args | User customization | `--music`, `--no-effects` |
## Anti-patterns to Avoid
❌ **Hardcoded values** → Use `constants.py`
❌ **Relative file paths** → Use `pathlib.Path(__file__).parent`
**Recursive input validation** → Use loops
**Mixing concerns** → Separate config, logic, UI, persistence
**Unhandled exceptions** → Catch and warn, don't crash
**Magic numbers** → Extract to constants
**Missing docstrings** → Document all functions
**Unused imports or code** → Remove or commit properly
## Resources
- **Type Hints**: [PEP 484](https://www.python.org/dev/peps/pep-0484/) and [Python typing docs](https://docs.python.org/3/library/typing.html)
- **Pathlib**: [Python pathlib docs](https://docs.python.org/3/library/pathlib.html)
- **Curses**: [Curses documentation](https://docs.python.org/3/library/curses.html)
- **Argparse**: [Argparse tutorial](https://docs.python.org/3/library/argparse.html)
- **Python Style**: [PEP 8](https://www.python.org/dev/peps/pep-0008/)
## Questions?
Refer to the project structure, examples in existing code, or ask the agent to reference this document when making changes.