boomwait 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/boomwait.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -0
- package/dist/cliArgs.d.ts +9 -0
- package/dist/cliArgs.js +37 -0
- package/dist/cliArgs.js.map +1 -0
- package/dist/game.d.ts +31 -0
- package/dist/game.js +106 -0
- package/dist/game.js.map +1 -0
- package/dist/input-latch.d.ts +12 -0
- package/dist/input-latch.js +49 -0
- package/dist/input-latch.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +22 -0
- package/dist/main.js.map +1 -0
- package/dist/net.d.ts +30 -0
- package/dist/net.js +114 -0
- package/dist/net.js.map +1 -0
- package/dist/offline.d.ts +6 -0
- package/dist/offline.js +53 -0
- package/dist/offline.js.map +1 -0
- package/dist/online.d.ts +6 -0
- package/dist/online.js +107 -0
- package/dist/online.js.map +1 -0
- package/dist/render.d.ts +9 -0
- package/dist/render.js +307 -0
- package/dist/render.js.map +1 -0
- package/dist/share.d.ts +2 -0
- package/dist/share.js +34 -0
- package/dist/share.js.map +1 -0
- package/dist/sprites.d.ts +2 -0
- package/dist/sprites.js +137 -0
- package/dist/sprites.js.map +1 -0
- package/package.json +10 -0
package/bin/boomwait.js
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAOxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;AAC1C,MAAM,IAAI,CAAC,IAAI,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Difficulty } from 'boomwait-core';
|
|
2
|
+
export declare const DEFAULT_SERVER = "https://fragwait-server.agthe7.workers.dev";
|
|
3
|
+
export interface CliOpts {
|
|
4
|
+
offline: boolean;
|
|
5
|
+
name?: string;
|
|
6
|
+
server: string;
|
|
7
|
+
difficulty: Difficulty;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseArgs(argv: string[]): CliOpts;
|
package/dist/cliArgs.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const DIFFICULTIES = ['easy', 'normal', 'hard'];
|
|
2
|
+
// Bomber routes live on the same Cloudflare Worker host as fragwait/checkwait's
|
|
3
|
+
// — one deployment serves all three games' Durable Objects (Task 11 stands up
|
|
4
|
+
// the actual routes; this default just points there ahead of time).
|
|
5
|
+
export const DEFAULT_SERVER = 'https://fragwait-server.agthe7.workers.dev';
|
|
6
|
+
export function parseArgs(argv) {
|
|
7
|
+
// Default difficulty is 'easy', mirroring fragwait/checkwait's cliArgs
|
|
8
|
+
// (72058a7): feel-gating found normal's 0.3-0.4 skills near-hard in
|
|
9
|
+
// practice. Harder bots are an explicit opt-in via --difficulty.
|
|
10
|
+
const opts = { offline: false, difficulty: 'easy', server: DEFAULT_SERVER };
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const a = argv[i];
|
|
13
|
+
if (a === '--offline')
|
|
14
|
+
opts.offline = true;
|
|
15
|
+
else if (a === '--name') {
|
|
16
|
+
const v = argv[++i];
|
|
17
|
+
if (v === undefined)
|
|
18
|
+
throw new Error('invalid --name: expected a value');
|
|
19
|
+
opts.name = v;
|
|
20
|
+
}
|
|
21
|
+
else if (a === '--server') {
|
|
22
|
+
const v = argv[++i];
|
|
23
|
+
if (v === undefined)
|
|
24
|
+
throw new Error('invalid --server: expected a URL');
|
|
25
|
+
opts.server = v;
|
|
26
|
+
}
|
|
27
|
+
else if (a === '--difficulty') {
|
|
28
|
+
const v = argv[++i];
|
|
29
|
+
if (!DIFFICULTIES.includes(v)) {
|
|
30
|
+
throw new Error(`invalid --difficulty: ${v} (expected one of ${DIFFICULTIES.join('|')})`);
|
|
31
|
+
}
|
|
32
|
+
opts.difficulty = v;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return opts;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=cliArgs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliArgs.js","sourceRoot":"","sources":["../src/cliArgs.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;AAEtE,gFAAgF;AAChF,8EAA8E;AAC9E,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,4CAA4C,CAAA;AAS1E,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,uEAAuE;IACvE,oEAAoE;IACpE,iEAAiE;IACjE,MAAM,IAAI,GAAY,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;IACpF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,IAAI,CAAC,KAAK,WAAW;YAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;aACrC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;YACnB,IAAI,CAAC,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAA;YACxE,IAAI,CAAC,IAAI,GAAG,CAAC,CAAA;QACf,CAAC;aAAM,IAAI,CAAC,KAAK,UAAU,EAAE,CAAC;YAC5B,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;YACnB,IAAI,CAAC,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAA;YACxE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QACjB,CAAC;aAAM,IAAI,CAAC,KAAK,cAAc,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;YACnB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAe,CAAC,EAAE,CAAC;gBAC5C,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,qBAAqB,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC3F,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,CAAe,CAAA;QACnC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
package/dist/game.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Input, Result } from 'boomwait-core';
|
|
2
|
+
import { KeyParser, TerminalSession } from 'termwait';
|
|
3
|
+
import type { ClaudeListener, ColorMode } from 'termwait';
|
|
4
|
+
import { type Layout } from './render.js';
|
|
5
|
+
export declare const REDRAW_MS: number;
|
|
6
|
+
export interface GameSession {
|
|
7
|
+
term: TerminalSession;
|
|
8
|
+
parser: KeyParser;
|
|
9
|
+
colorMode: ColorMode;
|
|
10
|
+
listener: ClaudeListener;
|
|
11
|
+
layout(): Layout;
|
|
12
|
+
drainInput(): Input;
|
|
13
|
+
statusLine(): string;
|
|
14
|
+
quitRequested(): boolean;
|
|
15
|
+
onResize(cb: () => void): void;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function setupGame(): Promise<GameSession>;
|
|
19
|
+
export declare function resultLine(result: Result, you: number): string;
|
|
20
|
+
export declare function teardownAndExit(o: {
|
|
21
|
+
term: TerminalSession;
|
|
22
|
+
parser: KeyParser;
|
|
23
|
+
listener: {
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
finale: {
|
|
27
|
+
screen: string;
|
|
28
|
+
shareText: string;
|
|
29
|
+
} | null;
|
|
30
|
+
beforeRestore?: () => void;
|
|
31
|
+
}): Promise<never>;
|
package/dist/game.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { TICK_RATE } from 'boomwait-core';
|
|
2
|
+
import { detectColorMode, KeyParser, QuitConfirm, startClaudeListener, TerminalSession, waitForPress, } from 'termwait';
|
|
3
|
+
import { chooseLayout } from './render.js';
|
|
4
|
+
import { createLatch, drain, onKey } from './input-latch.js';
|
|
5
|
+
// The sim steps at TICK_RATE (20Hz); the client loop runs in lockstep with
|
|
6
|
+
// it (real wall-clock interval — the no-Date.now rule binds core, not this
|
|
7
|
+
// client I/O shell, same as fragwait/checkwait's loops).
|
|
8
|
+
export const REDRAW_MS = 1000 / TICK_RATE;
|
|
9
|
+
export async function setupGame() {
|
|
10
|
+
const term = new TerminalSession(process.stdin, process.stdout);
|
|
11
|
+
const parser = new KeyParser();
|
|
12
|
+
const quitConfirm = new QuitConfirm(() => performance.now());
|
|
13
|
+
const colorMode = detectColorMode(process.env);
|
|
14
|
+
let latch = createLatch();
|
|
15
|
+
let banner = null;
|
|
16
|
+
let quit = false;
|
|
17
|
+
let cols = process.stdout.columns ?? 80;
|
|
18
|
+
let rows = process.stdout.rows ?? 24;
|
|
19
|
+
let resizeCb = null;
|
|
20
|
+
const listener = await startClaudeListener();
|
|
21
|
+
listener.onEvent((event) => {
|
|
22
|
+
banner = event === 'done' ? '✔ Claude is done' : '⚠ Claude needs your input';
|
|
23
|
+
});
|
|
24
|
+
const onData = (chunk) => {
|
|
25
|
+
for (const e of parser.feed(chunk)) {
|
|
26
|
+
if ('type' in e)
|
|
27
|
+
continue; // bomber has no mouse input
|
|
28
|
+
// Quit-intent keys only act on a genuine press — never a repeat/release —
|
|
29
|
+
// so holding q can't fast-forward past the confirm window (FEEL-12: a
|
|
30
|
+
// single keystroke, held or tapped, must never instantly quit).
|
|
31
|
+
if (e.kind === 'press') {
|
|
32
|
+
const lower = e.key.toLowerCase();
|
|
33
|
+
if (lower === 'ctrl-c') {
|
|
34
|
+
quit = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (lower === 'q' || lower === 'esc') {
|
|
38
|
+
quit = quitConfirm.request();
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
latch = onKey(latch, e);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
process.stdin.on('data', onData);
|
|
46
|
+
const onResize = () => {
|
|
47
|
+
cols = process.stdout.columns ?? 80;
|
|
48
|
+
rows = process.stdout.rows ?? 24;
|
|
49
|
+
// Full clear before the next frame: redraw alone only overwrites the new
|
|
50
|
+
// frame's footprint, leaving old-frame residue when the terminal grew or
|
|
51
|
+
// the layout's r/sideHud/glyph choice changed shape (chess-4 lesson).
|
|
52
|
+
term.write('\x1b[2J');
|
|
53
|
+
resizeCb?.();
|
|
54
|
+
};
|
|
55
|
+
process.stdout.on('resize', onResize);
|
|
56
|
+
return {
|
|
57
|
+
term,
|
|
58
|
+
parser,
|
|
59
|
+
colorMode,
|
|
60
|
+
listener,
|
|
61
|
+
layout: () => chooseLayout(cols, rows, colorMode),
|
|
62
|
+
drainInput: () => {
|
|
63
|
+
const { input, next } = drain(latch);
|
|
64
|
+
latch = next;
|
|
65
|
+
return input;
|
|
66
|
+
},
|
|
67
|
+
statusLine: () => (quitConfirm.armed ? 'press again to quit' : banner ?? ''),
|
|
68
|
+
quitRequested: () => quit,
|
|
69
|
+
onResize: (cb) => {
|
|
70
|
+
resizeCb = cb;
|
|
71
|
+
},
|
|
72
|
+
dispose: () => {
|
|
73
|
+
process.stdin.off('data', onData);
|
|
74
|
+
process.stdout.off('resize', onResize);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Exported for online.ts (Task 11) — same win/loss/draw phrasing offline and
|
|
79
|
+
// online.
|
|
80
|
+
export function resultLine(result, you) {
|
|
81
|
+
if (result.kind === 'draw')
|
|
82
|
+
return 'draw — no one survived';
|
|
83
|
+
return result.winner === you ? 'you won!' : 'you lost';
|
|
84
|
+
}
|
|
85
|
+
// Shared tail for both loops: finished screen (if the match ended, not on a
|
|
86
|
+
// mid-match quit) → waitForPress → Claude-listener close → caller cleanup →
|
|
87
|
+
// terminal restore → share card on the NORMAL screen (post-restore, so it
|
|
88
|
+
// lands in scrollback ready to copy into a post) → exit. Never actually
|
|
89
|
+
// returns (process.exit on every path) — callers declared to return
|
|
90
|
+
// Promise<Result> can `return await teardownAndExit(...)` since `never` is
|
|
91
|
+
// assignable to any type.
|
|
92
|
+
export async function teardownAndExit(o) {
|
|
93
|
+
if (o.finale) {
|
|
94
|
+
o.term.write(o.finale.screen);
|
|
95
|
+
// Only a real key press dismisses — never mouse motion, focus changes, or
|
|
96
|
+
// the release of a key held when the match ended (M1 lesson).
|
|
97
|
+
await waitForPress(process.stdin, o.parser);
|
|
98
|
+
}
|
|
99
|
+
await o.listener.close();
|
|
100
|
+
o.beforeRestore?.();
|
|
101
|
+
o.term.restore();
|
|
102
|
+
if (o.finale)
|
|
103
|
+
process.stdout.write('\n' + o.finale.shareText);
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=game.js.map
|
package/dist/game.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game.js","sourceRoot":"","sources":["../src/game.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Dir, Input } from 'boomwait-core';
|
|
2
|
+
import type { KeyEvent } from 'termwait';
|
|
3
|
+
export interface LatchState {
|
|
4
|
+
dir: Dir | null;
|
|
5
|
+
bombQueued: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLatch(): LatchState;
|
|
8
|
+
export declare function onKey(l: LatchState, e: KeyEvent): LatchState;
|
|
9
|
+
export declare function drain(l: LatchState): {
|
|
10
|
+
input: Input;
|
|
11
|
+
next: LatchState;
|
|
12
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function createLatch() {
|
|
2
|
+
return { dir: null, bombQueued: false };
|
|
3
|
+
}
|
|
4
|
+
const DIR_KEYS = {
|
|
5
|
+
w: 'up',
|
|
6
|
+
up: 'up',
|
|
7
|
+
s: 'down',
|
|
8
|
+
down: 'down',
|
|
9
|
+
a: 'left',
|
|
10
|
+
left: 'left',
|
|
11
|
+
d: 'right',
|
|
12
|
+
right: 'right',
|
|
13
|
+
};
|
|
14
|
+
const OPPOSITE = { up: 'down', down: 'up', left: 'right', right: 'left' };
|
|
15
|
+
// press/repeat of a dir key: same dir → no-op (the auto-repeat safety net);
|
|
16
|
+
// perpendicular → switch straight over; opposing → stop-first (tap again
|
|
17
|
+
// while stopped to reverse). space is a one-shot bomb queue: only a real
|
|
18
|
+
// press queues one — a repeat (only distinguishable at all on kitty-capable
|
|
19
|
+
// terminals) must never re-queue, or holding space would spam bombs every
|
|
20
|
+
// tick the OS repeats it. Release events never touch the latch — that's the
|
|
21
|
+
// whole point: the latch IS the persisted state, so a key release (which
|
|
22
|
+
// legacy terminals often can't even report) has nothing to do.
|
|
23
|
+
export function onKey(l, e) {
|
|
24
|
+
if (e.kind === 'release')
|
|
25
|
+
return l;
|
|
26
|
+
const dir = DIR_KEYS[e.key.toLowerCase()];
|
|
27
|
+
if (dir) {
|
|
28
|
+
if (l.dir === dir)
|
|
29
|
+
return l;
|
|
30
|
+
if (l.dir !== null && OPPOSITE[l.dir] === dir)
|
|
31
|
+
return { ...l, dir: null };
|
|
32
|
+
return { ...l, dir };
|
|
33
|
+
}
|
|
34
|
+
if (e.key === ' ') {
|
|
35
|
+
if (e.kind === 'repeat')
|
|
36
|
+
return l;
|
|
37
|
+
return { ...l, bombQueued: true };
|
|
38
|
+
}
|
|
39
|
+
return l;
|
|
40
|
+
}
|
|
41
|
+
// bombQueued is one-shot: drain hands it to this tick's Input and clears it,
|
|
42
|
+
// while dir simply carries forward (holding a direction needs no new events).
|
|
43
|
+
export function drain(l) {
|
|
44
|
+
return {
|
|
45
|
+
input: { dir: l.dir, bomb: l.bombQueued },
|
|
46
|
+
next: { dir: l.dir, bombQueued: false },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=input-latch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-latch.js","sourceRoot":"","sources":["../src/input-latch.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAA;AACzC,CAAC;AAED,MAAM,QAAQ,GAAwB;IACpC,CAAC,EAAE,IAAI;IACP,EAAE,EAAE,IAAI;IACR,CAAC,EAAE,MAAM;IACT,IAAI,EAAE,MAAM;IACZ,CAAC,EAAE,MAAM;IACT,IAAI,EAAE,MAAM;IACZ,CAAC,EAAE,OAAO;IACV,KAAK,EAAE,OAAO;CACf,CAAA;AAED,MAAM,QAAQ,GAAqB,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAE3F,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,4EAA4E;AAC5E,0EAA0E;AAC1E,4EAA4E;AAC5E,yEAAyE;AACzE,+DAA+D;AAC/D,MAAM,UAAU,KAAK,CAAC,CAAa,EAAE,CAAW;IAC9C,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IAElC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;IACzC,IAAI,GAAG,EAAE,CAAC;QACR,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG;YAAE,OAAO,CAAC,CAAA;QAC3B,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAA;QACzE,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAA;IACtB,CAAC;IAED,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAA;QACjC,OAAO,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAA;IACnC,CAAC;IAED,OAAO,CAAC,CAAA;AACV,CAAC;AAED,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,UAAU,KAAK,CAAC,CAAa;IACjC,OAAO;QACL,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,EAAE;QACzC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE;KACxC,CAAA;AACH,CAAC"}
|
package/dist/main.d.ts
ADDED
package/dist/main.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
import { sanitizeHandle } from 'boomwait-core';
|
|
3
|
+
import { runOffline } from './offline.js';
|
|
4
|
+
import { runOnline } from './online.js';
|
|
5
|
+
// Online is the default. --offline skips the join entirely (no network at all — useful when
|
|
6
|
+
// offline is explicitly wanted, e.g. no server reachable). Any join/connect failure while
|
|
7
|
+
// going online (join HTTP error, timeout, ws error, or a pre-start ws close) falls back to the
|
|
8
|
+
// same offline loop exactly once — runOnline only ever returns 'fallback' from a pre-match
|
|
9
|
+
// failure; once a match has actually started it always finishes and exits the process itself
|
|
10
|
+
// (teardownAndExit), so this branch can never run a second game on top of an online one.
|
|
11
|
+
export async function main(opts) {
|
|
12
|
+
const name = sanitizeHandle(opts.name ?? hostname());
|
|
13
|
+
const seed = Date.now() >>> 0;
|
|
14
|
+
if (!opts.offline) {
|
|
15
|
+
const result = await runOnline({ name, server: opts.server });
|
|
16
|
+
if (result !== 'fallback')
|
|
17
|
+
return; // unreachable in practice: runOnline exits the process itself once matched
|
|
18
|
+
console.log('boomwait: could not reach an online match — playing offline\n');
|
|
19
|
+
}
|
|
20
|
+
await runOffline({ difficulty: opts.difficulty, name, seed });
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvC,4FAA4F;AAC5F,0FAA0F;AAC1F,+FAA+F;AAC/F,2FAA2F;AAC3F,6FAA6F;AAC7F,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAa;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAA;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAE7B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAC7D,IAAI,MAAM,KAAK,UAAU;YAAE,OAAM,CAAC,2EAA2E;QAC7G,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAA;IAC9E,CAAC;IAED,MAAM,UAAU,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;AAC/D,CAAC"}
|
package/dist/net.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Dir, type Input, type InputMsg, type Result, type StartMsg, type WireState } from 'boomwait-core';
|
|
2
|
+
import { WebSocket } from 'ws';
|
|
3
|
+
export interface BomberNetHandlers {
|
|
4
|
+
onSnap(state: WireState): void;
|
|
5
|
+
onEnd(result: Result): void;
|
|
6
|
+
onClose(reason: string): void;
|
|
7
|
+
}
|
|
8
|
+
export type JoinOutcome = {
|
|
9
|
+
kind: 'joined';
|
|
10
|
+
matchId: string;
|
|
11
|
+
token: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'error';
|
|
14
|
+
};
|
|
15
|
+
export type WebSocketFactory = (url: string) => WebSocket;
|
|
16
|
+
export declare function joinBomberMatch(serverUrl: string, name: string, timeoutMs?: number): Promise<JoinOutcome>;
|
|
17
|
+
export declare class BomberNetClient {
|
|
18
|
+
private ws;
|
|
19
|
+
private constructor();
|
|
20
|
+
static connect(serverUrl: string, matchId: string, token: string, name: string, handlers: BomberNetHandlers, timeoutMs?: number, factory?: WebSocketFactory): Promise<{
|
|
21
|
+
client: BomberNetClient;
|
|
22
|
+
start: StartMsg;
|
|
23
|
+
}>;
|
|
24
|
+
sendInput(msg: InputMsg): void;
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function diffInputForWire(prevDir: Dir | null, input: Input): {
|
|
28
|
+
msg: InputMsg | null;
|
|
29
|
+
nextDir: Dir | null;
|
|
30
|
+
};
|
package/dist/net.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Bomber's online transport: a one-shot lobby POST (up to MAX_PLAYERS humans gather for
|
|
2
|
+
// ~10s, bots always backfill any empty slots — never a noOpponent-style outcome, see Task 8's
|
|
3
|
+
// bomber-lobby.ts) followed by a persistent match WebSocket. Mirrors
|
|
4
|
+
// packages/chess-client/src/net.ts's shape (factory seam for tests, handlers wired for the
|
|
5
|
+
// connection's whole life), but resolves on `start` instead of `welcome`, and the ongoing
|
|
6
|
+
// traffic afterward is a `snap` stream (client is render-only — it never locally steps the
|
|
7
|
+
// sim, every snap REPLACES the rendered state) rather than a per-move relay.
|
|
8
|
+
import { parseBomberServerMsg } from 'boomwait-core';
|
|
9
|
+
import { WebSocket } from 'ws';
|
|
10
|
+
const realFactory = (url) => new WebSocket(url);
|
|
11
|
+
// POST {serverUrl}/bomber/join {name}. The lobby gathers for ~10s (Task 8) then always answers
|
|
12
|
+
// {matchId, token} — bots backfill empty slots server-side, so there is no noOpponent case to
|
|
13
|
+
// distinguish here. ANY failure (network error, non-2xx, timeout, malformed body) collapses to
|
|
14
|
+
// the same 'error' outcome; the caller's fallback (play offline) is identical regardless of why.
|
|
15
|
+
export async function joinBomberMatch(serverUrl, name, timeoutMs = 12_000) {
|
|
16
|
+
const base = serverUrl.replace(/\/$/, '');
|
|
17
|
+
try {
|
|
18
|
+
const res = await fetch(`${base}/bomber/join`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
body: JSON.stringify({ name }),
|
|
22
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok)
|
|
25
|
+
return { kind: 'error' };
|
|
26
|
+
const body = (await res.json());
|
|
27
|
+
if (typeof body.matchId === 'string' && typeof body.token === 'string') {
|
|
28
|
+
return { kind: 'joined', matchId: body.matchId, token: body.token };
|
|
29
|
+
}
|
|
30
|
+
return { kind: 'error' };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { kind: 'error' };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class BomberNetClient {
|
|
37
|
+
ws;
|
|
38
|
+
constructor(ws) {
|
|
39
|
+
this.ws = ws;
|
|
40
|
+
}
|
|
41
|
+
// Opens ws(s)://.../bomber/match/{matchId}/ws?token={token}, sends hello, resolves once
|
|
42
|
+
// `start` arrives with the client plus the StartMsg payload (seeds the caller's local
|
|
43
|
+
// mirror). `handlers` stay wired for the life of the connection — snap/end/close events
|
|
44
|
+
// keep flowing through them after connect() resolves.
|
|
45
|
+
static async connect(serverUrl, matchId, token, name, handlers, timeoutMs = 12_000, factory = realFactory) {
|
|
46
|
+
const base = serverUrl.replace(/\/$/, '').replace(/^http/, 'ws');
|
|
47
|
+
const ws = factory(`${base}/bomber/match/${matchId}/ws?token=${encodeURIComponent(token)}`);
|
|
48
|
+
const client = new BomberNetClient(ws);
|
|
49
|
+
// Gates onClose so a pre-start close/error rejects connect() instead of reaching the
|
|
50
|
+
// caller's handlers — only an established session's close should ever fire onClose.
|
|
51
|
+
let started = false;
|
|
52
|
+
const start = await new Promise((resolve, reject) => {
|
|
53
|
+
const timer = setTimeout(() => reject(new Error('start timeout')), timeoutMs);
|
|
54
|
+
ws.on('open', () => ws.send(JSON.stringify({ t: 'hello', name })));
|
|
55
|
+
ws.on('message', (data) => {
|
|
56
|
+
const msg = parseBomberServerMsg(data.toString());
|
|
57
|
+
if (!msg)
|
|
58
|
+
return; // garbage/oversized/malformed: ignored, never crashes or corrupts state
|
|
59
|
+
if (msg.t === 'start') {
|
|
60
|
+
started = true;
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
resolve(msg);
|
|
63
|
+
}
|
|
64
|
+
else if (msg.t === 'snap') {
|
|
65
|
+
handlers.onSnap(msg.state);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
handlers.onEnd(msg.result);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
ws.on('close', (_code, reason) => {
|
|
72
|
+
const why = reason.toString();
|
|
73
|
+
if (!started) {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
reject(new Error(`connect closed: ${why || 'no reason'}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
handlers.onClose(why);
|
|
79
|
+
});
|
|
80
|
+
ws.on('error', (err) => {
|
|
81
|
+
if (!started) {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
reject(err);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return { client, start };
|
|
88
|
+
}
|
|
89
|
+
sendInput(msg) {
|
|
90
|
+
if (this.ws.readyState === WebSocket.OPEN)
|
|
91
|
+
this.ws.send(JSON.stringify(msg));
|
|
92
|
+
}
|
|
93
|
+
close() {
|
|
94
|
+
try {
|
|
95
|
+
this.ws.close();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* already closed */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Minimal-diff wire logic: only send an InputMsg when this tick's latched Input actually
|
|
103
|
+
// differs from what the server was last told. A quiet tick (dir unchanged, no bomb queued)
|
|
104
|
+
// sends nothing at all; a dir change sends the new dir; a bomb queued with the dir unchanged
|
|
105
|
+
// sends dir:'keep' (the server's own handleMessage in bomber-match.ts treats 'keep' as "don't
|
|
106
|
+
// touch the latched dir") so the bomb flag rides alone. Pure and separately tested (mirrors
|
|
107
|
+
// input-latch.ts's pure onKey/drain — the loop in online.ts just calls this every tick).
|
|
108
|
+
export function diffInputForWire(prevDir, input) {
|
|
109
|
+
const dirChanged = input.dir !== prevDir;
|
|
110
|
+
if (!dirChanged && !input.bomb)
|
|
111
|
+
return { msg: null, nextDir: prevDir };
|
|
112
|
+
return { msg: { t: 'input', dir: dirChanged ? input.dir : 'keep', bomb: input.bomb }, nextDir: input.dir };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=net.js.map
|
package/dist/net.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"net.js","sourceRoot":"","sources":["../src/net.ts"],"names":[],"mappings":"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"}
|
package/dist/offline.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { botDecide, createBotMind, createMatch, step } from 'boomwait-core';
|
|
2
|
+
import { renderFrame } from './render.js';
|
|
3
|
+
import { resultLine, setupGame, teardownAndExit, REDRAW_MS } from './game.js';
|
|
4
|
+
import { shareCard } from './share.js';
|
|
5
|
+
const YOU = 0;
|
|
6
|
+
const BOT_NAMES = ['bot·1', 'bot·2', 'bot·3'];
|
|
7
|
+
export async function runOffline(opts) {
|
|
8
|
+
const session = await setupGame();
|
|
9
|
+
const seed = opts.seed >>> 0;
|
|
10
|
+
let state = createMatch(seed, [opts.name, ...BOT_NAMES], [false, true, true, true]);
|
|
11
|
+
// Deterministic per-bot minds, seeded off the match seed (never wall-clock —
|
|
12
|
+
// the same seed always reproduces the same match, bots included).
|
|
13
|
+
const minds = [1, 2, 3].map((id) => createBotMind((seed + id) >>> 0));
|
|
14
|
+
const redraw = () => {
|
|
15
|
+
session.term.write(renderFrame(state, YOU, session.layout(), session.statusLine(), session.colorMode));
|
|
16
|
+
};
|
|
17
|
+
session.onResize(redraw);
|
|
18
|
+
session.term.enter();
|
|
19
|
+
session.term.installExitGuards(() => {
|
|
20
|
+
/* no extra resources to release offline */
|
|
21
|
+
});
|
|
22
|
+
redraw();
|
|
23
|
+
await new Promise((resolve) => {
|
|
24
|
+
const timer = setInterval(() => {
|
|
25
|
+
const yourInput = session.drainInput();
|
|
26
|
+
const inputs = [
|
|
27
|
+
yourInput,
|
|
28
|
+
botDecide(state, 1, minds[0], opts.difficulty),
|
|
29
|
+
botDecide(state, 2, minds[1], opts.difficulty),
|
|
30
|
+
botDecide(state, 3, minds[2], opts.difficulty),
|
|
31
|
+
];
|
|
32
|
+
state = step(state, inputs);
|
|
33
|
+
redraw();
|
|
34
|
+
if (session.quitRequested() || state.result) {
|
|
35
|
+
clearInterval(timer);
|
|
36
|
+
resolve();
|
|
37
|
+
}
|
|
38
|
+
}, REDRAW_MS);
|
|
39
|
+
});
|
|
40
|
+
session.dispose();
|
|
41
|
+
return teardownAndExit({
|
|
42
|
+
term: session.term,
|
|
43
|
+
parser: session.parser,
|
|
44
|
+
listener: session.listener,
|
|
45
|
+
finale: state.result
|
|
46
|
+
? {
|
|
47
|
+
screen: renderFrame(state, YOU, session.layout(), `${resultLine(state.result, YOU)} — press any key`, session.colorMode),
|
|
48
|
+
shareText: shareCard(state.result, YOU, state.tick, `bot·${opts.difficulty}`),
|
|
49
|
+
}
|
|
50
|
+
: null,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=offline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline.js","sourceRoot":"","sources":["../src/offline.ts"],"names":[],"mappings":"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"}
|
package/dist/online.d.ts
ADDED
package/dist/online.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createMatch, fromWire, MAX_PLAYERS, sanitizeHandle } from 'boomwait-core';
|
|
2
|
+
import { hostname } from 'node:os';
|
|
3
|
+
import { renderFrame } from './render.js';
|
|
4
|
+
import { REDRAW_MS, resultLine, setupGame, teardownAndExit } from './game.js';
|
|
5
|
+
import { diffInputForWire, joinBomberMatch, BomberNetClient } from './net.js';
|
|
6
|
+
import { shareCard } from './share.js';
|
|
7
|
+
export async function runOnline(opts) {
|
|
8
|
+
const name = sanitizeHandle(opts.name ?? hostname());
|
|
9
|
+
const joined = await joinBomberMatch(opts.server, name);
|
|
10
|
+
if (joined.kind !== 'joined')
|
|
11
|
+
return 'fallback';
|
|
12
|
+
let ended = null;
|
|
13
|
+
let closedEarly = false; // ws closed after start with no `end` ever arriving (dropped/kicked)
|
|
14
|
+
// Hoisted above the connect() call and seeded null: onSnap below closes over `state`, and a
|
|
15
|
+
// coalesced ws chunk can deliver `start` and the first `snap` in ONE macrotask, back to back
|
|
16
|
+
// — the 'start' message resolves connect()'s internal promise, but that resolution's
|
|
17
|
+
// continuation (back in this function, past the `await`) only runs on the NEXT microtask
|
|
18
|
+
// turn, so a `snap` arriving synchronously right after it is still handled by onSnap while
|
|
19
|
+
// this function is suspended mid-`await`. A `let state = ...` declared AFTER the `await`
|
|
20
|
+
// leaves `state` in the temporal dead zone for that window — accessing it throws a bare
|
|
21
|
+
// ReferenceError from inside a raw event-loop callback (an uncaught exception, since exit
|
|
22
|
+
// guards aren't installed yet), not a rejected promise this function's own try/catch can
|
|
23
|
+
// see. Declaring it here means onSnap always has a variable to assign into.
|
|
24
|
+
let state = null;
|
|
25
|
+
let connected;
|
|
26
|
+
try {
|
|
27
|
+
connected = await BomberNetClient.connect(opts.server, joined.matchId, joined.token, name, {
|
|
28
|
+
onSnap(wire) {
|
|
29
|
+
state = fromWire(wire);
|
|
30
|
+
},
|
|
31
|
+
onEnd(result) {
|
|
32
|
+
ended = result;
|
|
33
|
+
},
|
|
34
|
+
onClose() {
|
|
35
|
+
closedEarly = true;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return 'fallback';
|
|
41
|
+
}
|
|
42
|
+
const { client: net, start } = connected;
|
|
43
|
+
const you = start.you;
|
|
44
|
+
// Seeds the local mirror deterministically from the SAME inputs the server used to create
|
|
45
|
+
// its own tick-0 state — this is what's on screen for the handful of ticks before the
|
|
46
|
+
// first `snap` arrives; every `snap` after that overwrites it outright. Also the point past
|
|
47
|
+
// which `state` is guaranteed non-null: any pre-seed `snap` (the race above) already landed
|
|
48
|
+
// in the `state` variable via onSnap, and this unconditionally overwrites it with the
|
|
49
|
+
// server's own tick-0 board either way — same precedence a `snap` always has over the local
|
|
50
|
+
// mirror.
|
|
51
|
+
state = createMatch(start.seed, start.names, start.bots);
|
|
52
|
+
const session = await setupGame();
|
|
53
|
+
let lastSentDir = null;
|
|
54
|
+
const redraw = () => {
|
|
55
|
+
session.term.write(renderFrame(state, you, session.layout(), session.statusLine(), session.colorMode));
|
|
56
|
+
};
|
|
57
|
+
session.onResize(redraw);
|
|
58
|
+
session.term.enter();
|
|
59
|
+
// Quit-confirm during an online match sends nothing special: closing the socket IS
|
|
60
|
+
// elimination (the server's GRACE_MS window in bomber-match.ts covers an accidental drop).
|
|
61
|
+
session.term.installExitGuards(() => net.close());
|
|
62
|
+
redraw();
|
|
63
|
+
await new Promise((resolve) => {
|
|
64
|
+
const timer = setInterval(() => {
|
|
65
|
+
const input = session.drainInput();
|
|
66
|
+
const { msg, nextDir } = diffInputForWire(lastSentDir, input);
|
|
67
|
+
if (msg) {
|
|
68
|
+
net.sendInput(msg);
|
|
69
|
+
lastSentDir = nextDir;
|
|
70
|
+
}
|
|
71
|
+
redraw();
|
|
72
|
+
if (session.quitRequested() || ended !== null || closedEarly || state.result) {
|
|
73
|
+
clearInterval(timer);
|
|
74
|
+
resolve();
|
|
75
|
+
}
|
|
76
|
+
}, REDRAW_MS);
|
|
77
|
+
});
|
|
78
|
+
session.dispose();
|
|
79
|
+
if (session.quitRequested())
|
|
80
|
+
net.close();
|
|
81
|
+
// Result precedence: an explicit `end` message wins; otherwise the last snap's own baked-in
|
|
82
|
+
// result (the server always stamps the final snap's WireState.result before sending `end` —
|
|
83
|
+
// see bomber-match.ts tick()); if NEITHER ever arrived but the socket still closed (an
|
|
84
|
+
// abnormal disconnect pre-finish), synthesize "not you" won so the finale still reads
|
|
85
|
+
// correctly (resultLine only ever distinguishes winner === you from everyone else) — a
|
|
86
|
+
// dropped connection is the one thing we know for certain didn't end in our own win, even
|
|
87
|
+
// though the actual survivor is unknowable from here. A player-initiated quit never shows a
|
|
88
|
+
// finale at all (matches offline.ts's mid-match-quit behavior), so it's excluded first.
|
|
89
|
+
const finalResult = session.quitRequested()
|
|
90
|
+
? null
|
|
91
|
+
: (ended ?? state.result ?? (closedEarly ? { kind: 'win', winner: (you + 1) % MAX_PLAYERS } : null));
|
|
92
|
+
if (finalResult)
|
|
93
|
+
state = { ...state, result: finalResult };
|
|
94
|
+
return teardownAndExit({
|
|
95
|
+
term: session.term,
|
|
96
|
+
parser: session.parser,
|
|
97
|
+
listener: session.listener,
|
|
98
|
+
beforeRestore: () => net.close(),
|
|
99
|
+
finale: finalResult
|
|
100
|
+
? {
|
|
101
|
+
screen: renderFrame(state, you, session.layout(), `${resultLine(finalResult, you)} — press any key`, session.colorMode),
|
|
102
|
+
shareText: shareCard(finalResult, you, state.tick, start.names.filter((_, i) => i !== you).join(', ')),
|
|
103
|
+
}
|
|
104
|
+
: null,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=online.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"online.js","sourceRoot":"","sources":["../src/online.ts"],"names":[],"mappings":"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"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BomberState } from 'boomwait-core';
|
|
2
|
+
import type { ColorMode } from 'termwait';
|
|
3
|
+
export interface Layout {
|
|
4
|
+
r: number;
|
|
5
|
+
sideHud: boolean;
|
|
6
|
+
glyph: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function chooseLayout(cols: number, rows: number, mode: ColorMode): Layout;
|
|
9
|
+
export declare function renderFrame(s: BomberState, you: number, layout: Layout, claude: string, mode?: ColorMode): string;
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { GRID_H, GRID_W, FUSE_TICKS, SHRINK_START_TICK, TICK_RATE, idx } from 'boomwait-core';
|
|
2
|
+
import { SPRITES, scaleMask } from './sprites.js';
|
|
3
|
+
// Board = 26r cols x 11r rows (tile = 2r cols x r rows; GRID_W=13 tiles wide,
|
|
4
|
+
// GRID_H=11 tall). Largest r wins, preferring a side HUD (board gets every
|
|
5
|
+
// row — the checkwait/chess-4d lesson: default terminals are wide but short,
|
|
6
|
+
// so a below-board HUD line can starve the board of rows before the board
|
|
7
|
+
// ever needs the extra width) whenever it fits; the below-board ladder only
|
|
8
|
+
// matters for narrow-but-tall windows. r<2 or non-truecolor drops to glyph
|
|
9
|
+
// mode (2 cols/tile letters — an 8x8 pixel sprite scaled into <2 rows is
|
|
10
|
+
// illegible, and non-truecolor terminals can't composite the half-block fg/bg
|
|
11
|
+
// pairs sprites rely on).
|
|
12
|
+
function fitsSideHud(r, cols, rows) {
|
|
13
|
+
return 11 * r + 1 <= rows && 26 * r + 27 <= cols;
|
|
14
|
+
}
|
|
15
|
+
function fitsBelowHud(r, cols, rows) {
|
|
16
|
+
return 11 * r + 1 <= rows && 26 * r <= cols && 11 * r + 8 <= rows;
|
|
17
|
+
}
|
|
18
|
+
export function chooseLayout(cols, rows, mode) {
|
|
19
|
+
const upperBound = Math.floor((rows - 1) / 11);
|
|
20
|
+
let foundR = 0;
|
|
21
|
+
let foundSideHud = false;
|
|
22
|
+
for (let r = Math.max(upperBound, 1); r >= 1; r--) {
|
|
23
|
+
const sideOk = fitsSideHud(r, cols, rows);
|
|
24
|
+
const belowOk = fitsBelowHud(r, cols, rows);
|
|
25
|
+
if (sideOk || belowOk) {
|
|
26
|
+
foundR = r;
|
|
27
|
+
foundSideHud = sideOk; // side HUD preferred whenever it fits
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const glyph = mode !== 'truecolor' || foundR < 2;
|
|
32
|
+
return { r: Math.max(foundR, 1), sideHud: foundSideHud, glyph };
|
|
33
|
+
}
|
|
34
|
+
const ESC = '\x1b';
|
|
35
|
+
const RESET = `${ESC}[0m`;
|
|
36
|
+
function bg(rgb) {
|
|
37
|
+
return `${ESC}[48;2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
|
|
38
|
+
}
|
|
39
|
+
function fg(rgb) {
|
|
40
|
+
return `${ESC}[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
|
|
41
|
+
}
|
|
42
|
+
const HARD_RGB = [92, 92, 100];
|
|
43
|
+
const SOFT_RGB = [163, 116, 58];
|
|
44
|
+
const SOFT_TEXTURE_RGB = [110, 78, 38];
|
|
45
|
+
const EMPTY_RGB = [40, 92, 48];
|
|
46
|
+
const FLAME_BG_RGB = [120, 30, 20];
|
|
47
|
+
const FLAME_FG_RGB = [255, 200, 60];
|
|
48
|
+
const BOMB_SPARK_RGB = [
|
|
49
|
+
[255, 255, 255],
|
|
50
|
+
[255, 210, 60],
|
|
51
|
+
[255, 60, 40],
|
|
52
|
+
];
|
|
53
|
+
// 4 distinct, high-contrast team colors (feel-1 lesson: every fg must
|
|
54
|
+
// contrast every bg it can land on — floor/soft/hard are all mid-dark, so
|
|
55
|
+
// bright saturated hues stay legible on all three).
|
|
56
|
+
const TEAM_RGB = [
|
|
57
|
+
[230, 70, 70], // p0 red
|
|
58
|
+
[70, 150, 235], // p1 blue
|
|
59
|
+
[110, 220, 110], // p2 green
|
|
60
|
+
[235, 205, 70], // p3 gold
|
|
61
|
+
];
|
|
62
|
+
const DROP_RGB = {
|
|
63
|
+
bomb: [210, 210, 220],
|
|
64
|
+
range: [80, 225, 225],
|
|
65
|
+
speed: [255, 230, 90],
|
|
66
|
+
};
|
|
67
|
+
function bombFuseStage(fuse) {
|
|
68
|
+
if (fuse > (2 * FUSE_TICKS) / 3)
|
|
69
|
+
return 0;
|
|
70
|
+
if (fuse > FUSE_TICKS / 3)
|
|
71
|
+
return 1;
|
|
72
|
+
return 2;
|
|
73
|
+
}
|
|
74
|
+
function buildOccupancy(s) {
|
|
75
|
+
const flameAt = new Map();
|
|
76
|
+
for (const f of s.flames)
|
|
77
|
+
flameAt.set(idx(f.x, f.y), f);
|
|
78
|
+
const bombAt = new Map();
|
|
79
|
+
for (const b of s.bombs)
|
|
80
|
+
bombAt.set(idx(b.x, b.y), b);
|
|
81
|
+
const playerAt = new Map();
|
|
82
|
+
for (const p of s.players)
|
|
83
|
+
if (p.alive)
|
|
84
|
+
playerAt.set(idx(p.x, p.y), p.id);
|
|
85
|
+
const dropAt = new Map();
|
|
86
|
+
for (const d of s.drops)
|
|
87
|
+
dropAt.set(idx(d.x, d.y), d);
|
|
88
|
+
return { flameAt, bombAt, playerAt, dropAt };
|
|
89
|
+
}
|
|
90
|
+
// Draw priority: flame > player > bomb > drop > bare cell — a flame front
|
|
91
|
+
// visually consumes whatever was under it, and a player standing on their own
|
|
92
|
+
// (or anyone's) bomb/drop is the thing you're tracking, not the tile.
|
|
93
|
+
function tileVisual(s, occ, x, y) {
|
|
94
|
+
const i = idx(x, y);
|
|
95
|
+
const cell = s.grid[i];
|
|
96
|
+
const baseBg = cell === 'hard' ? HARD_RGB : cell === 'soft' ? SOFT_RGB : EMPTY_RGB;
|
|
97
|
+
if (occ.flameAt.has(i))
|
|
98
|
+
return { bgRgb: FLAME_BG_RGB, maskKey: 'flame', fgRgb: FLAME_FG_RGB };
|
|
99
|
+
const playerId = occ.playerAt.get(i);
|
|
100
|
+
if (playerId !== undefined)
|
|
101
|
+
return { bgRgb: baseBg, maskKey: `p${playerId}`, fgRgb: TEAM_RGB[playerId] };
|
|
102
|
+
const bomb = occ.bombAt.get(i);
|
|
103
|
+
if (bomb) {
|
|
104
|
+
const stage = bombFuseStage(bomb.fuse);
|
|
105
|
+
return { bgRgb: baseBg, maskKey: `bomb${stage}`, fgRgb: BOMB_SPARK_RGB[stage] };
|
|
106
|
+
}
|
|
107
|
+
const drop = occ.dropAt.get(i);
|
|
108
|
+
if (drop)
|
|
109
|
+
return { bgRgb: baseBg, maskKey: `drop-${drop.kind}`, fgRgb: DROP_RGB[drop.kind] };
|
|
110
|
+
if (cell === 'soft')
|
|
111
|
+
return { bgRgb: baseBg, maskKey: 'soft', fgRgb: SOFT_TEXTURE_RGB };
|
|
112
|
+
return { bgRgb: baseBg, maskKey: null, fgRgb: baseBg };
|
|
113
|
+
}
|
|
114
|
+
// Sprite-mode arena: half-block ▀▄ pixel compositing (chess-4's
|
|
115
|
+
// board-render.ts pipeline), one 2r x 2r pixel grid per tile.
|
|
116
|
+
function spriteArena(s, occ, r) {
|
|
117
|
+
const px = 2 * r;
|
|
118
|
+
const scaleCache = new Map();
|
|
119
|
+
const scaled = (key) => {
|
|
120
|
+
let m = scaleCache.get(key);
|
|
121
|
+
if (!m) {
|
|
122
|
+
m = scaleMask(SPRITES[key], px);
|
|
123
|
+
scaleCache.set(key, m);
|
|
124
|
+
}
|
|
125
|
+
return m;
|
|
126
|
+
};
|
|
127
|
+
const lines = [];
|
|
128
|
+
for (let ty = 0; ty < GRID_H; ty++) {
|
|
129
|
+
for (let subRow = 0; subRow < r; subRow++) {
|
|
130
|
+
let line = '';
|
|
131
|
+
for (let tx = 0; tx < GRID_W; tx++) {
|
|
132
|
+
const v = tileVisual(s, occ, tx, ty);
|
|
133
|
+
let cell = bg(v.bgRgb);
|
|
134
|
+
if (v.maskKey) {
|
|
135
|
+
const mask = scaled(v.maskKey);
|
|
136
|
+
const topRow = mask[2 * subRow];
|
|
137
|
+
const botRow = mask[2 * subRow + 1];
|
|
138
|
+
const sprFg = fg(v.fgRgb);
|
|
139
|
+
for (let px_ = 0; px_ < px; px_++) {
|
|
140
|
+
const top = topRow?.[px_] === true;
|
|
141
|
+
const bot = botRow?.[px_] === true;
|
|
142
|
+
if (!top && !bot)
|
|
143
|
+
cell += ' ';
|
|
144
|
+
else
|
|
145
|
+
cell += sprFg + (top && bot ? '█' : top ? '▀' : '▄');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
cell += ' '.repeat(px);
|
|
150
|
+
}
|
|
151
|
+
line += cell + RESET;
|
|
152
|
+
}
|
|
153
|
+
lines.push(line);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
158
|
+
// Basic-SGR (30-107 range) fallbacks for the truecolor palette above — glyph
|
|
159
|
+
// mode usually exists BECAUSE the terminal isn't truecolor (Apple Terminal
|
|
160
|
+
// detects as '256'), so it must not emit 24-bit escapes there. Mirrors chess
|
|
161
|
+
// board-render.ts's dedicated basic-mode branch.
|
|
162
|
+
const TEAM_BASIC = [91, 94, 92, 93]; // bright red/blue/green/yellow
|
|
163
|
+
const FLAME_BASIC = 93;
|
|
164
|
+
const BOMB_SPARK_BASIC = [97, 93, 91]; // white → yellow → red, matching the RGB fuse ramp
|
|
165
|
+
const DROP_BASIC = { bomb: 97, range: 96, speed: 93 };
|
|
166
|
+
const SOFT_BASIC = 33;
|
|
167
|
+
// Glyph-mode arena: 2 cols/tile letters — legible with no truecolor
|
|
168
|
+
// compositing and no minimum cell height. Color budget follows the terminal:
|
|
169
|
+
// truecolor keeps 24-bit fg (the r<2 tiny-window case), '256' uses basic SGR,
|
|
170
|
+
// mono emits no escapes at all (letters carry everything; soft's ▒ shade
|
|
171
|
+
// glyph also drops to ASCII there — some mono terminals are ASCII-only).
|
|
172
|
+
function glyphArena(s, occ, mode) {
|
|
173
|
+
const paint = (text, rgb, basic) => mode === 'truecolor' ? fg(rgb) + text + RESET : mode === '256' ? `${ESC}[${basic}m` + text + RESET : text;
|
|
174
|
+
const lines = [];
|
|
175
|
+
for (let ty = 0; ty < GRID_H; ty++) {
|
|
176
|
+
let line = '';
|
|
177
|
+
for (let tx = 0; tx < GRID_W; tx++) {
|
|
178
|
+
const i = idx(tx, ty);
|
|
179
|
+
if (occ.flameAt.has(i)) {
|
|
180
|
+
line += paint('* ', FLAME_FG_RGB, FLAME_BASIC);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const playerId = occ.playerAt.get(i);
|
|
184
|
+
if (playerId !== undefined) {
|
|
185
|
+
line += paint('@' + String(playerId + 1), TEAM_RGB[playerId], TEAM_BASIC[playerId]);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const bomb = occ.bombAt.get(i);
|
|
189
|
+
if (bomb) {
|
|
190
|
+
const stage = bombFuseStage(bomb.fuse);
|
|
191
|
+
line += paint('o ', BOMB_SPARK_RGB[stage], BOMB_SPARK_BASIC[stage]);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const drop = occ.dropAt.get(i);
|
|
195
|
+
if (drop) {
|
|
196
|
+
line += paint(drop.kind[0].toUpperCase() + ' ', DROP_RGB[drop.kind], DROP_BASIC[drop.kind]);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const cell = s.grid[i];
|
|
200
|
+
if (cell === 'hard')
|
|
201
|
+
line += '##';
|
|
202
|
+
else if (cell === 'soft')
|
|
203
|
+
line += mode === 'mono' ? 'xx' : paint('▒▒', SOFT_RGB, SOFT_BASIC);
|
|
204
|
+
else
|
|
205
|
+
line += ' ';
|
|
206
|
+
}
|
|
207
|
+
lines.push(line);
|
|
208
|
+
}
|
|
209
|
+
return lines;
|
|
210
|
+
}
|
|
211
|
+
function fmtClock(tick) {
|
|
212
|
+
const totalSec = Math.max(0, Math.floor(tick / TICK_RATE));
|
|
213
|
+
const m = Math.floor(totalSec / 60);
|
|
214
|
+
const sec = totalSec % 60;
|
|
215
|
+
return `${m}:${String(sec).padStart(2, '0')}`;
|
|
216
|
+
}
|
|
217
|
+
// Sudden-death shrink starts at SHRINK_START_TICK; the HUD counts down to it,
|
|
218
|
+
// then flags it active once the spiral starts closing tiles.
|
|
219
|
+
function shrinkStatus(tick) {
|
|
220
|
+
if (tick >= SHRINK_START_TICK)
|
|
221
|
+
return '⚠ shrinking';
|
|
222
|
+
const remainSec = Math.ceil((SHRINK_START_TICK - tick) / TICK_RATE);
|
|
223
|
+
return `shrink in ${remainSec}s`;
|
|
224
|
+
}
|
|
225
|
+
function playerLine(p, isYou) {
|
|
226
|
+
const mark = isYou ? '▸' : ' ';
|
|
227
|
+
const dead = p.alive ? '' : ' †';
|
|
228
|
+
return `${mark}${p.name}${dead}`;
|
|
229
|
+
}
|
|
230
|
+
// Fixed side-HUD text budget (matches the `+ 27` in fitsSideHud — constant
|
|
231
|
+
// regardless of r, so renderFrame doesn't need cols/rows: chooseLayout only
|
|
232
|
+
// ever returns a Layout whose canonical rendered size already fits whatever
|
|
233
|
+
// terminal it was computed against).
|
|
234
|
+
const SIDE_TEXT_WIDTH = 26;
|
|
235
|
+
// Spec HUD row: color swatch, name, alive/dead, bomb/range/speed counts. The
|
|
236
|
+
// swatch carries a color escape, so it is built and width-budgeted here
|
|
237
|
+
// (never handed to renderFrame's generic `text.slice(0, SIDE_TEXT_WIDTH)`,
|
|
238
|
+
// which operates on raw chars and would slice straight through an escape
|
|
239
|
+
// sequence, corrupting the terminal for every line after it) — 1 visible col
|
|
240
|
+
// for the swatch, the rest of the budget for the plain-text remainder.
|
|
241
|
+
function playerHudRow(p, isYou, mode) {
|
|
242
|
+
const swatch = mode === 'truecolor'
|
|
243
|
+
? fg(TEAM_RGB[p.id]) + '■' + RESET
|
|
244
|
+
: mode === '256'
|
|
245
|
+
? `${ESC}[${TEAM_BASIC[p.id]}m■${RESET}`
|
|
246
|
+
: '#';
|
|
247
|
+
const mark = isYou ? '▸' : ' ';
|
|
248
|
+
const dead = p.alive ? '' : ' †'; // dagger marks the DEAD (same polarity as playerLine above)
|
|
249
|
+
const stats = p.alive ? ` b${p.bombCap}r${p.range}s${p.speed}` : '';
|
|
250
|
+
const plain = `${mark}${p.name}${dead}${stats}`;
|
|
251
|
+
return swatch + plain.slice(0, SIDE_TEXT_WIDTH - 1);
|
|
252
|
+
}
|
|
253
|
+
// Two lines so each stays within SIDE_TEXT_WIDTH (26 cols) — matches the
|
|
254
|
+
// README's boomwait-controls table wording.
|
|
255
|
+
const KEY_HINT_ROWS = ['wasd/arrows move', 'space bomb, q-q quit'];
|
|
256
|
+
export function renderFrame(s, you, layout, claude, mode = 'truecolor') {
|
|
257
|
+
const { r, sideHud, glyph } = layout;
|
|
258
|
+
const occ = buildOccupancy(s);
|
|
259
|
+
const arenaLines = glyph ? glyphArena(s, occ, mode) : spriteArena(s, occ, r);
|
|
260
|
+
const boardCols = glyph ? GRID_W * 2 : GRID_W * 2 * r;
|
|
261
|
+
const clock = fmtClock(s.tick);
|
|
262
|
+
const shrink = shrinkStatus(s.tick);
|
|
263
|
+
const names = s.players.map((p) => playerLine(p, p.id === you));
|
|
264
|
+
const lines = [];
|
|
265
|
+
if (sideHud) {
|
|
266
|
+
// Per spec: player rows (color swatch, name, alive/dead, bomb/range/speed
|
|
267
|
+
// counts), round timer, shrink warning, Claude status (appended by the
|
|
268
|
+
// caller as `claude` below — already present), key hints. Player rows
|
|
269
|
+
// carry their own pre-budgeted color escape (see playerHudRow) and are
|
|
270
|
+
// appended verbatim; every other row here is plain text and goes through
|
|
271
|
+
// the same generic width slice as before.
|
|
272
|
+
const plainRows = [clock, shrink];
|
|
273
|
+
const playerRows = s.players.map((p) => playerHudRow(p, p.id === you, mode));
|
|
274
|
+
const hintRows = KEY_HINT_ROWS;
|
|
275
|
+
for (let i = 0; i < arenaLines.length; i++) {
|
|
276
|
+
let line = arenaLines[i];
|
|
277
|
+
if (i < plainRows.length) {
|
|
278
|
+
const text = plainRows[i];
|
|
279
|
+
if (text)
|
|
280
|
+
line += ' ' + text.slice(0, SIDE_TEXT_WIDTH);
|
|
281
|
+
}
|
|
282
|
+
else if (i < plainRows.length + playerRows.length) {
|
|
283
|
+
line += ' ' + playerRows[i - plainRows.length];
|
|
284
|
+
}
|
|
285
|
+
else if (i < plainRows.length + playerRows.length + hintRows.length) {
|
|
286
|
+
const text = hintRows[i - plainRows.length - playerRows.length];
|
|
287
|
+
if (text)
|
|
288
|
+
line += ' ' + text.slice(0, SIDE_TEXT_WIDTH);
|
|
289
|
+
}
|
|
290
|
+
lines.push(line);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
lines.push(...arenaLines);
|
|
295
|
+
const belowText = `${clock} ${shrink} ${names.join(' ')}`;
|
|
296
|
+
lines.push(belowText.slice(0, boardCols));
|
|
297
|
+
}
|
|
298
|
+
const statusWidth = boardCols + (sideHud ? SIDE_TEXT_WIDTH + 1 : 0);
|
|
299
|
+
lines.push(claude.slice(0, statusWidth));
|
|
300
|
+
// Per-line clear-to-EOL (ESC[K) kills resize residue to the right of every
|
|
301
|
+
// line (checkwait/chess-4 lesson); trailing ESC[J kills residue below —
|
|
302
|
+
// the renderer never scrolls, so both are always safe. Mono frames carry no
|
|
303
|
+
// SGR at all, so there is nothing to reset there.
|
|
304
|
+
const tail = mode === 'mono' ? '' : RESET;
|
|
305
|
+
return `${ESC}[H` + lines.join(`${ESC}[K\r\n`) + tail + `${ESC}[J`;
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAE7F,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAQjD,8EAA8E;AAC9E,2EAA2E;AAC3E,6EAA6E;AAC7E,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,8EAA8E;AAC9E,0BAA0B;AAC1B,SAAS,WAAW,CAAC,CAAS,EAAE,IAAY,EAAE,IAAY;IACxD,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,IAAI,CAAA;AAClD,CAAC;AACD,SAAS,YAAY,CAAC,CAAS,EAAE,IAAY,EAAE,IAAY;IACzD,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAA;AACnE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY,EAAE,IAAe;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAA;IAC9C,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,IAAI,YAAY,GAAG,KAAK,CAAA;IACxB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACzC,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QAC3C,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;YACtB,MAAM,GAAG,CAAC,CAAA;YACV,YAAY,GAAG,MAAM,CAAA,CAAC,sCAAsC;YAC5D,MAAK;QACP,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,IAAI,MAAM,GAAG,CAAC,CAAA;IAChD,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;AACjE,CAAC;AAED,MAAM,GAAG,GAAG,MAAM,CAAA;AAClB,MAAM,KAAK,GAAG,GAAG,GAAG,KAAK,CAAA;AACzB,SAAS,EAAE,CAAC,GAAsC;IAChD,OAAO,GAAG,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;AACrD,CAAC;AACD,SAAS,EAAE,CAAC,GAAsC;IAChD,OAAO,GAAG,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;AACrD,CAAC;AAED,MAAM,QAAQ,GAAsC,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAA;AACjE,MAAM,QAAQ,GAAsC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;AAClE,MAAM,gBAAgB,GAAsC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AACzE,MAAM,SAAS,GAAsC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AACjE,MAAM,YAAY,GAAsC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AACrE,MAAM,YAAY,GAAsC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;AACtE,MAAM,cAAc,GAAmD;IACrE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IACf,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;IACd,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;CACd,CAAA;AACD,sEAAsE;AACtE,0EAA0E;AAC1E,oDAAoD;AACpD,MAAM,QAAQ,GAAmD;IAC/D,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,SAAS;IACxB,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,UAAU;IAC1B,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,WAAW;IAC5B,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,UAAU;CAC3B,CAAA;AACD,MAAM,QAAQ,GAA2D;IACvE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IACrB,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC;IACrB,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;CACtB,CAAA;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAA;IACzC,IAAI,IAAI,GAAG,UAAU,GAAG,CAAC;QAAE,OAAO,CAAC,CAAA;IACnC,OAAO,CAAC,CAAA;AACV,CAAC;AASD,SAAS,cAAc,CAAC,CAAc;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAA;IACxC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAgB,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACrD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;QAAE,IAAI,CAAC,CAAC,KAAK;YAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;IACzE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAgB,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACrD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;AAC9C,CAAC;AAQD,0EAA0E;AAC1E,8EAA8E;AAC9E,sEAAsE;AACtE,SAAS,UAAU,CAAC,CAAc,EAAE,GAAc,EAAE,CAAS,EAAE,CAAS;IACtE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACnB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACtB,MAAM,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAElF,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;IAE7F,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACpC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAE,EAAE,CAAA;IAEzG,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC9B,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACtC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAE,EAAE,CAAA;IAClF,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC9B,IAAI,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;IAE5F,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;IACvF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AACxD,CAAC;AAED,gEAAgE;AAChE,8DAA8D;AAC9D,SAAS,WAAW,CAAC,CAAc,EAAE,GAAc,EAAE,CAAS;IAC5D,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;IAChB,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAA;IACjD,MAAM,MAAM,GAAG,CAAC,GAAW,EAAe,EAAE;QAC1C,IAAI,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAE,EAAE,EAAE,CAAC,CAAA;YAChC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACxB,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC,CAAA;IAED,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;YAC1C,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;gBACpC,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;gBACtB,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;oBACd,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;oBAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;oBAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAA;oBACnC,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;oBACzB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;wBAClC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,CAAA;wBAClC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,CAAA;wBAClC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;4BAAE,IAAI,IAAI,GAAG,CAAA;;4BACxB,IAAI,IAAI,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;oBAC3D,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;gBACxB,CAAC;gBACD,IAAI,IAAI,IAAI,GAAG,KAAK,CAAA;YACtB,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,6EAA6E;AAC7E,2EAA2E;AAC3E,6EAA6E;AAC7E,iDAAiD;AACjD,MAAM,UAAU,GAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA,CAAC,+BAA+B;AACtF,MAAM,WAAW,GAAG,EAAE,CAAA;AACtB,MAAM,gBAAgB,GAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA,CAAC,mDAAmD;AAC5G,MAAM,UAAU,GAAgC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;AAClF,MAAM,UAAU,GAAG,EAAE,CAAA;AAErB,oEAAoE;AACpE,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,yEAAyE;AACzE,SAAS,UAAU,CAAC,CAAc,EAAE,GAAc,EAAE,IAAe;IACjE,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,GAAsC,EAAE,KAAa,EAAU,EAAE,CAC5F,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;IAE3G,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,IAAI,IAAI,GAAG,EAAE,CAAA;QACb,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;YACrB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvB,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,CAAC,CAAA;gBAC9C,SAAQ;YACV,CAAC;YACD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,IAAI,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAE,EAAE,UAAU,CAAC,QAAQ,CAAE,CAAC,CAAA;gBACrF,SAAQ;YACV,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAC9B,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACtC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,KAAK,CAAE,EAAE,gBAAgB,CAAC,KAAK,CAAE,CAAC,CAAA;gBACrE,SAAQ;YACV,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAC9B,IAAI,IAAI,EAAE,CAAC;gBACT,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;gBAC5F,SAAQ;YACV,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACtB,IAAI,IAAI,KAAK,MAAM;gBAAE,IAAI,IAAI,IAAI,CAAA;iBAC5B,IAAI,IAAI,KAAK,MAAM;gBAAE,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAA;;gBACvF,IAAI,IAAI,IAAI,CAAA;QACnB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAA;IAC1D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,QAAQ,GAAG,EAAE,CAAA;IACzB,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;AAC/C,CAAC;AAED,8EAA8E;AAC9E,6DAA6D;AAC7D,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,IAAI,IAAI,iBAAiB;QAAE,OAAO,aAAa,CAAA;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;IACnE,OAAO,aAAa,SAAS,GAAG,CAAA;AAClC,CAAC;AAED,SAAS,UAAU,CAAC,CAAc,EAAE,KAAc;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAChC,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;AAClC,CAAC;AAED,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,qCAAqC;AACrC,MAAM,eAAe,GAAG,EAAE,CAAA;AAE1B,6EAA6E;AAC7E,wEAAwE;AACxE,2EAA2E;AAC3E,yEAAyE;AACzE,6EAA6E;AAC7E,uEAAuE;AACvE,SAAS,YAAY,CAAC,CAAc,EAAE,KAAc,EAAE,IAAe;IACnE,MAAM,MAAM,GACV,IAAI,KAAK,WAAW;QAClB,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAE,CAAC,GAAG,GAAG,GAAG,KAAK;QACnC,CAAC,CAAC,IAAI,KAAK,KAAK;YACd,CAAC,CAAC,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,CAAE,KAAK,KAAK,EAAE;YACzC,CAAC,CAAC,GAAG,CAAA;IACX,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA,CAAC,4DAA4D;IAC7F,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACnE,MAAM,KAAK,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,CAAA;IAC/C,OAAO,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,CAAA;AACrD,CAAC;AAED,yEAAyE;AACzE,4CAA4C;AAC5C,MAAM,aAAa,GAAsB,CAAC,kBAAkB,EAAE,sBAAsB,CAAC,CAAA;AAErF,MAAM,UAAU,WAAW,CAAC,CAAc,EAAE,GAAW,EAAE,MAAc,EAAE,MAAc,EAAE,OAAkB,WAAW;IACpH,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAAA;IACpC,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;IAC7B,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;IAC5E,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAA;IAErD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC9B,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACnC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;IAE/D,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,OAAO,EAAE,CAAC;QACZ,0EAA0E;QAC1E,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE,yEAAyE;QACzE,0CAA0C;QAC1C,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACjC,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;QAC5E,MAAM,QAAQ,GAAG,aAAa,CAAA;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,IAAI,IAAI,GAAG,UAAU,CAAC,CAAC,CAAE,CAAA;YACzB,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;gBACzB,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;gBACzB,IAAI,IAAI;oBAAE,IAAI,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAA;YACxD,CAAC;iBAAM,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;gBACpD,IAAI,IAAI,GAAG,GAAG,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,CAAE,CAAA;YACjD,CAAC;iBAAM,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACtE,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;gBAC/D,IAAI,IAAI;oBAAE,IAAI,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAA;YACxD,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAA;QACzB,MAAM,SAAS,GAAG,GAAG,KAAK,KAAK,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;QAC5D,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAA;IAC3C,CAAC;IAED,MAAM,WAAW,GAAG,SAAS,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAA;IAExC,2EAA2E;IAC3E,wEAAwE;IACxE,4EAA4E;IAC5E,kDAAkD;IAClD,MAAM,IAAI,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IACzC,OAAO,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAA;AACpE,CAAC"}
|
package/dist/share.d.ts
ADDED
package/dist/share.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TICK_RATE } from 'boomwait-core';
|
|
2
|
+
// Post-match share card, printed to the NORMAL screen after term.restore() so
|
|
3
|
+
// it survives in the player's scrollback ready to copy into a post. Plain
|
|
4
|
+
// box-drawing text (no ANSI colors): it must paste cleanly anywhere, and every
|
|
5
|
+
// share carries the install commands. Mirrors packages/chess-client/src/share.ts's
|
|
6
|
+
// box-drawing pattern; content rewritten for bomber (win/loss/draw + elapsed
|
|
7
|
+
// match time, instead of SAN move count).
|
|
8
|
+
function resultPhrase(result, you) {
|
|
9
|
+
if (result.kind === 'win')
|
|
10
|
+
return result.winner === you ? 'won' : 'lost';
|
|
11
|
+
return 'drew';
|
|
12
|
+
}
|
|
13
|
+
function fmtClock(tick) {
|
|
14
|
+
const totalSec = Math.max(0, Math.floor(tick / TICK_RATE));
|
|
15
|
+
const m = Math.floor(totalSec / 60);
|
|
16
|
+
const s = totalSec % 60;
|
|
17
|
+
return `${m}:${String(s).padStart(2, '0')}`;
|
|
18
|
+
}
|
|
19
|
+
export function shareCard(result, you, tick, opponentHandle) {
|
|
20
|
+
const time = fmtClock(tick);
|
|
21
|
+
const lines = [
|
|
22
|
+
'boomwait — terminal bomberman',
|
|
23
|
+
'',
|
|
24
|
+
`${resultPhrase(result, you)} in ${time} · vs ${opponentHandle}`,
|
|
25
|
+
'',
|
|
26
|
+
'play in your terminal: npx -y boomwait',
|
|
27
|
+
'Claude Code arcade: /plugin marketplace add jeremiahagthe/Claude-Games',
|
|
28
|
+
' /plugin install games@games',
|
|
29
|
+
];
|
|
30
|
+
const inner = Math.max(...lines.map((l) => l.length)) + 2;
|
|
31
|
+
const boxed = lines.map((l) => `│ ${l.padEnd(inner - 2)} │`);
|
|
32
|
+
return [`┌${'─'.repeat(inner)}┐`, ...boxed, `└${'─'.repeat(inner)}┘`, ''].join('\n');
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../src/share.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC,8EAA8E;AAC9E,0EAA0E;AAC1E,+EAA+E;AAC/E,mFAAmF;AACnF,6EAA6E;AAC7E,0CAA0C;AAE1C,SAAS,YAAY,CAAC,MAAc,EAAE,GAAW;IAC/C,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;IACxE,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAA;IAC1D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAA;IACnC,MAAM,CAAC,GAAG,QAAQ,GAAG,EAAE,CAAA;IACvB,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,GAAW,EAAE,IAAY,EAAE,cAAsB;IACzF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IAE3B,MAAM,KAAK,GAAG;QACZ,+BAA+B;QAC/B,EAAE;QACF,GAAG,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,IAAI,SAAS,cAAc,EAAE;QAChE,EAAE;QACF,yCAAyC;QACzC,4EAA4E;QAC5E,qDAAqD;KACtD,CAAA;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAA;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;IAC5D,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACtF,CAAC"}
|
package/dist/sprites.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// 8x8 sprites (chess-4 board-render.ts's half-block ▀▄ compositing pipeline,
|
|
2
|
+
// generalized): each mask is 8 numbers, one per row, each row an 8-bit value
|
|
3
|
+
// (bit 7 = leftmost pixel, bit 0 = rightmost). A tile is `2r` terminal
|
|
4
|
+
// columns x `r` terminal rows; two vertical pixels pack into each terminal
|
|
5
|
+
// row via ▀▄, so the pixel grid a mask scales into is always `2r x 2r` —
|
|
6
|
+
// square — hence scaleMask below takes a single `px` side length.
|
|
7
|
+
// prettier-ignore
|
|
8
|
+
const PERSON = [
|
|
9
|
+
0b00111100, // ..####..
|
|
10
|
+
0b01111110, // .######.
|
|
11
|
+
0b00111100, // ..####..
|
|
12
|
+
0b01111110, // .######.
|
|
13
|
+
0b11111111, // ########
|
|
14
|
+
0b01111110, // .######.
|
|
15
|
+
0b00100100, // ..#..#..
|
|
16
|
+
0b01000010, // .#....#.
|
|
17
|
+
];
|
|
18
|
+
// prettier-ignore
|
|
19
|
+
const BOMB0 = [
|
|
20
|
+
0b00011000,
|
|
21
|
+
0b00100000,
|
|
22
|
+
0b01111110,
|
|
23
|
+
0b11111111,
|
|
24
|
+
0b11111111,
|
|
25
|
+
0b11111111,
|
|
26
|
+
0b01111110,
|
|
27
|
+
0b00111100,
|
|
28
|
+
];
|
|
29
|
+
// prettier-ignore
|
|
30
|
+
const BOMB1 = [
|
|
31
|
+
0b00100000,
|
|
32
|
+
0b01000000,
|
|
33
|
+
0b01111110,
|
|
34
|
+
0b11111111,
|
|
35
|
+
0b11111111,
|
|
36
|
+
0b11111111,
|
|
37
|
+
0b01111110,
|
|
38
|
+
0b00111100,
|
|
39
|
+
];
|
|
40
|
+
// prettier-ignore
|
|
41
|
+
const BOMB2 = [
|
|
42
|
+
0b10101010,
|
|
43
|
+
0b01111110,
|
|
44
|
+
0b11111111,
|
|
45
|
+
0b11111111,
|
|
46
|
+
0b11111111,
|
|
47
|
+
0b11111111,
|
|
48
|
+
0b01111110,
|
|
49
|
+
0b10111101,
|
|
50
|
+
];
|
|
51
|
+
// prettier-ignore
|
|
52
|
+
const FLAME = [
|
|
53
|
+
0b00100100,
|
|
54
|
+
0b01011010,
|
|
55
|
+
0b10111101,
|
|
56
|
+
0b01111110,
|
|
57
|
+
0b11111111,
|
|
58
|
+
0b01111110,
|
|
59
|
+
0b10111101,
|
|
60
|
+
0b01000010,
|
|
61
|
+
];
|
|
62
|
+
// prettier-ignore
|
|
63
|
+
const SOFT = [
|
|
64
|
+
0b11111111,
|
|
65
|
+
0b10100101,
|
|
66
|
+
0b10100101,
|
|
67
|
+
0b11111111,
|
|
68
|
+
0b10100101,
|
|
69
|
+
0b10100101,
|
|
70
|
+
0b11111111,
|
|
71
|
+
0b11111111,
|
|
72
|
+
];
|
|
73
|
+
const HARD = [0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111];
|
|
74
|
+
// prettier-ignore
|
|
75
|
+
const DROP_BOMB = [
|
|
76
|
+
0b00000000,
|
|
77
|
+
0b00111100,
|
|
78
|
+
0b01111110,
|
|
79
|
+
0b01111110,
|
|
80
|
+
0b01111110,
|
|
81
|
+
0b00111100,
|
|
82
|
+
0b00000000,
|
|
83
|
+
0b00000000,
|
|
84
|
+
];
|
|
85
|
+
// prettier-ignore
|
|
86
|
+
const DROP_RANGE = [
|
|
87
|
+
0b00011000,
|
|
88
|
+
0b00011000,
|
|
89
|
+
0b00011000,
|
|
90
|
+
0b11111111,
|
|
91
|
+
0b11111111,
|
|
92
|
+
0b00011000,
|
|
93
|
+
0b00011000,
|
|
94
|
+
0b00011000,
|
|
95
|
+
];
|
|
96
|
+
// prettier-ignore
|
|
97
|
+
const DROP_SPEED = [
|
|
98
|
+
0b10000000,
|
|
99
|
+
0b11000000,
|
|
100
|
+
0b11100000,
|
|
101
|
+
0b11110000,
|
|
102
|
+
0b11110000,
|
|
103
|
+
0b11100000,
|
|
104
|
+
0b11000000,
|
|
105
|
+
0b10000000,
|
|
106
|
+
];
|
|
107
|
+
export const SPRITES = {
|
|
108
|
+
p0: PERSON,
|
|
109
|
+
p1: PERSON,
|
|
110
|
+
p2: PERSON,
|
|
111
|
+
p3: PERSON,
|
|
112
|
+
bomb0: BOMB0,
|
|
113
|
+
bomb1: BOMB1,
|
|
114
|
+
bomb2: BOMB2,
|
|
115
|
+
flame: FLAME,
|
|
116
|
+
soft: SOFT,
|
|
117
|
+
hard: HARD,
|
|
118
|
+
'drop-bomb': DROP_BOMB,
|
|
119
|
+
'drop-range': DROP_RANGE,
|
|
120
|
+
'drop-speed': DROP_SPEED,
|
|
121
|
+
};
|
|
122
|
+
// Nearest-neighbor scale of an 8x8 bitmask to a px*px boolean grid.
|
|
123
|
+
export function scaleMask(mask, px) {
|
|
124
|
+
const out = [];
|
|
125
|
+
for (let y = 0; y < px; y++) {
|
|
126
|
+
const sy = Math.min(7, Math.floor((y * 8) / px));
|
|
127
|
+
const bits = mask[sy] ?? 0;
|
|
128
|
+
const row = [];
|
|
129
|
+
for (let x = 0; x < px; x++) {
|
|
130
|
+
const sx = Math.min(7, Math.floor((x * 8) / px));
|
|
131
|
+
row.push(((bits >> (7 - sx)) & 1) === 1);
|
|
132
|
+
}
|
|
133
|
+
out.push(row);
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=sprites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sprites.js","sourceRoot":"","sources":["../src/sprites.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "boomwait",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": { "boomwait": "bin/boomwait.js" },
|
|
7
|
+
"files": ["dist", "bin"],
|
|
8
|
+
"scripts": { "build": "tsc -p tsconfig.json" },
|
|
9
|
+
"dependencies": { "boomwait-core": "0.1.0", "termwait": "0.1.0", "ws": "8.21.0" }
|
|
10
|
+
}
|