MarseyWorld/files/helpers/twentyone.py

384 lines
9.6 KiB
Python
Raw Normal View History

import json
import random
from enum import Enum
[DO NOT MERGE] import detanglation (#442) * move Base definition to files.classes.__init__.py * fix ImportError * move userpage listing to users.py * don't import the app from classes * consts: set default values to avoid crashes consts: warn if the secret key is the default config value * card view: sneed (user db schema) * cloudflare: use DEFAULT_CONFIG_VALUE * const: set default values * decouple media.py from __main__ * pass database to avoid imports * import cleanup and import request not in const, but in the requests mega import * move asset_submissions site check to __init__ * asset submissions feature flag * flag * g.is_tor * don't import request where it's not needed * i think this is fine * mail: move to own routes and helper * wrappers * required wrappers move * unfuck wrappers a bit * move snappy quotes and marseys to stateful consts * marsify * :pepodrool: * fix missing import * import cache * ...and settings.py * and static.py * static needs cache * route * lmao all of the jinja shit was in feeds.py amazing * classes should only import what they need from flask * import Response * hdjbjdhbhjf * ... * dfdfdfdf * make get a non-required import * isort imports (mostly) * but actually * configs * reload config on import * fgfgfgfg * config * config * initialize snappy and test * cookie of doom debug * edfjnkf * xikscdfd * debug config * set session cookie domain, i think this fixes the can't login bug * sdfbgnhvfdsghbnjfbdvvfghnn * hrsfxgf * dump the entire config on a request * kyskyskyskyskyskyskyskyskys * duifhdskfjdfd * dfdfdfdfdfdfdfdfdfdfdfdf * dfdfdfdf * imoprt all of the consts beacuse fuck it * 😭 * dfdfdfdfdfdfsdasdf * print the entire session * rffdfdfjkfksj * fgbhffh * not the secret keys * minor bug fixes * be helpful in the warning * gfgfgfg * move warning lower * isort main imports (i hope this doesn't fuck something up) * test * session cookie domain redux * dfdfdfd * try only importing Flask * formkeys fix * y * :pepodrool: * route helper * remove before flight * dfdfdfdfdf * isort classes * isort helpers * move check_for_alts to routehelpers and also sort imports and get rid of unused ones * that previous commit but actkally * readd the cache in a dozen places they were implicitly imported * use g.is_tor instead of request.headers. bla bla bla * upgrade streamers to their own route file * get rid of unused imports in __main__ * fgfgf * don't pull in the entire ORM where we don't need it * features * explicit imports for the get helper * explicit imports for the get helper redux * testing allroutes * remove unused import * decouple flask from classes * syntax fix also remember these have side fx for some reason (why?) * move side effects out of the class * posts * testing on devrama * settings * reloading * settingssdsdsds * streamer features * site settings * testing settings on devrama * import * fix modlog * remove debug stuff * revert commit 67275b21ab6e2f2520819e84d10bfc1c746a15b6 * archiveorg to _archiveorg * skhudkfkjfd * fix cron for PCM * fix bugs that snekky wants me to * Fix call to realbody passing db, standardize kwarg * test * import check_for_alts from the right place * cloudflare * testing on devrama * fix cron i think * shadow properly * tasks * Remove print which will surely be annoying in prod. * v and create new session * use files.classes * make errors import little and fix rare 500 in /allow_nsfw * Revert "use files.classes" This reverts commit 98c10b876cf86ce058b7fb955cf1ec0bfb9996c6. * pass v to media functions rather than using g * fix * dfdfdfdfd * cleanup, py type checking is dumb so don't use it where it causes issues * Fix some merge bugs, add DEFAULT_RATELIMIT to main. * Fix imports on sqlalchemy expressions. * `from random import random` is an error. * Fix replies db param. * errors: fix missing import * fix rare 500: only send to GIFT_NOTIF_ID if it exists, and send them the right text * Fix signup formkey. * fix 2 500s * propagate db to submissions * fix replies * dfdfdfdf * Fix verifiedcolor. * is_manual * can't use getters outside of an app context * don't attempt to do gumroad on sites where it's not enabled * don't attempt to do gumraod on sites's where it's unnecessary * Revert "don't attempt to do gumroad on sites where it's not enabled" This reverts commit 6f8a6331878655492dfaf1907b27f8be513c14d3. * fix 500 * validate media type Co-authored-by: TLSM <duolsm@outlook.com>
2022-11-15 09:19:08 +00:00
from math import floor
from flask import g
from files.classes.casino_game import CasinoGame
[DO NOT MERGE] import detanglation (#442) * move Base definition to files.classes.__init__.py * fix ImportError * move userpage listing to users.py * don't import the app from classes * consts: set default values to avoid crashes consts: warn if the secret key is the default config value * card view: sneed (user db schema) * cloudflare: use DEFAULT_CONFIG_VALUE * const: set default values * decouple media.py from __main__ * pass database to avoid imports * import cleanup and import request not in const, but in the requests mega import * move asset_submissions site check to __init__ * asset submissions feature flag * flag * g.is_tor * don't import request where it's not needed * i think this is fine * mail: move to own routes and helper * wrappers * required wrappers move * unfuck wrappers a bit * move snappy quotes and marseys to stateful consts * marsify * :pepodrool: * fix missing import * import cache * ...and settings.py * and static.py * static needs cache * route * lmao all of the jinja shit was in feeds.py amazing * classes should only import what they need from flask * import Response * hdjbjdhbhjf * ... * dfdfdfdf * make get a non-required import * isort imports (mostly) * but actually * configs * reload config on import * fgfgfgfg * config * config * initialize snappy and test * cookie of doom debug * edfjnkf * xikscdfd * debug config * set session cookie domain, i think this fixes the can't login bug * sdfbgnhvfdsghbnjfbdvvfghnn * hrsfxgf * dump the entire config on a request * kyskyskyskyskyskyskyskyskys * duifhdskfjdfd * dfdfdfdfdfdfdfdfdfdfdfdf * dfdfdfdf * imoprt all of the consts beacuse fuck it * 😭 * dfdfdfdfdfdfsdasdf * print the entire session * rffdfdfjkfksj * fgbhffh * not the secret keys * minor bug fixes * be helpful in the warning * gfgfgfg * move warning lower * isort main imports (i hope this doesn't fuck something up) * test * session cookie domain redux * dfdfdfd * try only importing Flask * formkeys fix * y * :pepodrool: * route helper * remove before flight * dfdfdfdfdf * isort classes * isort helpers * move check_for_alts to routehelpers and also sort imports and get rid of unused ones * that previous commit but actkally * readd the cache in a dozen places they were implicitly imported * use g.is_tor instead of request.headers. bla bla bla * upgrade streamers to their own route file * get rid of unused imports in __main__ * fgfgf * don't pull in the entire ORM where we don't need it * features * explicit imports for the get helper * explicit imports for the get helper redux * testing allroutes * remove unused import * decouple flask from classes * syntax fix also remember these have side fx for some reason (why?) * move side effects out of the class * posts * testing on devrama * settings * reloading * settingssdsdsds * streamer features * site settings * testing settings on devrama * import * fix modlog * remove debug stuff * revert commit 67275b21ab6e2f2520819e84d10bfc1c746a15b6 * archiveorg to _archiveorg * skhudkfkjfd * fix cron for PCM * fix bugs that snekky wants me to * Fix call to realbody passing db, standardize kwarg * test * import check_for_alts from the right place * cloudflare * testing on devrama * fix cron i think * shadow properly * tasks * Remove print which will surely be annoying in prod. * v and create new session * use files.classes * make errors import little and fix rare 500 in /allow_nsfw * Revert "use files.classes" This reverts commit 98c10b876cf86ce058b7fb955cf1ec0bfb9996c6. * pass v to media functions rather than using g * fix * dfdfdfdfd * cleanup, py type checking is dumb so don't use it where it causes issues * Fix some merge bugs, add DEFAULT_RATELIMIT to main. * Fix imports on sqlalchemy expressions. * `from random import random` is an error. * Fix replies db param. * errors: fix missing import * fix rare 500: only send to GIFT_NOTIF_ID if it exists, and send them the right text * Fix signup formkey. * fix 2 500s * propagate db to submissions * fix replies * dfdfdfdf * Fix verifiedcolor. * is_manual * can't use getters outside of an app context * don't attempt to do gumroad on sites where it's not enabled * don't attempt to do gumraod on sites's where it's unnecessary * Revert "don't attempt to do gumroad on sites where it's not enabled" This reverts commit 6f8a6331878655492dfaf1907b27f8be513c14d3. * fix 500 * validate media type Co-authored-by: TLSM <duolsm@outlook.com>
2022-11-15 09:19:08 +00:00
from files.helpers.casino import distribute_wager_badges
class BlackjackStatus(str, Enum):
2022-10-28 23:39:31 +00:00
PLAYING = "PLAYING"
STAYED = "STAYED"
PUSHED = "PUSHED"
WON = "WON"
LOST = "LOST"
BLACKJACK = "BLACKJACK"
class BlackjackAction(str, Enum):
2022-10-28 23:39:31 +00:00
DEAL = "DEAL"
HIT = "HIT"
STAY = "STAY"
DOUBLE_DOWN = "DOUBLE_DOWN"
BUY_INSURANCE = "BUY_INSURANCE"
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("S", "H", "C", "D")
deck = [rank + suit for rank in ranks for suit in suits]
deck_count = 4
minimum_bet = 5
def get_initial_state():
2022-10-28 23:39:31 +00:00
return {
"player": [],
"player_value": 0,
"dealer": [],
"dealer_value": 0,
"player_bought_insurance": False,
"player_doubled_down": False,
"status": BlackjackStatus.PLAYING,
2023-02-24 08:53:03 +00:00
"actions": [],
2022-10-28 23:39:31 +00:00
"wager": {
"amount": 0,
"currency": "coins"
},
"payout": 0
}
def build_casino_game(gambler, wager, currency):
2022-10-28 23:39:31 +00:00
initial_state = get_initial_state()
initial_state['wager']['amount'] = wager
initial_state['wager']['currency'] = currency
casino_game = CasinoGame()
2022-10-28 23:39:31 +00:00
casino_game.user_id = gambler.id
casino_game.currency = currency
casino_game.wager = wager
casino_game.winnings = 0
casino_game.kind = 'blackjack'
casino_game.game_state = json.dumps(initial_state)
casino_game.active = True
2023-03-16 06:27:58 +00:00
g.db.add(casino_game)
2022-10-28 23:39:31 +00:00
return casino_game
def get_active_twentyone_game(gambler):
2023-03-16 06:27:58 +00:00
return g.db.query(CasinoGame).filter(
CasinoGame.active == True,
CasinoGame.kind == 'blackjack',
CasinoGame.user_id == gambler.id).first()
def get_active_twentyone_game_state(gambler):
2022-10-28 23:39:31 +00:00
active_game = get_active_twentyone_game(gambler)
2022-10-30 00:32:40 +00:00
full_state = active_game.game_state_json
2022-10-28 23:39:31 +00:00
return remove_exploitable_information(full_state)
def charge_gambler(gambler, amount, currency):
2022-10-28 23:39:31 +00:00
charged = gambler.charge_account(currency, amount)
2023-01-01 11:36:20 +00:00
2022-10-28 23:39:31 +00:00
if not charged:
raise Exception("Gambler cannot afford charge.")
def create_new_game(gambler, wager, currency):
2022-10-28 23:39:31 +00:00
existing_game = get_active_twentyone_game(gambler)
over_minimum_bet = wager >= minimum_bet
2022-10-28 23:39:31 +00:00
if existing_game:
2023-02-24 09:01:57 +00:00
existing_game.active = False
2023-03-16 06:27:58 +00:00
g.db.add(existing_game)
2022-10-28 23:39:31 +00:00
if not over_minimum_bet:
raise Exception(f"Gambler must bet over {minimum_bet} {currency}.")
2022-10-28 23:39:31 +00:00
try:
charge_gambler(gambler, wager, currency)
new_game = build_casino_game(gambler, wager, currency)
2023-03-16 06:27:58 +00:00
g.db.add(new_game)
g.db.flush()
2022-10-28 23:39:31 +00:00
except:
raise Exception(f"Gambler cannot afford to bet {wager} {currency}.")
def handle_blackjack_deal(state):
2022-10-28 23:39:31 +00:00
deck = build_deck(state)
first = deck.pop()
second = deck.pop()
third = deck.pop()
fourth = deck.pop()
state['player'] = [first, third]
state['dealer'] = [second, fourth]
2022-10-28 23:39:31 +00:00
return state
def handle_blackjack_hit(state):
2022-10-28 23:39:31 +00:00
deck = build_deck(state)
next_card = deck.pop()
state['player'].append(next_card)
2022-10-28 23:39:31 +00:00
return state
def handle_blackjack_stay(state):
2022-10-28 23:39:31 +00:00
state['status'] = BlackjackStatus.STAYED
2022-10-28 23:39:31 +00:00
return state
def handle_blackjack_double_down(state):
2022-10-28 23:39:31 +00:00
state['player_doubled_down'] = True
state = handle_blackjack_hit(state)
state = handle_blackjack_stay(state)
2022-10-28 23:39:31 +00:00
return state
def handle_blackjack_buy_insurance(state):
2022-10-28 23:39:31 +00:00
state['player_bought_insurance'] = True
2022-10-28 23:39:31 +00:00
return state
def check_for_completion(state):
2022-10-28 23:39:31 +00:00
after_initial_deal = len(
state['player']) == 2 and len(state['dealer']) == 2
player_hand_value = get_value_of_hand(state['player'])
dealer_hand_value = get_value_of_hand(state['dealer'])
2022-10-28 23:39:31 +00:00
# Both player and dealer were initially dealt 21: Push.
if after_initial_deal and player_hand_value == 21 and dealer_hand_value == 21:
state['status'] = BlackjackStatus.PUSHED
return True, state
2022-10-28 23:39:31 +00:00
# Player was originally dealt 21, dealer was not: Blackjack.
if after_initial_deal and player_hand_value == 21:
state['status'] = BlackjackStatus.BLACKJACK
return True, state
2022-10-28 23:39:31 +00:00
# Player went bust: Lost.
if player_hand_value == -1:
state['status'] = BlackjackStatus.LOST
return True, state
2022-10-28 23:39:31 +00:00
# Player chose to stay: Deal rest for dealer then determine winner.
if state['status'] == BlackjackStatus.STAYED:
deck = build_deck(state)
2022-10-28 23:39:31 +00:00
while dealer_hand_value < 17 and dealer_hand_value != -1:
next_card = deck.pop()
state['dealer'].append(next_card)
dealer_hand_value = get_value_of_hand(state['dealer'])
2022-10-28 23:39:31 +00:00
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
2022-10-28 23:39:31 +00:00
state['player_value'] = get_value_of_hand(state['player'])
state['dealer_value'] = get_value_of_hand(state['dealer'])
2022-10-28 23:39:31 +00:00
return True, state
2022-10-28 23:39:31 +00:00
return False, state
def does_insurance_apply(state):
2022-10-28 23:39:31 +00:00
dealer = state['dealer']
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
def can_purchase_insurance(state):
2022-10-28 23:39:31 +00:00
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']
def can_double_down(state):
2022-10-28 23:39:31 +00:00
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
def handle_payout(gambler, state, game):
2022-10-28 23:39:31 +00:00
status = state['status']
payout = 0
if status == BlackjackStatus.BLACKJACK:
game.winnings = floor(game.wager * 3/2)
payout = game.wager + game.winnings
elif status == BlackjackStatus.WON:
game.winnings = game.wager
payout = game.wager * 2
elif status == BlackjackStatus.LOST:
dealer = state['dealer']
dealer_first_card_ace = dealer[0][0] == 'A'
dealer_never_hit = len(dealer) == 2
dealer_hand_value = get_value_of_hand(dealer) == 21
insurance_applies = dealer_hand_value == 21 and dealer_first_card_ace and dealer_never_hit
if insurance_applies and state['player_bought_insurance']:
game.winnings = 0
payout = game.wager
else:
game.winnings = -game.wager
payout = 0
elif status == BlackjackStatus.PUSHED:
game.winnings = 0
payout = game.wager
else:
raise Exception("Attempted to payout a game that has not finished.")
gambler.pay_account(game.currency, payout)
2023-01-01 11:36:20 +00:00
2022-10-28 23:39:31 +00:00
if game.currency == 'coins':
2022-12-23 22:22:41 +00:00
if status in {BlackjackStatus.BLACKJACK, BlackjackStatus.WON}:
2022-10-28 23:39:31 +00:00
distribute_wager_badges(gambler, game.wager, won=True)
elif status == BlackjackStatus.LOST:
distribute_wager_badges(gambler, game.wager, won=False)
game.active = False
2023-03-16 06:27:58 +00:00
g.db.add(game)
2022-10-28 23:39:31 +00:00
return payout
def remove_exploitable_information(state):
2022-10-28 23:39:31 +00:00
safe_state = state
2023-01-01 11:36:20 +00:00
2022-10-28 23:39:31 +00:00
if len(safe_state['dealer']) >= 2:
safe_state['dealer'][1] = '?'
2022-09-16 18:51:58 +00:00
2022-10-28 23:39:31 +00:00
safe_state['dealer_value'] = '?'
return safe_state
action_handlers = {
2022-10-28 23:39:31 +00:00
BlackjackAction.DEAL: handle_blackjack_deal,
BlackjackAction.HIT: handle_blackjack_hit,
BlackjackAction.STAY: handle_blackjack_stay,
BlackjackAction.DOUBLE_DOWN: handle_blackjack_double_down,
BlackjackAction.BUY_INSURANCE: handle_blackjack_buy_insurance,
}
def dispatch_action(gambler, action):
2022-10-28 23:39:31 +00:00
game = get_active_twentyone_game(gambler)
handler = action_handlers[action]
2022-10-28 23:39:31 +00:00
if not game:
raise Exception(
'Gambler has no active blackjack game.')
if not handler:
raise Exception(
f'Illegal action {action} passed to Blackjack#dispatch_action.')
2022-10-30 00:32:40 +00:00
state = game.game_state_json
2022-10-28 23:39:31 +00:00
if action == BlackjackAction.BUY_INSURANCE:
if not can_purchase_insurance(state):
raise Exception("Insurance cannot be purchased.")
2022-10-28 23:39:31 +00:00
charge_gambler(gambler, floor(game.wager / 2), game.currency)
if action == BlackjackAction.DOUBLE_DOWN:
if not can_double_down(state):
raise Exception("Cannot double down.")
2022-10-28 23:39:31 +00:00
charge_gambler(gambler, game.wager, game.currency)
game.wager *= 2
2022-10-28 23:39:31 +00:00
new_state = handler(state)
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)
2022-10-28 23:39:31 +00:00
game.game_state = json.dumps(new_state)
2023-03-16 06:27:58 +00:00
g.db.add(game)
2022-10-28 23:39:31 +00:00
game_over, final_state = check_for_completion(new_state)
2022-10-28 23:39:31 +00:00
if game_over:
payout = handle_payout(gambler, final_state, game)
final_state['actions'] = [BlackjackAction.DEAL]
final_state['payout'] = payout
return final_state
else:
safe_state = remove_exploitable_information(new_state)
return safe_state
def shuffle(collection):
2022-10-28 23:39:31 +00:00
random.shuffle(collection)
return collection
def build_deck(state):
2022-10-28 23:39:31 +00:00
card_counts = {}
2022-10-28 23:39:31 +00:00
for card in deck:
card_counts[card] = deck_count
2022-10-28 23:39:31 +00:00
cards_already_dealt = state['player'].copy()
cards_already_dealt.extend(state['dealer'].copy())
2022-10-28 23:39:31 +00:00
for card in cards_already_dealt:
card_counts[card] = card_counts[card] - 1
2022-10-28 23:39:31 +00:00
deck_without_already_dealt_cards = []
2022-10-28 23:39:31 +00:00
for card in deck:
amount = card_counts[card]
2022-10-28 23:39:31 +00:00
for _ in range(amount):
deck_without_already_dealt_cards.append(card)
2022-10-28 23:39:31 +00:00
return shuffle(deck_without_already_dealt_cards)
def get_value_of_card(card):
2022-10-28 23:39:31 +00:00
rank = card[0]
return 0 if rank == "A" else min(ranks.index(rank) + 2, 10)
def get_value_of_hand(hand):
2022-10-28 23:39:31 +00:00
without_aces = sum(map(get_value_of_card, hand))
ace_count = sum("A" in c for c in hand)
possibilities = []
2022-10-28 23:39:31 +00:00
for i in range(ace_count + 1):
value = without_aces + (ace_count - i) + i * 11
possibilities.append(-1 if value > 21 else value)
2022-10-28 23:39:31 +00:00
return max(possibilities)
def get_available_actions(state):
2022-10-28 23:39:31 +00:00
actions = []
2022-10-28 23:39:31 +00:00
if state['status'] == BlackjackStatus.PLAYING:
actions.append(BlackjackAction.HIT)
actions.append(BlackjackAction.STAY)
2022-10-28 23:39:31 +00:00
if can_double_down(state):
actions.append(BlackjackAction.DOUBLE_DOWN)
2022-10-28 23:39:31 +00:00
if can_purchase_insurance(state):
actions.append(BlackjackAction.BUY_INSURANCE)
2022-10-28 23:39:31 +00:00
return actions