Files
snake/.github/copilot-instructions.md
T

10 KiB

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:

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:

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:

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:

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:

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:

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:
    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:

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

Questions?

Refer to the project structure, examples in existing code, or ask the agent to reference this document when making changes.