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):
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

View File

@ -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()

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
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
return actions

View File

@ -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/<game>")
@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

View File

@ -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',

View File

@ -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")

View File

@ -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 @@
</div>
<div class="col">
<div class="game_screen-title">
<h5>Actions</h5>
<h5>{% block actiontext %}Actions{% endblock %}</h5>
<hr />
</div>
{% 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';