minimojs 1.0.0-alpha.35 → 1.0.0-alpha.36

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,338 @@
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
+ /** Options for {@link Room.showResults}. */
104
+ export interface RoomResultsOptions {
105
+ /** A title instead of `"Results"`. */
106
+ title?: string;
107
+ /** How a score is written, such as `(s) => s.toFixed(2) + " s"`. Default: the number, with digit grouping. */
108
+ format?: (score: number) => string;
109
+ }
110
+ /**
111
+ * What a room promise is refused with. `reason` is one of:
112
+ * `unavailable` (not on Minimo Games, or the game's creator is not on Pro),
113
+ * `cancelled` (the player closed the lobby), `room_not_found`, `full`,
114
+ * `in_progress`, `removed`, `not_your_turn`, `stale` (another change came
115
+ * first; read the room again and retry), `not_host`, `wrong_phase`,
116
+ * `not_enough_players`, `state_too_large`, `closed`, or another the room
117
+ * names.
118
+ */
119
+ export declare class MultiplayerError extends Error {
120
+ readonly reason: string;
121
+ constructor(message: string, reason: string);
122
+ }
123
+ /**
124
+ * A room the player is in: its players, its phase, its shared state, the
125
+ * player's private state, and whose turn it is. Every field reads the room as
126
+ * the player last received it.
127
+ *
128
+ * Changes made by the other players arrive between frames, and the listeners
129
+ * given to {@link Room.onChange} and {@link Room.onMessage} are called inside
130
+ * the game's loop, before the scene's `onUpdate`, so a game never sees the room
131
+ * change halfway through a frame. A change the player makes themselves is in
132
+ * the room when its promise resolves.
133
+ *
134
+ * Get a room from {@link Multiplayer.lobby}, {@link Multiplayer.create}, or
135
+ * {@link Multiplayer.join}.
136
+ */
137
+ export declare class Room {
138
+ /** The room's code, such as `KQ7XA`, which friends type to join. */
139
+ get code(): string;
140
+ /** The address that opens the game straight into this room. */
141
+ get link(): string;
142
+ get mode(): RoomMode;
143
+ get phase(): RoomPhase;
144
+ /** Grows each time the room starts a new round: 1 for the first. */
145
+ get round(): number;
146
+ /** The players in the room, by seat. */
147
+ get players(): RoomPlayer[];
148
+ /** The player playing on this device. */
149
+ get me(): RoomPlayer;
150
+ /** Whether the player on this device is the room's host. */
151
+ get isHost(): boolean;
152
+ /** The id of the player whose turn it is, or `null`. */
153
+ get turn(): string | null;
154
+ /** Whether it is the turn of the player on this device. */
155
+ get isMyTurn(): boolean;
156
+ /**
157
+ * Whether the player on this device can change the state now: on their
158
+ * turn, or, when no one has the turn, if they are the host.
159
+ */
160
+ get canUpdate(): boolean;
161
+ /** The shared state every player receives, or `null` before anyone writes one. */
162
+ get state(): any;
163
+ /** This player's private state, which no other player receives, or `null`. */
164
+ get mine(): any;
165
+ /** The state's version, which grows with every change. */
166
+ get version(): number;
167
+ /** The round's seed, the same for every player, or `null` before the first round. */
168
+ get seed(): number | null;
169
+ /** When a competition's round, or a challenge, ends, or `null`. */
170
+ get deadline(): Date | null;
171
+ /** The fewest players who can start. */
172
+ get minPlayers(): number;
173
+ /** The most players the room seats. */
174
+ get maxPlayers(): number;
175
+ /** For an asynchronous challenge, how many hours it runs; otherwise `null`. */
176
+ get challengeHours(): number | null;
177
+ /**
178
+ * The results, best first: in a competition, the players who have finished
179
+ * so far; in a game by turns, the scores given to {@link Room.end}. `null`
180
+ * when there are none.
181
+ */
182
+ get results(): RoomResult[] | null;
183
+ /** Whether the player's connection to the room is up. It reconnects on its own. */
184
+ get connected(): boolean;
185
+ /** Whether the room has closed, or the player left it. */
186
+ get closed(): boolean;
187
+ /**
188
+ * A random number from 0 up to 1, from the round's seed: every player who
189
+ * calls it in the same order gets the same numbers, so they can deal the
190
+ * same deck or build the same level. It starts again at each round.
191
+ */
192
+ random(): number;
193
+ /** A shuffled copy of `items`, from the round's seed, as {@link Room.random} is. */
194
+ shuffle<T>(items: readonly T[]): T[];
195
+ /** The player with this id, or `null`. */
196
+ player(id: string): RoomPlayer | null;
197
+ /**
198
+ * Calls `listener` whenever the room changes: a player joins or leaves, the
199
+ * state or the turn changes, the phase moves on. It runs inside the game's
200
+ * loop. Returns a function that stops it.
201
+ */
202
+ onChange(listener: (room: Room) => void): () => void;
203
+ /**
204
+ * Calls `listener` for each message another player sends with
205
+ * {@link Room.send}. It runs inside the game's loop. Returns a function that
206
+ * stops it.
207
+ */
208
+ onMessage(listener: (kind: string, data: any, from: RoomPlayer | null) => void): () => void;
209
+ /**
210
+ * Calls `listener` once when the room closes, the player is removed, or the
211
+ * player leaves. `reason` is `"closed"`, `"removed"`, or `"left"`.
212
+ */
213
+ onClose(listener: (reason: string) => void): () => void;
214
+ /** Starts the game, as the host, once enough players are in. Every player's lobby closes. */
215
+ start(): Promise<void>;
216
+ /**
217
+ * Changes the room, on the player's turn or, when no one has the turn, as
218
+ * the host: the shared state, players' private states, and whose turn it
219
+ * is. It names the version it started from, and is refused with `stale` if
220
+ * another change came first.
221
+ *
222
+ * ```js
223
+ * await room.update({ state: { board }, turn: "next" });
224
+ * ```
225
+ */
226
+ update(changes: RoomUpdate): Promise<void>;
227
+ /**
228
+ * Ends a game by turns, on the player's turn or as the host. `scores`, by
229
+ * player id, become the results, ranked by the room's order; `state` is a
230
+ * last state to show, such as the final board.
231
+ */
232
+ end(scores?: Record<string, number>, state?: unknown): Promise<void>;
233
+ /**
234
+ * Reports the player's result in a competition's round. The round finishes
235
+ * when every player has reported, when its time limit passes, or when the
236
+ * host ends it.
237
+ */
238
+ finish(score: number): Promise<void>;
239
+ /** Ends a competition's round now, as the host, with the results reported so far. */
240
+ endRound(): Promise<void>;
241
+ /** Passes the turn to the next player, as the host, such as when its player dropped out. */
242
+ skipTurn(): Promise<void>;
243
+ /** Removes a player from the room, as the host. They cannot join it again. */
244
+ remove(playerId: string): Promise<void>;
245
+ /**
246
+ * Plays again, as the host, once the room has finished: the room goes back
247
+ * to its lobby with the same players, or, for a challenge, starts a new
248
+ * round at once.
249
+ */
250
+ rematch(): Promise<void>;
251
+ /**
252
+ * Sends a message to the other players, which the room passes on and does
253
+ * not keep, such as a reaction. `kind` is a short name; `data` is plain
254
+ * JSON, at most 1 KB.
255
+ */
256
+ send(kind: string, data?: unknown): Promise<void>;
257
+ /** Leaves the room. The others are told, and the turn and the host pass on. */
258
+ leave(): Promise<void>;
259
+ /** Resolves when the room's game starts, or at once if it is playing. */
260
+ waitForStart(): Promise<void>;
261
+ /**
262
+ * Shows the results over the game, in HTML, and keeps them up to date as
263
+ * players finish. The host can play again from it, or end a round still
264
+ * running. Resolves `"rematch"` when the next game starts, with the lobby
265
+ * shown while the players wait, or `"leave"` when the player leaves or the
266
+ * room closes.
267
+ */
268
+ showResults(options?: RoomResultsOptions): Promise<"rematch" | "leave">;
269
+ }
270
+ /**
271
+ * Multiplayer rooms for a game on Minimo Games: two to eight players, each on
272
+ * their own device, meet in a room by its code or its link, and play by turns
273
+ * on a small state the room keeps, or each play the same round of a
274
+ * competition and compare their scores. Nothing is synchronized frame by
275
+ * frame: it suits card games, board games, word games, and races against the
276
+ * clock, not action games.
277
+ *
278
+ * It works only inside Minimo Games, and only while the game's creator is on
279
+ * Minimo Games Pro. Players need no account: a guest types a name once.
280
+ *
281
+ * {@link Multiplayer.lobby} shows the lobby over the game, in HTML: creating a
282
+ * room, joining one by its code, sharing its link, and waiting for the others
283
+ * until the host starts. A player who opens the room's link lands in it.
284
+ *
285
+ * ```js
286
+ * import { Multiplayer } from "https://cdn.jsdelivr.net/npm/minimojs@<version>/dist/minimo-multiplayer.js";
287
+ *
288
+ * const mp = new Multiplayer(game, { mode: "turns", minPlayers: 2, maxPlayers: 4 });
289
+ * const room = await mp.lobby(); // resolves when the host starts
290
+ * if (room.isHost) await room.update({ state: deal(room.shuffle(deck)), turn: "next" });
291
+ * room.onChange(() => redraw(room.state, room.mine));
292
+ * // On the player's turn:
293
+ * if (room.isMyTurn) await room.update({ state: next, turn: "next" });
294
+ * // When someone wins:
295
+ * await room.end({ [winnerId]: 1 });
296
+ * const choice = await room.showResults(); // "rematch" or "leave"
297
+ * ```
298
+ */
299
+ export declare class Multiplayer {
300
+ /**
301
+ * @param game - The game.
302
+ * @param options - How its rooms work, and how the lobby looks.
303
+ */
304
+ constructor(game: Game, options?: MultiplayerOptions);
305
+ /** The room the player is in, or `null`. */
306
+ get room(): Room | null;
307
+ /**
308
+ * Whether multiplayer can be played here: inside Minimo Games, with the
309
+ * game's creator on Pro.
310
+ */
311
+ available(): Promise<boolean>;
312
+ /** Creates a room, without the lobby. The player is its host. */
313
+ create(): Promise<Room>;
314
+ /** Joins a room by its code, without the lobby, or returns to one the player is in. */
315
+ join(code: string): Promise<Room>;
316
+ /**
317
+ * The player's name on Minimo Games, the one leaderboards show, or `null`
318
+ * before they choose one.
319
+ */
320
+ playerName(): Promise<string | null>;
321
+ /**
322
+ * Shows the lobby over the game, in HTML, and resolves with the room when
323
+ * its game starts. The player chooses a name if they have none, creates a
324
+ * room or joins one by its code, shares its code and link with friends, and
325
+ * waits; the host starts once enough players are in. A player who opened the
326
+ * game from a room's link goes straight into that room, and one who reloads
327
+ * the page returns to theirs.
328
+ *
329
+ * Refused with `unavailable` outside Minimo Games or when the game's creator
330
+ * is not on Pro, after the lobby says so, and with `cancelled` when the
331
+ * player closes it.
332
+ */
333
+ lobby(): Promise<Room>;
334
+ /** Closes the lobby or the results, if showing. The room is kept. */
335
+ close(): void;
336
+ /** Closes the lobby and the strip. The player stays in their room; call {@link Room.leave} to leave it. */
337
+ destroy(): void;
338
+ }