diff --git a/commands/draw.js b/commands/draw.js index 003d4c0..8d02df5 100644 --- a/commands/draw.js +++ b/commands/draw.js @@ -17,6 +17,7 @@ async function draw(interaction, chan) { const name = (card.color) ? card.toString() : card.value.toString(); const n2 = (name.includes("WILD_DRAW")) ? "WD4" : (name.includes("DRAW")) ? `${name.split(" ")[0]} DT` : name; await interaction.reply(`You drew a ${n2.toLowerCase()}`, { ephemeral: true }); + // TODO: pass button } exports.run = draw; diff --git a/commands/play.js b/commands/play.js index 5cd873c..f50e807 100644 --- a/commands/play.js +++ b/commands/play.js @@ -44,7 +44,7 @@ async function play(interaction, chan, opts, bot) { } chan.uno.players.get(p.name).interaction = interaction; const c = chan.uno.game.discardedCard.value.toString().toLowerCase(); - await interaction.reply(`Played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew ${(c.includes("two") ? "2" : "4")} cards.` : ""}`, { + await interaction.reply(`${interaction.member} played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew ${(c.includes("two") ? "2" : "4")} cards.` : ""}`, { allowedMentions: { users: [], }, diff --git a/game/botBrain.js b/game/botBrain.js index 4a67bc5..18d6f08 100644 --- a/game/botBrain.js +++ b/game/botBrain.js @@ -27,8 +27,15 @@ async function botPlay(chan, matchingHand, callUno = true) { const cardColors = []; const handWithoutWilds = player.hand.filter(c => !c.value.toString().includes("WILD")); handWithoutWilds.forEach((c) => { - cardColors.push(c.color.toString()); // Create list of colors in hand + if (c.color.toString() !== chan.uno.game.discardedCard.color.toString()) { + // Create list of colors in hand not matching current color + cardColors.push(c.color.toString()); + } }); + if (cardColors.length === 0) { + // All cards in hand had same color as the current card + cardColors.push(chan.uno.game.discardedCard.color.toString()); + } const cardCols = countOccurrences(cardColors); const keys = Object.keys(cardCols); diff --git a/game/game.js b/game/game.js index a80acae..706fbfa 100644 --- a/game/game.js +++ b/game/game.js @@ -1,5 +1,6 @@ -const { MessageActionRow, MessageButton, MessageEmbed } = require("discord.js"); +const { MessageButton, MessageEmbed } = require("discord.js"); const { Card, Values, Colors } = require("uno-engine"); +const { addButton, colorToButtonStyle, buttonsToMessageActions } = require("../util/buttons.js"); const errHandler = require("../util/err.js"); const botTurn = require("./botBrain.js"); const cardImages = require("../data/unocardimages.json"); @@ -43,20 +44,6 @@ function reset(chan) { chan.uno = null; } -function addButton(buttons, button) { - const last = buttons[buttons.length - 1]; - if (last.length !== 5) { - last.push(button); - } else if (buttons.length !== 5) { - buttons.push([button]); - } - return buttons; -} - -function colorToButtonStyle(color) { - return (color === "BLUE") ? "PRIMARY" : (color === "GREEN") ? "SUCCESS" : (color === "RED") ? "DANGER" : "SECONDARY"; -} - function createButtons(hand, discard) { let buttons = [ [], @@ -122,7 +109,7 @@ function createButtons(hand, discard) { // .setStyle("SECONDARY") // .setEmoji("⏭️"); // buttons = addButton(buttons, passButton); - return buttons.map(b => new MessageActionRow().addComponents(b)); + return buttonsToMessageActions(buttons); } async function playedWildCard(inter, chan, value, pid) { @@ -133,8 +120,9 @@ async function playedWildCard(inter, chan, value, pid) { .setStyle(colorToButtonStyle(c.toUpperCase()))), [ [], ]); + console.log(inter.type); await inter.update(inter.message.content, { - components: buttons.map(b => new MessageActionRow().addComponents(b)), + components: buttonsToMessageActions(buttons), }); const colorCollector = inter.message.createMessageComponentInteractionCollector(() => true, { max: 1, @@ -165,7 +153,7 @@ async function playedWildCard(inter, chan, value, pid) { await inter2.update(card.toString(), { components: [] }); chan.uno.players.get(p.name).interaction = inter2; - await inter2.followUp(`Played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew 4 cards.` : ""}`, { + await inter2.followUp(`${inter2.member} played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew 4 cards.` : ""}`, { allowedMentions: { users: [], }, @@ -198,6 +186,11 @@ async function sendHandWithButtons(chan, player, handStr, rows) { const cardArr = inter.customID.split(" "); if (cardArr.length === 1) { + if (chan.uno.drawn) { + await inter.update(inter.message.content, { components: [] }); + await inter.followUp("You cannot draw twice in a row.", { ephemeral: true }); + return false; + } chan.uno.game.draw(); const card = chan.uno.game.currentPlayer.hand[chan.uno.game.currentPlayer.hand.length - 1]; @@ -227,11 +220,13 @@ async function sendHandWithButtons(chan, player, handStr, rows) { .setStyle("SECONDARY") .setEmoji("⏭️"); buttons = addButton(buttons, passBtn); - // TODO: change draw and pass to channel messages so other players can see them - // TODO: move buttons to a new ephemeral message + await inter.update(`You drew a ${n2.toLowerCase()}`, { - components: buttons.map(b => new MessageActionRow().addComponents(b)), + ephemeral: true, + components: buttonsToMessageActions(buttons), }); + await chan.send(`${inter.member} drew`, { allowedMentions: { users: [] } }); + const passCollector = inter.message.createMessageComponentInteractionCollector(() => true, { max: 1, }); @@ -239,9 +234,20 @@ async function sendHandWithButtons(chan, player, handStr, rows) { passCollector.on("collect", resolve); }); console.log(`Collected ${inter2.customID}`); + if (chan.uno.game.currentPlayer.name !== inter2.member.id) { + await inter2.update(inter2.message.content, { components: [] }); + await inter2.followUp("It's not your turn.", { ephemeral: true }); + return false; + } + if (chan.uno.playerCustomID !== pid) { + inter2.update(inter.message.content, { components: [] }); + inter2.followUp("You can't use old Uno buttons.", { ephemeral: true }); + return false; + } if (inter2.customID === "PASS") { chan.uno.game.pass(); - inter2.update("Passed", { components: [] }); + await inter2.update("Passed", { components: [] }); + await chan.send(`${inter2.member} passed`, { allowedMentions: { users: [] } }); chan.uno.drawn = false; return true; } @@ -261,7 +267,7 @@ async function sendHandWithButtons(chan, player, handStr, rows) { } player.interaction = inter2; const c = chan.uno.game.discardedCard.value.toString().toLowerCase(); - await inter2.followUp(`Played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew ${(c.includes("two") ? "2" : "4")} cards.` : ""}`, { + await inter2.followUp(`${inter2.member} played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew ${(c.includes("two") ? "2" : "4")} cards.` : ""}`, { allowedMentions: { users: [], }, @@ -288,7 +294,7 @@ async function sendHandWithButtons(chan, player, handStr, rows) { } player.interaction = inter; console.log("getPlainCard(card)", getPlainCard(card)); - await inter.followUp(`Played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew 2 cards.` : ""}`, { + await inter.followUp(`${inter.member} played ${getPlainCard(card)}${(drawn.didDraw) ? `, ${drawn.player} drew 2 cards.` : ""}`, { allowedMentions: { users: [], }, @@ -315,7 +321,7 @@ async function nextTurn(chan) { if (handArr.length === 0) { return; // game.on end triggers } - const str = `${player} is up (${handArr.length}) - Card: ${chan.uno.game.discardedCard.toString()}`; + const str = `${player} (${handArr.length}) is up - Card: ${chan.uno.game.discardedCard.toString()}`; const file = { files: [getCardImage(chan.uno.game.discardedCard)] }; await chan.send(str, file); @@ -334,7 +340,7 @@ async function nextTurn(chan) { await player.interaction.followUp(`Your Uno hand: ${handStr}\nToo many cards to create buttons - use \`/play\` command.`, { ephemeral: true }); } } catch (e) { - await chan.send(`Could not send your hand, ${player}, use \`/hand\` to view it.`); + await chan.send(`An error occurred, ${player}, use slash comamnds.`); errHandler("error", e); } } @@ -409,7 +415,7 @@ async function finished(chan, err, winner, score) { .setColor(embedColor); const msg = await chan.send("Game finished!", { embed, - components: buttons.map(b => new MessageActionRow().addComponents(b)), + components: buttonsToMessageActions(buttons), }); const startCollector = msg.createMessageComponentInteractionCollector(() => true, { max: 1, @@ -425,8 +431,12 @@ async function finished(chan, err, winner, score) { if (inter.customID === "BOT") { opts.bot = true; } - // TODO: join button on new game - start(inter, chan, opts, reset, nextTurn, finished); + if (!chan.uno?.running) { + start(inter, chan, opts, reset, nextTurn, finished); + return; + } + await inter.update(inter.message.content, { components: [] }); + await inter.followUp("Uno is already running.", { ephemeral: true }); } module.exports = { diff --git a/game/gameStart.js b/game/gameStart.js index 7dc20fc..d7f2580 100644 --- a/game/gameStart.js +++ b/game/gameStart.js @@ -1,15 +1,20 @@ -const { Collection } = require("discord.js"); +const { Collection, MessageButton } = require("discord.js"); const { Game } = require("uno-engine"); +const { addButton, buttonsToMessageActions } = require("../util/buttons.js"); const sleep = require("./sleep.js"); const errHandler = require("../util/err.js"); -async function startMsg(i, msg) { +async function startMsg(i, msg, options) { let type = "reply"; - if (i.type === "MESSAGE_COMPONENT") { + if (i.type === "MESSAGE_COMPONENT") { // TODO: check i.replied await i.update(i.message.content, { components: [] }); type = "followUp"; } - i[type](msg); + const m = await i[type](msg, options); + if (m) { + return m; + } + return i.fetchReply(); } async function start(interaction, chan, opts, reset, nextTurn, finished) { @@ -55,8 +60,37 @@ async function start(interaction, chan, opts, reset, nextTurn, finished) { const { id } = chan.uno; if (!solo) { const startTime = 30; - await startMsg(interaction, `An Uno game${(botPlayer) ? " *with the bot*" : ""} will be started in ${startTime}s! Use \`/join\` to join.`); - await sleep(startTime * 1000); + // TODO: join button on new game + + const joinBtn = new MessageButton() + .setCustomID("JOIN") + .setLabel("Join") + .setStyle("SUCCESS") + .setEmoji("⏩"); + const buttons = addButton([ + [], + ], joinBtn); + + const msg = await startMsg(interaction, `An Uno game${(botPlayer) ? " *with the bot*" : ""} will be started in ${startTime}s! Use \`/join\` or click the button to join.`, { + components: buttonsToMessageActions(buttons), + }); + const joinCollector = msg.createMessageComponentInteractionCollector(() => true, { + time: startTime * 1000, + }); + joinCollector.on("collect", async (i) => { + if (chan.uno.players.has(i.member.id)) { + await i.reply("You are already in the current game.", { ephemeral: true }); + return; + } + chan.uno.players.set(i.member.id, i.member); + chan.uno.players.get(i.member.id).interaction = i; + i.reply(`${i.member} joined - Player ${chan.uno.players.size}`, { allowedMentions: { users: [] } }); + }); + await new Promise((resolve) => { // Wait full duration before starting game + joinCollector.on("end", resolve); + }); + msg.edit(msg.content, { components: [] }); + // await sleep(startTime * 1000); } else { await startMsg(interaction, "Uno is starting!"); } diff --git a/util/buttons.js b/util/buttons.js new file mode 100644 index 0000000..9f2a363 --- /dev/null +++ b/util/buttons.js @@ -0,0 +1,25 @@ +const { MessageActionRow } = require("discord.js"); + +function addButton(buttons, button) { + const last = buttons[buttons.length - 1]; + if (last.length !== 5) { + last.push(button); + } else if (buttons.length !== 5) { + buttons.push([button]); + } + return buttons; +} + +function colorToButtonStyle(color) { + return (color === "BLUE") ? "PRIMARY" : (color === "GREEN") ? "SUCCESS" : (color === "RED") ? "DANGER" : "SECONDARY"; +} + +function buttonsToMessageActions(buttons) { + return buttons.map(b => new MessageActionRow().addComponents(b)); +} + +module.exports = { + addButton, + colorToButtonStyle, + buttonsToMessageActions, +};