diff --git a/files/assets/images/pokerchip.webp b/files/assets/images/pokerchip.webp new file mode 100644 index 0000000000..3bb6067731 Binary files /dev/null and b/files/assets/images/pokerchip.webp differ diff --git a/files/helpers/casino.py b/files/helpers/casino.py index e7c2c7925b..160f438dbf 100644 --- a/files/helpers/casino.py +++ b/files/helpers/casino.py @@ -14,12 +14,13 @@ def get_game_feed(game): def format_game(game): user = g.db.query(User).filter(User.id == game.user_id).one() wonlost = 'lost' if game.winnings < 0 else 'won' + relevant_currency = "dramacoin" if game.currency == "coins" else "marseybux" return { "user": user.username, "won_or_lost": wonlost, "amount": abs(game.winnings), - "currency": game.currency + "currency": relevant_currency } return list(map(format_game, games)) @@ -80,7 +81,3 @@ def get_game_leaderboard(game): } } } - - -def get_game_stats_for_player(player): - pass diff --git a/files/helpers/cron.py b/files/helpers/cron.py index b8f93f44cc..bc17f8d2d1 100644 --- a/files/helpers/cron.py +++ b/files/helpers/cron.py @@ -2,6 +2,7 @@ from files.cli import g, app, db_session import click from files.helpers.const import * from files.helpers.alerts import send_repeatable_notification +from files.helpers.roulette import spin_roulette_wheel from files.helpers.get import * from files.helpers.actions import * from files.classes import * @@ -28,6 +29,7 @@ def cron(every_5m, every_1h, every_1d, every_1mo): if every_5m: lottery.check_if_end_lottery_task() offsitementions.offsite_mentions_task() + spin_roulette_wheel() if every_1h: awards.award_timers_bots_task() diff --git a/files/helpers/roulette.py b/files/helpers/roulette.py new file mode 100644 index 0000000000..e4778cc0a1 --- /dev/null +++ b/files/helpers/roulette.py @@ -0,0 +1,295 @@ +import json +from random import randint +from enum import Enum +from files.helpers.alerts import * +from files.classes.casino_game import Casino_Game +from files.helpers.get import get_account +from flask import g + + +class RouletteAction(str, Enum): + STRAIGHT_UP_BET = "STRAIGHT_UP_BET" + LINE_BET = "LINE_BET" + COLUMN_BET = "COLUMN_BET" + DOZEN_BET = "DOZEN_BET" + EVEN_ODD_BET = "EVEN_ODD_BET" + RED_BLACK_BET = "RED_BLACK_BET" + HIGH_LOW_BET = "HIGH_LOW_BET" + + +class RouletteEvenOdd(str, Enum): + EVEN = "EVEN" + ODD = "ODD" + + +class RouletteRedBlack(str, Enum): + RED = "RED" + BLACK = "BLACK" + + +class RouletteHighLow(str, Enum): + HIGH = "HIGH" + LOW = "LOW" + + +REDS = (1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36) +BLACKS = (2, 4, 6, 8, 10, 11, 13, 15, 17, 20, 22, 24, 26, 28, 29, 31, 33, 35) +LINES = { + 1: (1, 2, 3, 4, 5, 6), + 2: (7, 8, 9, 10, 11, 12), + 3: (13, 14, 15, 16, 17, 18), + 4: (19, 20, 21, 22, 23, 24), + 5: (25, 26, 27, 28, 29, 30), + 6: (31, 32, 33, 34, 35, 36) +} +COLUMNS = { + 1: (1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34), + 2: (2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35), + 3: (3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36) +} +DOZENS = { + 1: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), + 2: (13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24), + 3: (25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36) +} +PAYOUT_MULITPLIERS = { + RouletteAction.STRAIGHT_UP_BET: 35, + RouletteAction.LINE_BET: 5, + RouletteAction.COLUMN_BET: 2, + RouletteAction.DOZEN_BET: 2, + RouletteAction.EVEN_ODD_BET: 1, + RouletteAction.RED_BLACK_BET: 1, + RouletteAction.HIGH_LOW_BET: 1, +} + + +def get_active_roulette_games(): + return g.db.query(Casino_Game).filter( + Casino_Game.active == True, + Casino_Game.kind == 'roulette' + ).all() + + +def charge_gambler(gambler, amount, currency): + currency_gambler_holds = getattr(gambler, currency) + can_afford = currency_gambler_holds >= amount + + if not can_afford: + raise Exception("Gambler cannot afford charge.") + + setattr(gambler, currency, currency_gambler_holds - amount) + g.db.add(gambler) + + +def gambler_placed_roulette_bet(gambler, bet, which, amount, currency): + if not bet in ( + RouletteAction.STRAIGHT_UP_BET, + RouletteAction.LINE_BET, + RouletteAction.COLUMN_BET, + RouletteAction.DOZEN_BET, + RouletteAction.EVEN_ODD_BET, + RouletteAction.RED_BLACK_BET, + RouletteAction.HIGH_LOW_BET + ): + raise Exception( + f'Illegal bet {bet} passed to Roulette#gambler_placed_roulette_bet') + + active_games = get_active_roulette_games() + + if len(active_games) == 0: + parent_id = int(time.time()) + else: + parent_id = json.loads(active_games[0].game_state)['parent_id'] + + charge_gambler(gambler, amount, currency) + + game = Casino_Game() + game.user_id = gambler.id + game.currency = currency + game.wager = amount + game.winnings = 0 + game.kind = 'roulette' + game.game_state = json.dumps( + {"parent_id": parent_id, "bet": bet, "which": which}) + game.active = True + g.db.add(game) + g.db.commit() + + +def get_roulette_bets_and_betters(): + participants = [] + bets = { + RouletteAction.STRAIGHT_UP_BET: [], + RouletteAction.LINE_BET: [], + RouletteAction.COLUMN_BET: [], + RouletteAction.DOZEN_BET: [], + RouletteAction.EVEN_ODD_BET: [], + RouletteAction.RED_BLACK_BET: [], + RouletteAction.HIGH_LOW_BET: [], + } + active_games = get_active_roulette_games() + + for game in active_games: + if not game.user_id in participants: + participants.append(game.user_id) + + user = get_account(game.user_id) + game_state = json.loads(game.game_state) + bet = game_state['bet'] + bets[bet].append({ + 'game_id': game.id, + 'gambler': game.user_id, + 'gambler_username': user.username, + 'gambler_profile_url': user.profile_url, + 'bet': bet, + 'which': game_state['which'], + 'wager': { + 'amount': game.wager, + 'currency': game.currency + } + }) + + return participants, bets, active_games + + +def spin_roulette_wheel(): + participants, bets, active_games = get_roulette_bets_and_betters() + + if len(participants) > 0: + number = randint(0, 37) # 37 is 00 + winners, payouts, rewards_by_game_id = determine_roulette_winners( + number, bets) + + # Pay out to the winners and send a notification. + for user_id in winners: + gambler = get_account(user_id) + gambler_payout = payouts[user_id] + coin_winnings = gambler_payout['coins'] + procoin_winnings = gambler_payout['procoins'] + + setattr(gambler, 'coins', gambler.coins + coin_winnings) + setattr(gambler, 'procoins', gambler.procoins + procoin_winnings) + + g.db.add(gambler) + + # Notify the winners. + notification_text = f"Winning number: {number}\nCongratulations! One or more of your roulette bets paid off!\n" + + if coin_winnings > 0: + notification_text = notification_text + \ + f"* You received {coin_winnings} dramacoins.\n" + + if procoin_winnings > 0: + notification_text = notification_text + \ + f"* You received {procoin_winnings} marseybux.\n" + + send_repeatable_notification(user_id, notification_text) + + # Give condolences. + for participant in participants: + if not participant in winners: + send_repeatable_notification( + participant, f"Winning number: {number}\nSorry, none of your recent roulette bets paid off.") + + g.db.flush() + + # Adjust game winnings. + for game in active_games: + if rewards_by_game_id.get(game.id): + game.winnings = rewards_by_game_id[game.id] + else: + game.winnings = -game.wager + + game.active = False + g.db.add(game) + + +def determine_roulette_winners(number, bets): + winners = [] + payouts = {} + rewards_by_game_id = {} + + def add_to_winnings(bet): + game_id = int(bet['game_id']) + gambler_id = bet['gambler'] + wager_amount = bet['wager']['amount'] + bet_kind = bet['bet'] + reward = wager_amount * PAYOUT_MULITPLIERS[bet_kind] + payout = wager_amount + reward + currency = bet['wager']['currency'] + + if not gambler_id in winners: + winners.append(gambler_id) + + if not payouts.get(gambler_id): + payouts[gambler_id] = { + 'coins': 0, + 'procoins': 0 + } + + if not rewards_by_game_id.get(game_id): + rewards_by_game_id[game_id] = reward + + payouts[gambler_id][currency] += payout + + # Straight-Up Bet + for bet in bets[RouletteAction.STRAIGHT_UP_BET]: + if int(bet['which']) == number: + add_to_winnings(bet) + + # Line Bet + line = -1 + for i in range(1, 7): + if number in LINES[i]: + line = i + + for bet in bets[RouletteAction.LINE_BET]: + if int(bet['which']) == line: + add_to_winnings(bet) + + # Column Bet + column = -1 + for i in range(1, 4): + if number in COLUMNS[i]: + column = i + + for bet in bets[RouletteAction.COLUMN_BET]: + if int(bet['which']) == column: + add_to_winnings(bet) + + # Dozen Bet + dozen = -1 + for i in range(1, 4): + if number in DOZENS[i]: + dozen = i + + for bet in bets[RouletteAction.DOZEN_BET]: + if int(bet['which']) == dozen: + add_to_winnings(bet) + + # Even/Odd Bet + even_odd = RouletteEvenOdd.EVEN if number % 2 == 0 else RouletteEvenOdd.ODD + + for bet in bets[RouletteAction.EVEN_ODD_BET]: + if bet['which'] == even_odd: + add_to_winnings(bet) + + # Red/Black Bet + red_black = RouletteRedBlack.RED if number in REDS else RouletteRedBlack.BLACK + + for bet in bets[RouletteAction.RED_BLACK_BET]: + if bet['which'] == red_black: + add_to_winnings(bet) + + # High/Low Bet + high_low = RouletteHighLow.HIGH if number > 18 else RouletteHighLow.LOW + + for bet in bets[RouletteAction.HIGH_LOW_BET]: + if bet['which'] == high_low: + add_to_winnings(bet) + + return winners, payouts, rewards_by_game_id + + +def get_roulette_bets(): + return get_roulette_bets_and_betters()[1] diff --git a/files/helpers/twentyone.py b/files/helpers/twentyone.py index ca181506d9..fc57abaad6 100644 --- a/files/helpers/twentyone.py +++ b/files/helpers/twentyone.py @@ -1,8 +1,6 @@ import json -from locale import currency from math import floor import random -from functools import reduce from enum import Enum from files.classes.casino_game import Casino_Game from flask import g @@ -362,6 +360,7 @@ def get_value_of_hand(hand): return max(possibilities) + def get_available_actions(state): actions = [] @@ -371,8 +370,8 @@ def get_available_actions(state): if can_double_down(state): actions.append(BlackjackAction.DOUBLE_DOWN) - + if can_purchase_insurance(state): actions.append(BlackjackAction.BUY_INSURANCE) - return actions \ No newline at end of file + return actions diff --git a/files/routes/casino.py b/files/routes/casino.py index cec14ee717..bb367828a7 100644 --- a/files/routes/casino.py +++ b/files/routes/casino.py @@ -8,32 +8,26 @@ from files.helpers.slots import * from files.helpers.lottery import * from files.helpers.casino import * from files.helpers.twentyone import * +from files.helpers.roulette import * @app.get("/casino") @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def casino(v): - if v.rehab: return render_template("casino/rehab.html", v=v) + if v.rehab: + return render_template("casino/rehab.html", v=v) + return render_template("casino.html", v=v) -@app.get("/lottershe") -@limiter.limit("100/minute;2000/hour;12000/day") -@auth_required -def lottershe(v): - if v.rehab: return render_template("casino/rehab.html", v=v) - participants = get_users_participating_in_lottery() - return render_template("lottery.html", v=v, participants=participants) - - - @app.get("/casino/") @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def casino_game_page(v, game): - if v.rehab: return render_template("casino/rehab.html", v=v) - + if v.rehab: + return render_template("casino/rehab.html", v=v) + feed = json.dumps(get_game_feed(game)) leaderboard = json.dumps(get_game_leaderboard(game)) @@ -50,15 +44,31 @@ def casino_game_page(v, game): @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def casino_game_feed(v, game): + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 + feed = get_game_feed(game) return {"feed": feed} +# Lottershe +@app.get("/lottershe") +@limiter.limit("100/minute;2000/hour;12000/day") +@auth_required +def lottershe(v): + if v.rehab: + return render_template("casino/rehab.html", v=v) + + participants = get_users_participating_in_lottery() + return render_template("lottery.html", v=v, participants=participants) + +# Slots @app.post("/casino/slots") @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def pull_slots(v): - if v.rehab: return {"error": "You are under Rehab award effect!"}, 400 + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 try: wager = int(request.values.get("wager")) @@ -81,11 +91,13 @@ def pull_slots(v): return {"error": f"Wager must be more than 5 {currency}."}, 400 +# 21 @app.post("/casino/twentyone/deal") @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def blackjack_deal_to_player(v): - if v.rehab: return {"error": "You are under Rehab award effect!"}, 400 + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 try: wager = int(request.values.get("wager")) @@ -94,17 +106,18 @@ def blackjack_deal_to_player(v): state = dispatch_action(v, BlackjackAction.DEAL) feed = get_game_feed('blackjack') - return {"success": True, "state": state, "feed": feed} + return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except Exception as e: - return {"error": str(e)} + return {"error": str(e)}, 400 @app.post("/casino/twentyone/hit") @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def blackjack_player_hit(v): - if v.rehab: return {"error": "You are under Rehab award effect!"}, 400 - + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 + try: state = dispatch_action(v, BlackjackAction.HIT) feed = get_game_feed('blackjack') @@ -117,7 +130,8 @@ def blackjack_player_hit(v): @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def blackjack_player_stay(v): - if v.rehab: return {"error": "You are under Rehab award effect!"}, 400 + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 try: state = dispatch_action(v, BlackjackAction.STAY) @@ -131,7 +145,8 @@ def blackjack_player_stay(v): @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def blackjack_player_doubled_down(v): - if v.rehab: return {"error": "You are under Rehab award effect!"}, 400 + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 try: state = dispatch_action(v, BlackjackAction.DOUBLE_DOWN) @@ -145,7 +160,8 @@ def blackjack_player_doubled_down(v): @limiter.limit("100/minute;2000/hour;12000/day") @auth_required def blackjack_player_bought_insurance(v): - if v.rehab: return {"error": "You are under Rehab award effect!"}, 400 + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 try: state = dispatch_action(v, BlackjackAction.BUY_INSURANCE) @@ -153,3 +169,40 @@ def blackjack_player_bought_insurance(v): return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} except: return {"error": "Unable to buy insurance."}, 400 + +# Roulette +@app.get("/casino/roulette/bets") +@limiter.limit("100/minute;2000/hour;12000/day") +@auth_required +def roulette_get_bets(v): + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 + + bets = get_roulette_bets() + + return {"success": True, "bets": bets, "gambler": {"coins": v.coins, "procoins": v.procoins}} + + +@app.post("/casino/roulette/place-bet") +@limiter.limit("100/minute;2000/hour;12000/day") +@auth_required +def roulette_player_placed_bet(v): + if v.rehab: + return {"error": "You are under Rehab award effect!"}, 400 + + try: + bet = request.values.get("bet") + which = request.values.get("which") + amount = int(request.values.get("wager")) + currency = request.values.get("currency") + + if amount < 5: + return {"error": f"Minimum bet is 5 {currency}."} + + gambler_placed_roulette_bet(v, bet, which, amount, currency) + + bets = get_roulette_bets() + + return {"success": True, "bets": bets, "gambler": {"coins": v.coins, "procoins": v.procoins}} + except: + return {"error": "Unable to place a bet."}, 400 \ No newline at end of file diff --git a/files/templates/casino.html b/files/templates/casino.html index b0699e21b6..27ab08de9a 100644 --- a/files/templates/casino.html +++ b/files/templates/casino.html @@ -3,6 +3,12 @@ {# Title (~25char max), Description (~80char max), Icon (fa-foo-bar), Color (#ff0000), URL (/post/12345/) #} {%- set GAME_INDEX = [ + ( + 'Roulette', + 'Round and round the wheel of fate turns', + 'fa-circle', '#999', + '/casino/roulette', + ), ( 'Slots', 'Today\'s your lucky day', diff --git a/files/templates/casino/blackjack_screen.html b/files/templates/casino/blackjack_screen.html index 38776c0863..d8389be540 100644 --- a/files/templates/casino/blackjack_screen.html +++ b/files/templates/casino/blackjack_screen.html @@ -23,6 +23,7 @@ if (succeeded) { updateBlackjackTable(response.state); updateFeed(response.feed); + updatePlayerCurrencies(response.gambler); } else { console.error("Error: ", response.error); throw new Error("Error") diff --git a/files/templates/casino/game_screen.html b/files/templates/casino/game_screen.html index 6362debc51..e51717407a 100644 --- a/files/templates/casino/game_screen.html +++ b/files/templates/casino/game_screen.html @@ -66,7 +66,7 @@ ).value; const genericCurrency = currency == 'marseybux' ? 'procoins' : 'coins'; - return { amount, currency: genericCurrency }; + return { amount, currency: genericCurrency, localCurrency: currency }; } function disableWager() { @@ -82,6 +82,7 @@ } function updateResult(text, className) { + clearResult(); const result = document.getElementById("casinoGameResult"); result.style.visibility = "visible"; result.innerText = text; @@ -396,7 +397,7 @@
-
Actions
+
{% block actiontext %}Actions{% endblock %}

{% block actions %} {% endblock %} diff --git a/files/templates/casino/roulette_screen.html b/files/templates/casino/roulette_screen.html new file mode 100644 index 0000000000..09bb0c2991 --- /dev/null +++ b/files/templates/casino/roulette_screen.html @@ -0,0 +1,578 @@ +{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %} + +{% block script %} + +{% endblock %} + +{% block screen %} + + +
+
+{% endblock %} + +{% block actiontext %} +Bets +{% endblock %} + +{% block actions %} +
+
+
+
+ + +
+

111 is betting 4 and 4: +

+
+
    +
  • 2 coin that + the number will be black.
  • +
  • 2 coin that + the number will be even.
  • +
+
+
+ +
+
How to Bet
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BetPayoutDescription
Straight Up35:1 + Click any single number.
+ You win if the roulette lands on that number. +
Line5:1 + Click Line 1, Line 2 ... Line 6.
+ You win if the roulette lands on any of the six numbers beneath the line. +
Column2:1 + Click Col 1, Col 2 or Col 3.
+ You win if the roulette lands on any of the 12 numbers to the left of the column. +
Dozen2:1 + Click 1st12, 2nd12 or 3rd12.
+ You win if the roulette lands on a number within 1-12, 13-24 or 25-36, respectively. +
Even/Odd1:1 + Click EVEN or ODD.
+ You win if the roulette lands on a number that matches your choice. +
Red/Black1:1 + Click RED or BLACK.
+ You win if the roulette lands on a number that is the same color as your choice. +
High/Low1:1 + Click 1:18 or 19:36.
+ You win if the roulette lands on a number within your selected range. +
+{% endblock %} \ No newline at end of file diff --git a/sql/20220911-add-roulette.sql b/sql/20220911-add-roulette.sql new file mode 100644 index 0000000000..154f614591 --- /dev/null +++ b/sql/20220911-add-roulette.sql @@ -0,0 +1 @@ +ALTER TYPE casino_game_kind ADD VALUE 'roulette'; \ No newline at end of file