diff --git a/files/assets/css/casino.css b/files/assets/css/casino.css new file mode 100644 index 000000000..a92e6e5e5 --- /dev/null +++ b/files/assets/css/casino.css @@ -0,0 +1,137 @@ +.casino-games { + display: flex; + align-items: flex-start; + justify-content: space-between; +} + +.casino-game { + flex: 1; +} + +/* Slots */ +#slots-block { + max-width: 700px; +} + +.casino-slots-results { + display: flex; + align-items: center; + justify-content: center; +} + +.casino-slots-results .reel { + display: flex; + align-items: center; + justify-content: center; + width: 100px; + height: 100px; + border: 2px solid black; + background-color: var(--gray); + border: 1px solid var(--black); + border-radius: 8px; + font-size: 64px; +} + +.casino-slots-outcome { + visibility: hidden; + text-align: right; + margin: 1rem 0; +} + +/* Blackjack */ +#blackjack-block { + max-width: 800px; +} + +@media screen and (max-width: 600px) { + .blackjack-table .hand .playing-card { + width: 60px; + height: 90px; + } +} + +.casino-blackjack-outcome { + visibility: hidden; + text-align: right; + margin-bottom: 1rem; +} + +.blackjack-table { + max-width: 500px; +} + +.blackjack-table .hand { + display: flex; + align-items: center; + justify-content: space-evenly; +} + +.blackjack-table .hand .playing-card { + display: flex; + align-items: center; + justify-content: center; + width: 60px; + height: 90px; + border: 2px solid black; + background-color: var(--gray); + border: 1px solid var(--black); + border-radius: 8px; + font-size: 30px; +} + +.blackjack-table .hand .playing-card.dealt { + background-color: #ddd; +} + +.casino-blackjack-actions { + text-align: right; +} + +/* Lottery */ +.casino-lottery { + margin-top: 5rem; +} + +/* Blocks */ +.casino-block { + padding: 1rem; + border: 4px solid #361506; + border-radius: 8px; + background-color: #092711; + margin: 2rem 0; +} + +.casino-block-title { + display: flex; + align-items: center; + font-size: 32px; + text-transform: uppercase; + letter-spacing: 4px; + opacity: 0.5; +} + +.casino-block-inner { + display: flex; + align-items: center; + justify-content: space-between; +} + +.casino-block-left { + display: flex; + align-items: flex-end; + justify-content: center; + flex: 1; +} + +@media screen and (max-width: 600px) { + .casino-block-left { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } +} + +.casino-block-game { + margin-right: 2rem; +} diff --git a/files/assets/css/main.css b/files/assets/css/main.css index 2f63b44cc..351283f70 100644 --- a/files/assets/css/main.css +++ b/files/assets/css/main.css @@ -5383,7 +5383,6 @@ audio, video { display: flex; align-items: center; justify-content: center; - padding: 2rem; } .lottery-page--wrapper > div { @@ -5987,6 +5986,7 @@ g { .fa-snowflake:before{content:"\f2dc"} .fa-sparkles:before{content:"\f890"} .fa-ticket:before{content:"\f145"} +.fa-usd:before{content:"\f155"} .fa-spider:before{content:"\f717"} .fa-square:before{content:"\f0c8"} .fa-stocking:before{content:"\f7d5"} diff --git a/files/assets/js/casino.js b/files/assets/js/casino.js new file mode 100644 index 000000000..8073634a4 --- /dev/null +++ b/files/assets/js/casino.js @@ -0,0 +1,328 @@ +// Slots +function pullSlots() { + const wager = document.getElementById("casinoSlotsBet").value; + const currency = document.querySelector( + 'input[name="casinoSlotsCurrency"]:checked' + ).value; + + document.getElementById("casinoSlotsBet").disabled = true; + document.getElementById("casinoSlotsPull").disabled = true; + + const xhr = new XMLHttpRequest(); + xhr.open("post", "/casino/slots"); + xhr.onload = handleSlotsResponse.bind(null, xhr); + + const form = new FormData(); + form.append("formkey", formkey()); + form.append("wager", wager); + form.append("currency", currency); + + xhr.send(form); +} + +function handleSlotsResponse(xhr) { + let response; + + try { + response = JSON.parse(xhr.response); + } catch (error) { + console.error(error); + } + + const succeeded = + xhr.status >= 200 && xhr.status < 300 && response && !response.error; + const slotsResult = document.getElementById("casinoSlotsResult"); + slotsResult.classList.remove("text-success", "text-danger"); + + if (succeeded) { + const { game_state } = response; + const state = JSON.parse(game_state); + const reels = Array.from(document.querySelectorAll(".reel")); + const symbols = state.symbols.split(","); + + for (let i = 0; i < 3; i++) { + reels[i].innerHTML = symbols[i]; + } + + slotsResult.style.visibility = "visible"; + slotsResult.innerText = state.text; + + if (state.text.includes("Won")) { + if (state.text.includes("Jackpot")) { + slotsResult.classList.add("text-warning"); + } else { + slotsResult.classList.add("text-success"); + } + } else if (state.text.includes("Lost")) { + slotsResult.classList.add("text-danger"); + } + } else { + slotsResult.style.visibility = "visible"; + slotsResult.innerText = response.error; + slotsResult.classList.add("text-danger"); + + console.error(response.error); + } + + document.getElementById("casinoSlotsBet").disabled = false; + document.getElementById("casinoSlotsPull").disabled = false; +} + +// Blackjack +// When the casino loads, look up the "blackjack status" of a player to either start a new game or continue an existing game. +if ( + document.readyState === "complete" || + (document.readyState !== "loading" && !document.documentElement.doScroll) +) { + checkBlackjackStatus(); +} else { + document.addEventListener("DOMContentLoaded", checkBlackjackStatus); +} + +function checkBlackjackStatus() { + const xhr = new XMLHttpRequest(); + xhr.open("get", "/casino/blackjack"); + xhr.onload = handleBlackjackStatusResponse.bind(null, xhr); + xhr.send(); +} + +function handleBlackjackStatusResponse(xhr) { + let response; + + try { + response = JSON.parse(xhr.response); + } catch (error) { + console.error(error); + } + + const succeeded = + xhr.status >= 200 && xhr.status < 300 && response && !response.error; + + if (succeeded) { + if (response.active) { + updateBlackjack(response.game_state); + } + } else { + console.error("error"); + } +} + +// When starting a new game or taking an action in an existing one, a new state will be returned, and the DOM must be updated. +function updateBlackjack(state) { + const { player, dealer, status } = state; + const lettersToSuits = { + S: "♠️", + H: "♥️", + C: "♣️", + D: "♦️", + "?": "?", + }; + const suitsToColors = { + "♠️": "black", + "♥️": "red", + "♣️": "black", + "♦️": "red", + "?": "black", + }; + + // Clear everything. + Array.from(document.querySelectorAll(".playing-card")).forEach((card) => { + card.innerText = ""; + card.style.color = "unset"; + card.classList.remove("dealt"); + }); + + // Show dealer cards. + const dealerSlots = Array.from( + document.querySelectorAll('.playing-card[data-who="dealer"]') + ); + for (let i = 0; i < dealer.length; i++) { + const slot = dealerSlots[i]; + + if (slot) { + // Technically, the dealer can use more than 5 cards, though it's rare. + // In that case, the result message is good enough. + // Thanks, Carp. 🐠 + slot.classList.add("dealt"); + + if (i > 0 && status === "active") { + break; + } + + const rank = dealer[i][0]; + const suit = lettersToSuits[dealer[i][1]]; + const card = rank + suit; + slot.innerText = card; + slot.style.color = suitsToColors[suit]; + } + } + + // Show player cards. + const playerSlots = Array.from( + document.querySelectorAll('.playing-card[data-who="player"]') + ); + for (let i = 0; i < player.length; i++) { + const slot = playerSlots[i]; + const rank = player[i][0]; + const suit = lettersToSuits[player[i][1]]; + const card = rank + suit; + slot.innerText = card; + slot.style.color = suitsToColors[suit]; + slot.classList.add("dealt"); + } + + updateBlackjackActions(state); + + if (status !== "active") { + revealBlackjackResult(state); + } +} + +function revealBlackjackResult(state) { + const blackjackResult = document.getElementById("casinoBlackjackResult"); + const lookup = { + bust: ["Bust. Didn't work out for you, did it?", "danger"], + push: ["Pushed. This whole hand never happened.", "secondary"], + insured_loss: ["Lost, but at least you had insurance.", "secondary"], + lost: ["Lost. That was pathetic.", "danger"], + won: ["Won. This time.", "success"], + blackjack: ["Blackjack! Must be your lucky day.", "warning"], + }; + const [resultText, resultClass] = lookup[state.status]; + + blackjackResult.style.visibility = "visible"; + blackjackResult.innerText = resultText; + blackjackResult.classList.add(`text-${resultClass}`); +} + +function buildBlackjackAction(id, method, title, fullWidth = false) { + return ` + + `; +} + +function clearBlackjackActions() { + const actionWrapper = document.getElementById("casinoBlackjackActions"); + actionWrapper.innerHTML = ""; +} + +function updateBlackjackActions(state) { + const actionWrapper = document.getElementById("casinoBlackjackActions"); + + clearBlackjackActions(); + + if (state.status === "active") { + document.getElementById("casinoBlackjackWager").style.display = "none"; + + const actionLookup = { + hit: buildBlackjackAction("casinoBlackjackHit", "hitBlackjack", "Hit"), + stay: buildBlackjackAction( + "casinoBlackjackStay", + "stayBlackjack", + "Stay" + ), + double_down: buildBlackjackAction( + "casinoBlackjackDouble", + "doubleBlackjack", + "Double Down" + ), + insure: buildBlackjackAction( + "casinoBlackjackInsure", + "insureBlackjack", + "Insure" + ), + }; + const actions = state.actions.map((action) => actionLookup[action]); + + actionWrapper.innerHTML = actions.join("\n"); + } else { + // Game is over, deal a new game. + document.getElementById("casinoBlackjackWager").style.display = "flex"; + + const deal = buildBlackjackAction( + "casinoBlackjackDeal", + "dealBlackjack", + "Deal", + true + ); + + actionWrapper.innerHTML = deal; + } +} + +function dealBlackjack() { + const wager = document.getElementById("casinoBlackjackBet").value; + const currency = document.querySelector( + 'input[name="casinoBlackjackCurrency"]:checked' + ).value; + + document.getElementById("casinoBlackjackBet").disabled = true; + document.getElementById("casinoBlackjackDeal").disabled = true; + document.getElementById("casinoBlackjackWager").style.display = "none"; + document.getElementById("casinoBlackjackResult").style.visibility = "hidden"; + + const xhr = new XMLHttpRequest(); + xhr.open("post", "/casino/blackjack"); + xhr.onload = handleBlackjackResponse.bind(null, xhr); + + const form = new FormData(); + form.append("formkey", formkey()); + form.append("wager", wager); + form.append("currency", currency); + + xhr.send(form); +} + +function takeBlackjackAction(action) { + const xhr = new XMLHttpRequest(); + xhr.open("post", "/casino/blackjack/action"); + xhr.onload = handleBlackjackResponse.bind(null, xhr); + + const form = new FormData(); + form.append("formkey", formkey()); + form.append("action", action); + + xhr.send(form); +} + +const hitBlackjack = takeBlackjackAction.bind(null, "hit"); +const stayBlackjack = takeBlackjackAction.bind(null, "stay"); +const doubleBlackjack = takeBlackjackAction.bind(null, "double_down"); +const insureBlackjack = takeBlackjackAction.bind(null, "insure"); + +function handleBlackjackResponse(xhr) { + let response; + + try { + response = JSON.parse(xhr.response); + } catch (error) { + console.error(error); + } + + const succeeded = + xhr.status >= 200 && xhr.status < 300 && response && !response.error; + const blackjackResult = document.getElementById("casinoBlackjackResult"); + blackjackResult.classList.remove("text-success", "text-danger"); + + if (succeeded) { + if (response.game_state) { + updateBlackjack(response.game_state); + } + } else { + blackjackResult.style.visibility = "visible"; + blackjackResult.innerText = response.error; + blackjackResult.classList.add("text-danger"); + + console.error(response.error); + } +} diff --git a/files/classes/__init__.py b/files/classes/__init__.py index d50635b30..9badd9b4b 100644 --- a/files/classes/__init__.py +++ b/files/classes/__init__.py @@ -23,4 +23,5 @@ from .views import * from .notifications import * from .follows import * from .lottery import * -from .hats import * \ No newline at end of file +from .casino_game import * +from .hats import * diff --git a/files/classes/casino_game.py b/files/classes/casino_game.py new file mode 100644 index 000000000..3da8e9312 --- /dev/null +++ b/files/classes/casino_game.py @@ -0,0 +1,27 @@ +from sqlalchemy import * +from files.__main__ import Base +from files.helpers.lazy import lazy +from files.helpers.const import * +import time + + +class Casino_Game(Base): + __tablename__ = "casino_games" + + id = Column(Integer, primary_key=True) + user_id = Column(Integer, ForeignKey("users.id")) + created_utc = Column(Integer) + active = Column(Boolean, default=True) + currency = Column(String) + wager = Column(Integer) + winnings = Column(Integer) + kind = Column(String) + game_state = Column(JSON) + + def __init__(self, *args, **kwargs): + if "created_utc" not in kwargs: + kwargs["created_utc"] = int(time.time()) + super().__init__(*args, **kwargs) + + def __repr__(self): + return f"" diff --git a/files/classes/comment.py b/files/classes/comment.py index 05fe80d1e..9eff18aec 100644 --- a/files/classes/comment.py +++ b/files/classes/comment.py @@ -60,8 +60,6 @@ class Comment(Base): body = Column(String) body_html = Column(String) ban_reason = Column(String) - slots_result = Column(String) - blackjack_result = Column(String) wordle_result = Column(String) treasure_amount = Column(String) @@ -412,7 +410,7 @@ class Comment(Base): if self.is_banned: return True - if (self.slots_result or self.blackjack_result or self.wordle_result) and (not self.body or len(self.body_html) <= 100) and 9 > self.level > 1: return True + if (self.wordle_result) and (not self.body or len(self.body_html) <= 100) and 9 > self.level > 1: return True if v and v.filter_words and self.body and any(x in self.body for x in v.filter_words): return True @@ -448,73 +446,5 @@ class Comment(Base): elif wordle_status == 'lost': body += f"Lost. The answer was: {wordle_answer}" - body += '' - return body - - @lazy - def blackjack_html(self, v): - if not self.blackjack_result: return '' - - split_result = self.blackjack_result.split('_') - blackjack_status = split_result[3] - player_hand = split_result[0].replace('X', '10') - dealer_hand = split_result[1].split('/')[0] if blackjack_status == 'active' else split_result[1] - dealer_hand = dealer_hand.replace('X', '10') - wager = int(split_result[4]) - try: kind = split_result[5] - except: kind = "coins" - currency_kind = "Coins" if kind == "coins" else "Marseybux" - - try: is_insured = split_result[6] - except: is_insured = "0" - - body = f"{player_hand} vs. {dealer_hand}" - - if blackjack_status == 'active' and v and v.id == self.author_id: - body += f''' - - - - ''' - - if dealer_hand[0][0] == 'A' and not is_insured == "1": - body += f''' - - ''' - - elif blackjack_status == 'push': - body += f"Pushed. Refunded {wager} {currency_kind}." - elif blackjack_status == 'bust': - body += f"Bust. Lost {wager} {currency_kind}." - elif blackjack_status == 'lost': - body += f"Lost {wager} {currency_kind}." - elif blackjack_status == 'won': - body += f"Won {wager} {currency_kind}." - elif blackjack_status == 'blackjack': - body += f"Blackjack! Won {floor(wager * 3/2)} {currency_kind}." - - if is_insured == "1": - body += f" Insured." - body += '' return body \ No newline at end of file diff --git a/files/helpers/actions.py b/files/helpers/actions.py index 9f0b2ea4a..f5065156e 100644 --- a/files/helpers/actions.py +++ b/files/helpers/actions.py @@ -167,9 +167,6 @@ def execute_snappy(post, v): snappy.comment_count += 1 snappy.coins += 1 g.db.add(snappy) - - if body.startswith('!slots'): - check_for_slots_command(body, snappy, c) if FEATURES['PINS'] and (body.startswith(':#marseypin:') or body.startswith(':#marseypin2:')): post.stickied = "Snappy" diff --git a/files/helpers/blackjack.py b/files/helpers/blackjack.py index 52f861245..b570ad584 100644 --- a/files/helpers/blackjack.py +++ b/files/helpers/blackjack.py @@ -1,188 +1,297 @@ +import json from json.encoder import INFINITY import random from math import floor from files.helpers.const import * +from files.classes.casino_game import Casino_Game +from flask import g deck_count = 4 ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A") -suits = ("♠️", "♥️", "♣️", "♦️") -coins_command_word = "!blackjack" -marseybux_command_word = "!blackjackmb" +suits = ("S", "H", "C", "D") minimum_bet = 100 maximum_bet = INFINITY + +def build_game(gambler, currency_kind, wager): + casino_game = Casino_Game() + casino_game.user_id = gambler.id + casino_game.currency = currency_kind + casino_game.wager = wager + casino_game.winnings = 0 + casino_game.kind = 'blackjack' + casino_game.game_state = json.dumps(build_initial_state()) + g.db.add(casino_game) + g.db.flush() + + +def build_initial_state(): + player, dealer, deck = deal_initial_cards() + state = { + "player": player, + "dealer": dealer, + "deck": deck, + "actions": [], + "insurance": False, + "doubled_down": False, + "status": "active" + } + + state['actions'] = determine_actions(state) + + return state + + +def save_game_state(game, new_state): + game.game_state = json.dumps(new_state) + g.db.add(game) + + +def get_active_game(gambler): + game = g.db.query(Casino_Game) \ + .filter(Casino_Game.active == True and + Casino_Game.kind == 'blackjack' and + Casino_Game.user_id == gambler.id).first() + + if game: + return game, json.loads(game.game_state) + else: + return None, None + + +def get_safe_game_state(gambler): + game, game_state = get_active_game(gambler) + + if game: + return { + "player": game_state['player'], + "dealer": [game_state['dealer'][0], "?"], + "actions": game_state['actions'], + "insurance": game_state['insurance'], + "doubled_down": game_state['doubled_down'], + "status": game_state['status'] + } + else: + return None + + +def apply_blackjack_result(gambler): + game, game_state = get_active_game(gambler) + + if game and game.active: + result = game_state['status'] + + if result == 'push' or result == 'insured_loss': + reward = 0 + elif result == 'won': + reward = game.wager + elif result == 'blackjack': + reward = floor(game.wager * 3/2) + else: + reward = -game.wager + + gambler.winnings += reward + + if (reward > -1): + currency_value = int(getattr(gambler, game.currency, 0)) + setattr(gambler, game.currency, + currency_value + game.wager + reward) + + game.active = False + game.winnings = reward + g.db.add(game) + + +def deal_blackjack_game(gambler, wager_value, currency): + over_min = wager_value >= minimum_bet + under_max = wager_value <= maximum_bet + using_dramacoin = currency == "dramacoin" + using_marseybux = not using_dramacoin + has_proper_funds = (using_dramacoin and gambler.coins >= wager_value) or ( + using_marseybux and gambler.procoins >= wager_value) + currency_prop = "coins" if using_dramacoin else "procoins" + currency_value = getattr(gambler, currency_prop, 0) + + if (over_min and under_max and has_proper_funds): + # Charge the gambler for the game, reduce their winnings, and start the game. + setattr(gambler, currency_prop, currency_value - wager_value) + gambler.winnings -= wager_value + + build_game(gambler, currency_prop, wager_value) + + game, game_state = get_active_game(gambler) + player_value = get_hand_value(game_state['player']) + dealer_value = get_hand_value(game_state['dealer']) + + if player_value == 21 and dealer_value == 21: + game_state["status"] = 'push' + save_game_state(game, game_state) + apply_blackjack_result(gambler) + elif player_value == 21: + game_state["status"] = 'blackjack' + save_game_state(game, game_state) + apply_blackjack_result(gambler) + + g.db.flush() + + return True + else: + return False + +# region Actions + + +def gambler_hit(gambler): + game, game_state = get_active_game(gambler.id) + + if game: + player = game_state['player'] + deck = game_state['deck'] + doubled_down = game_state['doubled_down'] + player.append(deck.pop(0)) + player_value = get_hand_value(player) + went_bust = player_value == -1 + five_card_charlied = len(player) >= 5 + + if went_bust: + game_state['status'] = 'bust' + save_game_state(game, game_state) + apply_blackjack_result(gambler) + elif five_card_charlied: + game_state['status'] = 'won' + save_game_state(game, game_state) + apply_blackjack_result(gambler) + else: + save_game_state(game, game_state) + + if doubled_down or player_value == 21: + forced_stay_success, forced_stay_state = gambler_stayed(gambler) + return forced_stay_success, forced_stay_state + else: + return True, game_state + else: + return False, game_state + + +def gambler_stayed(gambler): + game, game_state = get_active_game(gambler.id) + + if game: + player = game_state['player'] + dealer = game_state['dealer'] + deck = game_state['deck'] + insured = game_state['insurance'] + + player_value = get_hand_value(player) + dealer_value = get_hand_value(dealer) + + if dealer_value == 21 and insured: + game_state["status"] = 'insured_loss' + save_game_state(game, game_state) + apply_blackjack_result(gambler) + else: + while dealer_value < 17 and dealer_value != -1: + next = deck.pop(0) + dealer.append(next) + dealer_value = get_hand_value(dealer) + + if player_value > dealer_value or dealer_value == -1: + game_state["status"] = 'won' + elif dealer_value > player_value: + game_state["status"] = 'lost' + else: + game_state["status"] = 'push' + + save_game_state(game, game_state) + apply_blackjack_result(gambler) + + return True, game_state + else: + return False, game_state + + +def gambler_doubled_down(gambler): + game, game_state = get_active_game(gambler.id) + + if game and not game_state['doubled_down']: + currency_value = getattr(gambler, game.currency, 0) + + if (currency_value < game.wager): + return False + + setattr(gambler, game.currency, currency_value - game.wager) + game.wager *= 2 + game_state['doubled_down'] = True + save_game_state(game, game_state) + + g.db.flush() + + last_hit_success, last_hit_state = gambler_hit(gambler) + + return last_hit_success, last_hit_state + else: + return False, game_state + + +def gambler_purchased_insurance(gambler): + game, game_state = get_active_game(gambler.id) + + if game and not game_state['insurance']: + insurance_cost = game.wager / 2 + currency_value = getattr(gambler, game.currency, 0) + + if (currency_value < insurance_cost): + return False, game_state + + setattr(gambler, game.currency, currency_value - insurance_cost) + game_state['insurance'] = True + game_state['actions'] = determine_actions(game_state) + save_game_state(game, game_state) + + return True, game_state + else: + return False, game_state + +# endregion + +# region Utilities + + def shuffle(x): - random.shuffle(x) - return x + random.shuffle(x) + return x + + +def determine_actions(state): + actions = ['hit', 'stay', 'double_down'] + + if (state['dealer'][0][0] == "A" and not state['insurance']): + actions.append('insure') + + return actions def deal_initial_cards(): - deck = shuffle([rank + suit for rank in ranks for suit in suits for _ in range(deck_count)]) - p1, d1, p2, d2, *rest_of_deck = deck - return [p1, p2], [d1, d2], rest_of_deck + deck = shuffle( + [rank + suit for rank in ranks for suit in suits for _ in range(deck_count)]) + p1, d1, p2, d2, *rest_of_deck = deck + return [p1, p2], [d1, d2], rest_of_deck def get_card_value(card): - rank = card[0] - return 0 if rank == "A" else min(ranks.index(rank) + 2, 10) + rank = card[0] + return 0 if rank == "A" else min(ranks.index(rank) + 2, 10) def get_hand_value(hand): - without_aces = sum(map(get_card_value, hand)) - ace_count = sum("A" in c for c in hand) - possibilities = [] + without_aces = sum(map(get_card_value, hand)) + ace_count = sum("A" in c for c in hand) + possibilities = [] - for i in range(ace_count + 1): - value = without_aces + (ace_count - i) + i * 11 - possibilities.append(-1 if value > 21 else value) + for i in range(ace_count + 1): + value = without_aces + (ace_count - i) + i * 11 + possibilities.append(-1 if value > 21 else value) - return max(possibilities) + return max(possibilities) - -def format_cards(hand): - return map(lambda x: "".join(x), hand) - - -def format_all(player_hand, dealer_hand, deck, status, wager, kind, is_insured=0): - formatted_player_hand = format_cards(player_hand) - formatted_dealer_hand = format_cards(dealer_hand) - formatted_deck = format_cards(deck) - - return f'{"/".join(formatted_player_hand)}_{"/".join(formatted_dealer_hand)}_{"/".join(formatted_deck)}_{status}_{wager}_{kind}_{str(is_insured)}' - - -def check_for_blackjack_commands(in_text, from_user, from_comment): - if not FEATURES['GAMBLING']: return - - for command_word in (coins_command_word, marseybux_command_word): - currency_prop = "coins" if command_word == coins_command_word else "procoins" - currency_value = getattr(from_user, currency_prop, 0) - - if command_word in in_text: - for word in in_text.split(): - if command_word in word: - try: - wager = word[len(command_word):] - wager_value = int(wager) - except: break - - if (wager_value < minimum_bet): break - elif (wager_value > maximum_bet): break - elif (wager_value <= currency_value): - setattr(from_user, currency_prop, currency_value - wager_value) - - player_hand, dealer_hand, rest_of_deck = deal_initial_cards() - status = 'active' - player_value = get_hand_value(player_hand) - dealer_value = get_hand_value(dealer_hand) - - if player_value == 21 and dealer_value == 21: - status = 'push' - apply_game_result(from_comment, wager, status, currency_prop) - elif player_value == 21: - status = 'blackjack' - apply_game_result(from_comment, wager, status, currency_prop) - - from_comment.blackjack_result = format_all(player_hand, dealer_hand, rest_of_deck, status, wager, currency_prop, 0) - -def player_hit(from_comment, did_double_down=False): - player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_") - player_hand = player_hand.split("/") - dealer_hand = dealer_hand.split("/") - deck = deck.split("/") - player_hand.append(deck.pop(0)) - player_value = get_hand_value(player_hand) - - if player_value == -1: - status = 'bust' - apply_game_result(from_comment, wager, status, kind) - elif len(player_hand) >= 5: - status = 'won' - apply_game_result(from_comment, wager, status, kind) - - from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, wager, kind, int(is_insured)) - - if (did_double_down or player_value == 21) and status == 'active': - player_stayed(from_comment) - -def player_stayed(from_comment): - player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_") - player_hand = player_hand.split("/") - player_value = get_hand_value(player_hand) - dealer_hand = dealer_hand.split("/") - dealer_value = get_hand_value(dealer_hand) - deck = deck.split("/") - - if dealer_value == 21 and is_insured == "1": - currency_value = getattr(from_comment.author, kind, 0) - setattr(from_comment.author, kind, currency_value + int(wager)) - else: - while dealer_value < 17 and dealer_value != -1: - next = deck.pop(0) - dealer_hand.append(next) - dealer_value = get_hand_value(dealer_hand) - - if player_value > dealer_value or dealer_value == -1: status = 'won' - elif dealer_value > player_value: status = 'lost' - else: status = 'push' - - from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, wager, kind, int(is_insured)) - - apply_game_result(from_comment, wager, status, kind) - -def player_doubled_down(from_comment): - # When doubling down, the player receives one additional card (a "hit") and their initial bet is doubled. - player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_") - wager_value = int(wager) - currency_value = getattr(from_comment.author, kind, 0) - - # Gotsta have enough coins - if (currency_value < wager_value): return - - # Double the initial wager - setattr(from_comment.author, kind, currency_value - wager_value) - wager_value *= 2 - - # Apply the changes to the stored hand. - player_hand = player_hand.split("/") - dealer_hand = dealer_hand.split("/") - deck = deck.split("/") - from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, str(wager_value), kind, int(is_insured)) - - player_hit(from_comment, True) - -def player_bought_insurance(from_comment): - # When buying insurance, the player pays a side bet equal to 1/2 the original bet. - # In the event the dealer actually had a blackjack, they receive a 2:1 payout limiting the negative effect. - player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_") - wager_value = int(wager) - insurance_cost = wager_value / 2 - currency_value = getattr(from_comment.author, kind, 0) - - # Gotsta have enough coins - if (currency_value < insurance_cost): return - - # Charge for (and grant) insurance - setattr(from_comment.author, kind, currency_value - insurance_cost) - is_insured = 1 - - # Apply the changes to the stored hand. - player_hand = player_hand.split("/") - dealer_hand = dealer_hand.split("/") - deck = deck.split("/") - from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, str(wager_value), kind, int(is_insured)) - -def apply_game_result(from_comment, wager, result, kind): - wager_value = int(wager) - user = from_comment.author - - if result == 'push': reward = 0 - elif result == 'won': reward = wager_value - elif result == 'blackjack': reward = floor(wager_value * 3/2) - else: reward = -wager_value - - user.winnings += reward - - if (reward > -1): - currency_value = int(getattr(user, kind, 0)) - setattr(user, kind, currency_value + wager_value + reward) \ No newline at end of file +# endregion diff --git a/files/helpers/const.py b/files/helpers/const.py index a4339ecf4..8c7967bf6 100644 --- a/files/helpers/const.py +++ b/files/helpers/const.py @@ -206,7 +206,7 @@ POLL_THREAD = 0 WELCOME_MSG = f"Welcome to {SITE_NAME}!" ROLES={} -LOTTERY_ENABLED = True +CASINO_ENABLED = True LOTTERY_TICKET_COST = 12 LOTTERY_SINK_RATE = 3 LOTTERY_DURATION = 60 * 60 * 24 * 7 diff --git a/files/helpers/jinja2.py b/files/helpers/jinja2.py index f599959af..748b55a4f 100644 --- a/files/helpers/jinja2.py +++ b/files/helpers/jinja2.py @@ -55,7 +55,7 @@ def inject_constants(): "PIZZASHILL_ID":PIZZASHILL_ID, "DEFAULT_COLOR":DEFAULT_COLOR, "COLORS":COLORS, "time":time, "PERMS":PERMS, "FEATURES":FEATURES, "HOLE_NAME":HOLE_NAME, "HOLE_STYLE_FLAIR":HOLE_STYLE_FLAIR, "HOLE_REQUIRED":HOLE_REQUIRED, - "LOTTERY_ENABLED":LOTTERY_ENABLED, "GUMROAD_LINK":GUMROAD_LINK, + "LOTTERY_ENABLED":CASINO_ENABLED, "GUMROAD_LINK":GUMROAD_LINK, "DEFAULT_THEME":DEFAULT_THEME, "DESCRIPTION":DESCRIPTION, "has_sidebar":has_sidebar, "has_logo":has_logo, "has_app":has_app, "FP":FP, "NOTIF_MODACTION_JL_MIN":NOTIF_MODACTION_JL_MIN, "cache":cache, "ONLINE_STR":ONLINE_STR, "patron":patron, "approved_embed_hosts":approved_embed_hosts, "dues":dues} diff --git a/files/helpers/slots.py b/files/helpers/slots.py index b8754dadf..548e003a5 100644 --- a/files/helpers/slots.py +++ b/files/helpers/slots.py @@ -1,119 +1,123 @@ +import json from json.encoder import INFINITY import random from .const import * +from files.classes.casino_game import Casino_Game +from flask import g -command_word = "!slots" -casino_word = "!slotsmb" -if SITE_NAME == 'rDrama': minimum_bet = 100 -else: minimum_bet = 10 +if SITE_NAME == 'rDrama': + minimum_bet = 100 +else: + minimum_bet = 10 maximum_bet = INFINITY payout_to_symbols = { - 2: ["👣", "🍀", "🌈", "⭐️"], - 3: ["🍎", "🔞", "⚛️", "☢️"], - 5: ["✡️", "⚔️", "🍆", "🍒"], - 12: ["🐱"] + 2: ["👣", "🍀", "🌈", "⭐️"], + 3: ["🍎", "🔞", "⚛️", "☢️"], + 5: ["✡️", "⚔️", "🍆", "🍒"], + 12: ["🐱"] } -def shuffle(stuff): - random.shuffle(stuff) - return stuff -def check_for_slots_command(in_text, from_user, from_comment): - if not FEATURES['GAMBLING']: return +def casino_slot_pull(gambler, wager_value, currency): + over_min = wager_value >= minimum_bet + under_max = wager_value <= maximum_bet + using_dramacoin = currency == "dramacoin" + using_marseybux = not using_dramacoin + has_proper_funds = (using_dramacoin and gambler.coins >= wager_value) or ( + using_marseybux and gambler.procoins >= wager_value) + currency_prop = "coins" if using_dramacoin else "procoins" + currency_value = getattr(gambler, currency_prop, 0) - in_text = in_text.lower() - if command_word in in_text: - for word in in_text.split(): - if command_word in word: - try: - wager = word[len(command_word):] - wager_value = int(wager) - except: break + if (over_min and under_max and has_proper_funds): + setattr(gambler, currency_prop, currency_value - wager_value) + gambler.winnings -= wager_value - if (wager_value < minimum_bet): break - elif (wager_value > maximum_bet): break - elif (wager_value > from_user.coins): break + payout = determine_payout() + reward = wager_value * payout - from_user.coins -= wager_value - from_user.winnings -= wager_value + setattr(gambler, currency_prop, currency_value + reward) + gambler.winnings += reward - payout = determine_payout() - symbols = build_symbols(payout) - text = build_text(wager_value, payout, from_user, "Coins") - reward = wager_value * payout + symbols = build_symbols(payout) + text = build_text(wager_value, payout, currency) - from_user.coins += reward - from_user.winnings += reward + game_state = { + "symbols": symbols, + "text": text + } + casino_game = Casino_Game() + casino_game.active = False + casino_game.user_id = gambler.id + casino_game.currency = currency_prop + casino_game.wager = wager_value + casino_game.winnings = reward - wager_value + casino_game.kind = 'slots' + casino_game.game_state = json.dumps(game_state) + g.db.add(casino_game) - from_comment.slots_result = f'{symbols} {text}' + return True, casino_game.game_state + else: + return False, "{}" - if casino_word in in_text: - for word in in_text.split(): - if casino_word in word: - try: - wager = word[len(casino_word):] - wager_value = int(wager) - except: break - if (wager_value < minimum_bet): break - elif (wager_value > maximum_bet): break - elif (wager_value > from_user.procoins): break +def build_symbols(for_payout): + all_symbols = [] - from_user.procoins -= wager_value - from_user.winnings -= wager_value + for payout in payout_to_symbols: + for symbol in payout_to_symbols[payout]: + all_symbols.append(symbol) - payout = determine_payout() - symbols = build_symbols(payout) - text = build_text(wager_value, payout, from_user, "Marseybux") - reward = wager_value * payout + shuffle(all_symbols) - from_user.procoins += reward - from_user.winnings += reward + if for_payout == 0: + return "".join([all_symbols[0], ",", all_symbols[1], ",", all_symbols[2]]) + elif for_payout == 1: + indices = shuffle([0, 1, 2]) + symbol_set = ["", "", ""] + match_a = indices[0] + match_b = indices[1] + nonmatch = indices[2] + matching_symbol = all_symbols[0] + other_symbol = all_symbols[1] + symbol_set[match_a] = matching_symbol + symbol_set[match_b] = matching_symbol + symbol_set[nonmatch] = other_symbol - from_comment.slots_result = f'{symbols} {text}' + return "".join([symbol_set[0], ",", symbol_set[1], ",", symbol_set[2]]) + else: + relevantSymbols = shuffle(payout_to_symbols[for_payout]) + symbol = relevantSymbols[0] + + return "".join([symbol, ",", symbol, ",", symbol]) + + +def build_text(wager_value, result, currency): + if result == 0: + return f'Lost {wager_value} {currency}' + elif result == 1: + return 'Broke Even' + elif result == 12: + return f'Jackpot! Won {wager_value * (result-1)} {currency}' + else: + return f'Won {wager_value * (result-1)} {currency}' def determine_payout(): - value = random.randint(1, 100) - if value == 100: return 12 - elif value >= 96: return 5 - elif value >= 88: return 3 - elif value >= 72: return 2 - elif value >= 61: return 1 - else: return 0 + value = random.randint(1, 100) + if value == 100: + return 12 + elif value >= 96: + return 5 + elif value >= 88: + return 3 + elif value >= 72: + return 2 + elif value >= 61: + return 1 + else: + return 0 -def build_symbols(for_payout): - all_symbols = [] - - for payout in payout_to_symbols: - for symbol in payout_to_symbols[payout]: - all_symbols.append(symbol) - - shuffle(all_symbols) - - if for_payout == 0: - return "".join([all_symbols[0], all_symbols[1], all_symbols[2]]) - elif for_payout == 1: - indices = shuffle([0, 1, 2]) - symbol_set = ["", "", ""] - match_a = indices[0] - match_b = indices[1] - nonmatch = indices[2] - matching_symbol = all_symbols[0] - other_symbol = all_symbols[1] - symbol_set[match_a] = matching_symbol - symbol_set[match_b] = matching_symbol - symbol_set[nonmatch] = other_symbol - return "".join(symbol_set) - else: - relevantSymbols = shuffle(payout_to_symbols[for_payout]) - symbol = relevantSymbols[0] - - return "".join([symbol, symbol, symbol]) - -def build_text(wager_value, result, user, currency): - if result == 0: return f'Lost {wager_value} {currency}' - elif result == 1: return 'Broke Even' - elif result == 12: return f'Jackpot! Won {wager_value * (result-1)} {currency}' - else: return f'Won {wager_value * (result-1)} {currency}' \ No newline at end of file +def shuffle(stuff): + random.shuffle(stuff) + return stuff diff --git a/files/helpers/treasure.py b/files/helpers/treasure.py index c34b8824c..77ff66390 100644 --- a/files/helpers/treasure.py +++ b/files/helpers/treasure.py @@ -14,7 +14,7 @@ def check_for_treasure(in_text, from_comment): if not FEATURES['GAMBLING']: return - if '!slots' not in in_text and '!blackjack' not in in_text and '!wordle' not in in_text: + if '!wordle' not in in_text: seed = randint(1, 1000) is_special = seed == 1000 is_standard = seed >= 990 diff --git a/files/helpers/wrappers.py b/files/helpers/wrappers.py index 7ffaea636..3ca6581dd 100644 --- a/files/helpers/wrappers.py +++ b/files/helpers/wrappers.py @@ -170,7 +170,7 @@ def lottery_required(f): def wrapper(*args, **kwargs): v = get_logged_in_user() - if not LOTTERY_ENABLED: abort(404) + if not CASINO_ENABLED: abort(404) return make_response(f(v=v)) diff --git a/files/routes/__init__.py b/files/routes/__init__.py index 78e7cf8b5..3bd028367 100644 --- a/files/routes/__init__.py +++ b/files/routes/__init__.py @@ -17,6 +17,7 @@ from .awards import * from .giphy import * from .subs import * from .lottery import * +from .casino import * from .polls import * from .notifications import * -from .hats import * \ No newline at end of file +from .hats import * diff --git a/files/routes/casino.py b/files/routes/casino.py new file mode 100644 index 000000000..a2a3f3443 --- /dev/null +++ b/files/routes/casino.py @@ -0,0 +1,118 @@ +import json +from files.__main__ import app +from files.helpers.wrappers import * +from files.helpers.alerts import * +from files.helpers.get import * +from files.helpers.const import * +from files.helpers.wrappers import * +from files.helpers.blackjack import * +from files.helpers.slots import * +from files.helpers.lottery import * + + +@app.get("/casino") +@auth_required +def casino(v): + participants = get_users_participating_in_lottery() + return render_template("casino.html", v=v, participants=participants) + + +@app.post("/casino/slots") +@auth_required +def pull_slots(v): + try: + wager = int(request.values.get("wager")) + except: + return {"error": "Invalid wager."} + + try: + currency = request.values.get("currency") + except: + return {"error": "Invalid currency (expected 'dramacoin' or 'marseybux')."} + + if (currency == "dramacoin" and wager > v.coins) or (currency == "marseybux" and wager > v.procoins): + return {"error": f"Not enough {currency} to make that bet."} + + success, game_state = casino_slot_pull(v, wager, currency) + + if success: + return {"game_state": game_state} + else: + return {"error": "Wager must be more than 100 {currency}."} + + +@app.get("/casino/blackjack") +@auth_required +def get_player_blackjack_status(v): + game, game_state = get_active_game(v) + + if game and game.active: + safe_state = get_safe_game_state(v) + return {"active": True, "game_state": safe_state} + else: + return {"active": False, "game_state": game_state} + + +@app.post("/casino/blackjack") +@auth_required +def deal_blackjack(v): + try: + wager = int(request.values.get("wager")) + except: + return {"error": "Invalid wager."} + + try: + currency = request.values.get("currency") + except: + return {"error": "Invalid currency (expected 'dramacoin' or 'marseybux')."} + + if (currency == "dramacoin" and wager > v.coins) or (currency == "marseybux" and wager > v.procoins): + return {"error": f"Not enough {currency} to make that bet."} + + success = deal_blackjack_game(v, wager, currency) + + if success: + game, game_state = get_active_game(v) + + if game and game.active: + safe_state = get_safe_game_state(v) + return {"game_state": safe_state} + else: + return {"game_state": game_state} + + else: + return {"error": "Wager must be more than 100 {currency}."} + + +@app.post("/casino/blackjack/action") +@auth_required +def player_took_blackjack_action(v): + try: + action = request.values.get("action") + except: + return {"error": "Invalid action."} + + was_successful = False + state = None + + if action == 'hit': + success, game_state = gambler_hit(v) + was_successful = success + state = game_state + elif action == 'stay': + success, game_state = gambler_stayed(v) + was_successful = success + state = game_state + elif action == 'double_down': + success, game_state = gambler_doubled_down(v) + was_successful = success + state = game_state + elif action == 'insure': + success, game_state = gambler_purchased_insurance(v) + was_successful = success + state = game_state + + if was_successful: + return {"active": True, "game_state": state} + else: + return {"active": False, "game_state": None} diff --git a/files/routes/comments.py b/files/routes/comments.py index 54c4b4dcd..82d84a87a 100644 --- a/files/routes/comments.py +++ b/files/routes/comments.py @@ -338,7 +338,7 @@ def comment(v): body_html = sanitize(body_for_sanitize, limit_pings=5) - if parent_post.id not in ADMIGGERS and '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower(): + if parent_post.id not in ADMIGGERS and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower(): existing = g.db.query(Comment.id).filter(Comment.author_id == v.id, Comment.deleted_utc == 0, Comment.parent_comment_id == parent_comment_id, @@ -655,12 +655,7 @@ def comment(v): c.upvotes += 3 g.db.add(c) - if not v.rehab: - check_for_slots_command(body, v, c) - - check_for_blackjack_commands(body, v, c) - - if not c.slots_result and not c.blackjack_result and v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html): + if v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html): return {"error":"You can only type marseys!"}, 403 check_for_treasure(body, c) @@ -669,7 +664,7 @@ def comment(v): answer = random.choice(WORDLE_LIST) c.wordle_result = f'_active_{answer}' - if not c.slots_result and not c.blackjack_result and not c.wordle_result and not rts: + if not c.wordle_result and not rts: parent_post.comment_count += 1 g.db.add(parent_post) @@ -980,29 +975,6 @@ def unsave_comment(cid, v): return {"message": "Comment unsaved!"} -@app.post("/blackjack/") -@limiter.limit("1/second;30/minute;200/hour;2500/day") -@limiter.limit("1/second;30/minute;200/hour;2500/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') -@auth_required -def handle_blackjack_action(cid, v): - comment = get_comment(cid) - - if v.id != comment.author_id: - abort(403) - - if 'active' in comment.blackjack_result: - try: action = request.values.get("thing").strip().lower() - except: abort(400) - - if action == 'hit': player_hit(comment) - elif action == 'stay': player_stayed(comment) - elif action == 'doubledown': player_doubled_down(comment) - elif action == 'insurance': player_bought_insurance(comment) - - g.db.add(comment) - g.db.add(v) - return {"response" : comment.blackjack_html(v)} - def diff_words(answer, guess): """ diff --git a/files/routes/lottery.py b/files/routes/lottery.py index 7397b234e..3be06bac5 100644 --- a/files/routes/lottery.py +++ b/files/routes/lottery.py @@ -50,13 +50,6 @@ def lottery_active(v): return {"message": "", "stats": {"user": v.lottery_stats, "lottery": lottery, "participants": participants}} -@app.get("/lottery") -@auth_required -@lottery_required -def lottery(v): - lottery_stats, participant_stats = get_active_lottery_stats() - return render_template("lottery.html", v=v, lottery_stats=lottery_stats, participant_stats=participant_stats) - @app.get("/admin/lottery/participants") @admin_level_required(2) @lottery_required diff --git a/files/routes/posts.py b/files/routes/posts.py index d15325eb4..3dfdf0213 100644 --- a/files/routes/posts.py +++ b/files/routes/posts.py @@ -176,8 +176,8 @@ def post_id(pid, anything=None, v=None, sub=None): comments = sort_comments(sort, comments) - first = [c[0] for c in comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()] - second = [c[0] for c in comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()] + first = [c[0] for c in comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()] + second = [c[0] for c in comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()] comments = first + second else: pinned = g.db.query(Comment).filter(Comment.parent_submission == post.id, Comment.stickied != None).all() @@ -186,8 +186,8 @@ def post_id(pid, anything=None, v=None, sub=None): comments = sort_comments(sort, comments) - first = comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all() - second = comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all() + first = comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all() + second = comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all() comments = first + second offset = 0 @@ -302,16 +302,16 @@ def viewmore(v, pid, sort, offset): comments = sort_comments(sort, comments) - first = [c[0] for c in comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()] - second = [c[0] for c in comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()] + first = [c[0] for c in comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()] + second = [c[0] for c in comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()] comments = first + second else: comments = g.db.query(Comment).join(Comment.author).filter(User.shadowbanned == None, Comment.parent_submission == pid, Comment.level == 1, Comment.stickied == None, Comment.id.notin_(ids)) comments = sort_comments(sort, comments) - first = comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all() - second = comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all() + first = comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all() + second = comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all() comments = first + second comments = comments[offset:] diff --git a/files/templates/admin/admin_home.html b/files/templates/admin/admin_home.html index 3bee53266..fa5f0d2ce 100644 --- a/files/templates/admin/admin_home.html +++ b/files/templates/admin/admin_home.html @@ -53,7 +53,7 @@ {%- endif %} -{% if LOTTERY_ENABLED -%} +{% if CASINO_ENABLED -%}

Lottery