wynham-games 1.0.0-beta.13 → 1.0.0-beta.15

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.
Files changed (70) hide show
  1. package/README.md +149 -153
  2. package/dist/games/Connect4.d.ts +6 -8
  3. package/dist/games/Connect4.d.ts.map +1 -1
  4. package/dist/games/Connect4.js +44 -105
  5. package/dist/games/Connect4.js.map +1 -1
  6. package/dist/games/Hangman.d.ts +8 -9
  7. package/dist/games/Hangman.d.ts.map +1 -1
  8. package/dist/games/Hangman.js +83 -140
  9. package/dist/games/Hangman.js.map +1 -1
  10. package/dist/games/RPS.d.ts +12 -110
  11. package/dist/games/RPS.d.ts.map +1 -1
  12. package/dist/games/RPS.js +63 -226
  13. package/dist/games/RPS.js.map +1 -1
  14. package/dist/games/Snake.d.ts +6 -9
  15. package/dist/games/Snake.d.ts.map +1 -1
  16. package/dist/games/Snake.js +68 -108
  17. package/dist/games/Snake.js.map +1 -1
  18. package/dist/games/TicTacToe.d.ts +8 -9
  19. package/dist/games/TicTacToe.d.ts.map +1 -1
  20. package/dist/games/TicTacToe.js +75 -131
  21. package/dist/games/TicTacToe.js.map +1 -1
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/locales/en/en-US.d.ts +8 -5
  27. package/dist/locales/en/en-US.d.ts.map +1 -1
  28. package/dist/locales/en/en-US.js +15 -12
  29. package/dist/locales/en/en-US.js.map +1 -1
  30. package/dist/locales/es/es.d.ts +8 -5
  31. package/dist/locales/es/es.d.ts.map +1 -1
  32. package/dist/locales/es/es.js +15 -12
  33. package/dist/locales/es/es.js.map +1 -1
  34. package/dist/locales/fr/fr.d.ts +8 -5
  35. package/dist/locales/fr/fr.d.ts.map +1 -1
  36. package/dist/locales/fr/fr.js +15 -12
  37. package/dist/locales/fr/fr.js.map +1 -1
  38. package/dist/locales/index.d.ts +32 -124
  39. package/dist/locales/index.d.ts.map +1 -1
  40. package/dist/locales/index.js +1 -1
  41. package/dist/locales/index.js.map +1 -1
  42. package/dist/locales/it/it.d.ts +8 -5
  43. package/dist/locales/it/it.d.ts.map +1 -1
  44. package/dist/locales/it/it.js +15 -12
  45. package/dist/locales/it/it.js.map +1 -1
  46. package/dist/structures/BaseGame.d.ts +39 -0
  47. package/dist/structures/BaseGame.d.ts.map +1 -0
  48. package/dist/structures/BaseGame.js +87 -0
  49. package/dist/structures/BaseGame.js.map +1 -0
  50. package/dist/structures/Errors.d.ts +96 -0
  51. package/dist/structures/Errors.d.ts.map +1 -0
  52. package/dist/structures/Errors.js +264 -0
  53. package/dist/structures/Errors.js.map +1 -0
  54. package/dist/structures/GameManager.d.ts +18 -13
  55. package/dist/structures/GameManager.d.ts.map +1 -1
  56. package/dist/structures/GameManager.js +136 -142
  57. package/dist/structures/GameManager.js.map +1 -1
  58. package/dist/structures/Types.d.ts +152 -66
  59. package/dist/structures/Types.d.ts.map +1 -1
  60. package/dist/structures/Types.js +42 -8
  61. package/dist/structures/Types.js.map +1 -1
  62. package/dist/structures/Utils.d.ts +24 -27
  63. package/dist/structures/Utils.d.ts.map +1 -1
  64. package/dist/structures/Utils.js +90 -100
  65. package/dist/structures/Utils.js.map +1 -1
  66. package/dist/variables/Game.d.ts +0 -1
  67. package/dist/variables/Game.d.ts.map +1 -1
  68. package/dist/variables/Game.js +11 -77
  69. package/dist/variables/Game.js.map +1 -1
  70. package/package.json +4 -4
package/README.md CHANGED
@@ -1,24 +1,15 @@
1
1
  # wynham-games
2
2
 
3
- An open-source Node.js library built on top of [Discord.js](https://discord.js.org/) that lets you create and manage interactive mini-games inside your Discord bot with a single function call.
3
+ Mini-games for Discord bots, built on [discord.js](https://discord.js.org/). You get Connect Four, Tic-Tac-Toe, Rock Paper Scissors, Hangman and Snake, each started with one call.
4
4
 
5
- It ships with player invitations, anti-spam cooldowns, bot opponents (with difficulty levels), full theming, and built-in translations.
6
-
7
- ## Features
8
-
9
- - 🎮 **5 ready-to-play games** — Connect Four, Tic-Tac-Toe, Rock Paper Scissors, Hangman and Snake.
10
- - 🤖 **Play against the bot** — set player 2 to your client user and pick a difficulty (`Easy` → `Impossible`).
11
- - 👥 **Player-vs-player invites** — the challenged user gets an Accept / Decline prompt before the game starts.
12
- - 🛡️ **Anti-spam & cooldowns** — built-in protection so the same users can't flood your bot with games.
13
- - 🌍 **i18n out of the box** — English, French, Spanish and Italian, selectable per manager or per game.
14
- - 🎨 **Theming** — customise embed colors, emojis, titles and input mode (buttons or select menu).
5
+ The library handles the boring parts for you: the invite your opponent accepts or declines, turn timers, anti-spam cooldowns, a Tic-Tac-Toe bot with four difficulty levels, and translations in English, French, Spanish and Italian.
15
6
 
16
7
  ## Requirements
17
8
 
18
- - Node.js `>=18`
19
- - [`discord.js`](https://www.npmjs.com/package/discord.js) `^14.23.0` (peer dependency — install it in your bot project).
9
+ - Node.js 22 or later (22, 24 and 26 are the versions Node.js still maintains)
10
+ - discord.js `^14.23.0`, installed in your bot (it is a peer dependency)
20
11
 
21
- > `14.23.0` is a hard floor: the library renders its boards with **Components V2** (`ContainerBuilder`, `MessageFlags.IsComponentsV2`) and uses the modal `LabelBuilder`, which is only guaranteed from `@discordjs/builders` `1.12.0` — the first version pulled in by discord.js `14.23.0`.
12
+ The boards use Components V2 and the modal `LabelBuilder`. Older discord.js versions don't ship them, so 14.23.0 is the minimum.
22
13
 
23
14
  ## Installation
24
15
 
@@ -28,25 +19,21 @@ npm install wynham-games@beta discord.js
28
19
 
29
20
  ## Quick start
30
21
 
31
- The whole library revolves around a single class: **`GameManager`**. You instantiate it once with your Discord client, then call `createGame(...)` from a slash command interaction.
22
+ Create one `GameManager` next to your client, then call `createGame` from your slash commands.
32
23
 
33
24
  ```ts
34
25
  import { Client, GatewayIntentBits } from "discord.js";
35
26
  import { GameManager, GameNameType } from "wynham-games";
36
27
 
37
28
  const client = new Client({ intents: [GatewayIntentBits.Guilds] });
38
-
39
- // Create the manager once, ideally next to your client setup.
40
29
  const games = new GameManager(client);
41
30
 
42
31
  client.on("interactionCreate", async interaction => {
43
32
  if (!interaction.isChatInputCommand()) return;
44
33
 
45
34
  if (interaction.commandName === "connect4") {
46
- const opponent = interaction.options.getUser("opponent", true);
47
-
48
35
  await games.createGame(GameNameType.Connect4, interaction, {
49
- players: [interaction.user, opponent],
36
+ players: [interaction.user, interaction.options.getUser("opponent", true)],
50
37
  });
51
38
  }
52
39
  });
@@ -54,83 +41,83 @@ client.on("interactionCreate", async interaction => {
54
41
  client.login(process.env.TOKEN);
55
42
  ```
56
43
 
57
- That's it — `createGame` automatically defers the reply, shows the invite, waits for the opponent, and runs the game to completion.
44
+ `createGame` defers the reply if you haven't, sends the invite, waits for the answer and runs the game until it ends.
58
45
 
59
- ## Core concept: `GameManager`
60
-
61
- ### Constructor
46
+ ## GameManager
62
47
 
63
48
  ```ts
64
49
  new GameManager(client: Client, options?: GameManagerOptions)
65
50
  ```
66
51
 
67
- | Option | Type | Default | Description |
68
- | ----------------- | --------- | ------------------ | ------------------------------------------------------------------------------------ |
69
- | `antiSpam.enable` | `boolean` | `true` | Prevents the same pair of players from starting back-to-back games. |
70
- | `antiSpam.time` | `number` | `5000` | Cooldown duration in milliseconds. |
71
- | `locale` | `Locale` | `Locale.EnglishUS` | Default language for every game created by this manager. Can be overridden per game. |
52
+ | Option | Type | Default | Description |
53
+ | ----------------- | --------- | ------------------ | ----------------------------------------------------------- |
54
+ | `antiSpam.enable` | `boolean` | `true` | Blocks the same players from starting games back to back. |
55
+ | `antiSpam.time` | `number` | `5000` | Anti-spam cooldown, in milliseconds. |
56
+ | `locale` | `Locale` | `Locale.EnglishUS` | Language of every game. A game's own `locale` overrides it. |
57
+ | `colors.play` | `number` | `0x5865f2` | Board color while a game runs. |
58
+ | `colors.win` | `number` | `0xffd700` | Board color when someone wins. |
59
+ | `colors.end` | `number` | `0x808080` | Board color on a draw, a loss or a timeout. |
60
+
61
+ The colors apply to every game and menu of the manager. A game's `theme.colors` still wins over them, so you can set your brand colors once and tweak a single game when you need to.
72
62
 
73
63
  ```ts
74
64
  import { Locale } from "discord.js";
75
65
 
76
66
  const games = new GameManager(client, {
77
- antiSpam: { enable: true, time: 10_000 },
67
+ antiSpam: { time: 10_000 },
78
68
  locale: Locale.French,
69
+ colors: { play: 0x2b2d31, win: 0x57f287 },
79
70
  });
80
71
  ```
81
72
 
82
- ### `createGame(gameName, interaction, options)`
83
-
84
- Validates the players, displays the invitation/difficulty menu when needed, and launches the game.
73
+ ### createGame(gameName, interaction, options)
85
74
 
86
75
  ```ts
87
- createGame(gameName: GameNameType, interaction: Interaction, options): Promise<void>
76
+ createGame<G extends GameNameType>(gameName: G, interaction: Interaction, options: GameOptionsMap[G]): Promise<void>
88
77
  ```
89
78
 
90
- - **`gameName`** — one of the `GameNameType` enum values.
91
- - **`interaction`** — a repliable slash-command interaction. It is deferred automatically if it hasn't been already.
92
- - **`options`** — the per-game options object (always contains the `players` tuple, see below).
93
-
94
- The method is overloaded, so TypeScript infers the correct options type from the `gameName` you pass.
79
+ - `gameName`: a `GameNameType` value.
80
+ - `interaction`: a repliable interaction, usually a slash command.
81
+ - `options`: the options of that game. TypeScript picks the right type from `gameName`, so passing `players` to Snake or `lifeCount` to Connect Four fails to compile.
95
82
 
96
- #### Bot opponents
83
+ ### Who can play against whom
97
84
 
98
- Only **RPS** and **Tic-Tac-Toe** support a bot opponent (`players[1].bot === true`):
85
+ - **Connect Four** and **Hangman** need two humans. The second player gets an Accept / Decline invite.
86
+ - **Rock Paper Scissors** accepts a bot as player 2. The game starts at once and the bot picks at random.
87
+ - **Tic-Tac-Toe** accepts a bot as player 2. The challenger picks a difficulty first, unless you set `difficulty`.
88
+ - **Snake** is single-player. It takes a `player` instead of `players` and starts without an invite.
99
89
 
100
- - **RPS** runs immediately against the bot (the bot plays a random choice).
101
- - **Tic-Tac-Toe** first shows a difficulty-selection menu, then runs against the bot.
90
+ A player can't challenge themselves, and the challenger can't be a bot.
102
91
 
103
- **Connect Four** and **Hangman** are player-vs-player only — passing a bot as the second player is rejected.
92
+ ### Timers
104
93
 
105
- For human-vs-human, the second player is sent an **Accept / Decline** invite and the game only starts once they accept.
94
+ Each game ends after `turnTimeout` seconds without a move. Tic-Tac-Toe, Hangman, Rock Paper Scissors and Snake show a live countdown under the board ("The game ends in 25 seconds if nobody plays."). In Connect Four, a missed turn passes to the other player, and the game is cancelled once both players miss in a row.
106
95
 
107
- **Snake** is single-player: it takes a single `player` instead of a `players` tuple, and starts immediately with no invite.
96
+ Connect Four and Hangman mention the player whose turn it is, so the message lights up for them. Discord doesn't send a notification for a mention added by an edit, though.
108
97
 
109
- ## Games & options
110
-
111
- Every option object shares the `players` field and most share `turnTimeout`, `locale`, `theme` and `lang`. The table below lists what is specific to each game.
98
+ ## Game options
112
99
 
113
100
  ### Shared options
114
101
 
115
- | Option | Type | Default | Description |
116
- | ------------------ | -------------- | ---------------- | -------------------------------------------------------------- |
117
- | `players` | `[User, User]` | — | **Required.** The two participants. Player 2 may be the bot. |
118
- | `turnTimeout` | `number` | `30` | Max seconds a player has to act before the game ends (15–360). |
119
- | `locale` | `Locale` | manager's locale | Overrides the manager language for this game only. |
120
- | `theme.colors` | `object` | see below | Embed colors for play / win / draw states. |
121
- | `lang.title` | `string` | game name | Title shown on the game board. |
122
- | `lang.inviteTitle` | `string` | `"… Challenge"` | Title shown in the invite message. |
123
-
124
- ### `GameNameType.Connect4` — `Connect4Options`
125
-
126
- | Option | Type | Default | Description |
127
- | ---------------------- | ----------- | --------- | --------------------------------------------- |
128
- | `theme.emojis.player1` | `string` | `🔴` | Player 1 pieces. |
129
- | `theme.emojis.player2` | `string` | `🟡` | Player 2 pieces. |
130
- | `theme.emojis.empty` | `string` | `⬛` | Empty cells. |
131
- | `theme.emojis.win` | `string` | `⚪` | Winning pieces. |
132
- | `theme.inputType` | `InputType` | `Buttons` | Column input mode: `Buttons` or `SelectMenu`. |
133
- | `theme.buttonStyle` | `ButtonStyle` | `Primary` | Style of the column input buttons: `Primary`, `Secondary`, `Success`, or `Danger`. |
102
+ | Option | Type | Default | Description |
103
+ | ------------------ | -------------- | --------------- | ------------------------------------------------------------ |
104
+ | `players` | `[User, User]` | required | Challenger first, opponent second. |
105
+ | `turnTimeout` | `number` | `30` | Seconds without a move before the game ends, from 15 to 360. |
106
+ | `locale` | `Locale` | manager locale | Language of this game only. |
107
+ | `theme.colors` | `object` | manager colors | Board colors for this game only. |
108
+ | `lang.title` | `string` | game name | Title on the board. |
109
+ | `lang.inviteTitle` | `string` | `"… Challenge"` | Title of the invite. |
110
+
111
+ ### Connect Four: `Connect4Options`
112
+
113
+ | Option | Type | Default | Description |
114
+ | ---------------------- | ------------- | --------- | ------------------------------------------------- |
115
+ | `theme.emojis.player1` | `string` | `🔴` | Player 1 pieces. |
116
+ | `theme.emojis.player2` | `string` | `🟡` | Player 2 pieces. |
117
+ | `theme.emojis.empty` | `string` | `⬛` | Empty cells. |
118
+ | `theme.emojis.win` | `string` | `⚪` | Pieces of the winning line. |
119
+ | `theme.inputType` | `InputType` | `Buttons` | Pick a column with buttons or with a select menu. |
120
+ | `theme.buttonStyle` | `ButtonStyle` | `Primary` | `Primary`, `Secondary`, `Success` or `Danger`. |
134
121
 
135
122
  ```ts
136
123
  import { GameNameType, InputType } from "wynham-games";
@@ -141,135 +128,144 @@ await games.createGame(GameNameType.Connect4, interaction, {
141
128
  turnTimeout: 45,
142
129
  theme: {
143
130
  emojis: { player1: "🔵", player2: "🔴" },
144
- inputType: InputType.Buttons,
131
+ inputType: InputType.SelectMenu,
145
132
  buttonStyle: ButtonStyle.Success,
146
133
  },
147
134
  });
148
135
  ```
149
136
 
150
- ### `GameNameType.TicTacToe` — `TicTacToeOptions`
137
+ ### Tic-Tac-Toe: `TicTacToeOptions`
151
138
 
152
- | Option | Type | Default | Description |
153
- | ---------------------- | --------------- | ------- | ------------------------------------------------------ |
154
- | `difficulty` | `BotDifficulty` | `Hard` | Bot strength when player 2 is a bot — see table below. |
155
- | `theme.emojis.player1` | `string` | `❌` | Player 1 marks. |
156
- | `theme.emojis.player2` | `string` | `⭕` | Player 2 marks. |
157
- | `theme.emojis.empty` | `string` | `⬜` | Empty cells. |
139
+ | Option | Type | Default | Description |
140
+ | ---------------------- | --------------- | ------- | ------------------------------------------ |
141
+ | `difficulty` | `BotDifficulty` | menu | Bot strength, used when player 2 is a bot. |
142
+ | `theme.emojis.player1` | `string` | `❌` | Player 1 marks. |
143
+ | `theme.emojis.player2` | `string` | `⭕` | Player 2 marks. |
144
+ | `theme.emojis.empty` | `string` | `⬜` | Empty cells. |
158
145
 
159
- The bot opponent behaves differently per difficulty:
146
+ | Difficulty | Bot behaviour |
147
+ | ------------ | ---------------------------------------------- |
148
+ | `Easy` | Random moves. |
149
+ | `Normal` | Best move half the time, random otherwise. |
150
+ | `Hard` | Best move 80% of the time. |
151
+ | `Impossible` | Best move every turn (minimax). You can't win. |
160
152
 
161
- | Difficulty | Behaviour |
162
- | ------------ | ---------------------------------------------------------- |
163
- | `Easy` | Always plays a random move. |
164
- | `Normal` | Plays the optimal move ~50% of the time, random otherwise. |
165
- | `Hard` | Plays the optimal move ~80% of the time. |
166
- | `Impossible` | Always optimal (minimax, unbeatable). |
153
+ Leave `difficulty` out and the challenger picks it from a menu before the game. Set it and the game starts straight away.
167
154
 
168
- ### `GameNameType.RPS` — `RPSOptions`
155
+ ### Rock Paper Scissors: `RPSOptions`
169
156
 
170
- | Option | Type | Default | Description |
171
- | ----------------------- | -------- | ------- | --------------- |
172
- | `theme.emojis.rock` | `string` | `✊` | Rock emoji. |
173
- | `theme.emojis.paper` | `string` | `🤚` | Paper emoji. |
174
- | `theme.emojis.scissors` | `string` | `✌️` | Scissors emoji. |
157
+ | Option | Type | Default |
158
+ | ----------------------- | -------- | ------- |
159
+ | `theme.emojis.rock` | `string` | `✊` |
160
+ | `theme.emojis.paper` | `string` | `🤚` |
161
+ | `theme.emojis.scissors` | `string` | `✌️` |
175
162
 
176
- ### `GameNameType.Hangman` — `HangmanOptions`
163
+ If only one player picks before the timer runs out, that player wins by default.
177
164
 
178
- | Option | Type | Default | Description |
179
- | ----------- | ------- | ------- | -------------------------------- |
180
- | `lifeCount` | `1`–`9` | `6` | Number of wrong guesses allowed. |
165
+ ### Hangman: `HangmanOptions`
181
166
 
182
- > The Hangman word list is taken from the active locale, so changing `locale` also changes the language of the words to guess.
167
+ | Option | Type | Default | Description |
168
+ | ----------- | ---------- | ------- | ------------------------------ |
169
+ | `lifeCount` | `1` to `9` | `6` | Wrong guesses before you lose. |
183
170
 
184
- ### `GameNameType.Snake` — `SnakeOptions`
171
+ Players take turns picking a letter or guessing the whole word. A wrong word costs two lives. The word list comes from the locale, so a French game gives French words.
185
172
 
186
- > **Single-player.** Snake does **not** use the shared `players` / `lang.inviteTitle` fields. It takes a single `player` and starts immediately — no invite is sent.
173
+ ### Snake: `SnakeOptions`
187
174
 
188
- | Option | Type | Default | Description |
189
- | -------------------- | -------- | ------- | ------------------------------------------------------------------- |
190
- | `player` | `User` | — | **Required.** The player (must not be a bot). |
191
- | `turnTimeout` | `number` | `30` | Max seconds the player can stay idle between two moves (15–360). |
192
- | `boardWidth` | `number` | `10` | Board width in cells (clamped 5–12). |
193
- | `boardHeight` | `number` | `10` | Board height in cells (clamped 5–12). |
194
- | `theme.colors.lose` | `number` | `0x808080` | Color on game over / timeout (replaces the shared `draw` color). |
195
- | `theme.emojis.head` | `string` | `🟢` | Snake head. |
196
- | `theme.emojis.body` | `string` | `🟩` | Snake body. |
197
- | `theme.emojis.food` | `string` | `🍎` | Food. |
198
- | `theme.emojis.empty` | `string` | `⬛` | Empty cells. |
199
- | `lang.title` | `string` | `"Snake"` | Title shown on the game board. |
175
+ | Option | Type | Default | Description |
176
+ | -------------------- | -------- | ------------- | ------------------------------------------ |
177
+ | `player` | `User` | required | The player. Bots are refused. |
178
+ | `boardWidth` | `number` | `10` | Width in cells, clamped between 5 and 12. |
179
+ | `boardHeight` | `number` | `10` | Height in cells, clamped between 5 and 12. |
180
+ | `theme.colors.lose` | `number` | manager `end` | Color on game over or timeout. |
181
+ | `theme.emojis.head` | `string` | `🟢` | Head. |
182
+ | `theme.emojis.body` | `string` | `🟩` | Body. |
183
+ | `theme.emojis.food` | `string` | `🍎` | Food. |
184
+ | `theme.emojis.empty` | `string` | `⬛` | Empty cells. |
200
185
 
201
- The player moves the snake one cell per button press (⬅️ ⬆️ ⬇️ ➡️) and can quit with ⏹️. Eating the 🍎 grows the snake and increases the score; hitting a wall or the snake's own body ends the game.
186
+ Snake also takes `turnTimeout`, `locale` and `lang.title`. Each arrow button moves the snake one cell and ⏹️ quits. Eating an apple adds a point. Hitting a wall or your own tail ends the game, and filling the whole board wins it.
202
187
 
203
188
  ```ts
204
- import { GameNameType } from "wynham-games";
205
-
206
189
  await games.createGame(GameNameType.Snake, interaction, {
207
190
  player: interaction.user,
208
- boardWidth: 10,
209
- boardHeight: 10,
191
+ boardWidth: 8,
192
+ boardHeight: 8,
210
193
  });
211
194
  ```
212
195
 
213
- ## Internationalisation
196
+ ## Languages
214
197
 
215
- Built-in locales are keyed by the Discord.js `Locale` enum:
216
-
217
- | Locale | Enum value |
198
+ | Language | Value |
218
199
  | ------------ | ------------------ |
219
200
  | English (US) | `Locale.EnglishUS` |
220
201
  | French | `Locale.French` |
221
- | Spanish (ES) | `Locale.SpanishES` |
202
+ | Spanish | `Locale.SpanishES` |
222
203
  | Italian | `Locale.Italian` |
223
204
 
224
- You can resolve a player's own language and fall back to English:
205
+ To speak each player's language, pass `interaction.locale` (or `interaction.guildLocale`) as the game's `locale`. An unsupported locale falls back to English.
225
206
 
226
207
  ```ts
227
- import { locales, Locale } from "wynham-games";
228
-
229
- const locale = locales[interaction.locale] ?? locales[Locale.EnglishUS];
208
+ await games.createGame(GameNameType.Hangman, interaction, {
209
+ players: [interaction.user, opponent],
210
+ locale: interaction.locale,
211
+ });
230
212
  ```
231
213
 
232
- Or simply pass `interaction.locale` (or `interaction.guildLocale`) as the `locale` option when creating a game.
214
+ The `GameManager` constructor rejects an unsupported `locale` with a `LocaleNotSupported` error.
215
+
216
+ ## Errors
217
+
218
+ The library wraps every Discord call it makes. Raw `DiscordAPIError`s come back as a `WynhamGamesError` with a stable `code`, so you never compare Discord error numbers yourself.
233
219
 
234
- ## Enums reference
220
+ Some failures only mean the game can't go on: the message was deleted (`10008`), the interaction expired (`10062`, `50027`), the channel is gone (`10003`), the interaction was already answered (`40060`) or the thread got archived (`50083`). The game then stops quietly and nothing is thrown.
221
+
222
+ Anything else throws. That covers missing permissions (`50013`), missing access (`50001`), an invalid payload (`50035`), rate limits and network errors. `createGame()` rejects, and your command handler can catch it:
235
223
 
236
224
  ```ts
237
- enum GameNameType {
238
- Connect4,
239
- RPS,
240
- TicTacToe,
241
- Hangman,
242
- Snake,
243
- }
244
- enum BotDifficulty {
245
- Easy,
246
- Normal,
247
- Hard,
248
- Impossible,
249
- }
250
- enum InputType {
251
- SelectMenu,
252
- Buttons,
253
- }
254
- enum Choice {
255
- Rock,
256
- Paper,
257
- Scissors,
258
- }
259
- enum InviteResult {
260
- Accepted,
261
- Declined,
262
- Timeout,
225
+ import { MessageFlags } from "discord.js";
226
+ import { GameNameType, WynhamGamesError, WynhamErrorCode } from "wynham-games";
227
+
228
+ try {
229
+ await games.createGame(GameNameType.Snake, interaction, { player: interaction.user });
230
+ } catch (error) {
231
+ if (error instanceof WynhamGamesError && error.code === WynhamErrorCode.MissingPermissions) {
232
+ await interaction.followUp({ content: "I need permission to edit my messages here.", flags: MessageFlags.Ephemeral });
233
+ }
263
234
  }
264
235
  ```
265
236
 
237
+ A `WynhamGamesError` carries `code`, `discordCode`, `status`, `action` (the discord.js call that failed), `game` and `cause` (the original error).
238
+
239
+ Once `createGame()` has resolved, errors come from collectors and timers that your code can't await. The library logs them with `console.error` instead of crashing your bot. Send them to your own logger with `setErrorHandler`:
240
+
241
+ ```ts
242
+ import { setErrorHandler } from "wynham-games";
243
+
244
+ setErrorHandler(error => logger.error({ code: error.code, game: error.game }, error.message));
245
+ ```
246
+
247
+ Bad options throw the same error type before anything reaches Discord: `ClientMissing`, `LocaleNotSupported`, `InvalidAntiSpamTime`, `InvalidColor`, `InvalidTurnTimeout`, `InvalidLifeCount` and `InteractionNotRepliable`.
248
+
249
+ The guarded helpers are exported if you want the same behaviour in your own code: `safeDiscordCall`, `safeEdit`, `safeFollowUp`, `safeReply`, `safeNotify`, `safeUpdate`, `safeDeferUpdate`, `safeMessageEdit`, `toWynhamError` and `isWynhamGamesError`.
250
+
251
+ ## Enums
252
+
253
+ | Enum | Values |
254
+ | ----------------- | ------------------------------------------------------------------------ |
255
+ | `GameNameType` | `Connect4`, `RPS`, `TicTacToe`, `Hangman`, `Snake` |
256
+ | `BotDifficulty` | `Easy`, `Normal`, `Hard`, `Impossible` |
257
+ | `InputType` | `Buttons`, `SelectMenu` |
258
+ | `Choice` | `Rock`, `Paper`, `Scissors` |
259
+ | `DiscordAction` | `EditReply`, `FollowUp`, `Reply`, `Update`, `DeferUpdate`, `EditMessage` |
260
+ | `WynhamErrorCode` | See [Errors](#errors) and `src/structures/Types.ts`. |
261
+
266
262
  ## Building from source
267
263
 
268
264
  ```bash
269
- npm run build # tsc && tsc-alias
270
- npm run format # prettier --write .
265
+ npm run build # tsc && tsc-alias
266
+ npm run format # prettier --write .
271
267
  ```
272
268
 
273
269
  ## License
274
270
 
275
- MIT — Yanis Hamburger & Stefanoz.
271
+ MIT, by Yanis Hamburger and Stefanoz.
@@ -1,9 +1,7 @@
1
- import { GameNameType, type Connect4Options, type IGame, type GameManagerType, type GameLocale } from "../structures/Types";
1
+ import { BaseGame } from "../structures/BaseGame.js";
2
+ import { type Connect4Options, type GameManagerType, type GameLocale } from "../structures/Types.js";
2
3
  import { RepliableInteraction } from "discord.js";
3
- export default class Connect4 implements IGame {
4
- readonly gameName = GameNameType.Connect4;
5
- private readonly manager;
6
- private interaction;
4
+ export default class Connect4 extends BaseGame {
7
5
  private readonly players;
8
6
  private readonly colors;
9
7
  private readonly emojis;
@@ -17,9 +15,7 @@ export default class Connect4 implements IGame {
17
15
  private currentPlayer;
18
16
  private turnTimer;
19
17
  private consecutiveMisses;
20
- private collector;
21
18
  private readonly buttonStyle;
22
- private readonly cooldownKey;
23
19
  constructor(manager: GameManagerType, interaction: RepliableInteraction, options: Connect4Options, gameLocale: GameLocale);
24
20
  private cellEmoji;
25
21
  private renderBoard;
@@ -29,10 +25,12 @@ export default class Connect4 implements IGame {
29
25
  private createButtons;
30
26
  private createSelectMenu;
31
27
  private buildContainer;
28
+ /** Discord timestamp of the current turn's deadline. */
29
+ private deadline;
32
30
  private startTurnTimer;
33
31
  private handleTurnTimeout;
34
32
  launch(): Promise<void>;
35
33
  private playMove;
36
- private cleanup;
34
+ protected cleanup(): void;
37
35
  }
38
36
  //# sourceMappingURL=Connect4.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Connect4.d.ts","sourceRoot":"","sources":["../../src/games/Connect4.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAa,KAAK,eAAe,EAAE,KAAK,KAAK,EAAE,KAAK,eAAe,EAAuB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE3J,OAAO,EAcH,oBAAoB,EAEvB,MAAM,YAAY,CAAC;AAYpB,MAAM,CAAC,OAAO,OAAO,QAAS,YAAW,KAAK;IAC1C,SAAgB,QAAQ,yBAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;IACrE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyD;IAChF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IAEtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IAEpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,YAAY,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,oBAAoB,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EA0BxH;IAED,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,IAAI;IASZ,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,QAAQ;IAyBhB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,cAAc;YAKR,iBAAiB;IAwBzB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAyC5B;YAEa,QAAQ;IAoDtB,OAAO,CAAC,OAAO;CAYlB"}
1
+ {"version":3,"file":"Connect4.d.ts","sourceRoot":"","sources":["../../src/games/Connect4.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAA2B,KAAK,eAAe,EAAE,KAAK,eAAe,EAAuB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE/I,OAAO,EAYH,oBAAoB,EAEvB,MAAM,YAAY,CAAC;AAYpB,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,QAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8C;IACrE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyD;IAChF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IAEtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IAEpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAE1C,YAAY,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,oBAAoB,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAwBxH;IAED,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,QAAQ;IAyBhB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,cAAc;IAatB,wDAAwD;IACxD,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,cAAc;YAOR,iBAAiB;IAczB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAoB5B;YAEa,QAAQ;IA4BtB,UAAmB,OAAO,IAAI,IAAI,CAMjC;CACJ"}