minimojs 1.0.0-alpha.35 → 1.0.0-alpha.37

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.
@@ -0,0 +1,374 @@
1
+ import { Game } from "./minimo.js";
2
+ /**
3
+ * How a room plays:
4
+ *
5
+ * - `"turns"`: the players take turns on a state the room keeps, such as a
6
+ * card game or a board game. Only the player whose turn it is changes it.
7
+ * - `"competition"`: every player plays the same round on their own, from the
8
+ * same seed, and the room compares their scores at the end.
9
+ */
10
+ export type RoomMode = "turns" | "competition";
11
+ /**
12
+ * Where a room is: `"lobby"`, waiting for its players; `"playing"`; or
13
+ * `"finished"`, showing its results until the host starts again.
14
+ */
15
+ export type RoomPhase = "lobby" | "playing" | "finished";
16
+ /** Which score wins: `higher`, for points, or `lower`, for times. */
17
+ export type RoomOrder = "higher" | "lower";
18
+ /**
19
+ * How the game's rooms work. Every field is optional.
20
+ */
21
+ export interface MultiplayerOptions {
22
+ /** How the room plays. Default `"turns"`. */
23
+ mode?: RoomMode;
24
+ /** The fewest players who can start. Default `2` by turns, `1` in a competition. */
25
+ minPlayers?: number;
26
+ /** The most players a room seats, at most `8`. Default `8`. */
27
+ maxPlayers?: number;
28
+ /** In a competition and in results, which score wins. Default `"higher"`. */
29
+ order?: RoomOrder;
30
+ /**
31
+ * A live competition's round ends this many seconds after it starts, from 5
32
+ * to 3600, even if some players have not finished. Default: it waits for
33
+ * every player, and the host can end it.
34
+ */
35
+ timeLimit?: number;
36
+ /**
37
+ * Makes a competition an asynchronous challenge: its round starts as soon
38
+ * as the room is made, and anyone with its link joins and plays it within
39
+ * this many hours, from 1 to 168. Results show as they arrive.
40
+ */
41
+ challengeHours?: number;
42
+ /**
43
+ * How many seconds a disconnected player keeps their turn, and a
44
+ * disconnected host the room, before it passes to the next player, from 0
45
+ * to 3600. `0` waits forever. Default `60`.
46
+ */
47
+ turnTimeout?: number;
48
+ /** The lobby's title. Default `"Play with friends"`. */
49
+ title?: string;
50
+ /** CSS font family of the lobby, such as one of the game's fonts. Default: the system font. */
51
+ font?: string;
52
+ /** Background color of the lobby's panels. Default `"#15151d"`. */
53
+ background?: string;
54
+ /** Text color. Default `"#ffffff"`. */
55
+ color?: string;
56
+ /** Color of titles, buttons, and the player's own row. Default `"#ffcc33"`. */
57
+ accent?: string;
58
+ /**
59
+ * Whether a thin strip at the top of the game says whose turn it is and who
60
+ * dropped out, during a game by turns. Default `true`.
61
+ */
62
+ strip?: boolean;
63
+ }
64
+ /** A player in a room. */
65
+ export interface RoomPlayer {
66
+ /** The player's id in this room. It says nothing about who they are. */
67
+ id: string;
68
+ /** The name Minimo Games shows for the player, the same as in leaderboards. */
69
+ name: string;
70
+ /** Their place at the table, from 0, in the order they joined. */
71
+ seat: number;
72
+ host: boolean;
73
+ /** Whether the player is connected now. */
74
+ connected: boolean;
75
+ /** In a competition, whether they have reported this round's result. */
76
+ finished: boolean;
77
+ /** Their result, once reported or given when the game ended, or `null`. */
78
+ score: number | null;
79
+ /** Whether this is the player playing on this device. */
80
+ me: boolean;
81
+ }
82
+ /** One row of a room's results, best first. */
83
+ export interface RoomResult {
84
+ id: string;
85
+ name: string;
86
+ score: number;
87
+ /** 1 for the best. Equal scores share a rank. */
88
+ rank: number;
89
+ me: boolean;
90
+ }
91
+ /** A change to the room, made with {@link Room.update}. Every field is optional. */
92
+ export interface RoomUpdate {
93
+ /** The new shared state, which every player receives: plain JSON, at most 16 KB. */
94
+ state?: unknown;
95
+ /**
96
+ * Private states by player id, each received only by that player, such as
97
+ * the hands dealt: plain JSON, at most 4 KB each. `null` clears one.
98
+ */
99
+ private?: Record<string, unknown>;
100
+ /** Passes the turn: `"next"`, the next player by seat, or a player's id. */
101
+ turn?: "next" | string;
102
+ }
103
+ /**
104
+ * The room a player is expected in when the game starts, from
105
+ * {@link Multiplayer.pendingRoom}.
106
+ */
107
+ export interface PendingRoom {
108
+ /** The room's code, such as `KQ7XA`. */
109
+ code: string;
110
+ /**
111
+ * `true` when the player opened the room's link, an invitation; `false`
112
+ * when they were already in the room on this device, as after a reload.
113
+ */
114
+ invited: boolean;
115
+ }
116
+ /** Options for {@link Room.showResults}. */
117
+ export interface RoomResultsOptions {
118
+ /** A title instead of `"Results"`. */
119
+ title?: string;
120
+ /** How a score is written, such as `(s) => s.toFixed(2) + " s"`. Default: the number, with digit grouping. */
121
+ format?: (score: number) => string;
122
+ }
123
+ /**
124
+ * What a room promise is refused with. `reason` is one of:
125
+ * `unavailable` (not on Minimo Games, or the game's creator is not on Pro),
126
+ * `cancelled` (the player closed the lobby), `room_not_found`, `full`,
127
+ * `in_progress`, `removed`, `not_your_turn`, `stale` (another change came
128
+ * first; read the room again and retry), `not_host`, `wrong_phase`,
129
+ * `not_enough_players`, `state_too_large`, `closed`, or another the room
130
+ * names.
131
+ */
132
+ export declare class MultiplayerError extends Error {
133
+ readonly reason: string;
134
+ constructor(message: string, reason: string);
135
+ }
136
+ /**
137
+ * A room the player is in: its players, its phase, its shared state, the
138
+ * player's private state, and whose turn it is. Every field reads the room as
139
+ * the player last received it.
140
+ *
141
+ * Changes made by the other players arrive between frames, and the listeners
142
+ * given to {@link Room.onChange} and {@link Room.onMessage} are called inside
143
+ * the game's loop, before the scene's `onUpdate`, so a game never sees the room
144
+ * change halfway through a frame. A change the player makes themselves is in
145
+ * the room when its promise resolves.
146
+ *
147
+ * Get a room from {@link Multiplayer.lobby}, {@link Multiplayer.create}, or
148
+ * {@link Multiplayer.join}.
149
+ */
150
+ export declare class Room {
151
+ /** The room's code, such as `KQ7XA`, which friends type to join. */
152
+ get code(): string;
153
+ /** The address that opens the game straight into this room. */
154
+ get link(): string;
155
+ get mode(): RoomMode;
156
+ get phase(): RoomPhase;
157
+ /** Grows each time the room starts a new round: 1 for the first. */
158
+ get round(): number;
159
+ /** The players in the room, by seat. */
160
+ get players(): RoomPlayer[];
161
+ /** The player playing on this device. */
162
+ get me(): RoomPlayer;
163
+ /** Whether the player on this device is the room's host. */
164
+ get isHost(): boolean;
165
+ /** The id of the player whose turn it is, or `null`. */
166
+ get turn(): string | null;
167
+ /** Whether it is the turn of the player on this device. */
168
+ get isMyTurn(): boolean;
169
+ /**
170
+ * Whether the player on this device can change the state now: on their
171
+ * turn, or, when no one has the turn, if they are the host.
172
+ */
173
+ get canUpdate(): boolean;
174
+ /** The shared state every player receives, or `null` before anyone writes one. */
175
+ get state(): any;
176
+ /** This player's private state, which no other player receives, or `null`. */
177
+ get mine(): any;
178
+ /** The state's version, which grows with every change. */
179
+ get version(): number;
180
+ /** The round's seed, the same for every player, or `null` before the first round. */
181
+ get seed(): number | null;
182
+ /** When a competition's round, or a challenge, ends, or `null`. */
183
+ get deadline(): Date | null;
184
+ /** The fewest players who can start. */
185
+ get minPlayers(): number;
186
+ /** The most players the room seats. */
187
+ get maxPlayers(): number;
188
+ /** For an asynchronous challenge, how many hours it runs; otherwise `null`. */
189
+ get challengeHours(): number | null;
190
+ /**
191
+ * The results, best first: in a competition, the players who have finished
192
+ * so far; in a game by turns, the scores given to {@link Room.end}. `null`
193
+ * when there are none.
194
+ */
195
+ get results(): RoomResult[] | null;
196
+ /** Whether the player's connection to the room is up. It reconnects on its own. */
197
+ get connected(): boolean;
198
+ /** Whether the room has closed, or the player left it. */
199
+ get closed(): boolean;
200
+ /**
201
+ * A random number from 0 up to 1, from the round's seed: every player who
202
+ * calls it in the same order gets the same numbers, so they can deal the
203
+ * same deck or build the same level. It starts again at each round.
204
+ */
205
+ random(): number;
206
+ /** A shuffled copy of `items`, from the round's seed, as {@link Room.random} is. */
207
+ shuffle<T>(items: readonly T[]): T[];
208
+ /** The player with this id, or `null`. */
209
+ player(id: string): RoomPlayer | null;
210
+ /**
211
+ * Calls `listener` whenever the room changes: a player joins or leaves, the
212
+ * state or the turn changes, the phase moves on. It runs inside the game's
213
+ * loop. Returns a function that stops it.
214
+ */
215
+ onChange(listener: (room: Room) => void): () => void;
216
+ /**
217
+ * Calls `listener` for each message another player sends with
218
+ * {@link Room.send}. It runs inside the game's loop. Returns a function that
219
+ * stops it.
220
+ */
221
+ onMessage(listener: (kind: string, data: any, from: RoomPlayer | null) => void): () => void;
222
+ /**
223
+ * Calls `listener` once when the room closes, the player is removed, or the
224
+ * player leaves. `reason` is `"closed"`, `"removed"`, or `"left"`.
225
+ */
226
+ onClose(listener: (reason: string) => void): () => void;
227
+ /** Starts the game, as the host, once enough players are in. Every player's lobby closes. */
228
+ start(): Promise<void>;
229
+ /**
230
+ * Changes the room, on the player's turn or, when no one has the turn, as
231
+ * the host: the shared state, players' private states, and whose turn it
232
+ * is. It names the version it started from, and is refused with `stale` if
233
+ * another change came first.
234
+ *
235
+ * ```js
236
+ * await room.update({ state: { board }, turn: "next" });
237
+ * ```
238
+ */
239
+ update(changes: RoomUpdate): Promise<void>;
240
+ /**
241
+ * Ends a game by turns, on the player's turn or as the host. `scores`, by
242
+ * player id, become the results, ranked by the room's order; `state` is a
243
+ * last state to show, such as the final board.
244
+ */
245
+ end(scores?: Record<string, number>, state?: unknown): Promise<void>;
246
+ /**
247
+ * Reports the player's result in a competition's round. The round finishes
248
+ * when every player has reported, when its time limit passes, or when the
249
+ * host ends it.
250
+ */
251
+ finish(score: number): Promise<void>;
252
+ /** Ends a competition's round now, as the host, with the results reported so far. */
253
+ endRound(): Promise<void>;
254
+ /** Passes the turn to the next player, as the host, such as when its player dropped out. */
255
+ skipTurn(): Promise<void>;
256
+ /** Removes a player from the room, as the host. They cannot join it again. */
257
+ remove(playerId: string): Promise<void>;
258
+ /**
259
+ * Plays again, as the host, once the room has finished: the room goes back
260
+ * to its lobby with the same players, or, for a challenge, starts a new
261
+ * round at once.
262
+ */
263
+ rematch(): Promise<void>;
264
+ /**
265
+ * Sends a message to the other players, which the room passes on and does
266
+ * not keep, such as a reaction. `kind` is a short name; `data` is plain
267
+ * JSON, at most 1 KB.
268
+ */
269
+ send(kind: string, data?: unknown): Promise<void>;
270
+ /** Leaves the room. The others are told, and the turn and the host pass on. */
271
+ leave(): Promise<void>;
272
+ /** Resolves when the room's game starts, or at once if it is playing. */
273
+ waitForStart(): Promise<void>;
274
+ /**
275
+ * Shows the results over the game, in HTML, and keeps them up to date as
276
+ * players finish. The host can play again from it, or end a round still
277
+ * running. Resolves `"rematch"` when the next game starts, with the lobby
278
+ * shown while the players wait, or `"leave"` when the player leaves or the
279
+ * room closes.
280
+ */
281
+ showResults(options?: RoomResultsOptions): Promise<"rematch" | "leave">;
282
+ }
283
+ /**
284
+ * Multiplayer rooms for a game on Minimo Games: two to eight players, each on
285
+ * their own device, meet in a room by its code or its link, and play by turns
286
+ * on a small state the room keeps, or each play the same round of a
287
+ * competition and compare their scores. Nothing is synchronized frame by
288
+ * frame: it suits card games, board games, word games, and races against the
289
+ * clock, not action games.
290
+ *
291
+ * It works only inside Minimo Games, and only while the game's creator is on
292
+ * Minimo Games Pro. Players need no account: a guest types a name once.
293
+ *
294
+ * {@link Multiplayer.lobby} shows the lobby over the game, in HTML: creating a
295
+ * room, joining one by its code, sharing its link, and waiting for the others
296
+ * until the host starts. A player who opens the room's link lands in it.
297
+ *
298
+ * ```js
299
+ * import { Multiplayer } from "https://cdn.jsdelivr.net/npm/minimojs@<version>/dist/minimo-multiplayer.js";
300
+ *
301
+ * const mp = new Multiplayer(game, { mode: "turns", minPlayers: 2, maxPlayers: 4 });
302
+ * const room = await mp.lobby(); // resolves when the host starts
303
+ * if (room.isHost) await room.update({ state: deal(room.shuffle(deck)), turn: "next" });
304
+ * room.onChange(() => redraw(room.state, room.mine));
305
+ * // On the player's turn:
306
+ * if (room.isMyTurn) await room.update({ state: next, turn: "next" });
307
+ * // When someone wins:
308
+ * await room.end({ [winnerId]: 1 });
309
+ * const choice = await room.showResults(); // "rematch" or "leave"
310
+ * ```
311
+ *
312
+ * A game with its own welcome screen asks {@link Multiplayer.pendingRoom}
313
+ * first, so a player who opened a room's link, or reloaded the page in a
314
+ * room, goes straight to it instead of to the welcome screen:
315
+ *
316
+ * ```js
317
+ * const pending = await mp.pendingRoom();
318
+ * if (pending) room = await mp.lobby(); // that room's lobby, or its game in progress
319
+ * else showWelcome(); // Play online, then mp.lobby() to create or join
320
+ * ```
321
+ */
322
+ export declare class Multiplayer {
323
+ /**
324
+ * @param game - The game.
325
+ * @param options - How its rooms work, and how the lobby looks.
326
+ */
327
+ constructor(game: Game, options?: MultiplayerOptions);
328
+ /** The room the player is in, or `null`. */
329
+ get room(): Room | null;
330
+ /**
331
+ * Whether multiplayer can be played here: inside Minimo Games, with the
332
+ * game's creator on Pro.
333
+ */
334
+ available(): Promise<boolean>;
335
+ /** Creates a room, without the lobby. The player is its host. */
336
+ create(): Promise<Room>;
337
+ /** Joins a room by its code, without the lobby, or returns to one the player is in. */
338
+ join(code: string): Promise<Room>;
339
+ /**
340
+ * The room this player is expected in, before any screen is shown: the room
341
+ * whose link they opened, an invitation, or the room they were in on this
342
+ * device before the page reloaded. `null` on a normal visit, outside
343
+ * Minimo Games, and when the game's creator is not on Pro.
344
+ *
345
+ * It opens nothing, joins nothing, and can be asked again: the room stays
346
+ * pending until the player is in it. {@link Multiplayer.lobby} then takes
347
+ * them straight into it. A room that has closed since, as an old link's,
348
+ * is still returned, and the lobby then says there is no room with that
349
+ * code.
350
+ */
351
+ pendingRoom(): Promise<PendingRoom | null>;
352
+ /**
353
+ * The player's name on Minimo Games, the one leaderboards show, or `null`
354
+ * before they choose one.
355
+ */
356
+ playerName(): Promise<string | null>;
357
+ /**
358
+ * Shows the lobby over the game, in HTML, and resolves with the room when
359
+ * its game starts. The player chooses a name if they have none, creates a
360
+ * room or joins one by its code, shares its code and link with friends, and
361
+ * waits; the host starts once enough players are in. A player who opened the
362
+ * game from a room's link goes straight into that room, and one who reloads
363
+ * the page returns to theirs.
364
+ *
365
+ * Refused with `unavailable` outside Minimo Games or when the game's creator
366
+ * is not on Pro, after the lobby says so, and with `cancelled` when the
367
+ * player closes it.
368
+ */
369
+ lobby(): Promise<Room>;
370
+ /** Closes the lobby or the results, if showing. The room is kept. */
371
+ close(): void;
372
+ /** Closes the lobby and the strip. The player stays in their room; call {@link Room.leave} to leave it. */
373
+ destroy(): void;
374
+ }