Files
scopone/docs/ARCHITECTURE.md
Giancarmine Salucci 3d1f3e5eb4 chore: initial commit
2026-03-31 18:38:34 +02:00

148 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture
> Last Updated: 2026-03-31T00:00:00.000Z
## Overview
| Attribute | Value |
|-----------------|--------------------------------------------------------|
| **Language** | TypeScript (ES2020 target, strict mode) |
| **Type** | 2D card game — Scopone Scientifico |
| **Framework** | Phaser 3.87+ (scene-based game engine) |
| **Bundler** | Vite 5 |
| **Native** | Capacitor 8.3 (Android) |
| **Resolution** | 1280 × 720, FIT scaling with auto-center |
## Project Structure
```
scopone-phaser/
├── src/
│ ├── main.ts # Phaser.Game bootstrap + config
│ ├── game/
│ │ ├── types.ts # Card, Suit, Player, GameState, TeamScore, ScoreBreakdown, PRIMIERA_VALUES
│ │ ├── engine.ts # Deck build, shuffle, capture logic, applyMove, scoring, primiera
│ │ └── ai.ts # Heuristic AI: chooseMove, scoreCapture, scoreDump
│ └── scenes/
│ ├── BootScene.ts # Asset loading (atlas, card back image)
│ ├── MenuScene.ts # Start menu with rules summary
│ └── GameScene.ts # Main game: rendering, turn management, effects, audio
├── index.html # Entry point, Italian locale, green felt background
├── public/ # Static assets (atlas.json, atlas.png, retro.png)
├── android/ # Capacitor Android native shell
├── package.json
├── tsconfig.json
├── vite.config.ts
└── capacitor.config.ts
```
## Key Directories
| Directory | Purpose |
|--------------------|----------------------------------------------------------------|
| `src/game/` | Domain logic — types, game engine, AI (no Phaser dependency) |
| `src/scenes/` | Phaser scenes — rendering, input, effects, audio |
| `public/` | Static assets served by Vite (card atlas, card back) |
| `android/` | Capacitor-generated Android project (Gradle, Java) |
| `prompts/` | JIRA agent pipeline artifacts |
## Design Patterns
| Pattern | Where |
|-----------------------------|--------------------------------------------------------------------|
| **Scene lifecycle** | BootScene → MenuScene → GameScene (Phaser scene graph) |
| **Immutable state updates** | `applyMove()` deep-clones `GameState` before mutation |
| **Heuristic scoring** | AI evaluates all legal moves with weighted feature scores |
| **Separation of concerns** | `game/` has no Phaser imports; `scenes/` bridges game ↔ rendering |
| **Procedural audio** | Web Audio API oscillators + delay reverb — no audio files |
No explicit GoF patterns (singleton, factory, observer, DI) detected.
## Key Components
### `types.ts` — Domain Types
- `Card { suit, value, id }` — 40-card Napoletane deck (suits: bastoni, coppe, denara, spade; values 1-10)
- `GameState` — full round state: 4 players, table, current player, team scores, round tracking
- `TeamScore` — per-team stats: cards, scope, denari, settebello, primiera, round/total points
- `ScoreBreakdown` — which team won each scoring category
- `PRIMIERA_VALUES` — lookup table for primiera card values
### `engine.ts` — Game Logic
- `buildDeck()` / `shuffle()` — Fisher-Yates 40-card deck
- `createInitialState()` — deals 10 cards per player, empty table (Scopone Scientifico rules)
- `findCaptures(played, table)` — direct value match (mandatory) or subset-sum combinations
- `applyMove(state, player, card, captureChoice)` — immutable state transition, scopa detection, end-of-round scoring
- `calculateScores()` / `scoreRound()` — carte, denari, settebello, primiera, scope points
- `calcPrimiera(pile)` — best card per suit using `PRIMIERA_VALUES`
- `teamOf(playerIdx)` — team assignment: 0+2 = Team A, 1+3 = Team B
### `ai.ts` — Heuristic AI
- `chooseMove(state, playerIdx)` — evaluates all legal moves (captures + dumps)
- `scoreCapture()` — weighted: scopa (+500), settebello (+300), denari (+50 each), card count, primiera value, opponent threat
- `scoreDump()` — avoids giving opponents scopa (-400), prefers low-value non-denari, penalises dumping 7s and aces
### `GameScene.ts` — Main Scene (~1340 lines)
- Four-player layout: South (human), West/East (AI, rotated ±90°), North (AI partner)
- Deal animation with staggered tweens
- Card selection with postFX glow pulse
- Capture highlighting with multiple-choice UI
- Particle effects: capture burst, scopa explosion, settebello flash, denari shimmer, primiera glow, card trails, victory confetti
- Camera shake + flash on scopa and settebello
- Live score bar with animated counter updates
- Think bar progress indicator during AI turns
- Procedural background music (oscillator drone + triangle melody + chord stabs)
- Round-end summary panel and game-over screen
## Dependencies
### Production
| Package | Version | Purpose |
|----------------------|----------|--------------------------------------|
| `phaser` | ^3.87.0 | 2D game engine |
| `@capacitor/core` | ^8.3.0 | Capacitor runtime |
| `@capacitor/cli` | ^8.3.0 | Capacitor CLI |
| `@capacitor/android` | ^8.3.0 | Android platform plugin |
### Development
| Package | Version | Purpose |
|---------------|---------|--------------------------|
| `typescript` | ^5.0.0 | TypeScript compiler |
| `vite` | ^5.0.0 | Dev server and bundler |
## Module Organisation
```
main.ts ──→ BootScene ──→ MenuScene ──→ GameScene
├── game/engine (createInitialState, applyMove, findCaptures, ...)
├── game/ai (chooseMove)
└── game/types (Card, GameState, ...)
```
`game/` modules are pure logic with no framework coupling. `scenes/` imports from `game/` but never vice versa.
## Data Flow
1. `createInitialState()` builds shuffled deck, deals 10 cards each, empty table
2. `GameScene.nextTurn()` detects current player: human → enable input; AI → delay + `chooseMove()`
3. `applyMove()` returns new `GameState` + capture result + scopa flag
4. `GameScene.executeMove()` animates: card flight → capture burst → pile collection
5. `updateScoreBar()` reflects live team stats with animated counter tweens
6. When all hands empty → `calculateScores()` → round-end overlay
7. First team to 11 points → game-over screen → optional restart
## Build System
| Command | Action |
|--------------------|--------------------------------------------|
| `npm run dev` | `vite` — dev server on port 3000 |
| `npm run build` | `tsc && vite build` — compile + bundle to `dist/` |
| `npm run preview` | `vite preview` — preview production build |
| `tsc --noEmit` | Type-check only (no test framework) |