Last active
August 26, 2025 20:35
-
-
Save cakedan/a1d56468374da52b80f983bdb4594cbe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| let crypto = require("crypto"); | |
| let db = { | |
| data: {}, | |
| load: () => db.data = JSON.parse(discord.storage.server.db || "{}"), | |
| save: () => discord.storage.server.db = JSON.stringify(db.data), | |
| get: (key) => db.data[key], | |
| set: (key, val) => db.data[key] = val, | |
| delete: (key) => delete db.data[key] | |
| }; | |
| let States = { | |
| START: 1, | |
| PLAYER_TURN: 2, | |
| GAME_OVER: 3 | |
| }; | |
| let Actions = { | |
| BET: 1, | |
| HIT: 2, | |
| STAND: 3, | |
| DOUBLE_DOWN: 4, | |
| SPLIT: 5, | |
| SPLIT_HIT: 6, | |
| FAKE: 7, | |
| PRINT: 8, | |
| MONEY: 9, | |
| HELP: 10 | |
| }; | |
| let isOwner = discord.user.id == "345691161530466304"; | |
| let session = { | |
| state: States.START, | |
| hand: [], | |
| dealerHand: [], | |
| splitHand: [], | |
| bet: 0, | |
| splitBet: 0, | |
| balance: 1000, | |
| doubled: false, | |
| split: false | |
| }; | |
| let cards = [ | |
| { name: "A", value: -1, }, | |
| { name: "J", value: 10, }, | |
| { name: "Q", value: 10, }, | |
| { name: "K", value: 10, } | |
| ]; | |
| let suits = [ "♥️", "♦️", "♣️", "♠️" ]; | |
| let deck = []; | |
| let action = -1; | |
| let nickname = discord.member && discord.member.nick; | |
| let displayname = discord.user.global_name; | |
| let username = discord.user.username; | |
| let embed = { | |
| title: `Blackjack - ${nickname || displayname || username}`, | |
| color: Math.floor(Math.random() * (0xFFFFFF + 1)), | |
| }; | |
| function createDeck() { | |
| for (let i = 0; i < 8; i++) { | |
| for (let i = 2; i <= 10; i++) { | |
| cards.push({ name: i.toString(), value: i }); | |
| } | |
| for (let suit of suits) { | |
| for (let card of cards) { | |
| deck.push({ | |
| id: `${card.name}${suit}`, | |
| value: card.value, | |
| hidden: false, | |
| }); | |
| } | |
| } | |
| } | |
| for (let i = 0; i < deck.length; i++) { | |
| let j = crypto.randomInt(deck.length); | |
| [deck[i], deck[j]] = [deck[j], deck[i]]; | |
| } | |
| } | |
| function manageSession() { | |
| db.load(); | |
| let data = db.get(discord.user.id); | |
| session = data ?? session; | |
| if (session.state == States.GAME_OVER) { | |
| session.state = States.START; | |
| session.hand = []; | |
| session.splitHand = []; | |
| session.dealerHand = []; | |
| session.bet = 0; | |
| session.splitBet = 0; | |
| session.doubled = false; | |
| session.split = false; | |
| if (session.balance <= 0) { | |
| session.balance = 1000; | |
| } | |
| } | |
| } | |
| function parseCommands() { | |
| let cmds = { | |
| ["--bet"]: Actions.BET, ["-b"]: Actions.BET, | |
| ["--hit"]: Actions.HIT, ["-h"]: Actions.HIT, | |
| ["--stand"]: Actions.STAND, ["-s"]: Actions.STAND, | |
| ["--double"]: Actions.DOUBLE_DOWN, ["-d"]: Actions.DOUBLE_DOWN, | |
| ["--split"]: Actions.SPLIT, ["-sp"]: Actions.SPLIT, | |
| ["--split_hit"]: Actions.SPLIT_HIT, ["-sh"]: Actions.SPLIT_HIT, | |
| ["--fake"]: Actions.FAKE, ["-f"]: Actions.FAKE, | |
| ["--print"]: Actions.PRINT, ["-p"]: Actions.PRINT, | |
| ["--money"]: Actions.MONEY, ["-m"]: Actions.MONEY, | |
| ["--help"]: Actions.HELP, ["-?"]: Actions.HELP | |
| }; | |
| action = cmds[discord.variables.__args[0]] || -1; | |
| if (action == -1) { | |
| let betAmount = parseInt(discord.variables.__args[0]); | |
| if (!isNaN(betAmount) && betAmount >= 0) { | |
| action = Actions.BET; | |
| discord.variables.__args[1] = betAmount.toString(); | |
| } | |
| } | |
| } | |
| function calculateScore(hand) { | |
| let score = 0; | |
| let aces = 0; | |
| for (let card of hand) { | |
| if (card.hidden) { | |
| continue; | |
| } | |
| if (card.value == -1) { | |
| aces++; | |
| score += 11; | |
| } else { | |
| score += card.value; | |
| } | |
| } | |
| while (score > 21 && aces > 0) { | |
| score -= 10; | |
| aces--; | |
| } | |
| return score; | |
| } | |
| function canDoubleDown() { | |
| if (session.hand.length > 2) return false; | |
| if (session.hand.some(c => c.value == -1)) return false; | |
| if (session.balance < session.bet) return false; | |
| let score = calculateScore(session.hand); | |
| if (score < 8 || score > 11) return false; | |
| return true; | |
| } | |
| function canSplit() { | |
| if (session.hand.length > 2) return false; | |
| if (session.balance < session.bet) return false; | |
| if (!session.hand[0] || !session.hand[1]) return false; // temporary check | |
| if (session.hand[0].value != session.hand[1].value) return false; | |
| return true; | |
| } | |
| function getPlayerActions() { | |
| let playerActions = ["hit", "stand"]; | |
| if (canDoubleDown()) playerActions.push("double down"); | |
| if (canSplit()) playerActions.push("split"); | |
| playerActions = playerActions.reduce( | |
| (text, element, index, array) => text + (index < array.length - 1 ? ", " : " or ") + element | |
| ); | |
| return playerActions; | |
| } | |
| function handleStart() { | |
| if (action == Actions.BET) { | |
| let betAmount = parseInt(discord.variables.__args[1]); | |
| if (isNaN(betAmount) || betAmount < 0) { | |
| console.log("Invalid bet amount. Please enter a positive number."); | |
| return -1; | |
| } | |
| if (betAmount > session.balance) { | |
| console.log(`Insufficient balance. Your current balance is $${session.balance}.`); | |
| return -1; | |
| } | |
| session.bet = betAmount; | |
| session.balance -= betAmount; | |
| let cards = deck.splice(0, 4); | |
| let first = cards[0]; | |
| let second = cards[1]; | |
| let third = cards[2]; | |
| let fourth = cards[3]; | |
| session.hand.push(first); | |
| session.dealerHand.push(second); | |
| session.hand.push(third); | |
| fourth.hidden = true; | |
| session.dealerHand.push(fourth); | |
| if ((first.value == -1 && third.value == 10) || (first.value == 10 && third.value == -1)) { | |
| session.state = States.GAME_OVER; | |
| embed.description = `Blackjack! You win with a score of 21!`; | |
| session.balance += session.bet * 2.5; | |
| return 0; | |
| } | |
| session.state = States.PLAYER_TURN; | |
| embed.description = `You drew a ${first.id} and a ${third.id}. You can ${getPlayerActions()}.` | |
| return 0; | |
| } else { | |
| console.log(`Please place a bet using bj <amount>. Your current balance is $${session.balance.toLocaleString()}.`); | |
| return -1; | |
| } | |
| } | |
| function handleDealerTurn() { | |
| session.dealerHand = session.dealerHand.map(c => ({ ...c, hidden: false })); | |
| while (calculateScore(session.dealerHand) < 17) { | |
| let card = deck.pop(); | |
| session.dealerHand.push(card); | |
| } | |
| let playerScore = calculateScore(session.hand); | |
| let splitScore = calculateScore(session.splitHand); | |
| let dealerScore = calculateScore(session.dealerHand); | |
| embed.description = ""; | |
| if (dealerScore > 21) { | |
| embed.description += ` You win! Dealer busted with a score of ${dealerScore}.`; | |
| session.balance += session.bet * 2 + session.splitBet * 2; | |
| session.state = States.GAME_OVER; | |
| return; | |
| } | |
| if (playerScore > dealerScore) { | |
| embed.description += ` You win! Your score was ${playerScore} against dealer's ${dealerScore}.`; | |
| session.balance += session.bet * 2; | |
| } else if (playerScore < dealerScore) { | |
| embed.description += ` You lose with a score of ${playerScore} against dealer's ${dealerScore}.`; | |
| } else { | |
| embed.description += ` It's a tie with both scoring ${playerScore}. You get your bet back.`; | |
| session.balance += session.bet; | |
| } | |
| if (session.split) { | |
| if (splitScore > dealerScore) { | |
| embed.description += ` You win your split hand! Your score was ${splitScore} against dealer's ${dealerScore}.`; | |
| session.balance += session.splitBet * 2; | |
| } else if (splitScore < dealerScore) { | |
| embed.description += ` You lose your split hand with a score of ${splitScore} against dealer's ${dealerScore}.`; | |
| } else { | |
| embed.description += ` It's a tie with your split hand scoring ${splitScore}. You get your split bet back.`; | |
| session.balance += session.splitBet; | |
| } | |
| } | |
| session.state = States.GAME_OVER; | |
| } | |
| function handlePlayerDraw() { | |
| let handScore = calculateScore(session.hand); | |
| let splitScore = calculateScore(session.splitHand); | |
| let hit = action == Actions.HIT || action == Actions.DOUBLE_DOWN; | |
| let splitHit = action == Actions.SPLIT_HIT && session.split; | |
| embed.description = ""; | |
| if (hit && handScore <= 21) { | |
| let handCard = deck.pop(); | |
| session.hand.push(handCard); | |
| embed.description += ` You drew a ${handCard.id}.`; | |
| } | |
| if (splitHit && splitScore <= 21) { | |
| let splitCard = deck.pop(); | |
| session.splitHand.push(splitCard); | |
| embed.description += ` You drew a ${splitCard.id} for your split hand.`; | |
| } | |
| handScore = calculateScore(session.hand); | |
| splitScore = calculateScore(session.splitHand); | |
| if (hit && handScore > 21) { | |
| embed.description += ` You busted! Your score was ${handScore}.`; | |
| } | |
| if (splitHit && splitScore > 21) { | |
| embed.description += ` You busted your split hand! Your score was ${splitScore}.`; | |
| } | |
| if (session.split && handScore > 21 && splitScore > 21) { | |
| session.state = States.GAME_OVER; | |
| } else if (handScore > 21) { | |
| session.state = States.GAME_OVER; | |
| } | |
| if (session.state == States.GAME_OVER) { | |
| session.dealerHand = session.dealerHand.map(c => ({ ...c, hidden: false })); | |
| } | |
| } | |
| function handleDoubleDown() { | |
| if (!canDoubleDown()) { | |
| embed.description = "You cannot double down at this time."; | |
| return; | |
| } | |
| session.balance -= session.bet; | |
| session.bet *= 2; | |
| session.doubled = true; | |
| handlePlayerDraw(); | |
| handleDealerTurn(); | |
| } | |
| function handleSplit() { | |
| if (!canSplit()) { | |
| embed.description = "You cannot split your hand at this time."; | |
| return; | |
| } | |
| session.split = true; | |
| session.splitHand.push(session.hand.pop()); | |
| session.splitBet = session.bet; | |
| session.balance -= session.bet; | |
| embed.description = `You have split your hand.`; | |
| } | |
| function handlePlayerTurn() { | |
| switch (action) { | |
| case Actions.HIT: | |
| case Actions.SPLIT_HIT: | |
| handlePlayerDraw(); | |
| break; | |
| case Actions.STAND: | |
| handleDealerTurn(); | |
| break; | |
| case Actions.DOUBLE_DOWN: | |
| handleDoubleDown(); | |
| break; | |
| case Actions.SPLIT: | |
| handleSplit(); | |
| break; | |
| default: | |
| embed.description = `Please ${getPlayerActions()}.`; | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| function loadCards() { | |
| session.hand = session.hand.map(id => deck.find(c => c.id == id)); | |
| session.splitHand = session.splitHand.map(id => deck.find(c => c.id == id)); | |
| session.dealerHand = session.dealerHand.map(data => ({ | |
| ...deck.find(c => c.id == data.id), | |
| hidden: data.hidden || false | |
| })); | |
| for (let card of [...session.hand, ...session.splitHand, ...session.dealerHand]) { | |
| let found = deck.find(c => c.id == card.id); | |
| if (found) deck.splice(deck.indexOf(found), 1); | |
| } | |
| } | |
| function saveCards() { | |
| session.hand = session.hand.map(c => c.id); | |
| session.splitHand = session.splitHand.map(c => c.id); | |
| session.dealerHand = session.dealerHand.map(c => ({ id: c.id, hidden: c.hidden })); | |
| } | |
| function handleMoney() { | |
| let target = discord.variables.__args[1]; | |
| if (discord.member && target) { | |
| let found = false; | |
| target = target.toLowerCase(); | |
| for (let member of discord.guild.members) { | |
| let userMatch = member.user.username | |
| .toLowerCase() | |
| .indexOf(target); | |
| let nickMatch = (member.nick || "") | |
| .toLowerCase() | |
| .indexOf(target); | |
| if (userMatch == -1 && nickMatch == -1) continue; | |
| let id = member.user.id; | |
| let data = db.get(id); | |
| if (data) { | |
| console.log(`<@${id}>'s account balance is $${data.balance.toLocaleString()}.`); | |
| found = true; | |
| return; | |
| } | |
| } | |
| if (!found) { | |
| console.log("No member found or member does not have any data."); | |
| } | |
| } else { | |
| console.log(`Your account balance is $${session.balance.toLocaleString()}.`); | |
| } | |
| } | |
| function handleHelp() { | |
| console.log(`\`\`\` | |
| Commands: | |
| -b, --bet <amount> : Place a bet | |
| -h, --hit : Hit | |
| -s, --stand : Stand | |
| -d, --double : Double down | |
| -sp, --split : Split hand | |
| -sh, --split_hit : Split hit | |
| -m, --money : Show current balance | |
| -?, --help : Show this help message | |
| \`\`\``); | |
| } | |
| function handleFakeGame() { | |
| let args = discord.variables.__args; | |
| if (args[3] && args[4]) { | |
| session.dealerHand = [ | |
| { id: args[3], hidden: false }, | |
| { id: args[4], hidden: true } | |
| ]; | |
| } else { | |
| session.dealerHand = deck.splice(0, 2); | |
| } | |
| session.hand = [args[1], args[2]]; | |
| session.state = States.PLAYER_TURN; | |
| db.set(discord.user.id, session); | |
| db.save(); | |
| } | |
| function runGame() { | |
| switch (action) { | |
| case Actions.MONEY: | |
| handleMoney(); | |
| return; | |
| case Actions.HELP: | |
| handleHelp(); | |
| return; | |
| case Actions.FAKE: | |
| if (isOwner) { | |
| handleFakeGame(); | |
| } else { | |
| console.log("You do not have permission to use this command."); | |
| } | |
| return; | |
| case Actions.PRINT: | |
| if (isOwner) { | |
| loadCards(); | |
| console.log(session); | |
| } else { | |
| console.log("You do not have permission to use this command."); | |
| } | |
| return; | |
| } | |
| let resultCode = 0; | |
| if (session.state == States.START) { | |
| resultCode = handleStart(); | |
| } else if (session.state == States.PLAYER_TURN) { | |
| loadCards(); | |
| resultCode = handlePlayerTurn(); | |
| } | |
| if (resultCode != 0) return; | |
| embed.fields = [ | |
| { | |
| name: "Your Hand", | |
| value: session.hand.map(c => c.id).join(", "), | |
| inline: true | |
| }, | |
| { | |
| name: "Dealer's Hand", | |
| value: session.dealerHand.map(c => c.hidden ? "?" : c.id).join(", "), | |
| inline: true | |
| }, | |
| { | |
| name: "Bet", | |
| value: `$${Number(session.bet).toLocaleString()}`, | |
| inline: true | |
| }, | |
| { | |
| name: "Your Score", | |
| value: calculateScore(session.hand).toString(), | |
| inline: true | |
| }, | |
| { | |
| name: "Dealer's Score", | |
| value: calculateScore(session.dealerHand).toString(), | |
| inline: true | |
| }, | |
| { | |
| name: "Balance", | |
| value: `$${Number(session.balance).toLocaleString()}`, | |
| inline: true | |
| }, | |
| ]; | |
| if (session.split) { | |
| embed.fields.splice(1, 0, { | |
| name: "Split Hand", | |
| value: session.splitHand.map(c => c.id).join(", "), | |
| inline: true | |
| }); | |
| embed.fields.splice(4, 0, { | |
| name: "Split Bet", | |
| value: `$${Number(session.splitBet).toLocaleString()}`, | |
| inline: true | |
| }); | |
| embed.fields.splice(7, 0, { | |
| name: "Split Score", | |
| value: calculateScore(session.splitHand).toString(), | |
| inline: true | |
| }); | |
| } | |
| let output = { | |
| embed: embed, | |
| components: [] | |
| }; | |
| if (session.split) { | |
| if (calculateScore(session.hand) <= 21) { | |
| output.components.push( | |
| { type: 2, label: "Hit", run: `\{tag:{tagid}|--hit\}` } | |
| ); | |
| } | |
| if (calculateScore(session.splitHand) <= 21) { | |
| output.components.push( | |
| { type: 2, label: "Split Hit", run: `\{tag:{tagid}|--split_hit\}` } | |
| ); | |
| } | |
| output.components.push( | |
| { type: 2, label: "Stand", run: `\{tag:{tagid}|--stand\}` } | |
| ); | |
| } else { | |
| output.components = [ | |
| { type: 2, label: "Hit", run: `\{tag:{tagid}|--hit\}` }, | |
| { type: 2, label: "Stand", run: `\{tag:{tagid}|--stand\}` } | |
| ]; | |
| } | |
| if (canSplit()) { | |
| output.components.push( | |
| { type: 2, label: "Split", run: `\{tag:{tagid}|--split\}` } | |
| ); | |
| } | |
| if (canDoubleDown()) { | |
| output.components.push( | |
| { type: 2, label: "Double Down", run: `\{tag:{tagid}|--double\}` } | |
| ); | |
| } | |
| if (session.state == States.GAME_OVER) { | |
| output.components = []; | |
| } | |
| console.log(JSON.stringify(output)); | |
| saveCards(); | |
| db.set(discord.user.id, session); | |
| db.save(); | |
| } | |
| function run() { | |
| createDeck(); | |
| manageSession(); | |
| parseCommands(); | |
| runGame(); | |
| } | |
| run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment