Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Counting game #535

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/coin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum UserCoinEvent {
BonusActivity,
BonusInterviewerList,
Blackjack,
Counting,
RpsLoss,
RpsDrawAgainstCodey,
RpsWin,
Expand Down
97 changes: 94 additions & 3 deletions src/events/messageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,30 @@ import { PDFDocument } from 'pdf-lib';
import { Logger } from 'winston';
import { applyBonusByUserId } from '../components/coin';
import { vars } from '../config';
import { sendKickEmbed } from '../utils/embeds';
import { sendKickEmbed, DEFAULT_EMBED_COLOUR } from '../utils/embeds';
import { convertPdfToPic } from '../utils/pdfToPic';
import { openDB } from '../components/db';
import { spawnSync } from 'child_process';
import { User } from 'discord.js';
import { getCoinEmoji } from '../components/emojis';
import { adjustCoinBalanceByUserId, UserCoinEvent } from '../components/coin';

const ANNOUNCEMENTS_CHANNEL_ID: string = vars.ANNOUNCEMENTS_CHANNEL_ID;
const RESUME_CHANNEL_ID: string = vars.RESUME_CHANNEL_ID;
const COUNTING_CHANNEL_ID: string = vars.COUNTING_CHANNEL_ID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add this variable into config/vars.template.json for future devs.

const IRC_USER_ID: string = vars.IRC_USER_ID;
const PDF_FILE_PATH = 'tmp/resume.pdf';
const HEIC_FILE_PATH = 'tmp/img.heic';
const CONVERTED_IMG_PATH = 'tmp/img.jpg';

// Variables and constants associated with the counting game
const coinsPerMessage = 0.1; // Number of coins awarded = coinsPerMessage * highest counting number * messages sent by user
Copy link
Contributor

@davidgan1218 davidgan1218 Sep 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to 0.25 to avoid floating point errors (ex. 18.900000000000002).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we prefer to keep all constant variable names capitalized and snake-cased (see above format).

const countingAuthorDelay = 1; // The minimum number of users that must count for someone to go again
const previousCountingAuthors: Array<User> = []; // Stores the most recent counters
const authorMessageCounts: Map<User, number> = new Map(); // Stores how many messages each user sent
const coinAwardNumberThreshold = 20; // The minimum number that must be reached for coins to be awarded
let currentCountingNumber = 1;

/*
* If honeypot is to exist again, then add HONEYPOT_CHANNEL_ID to the config
* and add a check for a message's channel ID being equal to HONEYPOT_CHANNEL_ID
Expand Down Expand Up @@ -93,13 +105,12 @@ const convertResumePdfsIntoImages = async (
message: Message,
): Promise<Message<boolean> | undefined> => {
const attachment = message.attachments.first();
const hasAttachment = attachment;
const isPDF = attachment && attachment.contentType === 'application/pdf';
const isImage =
attachment && attachment.contentType && attachment.contentType.startsWith('image');

// If no resume pdf is provided, nuke message and DM user about why their message got nuked
if (!(hasAttachment && (isPDF || isImage))) {
if (!(attachment && (isPDF || isImage))) {
const user = message.author.id;
const channel = message.channelId;

Expand Down Expand Up @@ -200,6 +211,82 @@ const convertResumePdfsIntoImages = async (
}
};

const countingGameLogic = async (
client: Client,
message: Message,
): Promise<Message<boolean> | undefined> => {
// Check to see if game should end
let reasonForFailure = '';
if (isNaN(Number(message.content))) {
// Message was not a number
reasonForFailure = `"${message.content}" is not a number!`;
} else if (previousCountingAuthors.find((author) => author === message.author)) {
// Author is still on cooldown
reasonForFailure = `<@${message.author.id}> counted too recently!`;
} else if (Number(message.content) != currentCountingNumber) {
// Wrong number was sent
reasonForFailure = `${message.content} is not the next number! The next number was ${currentCountingNumber}.`;
}

if (reasonForFailure) {
return endCountingGame(client, message, reasonForFailure);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtract currentCountingNumber by 1 when the game ends because this value of currentCountingNumber was not actually reached.

}

// If checks passed, continue the game
currentCountingNumber++;
message.react('✅');
previousCountingAuthors.unshift(message.author); // Add current author to list of authors on cooldown
while (previousCountingAuthors.length > countingAuthorDelay) {
previousCountingAuthors.pop(); // Remove last author from cooldown
}
const currentAuthorCount: number | undefined = authorMessageCounts.get(message.author);
authorMessageCounts.set(message.author, currentAuthorCount ? currentAuthorCount + 1 : 1);

return;
};

const endCountingGame = async (
client: Client,
message: Message,
reasonForFailure: string,
): Promise<Message<boolean> | undefined> => {
// Builds game over embed
const endGameEmbed = new EmbedBuilder()
.setColor(DEFAULT_EMBED_COLOUR)
.setTitle('Counting Game Over')
.addFields([
{
name: 'Reason for Game Over',
value: reasonForFailure,
},
]);

if (currentCountingNumber < coinAwardNumberThreshold) {
endGameEmbed.setDescription(
`Coins will not be awarded because the threshold, ${coinAwardNumberThreshold}, was not reached.`,
);
} else {
const sortedAuthorMessageCounts: Array<[User, number]> = Array.from(authorMessageCounts).sort(
(a, b) => b[1] - a[1],
); // Turns map into descending sorted array
const coinsAwarded: Array<string> = ['**Coins awarded:**'];
for (const pair of sortedAuthorMessageCounts) {
pair[1] *= coinsPerMessage * currentCountingNumber; // Changes number of messages sent to number of coins awarded
coinsAwarded.push(`<@${pair[0].id}> - ${pair[1]} ${getCoinEmoji()}`);
await adjustCoinBalanceByUserId(message.author.id, pair[1], UserCoinEvent.Counting);
}

endGameEmbed.setDescription(coinsAwarded.join('\n'));
}

currentCountingNumber = 1;
message.react('❌');
previousCountingAuthors.length = 0;
authorMessageCounts.clear();

return await message.channel?.send({ embeds: [endGameEmbed] });
};

export const initMessageCreate = async (
client: Client,
logger: Logger,
Expand All @@ -219,6 +306,10 @@ export const initMessageCreate = async (
await convertResumePdfsIntoImages(client, message);
}

if (message.channelId === COUNTING_CHANNEL_ID) {
await countingGameLogic(client, message);
}

// Ignore DMs; include announcements, thread, and regular text channels
if (message.channel.type !== ChannelType.DM) {
await applyBonusByUserId(message.author.id);
Expand Down
Loading