Browse Source

feat(chase): finalize hud with success counter and seed display

Made-with: Cursor
master
npmrun 2 weeks ago
parent
commit
1ba10c6d7e
  1. 46
      src/stages/page_chase.ts
  2. 67
      tests/stages/page_chase.test.ts

46
src/stages/page_chase.ts

@ -31,6 +31,8 @@ export default class ChaseScene extends BaseScene {
private model?: ChaseModelLike; private model?: ChaseModelLike;
private readonly modelFactory?: () => ChaseModelLike; private readonly modelFactory?: () => ChaseModelLike;
private hudText?: Text; private hudText?: Text;
private winCountText?: Text;
private seedText?: Text;
private setupText?: Text; private setupText?: Text;
private setupLayer = new Container(); private setupLayer = new Container();
private graphLayer = new Container(); private graphLayer = new Container();
@ -80,6 +82,29 @@ export default class ChaseScene extends BaseScene {
this.hudText.position.set(24, 24); this.hudText.position.set(24, 24);
this.stage.addChild(this.hudText); this.stage.addChild(this.hudText);
this.winCountText = new Text({
text: "",
style: new TextStyle({
fontSize: 18,
fill: 0xffffff,
fontFamily: "'Microsoft YaHei', 'PingFang SC', system-ui, sans-serif",
}),
});
this.winCountText.position.set(24, 6);
this.stage.addChild(this.winCountText);
this.seedText = new Text({
text: "",
style: new TextStyle({
fontSize: 18,
fill: 0xffffff,
fontFamily: "'Microsoft YaHei', 'PingFang SC', system-ui, sans-serif",
}),
});
this.seedText.anchor.set(1, 0);
this.seedText.position.set(760, 6);
this.stage.addChild(this.seedText);
this.setupText = new Text({ this.setupText = new Text({
text: "", text: "",
style: new TextStyle({ style: new TextStyle({
@ -137,11 +162,13 @@ export default class ChaseScene extends BaseScene {
} }
public refreshView(): void { public refreshView(): void {
if (!this.model || !this.hudText || !this.setupText) { if (!this.model || !this.hudText || !this.setupText || !this.winCountText || !this.seedText) {
return; return;
} }
const state = this.model.getState(); const state = this.model.getState();
this.winCountText.text = `成功次数: ${state.winCount}`;
this.seedText.text = `Seed: ${state.snapshot.seed}`;
this.hudText.text = `Turn ${state.turn} | ${state.snapshot.status} | thief=${state.snapshot.thiefNodeId} guard=${state.snapshot.guardNodeId}`; this.hudText.text = `Turn ${state.turn} | ${state.snapshot.status} | thief=${state.snapshot.thiefNodeId} guard=${state.snapshot.guardNodeId}`;
this.setupText.text = `difficulty=${this.selectedDifficulty} | seed=${this.seedInput.trim() || "(auto)"} | locked=${this.difficultyLocked ? "yes" : "no"}`; this.setupText.text = `difficulty=${this.selectedDifficulty} | seed=${this.seedInput.trim() || "(auto)"} | locked=${this.difficultyLocked ? "yes" : "no"}`;
this.drawDifficultyButtons(); this.drawDifficultyButtons();
@ -152,6 +179,23 @@ export default class ChaseScene extends BaseScene {
child.destroy({ children: true }); child.destroy({ children: true });
} }
const thiefNode = state.snapshot.graph.nodes[state.snapshot.thiefNodeId];
const availableMoves = thiefNode
? thiefNode.neighbors.filter((id) => id !== state.snapshot.guardNodeId)
: [];
for (const nodeId of availableMoves) {
const node = state.snapshot.graph.nodes[nodeId];
if (!node) {
continue;
}
const highlight = new Graphics();
highlight.label = "move-highlight";
highlight.circle(0, 0, NODE_RADIUS + 8);
highlight.stroke({ width: 3, color: 0xfacc15, alpha: 0.9 });
highlight.position.set(node.q * NODE_GAP, node.r * NODE_GAP);
this.graphLayer.addChild(highlight);
}
const nodes = Object.values(state.snapshot.graph.nodes); const nodes = Object.values(state.snapshot.graph.nodes);
for (const node of nodes) { for (const node of nodes) {
const nodeView = new Graphics(); const nodeView = new Graphics();

67
tests/stages/page_chase.test.ts

@ -7,7 +7,9 @@ import { Text } from "pixi.js";
function createSceneState() { function createSceneState() {
return { return {
turn: 0, turn: 0,
winCount: 0,
snapshot: { snapshot: {
seed: 123,
status: "playing", status: "playing",
thiefNodeId: "A", thiefNodeId: "A",
guardNodeId: "B", guardNodeId: "B",
@ -110,6 +112,8 @@ describe("chase stage skeleton", () => {
}; };
const scene = new ChaseScene({ model: model as any }); const scene = new ChaseScene({ model: model as any });
(scene as any).hudText = new Text({ text: "" }); (scene as any).hudText = new Text({ text: "" });
(scene as any).winCountText = new Text({ text: "" });
(scene as any).seedText = new Text({ text: "" });
(scene as any).setupText = new Text({ text: "" }); (scene as any).setupText = new Text({ text: "" });
scene.refreshView(); scene.refreshView();
@ -379,4 +383,67 @@ describe("chase stage skeleton", () => {
void (scene as any).onSceneLayout(); void (scene as any).onSceneLayout();
expect((scene as any).resultText.text).toContain("你被抓住了"); expect((scene as any).resultText.text).toContain("你被抓住了");
}); });
it("shows hud with success counter and current seed", () => {
const scene = new ChaseScene({
model: {
getState: vi.fn().mockReturnValue(createSceneState()),
moveThief: vi.fn(),
newRound: vi.fn(),
retryRound: vi.fn(),
} as any,
});
void (scene as any).onSceneLayout();
expect((scene as any).winCountText.text).toContain("成功次数: 0");
expect((scene as any).seedText.text).toContain("Seed: 123");
});
it("refreshes hud after state change", () => {
const getState = vi
.fn()
.mockReturnValueOnce(createSceneState())
.mockReturnValueOnce({
...createSceneState(),
winCount: 2,
snapshot: {
...createSceneState().snapshot,
seed: 999,
thiefNodeId: "B",
guardNodeId: "A",
},
});
const scene = new ChaseScene({
model: {
getState,
moveThief: vi.fn(),
newRound: vi.fn(),
retryRound: vi.fn(),
} as any,
});
void (scene as any).onSceneLayout();
scene.refreshView();
expect((scene as any).winCountText.text).toContain("成功次数: 2");
expect((scene as any).seedText.text).toContain("Seed: 999");
});
it("renders move highlights matching available moves count", () => {
const state = createSceneState();
const scene = new ChaseScene({
model: {
getState: vi.fn().mockReturnValue(state),
moveThief: vi.fn(),
newRound: vi.fn(),
retryRound: vi.fn(),
} as any,
});
void (scene as any).onSceneLayout();
const highlightCount = (scene as any).graphLayer.children.filter(
(c: any) => c.label === "move-highlight",
).length;
const thiefNode = state.snapshot.graph.nodes[state.snapshot.thiefNodeId];
const expectedMoves = thiefNode.neighbors.filter(
(id: string) => id !== state.snapshot.guardNodeId,
).length;
expect(highlightCount).toBe(expectedMoves);
});
}); });

Loading…
Cancel
Save