@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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/index.d.mts +2145 -0
- package/dist/index.d.ts +2145 -0
- package/dist/index.js +2614 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2561 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +66 -0
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
|