Enhance quiz with choices and rewards

This commit is contained in:
2026-01-26 19:15:43 +01:00
parent a1d469af4d
commit ad6642e3b0
3 changed files with 139 additions and 25 deletions
+2 -2
View File
@@ -40,8 +40,8 @@ Im going to make a To-do List for the features i want to implement for my Bot.
3. **Minigames**
- [ ] Implement a Quiz Game:
- [x] Add a `/quiz` command to start the game.
- [ ] Provide multiple-choice or true/false questions.
- [ ] Award virtual currency for correct answers.
- [x] Provide multiple-choice or true/false questions.
- [x] Award virtual currency for correct answers.
- [ ] Add a simple number-guessing game (e.g., `!guess [number]`).
- [ ] Create a leaderboard for game winners.
+121 -19
View File
@@ -1,43 +1,145 @@
const quiz = require('./quiz.json');
const {SlashCommandBuilder} = require("discord.js");
const {
SlashCommandBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
} = require('discord.js');
const { connectToDatabase } = require('../../db.js');
const QUIZ_REWARD = 50;
const QUIZ_TIMEOUT_MS = 30000;
const normalizeAnswer = (answer) => answer.trim().toLowerCase();
const buildRows = (choices) => {
const rows = [];
for (let i = 0; i < choices.length; i += 5) {
const slice = choices.slice(i, i + 5);
const row = new ActionRowBuilder().addComponents(
slice.map((choice, index) =>
new ButtonBuilder()
.setCustomId(`quiz_choice_${i + index}`)
.setLabel(choice)
.setStyle(ButtonStyle.Primary)
)
);
rows.push(row);
}
return rows;
};
const disableRows = (rows) =>
rows.map((row) => {
const disabledRow = ActionRowBuilder.from(row);
disabledRow.components = disabledRow.components.map((component) =>
ButtonBuilder.from(component).setDisabled(true)
);
return disabledRow;
});
const awardReward = async (userId) => {
const dbClient = await connectToDatabase();
const db = dbClient.db('discord');
const collection = db.collection('currency');
await collection.updateOne(
{ userID: userId },
{ $inc: { balance: QUIZ_REWARD } },
{ upsert: true }
);
};
module.exports = {
data: new SlashCommandBuilder()
.setName("quiz")
.setDescription("Start a cool quiz"),
.setName('quiz')
.setDescription('Start a cool quiz'),
category: 'Games',
async execute(interaction) {
const item = quiz[Math.floor(Math.random() * quiz.length)];
await interaction.reply({
content: `**Quiz Time!**\n${item.question}\n\n_You have 30 seconds to answer!_`,
const answers = (item.answers ?? [])
.map(normalizeAnswer)
.filter(Boolean);
const hasChoices = Array.isArray(item.choices) && item.choices.length > 0;
const embed = new EmbedBuilder()
.setColor(0xf7b731)
.setTitle('Quiz Time!')
.setDescription(item.question)
.setFooter({ text: 'You have 30 seconds to answer.' });
if (!interaction.channel) {
return interaction.reply({
content: 'I need a channel to run the quiz.',
ephemeral: true,
});
}
if (hasChoices) {
const choices = item.choices;
const rows = buildRows(choices);
const reply = await interaction.reply({
embeds: [embed],
components: rows,
fetchReply: true,
});
// Define a filter to check the correct answer
const collectorFilter = (response) => {
return (
response.author.id === interaction.user.id && // Ensure it's the same user
item.answers.some(
(answer) => answer.toLowerCase() === response.content.toLowerCase()
)
);
};
try {
const selection = await reply.awaitMessageComponent({
filter: (component) => component.user.id === interaction.user.id,
time: QUIZ_TIMEOUT_MS,
});
const choiceIndex = Number(selection.customId.split('_').pop());
const selected = choices[choiceIndex];
const isCorrect = answers.includes(normalizeAnswer(selected));
const resultText = isCorrect
? `${selection.user} nailed it! **${selected}** is correct. +${QUIZ_REWARD} coins! 🎉`
: `Nice try! The correct answer was **${item.answers[0]}**.`;
if (isCorrect) {
await awardReward(selection.user.id);
}
await selection.update({
content: resultText,
embeds: [],
components: disableRows(rows),
});
} catch (err) {
await interaction.editReply({
content: `⏳ Time's up! The correct answer was **${item.answers[0]}**.`,
embeds: [],
components: disableRows(rows),
});
}
return;
}
await interaction.reply({ embeds: [embed] });
const collectorFilter = (response) =>
response.author.id === interaction.user.id &&
answers.includes(normalizeAnswer(response.content));
try {
// Await messages in the same channel
const collected = await interaction.channel.awaitMessages({
filter: collectorFilter,
max: 1,
time: 30000, // 30 seconds
time: QUIZ_TIMEOUT_MS,
errors: ['time'],
});
// If a message is collected, reply with the correct response
const correctAnswer = collected.first();
await awardReward(correctAnswer.author.id);
await interaction.followUp(
`${correctAnswer.author} got the correct answer: **${correctAnswer.content}**! 🎉`
`${correctAnswer.author} got the correct answer: **${correctAnswer.content}**! +${QUIZ_REWARD} coins! 🎉`
);
} catch (err) {
// If no one answers in time
await interaction.followUp(
`⏳ Time's up! Nobody got the correct answer. The correct answers were: **${item.answers.join(', ')}**.`
);
+15 -3
View File
@@ -1,10 +1,22 @@
[
{
"question": "What color is the sky?",
"question": "What color is the clear daytime sky?",
"choices": ["Blue", "Green", "Red", "Yellow"],
"answers": ["blue"]
},
{
"question": "How many letters are there in the alphabet?",
"answers": ["26", "twenty-six", "twenty six", "twentysix"]
"question": "How many letters are there in the English alphabet?",
"choices": ["24", "25", "26", "27"],
"answers": ["26"]
},
{
"question": "True or False: The Earth orbits the Sun.",
"choices": ["True", "False"],
"answers": ["true"]
},
{
"question": "Which planet is known as the Red Planet?",
"choices": ["Mars", "Venus", "Jupiter", "Mercury"],
"answers": ["mars"]
}
]