@@ -0,0 +1,42 @@
|
|||||||
|
# Discord Blackjack
|
||||||
|
|
||||||
|
This all started when some of my buddies and I saw the games feature in Discord voice chat. We wanted to play the built in Blackjack game but didn't like the account permissions it asked for. The next day, this bot was up and running, letting us play all the Blackjack we want on Discord!
|
||||||
|
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Create a .env file containing your Discord bot token and optionally the channel Id that you'd like scoreboard messages sent to.
|
||||||
|
```
|
||||||
|
.env:
|
||||||
|
DISCORD_KEY=<Bot Token>
|
||||||
|
DISCORD_CHAN=<Channel Id>
|
||||||
|
```
|
||||||
|
Create a virtual environment and install the required packages.
|
||||||
|
```
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
Finally, from your venv, run the bot.
|
||||||
|
```
|
||||||
|
python3 bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
```
|
||||||
|
discord.py
|
||||||
|
python-dotenv
|
||||||
|
```
|
||||||
|
# Playing the game
|
||||||
|
- Each player begins with $100.
|
||||||
|
- To begin, place a bet with: $deal \<bet amount> | $d \<bet amount>
|
||||||
|
- To draw an additional card: $hit | $h
|
||||||
|
- To stand: $stand | $s
|
||||||
|
- To get your current stats: $info | $i
|
||||||
|
- For help: $help | $h
|
||||||
|
|
||||||
|
# Scoreboard and Allowance
|
||||||
|
The scoreboard is optional and will send a message containing the stats of all players. This can be configured with Cron jobs or scheduled tasks to repeat automatically. The scoreboard also gives every player an additional $10 by default, preventing lockout by way of repeated losses.
|
||||||
|
|
||||||
|
# License
|
||||||
|
GPLV3, as all good software should be :)
|
||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(game, player, phand, dhand):
|
||||||
|
game.player = player
|
||||||
|
game.phand = phand
|
||||||
|
game.dhand = dhand
|
||||||
|
def __str__(game):
|
||||||
|
return f"{game.player} {game.phand}"
|
||||||
|
|
||||||
|
class Player:
|
||||||
|
def __init__(player, name, wins, losses, money, bet):
|
||||||
|
player.name = name
|
||||||
|
player.wins = wins
|
||||||
|
player.losses = losses
|
||||||
|
player.money = money
|
||||||
|
player.bet = bet
|
||||||
|
def __str__(player):
|
||||||
|
return f"{player.name} WL: {player.wins}/{player.losses} | ${player.money}"
|
||||||
|
|
||||||
|
def getgame(activegames, author):
|
||||||
|
gameexists = False
|
||||||
|
i = -1
|
||||||
|
for x in activegames:
|
||||||
|
i += 1
|
||||||
|
if x.player == author:
|
||||||
|
gameexists = True
|
||||||
|
return [gameexists, i]
|
||||||
|
|
||||||
|
def newhand():
|
||||||
|
hand = []
|
||||||
|
hand.append(random.randint(2, 11))
|
||||||
|
hand.append(random.randint(2, 11))
|
||||||
|
return aces(hand)
|
||||||
|
|
||||||
|
def aces(hand):
|
||||||
|
i = 0
|
||||||
|
total = totalhand(hand)
|
||||||
|
if total > 21:
|
||||||
|
for card in hand:
|
||||||
|
if card == 11:
|
||||||
|
hand[i] = 1
|
||||||
|
break
|
||||||
|
i = i + 1
|
||||||
|
return hand
|
||||||
|
|
||||||
|
def totalhand(hand):
|
||||||
|
total = 0
|
||||||
|
for card in hand:
|
||||||
|
total = total + card
|
||||||
|
return total
|
||||||
|
|
||||||
|
def showhand(hand, player, action):
|
||||||
|
strlist = [str(card) for card in hand]
|
||||||
|
handstr = ",".join(strlist)
|
||||||
|
msg = f"> :joker::joker:{player} {action}: __{handstr}__ :joker::joker:\n>\t\t\t\t\tTotal: {totalhand(hand)}"
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def score(dtotal, ptotal):
|
||||||
|
won = False
|
||||||
|
draw = False
|
||||||
|
winmsg = "> :tada: **__YOU WIN!__** :tada:"
|
||||||
|
losemsg = "> :sob: **__You lose__** :sob:"
|
||||||
|
if dtotal > 21 and ptotal <= 21:
|
||||||
|
won = True
|
||||||
|
elif dtotal < 21 and ptotal > 21:
|
||||||
|
won = False
|
||||||
|
elif dtotal < 21 and ptotal < 21:
|
||||||
|
if dtotal > ptotal:
|
||||||
|
won = False
|
||||||
|
elif dtotal < ptotal:
|
||||||
|
won = True
|
||||||
|
elif dtotal == ptotal:
|
||||||
|
draw = True
|
||||||
|
elif dtotal == 21 and ptotal < 21:
|
||||||
|
won = False
|
||||||
|
elif dtotal < 21 and ptotal == 21:
|
||||||
|
won = True
|
||||||
|
elif dtotal == 21 and ptotal > 21:
|
||||||
|
won = False
|
||||||
|
elif dtotal > 21 and ptotal == 21:
|
||||||
|
won = True
|
||||||
|
elif dtotal > 21 and ptotal > 21:
|
||||||
|
draw = True
|
||||||
|
elif dtotal == ptotal:
|
||||||
|
draw = True
|
||||||
|
else: return ["SCORING ERROR", won, draw]
|
||||||
|
|
||||||
|
if won == True:
|
||||||
|
return [winmsg, won, draw]
|
||||||
|
elif draw == True:
|
||||||
|
return ["> :clown: It's a push :clown:", won, draw]
|
||||||
|
else:
|
||||||
|
return [losemsg, won, draw]
|
||||||
|
|
||||||
|
def getstats(player):
|
||||||
|
fn = "stats/"+player+".txt"
|
||||||
|
try:
|
||||||
|
with open(fn, "r") as f:
|
||||||
|
currentstats = f.read().split(",")
|
||||||
|
if len(currentstats) < 5:
|
||||||
|
print("[-] Error parsing player stats")
|
||||||
|
else:
|
||||||
|
stats = Player(currentstats[0], currentstats[1], currentstats[2], currentstats[3], currentstats[4])
|
||||||
|
except FileNotFoundError:
|
||||||
|
initplayer(player)
|
||||||
|
stats = getstats(player)
|
||||||
|
return stats
|
||||||
|
|
||||||
|
def initplayer(player):
|
||||||
|
fn = "stats/"+player+".txt"
|
||||||
|
try:
|
||||||
|
with open(fn, "x") as f:
|
||||||
|
f.write(player+",0,0,100,0")
|
||||||
|
except FileExistsError:
|
||||||
|
print("[-] Error parsing player data!")
|
||||||
|
|
||||||
|
def writestats(player):
|
||||||
|
fn = "stats/"+player.name+".txt"
|
||||||
|
with open(fn, "w") as f:
|
||||||
|
statsline = player.name+","+player.wins+","+player.losses+","+str(player.money)+","+str(player.bet)
|
||||||
|
f.writelines(statsline)
|
||||||
|
|
||||||
|
async def hit(message, currentgame, player):
|
||||||
|
gameover = False
|
||||||
|
if totalhand(currentgame.phand) >= 21:
|
||||||
|
await stand(message, currentgame, player)
|
||||||
|
gameover = True
|
||||||
|
else:
|
||||||
|
currentgame.phand.append(random.randint(2,11))
|
||||||
|
currentgame.phand = aces(currentgame.phand)
|
||||||
|
await message.channel.send(showhand(currentgame.phand, currentgame.player, "drew"))
|
||||||
|
if totalhand(currentgame.phand) > 21:
|
||||||
|
await message.channel.send(f"> :boom: **BUST** :boom:")
|
||||||
|
await stand(message, currentgame, player)
|
||||||
|
gameover = True
|
||||||
|
return gameover
|
||||||
|
|
||||||
|
async def stand(message, currentgame, player):
|
||||||
|
await message.channel.send(showhand(currentgame.dhand, "Dealer", "hand"))
|
||||||
|
if totalhand(currentgame.dhand) >= 17:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
while totalhand(currentgame.dhand) < 17:
|
||||||
|
currentgame.dhand.append(random.randint(2,11))
|
||||||
|
currentgame.dhand = aces(currentgame.dhand)
|
||||||
|
await message.channel.send(showhand(currentgame.dhand, "Dealer", "drew"))
|
||||||
|
result = score(totalhand(currentgame.dhand), totalhand(currentgame.phand))
|
||||||
|
player = player
|
||||||
|
if result[1] == True:
|
||||||
|
payout = int(player.money) + int(player.bet)
|
||||||
|
updatedplayer = Player(player.name, str(int(player.wins) + 1), str(player.losses), payout, 0)
|
||||||
|
elif result[1] == False and result[2] == True:
|
||||||
|
updatedplayer = Player(player.name, player.wins, player.losses, player.money, 0)
|
||||||
|
else:
|
||||||
|
payout = int(player.money) - int(player.bet)
|
||||||
|
updatedplayer = Player(player.name, player.wins, str(int(player.losses) + 1), payout, 0)
|
||||||
|
writestats(updatedplayer)
|
||||||
|
await message.channel.send(result[0])
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import blackjack as b
|
||||||
|
import random
|
||||||
|
import discord
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
load_dotenv()
|
||||||
|
api_key = getenv("DISCORD_KEY")
|
||||||
|
activegames = []
|
||||||
|
gameexists = False
|
||||||
|
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.message_content = True
|
||||||
|
client = discord.Client(intents=intents)
|
||||||
|
@client.event
|
||||||
|
async def on_ready():
|
||||||
|
print(f'Dealer on duty: {client.user}')
|
||||||
|
|
||||||
|
@client.event
|
||||||
|
async def on_message(message):
|
||||||
|
msg = message.content.lower()
|
||||||
|
if message.author == client.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
player = b.getstats(message.author.name.lower())
|
||||||
|
|
||||||
|
if msg.startswith("$deal") or msg.startswith("$d"):
|
||||||
|
bet = msg.split()
|
||||||
|
gameinfo = b.getgame(activegames, player.name)
|
||||||
|
if gameinfo[0] == True:
|
||||||
|
await message.channel.send("> **Game in progress, please !hit | !stand**")
|
||||||
|
elif len(bet) < 2:
|
||||||
|
await message.channel.send(">>> :clown: **Please place a bet!** :clown:\n\t\t\t\t__$u for help__")
|
||||||
|
elif bet[1].isnumeric() == False:
|
||||||
|
await message.channel.send(">>> :clown: **Please place a bet!** :clown:\n\t\t\t\t__$u for help__")
|
||||||
|
elif int(bet[1]) > int(player.money):
|
||||||
|
await message.channel.send("> :clown: **BET INVALID NOT ENOUGH CASH** :clown:")
|
||||||
|
else:
|
||||||
|
player = b.Player(player.name, player.wins, player.losses, player.money, bet[1])
|
||||||
|
b.writestats(player)
|
||||||
|
newgame = b.Game(player.name, b.newhand(), b.newhand())
|
||||||
|
await message.channel.send(f"> :clubs::diamonds: **Welcome __{newgame.player}!__** :hearts::spades:\n{b.showhand(newgame.phand, 'Player', 'hand')}\n> :joker::joker:Dealer hand: __{newgame.dhand[0]},?__ :joker::joker:\n>\t\t\t\t\tTotal: ?")
|
||||||
|
activegames.append(newgame)
|
||||||
|
|
||||||
|
elif msg.startswith("$hit") or msg.startswith("$h"):
|
||||||
|
player = b.getstats(message.author.name.lower())
|
||||||
|
gameinfo = b.getgame(activegames, player.name)
|
||||||
|
if gameinfo[0] == False:
|
||||||
|
await message.channel.send("No game found, please start one with $deal or get help with $usage!")
|
||||||
|
else:
|
||||||
|
currentgame = activegames[gameinfo[1]]
|
||||||
|
gameover = await b.hit(message, currentgame, player)
|
||||||
|
if gameover == True:
|
||||||
|
del activegames[gameinfo[1]]
|
||||||
|
|
||||||
|
elif msg.startswith("$stand") or msg.startswith("$s"):
|
||||||
|
player = b.getstats(message.author.name.lower())
|
||||||
|
gameinfo = b.getgame(activegames, player.name)
|
||||||
|
if gameinfo[0] == False:
|
||||||
|
await message.channel.send("No game found, please start one with !deal")
|
||||||
|
else:
|
||||||
|
currentgame = activegames[gameinfo[1]]
|
||||||
|
await b.stand(message, currentgame, player)
|
||||||
|
del activegames[gameinfo[1]]
|
||||||
|
|
||||||
|
elif msg.startswith("$useage") or msg.startswith("$u"):
|
||||||
|
await message.channel.send(">>> Welcome to the :clubs::hearts:**__Blackjack Bot__**:diamonds::spades:\nTo sart a game, use: $deal/$d + bet | ex: $d 10\nTo hit, use: $hit/$h\nTo stand, use: $stand/$s\nWritten by **__0xVoodoo__**")
|
||||||
|
|
||||||
|
elif msg.startswith("$info") or msg.startswith("$i"):
|
||||||
|
await message.channel.send(f">>> :joker: Blackjack Player: **__{player.name}__**\n:diamonds: W|L: {player.wins} | {player.losses}\n:money_with_wings: Money: {player.money}")
|
||||||
|
|
||||||
|
client.run(api_key)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
discord.py
|
||||||
|
python-dotenv
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import discord
|
||||||
|
import os
|
||||||
|
import blackjack as b
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
load_dotenv()
|
||||||
|
api_key = os.getenv("DISCORD_KEY")
|
||||||
|
channel_id = os.getenv("DISCORD_CHAN")
|
||||||
|
print(channel_id)
|
||||||
|
|
||||||
|
print("[*] Initializing scoreboard")
|
||||||
|
scoreboard = []
|
||||||
|
msg = f">>> **__---LEADERBOARD---__**\n"
|
||||||
|
players = os.listdir(path="stats/")
|
||||||
|
for player in players:
|
||||||
|
currentplayer = os.path.basename(player).split('.')[0]
|
||||||
|
playerstats = b.getstats(currentplayer)
|
||||||
|
scoreboard.append(playerstats)
|
||||||
|
updatedplayer = b.Player(playerstats.name, playerstats.wins, playerstats.losses, str(int(playerstats.money) + 10), playerstats.bet)
|
||||||
|
b.writestats(updatedplayer)
|
||||||
|
scoreboard.sort(reverse=True, key=lambda player: player.money)
|
||||||
|
print("[*] Scoreboard ready")
|
||||||
|
place = 0;
|
||||||
|
while place < len(scoreboard):
|
||||||
|
if place == 0:
|
||||||
|
msg = msg + f':crown: - **__{scoreboard[place].name}__** - {scoreboard[place].money}\n'
|
||||||
|
elif place == 1:
|
||||||
|
msg = msg + f':second_place: - **__{scoreboard[place].name}__** - {scoreboard[place].money}\n'
|
||||||
|
elif place == 2:
|
||||||
|
msg = msg + f':third_place: - **__{scoreboard[place].name}__** - {scoreboard[place].money}\n'
|
||||||
|
else:
|
||||||
|
msg = msg + f'**{place + 1}** - **{scoreboard[place].name}** - {scoreboard[place].money}\n'
|
||||||
|
place += 1
|
||||||
|
print("[*] Message created")
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.message_content = True
|
||||||
|
client = discord.Client(intents=intents)
|
||||||
|
print("[*] Bot configured")
|
||||||
|
@client.event
|
||||||
|
async def on_ready():
|
||||||
|
print("[*] Bot authenticated")
|
||||||
|
channel = client.get_channel(channel_id)
|
||||||
|
if channel:
|
||||||
|
await channel.send(msg)
|
||||||
|
print("[+] Scoreboard sent!")
|
||||||
|
else:
|
||||||
|
print("[-] CHANNEL NOT FOUND!")
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
client.run(api_key)
|
||||||
Reference in New Issue
Block a user