|
|
|
@ -19,7 +19,15 @@ export default class ChaseScene extends BaseScene { |
|
|
|
private readonly modelFactory?: () => ChaseModelLike; |
|
|
|
private hudText?: Text; |
|
|
|
private setupText?: Text; |
|
|
|
private setupLayer = new Container(); |
|
|
|
private graphLayer = new Container(); |
|
|
|
private difficultyButtons: Record<Difficulty, Graphics> = { |
|
|
|
easy: new Graphics(), |
|
|
|
normal: new Graphics(), |
|
|
|
hard: new Graphics(), |
|
|
|
}; |
|
|
|
private startButton = new Graphics(); |
|
|
|
private seedInputElement?: HTMLInputElement; |
|
|
|
private selectedDifficulty: Difficulty = "normal"; |
|
|
|
private seedInput = ""; |
|
|
|
private difficultyLocked = false; |
|
|
|
@ -56,6 +64,11 @@ export default class ChaseScene extends BaseScene { |
|
|
|
this.setupText.position.set(24, 54); |
|
|
|
this.stage.addChild(this.setupText); |
|
|
|
|
|
|
|
this.setupLayer.eventMode = "passive"; |
|
|
|
this.setupLayer.position.set(24, 84); |
|
|
|
this.stage.addChild(this.setupLayer); |
|
|
|
this.buildSetupControls(); |
|
|
|
|
|
|
|
this.graphLayer.eventMode = "passive"; |
|
|
|
this.graphLayer.position.set(140, 140); |
|
|
|
this.stage.addChild(this.graphLayer); |
|
|
|
@ -71,6 +84,14 @@ export default class ChaseScene extends BaseScene { |
|
|
|
this.refreshView(); |
|
|
|
} |
|
|
|
|
|
|
|
protected onSceneEnter(): void { |
|
|
|
this.attachSeedInputBridge(); |
|
|
|
} |
|
|
|
|
|
|
|
protected onSceneExit(): void { |
|
|
|
this.detachSeedInputBridge(); |
|
|
|
} |
|
|
|
|
|
|
|
public refreshView(): void { |
|
|
|
if (!this.model || !this.hudText || !this.setupText) { |
|
|
|
return; |
|
|
|
@ -79,6 +100,7 @@ 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.setupText.text = `difficulty=${this.selectedDifficulty} | seed=${this.seedInput.trim() || "(auto)"} | locked=${this.difficultyLocked ? "yes" : "no"}`; |
|
|
|
this.drawDifficultyButtons(); |
|
|
|
|
|
|
|
const oldChildren = this.graphLayer.removeChildren(); |
|
|
|
for (const child of oldChildren) { |
|
|
|
@ -125,6 +147,9 @@ export default class ChaseScene extends BaseScene { |
|
|
|
|
|
|
|
public setSeedInput(seedInput: string): void { |
|
|
|
this.seedInput = seedInput; |
|
|
|
if (this.seedInputElement) { |
|
|
|
this.seedInputElement.value = seedInput; |
|
|
|
} |
|
|
|
this.refreshView(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -142,6 +167,108 @@ export default class ChaseScene extends BaseScene { |
|
|
|
this.model.newRound(seed, this.selectedDifficulty); |
|
|
|
this.difficultyLocked = true; |
|
|
|
this.seedInput = `${seed}`; |
|
|
|
if (this.seedInputElement) { |
|
|
|
this.seedInputElement.value = this.seedInput; |
|
|
|
} |
|
|
|
this.refreshView(); |
|
|
|
} |
|
|
|
|
|
|
|
private buildSetupControls(): void { |
|
|
|
this.setupLayer.removeChildren(); |
|
|
|
const difficulties: Difficulty[] = ["easy", "normal", "hard"]; |
|
|
|
difficulties.forEach((difficulty, index) => { |
|
|
|
const button = this.difficultyButtons[difficulty]; |
|
|
|
button.eventMode = "static"; |
|
|
|
button.cursor = "pointer"; |
|
|
|
button.removeAllListeners(); |
|
|
|
button.on("pointerdown", () => this.setDifficulty(difficulty)); |
|
|
|
button.position.set(index * 96, 0); |
|
|
|
this.setupLayer.addChild(button); |
|
|
|
|
|
|
|
const label = new Text({ |
|
|
|
text: difficulty, |
|
|
|
style: new TextStyle({ |
|
|
|
fontSize: 14, |
|
|
|
fill: 0xffffff, |
|
|
|
fontFamily: "'Microsoft YaHei', 'PingFang SC', system-ui, sans-serif", |
|
|
|
}), |
|
|
|
}); |
|
|
|
label.anchor.set(0.5); |
|
|
|
label.position.set(index * 96 + 38, 18); |
|
|
|
this.setupLayer.addChild(label); |
|
|
|
}); |
|
|
|
|
|
|
|
this.startButton.eventMode = "static"; |
|
|
|
this.startButton.cursor = "pointer"; |
|
|
|
this.startButton.removeAllListeners(); |
|
|
|
this.startButton.on("pointerdown", () => this.startGame()); |
|
|
|
this.startButton.position.set(304, 0); |
|
|
|
this.setupLayer.addChild(this.startButton); |
|
|
|
|
|
|
|
const startLabel = new Text({ |
|
|
|
text: "开始游戏", |
|
|
|
style: new TextStyle({ |
|
|
|
fontSize: 14, |
|
|
|
fill: 0xffffff, |
|
|
|
fontFamily: "'Microsoft YaHei', 'PingFang SC', system-ui, sans-serif", |
|
|
|
}), |
|
|
|
}); |
|
|
|
startLabel.anchor.set(0.5); |
|
|
|
startLabel.position.set(304 + 48, 18); |
|
|
|
this.setupLayer.addChild(startLabel); |
|
|
|
|
|
|
|
this.drawDifficultyButtons(); |
|
|
|
this.startButton.clear(); |
|
|
|
this.startButton.roundRect(0, 0, 96, 36, 8); |
|
|
|
this.startButton.fill({ color: 0x2563eb, alpha: 0.95 }); |
|
|
|
} |
|
|
|
|
|
|
|
private drawDifficultyButtons(): void { |
|
|
|
const drawButton = (difficulty: Difficulty): void => { |
|
|
|
const button = this.difficultyButtons[difficulty]; |
|
|
|
button.clear(); |
|
|
|
button.roundRect(0, 0, 76, 36, 8); |
|
|
|
const selected = this.selectedDifficulty === difficulty; |
|
|
|
const lockedColor = this.difficultyLocked ? 0x475569 : selected ? 0x0ea5e9 : 0x1f2937; |
|
|
|
button.fill({ color: lockedColor, alpha: selected ? 0.95 : 0.88 }); |
|
|
|
button.stroke({ width: selected ? 2 : 1, color: 0xe2e8f0, alpha: 0.9 }); |
|
|
|
button.cursor = this.difficultyLocked ? "not-allowed" : "pointer"; |
|
|
|
}; |
|
|
|
drawButton("easy"); |
|
|
|
drawButton("normal"); |
|
|
|
drawButton("hard"); |
|
|
|
} |
|
|
|
|
|
|
|
private attachSeedInputBridge(): void { |
|
|
|
if (typeof document === "undefined" || this.seedInputElement) { |
|
|
|
return; |
|
|
|
} |
|
|
|
const input = document.createElement("input"); |
|
|
|
input.type = "text"; |
|
|
|
input.placeholder = "Seed (optional)"; |
|
|
|
input.value = this.seedInput; |
|
|
|
input.style.position = "fixed"; |
|
|
|
input.style.left = "24px"; |
|
|
|
input.style.top = "130px"; |
|
|
|
input.style.zIndex = "10"; |
|
|
|
input.style.width = "180px"; |
|
|
|
input.addEventListener("input", this.handleSeedInputEvent); |
|
|
|
document.body.appendChild(input); |
|
|
|
this.seedInputElement = input; |
|
|
|
} |
|
|
|
|
|
|
|
private detachSeedInputBridge(): void { |
|
|
|
if (!this.seedInputElement) { |
|
|
|
return; |
|
|
|
} |
|
|
|
this.seedInputElement.removeEventListener("input", this.handleSeedInputEvent); |
|
|
|
this.seedInputElement.remove(); |
|
|
|
this.seedInputElement = undefined; |
|
|
|
} |
|
|
|
|
|
|
|
private readonly handleSeedInputEvent = (event: Event): void => { |
|
|
|
const target = event.target as HTMLInputElement | null; |
|
|
|
this.seedInput = target?.value ?? ""; |
|
|
|
this.refreshView(); |
|
|
|
}; |
|
|
|
} |
|
|
|
|