Extended the code using Copilot with tons of cool features. Read the CHAT.md for what I asked and what it did
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user