Browse Source

feat(chase): add chase scene skeleton and init entry wiring

Made-with: Cursor
master
npmrun 2 weeks ago
parent
commit
d139c4867d
  1. 84
      src/stages/page_chase.ts
  2. 2
      src/stages/page_init.ts
  3. 34
      tests/stages/page_chase.test.ts

84
src/stages/page_chase.ts

@ -0,0 +1,84 @@
import { BaseScene } from "@/scene/BaseScene";
import { SceneType } from "@/enums/SceneType";
import { ChaseGameModel } from "@/game/chase/model";
import type { NodeId } from "@/game/chase/types";
import { Container, Graphics, Text, TextStyle } from "pixi.js";
const NODE_RADIUS = 18;
const NODE_GAP = 58;
export default class ChaseScene extends BaseScene {
stage = new Container();
private model?: ChaseGameModel;
private hudText?: Text;
private graphLayer = new Container();
constructor() {
super("chase", SceneType.Normal);
}
protected async onSceneLayout(): Promise<void> {
this.stage.sortableChildren = true;
this.stage.eventMode = "passive";
this.hudText = new Text({
text: "Chase: loading...",
style: new TextStyle({
fontSize: 20,
fill: 0xffffff,
fontFamily: "'Microsoft YaHei', 'PingFang SC', system-ui, sans-serif",
}),
});
this.hudText.position.set(24, 24);
this.stage.addChild(this.hudText);
this.graphLayer.eventMode = "passive";
this.graphLayer.position.set(140, 140);
this.stage.addChild(this.graphLayer);
this.model = ChaseGameModel.createWithSeed({
seed: 20260426,
difficulty: "normal",
});
this.renderFromModel();
}
private renderFromModel(): void {
if (!this.model || !this.hudText) {
return;
}
const state = this.model.getState();
this.hudText.text = `Turn ${state.turn} | ${state.snapshot.status} | thief=${state.snapshot.thiefNodeId} guard=${state.snapshot.guardNodeId}`;
this.graphLayer.removeChildren();
const nodes = Object.values(state.snapshot.graph.nodes);
for (const node of nodes) {
const nodeView = new Graphics();
nodeView.circle(0, 0, NODE_RADIUS);
const color =
node.id === state.snapshot.thiefNodeId
? 0x22c55e
: node.id === state.snapshot.guardNodeId
? 0xef4444
: node.id === state.snapshot.exitNodeId
? 0x3b82f6
: 0x64748b;
nodeView.fill({ color, alpha: 0.95 });
nodeView.stroke({ width: 2, color: 0xe2e8f0, alpha: 0.9 });
nodeView.position.set(node.q * NODE_GAP, node.r * NODE_GAP);
nodeView.eventMode = "static";
nodeView.cursor = "pointer";
nodeView.on("pointerdown", () => this.handleNodeClick(node.id));
this.graphLayer.addChild(nodeView);
}
}
private handleNodeClick(targetNodeId: NodeId): void {
if (!this.model) {
return;
}
this.model.moveThief(targetNodeId);
this.renderFromModel();
}
}

2
src/stages/page_init.ts

@ -208,6 +208,6 @@ export default class InitScene extends BaseScene {
this.startBtn.setDisabled(true); this.startBtn.setDisabled(true);
this.startBtn.setText("进入中..."); this.startBtn.setText("进入中...");
void this.changeScene("welcome"); void this.changeScene("chase");
} }
} }

34
tests/stages/page_chase.test.ts

@ -0,0 +1,34 @@
import { describe, expect, it, vi } from "vitest";
import ChaseScene from "../../src/stages/page_chase";
import InitScene from "../../src/stages/page_init";
describe("chase stage skeleton", () => {
it("can instantiate chase scene with name=chase", () => {
const scene = new ChaseScene();
expect(scene.name).toBe("chase");
});
it("tries to call model.moveThief when clicking a graph node", () => {
const scene = new ChaseScene();
const moveThief = vi.fn();
(scene as any).model = { moveThief };
(scene as any).handleNodeClick("n-1");
expect(moveThief).toHaveBeenCalledWith("n-1");
});
it("init start entry routes to chase", () => {
const scene = new InitScene();
const changeScene = vi.fn();
const setDisabled = vi.fn();
const setText = vi.fn();
(scene as any).changeScene = changeScene;
(scene as any).startBtn = { setDisabled, setText };
(scene as any).handleStartClick();
expect(setDisabled).toHaveBeenCalledWith(true);
expect(setText).toHaveBeenCalledWith("进入中...");
expect(changeScene).toHaveBeenCalledWith("chase");
});
});
Loading…
Cancel
Save