Guest User

Flood Game Command

a guest
Jan 24th, 2025
48
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.08 KB | None | 0 0
  1. const Command = require('../../base/Command.js');
  2. const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require('discord.js');
  3. const { QuickDB } = require('quick.db');
  4. const { Duration } = require('luxon');
  5. const db = new QuickDB();
  6.  
  7. class FloodButtons extends Command {
  8. constructor(client) {
  9. super(client, {
  10. name: 'flood-buttons',
  11. description: 'Play a game of flood',
  12. usage: 'flood-buttons',
  13. category: 'Games',
  14. aliases: ['floodbuttons', 'floodbutton'],
  15. });
  16. }
  17.  
  18. async run(msg) {
  19. const WIDTH = 13;
  20. const HEIGHT = 13;
  21. const moves = 25;
  22.  
  23. const SQUARES = {
  24. red_square: '🟥',
  25. blue_square: '🟦',
  26. orange_square: '🟧',
  27. purple_square: '🟪',
  28. green_square: '🟩',
  29. };
  30. const nameToEmoji = {
  31. ':red_square:': '🟥',
  32. ':blue_square:': '🟦',
  33. ':orange_square:': '🟧',
  34. ':purple_square:': '🟪',
  35. ':green_square:': '🟩',
  36. ':octagonal_sign:': '🛑',
  37. };
  38.  
  39. const color = msg.settings.embedColor;
  40. const gameStart = msg.createdAt;
  41. const gameBoard = [];
  42. let gameOver = false;
  43. let turn = 0;
  44. let lastMove;
  45. let message;
  46. let gameEnd;
  47. let result;
  48. let collected;
  49.  
  50. const current = this.client.games.get(msg.channel.id);
  51. if (current) {
  52. return this.client.util.errorEmbed(msg, `Please wait until the current game of \`${current.name}\` is finished.`);
  53. }
  54. this.client.games.set(msg.channel.id, { name: this.help.name, user: msg.author.id });
  55.  
  56. const up = (pos) => ({ x: pos.x, y: pos.y - 1 });
  57. const down = (pos) => ({ x: pos.x, y: pos.y + 1 });
  58. const left = (pos) => ({ x: pos.x - 1, y: pos.y });
  59. const right = (pos) => ({ x: pos.x + 1, y: pos.y });
  60.  
  61. // Randomly generate the game board
  62. for (let y = 0; y < HEIGHT; y++) {
  63. for (let x = 0; x < WIDTH; x++) {
  64. gameBoard[y * WIDTH + x] = Object.values(SQUARES)[Math.floor(Math.random() * Object.keys(SQUARES).length)];
  65. }
  66. }
  67.  
  68. // Return the game board as a string
  69. function gameBoardToString() {
  70. let str = '';
  71. for (let y = 0; y < HEIGHT; y++) {
  72. for (let x = 0; x < WIDTH; x++) {
  73. str += gameBoard[y * WIDTH + x];
  74. }
  75. str += '\n';
  76. }
  77. return str;
  78. }
  79.  
  80. // Get the content of the embed
  81. async function getContent() {
  82. let embed;
  83. if (gameOver === true) {
  84. const gameTimeMillis = gameEnd - gameStart;
  85. let gameTime;
  86. if (!isNaN(gameTimeMillis))
  87. gameTime = Duration.fromMillis(gameTimeMillis).shiftTo('minutes', 'seconds').toHuman();
  88. const gameTimeSeconds = gameTimeMillis / 1000;
  89. const turnResp = {
  90. winner: `Game beat in ${turn} turns! \nGame Time: ${gameTime}`,
  91. timeOut: 'Game timed out due to inactivity.',
  92. error: 'Game ended with an error.',
  93. maxTurns: 'Game ended because you reached the max turns.',
  94. playing: "Game shouldn't have ended. :(",
  95. earlyEnd: 'Game player decided to quit.',
  96. };
  97.  
  98. let highScore;
  99. let highScoreUser;
  100. let highScoreTime;
  101. if (result === 'winner') {
  102. const HS = { score: turn, user: msg.author.tag, time: gameTimeSeconds };
  103. const oldHS = await db.get('global.highScores.flood');
  104. highScore = oldHS?.score || 0;
  105. highScoreUser = oldHS?.user || 'N/A';
  106. highScoreTime = oldHS?.time || 0;
  107. if (HS.score < highScore || !oldHS) {
  108. await db.set('global.highScores.flood', HS);
  109. highScore = HS.score;
  110. highScoreUser = 'You';
  111. highScoreTime = gameTimeSeconds;
  112. } else if (HS.score === highScore && HS.time <= highScoreTime) {
  113. await db.set('global.highScores.flood', HS);
  114. highScore = HS.score;
  115. highScoreUser = 'You';
  116. highScoreTime = gameTimeSeconds;
  117. }
  118. } else {
  119. const oldHS = await db.get('global.highScores.flood');
  120. highScore = oldHS?.score || 0;
  121. highScoreUser = oldHS?.user || 'N/A';
  122. highScoreTime = oldHS?.time || 0;
  123. }
  124.  
  125. if (!isNaN(highScoreTime)) {
  126. highScoreTime = Duration.fromMillis(highScoreTime * 1000)
  127. .shiftTo('minutes', 'seconds')
  128. .toHuman();
  129. }
  130.  
  131. embed = new EmbedBuilder()
  132. .setAuthor({ name: msg.member.displayName, iconURL: msg.author.displayAvatarURL() })
  133. .setColor(color)
  134. .setTitle('Flood')
  135. .setDescription(`${gameBoardToString()} \nGame Over! \n${turnResp[result]}`)
  136. .addFields([{ name: 'High Score', value: `${highScore} turns by ${highScoreUser} in ${highScoreTime}` }])
  137. .setTimestamp();
  138. } else {
  139. embed = new EmbedBuilder()
  140. .setAuthor({ name: msg.member.displayName, iconURL: msg.author.displayAvatarURL() })
  141. .setColor(color)
  142. .setTitle('Flood')
  143. .setDescription(
  144. `${gameBoardToString()}
  145. Fill the entire image with the same color in ${moves} or fewer flood tiles (turns).
  146.  
  147. Click on the reactions below to fill the area above.
  148. Filling starts at the top left corner.`,
  149. )
  150. .addFields([{ name: 'Turn:', value: turn.toString() }])
  151. .setFooter({ text: `Currently Playing: ${msg.author.username}` })
  152. .setTimestamp();
  153. }
  154.  
  155. return embed;
  156. }
  157.  
  158. function getButtons(gameOver) {
  159. const redSquare = new ButtonBuilder().setCustomId(':red_square:').setEmoji('🟥').setStyle(ButtonStyle.Secondary);
  160. const blueSquare = new ButtonBuilder()
  161. .setCustomId(':blue_square:')
  162. .setEmoji('🟦')
  163. .setStyle(ButtonStyle.Secondary);
  164. const orangeSquare = new ButtonBuilder()
  165. .setCustomId(':orange_square:')
  166. .setEmoji('🟧')
  167. .setStyle(ButtonStyle.Secondary);
  168. const purpleSquare = new ButtonBuilder()
  169. .setCustomId(':purple_square:')
  170. .setEmoji('🟪')
  171. .setStyle(ButtonStyle.Secondary);
  172. const greenSquare = new ButtonBuilder()
  173. .setCustomId(':green_square:')
  174. .setEmoji('🟩')
  175. .setStyle(ButtonStyle.Secondary);
  176. const octagonalSign = new ButtonBuilder()
  177. .setCustomId(':octagonal_sign:')
  178. .setEmoji('🛑')
  179. .setStyle(ButtonStyle.Secondary);
  180.  
  181. if (gameOver) {
  182. redSquare.setDisabled(true);
  183. blueSquare.setDisabled(true);
  184. orangeSquare.setDisabled(true);
  185. purpleSquare.setDisabled(true);
  186. greenSquare.setDisabled(true);
  187. octagonalSign.setDisabled(true);
  188. }
  189.  
  190. const row1 = new ActionRowBuilder().addComponents(redSquare, blueSquare, orangeSquare, purpleSquare, greenSquare);
  191. const row2 = new ActionRowBuilder().addComponents(octagonalSign);
  192.  
  193. return [row1, row2];
  194. }
  195.  
  196. try {
  197. const embed = await getContent();
  198. message = await msg.channel.send({ embeds: [embed], components: getButtons(gameOver) }).catch(console.error);
  199.  
  200. while (!gameOver && turn < moves) {
  201. const current = gameBoard[0];
  202. const queue = [{ x: 0, y: 0 }];
  203. const visited = [];
  204. let selected = null;
  205.  
  206. const embed = await getContent();
  207. message.edit({ embeds: [embed] });
  208.  
  209. collected = await message
  210. .awaitMessageComponent({
  211. filter: (i) => i.deferUpdate(),
  212. time: 60_000,
  213. })
  214. .catch(console.error);
  215.  
  216. if (!collected) {
  217. gameOver = true;
  218. result = 'timeOut';
  219. this.client.games.delete(msg.channel.id);
  220. const embed = await getContent();
  221.  
  222. return message.edit({ embeds: [embed], components: getButtons(gameOver) }).catch(console.error);
  223. }
  224.  
  225. const interactionUser = collected.user;
  226. if (interactionUser.id !== msg.author.id) {
  227. await collected.editReply({ content: `These buttons aren't for you!`, ephemeral: true }).catch(console.error);
  228. continue;
  229. }
  230.  
  231. const customId = collected.customId;
  232. selected = nameToEmoji[customId];
  233.  
  234. if (selected === '🛑') {
  235. gameOver = true;
  236. result = 'earlyEnd';
  237. this.client.games.delete(msg.channel.id);
  238. const embed = await getContent();
  239.  
  240. return collected.editReply({ embeds: [embed], components: getButtons(gameOver) }).catch(console.error);
  241. } else if (selected === lastMove) {
  242. await collected
  243. .editReply({
  244. content: "You can't flood with the same color twice in a row!",
  245. ephemeral: true,
  246. })
  247. .catch(console.error);
  248. continue;
  249. }
  250.  
  251. lastMove = selected;
  252. turn += 1;
  253.  
  254. while (queue.length > 0) {
  255. const pos = queue.shift();
  256. if (!pos || visited.includes(pos)) {
  257. continue;
  258. }
  259. visited.push(pos);
  260. if (gameBoard[pos.y * WIDTH + pos.x] === current) {
  261. gameBoard[pos.y * WIDTH + pos.x] = selected;
  262. const upPos = up(pos);
  263. if (!visited.includes(upPos) && upPos.y >= 0) {
  264. queue.push(upPos);
  265. }
  266. const downPos = down(pos);
  267. if (!visited.includes(downPos) && downPos.y < HEIGHT) {
  268. queue.push(downPos);
  269. }
  270. const leftPos = left(pos);
  271. if (!visited.includes(leftPos) && leftPos.x >= 0) {
  272. queue.push(leftPos);
  273. }
  274. const rightPos = right(pos);
  275. if (!visited.includes(rightPos) && rightPos.x < WIDTH) {
  276. queue.push(rightPos);
  277. }
  278. }
  279. }
  280.  
  281. gameOver = true;
  282. result = 'winner';
  283. gameEnd = Date.now();
  284.  
  285. for (let y = 0; y < HEIGHT; y++) {
  286. for (let x = 0; x < WIDTH; x++) {
  287. if (gameBoard[y * WIDTH + x] !== selected) {
  288. gameOver = false;
  289. result = 'playing';
  290. }
  291. }
  292. }
  293. }
  294.  
  295. if (gameOver) {
  296. this.client.games.delete(msg.channel.id);
  297. const embed = await getContent();
  298.  
  299. return collected.editReply({ embeds: [embed], components: getButtons(gameOver) }).catch(console.error);
  300. }
  301.  
  302. if (turn >= moves) {
  303. this.client.games.delete(msg.channel.id);
  304. gameOver = true;
  305. result = 'maxTurns';
  306. const embed = await getContent();
  307.  
  308. return collected.editReply({ embeds: [embed], components: getButtons(gameOver) }).catch(console.error);
  309. }
  310.  
  311. this.client.games.delete(msg.channel.id);
  312. return msg.channel.send('Something went wrong, please try again later.');
  313. } catch (err) {
  314. this.client.games.delete(msg.channel.id);
  315. this.client.logger.error(`Flood: ${err}`);
  316. gameOver = true;
  317. result = 'error';
  318. const embed = await getContent();
  319.  
  320. return message.edit({ embeds: [embed], components: getButtons(gameOver) }).catch(console.error);
  321. }
  322. }
  323. }
  324.  
  325. module.exports = FloodButtons;
  326.  
Advertisement
Add Comment
Please, Sign In to add comment