114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
// Aufnahmeseite für die Katalog-Assets: nur das Brett, ein Bot spielt eine echte Partie.
|
||
// Gesteuert von scripts/capture/record.mjs über window.capture.
|
||
import '../../src/client/style.css'
|
||
import {ZONE_NAMES} from '../../src/client/texts.ts'
|
||
import {BoardView} from '../../src/engine/app/board/BoardView.ts'
|
||
import {BOTS} from '../../src/engine/core/bots.ts'
|
||
import {Game} from '../../src/engine/core/Game.ts'
|
||
import {mulberry32} from '../../src/engine/core/rng.ts'
|
||
import {dailyBoard, dailyMap} from '../../src/shared/daily.ts'
|
||
|
||
const params = new URLSearchParams(location.search)
|
||
// Karte von vor dem ersten Spieltag – läuft nie als Tageskarte, verrät also nichts
|
||
const day = params.get('day') ?? '2026-10-10'
|
||
const seed = Number(params.get('seed') ?? 1)
|
||
|
||
const view = new BoardView({interactive: true})
|
||
const game = new Game(dailyBoard(dailyMap(new Date(`${day}T00:00:00Z`))), {
|
||
undoLimit: 0,
|
||
})
|
||
view.setBoard(game.board)
|
||
view.setLandNames(ZONE_NAMES)
|
||
const host = document.getElementById('board') as HTMLDivElement
|
||
host.append(view.svg)
|
||
|
||
// Ganze Partie vorab berechnen, damit die Aufnahme nur noch abspielt
|
||
const moves: {unit: Parameters<Game['place']>[0]; cell: number}[] = []
|
||
{
|
||
const sim = game.clone()
|
||
const rng = mulberry32(seed)
|
||
while (!sim.isGameOver()) {
|
||
const m = BOTS.smart(sim, rng)
|
||
sim.place(m.unit, m.cell)
|
||
moves.push({unit: m.unit, cell: m.cell})
|
||
}
|
||
}
|
||
|
||
function draw(): void {
|
||
view.update(game)
|
||
view.setRulers(game.rulers())
|
||
}
|
||
|
||
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))
|
||
|
||
/** Einen Zug zeigen: kurz die Vorschau (beanspruchtes Land + Gold), dann setzen */
|
||
async function step(
|
||
i: number,
|
||
previewMs: number,
|
||
afterMs: number,
|
||
): Promise<void> {
|
||
const m = moves[i]
|
||
if (!m) return
|
||
const p = game.preview(m.unit, m.cell)
|
||
if (p.ok) {
|
||
view.setPlacementPreview({
|
||
cell: m.cell,
|
||
unit: m.unit,
|
||
zoi: p.zoi,
|
||
label: `${p.net >= 0 ? '+' : '−'}${Math.abs(p.net)}`,
|
||
owner: 1,
|
||
tone: p.net > 0 ? 'gain' : p.net < 0 ? 'loss' : 'neutral',
|
||
})
|
||
await sleep(previewMs)
|
||
}
|
||
view.setPlacementPreview(null)
|
||
const r = game.place(m.unit, m.cell)
|
||
if (r.ok) {
|
||
const net = r.yield.points - r.cost
|
||
if (net !== 0)
|
||
view.floatLabel(
|
||
m.cell,
|
||
`${net > 0 ? '+' : '−'}${Math.abs(net)}`,
|
||
net > 0 ? 'gain' : 'loss',
|
||
)
|
||
}
|
||
draw()
|
||
await sleep(afterMs)
|
||
}
|
||
|
||
declare global {
|
||
interface Window {
|
||
capture: {
|
||
moves: number
|
||
/** Partie abspielen: firstMoves sofort (für ein aussagekräftiges erstes Bild), dann im Takt */
|
||
play(
|
||
firstMoves: number,
|
||
previewMs: number,
|
||
afterMs: number,
|
||
): Promise<void>
|
||
/** Sofort bis Zug n springen (Standbilder) */
|
||
jump(n: number): void
|
||
}
|
||
}
|
||
}
|
||
|
||
window.capture = {
|
||
moves: moves.length,
|
||
async play(firstMoves, previewMs, afterMs) {
|
||
for (let i = 0; i < firstMoves; i++) {
|
||
const m = moves[i]
|
||
if (m) game.place(m.unit, m.cell)
|
||
}
|
||
draw()
|
||
for (let i = firstMoves; i < moves.length; i++)
|
||
await step(i, previewMs, afterMs)
|
||
},
|
||
jump(n) {
|
||
for (let i = game.moves.length; i < Math.min(n, moves.length); i++) {
|
||
const m = moves[i]
|
||
if (m) game.place(m.unit, m.cell)
|
||
}
|
||
draw()
|
||
},
|
||
}
|
||
draw()
|