@crowdedkingdoms/crowdyjs 18.11.0-dev.1 → 18.13.0-dev.1
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/MIGRATION.md +51 -0
- package/README.md +19 -1
- package/dist/generated/graphql-schema.d.ts +5 -5
- package/dist/generated/graphql-schema.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kit/index.d.ts +11 -0
- package/dist/kit/index.d.ts.map +1 -1
- package/dist/kit/index.js +11 -0
- package/dist/kit/kit.d.ts +99 -2
- package/dist/kit/kit.d.ts.map +1 -1
- package/dist/kit/kit.js +128 -2
- package/dist/kit/modules/arenas.d.ts +87 -0
- package/dist/kit/modules/arenas.d.ts.map +1 -0
- package/dist/kit/modules/arenas.js +66 -0
- package/dist/kit/modules/encounters.d.ts +75 -0
- package/dist/kit/modules/encounters.d.ts.map +1 -0
- package/dist/kit/modules/encounters.js +57 -0
- package/dist/kit/modules/instances.d.ts +78 -0
- package/dist/kit/modules/instances.d.ts.map +1 -0
- package/dist/kit/modules/instances.js +72 -0
- package/dist/kit/modules/leaderboards.d.ts +79 -0
- package/dist/kit/modules/leaderboards.d.ts.map +1 -0
- package/dist/kit/modules/leaderboards.js +69 -0
- package/dist/kit/modules/market.d.ts +159 -0
- package/dist/kit/modules/market.d.ts.map +1 -0
- package/dist/kit/modules/market.js +141 -0
- package/dist/kit/modules/minigames.d.ts +59 -0
- package/dist/kit/modules/minigames.d.ts.map +1 -0
- package/dist/kit/modules/minigames.js +55 -0
- package/dist/kit/modules/player.d.ts +264 -0
- package/dist/kit/modules/player.d.ts.map +1 -0
- package/dist/kit/modules/player.js +258 -0
- package/dist/kit/modules/racing.d.ts +131 -0
- package/dist/kit/modules/racing.d.ts.map +1 -0
- package/dist/kit/modules/racing.js +113 -0
- package/dist/kit/modules/tables.d.ts +76 -0
- package/dist/kit/modules/tables.d.ts.map +1 -0
- package/dist/kit/modules/tables.js +73 -0
- package/dist/kit/modules/tactics.d.ts +79 -0
- package/dist/kit/modules/tactics.d.ts.map +1 -0
- package/dist/kit/modules/tactics.js +67 -0
- package/dist/kit/modules/territory.d.ts +92 -0
- package/dist/kit/modules/territory.d.ts.map +1 -0
- package/dist/kit/modules/territory.js +84 -0
- package/dist/kit/protocol.d.ts +2 -0
- package/dist/kit/protocol.d.ts.map +1 -1
- package/dist/kit/protocol.js +2 -0
- package/dist/kit/runtime.d.ts.map +1 -1
- package/dist/kit/runtime.js +6 -1
- package/dist/live-coding/assets/browser-authoring-index.json +1 -1
- package/dist/live-coding/assets/manifest.json +1 -1
- package/dist/live-coding/browser-authoring-index.generated.d.ts.map +1 -1
- package/dist/live-coding/browser-authoring-index.generated.js +4776 -149
- package/package.json +1 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { CrowdyError } from '../../errors.js';
|
|
2
|
+
import { HubState, PrivateState } from '../mirror.js';
|
|
3
|
+
import { WritableKitStore } from '../store.js';
|
|
4
|
+
const TOPIC = 'player';
|
|
5
|
+
/** A store fed by another once it exists. */
|
|
6
|
+
class Relay extends WritableKitStore {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(undefined);
|
|
9
|
+
}
|
|
10
|
+
attach(source) {
|
|
11
|
+
source.subscribe((v) => this.set(v));
|
|
12
|
+
if (source.get() !== undefined)
|
|
13
|
+
this.set(source.get());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The caller's own `player` hub (the `player` starter), keyed by their player id: what they own
|
|
18
|
+
* and what they have done. Its stores exist from the start and fill once {@link watch} has read
|
|
19
|
+
* them; only the player reads `inventory`, `wallet` and `progress`, and each is read again when
|
|
20
|
+
* the hub says it changed. The economy is not a ledger: see the Game Kit's economy page.
|
|
21
|
+
*/
|
|
22
|
+
export class PlayerKit {
|
|
23
|
+
constructor(runtime, nodeType = 'player', playerKey) {
|
|
24
|
+
this.runtime = runtime;
|
|
25
|
+
this.nodeType = nodeType;
|
|
26
|
+
this.playerKey = playerKey;
|
|
27
|
+
this.profile = new Relay();
|
|
28
|
+
this.inventory = new Relay();
|
|
29
|
+
this.wallet = new Relay();
|
|
30
|
+
this.progress = new Relay();
|
|
31
|
+
this.parts = null;
|
|
32
|
+
this.rulesRead = null;
|
|
33
|
+
this.events = new Set();
|
|
34
|
+
}
|
|
35
|
+
/** The hub's key: the player's id. */
|
|
36
|
+
async key() {
|
|
37
|
+
const key = this.playerKey ?? (await this.runtime.playerId());
|
|
38
|
+
if (key === null || key === undefined) {
|
|
39
|
+
throw new CrowdyError({ message: 'the kit does not know which player it is: pass playerId to client.kit()' });
|
|
40
|
+
}
|
|
41
|
+
return key;
|
|
42
|
+
}
|
|
43
|
+
ready() {
|
|
44
|
+
this.parts ?? (this.parts = (async () => {
|
|
45
|
+
const key = await this.key();
|
|
46
|
+
const read = (method) => () => this.runtime.call(this.nodeType, key, method);
|
|
47
|
+
const source = { nodeType: this.nodeType, key, topic: TOPIC };
|
|
48
|
+
const parts = {
|
|
49
|
+
key,
|
|
50
|
+
profile: new HubState(this.runtime, this.nodeType, key, TOPIC),
|
|
51
|
+
inventory: new PrivateState(this.runtime, { ...source, what: 'inventory', read: read('inventory') }),
|
|
52
|
+
wallet: new PrivateState(this.runtime, { ...source, what: 'wallet', read: read('wallet') }),
|
|
53
|
+
progress: new PrivateState(this.runtime, { ...source, what: 'progress', read: read('progress') }),
|
|
54
|
+
};
|
|
55
|
+
this.profile.attach(parts.profile);
|
|
56
|
+
this.inventory.attach(parts.inventory);
|
|
57
|
+
this.wallet.attach(parts.wallet);
|
|
58
|
+
this.progress.attach(parts.progress);
|
|
59
|
+
parts.profile.onEvent((body) => {
|
|
60
|
+
for (const l of [...this.events])
|
|
61
|
+
l(body);
|
|
62
|
+
});
|
|
63
|
+
return parts;
|
|
64
|
+
})().catch((e) => {
|
|
65
|
+
this.parts = null;
|
|
66
|
+
throw e;
|
|
67
|
+
}));
|
|
68
|
+
return this.parts;
|
|
69
|
+
}
|
|
70
|
+
/** Mirrors the profile and reads what only the player reads (all of it, or the parts named). */
|
|
71
|
+
async watch(...which) {
|
|
72
|
+
const parts = await this.ready();
|
|
73
|
+
const all = which.length ? which : ['profile', 'inventory', 'wallet', 'progress'];
|
|
74
|
+
await Promise.all(all.map((w) => parts[w].start()));
|
|
75
|
+
}
|
|
76
|
+
async unwatch() {
|
|
77
|
+
if (!this.parts)
|
|
78
|
+
return;
|
|
79
|
+
const parts = await this.parts;
|
|
80
|
+
await Promise.all([parts.profile.stop(), parts.inventory.stop(), parts.wallet.stop(), parts.progress.stop()]);
|
|
81
|
+
}
|
|
82
|
+
/** Levels gained and achievements unlocked, as the `player` topic announces them. */
|
|
83
|
+
onEvent(listener) {
|
|
84
|
+
this.events.add(listener);
|
|
85
|
+
return () => this.events.delete(listener);
|
|
86
|
+
}
|
|
87
|
+
/** Calls the player's hub; a change carries an action id. */
|
|
88
|
+
async call(method, args, change = true) {
|
|
89
|
+
return this.runtime.call(this.nodeType, await this.key(), method, args ?? null, change ? { action: true } : {});
|
|
90
|
+
}
|
|
91
|
+
/** Everything the hub keeps for the player. */
|
|
92
|
+
me() {
|
|
93
|
+
return this.call('me', null, false);
|
|
94
|
+
}
|
|
95
|
+
/** The game's items, recipes, loot tables, quests and achievements (read once). */
|
|
96
|
+
rules() {
|
|
97
|
+
this.rulesRead ?? (this.rulesRead = this.call('rules', null, false).catch((e) => {
|
|
98
|
+
this.rulesRead = null;
|
|
99
|
+
throw e;
|
|
100
|
+
}));
|
|
101
|
+
return this.rulesRead;
|
|
102
|
+
}
|
|
103
|
+
/** Another player's public profile. */
|
|
104
|
+
profileOf(player) {
|
|
105
|
+
return this.runtime.call(this.nodeType, String(player), 'profile');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** The player's items: stacks in slots, and mail for what did not fit. */
|
|
109
|
+
export class InventoryKit {
|
|
110
|
+
constructor(player) {
|
|
111
|
+
this.player = player;
|
|
112
|
+
}
|
|
113
|
+
get items() {
|
|
114
|
+
return this.player.inventory;
|
|
115
|
+
}
|
|
116
|
+
async watch() {
|
|
117
|
+
await this.player.watch('inventory');
|
|
118
|
+
return this.player.inventory.get();
|
|
119
|
+
}
|
|
120
|
+
/** How many of `item` the player has, as last read. */
|
|
121
|
+
count(item) {
|
|
122
|
+
return (this.player.inventory.get()?.stacks ?? []).filter((s) => s.item === item).reduce((n, s) => n + s.count, 0);
|
|
123
|
+
}
|
|
124
|
+
/** @throws {KitError} `insufficient`. */
|
|
125
|
+
discard(item, count) {
|
|
126
|
+
return this.player.call('discard', { item, count });
|
|
127
|
+
}
|
|
128
|
+
/** Moves what fits from the mail into the inventory; resolves with what moved. */
|
|
129
|
+
takeMail() {
|
|
130
|
+
return this.player.call('take_mail');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** The player's soft currencies. */
|
|
134
|
+
export class WalletKit {
|
|
135
|
+
constructor(player) {
|
|
136
|
+
this.player = player;
|
|
137
|
+
}
|
|
138
|
+
get balances() {
|
|
139
|
+
return this.player.wallet;
|
|
140
|
+
}
|
|
141
|
+
async watch() {
|
|
142
|
+
await this.player.watch('wallet');
|
|
143
|
+
return this.player.wallet.get();
|
|
144
|
+
}
|
|
145
|
+
/** The balance of `currency`, as last read. */
|
|
146
|
+
balance(currency) {
|
|
147
|
+
return this.player.wallet.get()?.balances[currency] ?? 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Experience and levels, and the public profile. */
|
|
151
|
+
export class ProgressionKit {
|
|
152
|
+
constructor(player) {
|
|
153
|
+
this.player = player;
|
|
154
|
+
}
|
|
155
|
+
get progress() {
|
|
156
|
+
return this.player.progress;
|
|
157
|
+
}
|
|
158
|
+
get profile() {
|
|
159
|
+
return this.player.profile;
|
|
160
|
+
}
|
|
161
|
+
async watch() {
|
|
162
|
+
await this.player.watch('profile', 'progress');
|
|
163
|
+
return this.player.progress.get();
|
|
164
|
+
}
|
|
165
|
+
/** Another player's public profile. */
|
|
166
|
+
profileOf(player) {
|
|
167
|
+
return this.player.profileOf(player);
|
|
168
|
+
}
|
|
169
|
+
/** Called with each level the player reaches. */
|
|
170
|
+
onLevel(listener) {
|
|
171
|
+
return this.player.onEvent((e) => e.type === 'level' && listener(e.level));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Achievements: counts of what the player did, unlocked once. */
|
|
175
|
+
export class AchievementsKit {
|
|
176
|
+
constructor(player) {
|
|
177
|
+
this.player = player;
|
|
178
|
+
}
|
|
179
|
+
async watch() {
|
|
180
|
+
await this.player.watch('profile', 'progress');
|
|
181
|
+
return this.list();
|
|
182
|
+
}
|
|
183
|
+
/** The achievements with their counts, as last read (hidden ones once unlocked). */
|
|
184
|
+
list() {
|
|
185
|
+
return this.player.progress.get()?.achievements ?? [];
|
|
186
|
+
}
|
|
187
|
+
/** Called with each achievement the player unlocks. */
|
|
188
|
+
onUnlock(listener) {
|
|
189
|
+
return this.player.onEvent((e) => e.type === 'achievement' && listener(e.achievement));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Quests, first-session tutorials among them. */
|
|
193
|
+
export class QuestsKit {
|
|
194
|
+
constructor(player) {
|
|
195
|
+
this.player = player;
|
|
196
|
+
}
|
|
197
|
+
async watch() {
|
|
198
|
+
await this.player.watch('progress');
|
|
199
|
+
return this.list();
|
|
200
|
+
}
|
|
201
|
+
list() {
|
|
202
|
+
return this.player.progress.get()?.quests ?? [];
|
|
203
|
+
}
|
|
204
|
+
/** @throws {KitError} `conflict` (taken, done, or another first), `forbidden` (level), `not_found`. */
|
|
205
|
+
accept(quest) {
|
|
206
|
+
return this.player.call('accept', { quest });
|
|
207
|
+
}
|
|
208
|
+
/** Claims a complete quest's reward. @throws {KitError} `not_ready`, `full`. */
|
|
209
|
+
claim(quest) {
|
|
210
|
+
return this.player.call('claim', { quest });
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Reports one of the counters the game lets a client report (a tutorial step).
|
|
214
|
+
* @throws {KitError} `forbidden` for a counter the game's hubs count.
|
|
215
|
+
*/
|
|
216
|
+
step(counter) {
|
|
217
|
+
return this.player.call('step', { counter });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Crafting from the game's recipes. */
|
|
221
|
+
export class CraftingKit {
|
|
222
|
+
constructor(player) {
|
|
223
|
+
this.player = player;
|
|
224
|
+
}
|
|
225
|
+
async recipes() {
|
|
226
|
+
return (await this.player.rules()).recipes;
|
|
227
|
+
}
|
|
228
|
+
/** Crafts `times` (once by default). @throws {KitError} `insufficient`, `full`, `forbidden` (level), `not_found`. */
|
|
229
|
+
craft(recipe, times) {
|
|
230
|
+
return this.player.call('craft', times === undefined ? { recipe } : { recipe, times });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Loot tables with pity. */
|
|
234
|
+
export class LootKit {
|
|
235
|
+
constructor(player) {
|
|
236
|
+
this.player = player;
|
|
237
|
+
}
|
|
238
|
+
async tables() {
|
|
239
|
+
return (await this.player.rules()).loot;
|
|
240
|
+
}
|
|
241
|
+
/** Pays the table's cost and rolls once. @throws {KitError} `insufficient`, `full`, `not_found`. */
|
|
242
|
+
open(table) {
|
|
243
|
+
return this.player.call('open', { table });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Idle production that catches up on the time away. */
|
|
247
|
+
export class ProductionKit {
|
|
248
|
+
constructor(player) {
|
|
249
|
+
this.player = player;
|
|
250
|
+
}
|
|
251
|
+
async view() {
|
|
252
|
+
return (await this.player.me()).production;
|
|
253
|
+
}
|
|
254
|
+
/** Moves what the producers made into the inventory, as far as it fits. */
|
|
255
|
+
collect() {
|
|
256
|
+
return this.player.call('collect');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { HubState } from '../mirror.js';
|
|
2
|
+
import type { KitRuntime } from '../runtime.js';
|
|
3
|
+
export interface RaceGate {
|
|
4
|
+
x: number;
|
|
5
|
+
z: number;
|
|
6
|
+
radius: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RaceRacer {
|
|
9
|
+
player: number;
|
|
10
|
+
x: number;
|
|
11
|
+
z: number;
|
|
12
|
+
lap: number;
|
|
13
|
+
/** The gate the racer must pass next. */
|
|
14
|
+
next_gate: number;
|
|
15
|
+
best_lap_ms: number | null;
|
|
16
|
+
place: number | null;
|
|
17
|
+
total_ms: number | null;
|
|
18
|
+
did_not_finish: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** What the `race` topic carries. */
|
|
21
|
+
export interface RaceView {
|
|
22
|
+
phase: 'lobby' | 'countdown' | 'racing' | 'finished';
|
|
23
|
+
course_id: string;
|
|
24
|
+
gates: RaceGate[];
|
|
25
|
+
laps: number;
|
|
26
|
+
/** When the race starts (Unix ms), once counting down. */
|
|
27
|
+
go_at: number | null;
|
|
28
|
+
/** When the rest's time is up, once the winner is in. */
|
|
29
|
+
closes_at: number | null;
|
|
30
|
+
racers: RaceRacer[];
|
|
31
|
+
}
|
|
32
|
+
/** The winner's run: `[ms from the start, x, y, z, yaw]` frames to race against. */
|
|
33
|
+
export interface RaceGhost {
|
|
34
|
+
player: number;
|
|
35
|
+
total_ms: number;
|
|
36
|
+
frames: Array<[number, number, number, number, number]>;
|
|
37
|
+
}
|
|
38
|
+
export interface BallPlayer {
|
|
39
|
+
player: number;
|
|
40
|
+
team: 'red' | 'blue';
|
|
41
|
+
x: number;
|
|
42
|
+
z: number;
|
|
43
|
+
}
|
|
44
|
+
export interface BallState {
|
|
45
|
+
x: number;
|
|
46
|
+
z: number;
|
|
47
|
+
vx: number;
|
|
48
|
+
vz: number;
|
|
49
|
+
holder: number | null;
|
|
50
|
+
held_since: number;
|
|
51
|
+
last_touch: number | null;
|
|
52
|
+
}
|
|
53
|
+
/** What the `ball` topic carries. */
|
|
54
|
+
export interface BallView {
|
|
55
|
+
phase: 'lobby' | 'playing' | 'finished';
|
|
56
|
+
width: number;
|
|
57
|
+
depth: number;
|
|
58
|
+
goal_width: number;
|
|
59
|
+
ball: BallState;
|
|
60
|
+
players: BallPlayer[];
|
|
61
|
+
/** Red's, then blue's. */
|
|
62
|
+
score: [number, number];
|
|
63
|
+
ends_at: number | null;
|
|
64
|
+
winner: 'red' | 'blue' | null;
|
|
65
|
+
}
|
|
66
|
+
/** Races and ball games (the `racing` starter, types `race` and `ball`). */
|
|
67
|
+
export declare class RacingKit {
|
|
68
|
+
private readonly runtime;
|
|
69
|
+
private readonly raceType;
|
|
70
|
+
private readonly ballType;
|
|
71
|
+
private readonly races;
|
|
72
|
+
private readonly matches;
|
|
73
|
+
constructor(runtime: KitRuntime, raceType?: string, ballType?: string);
|
|
74
|
+
/** The race with key `key`. */
|
|
75
|
+
race(key: string): Race;
|
|
76
|
+
/** The ball game with key `key`. */
|
|
77
|
+
ball(key: string): BallMatch;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* One race: a course of gates passed in order, a countdown, laps and splits, places in finishing
|
|
81
|
+
* order, and a ghost of the winner's run. A client reports where its racer is ({@link pos}, a few
|
|
82
|
+
* times a second) and the hub times the positions its movement warden kept. `view.onEvent` hears
|
|
83
|
+
* `go`, `timing` (with its `stage`: `started`, `checkpoint`, `lap`, `finished`), `violation` and
|
|
84
|
+
* `finished`.
|
|
85
|
+
*/
|
|
86
|
+
export declare class Race {
|
|
87
|
+
private readonly runtime;
|
|
88
|
+
readonly nodeType: string;
|
|
89
|
+
readonly key: string;
|
|
90
|
+
readonly view: HubState<RaceView>;
|
|
91
|
+
constructor(runtime: KitRuntime, nodeType: string, key: string);
|
|
92
|
+
watch(): Promise<RaceView>;
|
|
93
|
+
unwatch(): Promise<void>;
|
|
94
|
+
/** @throws {KitError} `started`, `full`. */
|
|
95
|
+
join(): Promise<boolean>;
|
|
96
|
+
leave(): Promise<boolean>;
|
|
97
|
+
/** Starts the countdown (the first to join, or a developer). @throws {KitError} `forbidden`, `started`. */
|
|
98
|
+
start(): Promise<boolean>;
|
|
99
|
+
/** Reports where the racer is; resolves with the position the hub kept. */
|
|
100
|
+
pos(x: number, z: number): Promise<[number, number]>;
|
|
101
|
+
/** The winner's run, once there is one. */
|
|
102
|
+
ghost(): Promise<RaceGhost | null>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* One ball game: two teams, a ball the hub moves (passes, shots, friction, the side walls),
|
|
106
|
+
* possession (claim a loose ball near it, or take it from an opponent whose protection is over), a
|
|
107
|
+
* goal in each end wall. `view.onEvent` hears `claimed`, `passed`, `shot`, `goal`, `violation` and
|
|
108
|
+
* `finished`.
|
|
109
|
+
*/
|
|
110
|
+
export declare class BallMatch {
|
|
111
|
+
private readonly runtime;
|
|
112
|
+
readonly nodeType: string;
|
|
113
|
+
readonly key: string;
|
|
114
|
+
readonly view: HubState<BallView>;
|
|
115
|
+
constructor(runtime: KitRuntime, nodeType: string, key: string);
|
|
116
|
+
watch(): Promise<BallView>;
|
|
117
|
+
unwatch(): Promise<void>;
|
|
118
|
+
/** @throws {KitError} `bad_request` (red or blue), `full`, `started`. */
|
|
119
|
+
join(team: 'red' | 'blue'): Promise<boolean>;
|
|
120
|
+
/** @throws {KitError} `forbidden`, `not_ready` (each team needs a player), `started`. */
|
|
121
|
+
start(): Promise<boolean>;
|
|
122
|
+
/** Reports where the player is; resolves with the position the hub kept. */
|
|
123
|
+
pos(x: number, z: number): Promise<[number, number]>;
|
|
124
|
+
/** Claims the ball. @throws {KitError} `out_of_reach`, `cooldown` (too soon, or its holder's protection), `conflict` (a teammate has it). */
|
|
125
|
+
claim(): Promise<boolean>;
|
|
126
|
+
/** Passes to a teammate. @throws {KitError} `conflict` (not the holder), `bad_request` (not a teammate). */
|
|
127
|
+
pass(to: number | string): Promise<boolean>;
|
|
128
|
+
/** Shoots toward `(x, z)`. @throws {KitError} `conflict` (not the holder). */
|
|
129
|
+
shoot(x: number, z: number): Promise<boolean>;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=racing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"racing.d.ts","sourceRoot":"","sources":["../../../src/kit/modules/racing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yDAAyD;IACzD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,0BAA0B;IAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,4EAA4E;AAC5E,qBAAa,SAAS;IAKlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAN3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;gBAGrC,OAAO,EAAE,UAAU,EACnB,QAAQ,SAAS,EACjB,QAAQ,SAAS;IAGpC,+BAA+B;IAC/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IASvB,oCAAoC;IACpC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;CAQ7B;AAED;;;;;;GAMG;AACH,qBAAa,IAAI;IAIb,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM;IALtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAGf,OAAO,EAAE,UAAU,EAC3B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM;IAKtB,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;IAI1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAIxB,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzB,2GAA2G;IAC3G,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzB,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAIpD,2CAA2C;IAC3C,KAAK,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAGnC;AAED;;;;;GAKG;AACH,qBAAa,SAAS;IAIlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM;IALtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAGf,OAAO,EAAE,UAAU,EAC3B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM;IAKtB,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC;IAI1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,yEAAyE;IACzE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI5C,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzB,4EAA4E;IAC5E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAIpD,6IAA6I;IAC7I,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI3C,8EAA8E;IAC9E,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAG9C"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { HubState } from '../mirror.js';
|
|
2
|
+
/** Races and ball games (the `racing` starter, types `race` and `ball`). */
|
|
3
|
+
export class RacingKit {
|
|
4
|
+
constructor(runtime, raceType = 'race', ballType = 'ball') {
|
|
5
|
+
this.runtime = runtime;
|
|
6
|
+
this.raceType = raceType;
|
|
7
|
+
this.ballType = ballType;
|
|
8
|
+
this.races = new Map();
|
|
9
|
+
this.matches = new Map();
|
|
10
|
+
}
|
|
11
|
+
/** The race with key `key`. */
|
|
12
|
+
race(key) {
|
|
13
|
+
let r = this.races.get(key);
|
|
14
|
+
if (!r) {
|
|
15
|
+
r = new Race(this.runtime, this.raceType, key);
|
|
16
|
+
this.races.set(key, r);
|
|
17
|
+
}
|
|
18
|
+
return r;
|
|
19
|
+
}
|
|
20
|
+
/** The ball game with key `key`. */
|
|
21
|
+
ball(key) {
|
|
22
|
+
let m = this.matches.get(key);
|
|
23
|
+
if (!m) {
|
|
24
|
+
m = new BallMatch(this.runtime, this.ballType, key);
|
|
25
|
+
this.matches.set(key, m);
|
|
26
|
+
}
|
|
27
|
+
return m;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One race: a course of gates passed in order, a countdown, laps and splits, places in finishing
|
|
32
|
+
* order, and a ghost of the winner's run. A client reports where its racer is ({@link pos}, a few
|
|
33
|
+
* times a second) and the hub times the positions its movement warden kept. `view.onEvent` hears
|
|
34
|
+
* `go`, `timing` (with its `stage`: `started`, `checkpoint`, `lap`, `finished`), `violation` and
|
|
35
|
+
* `finished`.
|
|
36
|
+
*/
|
|
37
|
+
export class Race {
|
|
38
|
+
constructor(runtime, nodeType, key) {
|
|
39
|
+
this.runtime = runtime;
|
|
40
|
+
this.nodeType = nodeType;
|
|
41
|
+
this.key = key;
|
|
42
|
+
this.view = new HubState(runtime, nodeType, key, 'race');
|
|
43
|
+
}
|
|
44
|
+
watch() {
|
|
45
|
+
return this.view.start();
|
|
46
|
+
}
|
|
47
|
+
unwatch() {
|
|
48
|
+
return this.view.stop();
|
|
49
|
+
}
|
|
50
|
+
/** @throws {KitError} `started`, `full`. */
|
|
51
|
+
join() {
|
|
52
|
+
return this.runtime.call(this.nodeType, this.key, 'join', null, { action: true });
|
|
53
|
+
}
|
|
54
|
+
leave() {
|
|
55
|
+
return this.runtime.call(this.nodeType, this.key, 'leave', null, { action: true });
|
|
56
|
+
}
|
|
57
|
+
/** Starts the countdown (the first to join, or a developer). @throws {KitError} `forbidden`, `started`. */
|
|
58
|
+
start() {
|
|
59
|
+
return this.runtime.call(this.nodeType, this.key, 'start', null, { action: true });
|
|
60
|
+
}
|
|
61
|
+
/** Reports where the racer is; resolves with the position the hub kept. */
|
|
62
|
+
pos(x, z) {
|
|
63
|
+
return this.runtime.call(this.nodeType, this.key, 'pos', { x, z });
|
|
64
|
+
}
|
|
65
|
+
/** The winner's run, once there is one. */
|
|
66
|
+
ghost() {
|
|
67
|
+
return this.runtime.call(this.nodeType, this.key, 'ghost');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One ball game: two teams, a ball the hub moves (passes, shots, friction, the side walls),
|
|
72
|
+
* possession (claim a loose ball near it, or take it from an opponent whose protection is over), a
|
|
73
|
+
* goal in each end wall. `view.onEvent` hears `claimed`, `passed`, `shot`, `goal`, `violation` and
|
|
74
|
+
* `finished`.
|
|
75
|
+
*/
|
|
76
|
+
export class BallMatch {
|
|
77
|
+
constructor(runtime, nodeType, key) {
|
|
78
|
+
this.runtime = runtime;
|
|
79
|
+
this.nodeType = nodeType;
|
|
80
|
+
this.key = key;
|
|
81
|
+
this.view = new HubState(runtime, nodeType, key, 'ball');
|
|
82
|
+
}
|
|
83
|
+
watch() {
|
|
84
|
+
return this.view.start();
|
|
85
|
+
}
|
|
86
|
+
unwatch() {
|
|
87
|
+
return this.view.stop();
|
|
88
|
+
}
|
|
89
|
+
/** @throws {KitError} `bad_request` (red or blue), `full`, `started`. */
|
|
90
|
+
join(team) {
|
|
91
|
+
return this.runtime.call(this.nodeType, this.key, 'join', { team }, { action: true });
|
|
92
|
+
}
|
|
93
|
+
/** @throws {KitError} `forbidden`, `not_ready` (each team needs a player), `started`. */
|
|
94
|
+
start() {
|
|
95
|
+
return this.runtime.call(this.nodeType, this.key, 'start', null, { action: true });
|
|
96
|
+
}
|
|
97
|
+
/** Reports where the player is; resolves with the position the hub kept. */
|
|
98
|
+
pos(x, z) {
|
|
99
|
+
return this.runtime.call(this.nodeType, this.key, 'pos', { x, z });
|
|
100
|
+
}
|
|
101
|
+
/** Claims the ball. @throws {KitError} `out_of_reach`, `cooldown` (too soon, or its holder's protection), `conflict` (a teammate has it). */
|
|
102
|
+
claim() {
|
|
103
|
+
return this.runtime.call(this.nodeType, this.key, 'claim', null, { action: true });
|
|
104
|
+
}
|
|
105
|
+
/** Passes to a teammate. @throws {KitError} `conflict` (not the holder), `bad_request` (not a teammate). */
|
|
106
|
+
pass(to) {
|
|
107
|
+
return this.runtime.call(this.nodeType, this.key, 'pass', { to: Number(to) }, { action: true });
|
|
108
|
+
}
|
|
109
|
+
/** Shoots toward `(x, z)`. @throws {KitError} `conflict` (not the holder). */
|
|
110
|
+
shoot(x, z) {
|
|
111
|
+
return this.runtime.call(this.nodeType, this.key, 'shoot', { x, z }, { action: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { HubState, PrivateState } from '../mirror.js';
|
|
2
|
+
import type { KitRuntime } from '../runtime.js';
|
|
3
|
+
/** The last thing done at a table. */
|
|
4
|
+
export interface CardPlayed {
|
|
5
|
+
player: number;
|
|
6
|
+
card: string | null;
|
|
7
|
+
/** `play`, `draw` or `pass`. */
|
|
8
|
+
action: string;
|
|
9
|
+
}
|
|
10
|
+
/** What the `table` topic carries: the public table, never a hand. */
|
|
11
|
+
export interface CardTableView {
|
|
12
|
+
phase: 'lobby' | 'playing' | 'finished';
|
|
13
|
+
players: number[];
|
|
14
|
+
host: number | null;
|
|
15
|
+
/** Cards in each player's hand, by player id (the keys are the ids as text). */
|
|
16
|
+
hand_counts: Record<string, number>;
|
|
17
|
+
top: string | null;
|
|
18
|
+
/** The suit in play (an eight names one). */
|
|
19
|
+
suit: string | null;
|
|
20
|
+
draw_count: number;
|
|
21
|
+
turn: number | null;
|
|
22
|
+
turn_ends: number | null;
|
|
23
|
+
last: CardPlayed | null;
|
|
24
|
+
winner: number | null;
|
|
25
|
+
scores: Record<string, number>;
|
|
26
|
+
}
|
|
27
|
+
/** The caller's hand, as `hand` answers it. */
|
|
28
|
+
export interface CardHand {
|
|
29
|
+
cards: string[];
|
|
30
|
+
/** The cards the rule lets them play now. */
|
|
31
|
+
playable: string[];
|
|
32
|
+
/** Whether they drew this turn (and may pass). */
|
|
33
|
+
drawn: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Card tables (the `deck` starter): {@link table} by key. */
|
|
36
|
+
export declare class TablesKit {
|
|
37
|
+
private readonly runtime;
|
|
38
|
+
private readonly nodeType;
|
|
39
|
+
private readonly tables;
|
|
40
|
+
constructor(runtime: KitRuntime, nodeType?: string);
|
|
41
|
+
table(key: string): CardTable;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One table with true hidden information: the deck and every hand live in the hub, and a player
|
|
45
|
+
* reads only their own hand ({@link hand}, read again whenever the hub's `changed` message names
|
|
46
|
+
* them). The starter's game is crazy eights.
|
|
47
|
+
*/
|
|
48
|
+
export declare class CardTable {
|
|
49
|
+
private readonly runtime;
|
|
50
|
+
readonly nodeType: string;
|
|
51
|
+
readonly key: string;
|
|
52
|
+
/** The `table` topic: the public table. */
|
|
53
|
+
readonly table: HubState<CardTableView>;
|
|
54
|
+
/** The caller's hand: never on the topic. */
|
|
55
|
+
readonly hand: PrivateState<CardHand>;
|
|
56
|
+
constructor(runtime: KitRuntime, nodeType: string, key: string);
|
|
57
|
+
/** Mirrors the table and the caller's hand. */
|
|
58
|
+
watch(): Promise<CardTableView>;
|
|
59
|
+
unwatch(): Promise<void>;
|
|
60
|
+
/** @throws {KitError} `started`, `full`. */
|
|
61
|
+
join(): Promise<boolean>;
|
|
62
|
+
leave(): Promise<boolean>;
|
|
63
|
+
/** Deals (the host, from two players). @throws {KitError} `forbidden`, `too_few_players`, `started`. */
|
|
64
|
+
start(): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Plays a card from the caller's hand; an eight names `suit` (`S`, `H`, `D` or `C`).
|
|
67
|
+
* @throws {KitError} `not_your_turn`, `not_found` (not in the hand), `not_playable` (the rule),
|
|
68
|
+
* `bad_request` (an eight without a suit).
|
|
69
|
+
*/
|
|
70
|
+
play(card: string, suit?: string): Promise<boolean>;
|
|
71
|
+
/** Draws a card (once a turn). @throws {KitError} `not_your_turn`, `conflict`, `empty`. */
|
|
72
|
+
draw(): Promise<boolean>;
|
|
73
|
+
/** Passes, after drawing. @throws {KitError} `conflict` (draw first). */
|
|
74
|
+
pass(): Promise<boolean>;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../src/kit/modules/tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,+CAA+C;AAC/C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kDAAkD;IAClD,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,8DAA8D;AAC9D,qBAAa,SAAS;IAIlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJ3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,OAAO,EAAE,UAAU,EACnB,QAAQ,SAAS;IAGpC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;CAQ9B;AAED;;;;GAIG;AACH,qBAAa,SAAS;IAOlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM;IARtB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IACxC,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;gBAGnB,OAAO,EAAE,UAAU,EAC3B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM;IAYtB,+CAA+C;IACzC,KAAK,IAAI,OAAO,CAAC,aAAa,CAAC;IAK/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAIxB,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzB,wGAAwG;IACxG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAIzB;;;;OAIG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnD,2FAA2F;IAC3F,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAIxB,yEAAyE;IACzE,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;CAGzB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { HubState, PrivateState } from '../mirror.js';
|
|
2
|
+
/** Card tables (the `deck` starter): {@link table} by key. */
|
|
3
|
+
export class TablesKit {
|
|
4
|
+
constructor(runtime, nodeType = 'deck') {
|
|
5
|
+
this.runtime = runtime;
|
|
6
|
+
this.nodeType = nodeType;
|
|
7
|
+
this.tables = new Map();
|
|
8
|
+
}
|
|
9
|
+
table(key) {
|
|
10
|
+
let t = this.tables.get(key);
|
|
11
|
+
if (!t) {
|
|
12
|
+
t = new CardTable(this.runtime, this.nodeType, key);
|
|
13
|
+
this.tables.set(key, t);
|
|
14
|
+
}
|
|
15
|
+
return t;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One table with true hidden information: the deck and every hand live in the hub, and a player
|
|
20
|
+
* reads only their own hand ({@link hand}, read again whenever the hub's `changed` message names
|
|
21
|
+
* them). The starter's game is crazy eights.
|
|
22
|
+
*/
|
|
23
|
+
export class CardTable {
|
|
24
|
+
constructor(runtime, nodeType, key) {
|
|
25
|
+
this.runtime = runtime;
|
|
26
|
+
this.nodeType = nodeType;
|
|
27
|
+
this.key = key;
|
|
28
|
+
this.table = new HubState(runtime, nodeType, key, 'table');
|
|
29
|
+
this.hand = new PrivateState(runtime, {
|
|
30
|
+
nodeType,
|
|
31
|
+
key,
|
|
32
|
+
topic: 'table',
|
|
33
|
+
what: 'hand',
|
|
34
|
+
read: () => runtime.call(nodeType, key, 'hand'),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Mirrors the table and the caller's hand. */
|
|
38
|
+
async watch() {
|
|
39
|
+
const [view] = await Promise.all([this.table.start(), this.hand.start()]);
|
|
40
|
+
return view;
|
|
41
|
+
}
|
|
42
|
+
async unwatch() {
|
|
43
|
+
await Promise.all([this.table.stop(), this.hand.stop()]);
|
|
44
|
+
}
|
|
45
|
+
/** @throws {KitError} `started`, `full`. */
|
|
46
|
+
join() {
|
|
47
|
+
return this.runtime.call(this.nodeType, this.key, 'join', null, { action: true });
|
|
48
|
+
}
|
|
49
|
+
leave() {
|
|
50
|
+
return this.runtime.call(this.nodeType, this.key, 'leave', null, { action: true });
|
|
51
|
+
}
|
|
52
|
+
/** Deals (the host, from two players). @throws {KitError} `forbidden`, `too_few_players`, `started`. */
|
|
53
|
+
start() {
|
|
54
|
+
return this.runtime.call(this.nodeType, this.key, 'start', null, { action: true });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Plays a card from the caller's hand; an eight names `suit` (`S`, `H`, `D` or `C`).
|
|
58
|
+
* @throws {KitError} `not_your_turn`, `not_found` (not in the hand), `not_playable` (the rule),
|
|
59
|
+
* `bad_request` (an eight without a suit).
|
|
60
|
+
*/
|
|
61
|
+
play(card, suit) {
|
|
62
|
+
const args = suit === undefined ? { card } : { card, suit };
|
|
63
|
+
return this.runtime.call(this.nodeType, this.key, 'play', args, { action: true });
|
|
64
|
+
}
|
|
65
|
+
/** Draws a card (once a turn). @throws {KitError} `not_your_turn`, `conflict`, `empty`. */
|
|
66
|
+
draw() {
|
|
67
|
+
return this.runtime.call(this.nodeType, this.key, 'draw', null, { action: true });
|
|
68
|
+
}
|
|
69
|
+
/** Passes, after drawing. @throws {KitError} `conflict` (draw first). */
|
|
70
|
+
pass() {
|
|
71
|
+
return this.runtime.call(this.nodeType, this.key, 'pass', null, { action: true });
|
|
72
|
+
}
|
|
73
|
+
}
|