golfgame/CLAUDE.md
adlee-was-taken 7d28e83a49 Update CLAUDE.md with AI safety checks and architecture
- Add AI decision safety checks documentation
- Add simulation testing commands
- Update architecture with services/, stores/, and new files
- Add PostgreSQL to dependencies

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-14 08:42:38 -05:00

6.8 KiB

Golf Card Game - Project Context

A real-time multiplayer 6-card Golf card game with CPU opponents and smooth anime.js animations.

Quick Start

# Install dependencies
pip install -r server/requirements.txt

# Run the server
python server/main.py

# Visit http://localhost:5000

Architecture

golfgame/
├── server/                    # Python FastAPI backend
│   ├── main.py                # HTTP routes, WebSocket handling
│   ├── game.py                # Game logic, state machine
│   ├── ai.py                  # CPU opponent AI with timing/personality
│   ├── simulate.py            # AI simulation runner with stats
│   ├── game_analyzer.py       # Query tools for game analysis
│   ├── stores/
│   │   └── event_store.py     # PostgreSQL event sourcing
│   └── services/
│       └── game_logger.py     # Game move logging to PostgreSQL
│
├── client/                    # Vanilla JS frontend
│   ├── app.js                 # Main game controller
│   ├── card-animations.js     # Unified anime.js animation system
│   ├── card-manager.js        # DOM management for cards
│   ├── animation-queue.js     # Animation sequencing
│   ├── timing-config.js       # Centralized timing configuration
│   ├── state-differ.js        # Diff game state for animations
│   ├── style.css              # Styles (NO card transitions)
│   └── ANIMATIONS.md          # Animation system documentation
│
└── docs/
    ├── v2/                    # V2 feature docs (event sourcing, auth, etc.)
    └── v3/                    # V3 feature planning documents

Key Technical Decisions

Animation System

When to use anime.js vs CSS:

  • Anime.js (CardAnimations): Card movements, flips, swaps, draws - anything involving card elements
  • CSS keyframes/transitions: Simple UI feedback (button hover, badge entrance, status message fades) - non-card elements

General rule: If it moves a card, use anime.js. If it's UI chrome, CSS is fine.

  • See client/ANIMATIONS.md for full documentation
  • CardAnimations class in card-animations.js handles everything
  • Timing configured in timing-config.js

State Management

  • Server is source of truth
  • Client receives full game state on each update
  • state-differ.js computes diffs to trigger appropriate animations

Animation Race Condition Flags

Several flags in app.js prevent renderGame() from updating the discard pile during animations:

Flag Purpose
isDrawAnimating Local or opponent draw animation in progress
localDiscardAnimating Local player discarding drawn card
opponentDiscardAnimating Opponent discarding without swap
opponentSwapAnimation Opponent swap animation in progress
dealAnimationInProgress Deal animation running (suppresses flip prompts)

Critical: These flags must be cleared in ALL code paths (success, error, fallback). Failure to clear causes UI to freeze.

Clear flags when:

  • Animation completes (callback)
  • New animation starts (clear stale flags)
  • your_turn message received (safety clear)
  • Error/fallback paths

CPU Players

  • AI logic in server/ai.py
  • Configurable timing delays for natural feel
  • Multiple personality types affect decision-making (pair hunters, aggressive, conservative, etc.)

AI Decision Safety Checks:

  • Never swap high cards (8+) into unknown positions (expected value ~4.5)
  • Unpredictability has value threshold (7) to prevent obviously bad random plays
  • Comeback bonus only applies to cards < 8
  • Denial logic skips hidden positions for 8+ cards

Testing AI with simulations:

# Run 500 games and check dumb move rate
python server/simulate.py 500

# Detailed single game output
python server/simulate.py 1 --detailed

# Compare rule presets
python server/simulate.py 100 --compare

Common Development Tasks

Adjusting Animation Speed

Edit timing-config.js - all timings are centralized there.

Adding New Animations

  1. Add method to CardAnimations class in card-animations.js
  2. Use anime.js, not CSS transitions
  3. Track in activeAnimations Map for cancellation support
  4. Add timing config to timing-config.js if needed

Debugging Animations

// Check what's animating
console.log(window.cardAnimations.activeAnimations);

// Force cleanup
window.cardAnimations.cancelAll();

// Check timing config
console.log(window.TIMING);

Testing CPU Behavior

Adjust delays in server/ai.py CPU_TIMING dict.

Important Patterns

No CSS Transitions on Cards

Cards animate via anime.js only. The following should NOT have transition (especially on transform):

  • .card, .card-inner
  • .real-card, .swap-card
  • .held-card-floating

Card hover effects are handled by CardAnimations.hoverIn()/hoverOut() methods. CSS may still use box-shadow transitions for hover glow effects.

State Differ Logic (triggerAnimationsForStateChange)

The state differ in app.js detects what changed between game states:

STEP 1: Draw Detection

  • Detects when drawn_card goes from null to something
  • Triggers draw animation (from deck or discard)
  • Sets isDrawAnimating flag

STEP 2: Discard/Swap Detection

  • Detects when discard_top changes and it was another player's turn
  • Triggers swap or discard animation
  • Important: Skip STEP 2 if STEP 1 detected a draw from discard (the discard change was from REMOVING a card, not adding one)

Animation Overlays

Complex animations create temporary overlay elements:

  1. Create .draw-anim-card positioned over source
  2. Hide original card (or set opacity: 0 on discard pile during draw-from-discard)
  3. Animate overlay
  4. Remove overlay, reveal updated card, restore visibility

Fire-and-Forget for Opponents

Opponent animations don't block - no callbacks needed:

cardAnimations.animateOpponentFlip(cardElement, cardData);

Common Animation Pitfalls

Card position before append: Always set left/top styles BEFORE appending overlay cards to body, otherwise they flash at (0,0).

Deal animation source: Use getDeckRect() for deal animations, not getDealerRect(). The dealer rect returns the whole player area, causing cards to animate at wrong size.

Element rects during hidden: visibility: hidden still allows getBoundingClientRect() to work. display: none does not.

Dependencies

Server

  • FastAPI
  • uvicorn
  • websockets
  • asyncpg (PostgreSQL async driver)
  • PostgreSQL database

Client

  • anime.js (animations)
  • No other frameworks

Game Rules Reference

  • 6 cards per player in 2x3 grid
  • Lower score wins
  • Matching columns cancel out (0 points)
  • Jokers are -2 points
  • Kings are 0 points
  • Game ends when a player flips all cards