diff --git a/files/assets/js/casino/blackjack_screen.js b/files/assets/js/casino/blackjack_screen.js
index 762b37d3f..97fd8fe24 100644
--- a/files/assets/js/casino/blackjack_screen.js
+++ b/files/assets/js/casino/blackjack_screen.js
@@ -1,6 +1,6 @@
-function makeBlackjackRequest(action) {
+function makeBlackjackRequest(action, split = false) {
const xhr = new XMLHttpRequest();
- xhr.open("post", `/casino/twentyone/${action}`);
+ xhr.open("post", `/casino/twentyone/${action}?hand=${split ? 'split' : 'player'}`);
xhr.setRequestHeader('xhr', 'xhr');
xhr.onload = handleBlackjackResponse.bind(null, xhr);
xhr.blackjackAction = action;
@@ -37,7 +37,8 @@ function handleBlackjackResponse(xhr) {
hit: "Unable to hit.",
stay: "Unable to stay.",
"double-down": "Unable to double down.",
- "buy-insurance": "Unable to buy insurance."
+ "buy-insurance": "Unable to buy insurance.",
+ "split": "Unable to split"
};
result = results[xhr.blackjackAction];
@@ -51,11 +52,14 @@ function handleBlackjackResponse(xhr) {
function updateBlackjackActions(state) {
const actions = Array.from(document.querySelectorAll('.twentyone-btn'));
+ document.getElementById(`twentyone-SPLIT_ACTIONS`).style.display = 'none'
// Hide all actions.
actions.forEach(action => action.style.display = 'none');
if (state) {
+ if(state.actions.some((action) => action === 'HIT_SPLIT')) state.actions.push('SPLIT_ACTIONS');
+
// Show the correct ones.
state.actions.forEach(action => document.getElementById(`twentyone-${action}`).style.display = 'inline-block');
} else {
@@ -95,6 +99,7 @@ function updateBlackjackTable(state) {
`;
const dealerCards = makeCardset(state.dealer, 'Dealer', state.dealer_value);
const playerCards = makeCardset(state.player, 'Player', state.player_value);
+ const playerSplitCards = state.has_player_split ? makeCardset(state.player_split, 'Player', state.player_split_value) : '';
updateBlackjackActions(state);
@@ -103,47 +108,92 @@ function updateBlackjackTable(state) {
${dealerCards}
${playerCards}
+ ${playerSplitCards}
`;
const currency = state.wager.currency === 'coins' ? 'coins' : 'marseybux';
- switch (state.status) {
- case 'BLACKJACK':
- updateResult(`Blackjack: Received ${state.payout} ${currency}`, "warning");
- break;
- case 'WON':
- updateResult(`Won: Received ${state.payout} ${currency}`, "success");
- break;
- case 'PUSHED':
- updateResult(`Pushed: Received ${state.wager.amount} ${currency}`, "success");
- break;
- case 'LOST':
- let lost = state.wager.amount;
- if (state.player_doubled_down) {
- lost *= 2;
- }
- updateResult(`Lost ${lost} ${currency}`, "danger");
- break;
- default:
- break;
+ const gameCompleted = ['BLACKJACK', 'WON', 'PUSHED', 'LOST'].indexOf(state.status) !== -1 && (!state.has_player_split || ['WON', 'PUSHED', 'LOST'].indexOf(state.status_split) !== -1);
+
+ if(gameCompleted) {
+ switch (state.status) {
+ case 'BLACKJACK':
+ updateResult(`Blackjack: Received ${state.payout} ${currency}`, "warning");
+ break;
+ case 'WON':
+ if(state.status_split === 'LOST') {
+ updateResult(`Won and Lost: Received 0 ${currency}`, "success");
+ }
+ else if(state.status_split === 'PUSHED') {
+ updateResult(`Won and PUSHED: Received ${state.payout} ${currency}`, "success");
+ }
+ else {
+ updateResult(`Won: Received ${state.payout} ${currency}`, "success");
+ }
+ break;
+ case 'PUSHED':
+ if(state.status_split === 'WON') {
+ updateResult(`Won and PUSHED: Received ${state.payout} ${currency}`, "success");
+ }
+ else if(state.status_split === 'LOST') {
+ updateResult(`Lost and Pushed: Lost ${state.wager.amount} ${currency}`, "danger");
+ }
+ else {
+ updateResult(`Pushed: Received ${state.wager.amount} ${currency}`, "success");
+ }
+
+ break;
+ case 'LOST':
+ if(state.status_split === 'WON') {
+ updateResult(`Won and Lost: Received 0 ${currency}`, "success");
+ }
+ else if(state.status_split === 'PUSHED') {
+ updateResult(`Lost and Pushed: Lost ${state.wager.amount} ${currency}`, "danger");
+ }
+ else {
+ let lost = state.wager.amount;
+ if (state.player_doubled_down || state.has_player_split) {
+ lost *= 2;
+ }
+ updateResult(`Lost ${lost} ${currency}`, "danger");
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ updateCardsetBackgrounds(state, true);
+ }
+ else {
+ updateCardsetBackgrounds(state);
}
- updateCardsetBackgrounds(state);
- if (state.status === 'PLAYING') {
+ if (state.status === 'PLAYING' || (state.has_player_split && state.status_split === 'PLAYING')) {
updateResult(`${state.wager.amount} ${currency} are at stake`, "success");
} else {
enableWager();
}
}
-function updateCardsetBackgrounds(state) {
+function updateCardsetBackgrounds(state, complete = false) {
const cardsets = Array.from(document.querySelectorAll('.blackjack-cardset'));
for (const cardset of cardsets) {
['PLAYING', 'LOST', 'PUSHED', 'WON', 'BLACKJACK'].forEach(status => cardset.classList.remove(`blackjack-cardset__${status}`));
- cardset.classList.add(`blackjack-cardset__${state.status}`)
}
+ if(complete){
+ const wager = state.has_player_split ? state?.wager?.amount * 2 : state?.wager?.amount;
+ let dealerShows = state.payout > wager ? 'WON': 'LOST';
+ if(state.payout === wager) dealerShows = 'PUSHED'
+ cardsets[0]?.classList.add(`blackjack-cardset__${dealerShows}`)
+ }
+ else {
+ cardsets[0]?.classList.add(`blackjack-cardset__PLAYING`)
+ }
+ cardsets[1]?.classList.add(`blackjack-cardset__${state.status}`)
+ cardsets[2]?.classList.add(`blackjack-cardset__${state.status_split}`)
}
function deal() {
@@ -162,8 +212,8 @@ function deal() {
drawFromDeck();
}
-function hit() {
- const request = makeBlackjackRequest('hit');
+function hit(split = false) {
+ const request = makeBlackjackRequest('hit', split);
const form = new FormData();
form.append("formkey", formkey());
request.send(form);
@@ -171,13 +221,21 @@ function hit() {
drawFromDeck();
}
-function stay() {
- const request = makeBlackjackRequest('stay');
+function hitSplit() {
+ hit(true);
+}
+
+function stay(split = false) {
+ const request = makeBlackjackRequest('stay', split);
const form = new FormData();
form.append("formkey", formkey());
request.send(form);
}
+function staySplit() {
+ stay(true);
+}
+
function doubleDown() {
const request = makeBlackjackRequest('double-down');
const form = new FormData();
@@ -194,6 +252,13 @@ function buyInsurance() {
request.send(form);
}
+function split() {
+ const request = makeBlackjackRequest('split');
+ const form = new FormData();
+ form.append("formkey", formkey());
+ request.send(form);
+}
+
function buildBlackjackDeck() {
document.getElementById('blackjack-table-deck').innerHTML = `
diff --git a/files/helpers/twentyone.py b/files/helpers/twentyone.py
index 85a25b35e..fb14acb88 100644
--- a/files/helpers/twentyone.py
+++ b/files/helpers/twentyone.py
@@ -25,6 +25,9 @@ class BlackjackAction(str, Enum):
STAY = "STAY"
DOUBLE_DOWN = "DOUBLE_DOWN"
BUY_INSURANCE = "BUY_INSURANCE"
+ SPLIT = 'SPLIT'
+ HIT_SPLIT = 'HIT_SPLIT'
+ STAY_SPLIT = 'STAY_SPLIT'
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
@@ -37,18 +40,22 @@ minimum_bet = 5
def get_initial_state():
return {
"player": [],
+ "player_split": [],
+ "player_split_value": 0,
"player_value": 0,
"dealer": [],
"dealer_value": 0,
+ "has_player_split": False,
"player_bought_insurance": False,
"player_doubled_down": False,
"status": BlackjackStatus.PLAYING,
+ "status_split": BlackjackStatus.PLAYING,
"actions": [],
"wager": {
"amount": 0,
"currency": "coins"
},
- "payout": 0
+ "payout": 0,
}
@@ -119,7 +126,7 @@ def create_new_game(gambler, wager, currency):
raise Exception(f"Gambler cannot afford to bet {wager} {currency}.")
-def handle_blackjack_deal(state):
+def handle_blackjack_deal(state, split):
deck = build_deck(state)
first = deck.pop()
second = deck.pop()
@@ -131,21 +138,28 @@ def handle_blackjack_deal(state):
return state
-def handle_blackjack_hit(state):
+def handle_blackjack_hit(state, split = False):
deck = build_deck(state)
- next_card = deck.pop()
- state['player'].append(next_card)
+ if(split and state['has_player_split'] and state['status_split'] != BlackjackStatus.LOST):
+ next_card = deck.pop()
+ state['player_split'].append(next_card)
+ elif(state['status'] != BlackjackStatus.LOST):
+ next_card = deck.pop()
+ state['player'].append(next_card)
return state
-def handle_blackjack_stay(state):
- state['status'] = BlackjackStatus.STAYED
+def handle_blackjack_stay(state, split = False):
+ if(split and state['has_player_split'] and state['status_split'] != BlackjackStatus.LOST):
+ state['status_split'] = BlackjackStatus.STAYED
+ elif(state['status'] != BlackjackStatus.LOST):
+ state['status'] = BlackjackStatus.STAYED
return state
-def handle_blackjack_double_down(state):
+def handle_blackjack_double_down(state, split):
state['player_doubled_down'] = True
state = handle_blackjack_hit(state)
state = handle_blackjack_stay(state)
@@ -153,16 +167,27 @@ def handle_blackjack_double_down(state):
return state
-def handle_blackjack_buy_insurance(state):
+def handle_blackjack_buy_insurance(state, split):
state['player_bought_insurance'] = True
return state
+def handle_split(state, split):
+ state['has_player_split'] = True
+ state['player_split'] = [state['player'].pop()]
+
+ state = handle_blackjack_hit(state)
+ state = handle_blackjack_hit(state, True)
+
+ return state
+
def check_for_completion(state):
+ has_split = state['has_player_split']
after_initial_deal = len(
- state['player']) == 2 and len(state['dealer']) == 2
+ state['player']) == 2 and len(state['dealer']) == 2 and not has_split
player_hand_value = get_value_of_hand(state['player'])
+ player_split_hand_value = get_value_of_hand(state['player_split'])
dealer_hand_value = get_value_of_hand(state['dealer'])
# Both player and dealer were initially dealt 21: Push.
@@ -176,12 +201,22 @@ def check_for_completion(state):
return True, state
# Player went bust: Lost.
- if player_hand_value == -1:
+ if player_hand_value == -1 and state['status'] != BlackjackStatus.LOST:
state['status'] = BlackjackStatus.LOST
- return True, state
+ if(not has_split or state['status_split'] == BlackjackStatus.LOST):
+ return True, state
+
+ # Player went bust: Lost.
+ if player_split_hand_value == -1 and state['status_split'] != BlackjackStatus.LOST:
+ state['status_split'] = BlackjackStatus.LOST
+ if state['status'] == BlackjackStatus.LOST:
+ return True, state
+
+ hand_terminal_status = state['status'] in [BlackjackStatus.LOST, BlackjackStatus.STAYED]
+ hand_split_terminal_status = not has_split or state['status_split'] in [BlackjackStatus.LOST, BlackjackStatus.STAYED]
# Player chose to stay: Deal rest for dealer then determine winner.
- if state['status'] == BlackjackStatus.STAYED:
+ if hand_split_terminal_status and hand_terminal_status:
deck = build_deck(state)
while dealer_hand_value < 17 and dealer_hand_value != -1:
@@ -189,16 +224,27 @@ def check_for_completion(state):
state['dealer'].append(next_card)
dealer_hand_value = get_value_of_hand(state['dealer'])
- if player_hand_value > dealer_hand_value or dealer_hand_value == -1:
- state['status'] = BlackjackStatus.WON
- elif dealer_hand_value > player_hand_value:
- state['status'] = BlackjackStatus.LOST
- else:
- state['status'] = BlackjackStatus.PUSHED
-
+ if((not has_split) or state['status'] != BlackjackStatus.LOST):
+ if player_hand_value > dealer_hand_value or dealer_hand_value == -1:
+ state['status'] = BlackjackStatus.WON
+ elif dealer_hand_value > player_hand_value:
+ state['status'] = BlackjackStatus.LOST
+ else:
+ state['status'] = BlackjackStatus.PUSHED
+
state['player_value'] = get_value_of_hand(state['player'])
state['dealer_value'] = get_value_of_hand(state['dealer'])
+ if has_split and state['status_split'] != BlackjackStatus.LOST:
+ if player_split_hand_value > dealer_hand_value or dealer_hand_value == -1:
+ state['status_split'] = BlackjackStatus.WON
+ elif dealer_hand_value > player_split_hand_value:
+ state['status_split'] = BlackjackStatus.LOST
+ else:
+ state['status_split'] = BlackjackStatus.PUSHED
+
+ state['player_split_value'] = get_value_of_hand(state['player_split'])
+
return True, state
return False, state
@@ -209,25 +255,33 @@ def does_insurance_apply(state):
dealer_hand_value = get_value_of_hand(dealer)
dealer_first_card_ace = dealer[0][0] == 'A'
dealer_never_hit = len(dealer) == 2
- return dealer_hand_value == 21 and dealer_first_card_ace and dealer_never_hit
+ return not state['has_player_split'] and dealer_hand_value == 21 and dealer_first_card_ace and dealer_never_hit
def can_purchase_insurance(state):
dealer = state['dealer']
dealer_first_card_ace = dealer[0][0] == 'A'
dealer_never_hit = len(dealer) == 2
- return dealer_first_card_ace and dealer_never_hit and not state['player_bought_insurance']
+ return not state['has_player_split'] and dealer_first_card_ace and dealer_never_hit and not state['player_bought_insurance']
def can_double_down(state):
player = state['player']
player_hand_value = get_value_of_hand(player)
player_never_hit = len(player) == 2
- return player_hand_value in (10, 11) and player_never_hit
+ return not state['has_player_split'] and player_hand_value in (10, 11) and player_never_hit
+
+def can_split(state):
+ player = state['player']
+ player_never_hit = len(player) == 2
+ hand_can_split = player[0][0] == player[1][0]
+ player_has_split = state['has_player_split']
+ return hand_can_split and player_never_hit and not player_has_split
def handle_payout(gambler, state, game):
status = state['status']
+ split_status = state['status_split']
payout = 0
if status == BlackjackStatus.BLACKJACK:
@@ -254,12 +308,21 @@ def handle_payout(gambler, state, game):
payout = game.wager
else:
raise Exception("Attempted to payout a game that has not finished.")
+
+ if split_status == BlackjackStatus.WON:
+ game.winnings += game.wager
+ payout += game.wager * 2
+ elif split_status == BlackjackStatus.LOST:
+ game.winnings += -game.wager
+ elif status == BlackjackStatus.PUSHED:
+ payout += game.wager
+
gambler.pay_account(game.currency, payout)
- if status in {BlackjackStatus.BLACKJACK, BlackjackStatus.WON}:
+ if status in {BlackjackStatus.BLACKJACK, BlackjackStatus.WON} or split_status in {BlackjackStatus.WON}:
distribute_wager_badges(gambler, game.wager, won=True)
- elif status == BlackjackStatus.LOST:
+ elif status == BlackjackStatus.LOST or split_status == BlackjackStatus.LOST:
distribute_wager_badges(gambler, game.wager, won=False)
game.active = False
@@ -284,10 +347,11 @@ action_handlers = {
BlackjackAction.STAY: handle_blackjack_stay,
BlackjackAction.DOUBLE_DOWN: handle_blackjack_double_down,
BlackjackAction.BUY_INSURANCE: handle_blackjack_buy_insurance,
+ BlackjackAction.SPLIT: handle_split,
}
-def dispatch_action(gambler, action):
+def dispatch_action(gambler, action, is_split = False):
game = get_active_twentyone_game(gambler)
handler = action_handlers[action]
@@ -312,16 +376,24 @@ def dispatch_action(gambler, action):
charge_gambler(gambler, game.wager, game.currency)
game.wager *= 2
- new_state = handler(state)
+ if action == BlackjackAction.SPLIT:
+ if not can_split(state):
+ raise Exception("Cannot split")
+
+ charge_gambler(gambler, game.wager, game.currency)
+
+ new_state = handler(state, is_split)
new_state['player_value'] = get_value_of_hand(new_state['player'])
new_state['dealer_value'] = get_value_of_hand(new_state['dealer'])
- new_state['actions'] = get_available_actions(new_state)
+ new_state['player_split_value'] = get_value_of_hand(new_state['player_split'])
- game.game_state = json.dumps(new_state)
- g.db.add(game)
game_over, final_state = check_for_completion(new_state)
+ new_state['actions'] = get_available_actions(new_state)
+ game.game_state = json.dumps(new_state)
+ g.db.add(game)
+
if game_over:
payout = handle_payout(gambler, final_state, game)
final_state['actions'] = [BlackjackAction.DEAL]
@@ -344,6 +416,7 @@ def build_deck(state):
card_counts[card] = deck_count
cards_already_dealt = state['player'].copy()
+ cards_already_dealt.extend(state['player_split'].copy())
cards_already_dealt.extend(state['dealer'].copy())
for card in cards_already_dealt:
@@ -384,10 +457,17 @@ def get_available_actions(state):
actions.append(BlackjackAction.HIT)
actions.append(BlackjackAction.STAY)
+ if state['has_player_split'] and state['status_split'] == BlackjackStatus.PLAYING:
+ actions.append(BlackjackAction.HIT_SPLIT)
+ actions.append(BlackjackAction.STAY_SPLIT)
+
if can_double_down(state):
actions.append(BlackjackAction.DOUBLE_DOWN)
if can_purchase_insurance(state):
actions.append(BlackjackAction.BUY_INSURANCE)
+
+ if can_split(state):
+ actions.append(BlackjackAction.SPLIT)
return actions
diff --git a/files/routes/casino.py b/files/routes/casino.py
index efd0a85be..3a9249e7a 100644
--- a/files/routes/casino.py
+++ b/files/routes/casino.py
@@ -146,7 +146,8 @@ def blackjack_player_hit(v):
abort(403, "You are under Rehab award effect!")
try:
- state = dispatch_action(v, BlackjackAction.HIT)
+ hand = request.args.get('hand')
+ state = dispatch_action(v, BlackjackAction.HIT, True if hand == 'split' else False)
feed = get_game_feed('blackjack')
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "marseybux": v.marseybux}}
except:
@@ -164,7 +165,8 @@ def blackjack_player_stay(v):
abort(403, "You are under Rehab award effect!")
try:
- state = dispatch_action(v, BlackjackAction.STAY)
+ hand = request.args.get('hand')
+ state = dispatch_action(v, BlackjackAction.STAY, True if hand == 'split' else False)
feed = get_game_feed('blackjack')
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "marseybux": v.marseybux}}
except:
@@ -206,6 +208,23 @@ def blackjack_player_bought_insurance(v):
except:
abort(403, "Unable to buy insurance!")
+@app.post("/casino/twentyone/split")
+@limiter.limit('1/second', scope=rpath)
+@limiter.limit('1/second', scope=rpath, key_func=get_ID)
+@limiter.limit(CASINO_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(CASINO_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@auth_required
+def split(v):
+ if v.rehab:
+ abort(403, "You are under Rehab award effect!")
+
+ try:
+ state = dispatch_action(v, BlackjackAction.SPLIT)
+ feed = get_game_feed('blackjack')
+ return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "marseybux": v.marseybux}}
+ except:
+ abort(403, "Unable to split!")
+
# Roulette
@app.get("/casino/roulette/bets")
@limiter.limit(CASINO_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
diff --git a/files/templates/casino/blackjack_screen.html b/files/templates/casino/blackjack_screen.html
index 353ee3249..fc1a20d5b 100644
--- a/files/templates/casino/blackjack_screen.html
+++ b/files/templates/casino/blackjack_screen.html
@@ -15,6 +15,8 @@
+
@@ -23,6 +25,15 @@
+
+
+
+
+
Split
+
+
+
{% endblock %}