Architecture
Last Updated: 2026-04-09T20:59:51.000Z
Overview
| Attribute |
Value |
| Primary language |
TypeScript |
| Secondary language |
Java |
| Source counts |
15 TypeScript files under src/, 3 Java files under android/ |
| Project type |
Phaser browser game packaged for Android with Capacitor |
| Framework |
Phaser 3.87.0 |
| Tooling |
Vite 5, TypeScript 5.x, Capacitor 8.3, tsx 4.19 |
| 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 implementation of Scopone Scientifico. Pure game rules, scoring, imperfect-information tracking, AI heuristics, worker transport, and benchmark code live under src/game/. Phaser scenes under src/scenes/ own rendering, input, animation, menu flow, status messaging, and procedural audio. The android/ tree is a Capacitor wrapper with a small custom MainActivity that forces immersive full-screen behavior.
Project Structure
Key Directories
| Directory |
Purpose |
src/game/ |
Rules engine, score calculation, imperfect-information tracking, audio preference persistence, AI heuristics, worker transport, and benchmark harnesses |
src/scenes/ |
Phaser scene lifecycle, menus, settings UI, board rendering, interaction, HUD, audio, and FX |
public/ |
Atlas metadata and static assets loaded by Phaser |
android/ |
Capacitor Android project, Gradle configuration, generated wrapper assets, and the native activity |
docs/ |
Architecture, code style, findings, and cache metadata |
prompts/ |
JIRA workflow artifacts and iteration state |
Design Patterns
No explicit GoF patterns were detected in the source or by semantic search.
Observed architectural patterns:
| Pattern |
Where it appears |
| Scene-based flow |
BootScene -> MenuScene -> GameScene, with SettingsScene opened from the menu and returning to it |
| Functional core / imperative shell |
src/game/ avoids Phaser imports while src/scenes/ owns runtime side effects |
| Immutable state transitions |
applyMove() clones GameState before mutating round state |
| Worker offload with fallback |
AIWorkerClient uses ai.worker.ts when available and falls back to direct chooseMove() otherwise |
| Typed message protocol |
ai-worker-protocol.ts defines worker request, progress, result, and serialized error shapes |
| Persistence adapter |
preferences.ts normalizes and stores audio settings through a storage boundary instead of scene-local flags |
| Deterministic benchmark harness |
ai-benchmark.ts uses fixtures, seeded self-play, and simulated timing sources to evaluate AI quality |
Key Components
src/main.ts
- Creates the
Phaser.Game instance.
- Installs a one-shot fullscreen request on first user input when supported.
- Registers
BootScene, MenuScene, GameScene, and a local SettingsScene placeholder; MenuScene replaces that placeholder with the concrete src/scenes/SettingsScene.ts class before navigation.
src/game/types.ts
- Defines the core game model:
Card, Capture, Player, GameState, TeamScore, and ScoreBreakdown.
- Models constrained domains with unions such as
PlayerIndex, Difficulty, and DealerRelativeRole.
- Stores
PRIMIERA_VALUES for end-of-round scoring.
src/game/engine.ts
- Builds and shuffles the 40-card deck.
- Creates a round state for four players with dealer-relative opening order and stable player labels.
- Implements Scopone capture rules where direct value matches take priority over subset-sum captures.
- Applies moves immutably, awards scopa only before the final play, assigns leftover table cards to the last capturing team, and computes round and match scoring.
src/game/card-tracker.ts
- Tracks cards visible through play and capture events without exposing hidden hands.
- Reconstructs unseen cards from
played + myHand + table.
- Supplies suit counts, same-rank residue summaries, and hand-value probabilities used by the AI tiers.
src/game/preferences.ts
- Defines the persisted audio preference model.
- Normalizes possibly invalid storage payloads back to safe defaults.
- Loads and saves preferences through
localStorage when available, with browser-safe fallbacks.
src/game/ai.ts
- Exposes
chooseMove() as the async AI entry point.
- Implements three difficulty tiers:
beginner, advanced, and master.
- Uses role-aware heuristics, tracker-based inference, tactical priority scoring, determinization sampling, and alpha-beta search.
- Applies dynamic master search profiles based on total cards remaining, ranging from the base profile
4300 ms / 8 samples / depth 5 / batch 2 down to 3200 ms / 4 samples / exact endgame depth / batch 1 in the last four cards.
src/game/ai-worker-protocol.ts
- Defines a single
choose-move worker request type.
- Defines typed progress, result, and serialized error responses.
- Keeps worker communication schema isolated from UI code.
src/game/ai-worker-client.ts
- Wraps worker lifecycle and pending-request tracking behind the same
chooseMove() API that scenes consume.
- Creates the worker as an ES module with
new Worker(new URL('./ai.worker.ts', import.meta.url), { type: 'module' }).
- Fails over pending requests to in-thread AI execution if worker creation, posting, or deserialization fails.
src/game/ai.worker.ts
- Rehydrates
CardTracker from a serialized snapshot.
- Delegates move selection to
chooseMove().
- Posts progress, result, or serialized error messages back to the main thread.
src/game/ai-benchmark.ts and src/game/ai-benchmark-fixtures.ts
- Define the AI quality harness invoked by
npm run benchmark:ai-quality.
- Combine fixed fixtures, critical-concept checks, seeded self-play, regression watchlists, and simulated timing sources.
- Encode the current iteration 5 benchmark contract: 13 fixed fixtures, 6 critical concepts, and 48 self-play matches.
src/scenes/BootScene.ts
- Loads the card atlas and card back.
- Displays a simple loading bar.
- Transitions into
MenuScene after asset load.
- Renders the title, compact rules summary, difficulty selection, and audio-settings entry point.
- Reads persisted audio preferences to describe current state before match start.
- Ensures the concrete
SettingsScene class is registered before opening it.
src/scenes/SettingsScene.ts
- Provides a dedicated audio settings surface.
- Toggles music and effects independently.
- Saves each change immediately and returns control to the menu scene.
src/scenes/GameScene.ts
- Owns match flow, dealing, selection, capture resolution, AI turn orchestration, score HUD, status UI, think bar, particles, and procedural audio.
- Instantiates and disposes
AIWorkerClient on scene lifecycle events.
- Enforces a minimum AI think display time and timer-based move outcome status messages.
- Reads normalized audio preferences from scene data or persisted storage.
- Updates
CardTracker after play and capture events so AI inference remains derived from visible information.
android/app/src/main/java/com/phaser/scopa/MainActivity.java
- Extends
BridgeActivity.
- Applies immersive mode during
onCreate() and whenever window focus returns.
- Hides status and navigation bars with transient swipe behavior.
Dependencies
JavaScript production dependencies
| Package |
Version |
Purpose |
phaser |
^3.87.0 |
Game engine runtime |
@capacitor/core |
^8.3.0 |
Capacitor runtime bridge |
@capacitor/cli |
^8.3.0 |
Capacitor project tooling |
@capacitor/android |
^8.3.0 |
Android platform integration |
JavaScript development dependencies
| Package |
Version |
Purpose |
tsx |
^4.19.2 |
TypeScript execution for benchmark and prompt-local tooling |
typescript |
^5.0.0 |
Static type checking and TS compilation step |
vite |
^5.0.0 |
Dev server and production bundler |
Android / Gradle dependencies
| Dependency |
Version |
Source |
Purpose |
com.android.tools.build:gradle |
8.13.0 |
android/build.gradle |
Android Gradle plugin |
com.google.gms:google-services |
4.4.4 |
android/build.gradle |
Optional Google services integration |
androidx.appcompat:appcompat |
1.7.1 |
android/variables.gradle |
Android app compatibility |
androidx.coordinatorlayout:coordinatorlayout |
1.3.0 |
android/variables.gradle |
Layout coordination helpers |
androidx.core:core-splashscreen |
1.2.0 |
android/variables.gradle |
Splash screen support |
junit:junit |
4.13.2 |
android/variables.gradle |
JVM Android tests |
androidx.test.ext:junit |
1.3.0 |
android/variables.gradle |
Instrumented test runner |
androidx.test.espresso:espresso-core |
3.7.0 |
android/variables.gradle |
Instrumented UI testing |
Platform configuration
compileSdkVersion: 36
targetSdkVersion: 36
minSdkVersion: 24
Module Organization
Application-level dependency direction is one-way:
src/game/ imports only from sibling game modules.
src/scenes/ imports from src/game/ and Phaser.
src/game/ never imports Phaser.
Data Flow
main.ts creates the Phaser app, installs fullscreen-on-first-input, and registers the scene list.
BootScene loads atlas assets and starts MenuScene.
MenuScene reads persisted audio preferences, lets the player choose difficulty, and can open SettingsScene for audio toggles.
SettingsScene writes audio preferences immediately and returns to MenuScene.
GameScene.create() normalizes incoming scene data, creates a fresh CardTracker, constructs a new GameState, and starts the opening deal.
- Human turns use pointer-driven card selection and
findCaptures() output to choose legal captures.
- AI turns call
AIWorkerClient.chooseMove(state, playerIdx, difficulty, tracker, onProgress).
AIWorkerClient posts a typed request to ai.worker.ts; if workers are unavailable, it reruns the same request in-thread.
chooseMove() returns a heuristic move for lower tiers or performs batched master search while emitting AIDecisionProgress.
GameScene updates the think bar from progress callbacks, enforces a minimum visible think time, executes the returned move, records tracker state, and advances turn order.
- When every hand is empty,
engine.ts finalizes scoring and GameScene presents the round or match outcome.
- Separately,
ai-benchmark.ts exercises the same game and AI modules through fixed fixtures, seeded self-play, and simulated timing to produce quality summaries.
Build System
| Command |
Source |
Purpose |
npm run dev |
package.json |
Starts the Vite dev server |
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 with Vite preview |
npm run benchmark:ai-quality |
package.json |
Runs the AI benchmark harness through tsx |
npx tsc --noEmit |
user-provided test command |
Type-checks the TypeScript codebase without emitting files |