Browse Source

fix(chase): clean graph rerender nodes and improve scene testability

Made-with: Cursor
master
npmrun 2 weeks ago
parent
commit
75ecb862b4
  1. 37
      src/stages/page_chase.ts
  2. 54
      tests/stages/page_chase.test.ts

37
src/stages/page_chase.ts

@ -7,14 +7,23 @@ import { Container, Graphics, Text, TextStyle } from "pixi.js";
const NODE_RADIUS = 18;
const NODE_GAP = 58;
type ChaseModelLike = Pick<ChaseGameModel, "getState" | "moveThief">;
type ChaseSceneOptions = {
model?: ChaseModelLike;
modelFactory?: () => ChaseModelLike;
};
export default class ChaseScene extends BaseScene {
stage = new Container();
private model?: ChaseGameModel;
private model?: ChaseModelLike;
private readonly modelFactory?: () => ChaseModelLike;
private hudText?: Text;
private graphLayer = new Container();
constructor() {
constructor(options: ChaseSceneOptions = {}) {
super("chase", SceneType.Normal);
this.model = options.model;
this.modelFactory = options.modelFactory;
}
protected async onSceneLayout(): Promise<void> {
@ -36,14 +45,18 @@ export default class ChaseScene extends BaseScene {
this.graphLayer.position.set(140, 140);
this.stage.addChild(this.graphLayer);
this.model = ChaseGameModel.createWithSeed({
seed: 20260426,
difficulty: "normal",
});
this.renderFromModel();
if (!this.model) {
this.model =
this.modelFactory?.() ??
ChaseGameModel.createWithSeed({
seed: 20260426,
difficulty: "normal",
});
}
this.refreshView();
}
private renderFromModel(): void {
public refreshView(): void {
if (!this.model || !this.hudText) {
return;
}
@ -51,7 +64,11 @@ export default class ChaseScene extends BaseScene {
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 oldChildren = this.graphLayer.removeChildren();
for (const child of oldChildren) {
child.destroy({ children: true });
}
const nodes = Object.values(state.snapshot.graph.nodes);
for (const node of nodes) {
const nodeView = new Graphics();
@ -79,6 +96,6 @@ export default class ChaseScene extends BaseScene {
return;
}
this.model.moveThief(targetNodeId);
this.renderFromModel();
this.refreshView();
}
}

54
tests/stages/page_chase.test.ts

@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import ChaseScene from "../../src/stages/page_chase";
import InitScene from "../../src/stages/page_init";
import { Text } from "pixi.js";
describe("chase stage skeleton", () => {
it("can instantiate chase scene with name=chase", () => {
@ -17,6 +18,59 @@ describe("chase stage skeleton", () => {
expect(moveThief).toHaveBeenCalledWith("n-1");
});
it("cleans up old graph nodes when re-rendering", () => {
const model = {
moveThief: vi.fn(),
getState: vi
.fn()
.mockReturnValueOnce({
turn: 0,
snapshot: {
status: "playing",
thiefNodeId: "A",
guardNodeId: "B",
exitNodeId: "B",
graph: {
nodes: {
A: { id: "A", q: 0, r: 0, neighbors: ["B"] },
B: { id: "B", q: 1, r: 0, neighbors: ["A"] },
},
edgeList: [["A", "B"]],
},
},
})
.mockReturnValueOnce({
turn: 1,
snapshot: {
status: "playing",
thiefNodeId: "B",
guardNodeId: "A",
exitNodeId: "B",
graph: {
nodes: {
A: { id: "A", q: 0, r: 0, neighbors: ["B"] },
B: { id: "B", q: 1, r: 0, neighbors: ["A"] },
},
edgeList: [["A", "B"]],
},
},
}),
};
const scene = new ChaseScene({ model: model as any });
(scene as any).hudText = new Text({ text: "" });
scene.refreshView();
const firstChildren = [...(scene as any).graphLayer.children];
expect(firstChildren.length).toBe(2);
scene.refreshView();
const secondChildren = [...(scene as any).graphLayer.children];
expect(secondChildren.length).toBe(2);
expect(secondChildren[0]).not.toBe(firstChildren[0]);
expect((firstChildren[0] as any).destroyed).toBe(true);
expect((firstChildren[1] as any).destroyed).toBe(true);
});
it("init start entry routes to chase", () => {
const scene = new InitScene();
const changeScene = vi.fn();

Loading…
Cancel
Save