5.6 KiB
5.6 KiB
Code Style
Last Updated: 2026-04-02T18:12:18.000Z
Language & Version
- Primary language: TypeScript 5.x.
- Compiler settings from
tsconfig.json:target: ES2020,module: ESNext,moduleResolution: bundler,strict: true,noEmit: true. - Secondary language: Java for the Capacitor Android wrapper.
Naming Conventions
| Kind | Convention | Real examples |
|---|---|---|
| Classes | PascalCase | BootScene, MenuScene, GameScene, CardTracker |
| Interfaces | PascalCase | Card, Capture, GameState, AIDecisionProgress, SearchProfile |
| Type aliases | PascalCase | Suit, PlayerIndex, Difficulty |
| Functions | camelCase | installFullscreenRequest, findCaptures, createInitialState, chooseMove, updateThinkBar |
| Constants | UPPER_SNAKE or descriptive const names |
SUITS, PRIMIERA_VALUES, SEARCH_PROFILES, SCOREBAR_H |
| Private fields | camelCase with private modifier |
state, tracker, thinkBar, selectedGlowTween |
| Parameters and locals | camelCase | playerIdx, captureChoice, remainingRatio, partnerHandSize |
Structural Conventions
- Phaser scenes extend
Phaser.Sceneand put setup increate()and asset loading inpreload(). - Core rule modules use exported functions and interfaces rather than classes.
- Stateful scene code uses
privatefields for long-lived UI and interaction state. - AI configuration is table-driven through
SEARCH_PROFILESand helper interfaces rather than nested literals insidechooseMove().
Indentation & Formatting
- TypeScript files use 2-space indentation.
- String literals use single quotes consistently.
- Semicolons are used consistently.
- Early returns are common for guard clauses.
- Multi-line object literals and function calls keep trailing commas when the surrounding style already uses them.
constis the default;letis used only where reassignment is needed.
The Android Java wrapper follows the generated template style instead of the TypeScript style. MainActivity.java uses tab indentation and standard Android brace placement.
Import Patterns
Named imports are used for local modules:
import { Card, GameState, PlayerIndex, Difficulty } from './types';
import { findCaptures, canCapture, teamOf, applyMove } from './engine';
import { CardTracker } from './card-tracker';
Framework imports use default or type-only imports where appropriate:
import Phaser from 'phaser';
import type { CapacitorConfig } from '@capacitor/cli';
The project uses only relative import paths. No path aliases are configured.
Export Patterns
- Public APIs use named exports:
export function,export class,export interface,export type,export const. - Default exports are reserved for configuration entry points:
vite.config.tsandcapacitor.config.ts. - Internal helpers remain file-local, for example
getSubsets,scoreRound,shuffleArray, andalphaBeta.
Typing Patterns
- Union types model constrained domains:
PlayerIndex = 0 | 1 | 2 | 3,Difficulty = 'beginner' | 'advanced' | 'master'. - Fixed-size tuples represent stable game structures such as four players and two team scores.
Record<number, number>is used for lookup tables likePRIMIERA_VALUES.- Exported functions generally declare explicit return types.
- Async behavior is typed directly in signatures such as
chooseMove(...): Promise<AIMove>.
Comments & Documentation
- Section banners are used heavily in larger files:
// ---------------------------------------------------------------------------
// Turn management
// ---------------------------------------------------------------------------
- JSDoc appears on rule-heavy or non-obvious APIs, for example
findCaptures(),applyMove(), andCardTrackermethods. - Scene files include
[trueref]provenance notes near Phaser-specific APIs. - Inline comments explain rule edge cases, heuristics, and visual-effect intent rather than restating syntax.
Code Examples
Guarded DOM capability checks
const installFullscreenRequest = (host: HTMLElement): void => {
const canRequestFullscreen =
typeof document.fullscreenEnabled === 'boolean'
? document.fullscreenEnabled
: typeof host.requestFullscreen === 'function';
if (!canRequestFullscreen || typeof host.requestFullscreen !== 'function') {
return;
}
};
Rule-first capture selection
export function findCaptures(played: Card, table: Card[]): Card[][] {
const directMatches = table.filter(c => c.value === played.value);
if (directMatches.length > 0) {
return directMatches.map((directMatch): Card[] => [directMatch]);
}
const results: Card[][] = [];
const subsets = getSubsets(table);
for (const subset of subsets) {
if (subset.length >= 2) {
const sum = subset.reduce((acc, c) => acc + c.value, 0);
if (sum === played.value) {
results.push(subset);
}
}
}
return results;
}
Async AI entry point with progress reporting
export async function chooseMove(
state: GameState,
playerIdx: PlayerIndex,
difficulty: Difficulty = 'advanced',
tracker?: CardTracker,
onProgress?: (progress: AIDecisionProgress) => void,
): Promise<AIMove> {
const startedAt = Date.now();
const profile = getSearchProfile(state, difficulty);
reportDecisionProgress(onProgress, difficulty, startedAt, profile.timeBudgetMs, 0, 0);
// ...
}
Linting & Formatting
No ESLint or Prettier configuration is present.
Formatting is maintained manually, with TypeScript compiler strictness acting as the main automated correctness gate. The only repository-wide verification command currently supplied is npx tsc --noEmit.