12 KiB
12 KiB
Architecture
Last Updated: 2026-04-10T20:33:00.000Z
Overview
| Attribute | Value |
|---|---|
| Primary language | TypeScript |
| Secondary language | Java |
| Source counts | 17 TypeScript source files under src/, 3 Java files under android/app/src/ |
| Project type | Phaser browser game packaged for Android with Capacitor |
| Framework | Phaser 3.87.0 |
| Tooling | Vite 5.x, TypeScript 5.x, Capacitor 8.3.x, tsx 4.19.x |
| Runtime layout | 1280 x 720, Phaser.Scale.FIT, centered in #game |
| Build command | npm run build |
| Verification command | npx tsc --noEmit |
The repository is a TypeScript-first implementation of Scopone Scientifico. Pure rules, scoring, imperfect-information tracking, AI heuristics, worker transport, and benchmark code live under src/game/. Phaser scenes under src/scenes/ own rendering, input, pacing, menus, and procedural audio. The android/ tree is a Capacitor wrapper with Gradle configuration and a custom MainActivity for immersive full-screen behavior.
Project Structure
scopone-phaser/
|- src/
| |- main.ts
| |- game/
| | |- ai-benchmark-fixtures.ts
| | |- ai-benchmark.ts
| | |- ai-worker-client.ts
| | |- ai-worker-protocol.ts
| | |- ai.ts
| | |- ai.worker.ts
| | |- card-tracker.ts
| | |- engine.ts
| | |- preferences.ts
| | `- types.ts
| `- scenes/
| |- BootScene.ts
| |- GameScene.ts
| |- MenuScene.ts
| `- SettingsScene.ts
|- public/
|- android/
| |- app/
| |- build.gradle
| `- variables.gradle
|- docs/
|- prompts/
|- package.json
|- tsconfig.json
|- vite.config.ts
`- capacitor.config.ts
Key Directories
| Directory | Purpose |
|---|---|
src/game/ |
Rules engine, state transitions, score calculation, hidden-information tracking, audio preference persistence, AI heuristics, worker protocol, and benchmark harnesses |
src/scenes/ |
Phaser scene lifecycle, menu flow, settings UI, board rendering, HUD, interaction, particles, and audio playback |
public/ |
Card atlas metadata and other static assets loaded by Phaser |
android/ |
Capacitor Android project, Gradle configuration, generated wrapper assets, and native activity code |
docs/ |
Architecture, code style, findings, and cache metadata |
prompts/ |
JIRA workflow artifacts and iteration state |
Design Patterns
No explicit design patterns were detected 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 applying turn mutations |
| 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 |
| 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.Gameinstance. - Installs a one-shot fullscreen request on first user input when supported.
- Registers
BootScene,MenuScene,GameScene, andSettingsScenedirectly in the Phaser game config.
src/game/types.ts
- Defines the core game model:
Card,Capture,Player,GameState,TeamScore, andScoreBreakdown. - Models constrained domains with unions such as
PlayerIndex,Difficulty, andDealerRelativeRole. - Stores
PRIMIERA_VALUESfor primiera scoring.
src/game/engine.ts
- Builds and shuffles the 40-card deck.
- Creates a four-player round state 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 scoring.
src/game/card-tracker.ts
- Tracks cards visible through play and capture events without exposing hidden hands.
- Reconstructs unseen cards from visible information.
- Supplies suit counts, same-rank residue summaries, and probability signals used by higher AI tiers.
src/game/preferences.ts
- Defines the persisted audio preference model.
- Normalizes invalid storage payloads back to safe defaults.
- Loads and saves preferences through
localStoragewhen available, with browser-safe fallbacks.
src/game/ai.ts
- Exposes
chooseMove()as the async AI entry point. - Implements three difficulty tiers:
beginner,advanced, andmaster. - Uses tracker-based inference, tactical scoring, determinization sampling, and alpha-beta search.
- Applies dynamic master search profiles, with a base profile of
4300 ms / 8 samples / depth 5 / batch 2and tighter endgame branches down to3200 ms / 4 samples / exact remaining depth / batch 1.
src/game/ai-worker-protocol.ts
- Defines a single
choose-moveworker request type. - Defines typed progress, result, and serialized error responses.
- Keeps worker communication schema isolated from scene code.
src/game/ai-worker-client.ts
- Wraps worker lifecycle and pending-request tracking behind the same
chooseMove()API 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
CardTrackerfrom 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 exposed as
npm run benchmark:ai-quality. - Combine fixed fixtures, critical-concept checks, seeded self-play, and simulated timing sources.
- Enforce the current iteration 5 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 loading bar.
- Transitions into
MenuSceneafter asset load.
src/scenes/MenuScene.ts
- Renders the title, rules summary, difficulty selection, and audio-settings entry point.
- Reads persisted audio preferences before match start.
- Computes separate desktop and compact-viewport layouts for responsive menu composition.
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 orchestration, score HUD, status UI, think bar, particles, and procedural audio.
- Instantiates and disposes
AIWorkerClienton scene lifecycle events. - Enforces
AI_MIN_THINK_MS = 1000andMOVE_OUTCOME_STATUS_MS = 2000through timer-backed scene logic. - Reads normalized audio preferences from scene data or persisted storage.
- Updates
CardTrackerafter 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 when 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: 36targetSdkVersion: 36minSdkVersion: 24
Module Organization
main.ts
-> BootScene
-> MenuScene
-> SettingsScene
-> GameScene
-> engine.ts
-> types.ts
-> preferences.ts
-> card-tracker.ts
-> ai-worker-client.ts
-> ai-worker-protocol.ts
-> ai.worker.ts
-> ai.ts
ai-benchmark.ts
-> ai.ts
-> ai-benchmark-fixtures.ts
-> engine.ts
-> card-tracker.ts
Application-level dependency direction is one-way:
src/game/imports only from sibling game modules.src/scenes/imports fromsrc/game/and Phaser.src/game/never imports Phaser.
Data Flow
main.tscreates the Phaser app, installs fullscreen-on-first-input, and registers the scene list.BootSceneloads atlas assets and startsMenuScene.MenuScenereads persisted audio preferences, lets the player choose difficulty, and can openSettingsScenefor audio toggles.SettingsScenewrites audio preferences immediately and returns toMenuScene.GameScene.create()normalizes incoming scene data, creates a freshCardTracker, creates a newGameState, 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). AIWorkerClientposts a typed request toai.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 emittingAIDecisionProgress.GameSceneupdates 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.tsfinalizes scoring andGameScenepresents the round or match outcome. - Separately,
ai-benchmark.tsexercises 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 |
package.json |
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 |
inferred TypeScript verification | Type-checks the codebase without emitting build artifacts |
No top-level tests/ directory was discovered in the repository.