tippero/tipbot/modules/blackjack.py
moneromooo bb7d4c9f2c blackjack: fix double doubling the wrong bet amount
It would double the current full amount bet, rather than the
original bet of the current hand only.

Also fix an error message using a variable before it's initialized
when telling the player his/her balance is not enough when doing so.
2015-01-23 10:52:32 +00:00

1169 lines
44 KiB
Python

#!/bin/python
#
# Cryptonote tipbot - blackjack commands
# Copyright 2015 moneromooo
#
# The Cryptonote tipbot is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
import sys
import os
import redis
import hashlib
import time
import string
import random
import tipbot.config as config
from tipbot.log import log_error, log_warn, log_info, log_log
import tipbot.coinspecs as coinspecs
from tipbot.utils import *
from tipbot.command_manager import *
from tipbot.redisdb import *
from tipbot.betutils import *
players = dict()
utf8users = set()
deck_cards = "234567890JQKA"
deck_scores = [2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 1]
deck_suits = [ "spades", "hearts", "diamonds", "clubs" ]
deck_unicode_suits_ordering = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 1]
deck_names = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
def MakeNewDeck(decks,seed):
deck = []
for n in range(decks):
for s in range(4):
for c in range(13):
deck.append(deck_cards[c]+":"+deck_suits[s])
r = random.Random()
r.seed(seed)
r.shuffle(deck)
return deck
def DrawCard(deck):
card = deck[0]
del deck[0]
return card
def DrawForPlayer(link):
identity=link.identity()
card = DrawCard(players[identity]['deck'])
players[identity]['player_hands'][players[identity]['player_current_hand']]['hand'].append(card)
return card
def DrawForDealer(link):
identity=link.identity()
card = DrawCard(players[identity]['deck'])
players[identity]['dealer_hand'].append(card)
return card
def GetCardScore(card):
idx = deck_cards.find(card.split(':')[0])
return deck_scores[idx]
def GetCardName(card,utf8=False):
parts = card.split(':')
idx = deck_cards.find(parts[0])
if utf8:
suitidx = deck_suits.index(parts[1])
name = chr(0b11110000) + chr(0b10011111)
name = name + chr(0b10000010 + suitidx/2)
name = name + chr(0b10000000 + ((suitidx+2)%4) * 0b10000 + deck_unicode_suits_ordering[idx])
return name
else:
return deck_names[idx]
def GetHandScores(hand):
score = [0]
for card in hand:
card_score = GetCardScore(card)
for n in range(len(score)):
score[n] = score[n] + card_score
if card.split(':')[0] == 'A':
idx = len(score)
score.extend(score)
while idx < len(score):
score[idx] = score[idx] + 10
idx = idx + 1
return score
def GetHandScore(hand):
scores = GetHandScores(hand)
score = scores[0]
for s in scores:
if s > score and s <= 21:
score = s
return score
def IsSoftHand(hand):
ace_found = False
score = 0
for card in hand:
if card.split(':')[0] == 'A':
if ace_found:
score = score + 1
else:
score = score + 11
ace_found = True
else:
score = score + GetCardScore(card)
return ace_found and score <= 21
def GetPlayerCurrentHand(link):
identity=link.identity()
idx = players[identity]['player_current_hand']
hand = players[identity]['player_hands'][idx]['hand']
return hand
def GetPlayerCurrentHandScore(link):
return GetHandScore(GetPlayerCurrentHand(link))
def HandToString(hand,utf8=False,hide_last=False,with_score=False):
s = ""
if hide_last:
hand = hand[0:-1]
hand.append('?')
for card in hand:
if s != "":
if utf8:
s = s + " "
else:
s = s + ", "
if card == '?':
if utf8:
s = s + chr(0b11110000) + chr(0b10011111) + chr(0b10000010) + chr(0b10100000)
else:
s = s + card
else:
s = s + GetCardName(card,utf8)
if not utf8:
s = '['+ s + ']'
if with_score and not hide_last:
s = s + " (score " + str(GetHandScore(hand))+")"
return s
def PlayerHandsToString(link,utf8=False,with_score=False):
identity=link.identity()
S = ""
selected = players[identity]['player_current_hand']
for hand in players[identity]['player_hands']:
s = HandToString(hand['hand'],identity in utf8users,False,with_score)
if selected == 0:
s = ">>>" + s + "<<<"
selected = selected - 1
if S != "":
S = S + ", "
S = S + s
return S
def UpdateBlackjackRecord(link,win,lose,units):
try:
RecordGameResult(link,"blackjack",win,lose,units)
except Exception,e:
link.send("An error has occured")
def UpdateSidebetRecord(link,sidebet,win,lose,units):
try:
RecordGameResult(link,"blackjack:%s"%sidebet,win,lose,units)
except:
link.send("An error has occured")
def SwitchToNextHand(link):
identity=link.identity()
log_log('switching to next hand, from current %d' % players[identity]['player_current_hand'])
players[identity]['player_current_hand'] = players[identity]['player_current_hand'] + 1
if players[identity]['player_current_hand'] < len(players[identity]['player_hands']):
if not players[identity]['finished']:
dealer_hand = players[identity]['dealer_hand']
link.send("%s: Your hand is %s. Dealer's hand is %s" % (link.user.nick, PlayerHandsToString(link,True),HandToString(dealer_hand,identity in utf8users,True,False)))
elif not players[identity]['finished']:
DealerMove(link)
def IsBlackjack(hand):
return len(hand) == 2 and GetHandScore(hand) == 21
def Win(link,blackjack):
identity=link.identity()
idx = players[identity]['player_current_hand']
player_hand = players[identity]['player_hands'][idx]['hand']
dealer_hand = players[identity]['dealer_hand']
win_units = players[identity]['player_hands'][idx]['amount']
if blackjack:
win_units = win_units * 3 / 2
link.send("%s wins %s on hand %d - BLACKJACK! - %s, dealer %s" % (link.user.nick, AmountToString(win_units), idx+1, PlayerHandsToString(link,True), HandToString(dealer_hand,identity in utf8users)))
else:
link.send("%s wins %s on hand %d - %s, dealer %s" % (link.user.nick, AmountToString(win_units), idx+1, PlayerHandsToString(link,True), HandToString(dealer_hand, identity in utf8users)))
players[identity]['player_hands'][idx]['finished'] = True
UpdateBlackjackRecord(link,True,False,win_units)
SwitchToNextHand(link)
def Lose(link,blackjack):
identity=link.identity()
idx = players[identity]['player_current_hand']
player_hand = players[identity]['player_hands'][idx]['hand']
dealer_hand = players[identity]['dealer_hand']
lose_units = players[identity]['player_hands'][idx]['amount']
if blackjack:
link.send("%s loses %s on hand %d - %s, dealer BLACKJACK! %s" % (link.user.nick, AmountToString(lose_units), idx+1, PlayerHandsToString(link,True), HandToString(dealer_hand, identity in utf8users)))
else:
link.send("%s loses %s on hand %d - %s, dealer %s" % (link.user.nick, AmountToString(lose_units), idx+1, PlayerHandsToString(link,True), HandToString(dealer_hand, identity in utf8users)))
players[identity]['player_hands'][idx]['finished'] = True
UpdateBlackjackRecord(link,False,True,lose_units)
SwitchToNextHand(link)
def Draw(link,blackjack):
identity=link.identity()
idx = players[identity]['player_current_hand']
player_hand = players[identity]['player_hands'][idx]['hand']
dealer_hand = players[identity]['dealer_hand']
if blackjack:
link.send("%s pushes on hand %d - BLACKJACK vs BLACKJACK! - %s, dealer %s" % (link.user.nick, idx+1, PlayerHandsToString(link,True), HandToString(dealer_hand, identity in utf8users)))
else:
link.send("%s pushes on hand %d - %s, dealer %s" % (link.user.nick, idx+1, PlayerHandsToString(link,True), HandToString(dealer_hand, identity in utf8users)))
players[identity]['player_hands'][idx]['finished'] = True
UpdateBlackjackRecord(link,False,False,0)
SwitchToNextHand(link)
def CheckEndGame(link,force_end):
identity=link.identity()
player_score = GetPlayerCurrentHandScore(link)
dealer_score = GetHandScore(players[identity]['dealer_hand'])
dealer_blackjack = IsBlackjack(players[identity]['dealer_hand'])
if dealer_score > 21:
return Win(link,False)
if player_score==21 and dealer_score==21:
if dealer_blackjack:
return Lose(link,True)
else:
return Draw(link,False)
elif player_score==21:
return Win(link,False)
elif dealer_score==21:
return Lose(link,dealer_blackjack)
if force_end:
if player_score > dealer_score:
return Win(link,False)
elif player_score < dealer_score:
return Lose(link,False)
else:
return Draw(link,False)
def AreAllHandsFinished(link):
identity=link.identity()
for hand in players[identity]['player_hands']:
if not hand['finished']:
return False
return True
def ShouldDealerHit(link):
identity=link.identity()
dealer_hand = players[identity]['dealer_hand']
score = GetHandScore(dealer_hand)
if score < 17:
return True
if score == 17 and IsSoftHand(dealer_hand):
return True
return False
def DealerMove(link):
identity=link.identity()
log_log('dealer move - finished: %d' % players[identity]['finished'])
if AreAllHandsFinished(link):
log_log('all hands finished, marking game as finished')
players[identity]['finished'] = True
if not players[identity]['finished']:
if not IsBlackjack(players[identity]['dealer_hand']):
while ShouldDealerHit(link):
card = DrawForDealer(link)
dealer_hand = players[identity]['dealer_hand']
bustmsg = ""
if GetHandScore(dealer_hand) > 21:
bustmsg = " - Dealer busts!"
link.send("%s: Dealer draws %s: %s%s" % (link.user.nick, GetCardName(card,identity in utf8users), HandToString(dealer_hand,identity in utf8users,False,True), bustmsg))
players[identity]['finished'] = True
players[identity]['player_current_hand'] = 0
log_log('sweeping through open games')
while players[identity]['player_current_hand'] < len(players[identity]['player_hands']):
log_log('sweeping through hand %d' % players[identity]['player_current_hand'])
if players[identity]['player_hands'][players[identity]['player_current_hand']]['finished']:
log_log('%d is already finished, skipping' % players[identity]['player_current_hand'])
players[identity]['player_current_hand'] = players[identity]['player_current_hand'] + 1
continue
CheckEndGame(link,True)
log_log('done sweeping through open games')
sidebets = players[identity]['sidebets']
dealer_hand = players[identity]['dealer_hand']
if sidebets['splits']:
bet_units = sidebets['splits']
nsplits = len(players[identity]['player_hands'])
if nsplits == 1:
link.send('%s did not split - you lose %s' % (link.user.nick, AmountToString(bet_units)))
UpdateSidebetRecord(link,"splits",False,True,bet_units)
if sidebets['buster']:
bet_units = sidebets['buster']
if GetHandScore(dealer_hand) > 21:
cards = len(dealer_hand)
if cards == 3:
win_units = bet_units * 3 / 2
elif cards == 4:
win_units = bet_units * 3
elif cards == 5:
win_units = bet_units * 5
elif cards == 6:
win_units = bet_units * 11
elif cards >= 7:
win_units = bet_units * 21
link.send('The dealer busted with %s cards - you win %s' % (cards, AmountToString(win_units)))
UpdateSidebetRecord(link,"buster",True,False,win_units)
else:
link.send('The dealer did not bust - you lose %s' % (AmountToString(bet_units)))
UpdateSidebetRecord(link,"buster",False,True,bet_units)
del players[identity]
def GetNewShuffleSeed(link):
identity=link.identity()
try:
if redis_hexists('blackjack:rolls',identity):
rolls = redis_hget('blackjack:rolls',identity)
rolls = long(rolls) + 1
else:
rolls = 1
except Exception,e:
log_error('Failed to prepare roll for %s: %s' % (identity, str(e)))
raise
try:
s = GetServerSeed(link,'blackjack') + ":" + GetPlayerSeed(link,'blackjack') + ":" + str(rolls)
seed = hashlib.sha256(s).hexdigest()
redis_hset("blackjack:rolls",identity,rolls)
return rolls, seed
except Exception,e:
log_error('Failed to roll for %s: %s' % (identity,str(e)))
raise
def ParseSideBets(names,units):
sidebets = {
"total_amount_wagered": 0,
"potential_loss": 0,
"over13": None,
"under13": None,
"pair": None,
"climber": None,
"buster": None,
"splits": None,
"match": None,
"addup": None,
}
if len(config.blackjack_sidebets) == 0:
return sidebets, None
wagered_amount = 0
total_amount_wagered = 0
potential_loss = 0
payout = 0
for name in names:
name=name.strip()
if name == "":
continue
if not name in config.blackjack_sidebets:
return None, "Unavailable side bet: %s" % name
if name == "over13":
wagered_amount = units/2
payout = wagered_amount
elif name == "under13":
wagered_amount = units/2
payout = wagered_amount
elif name == "pair":
wagered_amount = units/2
payout = wagered_amount * 10
elif name == "climber":
wagered_amount = units/2
payout = wagered_amount * 21
elif name == "buster":
wagered_amount = units/2
payout = wagered_amount * 11
elif name == "splits":
wagered_amount = units/2
payout = wagered_amount * 21
elif name == "match":
wagered_amount = units/2
payout = wagered_amount * 21
elif name == "addup":
wagered_amount = units/2
payout = wagered_amount * 27
else:
return None, "Invalid side bet: %s" % name
total_amount_wagered = total_amount_wagered + wagered_amount
potential_loss = potential_loss + payout
sidebets[name] = wagered_amount
sidebets["total_amount_wagered"] = total_amount_wagered
sidebets["potential_loss"] = potential_loss
return sidebets, None
def GetBasicStrategyMove(link):
identity=link.identity()
player_hand = GetPlayerCurrentHand(link)
player_has_soft_hand = IsSoftHand(player_hand)
player_can_double = len(player_hand) == 2
player_has_split = len(players[identity]['player_hands']) > 1
player_can_split = len(players[identity]['player_hands']) < config.blackjack_split_to
player_score = GetHandScore(player_hand)
player_first_card = player_hand[0].split(':')[0]
player_second_card = player_hand[1].split(':')[0]
player_has_pair = player_first_card == player_second_card
dealer_hand = players[identity]['dealer_hand']
dealer_upcard = dealer_hand[0].split(':')[0]
if player_score == 21:
return "stand"
if player_has_split:
if player_first_card == 'A':
return "stand"
if player_has_pair:
if player_first_card in ['8', 'A']:
if player_can_split:
return "split"
if player_first_card in ['0', 'J', 'Q', 'K']:
return "stand"
if player_first_card == '9':
if dealer_upcard in ['7', '0', 'J', 'Q', 'K', 'A']:
return "stand"
if player_can_split:
return "split"
if player_first_card in ['2', '3', '7']:
if dealer_upcard in ['8', '9', '0', 'J', 'Q', 'K', 'A']:
return "hit"
if player_can_split:
return "split"
if player_first_card == '6':
if dealer_upcard in ['7', '8', '9', '0', 'J', 'Q', 'K', 'A']:
return "hit"
if player_can_split:
return "split"
if player_first_card == '5':
if dealer_upcard in ['0', 'J', 'Q', 'K', 'A']:
return "hit"
if player_can_double:
return "double"
else:
return "hit"
if player_first_card == '4':
if dealer_upcard in ['5', '6']:
if player_can_split:
return "split"
else:
return "hit"
if player_has_soft_hand:
if player_first_card == 'A':
other_card = player_second_card
else:
other_card = player_first_card
if other_card in ['8', '9']:
return "stand"
if other_card == '7':
if dealer_upcard in ['2', '7', '8']:
return "stand"
if dealer_upcard in ['3', '4', '5', '6']:
if player_can_double:
return "double"
else:
return "stand"
return "hit"
if other_card == '6':
if dealer_upcard in ['3', '4', '5', '6']:
if player_can_double:
return "double"
else:
return "hit"
return "hit"
if other_card in ['4', '5']:
if dealer_upcard in ['4', '5', '6']:
if player_can_double:
return "double"
else:
return "hit"
return "hit"
if other_card in ['2', '3']:
if dealer_upcard in ['5', '6']:
if player_can_double:
return "double"
else:
return "hit"
return "hit"
else:
if player_score >= 17:
return "stand"
if player_score >= 13:
if dealer_upcard in ['7', '8', '9', '0', 'J', 'Q', 'K', 'A']:
return "hit"
else:
return "stand"
if player_score == 12:
return "stand" if dealer_upcard in ['4', '5', '6'] else "hit"
if player_score == 11:
if dealer_upcard == 'A':
return "hit"
else:
return "double" if player_can_double else "hit"
if player_score == 10:
if dealer_upcard in ['0', 'J', 'Q', 'K', 'A']:
return "hit"
else:
return "double" if player_can_double else "hit"
if player_score == 9:
if dealer_upcard in ['3', '4', '5', '6']:
return "double" if player_can_double else "hit"
else:
return "hit"
if player_score >= 5:
return "hit"
log_error('GetBasicStrategyMove: missed a case: player %s, dealer %s' % (HandToString(player_hand), HandToString(dealer_hand)))
return None
def RecordMove(link,actual):
identity=link.identity()
basic = GetBasicStrategyMove(link)
log_info('%s %ss, basic strategy would %s' % (identity, actual, basic))
try:
p = redis_pipeline()
tname="blackjack:strategy:"+identity
alltname="blackjack:strategy:"
p.hincrby(tname,"moves",1)
if actual == basic:
p.hincrby(tname,"matching",1)
p.execute()
except Exception,e:
log_error('Failed to record move for %s: %s' % (identity, str(e)))
def Blackjack(link,cmd):
identity=link.identity()
if identity in players:
link.send("%s: you already are in a game of blackjack" % link.user.nick)
return
try:
amount=float(cmd[1])
units=long(amount*coinspecs.atomic_units)
except Exception,e:
link.send("%s: usage: !blackjack amount" % link.user.nick)
return
sidebets, reason = ParseSideBets(cmd[2:],units)
if not sidebets:
link.send("%s: invalid side bet list: %s" % (link.user.nick, reason))
return
total_amount_wagered = amount + sidebets["total_amount_wagered"] / coinspecs.atomic_units
total_units_wagered = units + sidebets["total_amount_wagered"]
potential_loss = amount * 1.5 + sidebets["potential_loss"] / coinspecs.atomic_units
potential_units_loss = long (potential_loss * coinspecs.atomic_units)
log_info('%s bets a total of %s (%.16g), potential loss %s, side bets %s' % (identity, AmountToString(total_units_wagered), total_amount_wagered, AmountToString(potential_units_loss), str(sidebets)))
valid,reason = IsBetValid(link,total_amount_wagered,config.blackjack_min_bet,config.blackjack_max_bet,potential_loss,config.blackjack_max_loss,config.blackjack_max_loss_ratio)
if not valid:
log_info("Dice: %s's bet refused: %s" % (identity, reason))
link.send("%s: %s" % (link.user.nick, reason))
return
try:
rolls, seed = GetNewShuffleSeed(link)
except Exception,e:
link.send("%s: An error occured" % link.user.nick)
return
players[identity] = {
'deck': MakeNewDeck(config.blackjack_decks,seed),
'amount': total_units_wagered,
'base_amount': units,
'player_hands': [dict({
'amount': units,
'hand': [],
'finished': False,
})],
'player_current_hand': 0,
'dealer_hand': [],
'finished': False,
'insurance': False,
'sidebets': sidebets,
}
DrawForPlayer(link)
DrawForDealer(link)
DrawForPlayer(link)
DrawForDealer(link)
if False: # TEST FOR SPLIT
if False:
players[identity]['player_hands'][0]['hand'][0].split(':')[0] = 'A'
players[identity]['player_hands'][0]['hand'][1] = players[identity]['player_hands'][0]['hand'][0]
dealer_hand = players[identity]['dealer_hand']
link.send("%s: Game %d starts. You draw %s. Dealer draws %s" % (link.user.nick, rolls, PlayerHandsToString(link,True),HandToString(dealer_hand,identity in utf8users,True,False)))
player_hand = players[identity]['player_hands'][0]['hand']
dealer_hand = players[identity]['dealer_hand']
plain_score = GetCardScore(player_hand[0])+GetCardScore(player_hand[1])
if sidebets['over13']:
bet_units = sidebets['over13']
if plain_score > 13:
win_units = bet_units
link.send('%s: your first two cards total %d, over 13 - you win %s' % (link.user.nick, plain_score, AmountToString(win_units)))
UpdateSidebetRecord(link,"over13",True,False,win_units)
else:
link.send('%s: your first two cards total %d, not over 13 - you lose %s' % (link.user.nick, plain_score, AmountToString(bet_units)))
UpdateSidebetRecord(link,"over13",False,True,bet_units)
if sidebets['under13']:
bet_units = sidebets['under13']
if plain_score < 13:
win_units = bet_units
link.send('%s: your first two cards total %d, under 13 - you win %s' % (link.user.nick, plain_score, AmountToString(win_units)))
UpdateSidebetRecord(link,"under13",True,False,win_units)
else:
link.send('%s: your first two cards total %d, not under 13 - you lose %s' % (link.user.nick, plain_score, AmountToString(bet_units)))
UpdateSidebetRecord(link,"under13",False,True,bet_units)
if sidebets['pair']:
bet_units = sidebets['pair']
if player_hand[0].split(':')[0] == player_hand[1].split(':')[0]:
win_units = bet_units * 10
link.send('%s: your first two cards are a pair - you win %s' % (link.user.nick, AmountToString(win_units)))
UpdateSidebetRecord(link,"pair",True,False,win_units)
else:
link.send('%s: your first two cards are not a pair - you lose %s' % (link.user.nick, AmountToString(bet_units)))
UpdateSidebetRecord(link,"pair",False,True,bet_units)
if sidebets['climber']:
bet_units = sidebets['climber']
if GetHandScore(player_hand) == 20:
if dealer_hand[0].split(':')[0] == 'A':
win_units = bet_units * 21
else:
win_units = bet_units * GetCardScore(dealer_hand[0])
link.send('%s: your first two cards score 20, dealer\'s first card is %s - you win %s' % (link.user.nick, GetCardName(dealer_hand[0],identity in utf8users), AmountToString(win_units)))
UpdateSidebetRecord(link,"climber",True,False,win_units)
else:
link.send('%s: your first two cards do not score 20 - you lose %s' % (link.user.nick,AmountToString(bet_units)))
UpdateSidebetRecord(link,"climber",False,True,bet_units)
if sidebets['match']:
bet_units = sidebets['match']
if player_hand[0].split(':')[0] == dealer_hand[0].split(':')[0] and player_hand[1].split(':')[0] == dealer_hand[0].split(':')[0]:
win_units = bet_units * 21
link.send('%s: your first two cards match the dealer\'s - you win %s' % (link.user.nick,AmountToString(win_units)))
UpdateSidebetRecord(link,"match",True,False,win_units)
elif player_hand[0].split(':')[0] == dealer_hand[0].split(':')[0] or player_hand[1].split(':')[0] == dealer_hand[0].split(':')[0]:
win_units = bet_units * 5
link.send('%s: one of your first two cards match the dealer\'s - you win %s' % (link.user.nick,AmountToString(win_units)))
UpdateSidebetRecord(link,"match",True,False,win_units)
else:
link.send('%s: none of your first two cards match the dealer\'s - you lose %s' % (link.user.nick,AmountToString(bet_units)))
UpdateSidebetRecord(link,"match",False,True,bet_units)
if sidebets['addup']:
bet_units = sidebets['addup']
scores = GetHandScores(player_hand)
if GetCardScore(dealer_hand[0]) in scores:
win_units = bet_units * 25
link.send('%s: your first two cards\' scores add up to the dealer\'s - you win %s' % (link.user.nick,AmountToString(win_units)))
UpdateSidebetRecord(link,"addup",True,False,win_units)
else:
link.send('%s: your first two cards\' scores do not add up to the dealer\'s - you lose %s' % (link.user.nick,AmountToString(bet_units)))
UpdateSidebetRecord(link,"addup",False,True,bet_units)
if IsBlackjack(GetPlayerCurrentHand(link)):
if IsBlackjack(dealer_hand):
Draw(link,True)
else:
Win(link,True)
return
if config.blackjack_insurance:
if dealer_hand[0].split(':')[0] == 'A':
link.send("%s: dealer's first card is an ace, you can claim !insurance as your first move" % link.user.nick)
return
if IsBlackjack(dealer_hand):
Lose(link,True)
def Insurance(link,cmd):
identity=link.identity()
if not config.blackjack_insurance:
return
if not identity in players:
link.send("%s: you are not in a game of blackjack - you can start one with !blackjack <amount>" % link.user.nick)
return
units = players[identity]['amount']
enough, reason = IsPlayerBalanceAtLeast(link,units)
if not enough:
link.send("%s: %s - please refund your account to continue playing" % (link.user.nick, reason))
return
nhands = len(players[identity]['player_hands'])
if nhands > 1 or len(GetPlayerCurrentHand(link)) > 2:
link.send("%s: you can only claim insurance as first move" % link.user.nick)
return
if players[identity]['insurance']:
link.send("%s: you can only claim insurance once" % link.user.nick)
return
dealer_hand = players[identity]['dealer_hand']
if dealer_hand[0].split(':')[0] != 'A':
link.send("%s: you can only claim insurance when the dealer's first card is an ace" % link.user.nick)
return
units = players[identity]['player_hands'][0]['amount']
insurance_units = units / 2
enough, reason = IsPlayerBalanceAtLeast(link,units + insurance_units)
if not enough:
link.send("%s: you do not have enough %s in your account to insure with %s" % (link.user.nick,coinspecs.name,idx+1,AmountToString(insurance_units)))
return
if IsBlackjack(dealer_hand):
Lose(link,True)
# From here on, players[identity] is deleted
win_insurance_units = insurance_units * 2
link.send('%s wins %s insurance - dealer had a blackjack - %s' % (link.user.nick, AmountToString(win_insurance_units), HandToString(dealer_hand,identity in utf8users,False,False)))
UpdateSidebetRecord(link,"insurance",True,False,win_insurance_units)
else:
link.send('%s loses %s insurance - dealer had no blackjack' % (link.user.nick, AmountToString(insurance_units)))
UpdateSidebetRecord(link,"insurance",False,True,insurance_units)
link.send("%s: Your hand is %s. Dealer's hand is %s" % (link.user.nick, PlayerHandsToString(link,True),HandToString(dealer_hand,identity in utf8users,True,False)))
if identity in players:
players[identity]['insurance'] = True
def IsCurrentPlayerHandASplitAce(link):
identity=link.identity()
if len(players[identity]['player_hands']) == 1:
return False
idx = players[identity]['player_current_hand']
if players[identity]['player_hands'][idx]['hand'][0].split(':')[0] == 'A':
return True
return False
def Hit(link,cmd):
identity=link.identity()
if not identity in players:
link.send("%s: you are not in a game of blackjack - you can start one with !blackjack <amount>" % link.user.nick)
return
units = players[identity]['amount']
enough, reason = IsPlayerBalanceAtLeast(link,units)
if not enough:
link.send("%s: %s - please refund your account to continue playing" % (link.user.nick, reason))
return
if IsCurrentPlayerHandASplitAce(link):
link.send("%s: You cannot hit a split ace" % (link.user.nick))
return
RecordMove(link, "hit")
card = DrawForPlayer(link)
link.send("%s: you draw %s. Your hand is %s. Dealer's hand is %s" % (link.user.nick, GetCardName(card,identity in utf8users), PlayerHandsToString(link,True),HandToString(players[identity]['dealer_hand'],identity in utf8users,True,True)))
score = GetPlayerCurrentHandScore(link)
if score > 21:
Lose(link,False)
elif score == 21:
SwitchToNextHand(link)
def Double(link,cmd):
identity=link.identity()
if not identity in players:
link.send("%s: you are not in a game of blackjack - you can start one with !blackjack <amount>" % link.user.nick)
return
if len(GetPlayerCurrentHand(link)) > 2:
link.send("%s: you can not double down with more than 2 cards" % link.user.nick)
return
units = players[identity]['amount']
enough, reason = IsPlayerBalanceAtLeast(link,units)
if not enough:
link.send("%s: %s - please refund your account to continue playing" % (link.user.nick, reason))
return
idx = players[identity]['player_current_hand']
units = players[identity]['player_hands'][idx]['amount']
enough, reason = IsPlayerBalanceAtLeast(link,units*2)
if not enough:
link.send("%s: you do not have enough %s in your account to double your bet on hand %d" % (link.user.nick,coinspecs.name,idx+1))
return
if IsCurrentPlayerHandASplitAce(link):
link.send("%s: You cannot double down on a split ace" % (link.user.nick))
return
RecordMove(link,"double")
players[identity]['player_hands'][idx]['amount'] = players[identity]['player_hands'][idx]['amount'] + units
players[identity]['amount'] = players[identity]['amount'] + units
card = DrawForPlayer(link)
link.send("%s: you draw %s. Your hand is %s. Dealer's hand is %s" % (link.user.nick, GetCardName(card,identity in utf8users), PlayerHandsToString(link,True),HandToString(players[identity]['dealer_hand'],identity in utf8users,True,True)))
score = GetPlayerCurrentHandScore(link)
if score > 21:
Lose(link,False)
else:
SwitchToNextHand(link)
def Stand(link,cmd):
identity=link.identity()
if not identity in players:
link.send("%s: you are not in a game of blackjack - you can start one with !blackjack <amount>" % link.user.nick)
return
units = players[identity]['amount']
enough, reason = IsPlayerBalanceAtLeast(link,units)
if not enough:
link.send("%s: %s - please refund your account to continue playing" % (link.user.nick, reason))
return
RecordMove(link,"stand")
link.send("%s stands" % link.user.nick)
SwitchToNextHand(link)
def Split(link,cmd):
identity=link.identity()
if not identity in players:
link.send("%s: you are not in a game of blackjack - you can start one with !blackjack <amount>" % link.user.nick)
return
units = players[identity]['amount']
enough, reason = IsPlayerBalanceAtLeast(link,units)
if not enough:
link.send("%s: %s - please refund your account to continue playing" % (link.user.nick, reason))
return
idx = players[identity]['player_current_hand']
hand = GetPlayerCurrentHand(link)
if len(hand)!=2 or GetCardScore(hand[0])!=GetCardScore(hand[1]):
link.send("%s: only pairs with the same value can be split" % (link.user.nick))
return
if len(players[identity]['player_hands']) >= config.blackjack_split_to:
link.send("%s: you can only split to %d" % (link.user.nick, config.blackjack_split_to))
return
enough, reason = IsPlayerBalanceAtLeast(link,units+players[identity]['base_amount'])
if not enough:
link.send("%s: you do not have enough %s in your account to split hand %d" % (link.user.nick,coinspecs.name,idx+1))
return
players[identity]['amount'] = players[identity]['amount'] + players[identity]['base_amount']
RecordMove(link,"split")
log_log('splitting hand %d' % idx)
split_card = hand[0]
players[identity]['player_hands'].insert(idx+1,players[identity]['player_hands'][idx].copy())
players[identity]['player_hands'][idx]['hand'] = [ split_card, DrawCard(players[identity]['deck']) ]
players[identity]['player_hands'][idx+1]['hand'] = [ split_card, DrawCard(players[identity]['deck']) ]
sidebets = players[identity]['sidebets']
if sidebets['splits']:
bet_units = sidebets['splits']
nsplits = len(players[identity]['player_hands'])
if nsplits == 2:
win_units = bet_units * 5
elif nsplits == 3:
win_units = bet_units * (11-5)
elif nsplits == 4:
win_units = bet_units * (21-11)
if nsplits == 2:
link.send('%s splits to %d - you win %s' % (link.user.nick, nsplits, AmountToString(win_units)))
else:
link.send('%s resplits to %d - you win another %s' % (link.user.nick, nsplits, AmountToString(win_units)))
UpdateSidebetRecord(link,"splits",True,False,win_units)
dealer_hand = players[identity]['dealer_hand']
link.send("%s: your hand is now %s. Dealer's hand is %s" % (link.user.nick,PlayerHandsToString(link),HandToString(dealer_hand,identity in utf8users,True,False)))
def Hand(link,cmd):
identity=link.identity()
if not identity in players:
link.send("%s: you are not in a game of blackjack - you can start one with !blackjack <amount>" % link.user.nick)
return
units = players[identity]['amount']
dealer_hand = players[identity]['dealer_hand']
link.send("%s: your total bet is %s. Your hand is %s. Dealer's hand is %s" % (link.user.nick, AmountToString(units),PlayerHandsToString(link,True),HandToString(dealer_hand,identity in utf8users,True,False)))
enough, reason = IsPlayerBalanceAtLeast(link,units)
if not enough:
link.send("%s: %s - please refund your account to continue playing" % (link.user.nick, reason))
def ResetBlackjackStats(link,cmd):
identity=link.identity()
sidentity = GetParam(cmd,1)
if sidentity:
sidentity=IdentityFromString(link,sidentity)
if sidentity and sidentity != identity:
if not IsAdmin(link):
log_error('%s is not admin, cannot see blackjack stats for %s' % (identity, sidentity))
link.send('Access denied')
return
else:
sidentity=identity
try:
ResetGameStats(link,sidentity,"blackjack")
except Exception,e:
link.send("An error occured")
def ShowBlackjackStats(link,sidentity,title):
return ShowGameStats(link,sidentity,title,"blackjack")
def GetBlackjackStats(link,cmd):
identity=link.identity()
sidentity = GetParam(cmd,1)
if sidentity:
sidentity=IdentityFromString(link,sidentity)
if sidentity and sidentity != identity:
if not IsAdmin(link):
log_error('%s is not admin, cannot see blackjack stats for %s' % (identity, sidentity))
link.send('Access denied')
return
else:
sidentity=identity
ShowBlackjackStats(link,sidentity,NickFromIdentity(sidentity))
ShowBlackjackStats(link,"reset:"+sidentity,'%s since reset' % NickFromIdentity(sidentity))
ShowBlackjackStats(link,'','overall')
def PlayerSeed(link,cmd):
identity=link.identity()
fair_string = GetParam(cmd,1)
if not fair_string:
link.send("Usage: !playerseed <string>")
return
try:
SetPlayerSeed(link,'blackjack',fair_string)
except Exception,e:
log_error('Failed to save player seed for %s: %s' % (identity, str(e)))
link.send('An error occured')
def FairCheck(link,cmd):
identity=link.identity()
try:
seed = GetServerSeed(link,'blackjack')
except Exception,e:
log_error('Failed to get server seed for %s: %s' % (identity,str(e)))
link.send('An error has occured')
return
try:
GenerateServerSeed(link,'blackjack')
except Exception,e:
log_error('Failed to generate server seed for %s: %s' % (identity,str(e)))
link.send('An error has occured')
return
link.send('%s: your server seed was %s - it has now been reset; see !fair for details' % (link.user.nick,str(seed)))
def Seeds(link,cmd):
identity=link.identity()
try:
sh = GetServerSeedHash(link,'blackjack')
ps = GetPlayerSeed(link,'blackjack')
except Exception,e:
log_error('Failed to get server seed for %s: %s' % (identity,str(e)))
link.send('An error has occured')
return
link.send('%s: your server seed hash is %s' % (link.user.nick,str(sh)))
if ps == "":
link.send('%s: you have not set a player seed' % link.user.nick)
else:
link.send('%s: your player seed hash is %s' % (link.user.nick,str(ps)))
def UseUTF8(link,cmd):
identity=link.identity()
onoff=GetParam(cmd,1)
if not onoff:
if identity in utf8users:
link.send('%s: utf8 is enabled'%link.user.nick)
else:
link.send('%s: utf8 is disabled'%link.user.nick)
elif onoff=="on":
utf8users.add(identity)
link.send('%s: utf8 is now enabled'%link.user.nick)
elif onoff=="off":
utf8users.discard(identity)
link.send('%s: utf8 is now disabled'%link.user.nick)
else:
link.send('usage: !utf8 [on|off]')
def Fair(link,cmd):
link.send_private("%s's blackjack betting is provably fair" % config.tipbot_name)
link.send_private("The deck shuffling is determined by three pieces of information:")
link.send_private(" - your server seed. You can see its hash with !seeds")
link.send_private(" - your player seed. Empty by default, you can set it with !playerseed")
link.send_private(" - the game number, displayed with each game you make")
link.send_private("To verify past games were fair, use !faircheck")
link.send_private("You will be given your server seed, and a new one will be generated")
link.send_private("for future games. Then follow these steps:")
link.send_private("Create a deck: 234567890JQKA - repeated %d times (4 suits, %d decks)" % (4*config.blackjack_decks, config.blackjack_decks))
link.send_private("Calculate the SHA-256 sum of serverseed:playerseed:gamenumber")
link.send_private("Use the resulting string as the seed for the Mersenne Twister PRNG")
link.send_private("Shuffle the deck using the Fisher-Yates algorithm with that PRNG")
link.send_private("Starting cards are dealt in the order: player, dealer, player, dealer")
link.send_private("See !faircode for Python code implementing this check")
def FairCode(link,cmd):
link.send_private("This Python 2 code takes the seeds and game number and outputs the shuffled")
link.send_private("deck used in the corresponding game. Run it with three arguments: server seed,")
link.send_private("player seed (use '' if you did not set any), and game number.")
link.send_private("import sys,hashlib,random")
link.send_private("try:")
link.send_private(" deck=['2','3','4','5','6','7','8','9','10','J','Q','K','A']*%s" % (4*config.blackjack_decks))
link.send_private(" s=hashlib.sha256(sys.argv[1]+':'+sys.argv[2]+':'+sys.argv[3]).hexdigest()")
link.send_private(" random.Random(s).shuffle(deck)")
link.send_private(" print str(deck)")
link.send_private("except:")
link.send_private(" print 'need serverseed, playerseed, and game number'")
# rough house edges:
# over13: 7%
# under13: 10%
# pair: 6%
# climber: 3%
# buster: 5%
# splits: 8%
# match: 5%
# addup: 8%
def SideBets(link,cmd):
link.send_private("Side bets are made by adding their name after the !blackjack <amount> command, eg:")
link.send_private(" !blackjack 1 buster over13 - bets 0.5 for dealer busting, and 0.5 for first 2 cards sum over 13")
link.send_private("Each side bet wager is for half of the amount wagered for the main blackjack game")
link.send_private("Payouts depend on the side bet:")
if "over13" in config.blackjack_sidebets:
link.send_private(" * over13 - 1:1 - Player's first two cards scores over 13 (aces count as 1)")
if "under13" in config.blackjack_sidebets:
link.send_private(" * under13 - 1:1 - Player's first two cards scores under 13 (aces count as 1)")
if "pair" in config.blackjack_sidebets:
link.send_private(" * pair - 12:1 - Player's first two cards are a pair")
if "climber" in config.blackjack_sidebets:
link.send_private(" * climber - 2:1 to 21:1 - Player's first two cards scores 20, payout as dealer's first card's score, ace pays 21:1")
if "buster" in config.blackjack_sidebets:
link.send_private(" * buster - 3:2 to 21:1 - Dealer busts with 3 cards (3:2), 4 (3:1), 5 (5:1), 6 (11:1), or 7+ (21:1)")
if "splits" in config.blackjack_sidebets:
link.send_private(" * splits - 5:1 to 21:1 - Split your hand to 2 (5:1), 3 (11:1), or 4 (21:1)")
if "match" in config.blackjack_sidebets:
link.send_private(" * match - 5:1 to 21:1 - Match the dealer's first card with one (5:1) or both (21:1) or the player's first two cards")
if "addup" in config.blackjack_sidebets:
link.send_private(" * addup - 27:1 - Sum of player's first two cards' scores match the dealer's first card's score")
def BlackjackHelp(link):
link.send_private("The blackjack module is a provably fair %s blackjack betting game" % coinspecs.name)
link.send_private("Basic usage: !blackjack <amount>")
link.send_private("The goal is to get a hand totalling as close to 21 without going over")
link.send_private("and have a total higher than the dealer. Suits do not matter.")
link.send_private("Available moves are: !hit (get a new card), !stand (finish playing the curent),")
link.send_private("hand, !double (double your bet and take a final card), !split (split a hand of")
link.send_private("two cards with equal value), and !insurance (when the dealer's first card is an ace")
link.send_private("See !fair and !faircode for a description of the provable fairness of the game")
link.send_private("See !faircheck to get the server seed to check past games were fair")
if len(config.blackjack_sidebets) > 0:
link.send_private("See !sidebets for a list of available side bets and how to use them")
random.seed(time.time())
RegisterModule({
'name': __name__,
'help': BlackjackHelp,
})
RegisterCommand({
'module': __name__,
'name': 'blackjack',
'parms': '<amount-in-monero>' if len(config.blackjack_sidebets) == 0 else '<amount-in-monero> [sidebet1 [sidebet2...]]',
'function': Blackjack,
'registered': True,
'help': "start a blackjack game - blackjack pays 3:2"
})
RegisterCommand({
'module': __name__,
'name': 'hit',
'function': Hit,
'registered': True,
'help': "Hit (draw a new card on the current hand)"
})
RegisterCommand({
'module': __name__,
'name': 'double',
'function': Double,
'registered': True,
'help': "Double down (double bet and draw a final card on the current hand)"
})
RegisterCommand({
'module': __name__,
'name': 'stand',
'function': Stand,
'registered': True,
'help': "Stand (finish the current hand)"
})
RegisterCommand({
'module': __name__,
'name': 'split',
'function': Split,
'registered': True,
'help': "Split current hand if first two cards are a pair - split to %d max" % config.blackjack_split_to
})
if config.blackjack_insurance:
RegisterCommand({
'module': __name__,
'name': 'insurance',
'function': Insurance,
'registered': True,
'help': "Insure against a dealer blackjack with half your bet (offered if the dealer's first card in an ace) - paid 2:1"
})
RegisterCommand({
'module': __name__,
'name': 'hand',
'function': Hand,
'registered': True,
'help': "Show your and the dealer's current hands"
})
RegisterCommand({
'module': __name__,
'name': 'stats',
'parms': '[<name>]',
'function': GetBlackjackStats,
'registered': True,
'help': "displays your blackjack stats"
})
RegisterCommand({
'module': __name__,
'name': 'resetstats',
'parms': '[<name>]',
'function': ResetBlackjackStats,
'registered': True,
'help': "resets your Blackjack stats"
})
if len(config.blackjack_sidebets) > 0:
RegisterCommand({
'module': __name__,
'name': 'sidebets',
'function': SideBets,
'registered': True,
'help': "List the available side bets"
})
RegisterCommand({
'module': __name__,
'name': 'playerseed',
'parms': '<string>',
'function': PlayerSeed,
'registered': True,
'help': "set a custom seed to use in the hash calculation"
})
RegisterCommand({
'module': __name__,
'name': 'seeds',
'function': Seeds,
'registered': True,
'help': "Show hash of your current server seed and your player seed"
})
RegisterCommand({
'module': __name__,
'name': 'utf8',
'parms': '[on|off]',
'function': UseUTF8,
'registered': True,
'help': "Enable or disable use of UTF-8 to display cards"
})
RegisterCommand({
'module': __name__,
'name': 'faircheck',
'function': FairCheck,
'registered': True,
'help': "Check provably fair rolls"
})
RegisterCommand({
'module': __name__,
'name': 'fair',
'function': Fair,
'registered': True,
'help': "describe the provably fair blackjack game"
})
RegisterCommand({
'module': __name__,
'name': 'faircode',
'function': FairCode,
'registered': True,
'help': "Show sample Python code to check game fairness"
})