Add Roulette (#351)

* Add roulette file

* Rename variable to avoid name collission

* Add basic route and create new template for game

* More work on the roulette template

* Initial connection of front-end roulette to back-end roulette

* Add to cron job

* Pass bets to the front end

* Update front-end looks

* Add stackable poker chips for bets

* Minor last changes

* Handle minimum bets

* Add bet table
remotes/1693045480750635534/spooky-22
outruncolors 2022-09-12 20:07:39 -05:00 committed by GitHub
parent e0d32c7105
commit 7d8cfe5576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 966 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -14,12 +14,13 @@ def get_game_feed(game):
def format_game(game): def format_game(game):
user = g.db.query(User).filter(User.id == game.user_id).one() user = g.db.query(User).filter(User.id == game.user_id).one()
wonlost = 'lost' if game.winnings < 0 else 'won' wonlost = 'lost' if game.winnings < 0 else 'won'
relevant_currency = "dramacoin" if game.currency == "coins" else "marseybux"
return { return {
"user": user.username, "user": user.username,
"won_or_lost": wonlost, "won_or_lost": wonlost,
"amount": abs(game.winnings), "amount": abs(game.winnings),
"currency": game.currency "currency": relevant_currency
} }
return list(map(format_game, games)) return list(map(format_game, games))
@ -80,7 +81,3 @@ def get_game_leaderboard(game):
} }
} }
} }
def get_game_stats_for_player(player):
pass

View File

@ -2,6 +2,7 @@ from files.cli import g, app, db_session
import click import click
from files.helpers.const import * from files.helpers.const import *
from files.helpers.alerts import send_repeatable_notification from files.helpers.alerts import send_repeatable_notification
from files.helpers.roulette import spin_roulette_wheel
from files.helpers.get import * from files.helpers.get import *
from files.helpers.actions import * from files.helpers.actions import *
from files.classes import * from files.classes import *
@ -28,6 +29,7 @@ def cron(every_5m, every_1h, every_1d, every_1mo):
if every_5m: if every_5m:
lottery.check_if_end_lottery_task() lottery.check_if_end_lottery_task()
offsitementions.offsite_mentions_task() offsitementions.offsite_mentions_task()
spin_roulette_wheel()
if every_1h: if every_1h:
awards.award_timers_bots_task() awards.award_timers_bots_task()

View File

@ -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]

View File

@ -1,8 +1,6 @@
import json import json
from locale import currency
from math import floor from math import floor
import random import random
from functools import reduce
from enum import Enum from enum import Enum
from files.classes.casino_game import Casino_Game from files.classes.casino_game import Casino_Game
from flask import g from flask import g
@ -362,6 +360,7 @@ def get_value_of_hand(hand):
return max(possibilities) return max(possibilities)
def get_available_actions(state): def get_available_actions(state):
actions = [] actions = []
@ -371,8 +370,8 @@ def get_available_actions(state):
if can_double_down(state): if can_double_down(state):
actions.append(BlackjackAction.DOUBLE_DOWN) actions.append(BlackjackAction.DOUBLE_DOWN)
if can_purchase_insurance(state): if can_purchase_insurance(state):
actions.append(BlackjackAction.BUY_INSURANCE) actions.append(BlackjackAction.BUY_INSURANCE)
return actions return actions

View File

@ -8,32 +8,26 @@ from files.helpers.slots import *
from files.helpers.lottery import * from files.helpers.lottery import *
from files.helpers.casino import * from files.helpers.casino import *
from files.helpers.twentyone import * from files.helpers.twentyone import *
from files.helpers.roulette import *
@app.get("/casino") @app.get("/casino")
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def casino(v): 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) 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/<game>") @app.get("/casino/<game>")
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def casino_game_page(v, game): 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)) feed = json.dumps(get_game_feed(game))
leaderboard = json.dumps(get_game_leaderboard(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") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def casino_game_feed(v, game): def casino_game_feed(v, game):
if v.rehab:
return {"error": "You are under Rehab award effect!"}, 400
feed = get_game_feed(game) feed = get_game_feed(game)
return {"feed": feed} 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") @app.post("/casino/slots")
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def pull_slots(v): 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: try:
wager = int(request.values.get("wager")) 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 return {"error": f"Wager must be more than 5 {currency}."}, 400
# 21
@app.post("/casino/twentyone/deal") @app.post("/casino/twentyone/deal")
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def blackjack_deal_to_player(v): 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: try:
wager = int(request.values.get("wager")) wager = int(request.values.get("wager"))
@ -94,17 +106,18 @@ def blackjack_deal_to_player(v):
state = dispatch_action(v, BlackjackAction.DEAL) state = dispatch_action(v, BlackjackAction.DEAL)
feed = get_game_feed('blackjack') 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: except Exception as e:
return {"error": str(e)} return {"error": str(e)}, 400
@app.post("/casino/twentyone/hit") @app.post("/casino/twentyone/hit")
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def blackjack_player_hit(v): 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: try:
state = dispatch_action(v, BlackjackAction.HIT) state = dispatch_action(v, BlackjackAction.HIT)
feed = get_game_feed('blackjack') feed = get_game_feed('blackjack')
@ -117,7 +130,8 @@ def blackjack_player_hit(v):
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def blackjack_player_stay(v): 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: try:
state = dispatch_action(v, BlackjackAction.STAY) state = dispatch_action(v, BlackjackAction.STAY)
@ -131,7 +145,8 @@ def blackjack_player_stay(v):
@limiter.limit("100/minute;2000/hour;12000/day") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def blackjack_player_doubled_down(v): 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: try:
state = dispatch_action(v, BlackjackAction.DOUBLE_DOWN) 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") @limiter.limit("100/minute;2000/hour;12000/day")
@auth_required @auth_required
def blackjack_player_bought_insurance(v): 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: try:
state = dispatch_action(v, BlackjackAction.BUY_INSURANCE) 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}} return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except: except:
return {"error": "Unable to buy insurance."}, 400 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

View File

@ -3,6 +3,12 @@
{# Title (~25char max), Description (~80char max), {# Title (~25char max), Description (~80char max),
Icon (fa-foo-bar), Color (#ff0000), URL (/post/12345/) #} Icon (fa-foo-bar), Color (#ff0000), URL (/post/12345/) #}
{%- set GAME_INDEX = [ {%- set GAME_INDEX = [
(
'Roulette',
'Round and round the wheel of fate turns',
'fa-circle', '#999',
'/casino/roulette',
),
( (
'Slots', 'Slots',
'Today\'s your lucky day', 'Today\'s your lucky day',

View File

@ -23,6 +23,7 @@
if (succeeded) { if (succeeded) {
updateBlackjackTable(response.state); updateBlackjackTable(response.state);
updateFeed(response.feed); updateFeed(response.feed);
updatePlayerCurrencies(response.gambler);
} else { } else {
console.error("Error: ", response.error); console.error("Error: ", response.error);
throw new Error("Error") throw new Error("Error")

View File

@ -66,7 +66,7 @@
).value; ).value;
const genericCurrency = currency == 'marseybux' ? 'procoins' : 'coins'; const genericCurrency = currency == 'marseybux' ? 'procoins' : 'coins';
return { amount, currency: genericCurrency }; return { amount, currency: genericCurrency, localCurrency: currency };
} }
function disableWager() { function disableWager() {
@ -82,6 +82,7 @@
} }
function updateResult(text, className) { function updateResult(text, className) {
clearResult();
const result = document.getElementById("casinoGameResult"); const result = document.getElementById("casinoGameResult");
result.style.visibility = "visible"; result.style.visibility = "visible";
result.innerText = text; result.innerText = text;
@ -396,7 +397,7 @@
</div> </div>
<div class="col"> <div class="col">
<div class="game_screen-title"> <div class="game_screen-title">
<h5>Actions</h5> <h5>{% block actiontext %}Actions{% endblock %}</h5>
<hr /> <hr />
</div> </div>
{% block actions %} {% endblock %} {% block actions %} {% endblock %}

View File

@ -0,0 +1,578 @@
{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %}
{% block script %}
<script type="text/javascript">
if (
document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)
) {
initializeGame();
} else {
document.addEventListener("DOMContentLoaded", initializeGame);
}
// Kiss my ass if you're judgin'
const CELL_TO_NUMBER_LOOKUP = {
1: 3,
2: 6,
3: 9,
4: 12,
5: 15,
6: 18,
7: 21,
8: 24,
9: 27,
10: 30,
11: 33,
12: 36,
13: 2,
14: 5,
15: 8,
16: 11,
17: 14,
18: 17,
19: 20,
20: 23,
21: 26,
22: 29,
23: 32,
24: 35,
25: 1,
26: 4,
27: 7,
28: 10,
29: 13,
30: 16,
31: 19,
32: 22,
33: 25,
34: 28,
35: 31,
36: 34
};
function initializeGame() {
buildRouletteTable();
updateResult("Rolls occur every five minutes", "success");
requestRouletteBets();
}
function buildRouletteTable() {
const table = document.getElementById('roulette-table');
const reds = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36];
let html = "";
// Lines
html += `
<div class="roulette-table-row">
<div id="LINE_BET#1" onclick="placeChip('LINE_BET', '1')" class="roulette-table-1to1">Line 1</div>
<div id="LINE_BET#2" onclick="placeChip('LINE_BET', '2')" class="roulette-table-1to1">Line 2</div>
<div id="LINE_BET#3" onclick="placeChip('LINE_BET', '3')" class="roulette-table-1to1">Line 3</div>
<div id="LINE_BET#4" onclick="placeChip('LINE_BET', '4')" class="roulette-table-1to1">Line 4</div>
<div id="LINE_BET#5" onclick="placeChip('LINE_BET', '5')" class="roulette-table-1to1">Line 5</div>
<div id="LINE_BET#6" onclick="placeChip('LINE_BET', '6')" class="roulette-table-1to1">Line 6</div>
<div style="flex: 1"></div>
</div>
`;
// First Column
html += "<div class=\"roulette-table-row\">";
for (let i = 1; i < 13; i++) {
const isRed = reds.includes(i);
const correctNumber = CELL_TO_NUMBER_LOOKUP[i];
html += `<div
id="STRAIGHT_UP_BET#${correctNumber}"
onclick="placeChip('STRAIGHT_UP_BET', '${correctNumber}')"
class="roulette-table-number roulette-table-number__${isRed ? 'red' : 'black'}">
${correctNumber}
</div>
`;
}
html += `<div id="COLUMN_BET#3" class="roulette-table-column" onclick="placeChip('COLUMN_BET', '3')">Col 3</div>`;
html += "</div>";
// Second Column
html += "<div class=\"roulette-table-row\">";
for (let i = 13; i < 25; i++) {
const isRed = reds.includes(i);
const correctNumber = CELL_TO_NUMBER_LOOKUP[i];
html += `<div
id="STRAIGHT_UP_BET#${correctNumber}"
onclick="placeChip('STRAIGHT_UP_BET', '${correctNumber}')"
class="roulette-table-number roulette-table-number__${isRed ? 'red' : 'black'}">
${correctNumber}
</div>
`;
}
html += `<div id="COLUMN_BET#2" class="roulette-table-column" onclick="placeChip('COLUMN_BET', '2')">Col 2</div>`;
html += "</div>";
// Third Column
html += "<div class=\"roulette-table-row\">";
for (let i = 25; i < 37; i++) {
const isRed = reds.includes(i);
const correctNumber = CELL_TO_NUMBER_LOOKUP[i];
html += `<div
id="STRAIGHT_UP_BET#${correctNumber}"
onclick="placeChip('STRAIGHT_UP_BET', '${correctNumber}')"
class="roulette-table-number roulette-table-number__${isRed ? 'red' : 'black'}">
${correctNumber}
</div>
`;
}
html += `<div id="COLUMN_BET#1" class="roulette-table-column" onclick="placeChip('COLUMN_BET', '1')">Col 1</div>`;
html += "</div>";
// Line Bets and 1:1 Bets
html += `
<div class="roulette-table-row">
<div id="DOZEN_BET#1" class="roulette-table-line" onclick="placeChip('DOZEN_BET', '1')">1st12</div>
<div id="DOZEN_BET#2" class="roulette-table-line" onclick="placeChip('DOZEN_BET', '2')">2nd12</div>
<div id="DOZEN_BET#3" class="roulette-table-line" onclick="placeChip('DOZEN_BET', '3')">3rd12</div>
<div style="flex: 1"></div>
</div>
<div class="roulette-table-row">
<div id="HIGH_LOW_BET#LOW" class="roulette-table-1to1" onclick="placeChip('HIGH_LOW_BET', 'LOW')">1:18</div>
<div id="EVEN_ODD_BET#EVEN" class="roulette-table-1to1" onclick="placeChip('EVEN_ODD_BET', 'EVEN')">EVEN</div>
<div id="RED_BLACK_BET#RED" class="roulette-table-1to1" onclick="placeChip('RED_BLACK_BET', 'RED')" style="background-color: red">RED</div>
<div id="RED_BLACK_BET#BLACK" class="roulette-table-1to1" onclick="placeChip('RED_BLACK_BET', 'BLACK')" style="background-color: black">BLACK</div>
<div id="EVEN_ODD_BET#ODD" class="roulette-table-1to1" onclick="placeChip('EVEN_ODD_BET', 'ODD')">ODD</div>
<div id="HIGH_LOW_BET#HIGH" class="roulette-table-1to1" onclick="placeChip('HIGH_LOW_BET', 'HIGH')">19:36</div>
<div style="flex: 1"></div>
</div>
`;
table.innerHTML = html;
}
function formatFlatBets(bets) {
let flatBets = [];
for (const betCollection of Object.values(bets)) {
flatBets = flatBets.concat(betCollection)
}
return flatBets;
}
function formatNormalizedBets(bets) {
const normalizedBets = {
gamblers: [],
gamblersByName: {}
};
const flatBets = formatFlatBets(bets);
for (const bet of flatBets) {
if (!normalizedBets.gamblers.includes(bet.gambler_username)) {
normalizedBets.gamblers.push(bet.gambler_username);
}
if (!normalizedBets.gamblersByName[bet.gambler_username]) {
normalizedBets.gamblersByName[bet.gambler_username] = {
name: bet.gambler_username,
avatar: bet.gambler_profile_url,
profile: `/@${bet.gambler_username}`,
wagerTotal: {
coins: 0,
procoins: 0
},
wagers: []
}
}
const entry = normalizedBets.gamblersByName[bet.gambler_username];
entry.wagerTotal[bet.wager.currency] += bet.wager.amount;
const existingWager = entry.wagers.find(wager => wager.bet === bet.bet && wager.which === bet.which);
if (existingWager) {
existingWager.amounts[bet.wager.currency] += bet.wager.amount;
} else {
const newEntry = {
bet: bet.bet,
which: bet.which,
amounts: {
coins: 0,
procoins: 0
},
};
newEntry.amounts[bet.wager.currency] += bet.wager.amount;
entry.wagers.push(newEntry);
}
}
return normalizedBets;
}
function buildPokerChip(avatar) {
return `
<div class="roulette-poker-chip">
<img src="/i/pokerchip.webp" width="40" height="40" />
<img src="${avatar}" width="40" height="40" />
</div>
`;
}
function buildRouletteBets(bets) {
const betArea = document.getElementById("roulette-bets");
const flatBets = formatFlatBets(bets);
const normalizedBets = formatNormalizedBets(bets);
const dramacoinImgHtml = `
<img
src="/i/rDrama/coins.webp?v=3009"
alt="coin"
width="32"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title=""
aria-label="Dramacoin"
data-bs-original-title="Dramacoin" />
`;
const marseybuxImgHtml = `
<img
src="/i/marseybux.webp?v=2000"
alt="marseybux"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title=""
aria-label="Marseybux"
width="32" class="mr-1 ml-1"
data-bs-original-title="Marseybux" />
`;
const { participants, dramacoin, marseybux } = flatBets.reduce((prev, next) => {
if (!prev.participants.includes(next.gambler_username)) {
prev.participants.push(next.gambler_username);
}
if (next.wager.currency == 'coins') {
prev.dramacoin += next.wager.amount;
} else {
prev.marseybux += next.wager.amount;
}
return prev;
}, { participants: [], dramacoin: 0, marseybux: 0 });
const dramacoinText = `${dramacoin} ${dramacoinImgHtml}`;
const marseybuxText = `${marseybux} ${marseybuxImgHtml}`;
const playerText = participants.length > 1 ? `${participants.length} players are` : `1 player is`;
const totalText = dramacoin && marseybux ? `${dramacoinText} and ${marseybuxText}` : dramacoin ? dramacoinText : marseybuxText;
const fullTotalText = participants.length === 0 ? "No one has placed a bet" : `${playerText} betting a total of ${totalText}`;
let betHtml = `
<small class="roulette-total-bets">${fullTotalText}</small>
<hr />
`;
for (player of normalizedBets.gamblers) {
const { name, avatar, wagerTotal, wagers } = normalizedBets.gamblersByName[player];
betHtml += `<div class="roulette-bet-summary">`;
// Heading
betHtml += ` <div class="roulette-bet-summary--heading">`;
betHtml += buildPokerChip(avatar);
const coinText = wagerTotal.coins > 0 ? `${wagerTotal.coins} ${dramacoinImgHtml}` : "";
const procoinText = wagerTotal.procoins > 0 ? `${wagerTotal.procoins} ${marseybuxImgHtml}` : "";
const bettingText = coinText && procoinText ? `${coinText} and ${procoinText}` : coinText || procoinText;
betHtml += `<p>${name} is betting ${bettingText}:</p>`;
betHtml += ` </div>`;
// Individual bets
betHtml += `<ul class="roulette-bet-summary--list">`;
for (const individualBet of wagers) {
const coinText = individualBet.amounts.coins > 0 ? `${individualBet.amounts.coins} ${dramacoinImgHtml}` : "";
const procoinText = individualBet.amounts.procoins > 0 ? `${individualBet.amounts.procoins} ${marseybuxImgHtml}` : "";
const details = {
STRAIGHT_UP_BET: `that the number will be ${individualBet.which}`,
LINE_BET: `that the number will be within line ${individualBet.which}`,
COLUMN_BET: `that the number will be within column ${individualBet.which}`,
DOZEN_BET: `that the number will be within dozen ${individualBet.which}`,
EVEN_ODD_BET: `that the number will be ${individualBet.which.toLowerCase()}`,
RED_BLACK_BET: `that the color of the number will be ${individualBet.which.toLowerCase()}`,
HIGH_LOW_BET: `that the number will be ${individualBet.which === "HIGH" ? "higher than 18" : "lower than 19"}`
}
const betText = coinText && procoinText ? `${coinText} and ${procoinText}` : coinText || procoinText;
betHtml += `<li>${betText} ${details[individualBet.bet]}</li>`;
}
betHtml += `</ul>`;
betHtml += `</div>`;
}
betArea.innerHTML = betHtml;
}
function placeChip(bet, which) {
const { amount, currency: safeCurrency, localCurrency: currency } = getWager();
const texts = {
STRAIGHT_UP_BET: `Bet ${amount} ${currency} on ${which}?\nYou could win ${amount * 35} ${currency}.`,
LINE_BET: `Bet ${amount} ${currency} on line ${which}?\nYou could win ${amount * 5} ${currency}.`,
COLUMN_BET: `Bet ${amount} ${currency} column ${which}?\nYou could win ${amount * 2} ${currency}.`,
DOZEN_BET: `Bet ${amount} ${currency} dozen ${which}?\nYou could win ${amount * 2} ${currency}.`,
EVEN_ODD_BET: `Bet ${amount} ${currency} that the number will be ${which.toLowerCase()}?\nYou could win ${amount} ${currency}.`,
RED_BLACK_BET: `Bet ${amount} ${currency} that the number will be ${which.toLowerCase()}?\nYou could win ${amount} ${currency}.`,
HIGH_LOW_BET: `Bet ${amount} ${currency} that the number will be ${which === "HIGH" ? "higher than 18" : "lower than 19"}?\nYou could win ${amount} ${currency}.`,
}
const text = texts[bet] || "";
const confirmed = window.confirm(text);
if (confirmed) {
const xhr = new XMLHttpRequest();
xhr.open("post", "/casino/roulette/place-bet");
xhr.onload = handleRouletteResponse.bind(null, xhr);
const form = new FormData();
form.append("formkey", formkey());
form.append("bet", bet);
form.append("which", which);
form.append("wager", amount);
form.append("currency", safeCurrency);
xhr.send(form);
}
}
function addChipsToTable(bets) {
const flatBets = formatFlatBets(bets);
for (const bet of flatBets) {
const tableElement = document.getElementById(`${bet.bet}#${bet.which}`);
tableElement.style.position = 'relative';
const count = tableElement.dataset.count ? parseInt(tableElement.dataset.count) + 1 : 1;
tableElement.dataset.count = count;
const chip = buildPokerChip(bet.gambler_profile_url)
tableElement.innerHTML = `${tableElement.innerHTML}<div style="position: absolute; bottom: ${count + 2}px; left: -${count + 2}px; transform: scale(0.5);">${chip}</div>`;
}
}
function requestRouletteBets() {
const xhr = new XMLHttpRequest();
xhr.open("get", "/casino/roulette/bets");
xhr.onload = handleRouletteResponse.bind(null, xhr);
xhr.send();
}
function handleRouletteResponse(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) {
buildRouletteBets(response.bets);
addChipsToTable(response.bets);
updatePlayerCurrencies(response.gambler);
updateResult("Rolls occur every five minutes", "success");
} else {
updateResult("Unable to place that bet.", "danger");
}
}
</script>
{% endblock %}
{% block screen %}
<style>
.roulette-table-number {
flex: 1;
height: 60px;
border: 1px solid white;
background: green;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bolder;
}
.roulette-table-number__black {
background: black;
}
.roulette-table-number__red {
background: red;
}
.roulette-table-row {
display: flex;
align-items: center;
justify-content: flex-start;
}
.roulette-table-column {
flex: 1;
height: 60px;
border: 1px solid white;
background: green;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bolder;
writing-mode: vertical-rl;
text-orientation: sideways;
}
.roulette-table-line,
.roulette-table-1to1 {
border: 1px solid white;
background: green;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bolder;
}
.roulette-table-line {
flex: 4;
}
.roulette-table-1to1 {
flex: 2;
}
.roulette-poker-chip {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
}
.roulette-bet-summary--heading {
display: flex;
align-items: center;
}
.roulette-bet-summary--heading p {
margin: 0;
margin-left: 1rem;
font-weight: bolder;
}
.roulette-bet-summary--list {
list-style-type: none;
}
.roulette-poker-chip img:last-child {
position: absolute;
}
.roulette-total-bets {
text-transform: uppercase;
letter-spacing: 2px;
text-align: right;
}
</style>
<div id="roulette-table">
</div>
{% endblock %}
{% block actiontext %}
Bets
{% endblock %}
{% block actions %}
<div id="roulette-bets">
<div class="roulette-bet-summary">
<div class="roulette-bet-summary--heading">
<div class="roulette-poker-chip">
<img src="/i/pokerchip.webp" width="40" height="40" />
<img src="/e/marseycodecellove.webp" width="40" height="40" />
</div>
<p>111 is betting 4 and 4:
</p>
</div>
<ul class="roulette-bet-summary--list">
<li>2 <img src="/i/rDrama/coins.webp?v=3009" alt="coin" width="32" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="" aria-label="Dramacoin" data-bs-original-title="Dramacoin"> that
the number will be black.</li>
<li>2 <img src="/i/rDrama/coins.webp?v=3009" alt="coin" width="32" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="" aria-label="Dramacoin" data-bs-original-title="Dramacoin"> that
the number will be even.</li>
</ul>
</div>
</div>
<div class="game_screen-title">
<h5>How to Bet</h5>
<hr />
</div>
<table class="table">
<thead>
<tr>
<th>Bet</th>
<th>Payout</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Straight Up</td>
<td>35:1</td>
<td>
Click any single number. <br />
You win if the roulette lands on that number.
</td>
</tr>
<tr>
<td>Line</td>
<td>5:1</td>
<td>
Click Line 1, Line 2 ... Line 6. <br />
You win if the roulette lands on any of the six numbers beneath the line.
</td>
</tr>
<tr>
<td>Column</td>
<td>2:1</td>
<td>
Click Col 1, Col 2 or Col 3. <br />
You win if the roulette lands on any of the 12 numbers to the left of the column.
</td>
</tr>
<tr>
<td>Dozen</td>
<td>2:1</td>
<td>
Click 1st12, 2nd12 or 3rd12. <br />
You win if the roulette lands on a number within 1-12, 13-24 or 25-36, respectively.
</td>
</tr>
<tr>
<td>Even/Odd</td>
<td>1:1</td>
<td>
Click EVEN or ODD. <br />
You win if the roulette lands on a number that matches your choice.
</td>
</tr>
<tr>
<td>Red/Black</td>
<td>1:1</td>
<td>
Click RED or BLACK. <br />
You win if the roulette lands on a number that is the same color as your choice.
</td>
</tr>
<tr>
<td>High/Low</td>
<td>1:1</td>
<td>
Click 1:18 or 19:36. <br />
You win if the roulette lands on a number within your selected range.
</td>
</tr>
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1 @@
ALTER TYPE casino_game_kind ADD VALUE 'roulette';