3 changed files with 119 additions and 1 deletions
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue