-
-
Save melmsie/8de434115b7ceb0b7f554b68c041ee08 to your computer and use it in GitHub Desktop.
import * as Constants from './constants'; | |
const randomInArray = <T>(arr: readonly T[]): T => | |
arr[Math.floor(Math.random() * arr.length)]; | |
export interface Card { | |
suit: typeof Constants.SUITS[number]; | |
face: typeof Constants.FACES[number]; | |
baseValue: number; | |
}; | |
const countHandRaw = (cards: Card[]): number => | |
cards.reduce((acc, curr) => curr.baseValue + acc, 0); | |
export const countHand = (hand: Card[]): number => { | |
for (const card of hand) { | |
if (card.face === 'A') { | |
card.baseValue = Constants.BJ_ACE_MAX; | |
} | |
} | |
let lowerAce: Card; | |
while ( | |
countHandRaw(hand) > Constants.BJ_WIN && | |
(lowerAce = hand.find(card => card.face === 'A' && card.baseValue !== Constants.BJ_ACE_MIN)) | |
) { | |
lowerAce.baseValue = Constants.BJ_ACE_MIN; | |
} | |
return countHandRaw(hand); | |
}; | |
export const deal = (hand: Card[], initial: boolean): void => { | |
const face = randomInArray(Constants.FACES); | |
const suit = randomInArray(Constants.SUITS); | |
if (hand.find(card => card.face === face && card.suit === suit)) { | |
return deal(hand, initial); | |
} | |
const card: Card = { | |
face, | |
suit, | |
baseValue: typeof face === 'number' | |
? face | |
: (face === 'A' ? Constants.BJ_ACE_MIN : Constants.BJ_FACE), | |
}; | |
if (initial && countHand([ ...hand, card ]) >= Constants.BJ_WIN) { | |
return deal(hand, initial); | |
} | |
hand.push(card); | |
}; |
export const BJ_WIN = 21; | |
export const BJ_DEALER_MAX = 17; | |
export const BJ_FACE = 10; | |
export const BJ_ACE_MIN = 1; | |
export const BJ_ACE_MAX = 11; | |
export const SUITS = [ | |
'♠', '♥', '♦', '♣', | |
] as const; | |
export const FACES = [ | |
'A', 'J', 'Q', 'K', | |
...Array.from({ length: 9 }, (_, i) => i + 2), | |
] as const; | |
export enum Outcome { | |
WIN = 1, | |
LOSS, | |
TIE, | |
OTHER | |
} | |
export const Outcomes: Record<Outcome, { | |
message: string; | |
color: number; | |
}> = { | |
[Outcome.WIN]: { message: 'You win!', color: 0x4CAF50 }, | |
[Outcome.LOSS]: { message: 'You lost ):', color: 0xE53935 }, | |
[Outcome.TIE]: { message: 'You tied.', color: 0xFFB300 }, | |
[Outcome.OTHER]: { message: '', color: 0xFFB300 }, | |
}; | |
export type OutcomeResult = { | |
outcome: Outcome; | |
reason: string; | |
extra?: string; | |
} | null; |
/* | |
- NOTE TO THE READER - | |
This code is (for the most part), extremely old. We are aware that some things are less than idea, outdated, or both. | |
We are rewriting the entire bot right now, which includes this command, so we aren't spending time improving this current implementation. | |
This file is available for transparency with people claiming the command is rigged, and this gist will be updated every time the command is. | |
This is not meant to be "understandable" by those who don't know javascript or the discord api, so please don't make inferences unless you know what you're talking about. | |
We welcome suggestions on how to fix known existing bugs in this on our subreddit if you so chose, thank you. | |
*/ | |
const GenericCurrencyCommand = require('../../../models/GenericCurrencyCommand'); | |
const { components: { Button } } = require('dawn/eris-interop/components'); | |
const util = require('./cards'); | |
const Constants = require('./constants'); | |
const logic = require('./logic'); | |
module.exports = new GenericCurrencyCommand( | |
async ({ Memer, msg, addCD, Currency, userEntry, donor, isGlobalPremiumGuild }) => { | |
if (userEntry.props.pocket >= Currency.constants.MAX_SAFE_COIN_AMOUNT) { | |
return msg.reply(`**Uh oh, looks like someone is too rich for their own good!**\nRather than wiping coins caused by inflation, we are now capping how many you can hold at one time.\nYou can either get rid of some coins (share, buy, donate them to the bot, prestige, etc) or you can just preserve your balance in the state that it's currently in.\nYou are not able to do any commands that gain you coins until you are under the cap of *${Currency.constants.MAX_SAFE_COIN_AMOUNT.toLocaleString()} coins*`); | |
} | |
const user = msg.author; | |
const coins = userEntry.props.pocket; | |
let multi = await Memer.calcMultiplier(Memer, msg.author, userEntry, donor, msg, isGlobalPremiumGuild); | |
multi = Math.max(Math.min(multi.total, 500), 0); | |
multi = (multi / 500) * 100; | |
// Multi is now a percent of having a full multiplier. To get 100% multi when gambling, you'll now need a 500% multi. Since it's not easy to get a 500% multi, I've upped the base pay again by a bit. | |
let maxAmount = Currency.constants.MAX_SAFE_COMMAND_AMOUNT; | |
if (userEntry.hasInventoryItem('pepecrown')) { | |
maxAmount = Currency.constants.MAX_SAFE_COMMAND_AMOUNT * 2; | |
} | |
if (coins >= maxAmount) { | |
return msg.reply('You are too rich to play! Why don\'t you go and do something productive with your coins smh'); | |
} | |
let bet = msg.args.args[0]; | |
if (!bet) { | |
return msg.reply('You need to bet something, seems like common sense tbh.'); | |
} | |
if (bet < 1 || !Number.isInteger(Number(bet))) { | |
if (bet && bet.toLowerCase().includes('k')) { | |
const givenKay = bet.replace(/k/g, ''); | |
if (!Number.isInteger(Number(givenKay * 1000)) || isNaN(givenKay * 1000)) { | |
return msg.reply('You have to to actually bet a whole number, dummy. Not ur dumb feelings'); | |
} else { | |
bet = givenKay * 1000; | |
} | |
} else if (bet.toLowerCase() === 'all') { | |
bet = coins; | |
} else if (bet.toLowerCase() === 'max') { | |
bet = Math.min(coins, Currency.constants.MAX_SAFE_BET_AMOUNT); | |
} else if (bet.toLowerCase() === 'half') { | |
bet = Math.round(coins / 2); | |
} else { | |
return msg.reply('You have to bet actual coins, dont try to break me.'); | |
} | |
} | |
if (coins === 0) { | |
return msg.reply('You have no coins in your wallet to gamble with lol.'); | |
} | |
if (bet > coins) { | |
return msg.reply(`You only have ${coins.toLocaleString()} coins, dont try and lie to me hoe.`); | |
} | |
if (bet > Currency.constants.MAX_SAFE_BET_AMOUNT) { | |
return msg.reply(`You can't bet more than **${Currency.constants.MAX_SAFE_BET_AMOUNT.toLocaleString()} coins** at once, sorry not sorry`); | |
} | |
if (bet < Currency.constants.MIN_SAFE_BET_AMOUNT) { | |
return msg.reply(`You can't bet less than **${Currency.constants.MIN_SAFE_BET_AMOUNT.toLocaleString()} coins**, sorry not sorry`); | |
} | |
await addCD(); | |
// initial state | |
let stood = false; | |
/** @type {import('./constants').OutcomeResult} */ | |
let outcome = null; | |
/** @type {Record<'player' | 'dealer', import('./cards').Card[]>} */ | |
const hands = { | |
player: [], | |
dealer: [] | |
}; | |
for (let i = 0; i < 2; i++) { | |
util.deal(hands.player, true); | |
util.deal(hands.dealer, true); | |
} | |
const getEmbed = () => ({ | |
embed: logic.renderEmbed(user, hands.player, hands.dealer, stood, outcome), | |
components: [ | |
new Button('Hit', 'hit'), | |
new Button('Stand', 'stand'), | |
new Button('Forfeit', 'end') | |
].map(b => b.setDisabled(!!outcome)) | |
}); | |
// main logic | |
await msg.collectComponentInteractions(getEmbed(), {}, (ctx, msg) => { | |
try { | |
if (outcome) { | |
return ctx.ack(); | |
} | |
/** | |
* @param {{ ctx: import('dawn/src/eris-interop/components/ResponseContext').CollectorResponseContext, msg: import('eris').Message }} param0 | |
*/ | |
const update = async ({ ctx, msg }) => { | |
outcome ??= logic.getOutcome(hands.player, hands.dealer, stood); | |
switch (outcome?.outcome) { | |
case Constants.Outcome.WIN: { | |
let winnings = Math.ceil(bet * (Math.random() + 0.35)); // "Base Multi" will pay between 35% of the bet and 135% of the bet | |
winnings = Math.min(Currency.constants.MAX_SAFE_WIN_AMOUNT, winnings + Math.ceil(winnings * (multi / 100))); // This brings in the user's secret multi (pls multi) | |
Memer.ddog.increment('BJ.WON'); | |
Memer.ddog.incrementBy('BJ.WON.TOTAL', winnings); | |
outcome.extra = `You won **⏣ ${winnings.toLocaleString()}**. You now have ⏣ ${(userEntry.props.pocket + winnings).toLocaleString()}.`; | |
await userEntry | |
.addPocket(winnings) | |
.calculateExperienceGain() | |
.updateGambleStats(true, winnings, 'blackjackStats') | |
.save(); | |
break; | |
} | |
case Constants.Outcome.OTHER: { | |
outcome.extra = 'The dealer is keeping your money to deal with your bullcrap.'; | |
await userEntry | |
.removePocket(bet, 'blackjack') | |
.calculateExperienceGain() | |
.updateGambleStats(false, bet, 'blackjackStats') | |
.save(); | |
break; | |
} | |
case Constants.Outcome.LOSS: { | |
Memer.ddog.increment('BJ.LOST'); | |
Memer.ddog.incrementBy('BJ.TOTAL.LOST', bet); | |
outcome.extra ??= `You lost **⏣ ${Number(bet).toLocaleString()}**. You now have ${(userEntry.props.pocket - bet).toLocaleString()}.`; | |
await userEntry | |
.removePocket(bet, 'blackjack') | |
.calculateExperienceGain() | |
.updateGambleStats(false, bet, 'blackjackStats') | |
.save(); | |
break; | |
} | |
case Constants.Outcome.TIE: { | |
Memer.ddog.increment('BJ.TIE'); | |
outcome.extra = `Your wallet hasn't changed! You have **⏣ ${userEntry.props.pocket.toLocaleString()}** still.`; | |
break; | |
} | |
} | |
if (ctx) { | |
await ctx.editOriginal(getEmbed(outcome)); | |
} else { | |
await msg.edit(getEmbed(outcome)); | |
} | |
if (outcome) { | |
ctx?.end(); | |
} | |
}; | |
if (ctx === null) { | |
outcome = { outcome: Constants.Outcome.OTHER, reason: 'You didn\'t respond in time. ' }; | |
return update({ ctx, msg }); | |
} | |
if (ctx.member.user.id !== user.id) { | |
return ctx.respond({ | |
ephemeral: true, | |
content: 'Go start your own game of blackjack.' | |
}); | |
} | |
switch (ctx.customID) { | |
case 'hit': | |
util.deal(hands.player, false); | |
return update({ ctx, msg }); | |
case 'stand': | |
stood = true; | |
while (util.countHand(hands.dealer) < Constants.BJ_DEALER_MAX) { | |
util.deal(hands.dealer, false); | |
} | |
return update({ ctx, msg }); | |
case 'end': | |
outcome = { | |
outcome: Constants.Outcome.OTHER, | |
reason: 'You ended the game.' | |
}; | |
return update({ ctx, msg }); | |
} | |
} catch (e) {} | |
}); | |
}, | |
{ | |
triggers: ['blackjack', 'bj'], | |
cooldown: 10 * 1000, | |
donorCD: 5 * 1000, | |
usage: '{command} <number>', | |
shortDescription: 'Play and bet against the bot in blackjack!', | |
description: 'Take your chances and test your skills at blackjack. Warning, I am very good at stealing your money. Learn to play blackjack [here](https://www.youtube.com/watch?v=VB-6MvXvsKo). (Multiplier affects this command up to 100% max)', | |
cooldownMessage: 'If I let you bet whenever you wanted, you\'d be a lot more poor. Wait ', | |
missingArgs: 'You gotta gamble some of ur coins bro' | |
} | |
); |
import { EmbedOptions, User } from 'eris'; | |
import { Card, countHand } from './cards'; | |
import { BJ_WIN, Outcome, OutcomeResult, Outcomes } from './constants'; | |
const renderCard = (card: Card, idx: number, hide: boolean): string => | |
`[\`${ | |
idx > 0 && hide | |
? '?' | |
: `${card.suit} ${card.face}` | |
}\`](https://i.imgur.com/1Ob4BIs.png)` | |
const renderHand = (hand: Card[], hide: boolean): string => | |
`Cards - **${ | |
hand | |
.map((card, idx) => renderCard(card, idx, hide)) | |
.join(' ') | |
}**\nTotal - \`${hide ? '` ? `' : countHand(hand)}\``; | |
export const renderEmbed = ( | |
author: User, | |
playerHand: Card[], | |
dealerHand: Card[], | |
stood: boolean, | |
outcome: OutcomeResult, | |
): EmbedOptions => ({ | |
author: { | |
name: `${author.username}'s blackjack game`, | |
icon_url: author.dynamicAvatarURL() | |
}, | |
color: outcome ? Outcomes[outcome.outcome].color : 0x26A69A, | |
description: !outcome | |
? '' | |
: `**${Outcomes[outcome.outcome].message} ${outcome.reason}**\n${outcome.extra ?? ''}`, | |
fields: [ { | |
name: `${author.username} (Player)`, | |
value: renderHand(playerHand, false), | |
inline: true, | |
}, { | |
name: `Dank Memer (Dealer)`, | |
value: renderHand(dealerHand, outcome ? false : !stood), | |
inline: true | |
} ], | |
footer: { | |
text: !outcome ? 'K, Q, J = 10 | A = 1 or 11' : '' | |
} | |
}); | |
const win = (reason: string): OutcomeResult => ({ | |
outcome: Outcome.WIN, | |
reason, | |
}); | |
const loss = (reason: string): OutcomeResult => ({ | |
outcome: Outcome.LOSS, | |
reason, | |
}); | |
const tie = (reason: string): OutcomeResult => ({ | |
outcome: Outcome.TIE, | |
reason, | |
}); | |
export const getOutcome = ( | |
playerHand: Card[], | |
dealerHand: Card[], | |
stood: boolean, | |
): OutcomeResult => { | |
const playerScore = countHand(playerHand); | |
const dealerScore = countHand(dealerHand); | |
if (playerScore === BJ_WIN) { | |
return win('You got to 21.'); | |
} else if (dealerScore === BJ_WIN) { | |
return loss('The dealer got to 21 before you.'); | |
} else if (playerScore <= BJ_WIN && playerHand.length === 5) { | |
return win('You took 5 cards without going over 21.'); | |
} else if (dealerScore <= BJ_WIN && dealerHand.length === 5) { | |
return loss('The dealer took 5 cards without going over 21.'); | |
} else if (playerScore > BJ_WIN) { | |
return loss('You went over 21 and busted.'); | |
} else if (dealerScore > BJ_WIN) { | |
return win('The dealer went over 21 and busted.'); | |
} else if (stood && playerScore > dealerScore) { | |
return win(`You stood with a higher score (\`${playerScore}\`) than the dealer (\`${dealerScore}\`)`); | |
} else if (stood && dealerScore > playerScore) { | |
return loss(`You stood with a lower score (\`${playerScore}\`) than the dealer (\`${dealerScore}\`)`); | |
} else if (stood && playerScore === dealerScore) { | |
return tie('You tied with the dealer.'); | |
} | |
return null; | |
} |
Not only that, I think Kronos is in Python too, but correct me if I am wrong.
Kronos is an instance of Red, which is in Python
what a pro! I remember starting off with Python, but I eventually migrated to JS.
how did you start learning js?
Just watching and listening to tutorials, dedication and interest.
You've listed many custom functions here, with us not knowing their purpose, or the actual code behind it.
The custom functions could still "rig" the blackjack output, as unlikely as it seems.
I'm assuming memer.ddog would be something related to datadog
also, why do you pass so many params in your run function?
I'm assuming memer.ddog would be something related to datadog
yes exactly. they use it to monitor global stats, just what Mel told us before that we generated billions of coins in just, minutes.
You've listed many custom functions here, with us not knowing their purpose, or the actual code behind it.
The custom functions could still "rig" the blackjack output, as unlikely as it seems.
It is unlikely yes. If you know javascript, you would know what those functions do.
also, why do you pass so many params in your run function?
Because of the wide range this bot covers
From rethink, their imgen, up to the code base itself. Also to navigate shit easier.
You've listed many custom functions here, with us not knowing their purpose, or the actual code behind it.
The custom functions could still "rig" the blackjack output, as unlikely as it seems.It is unlikely yes. If you know javascript, you would know what those functions do.
not really, no.
Memer was imported from run parameters. There is no way to tell what this function's purpose could be.
also, why do you pass so many params in your run function?
Because of the wide range this bot covers
From rethink, their imgen, up to the code base itself. Also to navigate shit easier.
I accept that, I'm just saying that you can add those into a property of an object, and pass that instead.
You've listed many custom functions here, with us not knowing their purpose, or the actual code behind it.
The custom functions could still "rig" the blackjack output, as unlikely as it seems.It is unlikely yes. If you know javascript, you would know what those functions do.
not really, no.
Memer was imported from run parameters. There is no way to tell what this function's purpose could be.
The parameters seem valid, yet I do get your point. They could, effectively, "rig" the output with these functions.
also, why do you pass so many params in your run function?
Because of the wide range this bot covers
From rethink, their imgen, up to the code base itself. Also to navigate shit easier.I accept that, I'm just saying that you can add those into a property of an object, and pass that instead.
imo it's less work to just insert the parameters to pass into directly, right?
also, why do you pass so many params in your run function?
Because of the wide range this bot covers
From rethink, their imgen, up to the code base itself. Also to navigate shit easier.I accept that, I'm just saying that you can add those into a property of an object, and pass that instead.
imo it's less work to just insert the parameters to pass into directly, right?
Not really.
client.on('messageCreate', async (msg) => {
async function addCD () => {
// ...
}
const object = {
addCD: addCD,
}
command.howEverItsRan(msg, object);
});
also, why do you pass so many params in your run function?
Because of the wide range this bot covers
From rethink, their imgen, up to the code base itself. Also to navigate shit easier.I accept that, I'm just saying that you can add those into a property of an object, and pass that instead.
imo it's less work to just insert the parameters to pass into directly, right?
Not really.
client.on('messageCreate', async (msg) => { async function addCD () => { // ... } const object = { addCD: addCD, } command.howEverItsRan(msg, object); });
ohhh that makes much more sense, thanks for telling me!
... (collapsed for readability purposes)
Not really.client.on('messageCreate', async (msg) => { async function addCD () => { // ... } const object = { addCD: addCD, } command.howEverItsRan(msg, object); });
From an old version of the bot (on github) there's this:
await command.run({
msg,
args,
cleanArgs,
Memer: this,
addCD: updateCooldowns,
isGlobalPremiumGuild
});
Where updateCooldowns is:
this.db.updateCooldowns(command.props.triggers[0], msg.author.id, isGlobalPremiumGuild);
class Memer extends Base {
where Base is the Base of eris-sharder
Also, this probably is NOT updated lol I pulled it out of a 2017 version of dank memer
... (collapsed for readability purposes)
Not really.client.on('messageCreate', async (msg) => { async function addCD () => { // ... } const object = { addCD: addCD, } command.howEverItsRan(msg, object); });From an old version of the bot (on github) there's this:
await command.run({ msg, args, cleanArgs, Memer: this, addCD: updateCooldowns, isGlobalPremiumGuild });Where updateCooldowns is:
this.db.updateCooldowns(command.props.triggers[0], msg.author.id, isGlobalPremiumGuild);class Memer extends Base {where Base is the Base of eris-sharder
Also, this probably is NOT updated lol I pulled it out of a 2017 version of dank memer
Can you link the 2017 version of dank memer? I'd like to see the beauty
@DaliborTrampota https://github.com/Bizorke/Dank-Memer, though I can assure you its not a beauty
They could, effectively, "rig" the output with these functions.
Memer.calcMultiplier
is deterministic, and the multiplier is only used to calculate a part of the bet won. Whether you win or lose a game of blackjack is unaffected by your multiplier.
People who claim blackjack is rigged aren't talking about the winnings, they're talking about card distribution, which is fully open sourced here. :)
Typescript. It's quite more complicated than before 😳
@DaliborTrampota https://github.com/Bizorke/Dank-Memer, though I can assure you its not a beauty
I use git@github.com:GeoffreyWesthoff/Dank-Memer-1.git, its a fork with more stuf
Its mainly not a beauty due to syntax and efficiency is what I meant by the way
They could, effectively, "rig" the output with these functions.
Memer.calcMultiplier
is deterministic, and the multiplier is only used to calculate a part of the bet won. Whether you win or lose a game of blackjack is unaffected by your multiplier.
People who claim blackjack is rigged aren't talking about the winnings, they're talking about card distribution, which is fully open sourced here. :)
ohh okay that clarifies everything for me. thanks @aetheryx :D
Hi @aetheryx, do you mind telling me the npm package you use for eris components? it's fine if you won't.
Hi @aetheryx, do you mind telling me the npm package you use for eris components? it's fine if you won't.
We don't use an npm package, we built out eris components ourselves with an internal library
Hi @aetheryx, do you mind telling me the npm package you use for eris components? it's fine if you won't.
We don't use an npm package, we built out eris components ourselves with an internal library
alright thank you.
Uhh hey @aetheryx one last question, what does ctx.ack()
do? Just curious though.
Uhh hey @aetheryx one last question, what does
ctx.ack()
do? Just curious though.
@BrianWasTaken this isn't a support line, so you won't get any more answers after this.
that's us ACKing the ping, which you can read about here by ctrl+f and typing ack
: https://discord.com/developers/docs/interactions/receiving-and-responding#responding-to-an-interaction
Got it, I'll dip.
wooo typescript!!!!!!
I mean I don't see why it shouldn't be.
Also, to those complaining about Python being shit:
I agree, it's not very efficient. However, discord.py is quite fast. Also, discord.py works very well with multiple files.
On top of this, guess which bot is coded in Python. Carl-bot. And who is he used by? Dank Memer Community.
Not only that, I think Kronos is in Python too, but correct me if I am wrong.