@joystyk/launcher 0.0.0 → 0.2.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/CHANGELOG.md +38 -0
- package/README.md +245 -2
- package/dist/browser/daily-prefetch.d.ts +8 -0
- package/dist/browser/daily-prefetch.js +32 -0
- package/dist/browser/daily-prefetch.js.map +1 -0
- package/dist/browser/index.d.ts +3 -0
- package/dist/browser/index.js +4 -0
- package/dist/browser/rooms.d.ts +2 -0
- package/dist/browser/rooms.js +49 -0
- package/dist/browser/rooms.js.map +1 -0
- package/dist/browser/services.d.ts +127 -0
- package/dist/browser/services.js +95 -0
- package/dist/browser/services.js.map +1 -0
- package/dist/browser/stage.d.ts +28 -0
- package/dist/browser/stage.js +1195 -0
- package/dist/browser/stage.js.map +1 -0
- package/dist/fixture/engine.d.ts +60 -0
- package/dist/fixture/engine.js +164 -0
- package/dist/fixture/engine.js.map +1 -0
- package/dist/fixture/index.d.ts +52 -0
- package/dist/fixture/index.js +63 -0
- package/dist/fixture/index.js.map +1 -0
- package/dist/fixture/module.d.ts +2 -0
- package/dist/fixture/module.js +135 -0
- package/dist/fixture/module.js.map +1 -0
- package/dist/fixture/probe.d.ts +10 -0
- package/dist/fixture/probe.js +19 -0
- package/dist/fixture/probe.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/launcher.d.ts +190 -0
- package/dist/launcher.js +4614 -0
- package/dist/launcher.js.map +1 -0
- package/dist/model.d.ts +431 -0
- package/dist/model.js +566 -0
- package/dist/model.js.map +1 -0
- package/dist/report-problem.d.ts +39 -0
- package/dist/report-problem.js +359 -0
- package/dist/report-problem.js.map +1 -0
- package/dist/report.d.ts +18 -0
- package/dist/report.js +32 -0
- package/dist/report.js.map +1 -0
- package/dist/room-drivers.d.ts +26 -0
- package/dist/room-drivers.js +92 -0
- package/dist/room-drivers.js.map +1 -0
- package/dist/safe-point.d.ts +33 -0
- package/dist/safe-point.js +44 -0
- package/dist/safe-point.js.map +1 -0
- package/dist/save-slots.d.ts +6 -0
- package/dist/save-slots.js +18 -0
- package/dist/save-slots.js.map +1 -0
- package/dist/theme.d.ts +35 -0
- package/dist/theme.js +111 -0
- package/dist/theme.js.map +1 -0
- package/package.json +67 -11
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { tallyEngine } from "./engine.js";
|
|
2
|
+
import { tallyProbe } from "./probe.js";
|
|
3
|
+
//#region src/fixture/module.ts
|
|
4
|
+
var STYLES = `
|
|
5
|
+
:host { display: block; }
|
|
6
|
+
section {
|
|
7
|
+
display: grid;
|
|
8
|
+
gap: 0.75rem;
|
|
9
|
+
padding: 1rem;
|
|
10
|
+
border-radius: 0.75rem;
|
|
11
|
+
background: var(--game-surface, Canvas);
|
|
12
|
+
color: var(--game-ink, CanvasText);
|
|
13
|
+
font: inherit;
|
|
14
|
+
}
|
|
15
|
+
h3 { margin: 0; color: var(--game-accent, currentColor); }
|
|
16
|
+
p, ul { margin: 0; }
|
|
17
|
+
.moves { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
18
|
+
button {
|
|
19
|
+
min-block-size: 2.75rem;
|
|
20
|
+
padding-inline: 1rem;
|
|
21
|
+
border: 2px solid var(--game-accent, currentColor);
|
|
22
|
+
border-radius: 0.5rem;
|
|
23
|
+
background: transparent;
|
|
24
|
+
color: inherit;
|
|
25
|
+
font: inherit;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
}
|
|
28
|
+
.scores li { transition: color 300ms ease-out; }
|
|
29
|
+
:host([data-reduced-motion]) .scores li { transition: none; }
|
|
30
|
+
`;
|
|
31
|
+
var describeTurn = (view, snapshot) => {
|
|
32
|
+
if (view.winner !== null) return `${view.names[view.winner] ?? "Someone"} reached ${String(view.target)}.`;
|
|
33
|
+
if (snapshot.meta.outcome !== null) return "Game over.";
|
|
34
|
+
return `${view.names[view.turn] ?? "Someone"} to play.`;
|
|
35
|
+
};
|
|
36
|
+
var tallyModule = {
|
|
37
|
+
engine: tallyEngine,
|
|
38
|
+
migrations: [],
|
|
39
|
+
mount(slot, anySession, platform) {
|
|
40
|
+
const session = anySession;
|
|
41
|
+
tallyProbe.mounts += 1;
|
|
42
|
+
const host = document.createElement("div");
|
|
43
|
+
host.className = "tally-board";
|
|
44
|
+
host.dataset["fixtureRelease"] = typeof __JOYSTYK_RELEASE__ === "string" ? __JOYSTYK_RELEASE__ : "unversioned";
|
|
45
|
+
const root = host.attachShadow({ mode: "open" });
|
|
46
|
+
const sheet = new CSSStyleSheet();
|
|
47
|
+
sheet.replaceSync(STYLES);
|
|
48
|
+
root.adoptedStyleSheets = [sheet];
|
|
49
|
+
root.innerHTML = `<section aria-labelledby="tally-title">
|
|
50
|
+
<h3 id="tally-title"></h3>
|
|
51
|
+
<ul class="scores"></ul>
|
|
52
|
+
<p class="secret"></p>
|
|
53
|
+
<p class="turn"></p>
|
|
54
|
+
<div class="moves">
|
|
55
|
+
<button type="button" data-amount="1">Add 1</button>
|
|
56
|
+
<button type="button" data-amount="2">Add 2</button>
|
|
57
|
+
<button type="button" data-hint>Hint</button>
|
|
58
|
+
<button type="button" data-quit>Quit</button>
|
|
59
|
+
</div>
|
|
60
|
+
</section>`;
|
|
61
|
+
slot.append(host);
|
|
62
|
+
const q = (selector) => root.querySelector(selector);
|
|
63
|
+
let preferences = platform.preferences.get();
|
|
64
|
+
let mySeat = null;
|
|
65
|
+
let canMove = false;
|
|
66
|
+
const add = (amount) => {
|
|
67
|
+
if (canMove) session.dispatch({
|
|
68
|
+
kind: "command",
|
|
69
|
+
command: {
|
|
70
|
+
type: "add",
|
|
71
|
+
amount
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
const render = (snapshot) => {
|
|
76
|
+
const view = snapshot.view;
|
|
77
|
+
mySeat = snapshot.meta.seat;
|
|
78
|
+
canMove = mySeat !== null && snapshot.pending.includes(mySeat) && snapshot.meta.outcome === null && !snapshot.paused;
|
|
79
|
+
q("h3").textContent = `First to ${String(view.target)}`;
|
|
80
|
+
q(".scores").replaceChildren(...view.scores.map((score, seat) => {
|
|
81
|
+
const li = document.createElement("li");
|
|
82
|
+
li.textContent = `${view.names[seat] ?? `Seat ${String(seat + 1)}`}: ${String(score)}`;
|
|
83
|
+
return li;
|
|
84
|
+
}));
|
|
85
|
+
q(".secret").textContent = view.secret === null ? "" : `Your secret number is ${String(view.secret)}.`;
|
|
86
|
+
q(".turn").textContent = describeTurn(view, snapshot);
|
|
87
|
+
for (const button of root.querySelectorAll("[data-amount], [data-hint]")) button.disabled = !canMove;
|
|
88
|
+
for (const beat of snapshot.beats) if (beat.event.type === "added") platform.announce(`${view.names[beat.event.seat] ?? "Someone"} adds ${String(beat.event.amount)}.`, "polite");
|
|
89
|
+
};
|
|
90
|
+
const applyPreferences = (next) => {
|
|
91
|
+
preferences = next;
|
|
92
|
+
tallyProbe.preferences.push(next);
|
|
93
|
+
host.toggleAttribute("data-reduced-motion", next.reducedMotion);
|
|
94
|
+
};
|
|
95
|
+
applyPreferences(preferences);
|
|
96
|
+
const onClick = (event) => {
|
|
97
|
+
const button = event.target.closest("button");
|
|
98
|
+
if (button === null) return;
|
|
99
|
+
if (button.dataset["amount"] !== void 0) add(button.dataset["amount"] === "2" ? 2 : 1);
|
|
100
|
+
else if (button.hasAttribute("data-hint")) session.dispatch({
|
|
101
|
+
kind: "command",
|
|
102
|
+
command: { type: "hint" }
|
|
103
|
+
});
|
|
104
|
+
else if (button.hasAttribute("data-quit")) platform.requestExit();
|
|
105
|
+
};
|
|
106
|
+
const onKey = (event) => {
|
|
107
|
+
if (!preferences.singleKeyShortcuts || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
108
|
+
if (event.key === "1" || event.key === "2") add(event.key === "2" ? 2 : 1);
|
|
109
|
+
};
|
|
110
|
+
root.addEventListener("click", onClick);
|
|
111
|
+
host.addEventListener("keydown", onKey);
|
|
112
|
+
const stopSession = session.subscribe(render);
|
|
113
|
+
const stopPreferences = platform.preferences.subscribe(applyPreferences);
|
|
114
|
+
platform.storage.get("plays").then(async (stored) => {
|
|
115
|
+
const plays = (typeof stored === "number" ? stored : 0) + 1;
|
|
116
|
+
tallyProbe.plays.push(plays);
|
|
117
|
+
await platform.storage.set("plays", plays);
|
|
118
|
+
});
|
|
119
|
+
let disposed = false;
|
|
120
|
+
return { dispose: () => {
|
|
121
|
+
if (disposed) return;
|
|
122
|
+
disposed = true;
|
|
123
|
+
tallyProbe.disposals += 1;
|
|
124
|
+
stopSession();
|
|
125
|
+
stopPreferences();
|
|
126
|
+
root.removeEventListener("click", onClick);
|
|
127
|
+
host.removeEventListener("keydown", onKey);
|
|
128
|
+
host.remove();
|
|
129
|
+
} };
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { tallyModule };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","names":[],"sources":["../../src/fixture/module.ts"],"sourcesContent":["// Tally's canvas: what a game's module looks like to the launcher. It draws into the slot it\n// is given and nothing else, reads the theme tokens the frame hands the canvas, talks to the\n// platform only through PlatformApi, and records what it saw in `tallyProbe` for tests.\nimport type { GameModule, GameSession, Json, Preferences, SeatId } from '@joystyk/contracts';\nimport type { LocalInput, LocalSnapshot } from '@joystyk/session';\nimport { tallyEngine, type TallyCommand, type TallyEvent, type TallyView } from './engine.ts';\nimport { tallyProbe } from './probe.ts';\n\ntype TallySession = GameSession<LocalSnapshot<TallyView, TallyEvent>, LocalInput<TallyCommand>>;\n\nconst STYLES = `\n :host { display: block; }\n section {\n display: grid;\n gap: 0.75rem;\n padding: 1rem;\n border-radius: 0.75rem;\n background: var(--game-surface, Canvas);\n color: var(--game-ink, CanvasText);\n font: inherit;\n }\n h3 { margin: 0; color: var(--game-accent, currentColor); }\n p, ul { margin: 0; }\n .moves { display: flex; flex-wrap: wrap; gap: 0.5rem; }\n button {\n min-block-size: 2.75rem;\n padding-inline: 1rem;\n border: 2px solid var(--game-accent, currentColor);\n border-radius: 0.5rem;\n background: transparent;\n color: inherit;\n font: inherit;\n cursor: pointer;\n }\n .scores li { transition: color 300ms ease-out; }\n :host([data-reduced-motion]) .scores li { transition: none; }\n`;\n\n// The site's two-release test needs genuinely different lazy game bytes, rather\n// than relying on incidental changes in the shell's shared dependency hashes.\ndeclare const __JOYSTYK_RELEASE__: string;\n\nconst describeTurn = (view: TallyView, snapshot: LocalSnapshot<TallyView, TallyEvent>): string => {\n if (view.winner !== null) return `${view.names[view.winner] ?? 'Someone'} reached ${String(view.target)}.`;\n if (snapshot.meta.outcome !== null) return 'Game over.';\n return `${view.names[view.turn] ?? 'Someone'} to play.`;\n};\n\nexport const tallyModule: GameModule<HTMLElement> = {\n engine: tallyEngine,\n migrations: [],\n mount(slot, anySession, platform) {\n // The launcher built this session from Tally's own engine, so its types are Tally's.\n const session = anySession as unknown as TallySession;\n tallyProbe.mounts += 1;\n\n const host = document.createElement('div');\n host.className = 'tally-board';\n host.dataset['fixtureRelease'] = typeof __JOYSTYK_RELEASE__ === 'string' ? __JOYSTYK_RELEASE__ : 'unversioned';\n const root = host.attachShadow({ mode: 'open' });\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(STYLES);\n root.adoptedStyleSheets = [sheet];\n root.innerHTML = `<section aria-labelledby=\"tally-title\">\n <h3 id=\"tally-title\"></h3>\n <ul class=\"scores\"></ul>\n <p class=\"secret\"></p>\n <p class=\"turn\"></p>\n <div class=\"moves\">\n <button type=\"button\" data-amount=\"1\">Add 1</button>\n <button type=\"button\" data-amount=\"2\">Add 2</button>\n <button type=\"button\" data-hint>Hint</button>\n <button type=\"button\" data-quit>Quit</button>\n </div>\n </section>`;\n slot.append(host);\n\n const q = <E extends Element>(selector: string): E => root.querySelector(selector) as E;\n let preferences = platform.preferences.get();\n let mySeat: SeatId | null = null;\n let canMove = false;\n\n const add = (amount: 1 | 2): void => {\n if (canMove) session.dispatch({ kind: 'command', command: { type: 'add', amount } });\n };\n\n const render = (snapshot: LocalSnapshot<TallyView, TallyEvent>): void => {\n const view = snapshot.view;\n mySeat = snapshot.meta.seat;\n canMove =\n mySeat !== null && snapshot.pending.includes(mySeat) && snapshot.meta.outcome === null && !snapshot.paused;\n q('h3').textContent = `First to ${String(view.target)}`;\n const scores = q('.scores');\n scores.replaceChildren(\n ...view.scores.map((score, seat) => {\n const li = document.createElement('li');\n li.textContent = `${view.names[seat] ?? `Seat ${String(seat + 1)}`}: ${String(score)}`;\n return li;\n }),\n );\n q('.secret').textContent = view.secret === null ? '' : `Your secret number is ${String(view.secret)}.`;\n q('.turn').textContent = describeTurn(view, snapshot);\n for (const button of root.querySelectorAll<HTMLButtonElement>('[data-amount], [data-hint]'))\n button.disabled = !canMove;\n for (const beat of snapshot.beats) {\n if (beat.event.type === 'added') {\n platform.announce(`${view.names[beat.event.seat] ?? 'Someone'} adds ${String(beat.event.amount)}.`, 'polite');\n }\n }\n };\n\n const applyPreferences = (next: Preferences): void => {\n preferences = next;\n tallyProbe.preferences.push(next);\n host.toggleAttribute('data-reduced-motion', next.reducedMotion);\n };\n applyPreferences(preferences);\n\n const onClick = (event: Event): void => {\n const button = (event.target as Element).closest('button');\n if (button === null) return;\n if (button.dataset['amount'] !== undefined) add(button.dataset['amount'] === '2' ? 2 : 1);\n else if (button.hasAttribute('data-hint')) session.dispatch({ kind: 'command', command: { type: 'hint' } });\n else if (button.hasAttribute('data-quit')) platform.requestExit();\n };\n const onKey = (event: KeyboardEvent): void => {\n // Single-key shortcuts honor the player's preference (WCAG 2.1.4).\n if (!preferences.singleKeyShortcuts || event.ctrlKey || event.metaKey || event.altKey) return;\n if (event.key === '1' || event.key === '2') add(event.key === '2' ? 2 : 1);\n };\n root.addEventListener('click', onClick);\n host.addEventListener('keydown', onKey);\n const stopSession = session.subscribe(render);\n const stopPreferences = platform.preferences.subscribe(applyPreferences);\n\n void platform.storage.get('plays').then(async (stored: Json | null) => {\n const plays = (typeof stored === 'number' ? stored : 0) + 1;\n tallyProbe.plays.push(plays);\n await platform.storage.set('plays', plays);\n });\n\n let disposed = false;\n return {\n dispose: () => {\n if (disposed) return;\n disposed = true;\n tallyProbe.disposals += 1;\n stopSession();\n stopPreferences();\n root.removeEventListener('click', onClick);\n host.removeEventListener('keydown', onKey);\n host.remove();\n },\n };\n },\n};\n"],"mappings":";;;AAUA,IAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCf,IAAM,gBAAgB,MAAiB,aAA2D;CAChG,IAAI,KAAK,WAAW,MAAM,OAAO,GAAG,KAAK,MAAM,KAAK,WAAW,UAAU,WAAW,OAAO,KAAK,MAAM,EAAE;CACxG,IAAI,SAAS,KAAK,YAAY,MAAM,OAAO;CAC3C,OAAO,GAAG,KAAK,MAAM,KAAK,SAAS,UAAU;AAC/C;AAEA,IAAa,cAAuC;CAClD,QAAQ;CACR,YAAY,CAAC;CACb,MAAM,MAAM,YAAY,UAAU;EAEhC,MAAM,UAAU;EAChB,WAAW,UAAU;EAErB,MAAM,OAAO,SAAS,cAAc,KAAK;EACzC,KAAK,YAAY;EACjB,KAAK,QAAQ,oBAAoB,OAAO,wBAAwB,WAAW,sBAAsB;EACjG,MAAM,OAAO,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;EAC/C,MAAM,QAAQ,IAAI,cAAc;EAChC,MAAM,YAAY,MAAM;EACxB,KAAK,qBAAqB,CAAC,KAAK;EAChC,KAAK,YAAY;;;;;;;;;;;;EAYjB,KAAK,OAAO,IAAI;EAEhB,MAAM,KAAwB,aAAwB,KAAK,cAAc,QAAQ;EACjF,IAAI,cAAc,SAAS,YAAY,IAAI;EAC3C,IAAI,SAAwB;EAC5B,IAAI,UAAU;EAEd,MAAM,OAAO,WAAwB;GACnC,IAAI,SAAS,QAAQ,SAAS;IAAE,MAAM;IAAW,SAAS;KAAE,MAAM;KAAO;IAAO;GAAE,CAAC;EACrF;EAEA,MAAM,UAAU,aAAyD;GACvE,MAAM,OAAO,SAAS;GACtB,SAAS,SAAS,KAAK;GACvB,UACE,WAAW,QAAQ,SAAS,QAAQ,SAAS,MAAM,KAAK,SAAS,KAAK,YAAY,QAAQ,CAAC,SAAS;GACtG,EAAE,IAAI,CAAC,CAAC,cAAc,YAAY,OAAO,KAAK,MAAM;GAEpD,EADiB,SACjB,CAAA,CAAO,gBACL,GAAG,KAAK,OAAO,KAAK,OAAO,SAAS;IAClC,MAAM,KAAK,SAAS,cAAc,IAAI;IACtC,GAAG,cAAc,GAAG,KAAK,MAAM,SAAS,QAAQ,OAAO,OAAO,CAAC,IAAI,IAAI,OAAO,KAAK;IACnF,OAAO;GACT,CAAC,CACH;GACA,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,WAAW,OAAO,KAAK,yBAAyB,OAAO,KAAK,MAAM,EAAE;GACpG,EAAE,OAAO,CAAC,CAAC,cAAc,aAAa,MAAM,QAAQ;GACpD,KAAK,MAAM,UAAU,KAAK,iBAAoC,4BAA4B,GACxF,OAAO,WAAW,CAAC;GACrB,KAAK,MAAM,QAAQ,SAAS,OAC1B,IAAI,KAAK,MAAM,SAAS,SACtB,SAAS,SAAS,GAAG,KAAK,MAAM,KAAK,MAAM,SAAS,UAAU,QAAQ,OAAO,KAAK,MAAM,MAAM,EAAE,IAAI,QAAQ;EAGlH;EAEA,MAAM,oBAAoB,SAA4B;GACpD,cAAc;GACd,WAAW,YAAY,KAAK,IAAI;GAChC,KAAK,gBAAgB,uBAAuB,KAAK,aAAa;EAChE;EACA,iBAAiB,WAAW;EAE5B,MAAM,WAAW,UAAuB;GACtC,MAAM,SAAU,MAAM,OAAmB,QAAQ,QAAQ;GACzD,IAAI,WAAW,MAAM;GACrB,IAAI,OAAO,QAAQ,cAAc,KAAA,GAAW,IAAI,OAAO,QAAQ,cAAc,MAAM,IAAI,CAAC;QACnF,IAAI,OAAO,aAAa,WAAW,GAAG,QAAQ,SAAS;IAAE,MAAM;IAAW,SAAS,EAAE,MAAM,OAAO;GAAE,CAAC;QACrG,IAAI,OAAO,aAAa,WAAW,GAAG,SAAS,YAAY;EAClE;EACA,MAAM,SAAS,UAA+B;GAE5C,IAAI,CAAC,YAAY,sBAAsB,MAAM,WAAW,MAAM,WAAW,MAAM,QAAQ;GACvF,IAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;EAC3E;EACA,KAAK,iBAAiB,SAAS,OAAO;EACtC,KAAK,iBAAiB,WAAW,KAAK;EACtC,MAAM,cAAc,QAAQ,UAAU,MAAM;EAC5C,MAAM,kBAAkB,SAAS,YAAY,UAAU,gBAAgB;EAEvE,SAAc,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,WAAwB;GACrE,MAAM,SAAS,OAAO,WAAW,WAAW,SAAS,KAAK;GAC1D,WAAW,MAAM,KAAK,KAAK;GAC3B,MAAM,SAAS,QAAQ,IAAI,SAAS,KAAK;EAC3C,CAAC;EAED,IAAI,WAAW;EACf,OAAO,EACL,eAAe;GACb,IAAI,UAAU;GACd,WAAW;GACX,WAAW,aAAa;GACxB,YAAY;GACZ,gBAAgB;GAChB,KAAK,oBAAoB,SAAS,OAAO;GACzC,KAAK,oBAAoB,WAAW,KAAK;GACzC,KAAK,OAAO;EACd,EACF;CACF;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Preferences } from '@joystyk/contracts';
|
|
2
|
+
export declare const tallyProbe: {
|
|
3
|
+
mounts: number;
|
|
4
|
+
disposals: number;
|
|
5
|
+
/** Every preferences value the game received, first the current one. */
|
|
6
|
+
preferences: Preferences[];
|
|
7
|
+
/** The `plays` counter read from the game's scoped storage at each mount. */
|
|
8
|
+
plays: number[];
|
|
9
|
+
reset(): void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/fixture/probe.ts
|
|
2
|
+
var tallyProbe = {
|
|
3
|
+
mounts: 0,
|
|
4
|
+
disposals: 0,
|
|
5
|
+
/** Every preferences value the game received, first the current one. */
|
|
6
|
+
preferences: [],
|
|
7
|
+
/** The `plays` counter read from the game's scoped storage at each mount. */
|
|
8
|
+
plays: [],
|
|
9
|
+
reset() {
|
|
10
|
+
this.mounts = 0;
|
|
11
|
+
this.disposals = 0;
|
|
12
|
+
this.preferences = [];
|
|
13
|
+
this.plays = [];
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { tallyProbe };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.js","names":[],"sources":["../../src/fixture/probe.ts"],"sourcesContent":["// What the mounted Tally fixture observed. Kept apart from module.ts so the module stays a\n// lazily loaded chunk while tests import the probe directly.\nimport type { Preferences } from '@joystyk/contracts';\n\nexport const tallyProbe = {\n mounts: 0,\n disposals: 0,\n /** Every preferences value the game received, first the current one. */\n preferences: [] as Preferences[],\n /** The `plays` counter read from the game's scoped storage at each mount. */\n plays: [] as number[],\n reset(): void {\n this.mounts = 0;\n this.disposals = 0;\n this.preferences = [];\n this.plays = [];\n },\n};\n"],"mappings":";AAIA,IAAa,aAAa;CACxB,QAAQ;CACR,WAAW;;CAEX,aAAa,CAAC;;CAEd,OAAO,CAAC;CACR,QAAc;EACZ,KAAK,SAAS;EACd,KAAK,YAAY;EACjB,KAAK,cAAc,CAAC;EACpB,KAAK,QAAQ,CAAC;CAChB;AACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { freshState, isSafePoint, Launcher, stageLoader, type LauncherMsg, type LauncherProps, type LauncherState, type Phase, } from './launcher.js';
|
|
2
|
+
export { accessGate, assistLine, defaultName, formatElapsed, GUEST_NAME, initialSetup, isLauncherGame, isoDuration, isStoredPreferences, launcherGame, MODE_LABELS, modeOptions, parsePreferenceChange, parseStageOutput, pickMode, playerCounts, resultView, computerSeatsFor, seatsFor, toLocalMode, type Begin, type FrameState, type GameStatus, type LauncherGame, type ModeOption, type ResultView, type SaveSummary, type Setup, type StageControl, type StageOutput, } from './model.js';
|
|
3
|
+
export { reportSafePoint, requestSave, SAFE_POINT_EVENT, safePointDriver, SAVE_REQUEST_EVENT, watchSafePoints, type SafePointDetail, type SaveRequestDetail, } from './safe-point.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SAFE_POINT_EVENT, SAVE_REQUEST_EVENT, reportSafePoint, requestSave, safePointDriver, watchSafePoints } from "./safe-point.js";
|
|
2
|
+
import { GUEST_NAME, MODE_LABELS, accessGate, assistLine, computerSeatsFor, defaultName, formatElapsed, initialSetup, isLauncherGame, isStoredPreferences, isoDuration, launcherGame, modeOptions, parsePreferenceChange, parseStageOutput, pickMode, playerCounts, resultView, seatsFor, toLocalMode } from "./model.js";
|
|
3
|
+
import { Launcher, freshState, isSafePoint, stageLoader } from "./launcher.js";
|
|
4
|
+
export { GUEST_NAME, Launcher, MODE_LABELS, SAFE_POINT_EVENT, SAVE_REQUEST_EVENT, accessGate, assistLine, computerSeatsFor, defaultName, formatElapsed, freshState, initialSetup, isLauncherGame, isSafePoint, isStoredPreferences, isoDuration, launcherGame, modeOptions, parsePreferenceChange, parseStageOutput, pickMode, playerCounts, reportSafePoint, requestSave, resultView, safePointDriver, seatsFor, stageLoader, toLocalMode, watchSafePoints };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { type Json, type Mode, type SeatInfo } from '@joystyk/contracts';
|
|
2
|
+
import type { StoredPreferences } from '@joystyk/storage';
|
|
3
|
+
import '@joystyk/ui/ad-slot';
|
|
4
|
+
import { type Begin, type DailyFrame, type FrameState, type LauncherGame, type OnlineRooms, type ResultView, type RoomFrame, type RoomPlan, type RoomRequest, type SaveSummary, type Setup, type StageOutput } from './model.js';
|
|
5
|
+
import './report-problem.ts';
|
|
6
|
+
export type Phase = {
|
|
7
|
+
readonly _tag: 'Setup';
|
|
8
|
+
} | {
|
|
9
|
+
readonly _tag: 'Loading';
|
|
10
|
+
}
|
|
11
|
+
/** D-44: an AI-content game waits for the player to accept the notice. */
|
|
12
|
+
| {
|
|
13
|
+
readonly _tag: 'AiNotice';
|
|
14
|
+
readonly save: SaveSummary | null;
|
|
15
|
+
} | {
|
|
16
|
+
readonly _tag: 'Resume';
|
|
17
|
+
readonly save: SaveSummary;
|
|
18
|
+
}
|
|
19
|
+
/** Online with friends: the room is open and its match hasn't started. */
|
|
20
|
+
| {
|
|
21
|
+
readonly _tag: 'Lobby';
|
|
22
|
+
} | {
|
|
23
|
+
readonly _tag: 'Playing';
|
|
24
|
+
readonly frame: FrameState | null;
|
|
25
|
+
readonly menu: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The game is over. Its canvas stays mounted with the final board; `shown` says whether the
|
|
29
|
+
* results dialog is open over it (the canvas is inert then) or closed for a look at the board.
|
|
30
|
+
*/
|
|
31
|
+
| {
|
|
32
|
+
readonly _tag: 'Results';
|
|
33
|
+
readonly result: ResultView;
|
|
34
|
+
readonly shown: boolean;
|
|
35
|
+
} | {
|
|
36
|
+
readonly _tag: 'Failed';
|
|
37
|
+
readonly message: string;
|
|
38
|
+
};
|
|
39
|
+
export type LauncherState = {
|
|
40
|
+
/** True once hydrated; exposed as `:state(live)`. */
|
|
41
|
+
readonly live: boolean;
|
|
42
|
+
readonly setup: Setup;
|
|
43
|
+
readonly phase: Phase;
|
|
44
|
+
/** Counts games started here; each gets a fresh stage element. */
|
|
45
|
+
readonly round: number;
|
|
46
|
+
/** The stage element's code has loaded. */
|
|
47
|
+
readonly stageReady: boolean;
|
|
48
|
+
readonly seats: readonly SeatInfo[];
|
|
49
|
+
readonly begin: Begin;
|
|
50
|
+
readonly resumeSlot: string | null;
|
|
51
|
+
readonly reveals: number;
|
|
52
|
+
readonly preferences: StoredPreferences | null;
|
|
53
|
+
/** The frame's live regions. */
|
|
54
|
+
readonly polite: string;
|
|
55
|
+
readonly assertive: string;
|
|
56
|
+
/** A message about the last game or save, shown on the setup and play screens. */
|
|
57
|
+
readonly notice: string | null;
|
|
58
|
+
/** The safe point last reported to the page. */
|
|
59
|
+
readonly safe: boolean;
|
|
60
|
+
/** The config this round starts with: null for the game's own setup, or a rematch's. */
|
|
61
|
+
readonly config: Json | null;
|
|
62
|
+
/** The config "Play again" starts with, when the finished game's engine named one. */
|
|
63
|
+
readonly rematch: Json | null;
|
|
64
|
+
/** Online with friends: how this round opens its room, or null for local play. */
|
|
65
|
+
readonly plan: RoomPlan | null;
|
|
66
|
+
/** The room as the stage last reported it. */
|
|
67
|
+
readonly room: RoomFrame | null;
|
|
68
|
+
/** The daily puzzle's results: streaks and share text, once the stage has recorded them. */
|
|
69
|
+
readonly daily: DailyFrame | null;
|
|
70
|
+
/** The puzzle this round plays, when the stage names it (a daily puzzle), for "Report a problem". */
|
|
71
|
+
readonly puzzle: string | null;
|
|
72
|
+
/** Which games the room server hosts, as it last answered (the same for every game page). */
|
|
73
|
+
readonly online: OnlineRooms;
|
|
74
|
+
};
|
|
75
|
+
export type LauncherMsg = {
|
|
76
|
+
readonly _tag: 'PickMode';
|
|
77
|
+
readonly mode: Mode;
|
|
78
|
+
} | {
|
|
79
|
+
readonly _tag: 'PickLevel';
|
|
80
|
+
readonly level: string;
|
|
81
|
+
} | {
|
|
82
|
+
readonly _tag: 'PickPlayers';
|
|
83
|
+
readonly players: number;
|
|
84
|
+
} | {
|
|
85
|
+
readonly _tag: 'Rename';
|
|
86
|
+
readonly seat: number;
|
|
87
|
+
readonly name: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly _tag: 'PickGameOption';
|
|
90
|
+
readonly key: string;
|
|
91
|
+
readonly value: string;
|
|
92
|
+
readonly checked: boolean | undefined;
|
|
93
|
+
} | {
|
|
94
|
+
readonly _tag: 'Start';
|
|
95
|
+
} | {
|
|
96
|
+
readonly _tag: 'StageLoaded';
|
|
97
|
+
} | {
|
|
98
|
+
readonly _tag: 'StageOut';
|
|
99
|
+
readonly out: StageOutput;
|
|
100
|
+
} | {
|
|
101
|
+
readonly _tag: 'AcceptAiNotice';
|
|
102
|
+
} | {
|
|
103
|
+
readonly _tag: 'Resume';
|
|
104
|
+
} | {
|
|
105
|
+
readonly _tag: 'ChooseSave';
|
|
106
|
+
readonly slot: string;
|
|
107
|
+
} | {
|
|
108
|
+
readonly _tag: 'NewGame';
|
|
109
|
+
} | {
|
|
110
|
+
readonly _tag: 'Reveal';
|
|
111
|
+
} | {
|
|
112
|
+
readonly _tag: 'OpenMenu';
|
|
113
|
+
} | {
|
|
114
|
+
readonly _tag: 'CloseMenu';
|
|
115
|
+
} | {
|
|
116
|
+
readonly _tag: 'MenuClosed';
|
|
117
|
+
} | {
|
|
118
|
+
readonly _tag: 'SetPreference';
|
|
119
|
+
readonly changes: Partial<StoredPreferences>;
|
|
120
|
+
} | {
|
|
121
|
+
readonly _tag: 'Exit';
|
|
122
|
+
} | {
|
|
123
|
+
readonly _tag: 'PlayAgain';
|
|
124
|
+
} | {
|
|
125
|
+
readonly _tag: 'ChangeMode';
|
|
126
|
+
}
|
|
127
|
+
/** Close the results to look at the final board (Escape, or "Review the board"). */
|
|
128
|
+
| {
|
|
129
|
+
readonly _tag: 'HideResults';
|
|
130
|
+
} | {
|
|
131
|
+
readonly _tag: 'ShowResults';
|
|
132
|
+
}
|
|
133
|
+
/** Online with friends: the join form, the invite in the address bar, and lobby requests. */
|
|
134
|
+
| {
|
|
135
|
+
readonly _tag: 'TypeCode';
|
|
136
|
+
readonly code: string;
|
|
137
|
+
} | {
|
|
138
|
+
readonly _tag: 'ToggleWatch';
|
|
139
|
+
readonly watch: boolean;
|
|
140
|
+
} | {
|
|
141
|
+
readonly _tag: 'JoinRoom';
|
|
142
|
+
} | {
|
|
143
|
+
readonly _tag: 'InviteFound';
|
|
144
|
+
readonly code: string;
|
|
145
|
+
}
|
|
146
|
+
/** The room server's answer: which games it hosts, or that it can't be reached. */
|
|
147
|
+
| {
|
|
148
|
+
readonly _tag: 'OnlineChecked';
|
|
149
|
+
readonly online: OnlineRooms;
|
|
150
|
+
}
|
|
151
|
+
/** D-61: ask to end the match early, or answer the room's open offer. */
|
|
152
|
+
| {
|
|
153
|
+
readonly _tag: 'OfferEnd';
|
|
154
|
+
} | {
|
|
155
|
+
readonly _tag: 'RoomRequest';
|
|
156
|
+
readonly request: RoomRequest;
|
|
157
|
+
} | {
|
|
158
|
+
readonly _tag: 'AnswerOffer';
|
|
159
|
+
readonly answer: 'accept' | 'decline' | 'withdraw';
|
|
160
|
+
} | {
|
|
161
|
+
readonly _tag: 'Copy';
|
|
162
|
+
readonly what: 'link' | 'code';
|
|
163
|
+
} | {
|
|
164
|
+
readonly _tag: 'Copied';
|
|
165
|
+
readonly what: 'link' | 'code';
|
|
166
|
+
readonly ok: boolean;
|
|
167
|
+
}
|
|
168
|
+
/** The daily results: share (or copy) the emoji form, or copy the words form. */
|
|
169
|
+
| {
|
|
170
|
+
readonly _tag: 'Share';
|
|
171
|
+
readonly form: 'symbols' | 'words';
|
|
172
|
+
} | {
|
|
173
|
+
readonly _tag: 'Shared';
|
|
174
|
+
readonly form: 'symbols' | 'words';
|
|
175
|
+
readonly how: 'shared' | 'copied' | 'cancelled' | 'failed';
|
|
176
|
+
};
|
|
177
|
+
export type LauncherProps = {
|
|
178
|
+
readonly game: LauncherGame;
|
|
179
|
+
};
|
|
180
|
+
/** Loads and registers `<jy-game-stage>` on first Start, so pages pay for it only when played. */
|
|
181
|
+
export declare const stageLoader: import("@gyral/core").Driver<undefined, void, string>;
|
|
182
|
+
export declare const freshState: (game: LauncherGame, live?: boolean, online?: OnlineRooms) => LauncherState;
|
|
183
|
+
/** Whether an update could be offered now (offline-and-updates.md, Rule 2). */
|
|
184
|
+
export declare const isSafePoint: (phase: Phase) => boolean;
|
|
185
|
+
export declare const Launcher: import("@gyral/core").GyralElementClass<LauncherState, LauncherMsg, LauncherProps, never, "Resume" | "PickMode" | "PickLevel" | "PickPlayers" | "Rename" | "PickGameOption" | "Start" | "StageOut" | "AcceptAiNotice" | "ChooseSave" | "NewGame" | "Reveal" | "OpenMenu" | "CloseMenu" | "MenuClosed" | "SetPreference" | "Exit" | "PlayAgain" | "ChangeMode" | "HideResults" | "ShowResults" | "TypeCode" | "ToggleWatch" | "JoinRoom" | "OfferEnd" | "AnswerOffer" | "Copy" | "Share" | "ToggleReady" | "StartMatch" | "PickClock" | "PickRoomLevel" | "FillSeat" | "RemoveSeat" | "RoomAction">;
|
|
186
|
+
declare global {
|
|
187
|
+
interface HTMLElementTagNameMap {
|
|
188
|
+
'jy-launcher': InstanceType<typeof Launcher>;
|
|
189
|
+
}
|
|
190
|
+
}
|