@sodiumlabs/gamecord 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,2561 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/games/2048.ts
5
+ import z2 from "zod/v4";
6
+ import { createCanvas } from "@napi-rs/canvas";
7
+ import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from "discord.js";
8
+
9
+ // src/core/Game.ts
10
+ import { EventEmitter } from "events";
11
+ import {
12
+ Message
13
+ } from "discord.js";
14
+ var Game = class extends EventEmitter {
15
+ static {
16
+ __name(this, "Game");
17
+ }
18
+ /**
19
+ * The initial context that started this game, either an interaction or a message.
20
+ */
21
+ context;
22
+ /**
23
+ * If the {@link Game#context} is a `Message` or not.
24
+ */
25
+ isMessage;
26
+ /**
27
+ * The player that started the game.
28
+ */
29
+ player;
30
+ /**
31
+ * The timestamp at which the class was instantiated. Not when {@link Game#start} was called.
32
+ */
33
+ gameStartedAt;
34
+ constructor(context) {
35
+ super();
36
+ this.context = context;
37
+ const isMessage = context instanceof Message;
38
+ this.isMessage = isMessage;
39
+ this.player = isMessage ? context.author : context.user;
40
+ this.gameStartedAt = Date.now();
41
+ if (isMessage && !context.channel.isSendable()) {
42
+ throw new Error("The context channel must be a sendable channel");
43
+ }
44
+ }
45
+ /**
46
+ * Returns the current duration of the game.
47
+ */
48
+ getGameDuration() {
49
+ return Date.now() - this.gameStartedAt;
50
+ }
51
+ async sendMessage(options) {
52
+ if (this.isMessage) {
53
+ const message = this.context;
54
+ const channel = message.channel;
55
+ return await channel.send(options);
56
+ } else {
57
+ const interaction = this.context;
58
+ if (interaction.deferred || interaction.replied) {
59
+ return await interaction.editReply(options);
60
+ } else {
61
+ const result = await interaction.reply({ ...options, withResponse: true });
62
+ return result.resource.message;
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Edit the context message if it was an interaction,
68
+ * or edit the given message.
69
+ *
70
+ * Since the original context is only valid 15 minutes,
71
+ * we need the message reference if the interaction died.
72
+ */
73
+ async editContextOrMessage(message, options) {
74
+ if (!this.isMessage) {
75
+ const interaction = this.context;
76
+ if (Date.now() - interaction.createdTimestamp < 6e4 * 15) {
77
+ return await interaction.editReply(options);
78
+ }
79
+ }
80
+ return await message.edit(options);
81
+ }
82
+ /**
83
+ * Utility to build an embed from the options.
84
+ */
85
+ async buildEmbed(embed, props) {
86
+ return typeof embed === "function" ? await embed(this) : {
87
+ author: {
88
+ name: this.player.displayName,
89
+ icon_url: this.player.displayAvatarURL()
90
+ },
91
+ ...props,
92
+ ...embed
93
+ };
94
+ }
95
+ /**
96
+ * Utility to build an end embed from the options.
97
+ */
98
+ async buildEndEmbed(embed, endEmbed, result, props) {
99
+ let built;
100
+ if (endEmbed) {
101
+ built = typeof endEmbed === "function" ? await endEmbed(this, result) : endEmbed;
102
+ } else {
103
+ built = typeof embed === "function" ? await embed(this) : {
104
+ author: {
105
+ name: this.player.displayName,
106
+ icon_url: this.player.displayAvatarURL()
107
+ },
108
+ ...embed,
109
+ ...props
110
+ };
111
+ }
112
+ return built;
113
+ }
114
+ /**
115
+ * Utility to build the result of a game.
116
+ */
117
+ result(data) {
118
+ return {
119
+ player: this.player,
120
+ gameStartedAt: this.gameStartedAt,
121
+ gameDuration: this.getGameDuration(),
122
+ ...data
123
+ };
124
+ }
125
+ };
126
+
127
+ // src/utils/games.ts
128
+ import assert from "assert";
129
+ var numberEmojis = ["0\uFE0F\u20E3", "1\uFE0F\u20E3", "2\uFE0F\u20E3", "3\uFE0F\u20E3", "4\uFE0F\u20E3", "5\uFE0F\u20E3", "6\uFE0F\u20E3", "7\uFE0F\u20E3", "8\uFE0F\u20E3", "9\uFE0F\u20E3", "\u{1F51F}"];
130
+ var getNumberEmoji = /* @__PURE__ */ __name((number) => {
131
+ assert(number >= 0 && number <= 9);
132
+ return numberEmojis[number];
133
+ }, "getNumberEmoji");
134
+ var removeEmoji = /* @__PURE__ */ __name((button) => {
135
+ if ("emoji" in button.data) {
136
+ button.data.emoji = void 0;
137
+ }
138
+ return button;
139
+ }, "removeEmoji");
140
+ var moveInDirection = /* @__PURE__ */ __name((pos, direction) => {
141
+ if (direction === "up") return { x: pos.x, y: pos.y - 1 };
142
+ if (direction === "down") return { x: pos.x, y: pos.y + 1 };
143
+ if (direction === "left") return { x: pos.x - 1, y: pos.y };
144
+ if (direction === "right") return { x: pos.x + 1, y: pos.y };
145
+ return pos;
146
+ }, "moveInDirection");
147
+ var getOppositeDirection = /* @__PURE__ */ __name((direction) => {
148
+ if (direction === "up") return "down";
149
+ if (direction === "down") return "up";
150
+ if (direction === "left") return "right";
151
+ return "left";
152
+ }, "getOppositeDirection");
153
+
154
+ // src/utils/schemas.ts
155
+ import z from "zod/v4";
156
+ var apiEmbed = /* @__PURE__ */ __name(() => z.object({
157
+ author: z.object({
158
+ name: z.string(),
159
+ url: z.string().optional(),
160
+ icon_url: z.string().optional()
161
+ }),
162
+ thumbnail: z.object({
163
+ url: z.string()
164
+ }),
165
+ title: z.string(),
166
+ color: z.union([
167
+ z.string().transform((val) => {
168
+ return parseInt(val.slice(1), 16);
169
+ }),
170
+ z.number().int()
171
+ ]),
172
+ description: z.string(),
173
+ image: z.object({
174
+ url: z.string()
175
+ }),
176
+ footer: z.object({
177
+ text: z.string(),
178
+ icon_url: z.string().optional()
179
+ })
180
+ }).partial(), "apiEmbed");
181
+ var functionable = /* @__PURE__ */ __name((schema) => () => z.union([schema, z.custom()]), "functionable");
182
+ var embedBuilder = /* @__PURE__ */ __name(() => functionable(apiEmbed())(), "embedBuilder");
183
+ var gameMessage = /* @__PURE__ */ __name(() => z.union([z.string().transform((val) => () => val), z.custom()]), "gameMessage");
184
+ var resultMessage = /* @__PURE__ */ __name(() => z.union([z.string().transform((val) => () => val), z.custom()]), "resultMessage");
185
+ var var2Message = /* @__PURE__ */ __name(() => z.union([z.string().transform((val) => () => val), z.custom()]), "var2Message");
186
+
187
+ // src/utils/constants.ts
188
+ var colors = {
189
+ green: 2600544,
190
+ red: 15158332,
191
+ yellow: 16236389,
192
+ blue: 2719929,
193
+ blurple: 5793266
194
+ };
195
+
196
+ // src/utils/random.ts
197
+ var getRandomInt = /* @__PURE__ */ __name((max, min = 0) => {
198
+ if (min >= max) throw new RangeError("`min` should be less than `max`");
199
+ return Math.floor(Math.random() * (min ? max + 1 - min : max + 1)) + min;
200
+ }, "getRandomInt");
201
+ var getRandomElement = /* @__PURE__ */ __name((array) => {
202
+ if (!array.length) throw new RangeError("The array should not be empty");
203
+ return array[Math.floor(Math.random() * array.length)];
204
+ }, "getRandomElement");
205
+ var shuffleArray = /* @__PURE__ */ __name((array) => {
206
+ for (let i = array.length - 1; i > 0; i--) {
207
+ const j = Math.floor(Math.random() * (i + 1));
208
+ [array[i], array[j]] = [array[j], array[i]];
209
+ }
210
+ return array;
211
+ }, "shuffleArray");
212
+
213
+ // src/games/2048.ts
214
+ var defaultOptions = {
215
+ embed: {
216
+ title: "2048",
217
+ color: colors.blurple
218
+ },
219
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} can use this menu.`, "notPlayerMessage"),
220
+ timeout: 6e4,
221
+ buttonStyle: ButtonStyle.Secondary,
222
+ emojis: {
223
+ up: "\u{1F53C}",
224
+ down: "\u{1F53D}",
225
+ right: "\u25B6\uFE0F",
226
+ left: "\u25C0\uFE0F"
227
+ }
228
+ };
229
+ var game2048Options = z2.object({
230
+ embed: embedBuilder().optional().default(defaultOptions.embed),
231
+ endEmbed: embedBuilder().optional(),
232
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions.notPlayerMessage),
233
+ timeout: z2.number().int().optional().default(defaultOptions.timeout),
234
+ buttonStyle: z2.number().int().optional().default(ButtonStyle.Secondary),
235
+ emojis: z2.object({
236
+ up: z2.string().optional().default(defaultOptions.emojis.up),
237
+ down: z2.string().optional().default(defaultOptions.emojis.down),
238
+ right: z2.string().optional().default(defaultOptions.emojis.right),
239
+ left: z2.string().optional().default(defaultOptions.emojis.left)
240
+ }).optional().default(defaultOptions.emojis)
241
+ });
242
+ var Game2048 = class extends Game {
243
+ static {
244
+ __name(this, "Game2048");
245
+ }
246
+ options;
247
+ /**
248
+ * The numbers are stored as exponents (1 => 2, 2 => 4, etc.).
249
+ */
250
+ gameboard = [];
251
+ length = 4;
252
+ score = 0;
253
+ message = null;
254
+ mergedPos = [];
255
+ constructor(context, options) {
256
+ super(context);
257
+ this.options = game2048Options.parse(options || {});
258
+ for (let y = 0; y < this.length; y++) {
259
+ for (let x = 0; x < this.length; x++) {
260
+ this.gameboard[y * this.length + x] = 0;
261
+ }
262
+ }
263
+ }
264
+ async start() {
265
+ this.placeRandomTile();
266
+ this.placeRandomTile();
267
+ const embed = await this.buildEmbed(this.options.embed, {
268
+ image: {
269
+ url: "attachment://board.png"
270
+ },
271
+ fields: [
272
+ {
273
+ name: "Current Score",
274
+ value: this.score.toString()
275
+ }
276
+ ]
277
+ });
278
+ this.message = await this.sendMessage({
279
+ embeds: [embed],
280
+ components: this.getComponents(),
281
+ files: [await this.getBoardAttachment()]
282
+ });
283
+ this.handleButtons(this.message);
284
+ }
285
+ handleButtons(message) {
286
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
287
+ collector.on("collect", async (i) => {
288
+ if (!i.customId.startsWith("$gamecord")) return;
289
+ if (i.user.id !== this.player.id) {
290
+ if (this.options.notPlayerMessage) {
291
+ try {
292
+ await i.reply({
293
+ content: this.options.notPlayerMessage(this),
294
+ flags: MessageFlags.Ephemeral
295
+ });
296
+ } catch (err) {
297
+ this.emit("error", err);
298
+ }
299
+ }
300
+ return;
301
+ }
302
+ let moved = false;
303
+ this.mergedPos = [];
304
+ const direction = i.customId.split("-").at(-1);
305
+ try {
306
+ await i.deferUpdate();
307
+ } catch (err) {
308
+ this.emit("error", err);
309
+ return;
310
+ }
311
+ if (direction === "up" || direction === "down") moved = this.shiftVertical(direction);
312
+ if (direction === "left" || direction === "right") moved = this.shiftHorizontal(direction);
313
+ if (moved) this.placeRandomTile();
314
+ if (this.isGameOver()) return collector.stop("$over");
315
+ const embed = await this.buildEmbed(this.options.embed, {
316
+ image: {
317
+ url: "attachment://board.png"
318
+ },
319
+ fields: [
320
+ {
321
+ name: "Current Score",
322
+ value: this.score.toString()
323
+ }
324
+ ]
325
+ });
326
+ try {
327
+ await i.editReply({ embeds: [embed], files: [await this.getBoardAttachment()] });
328
+ } catch (err) {
329
+ this.emit("error", err);
330
+ }
331
+ });
332
+ collector.on("end", async (_, reason) => {
333
+ this.emit("end");
334
+ if (reason === "idle" || reason === "$over") {
335
+ await this.gameOver(message, reason === "$over" ? "over" : "timeout");
336
+ }
337
+ });
338
+ }
339
+ async gameOver(message, outcome) {
340
+ const result = this.result({ outcome, score: this.score, hasWon: !!this.gameboard.find((n) => n >= 11) });
341
+ this.emit("gameOver", result);
342
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
343
+ image: {
344
+ url: "attachment://board.png"
345
+ },
346
+ fields: [
347
+ {
348
+ name: "Total Score",
349
+ value: this.score.toString()
350
+ }
351
+ ]
352
+ });
353
+ try {
354
+ await this.editContextOrMessage(message, {
355
+ embeds: [embed],
356
+ components: this.getComponents(true),
357
+ files: [await this.getBoardAttachment()]
358
+ });
359
+ } catch (err) {
360
+ this.emit("error", err);
361
+ }
362
+ }
363
+ isGameOver() {
364
+ let boardFull = true;
365
+ let numMoves = 0;
366
+ for (let y = 0; y < this.length; y++) {
367
+ for (let x = 0; x < this.length; x++) {
368
+ if (this.gameboard[y * this.length + x] === 0) boardFull = false;
369
+ const posNum = this.gameboard[y * this.length + x];
370
+ for (const dir of ["down", "left", "right", "up"]) {
371
+ const newPos = moveInDirection({ x, y }, dir);
372
+ if (this.isInsideBlock(newPos) && (this.gameboard[newPos.y * this.length + newPos.x] === 0 || this.gameboard[newPos.y * this.length + newPos.x] === posNum)) {
373
+ numMoves++;
374
+ }
375
+ }
376
+ }
377
+ }
378
+ return boardFull && numMoves === 0;
379
+ }
380
+ placeRandomTile() {
381
+ let tilePos = { x: 0, y: 0 };
382
+ do {
383
+ tilePos = { x: getRandomInt(this.length), y: getRandomInt(this.length) };
384
+ } while (this.gameboard[tilePos.y * this.length + tilePos.x] != 0);
385
+ this.gameboard[tilePos.y * this.length + tilePos.x] = Math.random() > 0.8 ? 2 : 1;
386
+ }
387
+ /**
388
+ * @returns Has moved
389
+ */
390
+ shiftVertical(dir) {
391
+ let moved = false;
392
+ for (let x = 0; x < this.length; x++) {
393
+ if (dir === "up") {
394
+ for (let y = 1; y < this.length; y++) moved = this.shift({ x, y }, "up") || moved;
395
+ } else {
396
+ for (let y = this.length - 2; y >= 0; y--) moved = this.shift({ x, y }, "down") || moved;
397
+ }
398
+ }
399
+ return moved;
400
+ }
401
+ /**
402
+ * @returns Has moved
403
+ */
404
+ shiftHorizontal(dir) {
405
+ let moved = false;
406
+ for (let y = 0; y < this.length; y++) {
407
+ if (dir === "left") {
408
+ for (let x = 1; x < this.length; x++) {
409
+ moved = this.shift({ x, y }, "left") || moved;
410
+ }
411
+ } else {
412
+ for (let x = this.length - 2; x >= 0; x--) {
413
+ moved = this.shift({ x, y }, "right") || moved;
414
+ }
415
+ }
416
+ }
417
+ return moved;
418
+ }
419
+ isInsideBlock(pos) {
420
+ return pos.x >= 0 && pos.y >= 0 && pos.x < this.length && pos.y < this.length;
421
+ }
422
+ /**
423
+ * @returns Has moved
424
+ */
425
+ shift(pos, dir) {
426
+ let moved = false;
427
+ const movingTile = this.gameboard[pos.y * this.length + pos.x];
428
+ if (movingTile === 0) return false;
429
+ let set = false;
430
+ let moveTo = pos;
431
+ while (!set) {
432
+ moveTo = moveInDirection(moveTo, dir);
433
+ const moveToTile = this.gameboard[moveTo.y * this.length + moveTo.x];
434
+ if (!this.isInsideBlock(moveTo) || moveToTile !== 0 && moveToTile !== movingTile || !!this.mergedPos.find((p) => p.x === moveTo.x && p.y === moveTo.y)) {
435
+ const moveBack = moveInDirection(moveTo, getOppositeDirection(dir));
436
+ if (!(moveBack.x === pos.x && moveBack.y === pos.y)) {
437
+ this.gameboard[pos.y * this.length + pos.x] = 0;
438
+ this.gameboard[moveBack.y * this.length + moveBack.x] = movingTile;
439
+ moved = true;
440
+ }
441
+ set = true;
442
+ } else if (moveToTile === movingTile) {
443
+ moved = true;
444
+ this.gameboard[moveTo.y * this.length + moveTo.x] += 1;
445
+ this.score += Math.floor(Math.pow(this.gameboard[moveTo.y * this.length + moveTo.x], 2));
446
+ this.gameboard[pos.y * this.length + pos.x] = 0;
447
+ this.mergedPos.push(moveTo);
448
+ set = true;
449
+ }
450
+ }
451
+ return moved;
452
+ }
453
+ getComponents(disabled = false) {
454
+ const row1 = new ActionRowBuilder().setComponents(
455
+ new ButtonBuilder().setDisabled(true).setLabel("\u200B").setStyle(this.options.buttonStyle).setCustomId("$gamecord-2048-none1"),
456
+ new ButtonBuilder().setDisabled(disabled).setEmoji(this.options.emojis.up).setStyle(this.options.buttonStyle).setCustomId("$gamecord-2048-up"),
457
+ new ButtonBuilder().setDisabled(true).setLabel("\u200B").setStyle(this.options.buttonStyle).setCustomId("$gamecord-2048-none2")
458
+ );
459
+ const row2 = new ActionRowBuilder().setComponents(
460
+ new ButtonBuilder().setDisabled(disabled).setEmoji(this.options.emojis.left).setStyle(this.options.buttonStyle).setCustomId("$gamecord-2048-left"),
461
+ new ButtonBuilder().setDisabled(disabled).setEmoji(this.options.emojis.down).setStyle(this.options.buttonStyle).setCustomId("$gamecord-2048-down"),
462
+ new ButtonBuilder().setDisabled(disabled).setEmoji(this.options.emojis.right).setStyle(this.options.buttonStyle).setCustomId("$gamecord-2048-right")
463
+ );
464
+ return [row1, row2];
465
+ }
466
+ async getBoardAttachment() {
467
+ return new AttachmentBuilder(await this.getBoardImage(), { name: "board.png" });
468
+ }
469
+ async getBoardImage() {
470
+ const rows = this.length;
471
+ const cols = this.length;
472
+ const tile = 88;
473
+ const gap = 12;
474
+ const padding = 20;
475
+ const width = padding * 2 + cols * tile + (cols - 1) * gap;
476
+ const height = padding * 2 + rows * tile + (rows - 1) * gap;
477
+ const canvas = createCanvas(width, height);
478
+ const ctx = canvas.getContext("2d");
479
+ const PALETTE = {
480
+ bg: "#faf8ef",
481
+ board: "#bbada0",
482
+ empty: "#cdc1b4",
483
+ textDark: "#776e65",
484
+ textLight: "#f9f6f2",
485
+ tiles: /* @__PURE__ */ new Map([
486
+ [2, "#eee4da"],
487
+ [4, "#ede0c8"],
488
+ [8, "#f2b179"],
489
+ [16, "#f59563"],
490
+ [32, "#f67c5f"],
491
+ [64, "#f65e3b"],
492
+ [128, "#edcf72"],
493
+ [256, "#edcc61"],
494
+ [512, "#edc850"],
495
+ [1024, "#edc53f"],
496
+ [2048, "#edc22e"]
497
+ ])
498
+ };
499
+ ctx.fillStyle = PALETTE.bg;
500
+ ctx.fillRect(0, 0, width, height);
501
+ const boardRadius = 8;
502
+ const boardX = padding - 8;
503
+ const boardY = padding - 8;
504
+ const boardWidth = cols * tile + (cols - 1) * gap + 16;
505
+ const boardHeight = rows * tile + (rows - 1) * gap + 16;
506
+ function roundRect(x, y, w, h, r) {
507
+ ctx.beginPath();
508
+ ctx.moveTo(x + r, y);
509
+ ctx.lineTo(x + w - r, y);
510
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
511
+ ctx.lineTo(x + w, y + h - r);
512
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
513
+ ctx.lineTo(x + r, y + h);
514
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
515
+ ctx.lineTo(x, y + r);
516
+ ctx.quadraticCurveTo(x, y, x + r, y);
517
+ ctx.closePath();
518
+ }
519
+ __name(roundRect, "roundRect");
520
+ ctx.fillStyle = PALETTE.board;
521
+ roundRect(boardX, boardY, boardWidth, boardHeight, boardRadius);
522
+ ctx.fill();
523
+ ctx.textAlign = "center";
524
+ ctx.textBaseline = "middle";
525
+ for (let r = 0; r < rows; r++) {
526
+ for (let c = 0; c < cols; c++) {
527
+ const x = padding + c * (tile + gap);
528
+ const y = padding + r * (tile + gap);
529
+ const exponent = this.gameboard[r * this.length + c] ?? 0;
530
+ const value = exponent > 0 ? 2 ** exponent : 0;
531
+ const bgColor = value === 0 ? PALETTE.empty : PALETTE.tiles.get(value) ?? "#3c3a32";
532
+ const radius = 6;
533
+ ctx.fillStyle = bgColor;
534
+ roundRect(x, y, tile, tile, radius);
535
+ ctx.fill();
536
+ ctx.lineWidth = 2;
537
+ ctx.strokeStyle = value === 0 ? "rgba(0,0,0,0.06)" : "rgba(0,0,0,0.08)";
538
+ ctx.stroke();
539
+ if (value !== 0) {
540
+ const digits = value.toString().length;
541
+ let fontSize = Math.floor(tile * 0.54);
542
+ if (digits >= 3) fontSize = Math.floor(tile * 0.46);
543
+ if (digits >= 4) fontSize = Math.floor(tile * 0.36);
544
+ ctx.font = `700 ${fontSize}px sans-serif`;
545
+ ctx.fillStyle = value <= 4 ? PALETTE.textDark : PALETTE.textLight;
546
+ ctx.fillText(value.toString(), x + tile / 2, y + tile / 2 + 2);
547
+ }
548
+ }
549
+ }
550
+ return canvas.toBuffer("image/png");
551
+ }
552
+ };
553
+
554
+ // src/games/Connect4.ts
555
+ import z4 from "zod/v4";
556
+ import { ActionRowBuilder as ActionRowBuilder3, ButtonBuilder as ButtonBuilder3, ButtonStyle as ButtonStyle3, MessageFlags as MessageFlags2 } from "discord.js";
557
+
558
+ // src/core/VersusGame.ts
559
+ import z3 from "zod";
560
+ import { ActionRowBuilder as ActionRowBuilder2, ButtonBuilder as ButtonBuilder2, ButtonStyle as ButtonStyle2, User as User2 } from "discord.js";
561
+ var defaultOptions2 = {
562
+ timeout: 6e4,
563
+ requestEmbed: /* @__PURE__ */ __name((name) => ({ player }) => ({
564
+ color: colors.blue,
565
+ description: `${player} has invited you for a round of **${name}**.`
566
+ }), "requestEmbed"),
567
+ rejectEmbed: /* @__PURE__ */ __name((name) => ({ opponent }) => ({
568
+ color: colors.red,
569
+ description: `The player ${opponent} denied your request for a round of **${name}**.`
570
+ }), "rejectEmbed"),
571
+ timeoutEmbed: /* @__PURE__ */ __name(({ opponent }) => ({
572
+ color: colors.yellow,
573
+ description: `Dropped the game as the player ${opponent} did not respond.`
574
+ }), "timeoutEmbed"),
575
+ buttons: {
576
+ accept: "Accept",
577
+ reject: "Reject"
578
+ }
579
+ };
580
+ var versusOptions = /* @__PURE__ */ __name((name) => z3.object({
581
+ opponent: z3.custom((val) => val instanceof User2, {
582
+ error: "The opponent must be an instance of a discord.js User"
583
+ }),
584
+ timeout: z3.number().int().optional().default(defaultOptions2.timeout),
585
+ requestEmbed: z3.custom().optional().default(() => defaultOptions2.requestEmbed(name)),
586
+ rejectEmbed: z3.custom().optional().default(() => defaultOptions2.rejectEmbed(name)),
587
+ timeoutEmbed: z3.custom().optional().default(() => defaultOptions2.timeoutEmbed),
588
+ buttons: z3.object({
589
+ accept: z3.string().optional().default(defaultOptions2.buttons.accept),
590
+ reject: z3.string().optional().default(defaultOptions2.buttons.reject)
591
+ }).optional().default(defaultOptions2.buttons)
592
+ }), "versusOptions");
593
+ var VersusGame = class extends Game {
594
+ static {
595
+ __name(this, "VersusGame");
596
+ }
597
+ versusOptions;
598
+ /**
599
+ * The opponent user.
600
+ */
601
+ opponent;
602
+ constructor(context, options) {
603
+ super(context);
604
+ this.versusOptions = options;
605
+ this.opponent = options.opponent;
606
+ }
607
+ async requestVersus() {
608
+ const players = { player: this.player, opponent: this.opponent };
609
+ return new Promise(async (resolve) => {
610
+ const row = new ActionRowBuilder2().setComponents(
611
+ new ButtonBuilder2().setLabel(this.versusOptions.buttons.accept).setCustomId("$gamecord-versus-accept").setStyle(ButtonStyle2.Success),
612
+ new ButtonBuilder2().setLabel(this.versusOptions.buttons.reject).setCustomId("$gamecord-versus-reject").setStyle(ButtonStyle2.Danger)
613
+ );
614
+ const msg = await this.sendMessage({
615
+ content: this.opponent.toString(),
616
+ embeds: [this.versusOptions.requestEmbed(players)],
617
+ components: [row],
618
+ allowedMentions: { parse: ["users"] }
619
+ });
620
+ const collector = msg.createMessageComponentCollector({ time: this.versusOptions.timeout });
621
+ collector.on("collect", async (i) => {
622
+ try {
623
+ await i.deferUpdate();
624
+ } catch (err) {
625
+ this.emit("error", err);
626
+ }
627
+ if (i.user.id === this.opponent.id) {
628
+ collector.stop(i.customId.split("-").at(-1));
629
+ }
630
+ });
631
+ collector.on("end", async (_, reason) => {
632
+ if (reason === "accept") return resolve(msg);
633
+ const isTime = reason === "time";
634
+ const embed = isTime ? this.versusOptions.timeoutEmbed : this.versusOptions.rejectEmbed;
635
+ this.emit("versusReject", isTime);
636
+ this.emit("end");
637
+ try {
638
+ for (const btn of row.components) {
639
+ btn.setDisabled(true);
640
+ }
641
+ await this.editContextOrMessage(msg, {
642
+ embeds: [embed(players)],
643
+ components: [row]
644
+ });
645
+ } catch (err) {
646
+ this.emit("error", err);
647
+ }
648
+ return resolve(null);
649
+ });
650
+ });
651
+ }
652
+ /**
653
+ * Utility to build an embed from the options.
654
+ */
655
+ async buildEmbed(embed, props) {
656
+ return await super.buildEmbed(embed, {
657
+ author: void 0,
658
+ footer: {
659
+ text: `${this.player.displayName} vs ${this.opponent.displayName}`
660
+ },
661
+ ...props
662
+ });
663
+ }
664
+ /**
665
+ * Utility to build an end embed from the options.
666
+ */
667
+ async buildEndEmbed(embed, endEmbed, result, props) {
668
+ return await super.buildEndEmbed(embed, endEmbed, result, {
669
+ author: void 0,
670
+ footer: {
671
+ text: `${this.player.displayName} vs ${this.opponent.displayName}`
672
+ },
673
+ ...props
674
+ });
675
+ }
676
+ };
677
+
678
+ // src/games/Connect4.ts
679
+ var defaultOptions3 = {
680
+ embed: {
681
+ title: "Connect 4",
682
+ color: colors.blurple
683
+ },
684
+ winMessage: /* @__PURE__ */ __name((res) => `${res.winnerEmoji} | **${res.winner}** won the TicTacToe Game.`, "winMessage"),
685
+ tieMessage: /* @__PURE__ */ __name(() => "The Game tied! No one won the Game!", "tieMessage"),
686
+ timeoutMessage: /* @__PURE__ */ __name(() => "The Game went unfinished! No one won the Game!", "timeoutMessage"),
687
+ turnMessage: /* @__PURE__ */ __name((turn) => `${turn.emoji} | It's player **${turn.player.displayName}**'s turn.`, "turnMessage"),
688
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} and ${game.opponent} can use this menu.`, "notPlayerMessage"),
689
+ statusText: "Game Status",
690
+ emojis: {
691
+ board: "\u26AB",
692
+ player1: "\u{1F534}",
693
+ player2: "\u{1F7E1}"
694
+ },
695
+ buttonStyle: ButtonStyle3.Secondary,
696
+ timeout: 6e4
697
+ };
698
+ var connect4Options = z4.object({
699
+ versus: versusOptions("Connect 4"),
700
+ embed: embedBuilder().optional().default(defaultOptions3.embed),
701
+ endEmbed: embedBuilder().optional(),
702
+ winMessage: resultMessage().optional().default(() => defaultOptions3.winMessage),
703
+ tieMessage: resultMessage().optional().default(() => defaultOptions3.tieMessage),
704
+ timeoutMessage: resultMessage().optional().default(() => defaultOptions3.timeoutMessage),
705
+ turnMessage: var2Message().optional().default(() => defaultOptions3.turnMessage),
706
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions3.notPlayerMessage),
707
+ statusText: z4.string().optional().default(defaultOptions3.statusText),
708
+ emojis: z4.object({
709
+ board: z4.string().optional().default(defaultOptions3.emojis.board),
710
+ player1: z4.string().optional().default(defaultOptions3.emojis.player1),
711
+ player2: z4.string().optional().default(defaultOptions3.emojis.player2)
712
+ }).optional().default(defaultOptions3.emojis),
713
+ buttonStyle: z4.number().int().optional().default(defaultOptions3.buttonStyle),
714
+ timeout: z4.number().int().optional().default(defaultOptions3.timeout)
715
+ });
716
+ var Connect4 = class extends VersusGame {
717
+ static {
718
+ __name(this, "Connect4");
719
+ }
720
+ options;
721
+ /**
722
+ * A 6*7 elements array.
723
+ *
724
+ * - `0` : empty
725
+ * - `1` : player 1
726
+ * - `2` : player 2
727
+ */
728
+ gameboard = [];
729
+ isPlayer1Turn = true;
730
+ message = null;
731
+ locked = false;
732
+ constructor(context, options) {
733
+ const parsed = connect4Options.parse(options || {});
734
+ super(context, parsed.versus);
735
+ this.options = parsed;
736
+ for (let y = 0; y < 6; y++) {
737
+ for (let x = 0; x < 7; x++) {
738
+ this.gameboard[y * 7 + x] = 0;
739
+ }
740
+ }
741
+ }
742
+ async start() {
743
+ this.message = await this.requestVersus();
744
+ if (this.message) this.runGame(this.message);
745
+ }
746
+ async runGame(message) {
747
+ try {
748
+ const embed = await this.buildEmbed(this.options.embed, {
749
+ description: this.getBoardContent(),
750
+ fields: [
751
+ {
752
+ name: this.options.statusText,
753
+ value: this.options.turnMessage(this.getPlayerTurnData(), this)
754
+ }
755
+ ]
756
+ });
757
+ await this.editContextOrMessage(message, {
758
+ content: null,
759
+ embeds: [embed],
760
+ components: this.getComponents()
761
+ });
762
+ } catch (err) {
763
+ this.emit("fatalError", err);
764
+ this.emit("end");
765
+ return;
766
+ }
767
+ this.handleButtons(message);
768
+ }
769
+ handleButtons(message) {
770
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
771
+ collector.on("collect", async (i) => {
772
+ if (!i.customId.startsWith("$gamecord")) return;
773
+ if (i.user.id !== this.player.id && i.user.id !== this.opponent.id) {
774
+ if (this.options.notPlayerMessage) {
775
+ try {
776
+ await i.reply({
777
+ content: this.options.notPlayerMessage(this),
778
+ flags: MessageFlags2.Ephemeral
779
+ });
780
+ } catch (err) {
781
+ this.emit("error", err);
782
+ }
783
+ }
784
+ return;
785
+ }
786
+ try {
787
+ await i.deferUpdate();
788
+ } catch (err) {
789
+ this.emit("error", err);
790
+ return;
791
+ }
792
+ if (this.locked) return;
793
+ this.locked = true;
794
+ try {
795
+ if (i.user.id !== (this.isPlayer1Turn ? this.player : this.opponent).id) return;
796
+ const column = Number(i.customId.split("-").at(-1));
797
+ const block = { x: -1, y: -1 };
798
+ for (let y = 6 - 1; y >= 0; y--) {
799
+ const number = this.gameboard[column + y * 7];
800
+ if (number === 0) {
801
+ this.gameboard[column + y * 7] = this.getPlayerNumber();
802
+ block.x = column;
803
+ block.y = y;
804
+ break;
805
+ }
806
+ }
807
+ if (this.isWinAt(block.x, block.y)) return collector.stop("$win");
808
+ if (this.isBoardFull()) return collector.stop("$tie");
809
+ this.isPlayer1Turn = !this.isPlayer1Turn;
810
+ const embed = await this.buildEmbed(this.options.embed, {
811
+ description: this.getBoardContent(),
812
+ fields: [
813
+ {
814
+ name: this.options.statusText,
815
+ value: this.options.turnMessage(this.getPlayerTurnData(), this)
816
+ }
817
+ ]
818
+ });
819
+ try {
820
+ await i.editReply({ embeds: [embed], components: this.getComponents() });
821
+ } catch (err) {
822
+ this.emit("error", err);
823
+ }
824
+ } finally {
825
+ this.locked = false;
826
+ }
827
+ });
828
+ collector.on("end", async (_, reason) => {
829
+ this.emit("end");
830
+ if (reason === "$win" || reason === "$tie") {
831
+ return await this.gameOver(message, reason === "$win" ? "win" : "tie");
832
+ }
833
+ if (reason === "idle") {
834
+ return await this.gameOver(message, "timeout");
835
+ }
836
+ });
837
+ }
838
+ async gameOver(message, outcome) {
839
+ const winner = this.isPlayer1Turn ? this.player : this.opponent;
840
+ const winnerEmoji = this.isPlayer1Turn ? this.options.emojis.player1 : this.options.emojis.player2;
841
+ const result = this.result({
842
+ opponent: this.opponent,
843
+ outcome,
844
+ winner: outcome === "win" ? winner : null,
845
+ winnerEmoji: outcome === "win" ? winnerEmoji : null
846
+ });
847
+ this.emit("gameOver", result);
848
+ let getMessage;
849
+ switch (outcome) {
850
+ case "win": {
851
+ getMessage = this.options.winMessage;
852
+ break;
853
+ }
854
+ case "tie": {
855
+ getMessage = this.options.tieMessage;
856
+ break;
857
+ }
858
+ case "timeout": {
859
+ getMessage = this.options.timeoutMessage;
860
+ break;
861
+ }
862
+ }
863
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
864
+ description: this.getBoardContent(),
865
+ fields: [
866
+ {
867
+ name: this.options.statusText,
868
+ value: getMessage(result, this)
869
+ }
870
+ ]
871
+ });
872
+ try {
873
+ await this.editContextOrMessage(message, {
874
+ embeds: [embed],
875
+ components: this.getComponents(true)
876
+ });
877
+ } catch (err) {
878
+ this.emit("error", err);
879
+ }
880
+ }
881
+ getPlayerTurnData() {
882
+ return this.isPlayer1Turn ? { player: this.player, emoji: this.options.emojis.player1 } : { player: this.opponent, emoji: this.options.emojis.player2 };
883
+ }
884
+ getBoardContent() {
885
+ let board = "";
886
+ for (let y = 0; y < 6; y++) {
887
+ for (let x = 0; x < 7; x++) {
888
+ board += this.getEmoji(this.gameboard[y * 7 + x]);
889
+ }
890
+ board += "\n";
891
+ }
892
+ board += "1\uFE0F\u20E32\uFE0F\u20E33\uFE0F\u20E34\uFE0F\u20E35\uFE0F\u20E36\uFE0F\u20E37\uFE0F\u20E3";
893
+ return board;
894
+ }
895
+ /**
896
+ * Get the current player number (`1` or `2`).
897
+ */
898
+ getPlayerNumber() {
899
+ return this.isPlayer1Turn ? 1 : 2;
900
+ }
901
+ isBoardFull() {
902
+ for (let y = 0; y < 6; y++) {
903
+ for (let x = 0; x < 7; x++) {
904
+ if (this.gameboard[y * 7 + x] === 0) return false;
905
+ }
906
+ }
907
+ return true;
908
+ }
909
+ /**
910
+ * Get the emoji of a number of the game board (`0`, `1` or `2`).
911
+ */
912
+ getEmoji(number) {
913
+ if (number === 1) return this.options.emojis.player1;
914
+ if (number === 2) return this.options.emojis.player2;
915
+ return this.options.emojis.board;
916
+ }
917
+ isWinAt(x, y) {
918
+ const number = this.getPlayerNumber();
919
+ for (let i = Math.max(0, x - 3); i <= x; i++) {
920
+ const adj = i + y * 7;
921
+ if (i + 3 < 7) {
922
+ if (this.gameboard[adj] === number && this.gameboard[adj + 1] === number && this.gameboard[adj + 2] === number && this.gameboard[adj + 3] === number)
923
+ return true;
924
+ }
925
+ }
926
+ for (let i = Math.max(0, y - 3); i <= y; i++) {
927
+ const adj = x + i * 7;
928
+ if (i + 3 < 6) {
929
+ if (this.gameboard[adj] === number && this.gameboard[adj + 7] === number && this.gameboard[adj + 2 * 7] === number && this.gameboard[adj + 3 * 7] === number)
930
+ return true;
931
+ }
932
+ }
933
+ for (let i = -3; i <= 0; i++) {
934
+ const block = { x: x + i, y: y + i };
935
+ const adj = block.x + block.y * 7;
936
+ if (block.x + 3 < 7 && block.y + 3 < 6) {
937
+ if (this.gameboard[adj] === number && this.gameboard[adj + 7 + 1] === number && this.gameboard[adj + 2 * 7 + 2] === number && this.gameboard[adj + 3 * 7 + 3] === number)
938
+ return true;
939
+ }
940
+ }
941
+ for (let i = -3; i <= 0; i++) {
942
+ const block = { x: x + i, y: y - i };
943
+ const adj = block.x + block.y * 7;
944
+ if (block.x + 3 < 7 && block.y - 3 >= 0 && block.x >= 0) {
945
+ if (this.gameboard[adj] === number && this.gameboard[adj - 7 + 1] === number && this.gameboard[adj - 2 * 7 + 2] === number && this.gameboard[adj - 3 * 7 + 3] === number)
946
+ return true;
947
+ }
948
+ }
949
+ return false;
950
+ }
951
+ isColumnFilled(x) {
952
+ return this.gameboard[x] !== 0;
953
+ }
954
+ getComponents(disabled = false) {
955
+ const row1 = new ActionRowBuilder3().setComponents(
956
+ ["1\uFE0F\u20E3", "2\uFE0F\u20E3", "3\uFE0F\u20E3", "4\uFE0F\u20E3"].map(
957
+ (e, i) => new ButtonBuilder3().setStyle(this.options.buttonStyle).setEmoji(e).setCustomId(`$gamecord-connect4-${i}`).setDisabled(disabled || this.isColumnFilled(i))
958
+ )
959
+ );
960
+ const row2 = new ActionRowBuilder3().setComponents(
961
+ ["5\uFE0F\u20E3", "6\uFE0F\u20E3", "7\uFE0F\u20E3"].map(
962
+ (e, i) => new ButtonBuilder3().setStyle(this.options.buttonStyle).setEmoji(e).setCustomId(`$gamecord-connect4-${i + 4}`).setDisabled(disabled || this.isColumnFilled(i + 4))
963
+ )
964
+ );
965
+ return [row1, row2];
966
+ }
967
+ };
968
+
969
+ // src/games/FastType.ts
970
+ import z5 from "zod/v4";
971
+ var defaultOptions4 = {
972
+ embed: {
973
+ title: "Fast Type",
974
+ color: colors.blurple,
975
+ description: `You have 30 seconds to type the sentence below.`
976
+ },
977
+ winMessage: /* @__PURE__ */ __name((res) => `You won! You finished the type race in ${res.secondsTaken} seconds with word per minute of ${res.wpm}.`, "winMessage"),
978
+ loseMessage: /* @__PURE__ */ __name(() => "You lost! You didn't type the correct sentence in time.", "loseMessage"),
979
+ sentence: "Some really cool sentence to fast type.",
980
+ timeout: 3e4
981
+ };
982
+ var fastTypeOptions = z5.object({
983
+ embed: embedBuilder().optional().default(() => defaultOptions4.embed),
984
+ endEmbed: embedBuilder().optional(),
985
+ winMessage: resultMessage().optional().default(() => defaultOptions4.winMessage),
986
+ loseMessage: resultMessage().optional().default(() => defaultOptions4.loseMessage),
987
+ sentence: z5.string().optional().default(defaultOptions4.sentence),
988
+ timeout: z5.number().int().optional().default(defaultOptions4.timeout)
989
+ });
990
+ var FastType = class extends Game {
991
+ static {
992
+ __name(this, "FastType");
993
+ }
994
+ options;
995
+ timeTaken = 0;
996
+ wpm = 0;
997
+ constructor(context, options) {
998
+ super(context);
999
+ this.options = fastTypeOptions.parse(options || {});
1000
+ }
1001
+ async start() {
1002
+ const embed = typeof this.options.embed === "function" ? await this.options.embed(this) : {
1003
+ author: {
1004
+ name: this.player.displayName,
1005
+ icon_url: this.player.displayAvatarURL()
1006
+ },
1007
+ fields: [
1008
+ {
1009
+ name: "Sentence",
1010
+ value: this.options.sentence.split(" ").map((e) => "`" + e.split("").join(" ") + "`").join(" ")
1011
+ }
1012
+ ],
1013
+ ...this.options.embed
1014
+ };
1015
+ const message = await this.sendMessage({ embeds: [embed] });
1016
+ if (!message.channel.isSendable()) return;
1017
+ const startTime = Date.now();
1018
+ const collector = message.channel.createMessageCollector({
1019
+ time: this.options.timeout,
1020
+ filter: /* @__PURE__ */ __name((m) => m.author.id === this.player.id, "filter")
1021
+ });
1022
+ collector.on("collect", async (msg) => {
1023
+ this.timeTaken = Math.floor(Date.now() - startTime);
1024
+ this.wpm = Math.floor(msg.content.trim().length / (this.timeTaken / 6e4 % 60) / 5);
1025
+ const hasWon = msg.content?.toLowerCase().trim() === this.options.sentence.toLowerCase();
1026
+ collector.stop(hasWon ? "$win" : "$lose");
1027
+ });
1028
+ collector.on("end", async (_, reason) => {
1029
+ this.emit("end");
1030
+ if (!this.timeTaken) {
1031
+ this.timeTaken = Math.floor(Date.now() - startTime);
1032
+ }
1033
+ if (reason === "$win" || reason === "$lose" || reason === "time") {
1034
+ await this.gameOver(message, reason === "$win" ? "win" : reason === "$lose" ? "lose" : "timeout");
1035
+ }
1036
+ });
1037
+ }
1038
+ async gameOver(message, outcome) {
1039
+ const result = this.result({
1040
+ outcome,
1041
+ timeTaken: this.timeTaken,
1042
+ secondsTaken: Math.floor(this.timeTaken / 1e3),
1043
+ wpm: this.wpm
1044
+ });
1045
+ this.emit("gameOver", result);
1046
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
1047
+ description: result.outcome === "win" ? this.options.winMessage(result, this) : this.options.loseMessage(result, this)
1048
+ });
1049
+ try {
1050
+ await this.editContextOrMessage(message, { embeds: [embed] });
1051
+ } catch (err) {
1052
+ this.emit("error", err);
1053
+ }
1054
+ }
1055
+ };
1056
+
1057
+ // src/games/Flood.ts
1058
+ import z6 from "zod/v4";
1059
+ import { ActionRowBuilder as ActionRowBuilder4, ButtonBuilder as ButtonBuilder4, ButtonStyle as ButtonStyle4, MessageFlags as MessageFlags3 } from "discord.js";
1060
+ var defaultOptions5 = {
1061
+ embed: {
1062
+ title: "Flood",
1063
+ color: colors.blurple
1064
+ },
1065
+ winMessage: /* @__PURE__ */ __name((res) => `You won! You took **${res.turns}** turns.`, "winMessage"),
1066
+ loseMessage: /* @__PURE__ */ __name((res) => `You lost! You took **${res.turns}** turns.`, "loseMessage"),
1067
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} can use these buttons.`, "notPlayerMessage"),
1068
+ size: 13,
1069
+ maxTurns: 25,
1070
+ timeout: 12e4,
1071
+ buttonStyle: ButtonStyle4.Primary,
1072
+ emojis: ["\u{1F7E5}", "\u{1F7E6}", "\u{1F7E7}", "\u{1F7EA}", "\u{1F7E9}"]
1073
+ };
1074
+ var floodOptions = z6.object({
1075
+ embed: embedBuilder().optional().default(defaultOptions5.embed),
1076
+ endEmbed: embedBuilder().optional(),
1077
+ winMessage: resultMessage().optional().default(() => defaultOptions5.winMessage),
1078
+ loseMessage: resultMessage().optional().default(() => defaultOptions5.loseMessage),
1079
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions5.notPlayerMessage),
1080
+ size: z6.number().int().optional().default(defaultOptions5.size),
1081
+ maxTurns: z6.number().int().optional().default(defaultOptions5.maxTurns),
1082
+ timeout: z6.number().int().optional().default(defaultOptions5.timeout),
1083
+ buttonStyle: z6.number().int().optional().default(defaultOptions5.buttonStyle),
1084
+ emojis: z6.array(z6.string()).length(5).optional().default(defaultOptions5.emojis)
1085
+ });
1086
+ var Flood = class extends Game {
1087
+ static {
1088
+ __name(this, "Flood");
1089
+ }
1090
+ options;
1091
+ board = [];
1092
+ turns = 0;
1093
+ message = null;
1094
+ constructor(context, options) {
1095
+ super(context);
1096
+ this.options = floodOptions.parse(options || {});
1097
+ for (let y = 0; y < this.options.size; y++) {
1098
+ for (let x = 0; x < this.options.size; x++) {
1099
+ this.board[y * this.options.size + x] = getRandomInt(this.options.emojis.length - 1);
1100
+ }
1101
+ }
1102
+ }
1103
+ async start() {
1104
+ this.message = await this.sendMessage({ embeds: [await this.getEmbed()], components: [this.getActionRow()] });
1105
+ this.handleButtons(this.message);
1106
+ }
1107
+ handleButtons(message) {
1108
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
1109
+ collector.on("collect", async (i) => {
1110
+ if (!i.customId.startsWith("$gamecord")) return;
1111
+ if (i.user.id !== this.player.id) {
1112
+ if (this.options.notPlayerMessage) {
1113
+ try {
1114
+ await i.reply({
1115
+ content: this.options.notPlayerMessage(this),
1116
+ flags: MessageFlags3.Ephemeral
1117
+ });
1118
+ } catch (err) {
1119
+ this.emit("error", err);
1120
+ }
1121
+ }
1122
+ return;
1123
+ }
1124
+ try {
1125
+ await i.deferUpdate();
1126
+ } catch (err) {
1127
+ this.emit("error", err);
1128
+ return;
1129
+ }
1130
+ const index = Number(i.customId.split("-").at(-1));
1131
+ const updateResult = this.updateGame(index);
1132
+ if (updateResult === "win") return collector.stop("$win");
1133
+ if (updateResult === "lose") return collector.stop("$lose");
1134
+ try {
1135
+ await i.editReply({ embeds: [await this.getEmbed()], components: [this.getActionRow()] });
1136
+ } catch (err) {
1137
+ this.emit("error", err);
1138
+ }
1139
+ });
1140
+ collector.on("end", async (_, reason) => {
1141
+ this.emit("end");
1142
+ if (reason === "$win" || reason == "$lose" || reason === "idle") {
1143
+ await this.gameOver(message, reason === "$win" ? "win" : reason === "$lose" ? "lose" : "timeout");
1144
+ }
1145
+ });
1146
+ }
1147
+ getBoardContent() {
1148
+ let board = "";
1149
+ for (let y = 0; y < this.options.size; y++) {
1150
+ for (let x = 0; x < this.options.size; x++) {
1151
+ const index = this.board[y * this.options.size + x];
1152
+ board += this.options.emojis[index];
1153
+ }
1154
+ board += "\n";
1155
+ }
1156
+ return board;
1157
+ }
1158
+ getActionRow(disabled = false) {
1159
+ const row = new ActionRowBuilder4();
1160
+ for (let i = 0; i < 5; i++) {
1161
+ row.addComponents(
1162
+ new ButtonBuilder4().setStyle(this.options.buttonStyle).setEmoji(this.options.emojis[i]).setCustomId(`$gamecord-flood-${i}`).setDisabled(disabled)
1163
+ );
1164
+ }
1165
+ return row;
1166
+ }
1167
+ async getEmbed() {
1168
+ return typeof this.options.embed === "function" ? await this.options.embed(this) : {
1169
+ author: {
1170
+ name: this.player.displayName,
1171
+ icon_url: this.player.displayAvatarURL()
1172
+ },
1173
+ fields: [{ name: "Turns", value: `${this.turns}/${this.options.maxTurns}` }],
1174
+ ...this.options.embed,
1175
+ description: this.getBoardContent()
1176
+ };
1177
+ }
1178
+ async gameOver(message, outcome) {
1179
+ const result = this.result({
1180
+ outcome,
1181
+ turns: this.turns,
1182
+ maxTurns: this.options.maxTurns,
1183
+ boardEmojiIndex: this.board[0]
1184
+ });
1185
+ this.emit("gameOver", result);
1186
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
1187
+ description: this.getBoardContent(),
1188
+ fields: [
1189
+ {
1190
+ name: "Game Over",
1191
+ value: result.outcome === "win" ? this.options.winMessage(result, this) : this.options.loseMessage(result, this)
1192
+ }
1193
+ ]
1194
+ });
1195
+ try {
1196
+ await this.editContextOrMessage(message, { embeds: [embed], components: [this.getActionRow(true)] });
1197
+ } catch (err) {
1198
+ this.emit("error", err);
1199
+ }
1200
+ }
1201
+ updateGame(selected) {
1202
+ if (selected === this.board[0]) return "none";
1203
+ const firstBlock = this.board[0];
1204
+ const queue = [{ x: 0, y: 0 }];
1205
+ const visited = [];
1206
+ this.turns += 1;
1207
+ while (queue.length > 0) {
1208
+ const block = queue.shift();
1209
+ if (!block || visited.some((v) => v.x === block.x && v.y === block.y)) continue;
1210
+ const index = block.y * this.options.size + block.x;
1211
+ visited.push(block);
1212
+ if (this.board[index] === firstBlock) {
1213
+ this.board[index] = selected;
1214
+ const up = { x: block.x, y: block.y - 1 };
1215
+ if (!visited.some((v) => v.x === up.x && v.y === up.y) && up.y >= 0) queue.push(up);
1216
+ const down = { x: block.x, y: block.y + 1 };
1217
+ if (!visited.some((v) => v.x === down.x && v.y === down.y) && down.y < this.options.size)
1218
+ queue.push(down);
1219
+ const left = { x: block.x - 1, y: block.y };
1220
+ if (!visited.some((v) => v.x === left.x && v.y === left.y) && left.x >= 0) queue.push(left);
1221
+ const right = { x: block.x + 1, y: block.y };
1222
+ if (!visited.some((v) => v.x === right.x && v.y === right.y) && right.x < this.options.size)
1223
+ queue.push(right);
1224
+ }
1225
+ }
1226
+ let gameOver = true;
1227
+ for (let y = 0; y < this.options.size; y++) {
1228
+ for (let x = 0; x < this.options.size; x++) {
1229
+ if (this.board[y * this.options.size + x] !== selected) gameOver = false;
1230
+ }
1231
+ }
1232
+ if (this.turns >= this.options.maxTurns && !gameOver) return "lose";
1233
+ if (gameOver) return "win";
1234
+ return "none";
1235
+ }
1236
+ };
1237
+
1238
+ // src/games/Memory.ts
1239
+ import z7 from "zod/v4";
1240
+ import { ActionRowBuilder as ActionRowBuilder5, ButtonBuilder as ButtonBuilder5, ButtonStyle as ButtonStyle5, MessageFlags as MessageFlags4 } from "discord.js";
1241
+ var jokerEmoji = "\u{1F0CF}";
1242
+ var defaultOptions6 = {
1243
+ embed: {
1244
+ title: "Memory",
1245
+ color: colors.blurple,
1246
+ description: "**Click on the buttons to match emojis with their pairs.**"
1247
+ },
1248
+ winMessage: /* @__PURE__ */ __name((res) => `**You won the Game! You turned a total of \`${res.tilesTurned}\` tiles.**`, "winMessage"),
1249
+ loseMessage: /* @__PURE__ */ __name((res) => `**You lost the Game! You turned a total of \`${res.tilesTurned}\` tiles.**`, "loseMessage"),
1250
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} can use this menu.`, "notPlayerMessage"),
1251
+ timeout: 12e4,
1252
+ emojis: [
1253
+ "\u{1F349}",
1254
+ "\u{1F347}",
1255
+ "\u{1F34A}",
1256
+ "\u{1F34B}",
1257
+ "\u{1F96D}",
1258
+ "\u{1F34E}",
1259
+ "\u{1F34F}",
1260
+ "\u{1F95D}",
1261
+ "\u{1F965}",
1262
+ "\u{1F353}",
1263
+ "\u{1F352}",
1264
+ "\u{1FAD0}",
1265
+ "\u{1F34D}",
1266
+ "\u{1F345}",
1267
+ "\u{1F350}",
1268
+ "\u{1F954}",
1269
+ "\u{1F33D}",
1270
+ "\u{1F955}",
1271
+ "\u{1F96C}",
1272
+ "\u{1F966}"
1273
+ ]
1274
+ };
1275
+ var memoryOptions = z7.object({
1276
+ embed: embedBuilder().optional().default(defaultOptions6.embed),
1277
+ endEmbed: embedBuilder().optional(),
1278
+ winMessage: resultMessage().optional().default(() => defaultOptions6.winMessage),
1279
+ loseMessage: resultMessage().optional().default(() => defaultOptions6.loseMessage),
1280
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions6.notPlayerMessage),
1281
+ timeout: z7.number().int().optional().default(defaultOptions6.timeout),
1282
+ emojis: z7.array(z7.string()).length(20).optional().default(defaultOptions6.emojis)
1283
+ });
1284
+ var Memory = class extends Game {
1285
+ static {
1286
+ __name(this, "Memory");
1287
+ }
1288
+ options;
1289
+ emojis;
1290
+ components = [];
1291
+ selected = null;
1292
+ remainingPairs = 12;
1293
+ tilesTurned = 0;
1294
+ size = 5;
1295
+ message = null;
1296
+ constructor(context, options) {
1297
+ super(context);
1298
+ this.options = memoryOptions.parse(options || {});
1299
+ this.emojis = [...this.options.emojis];
1300
+ this.emojis = shuffleArray(this.emojis).slice(0, 12);
1301
+ this.emojis.push(...this.emojis, jokerEmoji);
1302
+ this.emojis = shuffleArray(this.emojis);
1303
+ }
1304
+ async start() {
1305
+ this.components = this.getComponents();
1306
+ const embed = await this.buildEmbed(this.options.embed);
1307
+ this.message = await this.sendMessage({ embeds: [embed], components: this.components });
1308
+ this.handleButtons(this.message);
1309
+ }
1310
+ /**
1311
+ * Get the positions of this emoji.
1312
+ */
1313
+ getPairEmojiPositions(emoji) {
1314
+ const emojis = [];
1315
+ for (let y = 0; y < this.size; y++) {
1316
+ for (let x = 0; x < this.size; x++) {
1317
+ const index = y * this.size + x;
1318
+ if (this.emojis[index] === emoji) emojis.push({ x, y, index });
1319
+ }
1320
+ }
1321
+ return emojis;
1322
+ }
1323
+ getComponents() {
1324
+ const components = [];
1325
+ for (let y = 0; y < this.size; y++) {
1326
+ const row = new ActionRowBuilder5();
1327
+ for (let x = 0; x < this.size; x++) {
1328
+ row.addComponents(
1329
+ new ButtonBuilder5().setStyle(ButtonStyle5.Secondary).setLabel("\u200B").setCustomId(`$gamecord-memory-${x}-${y}`)
1330
+ );
1331
+ }
1332
+ components.push(row);
1333
+ }
1334
+ return components;
1335
+ }
1336
+ async gameOver(message, hasTimedOut) {
1337
+ const result = this.result({
1338
+ outcome: hasTimedOut ? "timeout" : "win",
1339
+ tilesTurned: this.tilesTurned,
1340
+ remainingPairs: this.remainingPairs
1341
+ });
1342
+ this.emit("gameOver", result);
1343
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
1344
+ description: result.outcome === "win" ? this.options.winMessage(result, this) : this.options.loseMessage(result, this)
1345
+ });
1346
+ try {
1347
+ for (const btn of this.components[0].components) {
1348
+ btn.setDisabled(true);
1349
+ }
1350
+ await this.editContextOrMessage(message, {
1351
+ embeds: [embed],
1352
+ components: this.components
1353
+ });
1354
+ } catch (err) {
1355
+ this.emit("error", err);
1356
+ }
1357
+ }
1358
+ async handleButtons(message) {
1359
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
1360
+ collector.on("collect", async (i) => {
1361
+ if (!i.customId.startsWith("$gamecord")) return;
1362
+ if (i.user.id !== this.player.id) {
1363
+ if (this.options.notPlayerMessage) {
1364
+ try {
1365
+ await i.reply({
1366
+ content: this.options.notPlayerMessage(this),
1367
+ flags: MessageFlags4.Ephemeral
1368
+ });
1369
+ } catch (err) {
1370
+ this.emit("error", err);
1371
+ }
1372
+ }
1373
+ return;
1374
+ }
1375
+ const x = Number(i.customId.split("-").at(-2));
1376
+ const y = Number(i.customId.split("-").at(-1));
1377
+ const index = y * this.size + x;
1378
+ try {
1379
+ await i.deferUpdate();
1380
+ } catch (err) {
1381
+ this.emit("error", err);
1382
+ return;
1383
+ }
1384
+ const emoji = this.emojis[index];
1385
+ const emojiBtn = this.components[y].components[x];
1386
+ this.tilesTurned += 1;
1387
+ if (!this.selected) {
1388
+ this.selected = { x, y, index };
1389
+ emojiBtn.setEmoji(emoji).setStyle(ButtonStyle5.Primary);
1390
+ } else if (this.selected.index === index) {
1391
+ this.selected = null;
1392
+ removeEmoji(emojiBtn).setStyle(ButtonStyle5.Secondary);
1393
+ } else {
1394
+ const selectedEmoji = this.emojis[this.selected.index];
1395
+ const selectedBtn = this.components[this.selected.y].components[this.selected.x];
1396
+ const matched = emoji === selectedEmoji || selectedEmoji === jokerEmoji || emoji === jokerEmoji;
1397
+ if (selectedEmoji === jokerEmoji || emoji === jokerEmoji) {
1398
+ const joker = emoji === jokerEmoji ? this.selected : { x, y, index };
1399
+ const pair = this.getPairEmojiPositions(this.emojis[joker.index]).filter(
1400
+ (b) => b.index !== joker.index
1401
+ )[0];
1402
+ const pairBtn = this.components[pair.y].components[pair.x];
1403
+ pairBtn.setEmoji(this.emojis[pair.index]).setStyle(ButtonStyle5.Success).setDisabled(true);
1404
+ }
1405
+ emojiBtn.setEmoji(emoji).setStyle(matched ? ButtonStyle5.Success : ButtonStyle5.Danger).setDisabled(matched);
1406
+ selectedBtn.setEmoji(selectedEmoji).setStyle(matched ? ButtonStyle5.Success : ButtonStyle5.Danger).setDisabled(matched);
1407
+ if (!matched) {
1408
+ try {
1409
+ await i.editReply({ components: this.components });
1410
+ } catch (err) {
1411
+ this.emit("error", err);
1412
+ }
1413
+ removeEmoji(emojiBtn).setStyle(ButtonStyle5.Secondary);
1414
+ removeEmoji(selectedBtn).setStyle(ButtonStyle5.Secondary);
1415
+ this.selected = null;
1416
+ return;
1417
+ }
1418
+ this.remainingPairs -= 1;
1419
+ this.selected = null;
1420
+ }
1421
+ if (this.remainingPairs === 0) return collector.stop("$win");
1422
+ try {
1423
+ await i.editReply({ components: this.components });
1424
+ } catch (err) {
1425
+ this.emit("error", err);
1426
+ }
1427
+ });
1428
+ collector.on("end", async (_, reason) => {
1429
+ this.emit("end");
1430
+ if (reason === "$win" || reason === "idle") {
1431
+ await this.gameOver(message, reason === "idle");
1432
+ }
1433
+ });
1434
+ }
1435
+ };
1436
+
1437
+ // src/games/Minesweeper.ts
1438
+ import z8 from "zod/v4";
1439
+ import { ActionRowBuilder as ActionRowBuilder6, ButtonBuilder as ButtonBuilder6, ButtonStyle as ButtonStyle6, MessageFlags as MessageFlags5 } from "discord.js";
1440
+ var defaultOptions7 = {
1441
+ embed: {
1442
+ title: "Minesweeper",
1443
+ color: colors.blurple,
1444
+ description: "Click on the buttons to reveal the blocks except mines."
1445
+ },
1446
+ timeout: 12e4,
1447
+ winMessage: /* @__PURE__ */ __name(() => "You won the Game! You successfully avoided all the mines.", "winMessage"),
1448
+ loseMessage: /* @__PURE__ */ __name(() => "You lost the Game! Beaware of the mines next time.", "loseMessage"),
1449
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} can use this menu.`, "notPlayerMessage"),
1450
+ mines: 5,
1451
+ emojis: {
1452
+ flag: "\u{1F6A9}",
1453
+ mine: "\u{1F4A3}"
1454
+ }
1455
+ };
1456
+ var minesweeperOptions = z8.object({
1457
+ embed: embedBuilder().optional().default(defaultOptions7.embed),
1458
+ endEmbed: embedBuilder().optional(),
1459
+ winMessage: resultMessage().optional().default(() => defaultOptions7.winMessage),
1460
+ loseMessage: resultMessage().optional().default(() => defaultOptions7.loseMessage),
1461
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions7.notPlayerMessage),
1462
+ timeout: z8.number().int().optional().default(defaultOptions7.timeout),
1463
+ mines: z8.number().int().min(1).max(24).optional().default(defaultOptions7.mines),
1464
+ emojis: z8.object({
1465
+ flag: z8.string().optional().default(defaultOptions7.emojis.flag),
1466
+ mine: z8.string().optional().default(defaultOptions7.emojis.mine)
1467
+ }).optional().default(defaultOptions7.emojis)
1468
+ });
1469
+ var Minesweeper = class extends Game {
1470
+ static {
1471
+ __name(this, "Minesweeper");
1472
+ }
1473
+ options;
1474
+ /**
1475
+ * The board of the game.
1476
+ *
1477
+ * - `true` : empty
1478
+ * - `false` : mine
1479
+ * - `number` : empty, with `n` mines around it
1480
+ */
1481
+ board = [];
1482
+ /**
1483
+ * The width/height of the board
1484
+ */
1485
+ size = 5;
1486
+ message = null;
1487
+ constructor(context, options) {
1488
+ super(context);
1489
+ this.options = minesweeperOptions.parse(options || {});
1490
+ for (let y = 0; y < this.size; y++) {
1491
+ for (let x = 0; x < this.size; x++) {
1492
+ this.board[y * this.size + x] = false;
1493
+ }
1494
+ }
1495
+ }
1496
+ async start() {
1497
+ this.plantMines();
1498
+ this.showFirstBlock();
1499
+ const embed = await this.buildEmbed(this.options.embed);
1500
+ this.message = await this.sendMessage({ embeds: [embed], components: this.getComponents() });
1501
+ this.handleButtons(this.message);
1502
+ }
1503
+ handleButtons(message) {
1504
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
1505
+ collector.on("collect", async (i) => {
1506
+ if (!i.customId.startsWith("$gamecord")) return;
1507
+ if (i.user.id !== this.player.id) {
1508
+ if (this.options.notPlayerMessage) {
1509
+ try {
1510
+ await i.reply({
1511
+ content: this.options.notPlayerMessage(this),
1512
+ flags: MessageFlags5.Ephemeral
1513
+ });
1514
+ } catch (err) {
1515
+ this.emit("error", err);
1516
+ }
1517
+ }
1518
+ return;
1519
+ }
1520
+ const x = Number(i.customId.split("-").at(-2));
1521
+ const y = Number(i.customId.split("-").at(-1));
1522
+ const index = y * this.size + x;
1523
+ try {
1524
+ await i.deferUpdate();
1525
+ } catch (err) {
1526
+ this.emit("error", err);
1527
+ return;
1528
+ }
1529
+ if (this.board[index] === true) return collector.stop("$end");
1530
+ const mines = this.getMinesAround(x, y);
1531
+ this.board[index] = mines;
1532
+ if (this.hasFoundAllMines()) return collector.stop("$end");
1533
+ try {
1534
+ await i.editReply({ components: this.getComponents() });
1535
+ } catch (err) {
1536
+ this.emit("error", err);
1537
+ }
1538
+ });
1539
+ collector.on("end", async (_, reason) => {
1540
+ this.emit("end");
1541
+ if (reason === "$end" || reason === "idle") {
1542
+ await this.gameOver(message, reason === "idle");
1543
+ }
1544
+ });
1545
+ }
1546
+ async gameOver(message, hasTimedOut) {
1547
+ const result = this.result({
1548
+ outcome: hasTimedOut ? "timeout" : this.hasFoundAllMines() ? "win" : "lose",
1549
+ tilesTurned: this.board.filter(Number.isInteger).length
1550
+ });
1551
+ this.emit("gameOver", result);
1552
+ for (let y = 0; y < this.size; y++) {
1553
+ for (let x = 0; x < this.size; x++) {
1554
+ const index = y * this.size + x;
1555
+ if (this.board[index] !== true) this.board[index] = this.getMinesAround(x, y);
1556
+ }
1557
+ }
1558
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
1559
+ description: result.outcome === "win" ? this.options.winMessage(result, this) : this.options.loseMessage(result, this)
1560
+ });
1561
+ try {
1562
+ await this.editContextOrMessage(message, {
1563
+ embeds: [embed],
1564
+ components: this.getComponents(true, result.outcome === "win", true)
1565
+ });
1566
+ } catch (err) {
1567
+ this.emit("error", err);
1568
+ }
1569
+ }
1570
+ plantMines() {
1571
+ for (let i = 0; i <= this.options.mines; i++) {
1572
+ const x = Math.floor(Math.random() * 5);
1573
+ const y = Math.floor(Math.random() * 5);
1574
+ const index = y * this.size + x;
1575
+ if (this.board[index] !== true) {
1576
+ this.board[index] = true;
1577
+ } else {
1578
+ i -= 1;
1579
+ }
1580
+ }
1581
+ }
1582
+ getMinesAround(x, y) {
1583
+ let minesAround = 0;
1584
+ for (let row = -1; row < 2; row++) {
1585
+ for (let col = -1; col < 2; col++) {
1586
+ const block = { x: x + col, y: y + row };
1587
+ if (block.x < 0 || block.x >= 5 || block.y < 0 || block.y >= 5) continue;
1588
+ if (row === 0 && col === 0) continue;
1589
+ if (this.board[block.y * this.size + block.x] === true) minesAround += 1;
1590
+ }
1591
+ }
1592
+ return minesAround;
1593
+ }
1594
+ showFirstBlock() {
1595
+ const emptyBlocks = [];
1596
+ for (let y = 0; y < this.size; y++) {
1597
+ for (let x = 0; x < this.size; x++) {
1598
+ if (this.board[y * this.size + x] === true) emptyBlocks.push({ x, y });
1599
+ }
1600
+ }
1601
+ const safeBlocks = emptyBlocks.filter((b) => !this.getMinesAround(b.x, b.y));
1602
+ const blocks = safeBlocks.length ? safeBlocks : emptyBlocks;
1603
+ const rBlock = blocks[Math.floor(Math.random() * blocks.length)];
1604
+ this.board[rBlock.y * this.size + rBlock.x] = this.getMinesAround(rBlock.x, rBlock.y);
1605
+ }
1606
+ hasFoundAllMines() {
1607
+ let found = true;
1608
+ for (let y = 0; y < this.size; y++) {
1609
+ for (let x = 0; x < this.size; x++) {
1610
+ if (this.board[y * this.size + x] === false) found = false;
1611
+ }
1612
+ }
1613
+ return found;
1614
+ }
1615
+ /**
1616
+ * Build the minesweeper grid using buttons.
1617
+ */
1618
+ getComponents(showMines = false, found = false, disabled = false) {
1619
+ const components = [];
1620
+ for (let y = 0; y < this.size; y++) {
1621
+ const row = new ActionRowBuilder6();
1622
+ for (let x = 0; x < this.size; x++) {
1623
+ const block = this.board[y * this.size + x];
1624
+ const numberEmoji = typeof block === "number" && block > 0 ? getNumberEmoji(block) : null;
1625
+ const displayMine = block === true && showMines;
1626
+ const btn = new ButtonBuilder6().setStyle(
1627
+ displayMine ? found ? ButtonStyle6.Success : ButtonStyle6.Danger : typeof block === "number" ? ButtonStyle6.Secondary : ButtonStyle6.Primary
1628
+ ).setCustomId(`$gamecord-minesweeper-${x}-${y}`).setDisabled(disabled);
1629
+ if (displayMine || numberEmoji)
1630
+ btn.setEmoji(
1631
+ displayMine ? found ? this.options.emojis.flag : this.options.emojis.mine : numberEmoji
1632
+ );
1633
+ else {
1634
+ btn.setLabel("\u200B");
1635
+ }
1636
+ row.addComponents(btn);
1637
+ }
1638
+ components.push(row);
1639
+ }
1640
+ return components;
1641
+ }
1642
+ };
1643
+
1644
+ // src/games/RockPaperScissors.ts
1645
+ import z9 from "zod/v4";
1646
+ import { ActionRowBuilder as ActionRowBuilder7, ButtonBuilder as ButtonBuilder7, ButtonStyle as ButtonStyle7, MessageFlags as MessageFlags6 } from "discord.js";
1647
+ var defaultOptions8 = {
1648
+ embed: {
1649
+ title: "Rock Paper Scissors",
1650
+ color: colors.blurple,
1651
+ description: "Press a button below to make a choice."
1652
+ },
1653
+ winMessage: /* @__PURE__ */ __name((res) => `**${res.winner}** won the Game! Congratulations!`, "winMessage"),
1654
+ tieMessage: /* @__PURE__ */ __name(() => "The Game tied! No one won the Game!", "tieMessage"),
1655
+ timeoutMessage: /* @__PURE__ */ __name(() => "The Game went unfinished! No one won the Game!", "timeoutMessage"),
1656
+ choiceMessage: /* @__PURE__ */ __name((emoji) => `You choose ${emoji}.`, "choiceMessage"),
1657
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} and ${game.opponent} can use this menu.`, "notPlayerMessage"),
1658
+ buttons: {
1659
+ rock: "Rock",
1660
+ paper: "Paper",
1661
+ scissors: "Scissors"
1662
+ },
1663
+ emojis: {
1664
+ rock: "\u{1F311}",
1665
+ paper: "\u{1F4F0}",
1666
+ scissors: "\u2702\uFE0F"
1667
+ },
1668
+ timeout: 6e4
1669
+ };
1670
+ var rockPaperScissorsOptions = z9.object({
1671
+ versus: versusOptions("Rock Paper Scissors"),
1672
+ embed: embedBuilder().optional().default(defaultOptions8.embed),
1673
+ endEmbed: embedBuilder().optional(),
1674
+ winMessage: resultMessage().optional().default(() => defaultOptions8.winMessage),
1675
+ tieMessage: resultMessage().optional().default(() => defaultOptions8.tieMessage),
1676
+ timeoutMessage: resultMessage().optional().default(() => defaultOptions8.timeoutMessage),
1677
+ choiceMessage: var2Message().optional().default(() => defaultOptions8.choiceMessage),
1678
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions8.notPlayerMessage),
1679
+ buttonStyle: z9.number().int().optional().default(ButtonStyle7.Primary),
1680
+ buttons: z9.object({
1681
+ rock: z9.string().optional().default(defaultOptions8.buttons.rock),
1682
+ paper: z9.string().optional().default(defaultOptions8.buttons.paper),
1683
+ scissors: z9.string().optional().default(defaultOptions8.buttons.scissors)
1684
+ }).optional().default(defaultOptions8.buttons),
1685
+ emojis: z9.object({
1686
+ rock: z9.string().optional().default(defaultOptions8.emojis.rock),
1687
+ paper: z9.string().optional().default(defaultOptions8.emojis.paper),
1688
+ scissors: z9.string().optional().default(defaultOptions8.emojis.scissors)
1689
+ }).optional().default(defaultOptions8.emojis),
1690
+ timeout: z9.number().int().optional().default(defaultOptions8.timeout)
1691
+ });
1692
+ var RockPaperScissors = class extends VersusGame {
1693
+ static {
1694
+ __name(this, "RockPaperScissors");
1695
+ }
1696
+ options;
1697
+ playerChoice = null;
1698
+ opponentChoice = null;
1699
+ message = null;
1700
+ constructor(context, options) {
1701
+ const parsed = rockPaperScissorsOptions.parse(options || {});
1702
+ super(context, parsed.versus);
1703
+ this.options = parsed;
1704
+ }
1705
+ async start() {
1706
+ this.message = await this.requestVersus();
1707
+ if (this.message) this.runGame(this.message);
1708
+ }
1709
+ async runGame(message) {
1710
+ try {
1711
+ const embed = await this.buildEmbed(this.options.embed);
1712
+ await this.editContextOrMessage(message, {
1713
+ content: null,
1714
+ embeds: [embed],
1715
+ components: [this.getActionRow()]
1716
+ });
1717
+ } catch (err) {
1718
+ this.emit("fatalError", err);
1719
+ this.emit("end");
1720
+ return;
1721
+ }
1722
+ this.handleButtons(message);
1723
+ }
1724
+ handleButtons(message) {
1725
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
1726
+ const emojis = this.options.emojis;
1727
+ const choices = { r: emojis.rock, p: emojis.paper, s: emojis.scissors };
1728
+ collector.on("collect", async (i) => {
1729
+ if (!i.customId.startsWith("$gamecord")) return;
1730
+ if (i.user.id !== this.player.id && i.user.id !== this.opponent.id) {
1731
+ if (this.options.notPlayerMessage) {
1732
+ try {
1733
+ await i.reply({
1734
+ content: this.options.notPlayerMessage(this),
1735
+ flags: MessageFlags6.Ephemeral
1736
+ });
1737
+ } catch (err) {
1738
+ this.emit("error", err);
1739
+ }
1740
+ }
1741
+ return;
1742
+ }
1743
+ const choice = choices[i.customId.split("-").at(-1)];
1744
+ let replyChoice = false;
1745
+ if (i.user.id === this.player.id && !this.playerChoice) {
1746
+ this.playerChoice = choice;
1747
+ replyChoice = true;
1748
+ } else if (i.user.id === this.opponent.id && !this.opponentChoice) {
1749
+ this.opponentChoice = choice;
1750
+ replyChoice = true;
1751
+ }
1752
+ if (replyChoice) {
1753
+ try {
1754
+ await i.reply({
1755
+ content: this.options.choiceMessage(choice, this),
1756
+ flags: MessageFlags6.Ephemeral
1757
+ });
1758
+ } catch (err) {
1759
+ this.emit("error", err);
1760
+ }
1761
+ }
1762
+ if (!i.replied) {
1763
+ try {
1764
+ await i.deferUpdate();
1765
+ } catch (err) {
1766
+ this.emit("error", err);
1767
+ return;
1768
+ }
1769
+ }
1770
+ if (this.playerChoice && this.opponentChoice) return collector.stop("$end");
1771
+ });
1772
+ collector.on("end", async (_, reason) => {
1773
+ this.emit("end");
1774
+ if (reason === "idle" || reason === "$end") {
1775
+ await this.gameOver(message);
1776
+ }
1777
+ });
1778
+ }
1779
+ getResult() {
1780
+ if (!this.playerChoice && !this.opponentChoice) return "timeout";
1781
+ else if (this.playerChoice === this.opponentChoice) return "tie";
1782
+ else return "win";
1783
+ }
1784
+ hasPlayer1Won() {
1785
+ const { rock: r, paper: p, scissors: s } = this.options.emojis;
1786
+ return this.playerChoice === s && this.opponentChoice === p || this.playerChoice === r && this.opponentChoice === s || this.playerChoice === p && this.opponentChoice === r;
1787
+ }
1788
+ getActionRow(disabled = false) {
1789
+ return new ActionRowBuilder7().setComponents(
1790
+ new ButtonBuilder7().setStyle(this.options.buttonStyle).setEmoji(this.options.emojis.rock).setCustomId("$gamecord-rps-r").setLabel(this.options.buttons.rock).setDisabled(disabled),
1791
+ new ButtonBuilder7().setStyle(this.options.buttonStyle).setEmoji(this.options.emojis.paper).setCustomId("$gamecord-rps-p").setLabel(this.options.buttons.paper).setDisabled(disabled),
1792
+ new ButtonBuilder7().setStyle(this.options.buttonStyle).setEmoji(this.options.emojis.scissors).setCustomId("$gamecord-rps-s").setLabel(this.options.buttons.scissors).setDisabled(disabled)
1793
+ );
1794
+ }
1795
+ async gameOver(message) {
1796
+ const outcome = this.getResult();
1797
+ const result = this.result({
1798
+ opponent: this.opponent,
1799
+ outcome,
1800
+ winner: outcome === "win" ? this.hasPlayer1Won() ? this.player : this.opponent : null,
1801
+ playerChoice: this.playerChoice,
1802
+ opponentChoice: this.opponentChoice
1803
+ });
1804
+ this.emit("gameOver", result);
1805
+ let getMessage;
1806
+ switch (outcome) {
1807
+ case "win": {
1808
+ getMessage = this.options.winMessage;
1809
+ break;
1810
+ }
1811
+ case "tie": {
1812
+ getMessage = this.options.tieMessage;
1813
+ break;
1814
+ }
1815
+ case "timeout": {
1816
+ getMessage = this.options.timeoutMessage;
1817
+ break;
1818
+ }
1819
+ }
1820
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
1821
+ description: getMessage(result, this),
1822
+ fields: [
1823
+ {
1824
+ name: this.player.displayName,
1825
+ value: this.playerChoice ?? "\u2754",
1826
+ inline: true
1827
+ },
1828
+ {
1829
+ name: "VS",
1830
+ value: "\u26A1",
1831
+ inline: true
1832
+ },
1833
+ {
1834
+ name: this.opponent.displayName,
1835
+ value: this.opponentChoice ?? "\u2754",
1836
+ inline: true
1837
+ }
1838
+ ]
1839
+ });
1840
+ try {
1841
+ await this.editContextOrMessage(message, {
1842
+ embeds: [embed],
1843
+ components: [this.getActionRow(true)]
1844
+ });
1845
+ } catch (err) {
1846
+ this.emit("error", err);
1847
+ }
1848
+ }
1849
+ };
1850
+
1851
+ // src/games/TicTacToe.ts
1852
+ import z10 from "zod/v4";
1853
+ import { ActionRowBuilder as ActionRowBuilder8, ButtonBuilder as ButtonBuilder8, ButtonStyle as ButtonStyle8, MessageFlags as MessageFlags7 } from "discord.js";
1854
+ var defaultOptions9 = {
1855
+ embed: {
1856
+ title: "Tic Tac Toe",
1857
+ color: colors.blurple
1858
+ },
1859
+ winMessage: /* @__PURE__ */ __name((res) => `${res.winnerEmoji} | **${res.winner}** won the TicTacToe Game.`, "winMessage"),
1860
+ tieMessage: /* @__PURE__ */ __name(() => "The Game tied! No one won the Game!", "tieMessage"),
1861
+ timeoutMessage: /* @__PURE__ */ __name(() => "The Game went unfinished! No one won the Game!", "timeoutMessage"),
1862
+ turnMessage: /* @__PURE__ */ __name((turn) => `${turn.emoji} | It's player **${turn.player.displayName}**'s turn.`, "turnMessage"),
1863
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} and ${game.opponent} can use this menu.`, "notPlayerMessage"),
1864
+ emojis: {
1865
+ xButton: "\u274C",
1866
+ oButton: "\u{1F535}"
1867
+ },
1868
+ styles: {
1869
+ xButton: ButtonStyle8.Secondary,
1870
+ oButton: ButtonStyle8.Secondary
1871
+ },
1872
+ timeout: 6e4
1873
+ };
1874
+ var ticTacToeOptions = z10.object({
1875
+ versus: versusOptions("Tic Tac Toe"),
1876
+ embed: embedBuilder().optional().default(defaultOptions9.embed),
1877
+ endEmbed: embedBuilder().optional(),
1878
+ winMessage: resultMessage().optional().default(() => defaultOptions9.winMessage),
1879
+ tieMessage: resultMessage().optional().default(() => defaultOptions9.tieMessage),
1880
+ timeoutMessage: resultMessage().optional().default(() => defaultOptions9.timeoutMessage),
1881
+ turnMessage: var2Message().optional().default(() => defaultOptions9.turnMessage),
1882
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions9.notPlayerMessage),
1883
+ emojis: z10.object({
1884
+ xButton: z10.string().optional().default(defaultOptions9.emojis.xButton),
1885
+ oButton: z10.string().optional().default(defaultOptions9.emojis.oButton)
1886
+ }).optional().default(defaultOptions9.emojis),
1887
+ styles: z10.object({
1888
+ xButton: z10.number().int().optional().default(defaultOptions9.styles.xButton),
1889
+ oButton: z10.number().int().optional().default(defaultOptions9.styles.oButton)
1890
+ }).optional().default(defaultOptions9.styles),
1891
+ timeout: z10.number().int().optional().default(defaultOptions9.timeout)
1892
+ });
1893
+ var TicTacToe = class extends VersusGame {
1894
+ static {
1895
+ __name(this, "TicTacToe");
1896
+ }
1897
+ options;
1898
+ /**
1899
+ * A 9-elements array.
1900
+ *
1901
+ * - `0` : empty
1902
+ * - `1` : X (player 1)
1903
+ * - `2` : O (player 2)
1904
+ */
1905
+ gameboard = [0, 0, 0, 0, 0, 0, 0, 0, 0];
1906
+ isPlayer1Turn = true;
1907
+ message = null;
1908
+ locked = false;
1909
+ constructor(context, options) {
1910
+ const parsed = ticTacToeOptions.parse(options || {});
1911
+ super(context, parsed.versus);
1912
+ this.options = parsed;
1913
+ }
1914
+ async start() {
1915
+ this.message = await this.requestVersus();
1916
+ if (this.message) this.runGame(this.message);
1917
+ }
1918
+ async runGame(message) {
1919
+ try {
1920
+ const embed = await this.buildEmbed(this.options.embed, {
1921
+ description: this.options.turnMessage(this.getPlayerTurnData(), this)
1922
+ });
1923
+ await this.editContextOrMessage(message, {
1924
+ content: null,
1925
+ embeds: [embed],
1926
+ components: this.getComponents()
1927
+ });
1928
+ } catch (err) {
1929
+ this.emit("fatalError", err);
1930
+ this.emit("end");
1931
+ return;
1932
+ }
1933
+ this.handleButtons(message);
1934
+ }
1935
+ handleButtons(message) {
1936
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
1937
+ collector.on("collect", async (i) => {
1938
+ if (!i.customId.startsWith("$gamecord")) return;
1939
+ if (i.user.id !== this.player.id && i.user.id !== this.opponent.id) {
1940
+ if (this.options.notPlayerMessage) {
1941
+ try {
1942
+ await i.reply({
1943
+ content: this.options.notPlayerMessage(this),
1944
+ flags: MessageFlags7.Ephemeral
1945
+ });
1946
+ } catch (err) {
1947
+ this.emit("error", err);
1948
+ }
1949
+ }
1950
+ return;
1951
+ }
1952
+ try {
1953
+ await i.deferUpdate();
1954
+ } catch (err) {
1955
+ this.emit("error", err);
1956
+ return;
1957
+ }
1958
+ if (this.locked) return;
1959
+ this.locked = true;
1960
+ try {
1961
+ if (i.user.id !== (this.isPlayer1Turn ? this.player : this.opponent).id) return;
1962
+ const index = Number(i.customId.split("-").at(-1));
1963
+ if (this.gameboard[index] !== 0) return;
1964
+ this.gameboard[index] = this.isPlayer1Turn ? 1 : 2;
1965
+ if (this.hasWonGame(1) || this.hasWonGame(2)) return collector.stop("$win");
1966
+ if (!this.gameboard.includes(0)) return collector.stop("$tie");
1967
+ this.isPlayer1Turn = !this.isPlayer1Turn;
1968
+ const embed = await this.buildEmbed(this.options.embed, {
1969
+ description: this.options.turnMessage(this.getPlayerTurnData(), this)
1970
+ });
1971
+ try {
1972
+ await i.editReply({ embeds: [embed], components: this.getComponents() });
1973
+ } catch (err) {
1974
+ this.emit("error", err);
1975
+ }
1976
+ } finally {
1977
+ this.locked = false;
1978
+ }
1979
+ });
1980
+ collector.on("end", async (_, reason) => {
1981
+ this.emit("end");
1982
+ if (reason === "$win" || reason === "$tie") {
1983
+ return await this.gameOver(message, reason === "$win" ? "win" : "tie");
1984
+ }
1985
+ if (reason === "idle") {
1986
+ return await this.gameOver(message, "timeout");
1987
+ }
1988
+ });
1989
+ }
1990
+ async gameOver(message, outcome) {
1991
+ const winner = this.hasWonGame(1) ? this.player : this.opponent;
1992
+ const winnerEmoji = this.hasWonGame(1) ? this.options.emojis.xButton : this.options.emojis.oButton;
1993
+ const result = this.result({
1994
+ opponent: this.opponent,
1995
+ outcome,
1996
+ winner: outcome === "win" ? winner : null,
1997
+ winnerEmoji: outcome === "win" ? winnerEmoji : null
1998
+ });
1999
+ this.emit("gameOver", result);
2000
+ let getMessage;
2001
+ switch (outcome) {
2002
+ case "win": {
2003
+ getMessage = this.options.winMessage;
2004
+ break;
2005
+ }
2006
+ case "tie": {
2007
+ getMessage = this.options.tieMessage;
2008
+ break;
2009
+ }
2010
+ case "timeout": {
2011
+ getMessage = this.options.timeoutMessage;
2012
+ break;
2013
+ }
2014
+ }
2015
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
2016
+ description: getMessage(result, this)
2017
+ });
2018
+ try {
2019
+ await this.editContextOrMessage(message, {
2020
+ embeds: [embed],
2021
+ components: this.getComponents(true)
2022
+ });
2023
+ } catch (err) {
2024
+ this.emit("error", err);
2025
+ }
2026
+ }
2027
+ hasWonGame(player) {
2028
+ if (this.gameboard[0] === this.gameboard[4] && this.gameboard[0] === this.gameboard[8] && this.gameboard[0] === player) {
2029
+ return true;
2030
+ } else if (this.gameboard[6] === this.gameboard[4] && this.gameboard[6] === this.gameboard[2] && this.gameboard[6] === player) {
2031
+ return true;
2032
+ }
2033
+ for (let i = 0; i < 3; ++i) {
2034
+ if (this.gameboard[i * 3] === this.gameboard[i * 3 + 1] && this.gameboard[i * 3] === this.gameboard[i * 3 + 2] && this.gameboard[i * 3] === player) {
2035
+ return true;
2036
+ }
2037
+ if (this.gameboard[i] === this.gameboard[i + 3] && this.gameboard[i] === this.gameboard[i + 6] && this.gameboard[i] === player) {
2038
+ return true;
2039
+ }
2040
+ }
2041
+ return false;
2042
+ }
2043
+ getPlayerTurnData() {
2044
+ return this.isPlayer1Turn ? { player: this.player, emoji: this.options.emojis.xButton } : { player: this.opponent, emoji: this.options.emojis.oButton };
2045
+ }
2046
+ getButtonData(player) {
2047
+ if (player === 1) return { emoji: this.options.emojis.xButton, style: this.options.styles.xButton };
2048
+ else if (player === 2) return { emoji: this.options.emojis.oButton, style: this.options.styles.oButton };
2049
+ else return { emoji: null, style: ButtonStyle8.Secondary };
2050
+ }
2051
+ getComponents(disabled = false) {
2052
+ const components = [];
2053
+ for (let x = 0; x < 3; x++) {
2054
+ const row = new ActionRowBuilder8();
2055
+ for (let y = 0; y < 3; y++) {
2056
+ const index = y * 3 + x;
2057
+ const data = this.getButtonData(this.gameboard[index]);
2058
+ const btn = new ButtonBuilder8().setStyle(data.style).setCustomId(`$gamecord-tictactoe-${index}`);
2059
+ if (data.emoji) {
2060
+ btn.setEmoji(data.emoji);
2061
+ } else {
2062
+ btn.setLabel("\u200B");
2063
+ }
2064
+ if (this.gameboard[y * 3 + x] !== 0) btn.setDisabled(true);
2065
+ if (disabled) btn.setDisabled(true);
2066
+ row.addComponents(btn);
2067
+ }
2068
+ components.push(row);
2069
+ }
2070
+ return components;
2071
+ }
2072
+ };
2073
+
2074
+ // src/games/Trivia.ts
2075
+ import z11 from "zod/v4";
2076
+ import { ActionRowBuilder as ActionRowBuilder9, ButtonBuilder as ButtonBuilder9, ButtonStyle as ButtonStyle9, MessageFlags as MessageFlags8 } from "discord.js";
2077
+ var triviaAPISchema = z11.object({
2078
+ results: z11.array(
2079
+ z11.object({
2080
+ difficulty: z11.string(),
2081
+ category: z11.string(),
2082
+ question: z11.string(),
2083
+ correct_answer: z11.string(),
2084
+ incorrect_answers: z11.array(z11.string())
2085
+ })
2086
+ )
2087
+ });
2088
+ var difficulties = ["easy", "medium", "hard"];
2089
+ var defaultOptions10 = {
2090
+ embed: {
2091
+ title: "Trivia",
2092
+ color: colors.blurple,
2093
+ fields: [
2094
+ {
2095
+ name: "\u200B",
2096
+ value: "You have 60 seconds to guess the answer."
2097
+ }
2098
+ ]
2099
+ },
2100
+ winMessage: /* @__PURE__ */ __name((res) => `You won! The correct answer was ${res.trivia.answer}.`, "winMessage"),
2101
+ loseMessage: /* @__PURE__ */ __name((res) => `You lost! The correct answer was ${res.trivia.answer}.`, "loseMessage"),
2102
+ errorMessage: /* @__PURE__ */ __name(() => "Unable to fetch question data! Please try again.", "errorMessage"),
2103
+ notPlayerMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} can use this menu.`, "notPlayerMessage"),
2104
+ mode: "multiple",
2105
+ timeout: 6e4,
2106
+ trueText: "True",
2107
+ falseText: "False"
2108
+ };
2109
+ var triviaOptions = z11.object({
2110
+ embed: embedBuilder().optional().default(defaultOptions10.embed),
2111
+ endEmbed: embedBuilder().optional(),
2112
+ winMessage: resultMessage().optional().default(() => defaultOptions10.winMessage),
2113
+ loseMessage: resultMessage().optional().default(() => defaultOptions10.loseMessage),
2114
+ errorMessage: gameMessage().optional().default(() => defaultOptions10.errorMessage),
2115
+ notPlayerMessage: gameMessage().optional().default(() => defaultOptions10.notPlayerMessage),
2116
+ mode: z11.enum(["multiple", "single"]).optional().default(defaultOptions10.mode),
2117
+ difficulty: z11.enum(difficulties).optional(),
2118
+ trivia: z11.custom().optional(),
2119
+ timeout: z11.number().int().optional().default(defaultOptions10.timeout),
2120
+ trueText: z11.string().optional().default(defaultOptions10.trueText),
2121
+ falseText: z11.string().optional().default(defaultOptions10.falseText)
2122
+ });
2123
+ var Trivia = class extends Game {
2124
+ static {
2125
+ __name(this, "Trivia");
2126
+ }
2127
+ options;
2128
+ difficulty;
2129
+ selected = null;
2130
+ trivia = null;
2131
+ message = null;
2132
+ constructor(context, options) {
2133
+ super(context);
2134
+ this.options = triviaOptions.parse(options || {});
2135
+ this.difficulty = this.options.difficulty || getRandomElement(difficulties);
2136
+ }
2137
+ async start() {
2138
+ this.trivia = await this.getTriviaQuestion();
2139
+ if (!this.trivia) {
2140
+ try {
2141
+ await this.sendMessage({
2142
+ content: this.options.errorMessage(this)
2143
+ });
2144
+ } catch (err) {
2145
+ this.emit("error", err);
2146
+ }
2147
+ return;
2148
+ }
2149
+ const embed = await this.buildEmbed(this.options.embed, {
2150
+ description: `**${this.trivia.question}**
2151
+
2152
+ **Difficulty:** ${this.trivia.difficulty}
2153
+ **Category:** ${this.trivia.category}`
2154
+ });
2155
+ this.message = await this.sendMessage({ embeds: [embed], components: this.getComponents() });
2156
+ this.handleButtons(this.message);
2157
+ }
2158
+ handleButtons(message) {
2159
+ const trivia = this.trivia;
2160
+ const collector = message.createMessageComponentCollector({ idle: this.options.timeout });
2161
+ collector.on("collect", async (i) => {
2162
+ if (!i.customId.startsWith("$gamecord")) return;
2163
+ if (i.user.id !== this.player.id) {
2164
+ if (this.options.notPlayerMessage) {
2165
+ try {
2166
+ await i.reply({
2167
+ content: this.options.notPlayerMessage(this),
2168
+ flags: MessageFlags8.Ephemeral
2169
+ });
2170
+ } catch (err) {
2171
+ this.emit("error", err);
2172
+ }
2173
+ }
2174
+ return;
2175
+ }
2176
+ collector.stop();
2177
+ this.selected = Number(i.customId.split("-").at(-1));
2178
+ try {
2179
+ await i.deferUpdate();
2180
+ } catch (err) {
2181
+ this.emit("error", err);
2182
+ }
2183
+ await this.gameOver(message, trivia.options[this.selected] === trivia.answer);
2184
+ });
2185
+ collector.on("end", async (_, reason) => {
2186
+ this.emit("end");
2187
+ if (reason === "idle") {
2188
+ await this.gameOver(message, false, true);
2189
+ }
2190
+ });
2191
+ }
2192
+ async gameOver(message, hasWon, hasTimedOut = false) {
2193
+ const trivia = this.trivia;
2194
+ const result = this.result({
2195
+ outcome: hasTimedOut ? "timeout" : hasWon ? "win" : "lose",
2196
+ trivia,
2197
+ selected: this.selected
2198
+ });
2199
+ this.emit("gameOver", result);
2200
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
2201
+ description: `**${trivia.question}**
2202
+
2203
+ **Difficulty:** ${trivia.difficulty}
2204
+ **Category:** ${trivia.category}`,
2205
+ fields: [
2206
+ {
2207
+ name: "Game Over",
2208
+ value: result.outcome === "win" ? this.options.winMessage(result, this) : this.options.loseMessage(result, this)
2209
+ }
2210
+ ]
2211
+ });
2212
+ try {
2213
+ await this.editContextOrMessage(message, {
2214
+ embeds: [embed],
2215
+ components: this.getComponents(true)
2216
+ });
2217
+ } catch (err) {
2218
+ this.emit("error", err);
2219
+ }
2220
+ }
2221
+ /**
2222
+ * Build the selection buttons.
2223
+ */
2224
+ getComponents(ended = false) {
2225
+ const trivia = this.trivia;
2226
+ const row = new ActionRowBuilder9();
2227
+ if (ended && this.selected === null) {
2228
+ this.selected = trivia.options.indexOf(trivia.answer);
2229
+ }
2230
+ if (this.options.mode === "multiple") {
2231
+ for (let i = 0; i < 4; i++) {
2232
+ row.addComponents(
2233
+ new ButtonBuilder9().setStyle(ButtonStyle9.Secondary).setCustomId(`$gamecord-trivia-${i}`).setLabel(trivia.options[i]).setDisabled(ended)
2234
+ );
2235
+ }
2236
+ if (this.selected !== null) {
2237
+ if (trivia.answer !== trivia.options[this.selected]) {
2238
+ row.components[this.selected].setStyle(ButtonStyle9.Danger);
2239
+ } else {
2240
+ row.components[this.selected].setStyle(ButtonStyle9.Success);
2241
+ }
2242
+ }
2243
+ } else {
2244
+ row.setComponents(
2245
+ new ButtonBuilder9().setStyle(
2246
+ this.selected === 0 ? trivia.answer === "True" ? ButtonStyle9.Success : ButtonStyle9.Danger : ButtonStyle9.Secondary
2247
+ ).setCustomId("$gamecord-trivia-0").setLabel(this.options.trueText).setDisabled(ended),
2248
+ new ButtonBuilder9().setStyle(
2249
+ this.selected === 1 ? trivia.answer === "False" ? ButtonStyle9.Success : ButtonStyle9.Danger : ButtonStyle9.Secondary
2250
+ ).setCustomId("$gamecord-trivia-1").setLabel(this.options.falseText).setDisabled(ended)
2251
+ );
2252
+ }
2253
+ return [row];
2254
+ }
2255
+ async getTriviaQuestion() {
2256
+ if (this.options.trivia) {
2257
+ return await this.options.trivia(this);
2258
+ }
2259
+ const mode = this.options.mode.replace("single", "boolean");
2260
+ let data;
2261
+ try {
2262
+ const res = await fetch(
2263
+ `https://opentdb.com/api.php?amount=1&type=${mode}&difficulty=${this.difficulty}&encode=url3986`
2264
+ );
2265
+ const json = await res.json();
2266
+ if (json.response_code === 5) {
2267
+ throw new Error("Ratelimited by opentdb");
2268
+ }
2269
+ data = triviaAPISchema.parse(json);
2270
+ data = data.results[0];
2271
+ } catch (err) {
2272
+ this.emit("error", err);
2273
+ return null;
2274
+ }
2275
+ if (!data) {
2276
+ return null;
2277
+ }
2278
+ const trivia = {
2279
+ question: decodeURIComponent(data.question),
2280
+ difficulty: decodeURIComponent(data.difficulty),
2281
+ category: decodeURIComponent(data.category),
2282
+ answer: decodeURIComponent(data.correct_answer),
2283
+ options: []
2284
+ };
2285
+ if (mode === "multiple") {
2286
+ data.incorrect_answers.push(data.correct_answer);
2287
+ trivia.options = shuffleArray(data.incorrect_answers).map((a) => decodeURIComponent(a));
2288
+ } else {
2289
+ trivia.options = ["True", "False"];
2290
+ }
2291
+ return trivia;
2292
+ }
2293
+ };
2294
+
2295
+ // src/games/Wordle.ts
2296
+ import z12 from "zod/v4";
2297
+ import { createCanvas as createCanvas2 } from "@napi-rs/canvas";
2298
+ import { AttachmentBuilder as AttachmentBuilder2 } from "discord.js";
2299
+
2300
+ // src/data/wordle.json
2301
+ var wordle_default = [
2302
+ "APPLE",
2303
+ "BEACH",
2304
+ "CHAIR",
2305
+ "DREAM",
2306
+ "EAGLE",
2307
+ "FIGHT",
2308
+ "GRADE",
2309
+ "HEART",
2310
+ "IDEAS",
2311
+ "JELLY",
2312
+ "KNIFE",
2313
+ "LIGHT",
2314
+ "MOUSE",
2315
+ "NOVEL",
2316
+ "OCEAN",
2317
+ "PLANT",
2318
+ "QUICK",
2319
+ "READY",
2320
+ "SHEEP",
2321
+ "TIGER",
2322
+ "UNION",
2323
+ "VALUE",
2324
+ "WHALE",
2325
+ "YOUNG",
2326
+ "ZEBRA",
2327
+ "ABOVE",
2328
+ "BLANK",
2329
+ "CRISP",
2330
+ "DIRTY",
2331
+ "ERROR",
2332
+ "FLUID",
2333
+ "GIANT",
2334
+ "HAPPY",
2335
+ "INDEX",
2336
+ "JOKER",
2337
+ "KNEEL",
2338
+ "LEMON",
2339
+ "MAGIC",
2340
+ "NIGHT",
2341
+ "ORDER",
2342
+ "POWER",
2343
+ "RIVER",
2344
+ "SCOPE",
2345
+ "SNAKE",
2346
+ "TABLE",
2347
+ "ULTRA",
2348
+ "VOICE",
2349
+ "WATCH",
2350
+ "YIELD",
2351
+ "ZONAL"
2352
+ ];
2353
+
2354
+ // src/games/Wordle.ts
2355
+ var defaultOptions11 = {
2356
+ embed: {
2357
+ title: "Wordle",
2358
+ color: colors.blurple
2359
+ },
2360
+ winMessage: /* @__PURE__ */ __name((res) => `You won! The word was **${res.word}**.`, "winMessage"),
2361
+ loseMessage: /* @__PURE__ */ __name((res) => `You lost! The word was **${res.word}**.`, "loseMessage"),
2362
+ notOwnedMessage: /* @__PURE__ */ __name((game) => `Only ${game.player} can use this menu.`, "notOwnedMessage"),
2363
+ timeout: 12e4
2364
+ };
2365
+ var wordleOptions = z12.object({
2366
+ embed: embedBuilder().optional().default(defaultOptions11.embed),
2367
+ endEmbed: embedBuilder().optional(),
2368
+ winMessage: resultMessage().optional().default(() => defaultOptions11.winMessage),
2369
+ loseMessage: resultMessage().optional().default(() => defaultOptions11.loseMessage),
2370
+ timeout: z12.number().int().optional().default(defaultOptions11.timeout),
2371
+ word: z12.string().length(5).optional()
2372
+ });
2373
+ var Wordle = class extends Game {
2374
+ static {
2375
+ __name(this, "Wordle");
2376
+ }
2377
+ options;
2378
+ /**
2379
+ * The word to guess, in lowercase. Either the one specified or a random one.
2380
+ */
2381
+ word;
2382
+ guesses = [];
2383
+ message = null;
2384
+ collector = null;
2385
+ constructor(context, options) {
2386
+ super(context);
2387
+ this.options = wordleOptions.parse(options || {});
2388
+ this.word = (this.options.word || getRandomElement(wordle_default)).toLowerCase();
2389
+ }
2390
+ async start() {
2391
+ const embed = await this.buildEmbed(this.options.embed, {
2392
+ image: { url: "attachment://board.png" }
2393
+ });
2394
+ this.message = await this.sendMessage({ embeds: [embed], files: [await this.getBoardAttachment()] });
2395
+ const message = this.message;
2396
+ if (!message.channel.isSendable()) return;
2397
+ const collector = message.channel.createMessageCollector({
2398
+ idle: this.options.timeout,
2399
+ filter: /* @__PURE__ */ __name((msg) => msg.author.id === this.player.id && msg.content.length === 5, "filter")
2400
+ });
2401
+ this.collector = collector;
2402
+ collector.on("collect", async (msg) => {
2403
+ const guess = msg.content.toLowerCase();
2404
+ if (msg.deletable) {
2405
+ try {
2406
+ await msg.delete();
2407
+ } catch (err) {
2408
+ this.emit("error", err);
2409
+ }
2410
+ }
2411
+ this.guesses.push(guess);
2412
+ if (this.word === guess || this.guesses.length > 5) return collector.stop("$end");
2413
+ try {
2414
+ await this.editContextOrMessage(message, { embeds: [embed], files: [await this.getBoardAttachment()] });
2415
+ } catch (err) {
2416
+ this.emit("error", err);
2417
+ }
2418
+ });
2419
+ collector.on("end", async (_, reason) => {
2420
+ this.emit("end");
2421
+ if (reason === "$end" || reason === "idle") {
2422
+ await this.gameOver(message, reason === "idle");
2423
+ }
2424
+ });
2425
+ }
2426
+ async gameOver(message, isTimeout) {
2427
+ const result = this.result({
2428
+ outcome: isTimeout ? "timeout" : this.guesses.includes(this.word) ? "win" : "lose",
2429
+ word: this.word,
2430
+ guesses: this.guesses
2431
+ });
2432
+ this.emit("gameOver", result);
2433
+ const embed = await this.buildEndEmbed(this.options.embed, this.options.endEmbed, result, {
2434
+ image: {
2435
+ url: "attachment://board.png"
2436
+ },
2437
+ fields: [
2438
+ {
2439
+ name: "Game Over",
2440
+ value: result.outcome === "win" ? this.options.winMessage(result, this) : this.options.loseMessage(result, this)
2441
+ }
2442
+ ]
2443
+ });
2444
+ try {
2445
+ await this.editContextOrMessage(message, {
2446
+ embeds: [embed],
2447
+ files: [await this.getBoardAttachment()]
2448
+ });
2449
+ } catch (err) {
2450
+ this.emit("error", err);
2451
+ }
2452
+ }
2453
+ async getBoardAttachment() {
2454
+ return new AttachmentBuilder2(await this.getBoardImage(), { name: "board.png" });
2455
+ }
2456
+ async getBoardImage() {
2457
+ const rows = 6;
2458
+ const cols = 5;
2459
+ const tile = 88;
2460
+ const gap = 12;
2461
+ const padding = 20;
2462
+ const width = padding * 2 + cols * tile + (cols - 1) * gap;
2463
+ const height = padding * 2 + rows * tile + (rows - 1) * gap;
2464
+ const canvas = createCanvas2(width, height);
2465
+ const ctx = canvas.getContext("2d");
2466
+ const PALETTE = {
2467
+ green: "#6aaa64",
2468
+ yellow: "#c9b458",
2469
+ gray: "#3a3a3c",
2470
+ empty: "#121213",
2471
+ border: "#3a3a3c",
2472
+ bg: "#121213",
2473
+ textLight: "#ffffff"
2474
+ };
2475
+ ctx.fillStyle = PALETTE.bg;
2476
+ ctx.fillRect(0, 0, width, height);
2477
+ ctx.textAlign = "center";
2478
+ ctx.textBaseline = "middle";
2479
+ ctx.font = `700 ${Math.floor(tile * 0.54)}px sans-serif`;
2480
+ function scoreGuess(guess, answer) {
2481
+ const res = Array(cols).fill(PALETTE.gray);
2482
+ const a = answer.split("");
2483
+ const g = guess.split("");
2484
+ const counts = {};
2485
+ for (let i = 0; i < a.length; i++) counts[a[i]] = (counts[a[i]] || 0) + 1;
2486
+ for (let i = 0; i < cols; i++) {
2487
+ if (g[i] === a[i]) {
2488
+ res[i] = PALETTE.green;
2489
+ counts[g[i]] = Math.max(0, counts[g[i]] - 1);
2490
+ }
2491
+ }
2492
+ for (let i = 0; i < cols; i++) {
2493
+ if (res[i] === PALETTE.green) continue;
2494
+ const ch = g[i];
2495
+ if (counts[ch] && counts[ch] > 0) {
2496
+ res[i] = PALETTE.yellow;
2497
+ counts[ch]--;
2498
+ } else {
2499
+ res[i] = PALETTE.gray;
2500
+ }
2501
+ }
2502
+ return res;
2503
+ }
2504
+ __name(scoreGuess, "scoreGuess");
2505
+ for (let r = 0; r < rows; r++) {
2506
+ const guess = this.guesses[r];
2507
+ const colors2 = guess ? scoreGuess(guess.toLowerCase(), this.word.toLowerCase()) : Array(cols).fill(PALETTE.empty);
2508
+ for (let c = 0; c < cols; c++) {
2509
+ const x = padding + c * (tile + gap);
2510
+ const y = padding + r * (tile + gap);
2511
+ ctx.fillStyle = colors2[c] === PALETTE.empty ? PALETTE.empty : colors2[c];
2512
+ const radius = 8;
2513
+ ctx.beginPath();
2514
+ ctx.moveTo(x + radius, y);
2515
+ ctx.lineTo(x + tile - radius, y);
2516
+ ctx.quadraticCurveTo(x + tile, y, x + tile, y + radius);
2517
+ ctx.lineTo(x + tile, y + tile - radius);
2518
+ ctx.quadraticCurveTo(x + tile, y + tile, x + tile - radius, y + tile);
2519
+ ctx.lineTo(x + radius, y + tile);
2520
+ ctx.quadraticCurveTo(x, y + tile, x, y + tile - radius);
2521
+ ctx.lineTo(x, y + radius);
2522
+ ctx.quadraticCurveTo(x, y, x + radius, y);
2523
+ ctx.closePath();
2524
+ ctx.fill();
2525
+ ctx.lineWidth = 4;
2526
+ ctx.strokeStyle = colors2[c] === PALETTE.empty ? PALETTE.border : colors2[c];
2527
+ ctx.stroke();
2528
+ const ch = guess ? (guess[c] || "").toUpperCase() : "";
2529
+ if (ch) {
2530
+ ctx.fillStyle = PALETTE.textLight;
2531
+ ctx.fillText(ch, x + tile / 2, y + tile / 2 + 2);
2532
+ }
2533
+ }
2534
+ }
2535
+ return canvas.toBuffer("image/png");
2536
+ }
2537
+ };
2538
+ export {
2539
+ Connect4,
2540
+ FastType,
2541
+ Flood,
2542
+ Game2048,
2543
+ Memory,
2544
+ Minesweeper,
2545
+ RockPaperScissors,
2546
+ TicTacToe,
2547
+ Trivia,
2548
+ Wordle,
2549
+ connect4Options,
2550
+ fastTypeOptions,
2551
+ floodOptions,
2552
+ game2048Options,
2553
+ jokerEmoji,
2554
+ memoryOptions,
2555
+ minesweeperOptions,
2556
+ rockPaperScissorsOptions,
2557
+ ticTacToeOptions,
2558
+ triviaOptions,
2559
+ wordleOptions
2560
+ };
2561
+ //# sourceMappingURL=index.mjs.map