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
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
game/ modules are pure logic with no framework coupling. scenes/ imports from game/ but never vice versa.
Data Flow
createInitialState() builds shuffled deck, deals 10 cards each, empty table
GameScene.nextTurn() detects current player: human → enable input; AI → delay + chooseMove()
applyMove() returns new GameState + capture result + scopa flag
GameScene.executeMove() animates: card flight → capture burst → pile collection
updateScoreBar() reflects live team stats with animated counter tweens
- When all hands empty →
calculateScores() → round-end overlay
- 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) |