Architecture
Last Updated: 2026-04-02T19:05:00.000Z
Overview
| Attribute |
Value |
| Primary language |
TypeScript |
| Secondary language |
Java (Capacitor Android shell) |
| Project type |
Browser card game with Android packaging |
| Framework |
Phaser 3.87.0 |
| Tooling |
Vite 5, TypeScript 5.x, Capacitor 8.3 |
| Runtime layout |
1280 x 720, Phaser.Scale.FIT, centered in #game |
| Build command |
npm run build |
| Test command |
npx tsc --noEmit |
The repository is a TypeScript-first Scopone Scientifico implementation. The web client contains the real game logic and presentation. The Android tree is a Capacitor wrapper with a custom immersive MainActivity.
Project Structure
Key Directories
| Directory |
Purpose |
src/game/ |
Framework-independent rules, scoring, imperfect-information tracking, and AI search |
src/scenes/ |
Phaser scene lifecycle, UI, animation, effects, and round orchestration |
public/ |
Static web assets consumed by Phaser loaders |
android/ |
Capacitor Android project, Gradle config, and immersive activity wrapper |
docs/ |
Architecture, code style, findings, and cache metadata |
prompts/ |
JIRA pipeline artifacts and iteration state |
Design Patterns
No explicit GoF patterns were detected in the source or by semantic search.
Observed architectural patterns in the current codebase:
| Pattern |
Where it appears |
| Scene-based flow |
BootScene -> MenuScene -> GameScene via Phaser scene registration |
| Functional core / imperative shell |
src/game/ stays free of Phaser imports, while src/scenes/ owns rendering and input |
| Clone-before-mutate state transitions |
applyMove() clones GameState before applying move effects |
| Worker offload with fallback |
AIWorkerClient runs heavy AI inside ai.worker.ts and falls back to in-thread chooseMove() if worker startup or messaging fails |
| Determinization search |
Master AI samples hidden hands before alpha-beta evaluation |
| Message-based progress reporting |
Worker and main thread exchange typed request/result/progress messages through ai-worker-protocol.ts |
Key Components
src/main.ts
- Bootstraps
Phaser.Game.
- Registers
BootScene, MenuScene, and GameScene.
- Installs a one-shot fullscreen request handler on first user interaction.
src/game/types.ts
- Defines the game model:
Card, Capture, Player, GameState, TeamScore, ScoreBreakdown.
- Encodes difficulty tiers as
'beginner' | 'advanced' | 'master'.
- Stores the
PRIMIERA_VALUES lookup table.
src/game/engine.ts (371 lines)
- Builds and shuffles the 40-card deck.
- Creates the initial round state for four players.
- Implements capture selection rules: single direct matches take priority; subset sums are considered only when no direct match exists.
- Applies moves immutably, detects scopas, assigns leftover table cards, and computes round scores.
src/game/card-tracker.ts (89 lines)
- Tracks seen cards across a round without exposing hidden hands directly.
- Computes unseen cards from
played + myHand + table.
- Supplies probability helpers used by the AI for value-based inference.
src/game/ai.ts
- Exposes
chooseMove() as an async entry point.
- Implements three difficulty levels:
beginner: noisy heuristic play.
advanced: stronger heuristics with race awareness, partner setup, and card-tracker inference.
master: determinization plus alpha-beta search with dynamic time budgets, batching, and progress callbacks.
- Uses
yieldToBrowser() between master-search batches so Phaser can repaint the think bar.
src/game/ai-worker-protocol.ts
- Defines the typed message contract between the main thread and the worker.
- Serializes requests around
GameState, Difficulty, PlayerIndex, tracker snapshots, progress, results, and worker-safe errors.
src/game/ai-worker-client.ts
- Wraps the worker lifecycle behind the same
chooseMove() API the scene needs.
- Creates module workers with
new Worker(new URL('./ai.worker.ts', import.meta.url), { type: 'module' }).
- Streams progress callbacks back into
GameScene and degrades to direct chooseMove() execution when workers are unavailable.
src/game/ai.worker.ts
- Rehydrates
CardTracker from a snapshot, delegates move selection to chooseMove(), and posts progress/result/error messages back to the scene thread.
- Keeps the expensive
master search off the main rendering thread when worker support is available.
src/scenes/BootScene.ts
- Loads the card atlas and card-back texture.
- Shows a simple progress bar and transitions into the menu.
- Renders the title screen and rules summary.
- Lets the player choose
beginner, advanced, or master difficulty.
- Starts
GameScene with the selected difficulty in scene data.
src/scenes/GameScene.ts
- Owns the match loop, HUD, think bar, card interaction, animation, FX, audio, and round transitions.
- Uses
CardTracker to record played and captured cards after each move.
- Instantiates
AIWorkerClient, bridges async AI progress into a visible top-of-screen think bar, and disposes worker resources on scene shutdown.
- Handles end-of-round overlays and full-match restart flow.
android/app/src/main/java/com/phaser/scopa/MainActivity.java
- Extends
BridgeActivity.
- Forces immersive mode by hiding status and navigation bars whenever the window gains focus.
Dependencies
JavaScript production dependencies
| Package |
Version |
Purpose |
phaser |
^3.87.0 |
Game engine |
@capacitor/core |
^8.3.0 |
Capacitor runtime |
@capacitor/cli |
^8.3.0 |
Capacitor tooling |
@capacitor/android |
^8.3.0 |
Android platform integration |
JavaScript development dependencies
| Package |
Version |
Purpose |
typescript |
^5.0.0 |
Type-checking and TS compilation for builds |
vite |
^5.0.0 |
Dev server and bundling |
Android / Gradle dependencies
| Dependency |
Source |
Purpose |
com.android.tools.build:gradle:8.13.0 |
android/build.gradle |
Android build plugin |
com.google.gms:google-services:4.4.4 |
android/build.gradle |
Optional Google services integration |
androidx.appcompat:appcompat |
android/app/build.gradle |
Android UI compatibility |
androidx.coordinatorlayout:coordinatorlayout |
android/app/build.gradle |
Android layout support |
androidx.core:core-splashscreen |
android/app/build.gradle |
Splash screen support |
junit:junit |
android/app/build.gradle |
JVM-side Android tests |
androidx.test.ext:junit |
android/app/build.gradle |
Instrumented Android testing |
androidx.test.espresso:espresso-core |
android/app/build.gradle |
Android UI testing |
Module Organization
Dependencies are one-directional at the application level:
src/game/ imports only from sibling game modules.
src/scenes/ imports from src/game/.
src/game/ never imports Phaser.
Data Flow
main.ts creates the Phaser app and registers all scenes.
BootScene loads assets, then starts MenuScene.
MenuScene passes the chosen difficulty into GameScene.
GameScene.create() initializes a new CardTracker, creates the initial GameState, and animates the opening deal.
- On each turn:
- Human turns use click-driven selection and capture highlighting.
- AI turns call
AIWorkerClient.chooseMove(state, playerIdx, difficulty, tracker, onProgress).
AIWorkerClient posts a typed request into ai.worker.ts; if worker setup fails, it falls back to in-thread chooseMove().
chooseMove() either returns immediately for heuristic tiers or performs batched master search while reporting AIDecisionProgress.
- Worker progress messages drive
GameScene.updateThinkBar() until a result is posted back.
GameScene.executeMove() applies the move, updates the tracker, animates the result, refreshes the HUD, and advances the round.
- When all hands are empty,
engine.ts finalizes scoring and GameScene displays the round summary or final match screen.
Build System
| Command |
Source |
Purpose |
npm run dev |
package.json |
Starts the Vite development server on port 3000 and opens the browser |
npm run build |
user-provided build command |
Runs tsc && vite build and writes web output to dist/ |
npm run preview |
package.json |
Serves the built app locally via Vite preview |
npx tsc --noEmit |
user-provided test command |
Type-checks the TypeScript codebase without emitting files |