# Terminal Client - Game Engine Testing Tool

## Overview

Interactive REPL (Read-Eval-Print Loop) terminal UI for testing the game engine directly without WebSockets or frontend dependencies. Built with Python's `cmd` module, Click, and Rich for a polished CLI experience.

**Purpose**: Rapid iteration on game engine development without needing to build/maintain a web frontend during core logic development.

**Key Feature**: Persistent in-memory state - the game engine `state_manager` stays loaded throughout your REPL session, allowing you to test gameplay flow without losing state between commands.

## Usage Modes

### Interactive REPL (Recommended)

Start the interactive shell for persistent in-memory state:

```bash
uv run python -m terminal_client
# Or: source .venv/bin/activate && python -m terminal_client

# Then type commands interactively:
⚾ > new_game
⚾ > defensive
⚾ > offensive
⚾ > resolve
⚾ > quick_play 10
⚾ > status
⚾ > quit
```

**Advantages**:
- ✅ Game state stays in memory across commands
- ✅ Fast iteration - no process startup overhead
- ✅ Natural workflow like `psql` or `redis-cli`
- ✅ Persistent event loop prevents database connection issues

### Standalone Commands (Alternative)

Run individual commands with persistent config file:

```bash
uv run python -m terminal_client new-game
uv run python -m terminal_client defensive --alignment normal
uv run python -m terminal_client offensive --approach power
uv run python -m terminal_client resolve
```

**Note**: Config file (`~/.terminal_client_config.json`) remembers current game between commands.

## Architecture

### Design Decisions

**Why Terminal UI?**
- ✅ Zero frontend overhead - test game logic immediately
- ✅ No WebSocket complexity - direct function calls to GameEngine
- ✅ Fast iteration - change code, test instantly
- ✅ Easy debugging - logs and state visible in same terminal
- ✅ CI/CD ready - can be scripted for automated testing

**Why Click over Typer?**
- ✅ Already installed (FastAPI dependency)
- ✅ Battle-tested and stable (v8.1.8)
- ✅ Python 3.13 compatible
- ✅ No version compatibility issues

**Why Rich?**
- ✅ Beautiful formatted output (colors, panels, tables)
- ✅ Clear game state visualization
- ✅ Better testing UX than plain print statements

**Why REPL (cmd module)?**
- ✅ Single persistent process - state manager stays in memory
- ✅ Persistent event loop - no database connection conflicts
- ✅ Command history and readline support
- ✅ Tab completion (future enhancement)

### Technology Stack

```python
click==8.1.8      # CLI framework (already installed via FastAPI)
rich==13.9.4      # Terminal formatting and colors
cmd (stdlib)      # REPL framework (built into Python)
```

## Project Structure

```
terminal_client/
├── __init__.py         # Package marker
├── __main__.py         # Entry point - routes to REPL or CLI
├── repl.py             # Interactive REPL with cmd.Cmd
├── main.py             # Click CLI standalone commands
├── display.py          # Rich formatting for game state
├── config.py           # Persistent configuration file manager
└── CLAUDE.md           # This file
```

## REPL Commands Reference

All commands in interactive REPL mode. Use underscores (e.g., `new_game` not `new-game`).

### new_game

Create a new game with lineups and start it (all-in-one).

```
⚾ > new_game [--league sba|pd] [--home-team N] [--away-team N]

Examples:
  new_game
  new_game --league pd
  new_game --home-team 5 --away-team 3
```

Automatically:
1. Creates game in database
2. Generates 9-player lineups for both teams
3. Starts the game
4. Sets as current game

### defensive

Submit defensive decision.

```
⚾ > defensive [--alignment TYPE] [--infield DEPTH] [--outfield DEPTH] [--hold BASES]

Options:
  --alignment    normal, shifted_left, shifted_right, extreme_shift
  --infield      in, normal, back, double_play
  --outfield     in, normal, back
  --hold         Comma-separated bases (e.g., 1,3)

Examples:
  defensive
  defensive --alignment shifted_left
  defensive --infield double_play --hold 1,3
```

### offensive

Submit offensive decision.

```
⚾ > offensive [--approach TYPE] [--steal BASES] [--hit-run] [--bunt]

Options:
  --approach     normal, contact, power, patient
  --steal        Comma-separated bases (e.g., 2,3)
  --hit-run      Enable hit-and-run
  --bunt         Attempt bunt

Examples:
  offensive
  offensive --approach power
  offensive --steal 2 --hit-run
```

### resolve

Resolve the current play.

```
⚾ > resolve
```

Both defensive and offensive decisions must be submitted first.
Displays play result with dice roll, runner advancement, and updated state.

### status

Display current game state.

```
⚾ > status
```

Shows score, inning, outs, runners, and pending decisions.

### quick_play

Auto-play multiple plays with default decisions.

```
⚾ > quick_play [COUNT]

Examples:
  quick_play        # Play 1 play
  quick_play 10     # Play 10 plays
  quick_play 27     # Play ~3 innings
```

Perfect for rapidly advancing game state for testing.

### box_score

Display box score.

```
⚾ > box_score
```

### list_games

List all games in state manager (in memory).

```
⚾ > list_games
```

Shows active games with current game marked.

### use_game

Switch to a different game.

```
⚾ > use_game <game_id>

Example:
  use_game a1b2c3d4-e5f6-7890-abcd-ef1234567890
```

### config

Show configuration.

```
⚾ > config
```

Displays config file path and current game.

### clear

Clear the screen.

```
⚾ > clear
```

### quit / exit

Exit the REPL.

```
⚾ > quit
⚾ > exit
```

Or press Ctrl+D.

---

## Standalone Commands Reference

For running individual commands outside REPL mode.

### new-game
```bash
uv run python -m terminal_client new-game [OPTIONS]

Options:
  --league TEXT        League (sba or pd) [default: sba]
  --game-id TEXT       Game UUID (auto-generated if not provided)
  --home-team INTEGER  Home team ID [default: 1]
  --away-team INTEGER  Away team ID [default: 2]

Example:
  uv run python -m terminal_client start-game --league sba
  uv run python -m terminal_client start-game --game-id <uuid> --home-team 5 --away-team 3
```

### Defensive Decision
```bash
uv run python -m terminal_client defensive [OPTIONS]

Options:
  --game-id TEXT    Game UUID (uses current if not provided)
  --alignment TEXT  Defensive alignment [default: normal]
                    Values: normal, shifted_left, shifted_right, extreme_shift
  --infield TEXT    Infield depth [default: normal]
                    Values: in, normal, back, double_play
  --outfield TEXT   Outfield depth [default: normal]
                    Values: in, normal, back
  --hold TEXT       Comma-separated bases to hold (e.g., 1,3)

Example:
  uv run python -m terminal_client defensive --alignment shifted_left
  uv run python -m terminal_client defensive --infield double_play --hold 1,3
```

### Offensive Decision
```bash
uv run python -m terminal_client offensive [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)
  --approach TEXT Batting approach [default: normal]
                  Values: normal, contact, power, patient
  --steal TEXT    Comma-separated bases to steal (e.g., 2,3)
  --hit-run       Hit-and-run play (flag)
  --bunt          Bunt attempt (flag)

Example:
  uv run python -m terminal_client offensive --approach power
  uv run python -m terminal_client offensive --steal 2 --hit-run
```

### Resolve Play
```bash
uv run python -m terminal_client resolve [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client resolve
```

Resolves the current play using submitted defensive and offensive decisions.
Displays full play result with dice rolls, runner advancement, and updated game state.

### Game Status
```bash
uv run python -m terminal_client status [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client status
```

### Box Score
```bash
uv run python -m terminal_client box-score [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client box-score
```

### Quick Play
```bash
uv run python -m terminal_client quick-play [OPTIONS]

Options:
  --count INTEGER   Number of plays to execute [default: 1]
  --game-id TEXT    Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client quick-play --count 10
  uv run python -m terminal_client quick-play --count 27  # Full inning
```

**Use Case**: Rapidly advance game state for testing specific scenarios (e.g., test 9th inning logic).

Submits default decisions (normal alignment, normal approach) and auto-resolves plays.

### List Games
```bash
uv run python -m terminal_client list-games

Example:
  uv run python -m terminal_client list-games
```

Shows all active games in the state manager.

### Use Game
```bash
uv run python -m terminal_client use-game <game_id>

Example:
  uv run python -m terminal_client use-game a1b2c3d4-e5f6-7890-abcd-ef1234567890
```

Switch to a different game (sets as "current" game for subsequent commands).

## Usage Patterns

### Typical REPL Testing Workflow

```bash
# Start REPL
uv run python -m terminal_client
# Or: source .venv/bin/activate && python -m terminal_client

# Create and play a game
⚾ > new_game
⚾ > defensive
⚾ > offensive
⚾ > resolve
⚾ > status

# Continue playing
⚾ > defensive --alignment shifted_left
⚾ > offensive --approach power
⚾ > resolve

# Or use quick-play to auto-advance
⚾ > quick_play 10

# Check final state
⚾ > status
⚾ > quit
```

**Advantages**: Game state stays in memory throughout the session!

### Testing Specific Scenarios

```bash
# Test defensive shifts
uv run python -m terminal_client start-game
uv run python -m terminal_client defensive --alignment shifted_left --infield double_play
uv run python -m terminal_client offensive
uv run python -m terminal_client resolve

# Test stealing
uv run python -m terminal_client start-game
uv run python -m terminal_client defensive
uv run python -m terminal_client offensive --steal 2,3  # Double steal
uv run python -m terminal_client resolve

# Test hit-and-run
uv run python -m terminal_client defensive
uv run python -m terminal_client offensive --hit-run
uv run python -m terminal_client resolve

# Advance to late game quickly
uv run python -m terminal_client start-game
uv run python -m terminal_client quick-play --count 50  # ~6 innings
uv run python -m terminal_client status
```

### Multiple Games

```bash
# Start game 1
uv run python -m terminal_client start-game
# ... play some ...

# Start game 2
uv run python -m terminal_client start-game
# ... play some ...

# List all games
uv run python -m terminal_client list-games

# Switch back to game 1
uv run python -m terminal_client use-game <game-1-uuid>
uv run python -m terminal_client status
```

## Display Features

### Game State Panel
- **Game Info**: UUID, league, status
- **Score**: Away vs Home
- **Inning**: Current inning and half
- **Outs**: Current out count
- **Runners**: Bases occupied with lineup IDs
- **Current Players**: Batter, pitcher (by lineup ID)
- **Pending Decision**: Enhanced display showing what action is needed next (Added 2025-10-28)
  - Shows "⚠️ WAITING FOR ACTION" header when play is pending
  - Displays specific message: "The defense needs to submit their decision" or "Ready to resolve play"
  - Shows exact command to run next: `defensive [OPTIONS]`, `offensive [OPTIONS]`, or `resolve`
  - Color-coded for clarity (yellow warning + cyan command hints)
- **Last Play**: Result description

### Play Result Panel
- **Outcome**: Hit type (GB, FB, LD, etc.)
- **Result Description**: Human-readable play result
- **Dice Roll**: Actual d20 roll with context
- **Outs/Runs**: Changes from play
- **Runner Movement**: Base-by-base advancement
- **Updated Score**: Current score after play

### Color Coding
- ✅ Green: Success messages, runs scored
- ❌ Red: Error messages, outs recorded
- ℹ️ Blue: Info messages
- ⚠️ Yellow: Warning messages
- Cyan: Game state highlights

## Implementation Details

### Persistent Event Loop (REPL Mode)

The REPL uses a single persistent event loop for the entire session:

```python
def __init__(self):
    # Create persistent event loop
    self.loop = asyncio.new_event_loop()
    asyncio.set_event_loop(self.loop)

def _run_async(self, coro):
    # Reuse same loop for all commands
    return self.loop.run_until_complete(coro)

def do_quit(self, arg):
    # Clean up on exit
    self.loop.close()
```

**Why**: Prevents database connection pool conflicts that occur when creating new event loops for each command.

### Game State Management

**REPL Mode**: Game state manager stays in memory throughout the session.
- In-memory state persists across all commands
- O(1) state lookups
- Perfect for testing gameplay flow

**Standalone Mode**: Uses persistent config file (`~/.terminal_client_config.json`).
- Stores current game UUID between command invocations
- Set automatically by `new-game` or `use-game`
- Used as default if `--game-id` not specified

### Async Command Pattern

All commands use the same async pattern:
```python
@cli.command()
@click.option('--game-id', default=None)
def some_command(game_id):
    async def _some_command():
        gid = UUID(game_id) if game_id else get_current_game()
        # ... async operations ...
        state = await game_engine.some_method(gid)
        display.show_something(state)

    asyncio.run(_some_command())
```

This allows direct async calls to GameEngine without WebSocket overhead.

### Error Handling

- **Click Abort**: Raises `click.Abort()` on errors (clean exit)
- **Rich Display**: All errors shown with colored formatting
- **Logger**: Exceptions logged with full traceback
- **User-Friendly**: Clear error messages (not stack traces)

## Testing with Terminal Client

### Unit Test Scenarios

```bash
# Test game startup validation
uv run python -m terminal_client start-game
# Should fail: No lineups set

# Test invalid decisions
uv run python -m terminal_client defensive --alignment invalid_value
# Should fail: ValidationError

# Test resolve without decisions
uv run python -m terminal_client resolve
# Should fail: No decisions submitted
```

### Integration Test Scenarios

```bash
# Full game flow
uv run python -m terminal_client start-game
# ... set lineups via database directly ...
uv run python -m terminal_client start-game  # Retry
uv run python -m terminal_client quick-play --count 100
uv run python -m terminal_client status
# Verify: Game status = "completed"

# State persistence
uv run python -m terminal_client start-game
# Note the game UUID
uv run python -m terminal_client quick-play --count 10
# Kill terminal
# Restart and use same UUID
uv run python -m terminal_client use-game <uuid>
uv run python -m terminal_client status
# Verify: State recovered from database
```

## Limitations

### Current Limitations (Phase 2)
1. **No Lineup Management**: Must set lineups via database directly
2. **No Player Data**: Shows lineup IDs only (no names/stats)
3. **Simple Box Score**: Only shows final scores (no detailed stats)
4. **No Substitutions**: Cannot make mid-game substitutions
5. **No AI Decisions**: All decisions manual (no AI opponent)

### Future Enhancements (Post-MVP)
- Interactive lineup builder
- Player name/stat display from league API
- Full box score with batting stats
- Substitution commands
- AI decision simulation
- Play-by-play export
- Replay mode for completed games

## Development Notes

### Adding New Commands

```python
# In main.py
@cli.command('new-command')
@click.option('--some-option', default='value', help='Description')
def new_command(some_option):
    """Command description for help text."""
    async def _new_command():
        # Implementation
        pass

    asyncio.run(_new_command())
```

### Adding New Display Functions

```python
# In display.py
def display_new_thing(data: SomeModel) -> None:
    """Display new thing with Rich formatting."""
    panel = Panel(
        Text("Content here"),
        title="[bold]Title[/bold]",
        border_style="color",
        box=box.ROUNDED
    )
    console.print(panel)
```

### Logging

All commands use the module logger:
```python
logger = logging.getLogger(f'{__name__}.main')

logger.info("Game started")
logger.error("Failed to resolve play", exc_info=True)
```

Logs appear in both terminal and `backend/logs/app_YYYYMMDD.log`.

## Troubleshooting

### Command Not Found
```bash
# Wrong
python terminal_client/main.py  # ❌

# Correct
uv run python -m terminal_client start-game  # ✅
```

### Import Errors
```bash
# Ensure you're in backend directory
cd backend

# Recommended: Use UV (auto-activates venv)
uv run python -m terminal_client start-game

# Alternative: Manually activate venv
source .venv/bin/activate
python -m terminal_client start-game
```

### Game Not Found
```bash
# Check active games
uv run python -m terminal_client list-games

# Use specific game ID
uv run python -m terminal_client status --game-id <uuid>
```

### Validation Errors
Check logs for detailed error messages:
```bash
tail -f logs/app_$(date +%Y%m%d).log
```

## Performance

### REPL Mode
- **Startup Time**: ~500ms (one-time on launch)
- **Command Execution**: <50ms (in-memory state lookups)
- **State Display**: <50ms (Rich rendering)
- **Quick Play (10 plays)**: ~4-5 seconds (includes 0.3s sleeps per play)
- **Memory**: ~10MB for active game state

### Standalone Mode
- **Per-Command Startup**: ~500ms (process + imports)
- **Command Execution**: <100ms (direct function calls)
- **Database Queries**: 50-100ms (config file + state recovery)

**REPL is significantly faster for iterative testing!**

## Configuration File

Location: `~/.terminal_client_config.json`

```json
{
  "current_game_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

Automatically managed by:
- `new_game` command (sets current)
- `use_game` command (changes current)
- `config --clear` command (clears current)

## Related Documentation

- **Game Engine**: `../app/core/game_engine.py`
- **State Manager**: `../app/core/state_manager.py`
- **Game Models**: `../app/models/game_models.py`
- **Backend Guide**: `../CLAUDE.md`

## Recent Updates

### 2025-10-28: Enhanced Status Display

**Improvement**: Added user-friendly pending action guidance to status command.

**Changes**:
- Status display now shows prominent "⚠️ WAITING FOR ACTION" section when play is pending
- Provides specific guidance based on game state:
  - "The defense needs to submit their decision" → Run `defensive [OPTIONS]`
  - "The offense needs to submit their decision" → Run `offensive [OPTIONS]`
  - "Ready to resolve play - both teams have decided" → Run `resolve`
- Command hints color-coded (yellow warnings + cyan command text + green command names)
- Makes testing workflow clearer by showing exactly what to do next

**Location**: `terminal_client/display.py:75-97`

**Usage Example**:
```bash
⚾ > status

╭─────────────────────────── Game State ───────────────────────────╮
│ ...                                                               │
│                                                                   │
│ ⚠️  WAITING FOR ACTION                                            │
│ ────────────────────────────────────────                          │
│ The defense needs to submit their decision.                       │
│ Run: defensive [OPTIONS]                                          │
│                                                                   │
╰───────────────────────────────────────────────────────────────────╯
```

---

### 2025-11-04: X-Check Testing & GameState Refactoring

**Major Update**: Added complete X-Check defensive play testing capabilities and fixed GameState structure.

**New Features**:

1. **X-Check Resolution Testing** - `resolve_with x-check <position>`
   - Test complete X-Check defensive plays with actual player ratings
   - Includes defense range tables, error charts, SPD tests, G2#/G3# conversions
   - Shows full XCheckResult audit trail with all resolution steps

   ```bash
   ⚾ > defensive
   ⚾ > offensive
   ⚾ > resolve_with x-check SS    # Test X-Check to shortstop
   ⚾ > resolve_with x-check LF    # Test X-Check to left field
   ```

   **What It Shows**:
   - Dice rolls (1d20 range + 3d6 error)
   - Defender ratings (range/error from PD API or defaults)
   - Defense table lookup (G1, G2, F3, SI2, DO3, etc.)
   - SPD test results (if applicable)
   - G2#/G3# conversion (playing in or holding runners)
   - Error chart lookup (NO, E1, E2, E3, RP)
   - Final outcome with error modifiers
   - Complete runner advancement

2. **X-Check Help Documentation** (8 commands documented)
   - `roll_jump` / `test_jump` - Stolen base jump dice testing
   - `roll_fielding` / `test_fielding` - Fielding dice testing
   - `test_location` - Hit location distribution testing
   - `rollback` - Undo plays
   - `force_wild_pitch` / `force_passed_ball` - Interrupt play testing
   - All commands now appear in `help` output

**Bug Fixes**:

1. **GameState Structure** - Updated for LineupPlayerState refactoring
   - `display.py`: Now accesses `state.current_batter.lineup_id` instead of `state.current_batter_lineup_id`
   - `repl.py`: Fixed typos (self.current_game → self.current_game_id)
   - All 105 terminal client tests passing

2. **Game Recovery** - Fixed validation error when loading saved games
   - State manager now creates placeholder `current_batter` during recovery
   - Placeholder is corrected by `_prepare_next_play()` after recovery

3. **DO3 Advancement** - Fixed incorrect batter advancement
   - DO3 now correctly: Batter reaches 2B (it's a double), runners advance 3 bases
   - Was incorrectly sending batter to 3B

**Testing Workflow**:
```bash
# Complete X-Check testing workflow
⚾ > new_game
⚾ > defensive --infield in          # Set defensive positioning
⚾ > offensive --approach power      # Set offensive approach
⚾ > resolve_with x-check 2B         # Test X-Check to second base

# See complete resolution:
# - Defense table: d20=15 + range=4 → G2
# - Error chart: 3d6=8 vs error=12 → NO
# - Final: G2 + NO = groundout to 2B
# - Runner advancement based on G2 tables
```

**Files Updated**:
- `terminal_client/repl.py` - Added x-check parsing to resolve_with
- `terminal_client/commands.py` - Added xcheck_position parameter
- `terminal_client/help_text.py` - Documented 8 X-Check commands
- `terminal_client/display.py` - Fixed GameState references
- `app/core/game_engine.py` - Added xcheck_position support
- `app/core/play_resolver.py` - Fixed DO3 batter advancement
- `app/core/runner_advancement.py` - Updated all 17 GameState references

**Test Coverage**:
- ✅ All 34 runner_advancement tests passing
- ✅ All 105 terminal_client tests passing
- ✅ All 26 state_manager tests passing

---

**Created**: 2025-10-26
**Author**: Claude
**Purpose**: Testing tool for Phase 2 game engine development
**Status**: ✅ Complete - Interactive REPL with persistent state and full X-Check testing!
**Last Updated**: 2025-11-04
