Draw the board as track, split the site into three pages, and fix curve geometry
Adds SVG board rendering shared by the game and the replay, a splash page with a shareable replay directory, hover tooltips for reference detail, and makes curves two-port arcs so sidings and run-arounds can finally be built.
This commit is contained in:
+48
-6
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
@@ -26,6 +26,8 @@ execFileSync(
|
||||
'tsc',
|
||||
'--ignoreConfig',
|
||||
'src/web/main.ts',
|
||||
'src/web/splash.ts',
|
||||
'src/web/replays.ts',
|
||||
'--outDir', dist,
|
||||
'--rootDir', 'src',
|
||||
'--target', 'es2022',
|
||||
@@ -107,10 +109,47 @@ function bustImports(dir: string): number {
|
||||
|
||||
const busted = bustImports(dist);
|
||||
|
||||
const page = readFileSync(join(root, 'src/web/index.html'), 'utf8')
|
||||
.replaceAll('__BUILD__', stamp)
|
||||
.replace(/(<script type="module" src="[^"]+?\.js)(")/, `$1?v=${tag}$2`);
|
||||
writeFileSync(join(dist, 'index.html'), page);
|
||||
// Three pages now: the splash, the game, and the replay directory. Each is stamped and each has
|
||||
// its entry script cache-busted, or a returning visitor gets new HTML against old modules.
|
||||
for (const name of ['index.html', 'play.html', 'replays.html']) {
|
||||
const page = readFileSync(join(root, 'src/web', name), 'utf8')
|
||||
.replaceAll('__BUILD__', stamp)
|
||||
.replace(/(<script type="module" src="[^"]+?\.js)(")/, `$1?v=${tag}$2`);
|
||||
writeFileSync(join(dist, name), page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish whatever replays are in `public/replays/`, plus an index of them.
|
||||
*
|
||||
* Static hosting cannot list a directory, so the page needs a manifest. A replay is a save — seed
|
||||
* plus moves — so these are a few hundred bytes each, not megabytes.
|
||||
*/
|
||||
const replaySrc = join(root, 'public/replays');
|
||||
const replayOut = join(dist, 'replays');
|
||||
mkdirSync(replayOut, { recursive: true });
|
||||
const manifest: { file: string; title: string; note?: string; seed?: number }[] = [];
|
||||
if (existsSync(replaySrc)) {
|
||||
for (const f of readdirSync(replaySrc).filter((n) => n.endsWith('.json') && n !== 'manifest.json')) {
|
||||
const raw = readFileSync(join(replaySrc, f), 'utf8');
|
||||
try {
|
||||
const save = JSON.parse(raw) as { seed?: number; title?: string; note?: string; history?: unknown[] };
|
||||
if (typeof save.seed !== 'number' || !Array.isArray(save.history)) {
|
||||
console.warn(` skipped ${f} — not a Station Master save`);
|
||||
continue;
|
||||
}
|
||||
writeFileSync(join(replayOut, f), raw);
|
||||
manifest.push({
|
||||
file: f,
|
||||
title: save.title ?? f.replace(/\.json$/, ''),
|
||||
...(save.note ? { note: save.note } : {}),
|
||||
seed: save.seed,
|
||||
});
|
||||
} catch {
|
||||
console.warn(` skipped ${f} — could not be parsed`);
|
||||
}
|
||||
}
|
||||
}
|
||||
writeFileSync(join(replayOut, 'manifest.json'), JSON.stringify(manifest, null, 1));
|
||||
|
||||
// A tiny note for whoever unzips this later and wonders what it needs.
|
||||
writeFileSync(
|
||||
@@ -129,4 +168,7 @@ writeFileSync(
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
console.log(`built -> ${dist}\n ${stamp}\n cache-busted ${busted} modules with ?v=${tag}`);
|
||||
console.log(
|
||||
`built -> ${dist}\n ${stamp}\n cache-busted ${busted} modules with ?v=${tag}` +
|
||||
`\n ${manifest.length} replay(s) published`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user