feat(SCOPONE-0013): PIMC AI rewrite + Gitea Android CI pipeline
Some checks failed
Android Build & Publish / android (push) Failing after 2m10s
Some checks failed
Android Build & Publish / android (push) Failing after 2m10s
- Replace minimax with PIMC (Perfect Information Monte Carlo) search - Add PIMC_SCOPE_BOOST=150 → effective scopa value 540 (was 390) → Master win rate: 67.5% → 72.5% vs legacy AI (target ≥60%) → Advanced win rate: 97.5% vs beginner AI (target ≥55%) → Scope gap in losses: 6.54 → 3.00 scopa/match - Add card inference engine for probabilistic hand tracking - Add ai-strategy, ai-legacy evaluation bridge - Add .gitea/workflows/android-build.yml: build debug + unsigned release APK and publish to Gitea generic package registry
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
import { AIMove, AIDecisionProgress } from '../game/ai';
|
||||
import { AIWorkerClient, AIWorkerClientLike } from '../game/ai-worker-client';
|
||||
import { CardTracker } from '../game/card-tracker';
|
||||
import { CardInferenceEngine } from '../game/card-inference';
|
||||
import {
|
||||
DEFAULT_AUDIO_PREFERENCES,
|
||||
GameSceneData,
|
||||
@@ -71,6 +72,7 @@ export class GameScene extends Phaser.Scene {
|
||||
// Difficulty & card tracker
|
||||
private difficulty: Difficulty = 'advanced';
|
||||
private tracker: CardTracker = new CardTracker();
|
||||
private inference: CardInferenceEngine = new CardInferenceEngine(this.tracker);
|
||||
private aiClient: AIWorkerClientLike | null = null;
|
||||
|
||||
// Active player highlight
|
||||
@@ -133,6 +135,7 @@ export class GameScene extends Phaser.Scene {
|
||||
? normalizeAudioPreferences(data.audioPreferences)
|
||||
: loadAudioPreferences();
|
||||
this.tracker = new CardTracker();
|
||||
this.inference = new CardInferenceEngine(this.tracker);
|
||||
this.aiClient?.dispose();
|
||||
this.aiClient = new AIWorkerClient();
|
||||
this.events.once(Phaser.Scenes.Events.SHUTDOWN, this.handleSceneShutdown, this);
|
||||
@@ -793,7 +796,8 @@ export class GameScene extends Phaser.Scene {
|
||||
this.updateThinkBar(playerIdx, progress);
|
||||
if (progress.difficulty !== 'master') return;
|
||||
finalProgress = progress;
|
||||
}
|
||||
},
|
||||
{ inference: this.inference },
|
||||
);
|
||||
|
||||
const remainingThinkMs = AI_MIN_THINK_MS - (Date.now() - thinkStartedAt);
|
||||
@@ -1018,6 +1022,7 @@ export class GameScene extends Phaser.Scene {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private executeMove(playerIdx: PlayerIndex, card: Card, capture: Card[]): void {
|
||||
const tableBeforeMove = [...this.state.table];
|
||||
const { nextState, capture: captureResult, isScopa } = applyMove(
|
||||
this.state, playerIdx, card, capture.length > 0 ? capture : undefined
|
||||
);
|
||||
@@ -1030,6 +1035,13 @@ export class GameScene extends Phaser.Scene {
|
||||
this.tracker.trackCapture(captureResult.captured);
|
||||
}
|
||||
|
||||
// Update inference engine
|
||||
this.inference.onMove(
|
||||
playerIdx,
|
||||
{ card, capture: captureResult?.captured ?? [] },
|
||||
tableBeforeMove,
|
||||
);
|
||||
|
||||
const cardImg = this.cardImages.get(card.id)!;
|
||||
cardImg.setDepth(15);
|
||||
|
||||
@@ -1650,6 +1662,7 @@ export class GameScene extends Phaser.Scene {
|
||||
for (const img of this.cardImages.values()) img.destroy();
|
||||
this.cardImages.clear();
|
||||
this.tracker.reset();
|
||||
this.inference.reset();
|
||||
this.state = createInitialState(nextDealer);
|
||||
this.state.matchStartingPlayer = matchStartingPlayer;
|
||||
this.state.teamScores[0].totalPoints = totals[0];
|
||||
|
||||
Reference in New Issue
Block a user