[DO NOT MERGE] Casino changes (#350)

* Merge up

* Create composable template for casino games

* Big changerinos

* Only allow double down sometimes

* Add drawing capability

* Make everything pretty

* Add leaderboard

* Update files/routes/casino.py

Co-authored-by: code-review-doctor[bot] <72320148+code-review-doctor[bot]@users.noreply.github.com>

* Update files/helpers/casino.py

Co-authored-by: code-review-doctor[bot] <72320148+code-review-doctor[bot]@users.noreply.github.com>

* Update files/helpers/twentyone.py

Co-authored-by: code-review-doctor[bot] <72320148+code-review-doctor[bot]@users.noreply.github.com>

* Update files/helpers/twentyone.py

Co-authored-by: code-review-doctor[bot] <72320148+code-review-doctor[bot]@users.noreply.github.com>

* Update files/helpers/twentyone.py

Co-authored-by: code-review-doctor[bot] <72320148+code-review-doctor[bot]@users.noreply.github.com>

* Add some stuff

* Rehab screen

* Default sets for no games

* Stupid revert

Co-authored-by: code-review-doctor[bot] <72320148+code-review-doctor[bot]@users.noreply.github.com>
remotes/1693045480750635534/spooky-22
outruncolors 2022-09-10 16:01:34 -05:00 committed by GitHub
parent 89a162c2f2
commit d57a569125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1532 additions and 1182 deletions

View File

@ -1,138 +0,0 @@
.casino-games {
display: flex;
flex-direction: column;
align-items: center;
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: 2rem;
}
/* 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 KiB

View File

@ -1,350 +0,0 @@
// Shared
function updatePlayerCoins(updated) {
if (updated.coins) {
document.getElementById("user-coins-amount").innerText = updated.coins;
}
if (updated.procoins) {
document.getElementById("user-bux-amount").innerText = updated.procoins;
}
}
// 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, gambler } = 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");
}
updatePlayerCoins(gambler);
} 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
// Checking existing status onload.
// 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/status");
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 {
updateBlackjackActions(null);
}
} else {
console.error("error");
}
}
// DOM Manipulation
// 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 `
<button
type="button"
class="btn btn-${fullWidth ? "primary" : "secondary"} lottery-page--action"
id="${id}"
onclick="${method}()"
style="${fullWidth ? "width: 100%;" : ""}"
>
${title}
</button>
`;
}
function clearBlackjackActions() {
const actionWrapper = document.getElementById("casinoBlackjackActions");
actionWrapper.innerHTML = "";
}
function updateBlackjackActions(state) {
const actionWrapper = document.getElementById("casinoBlackjackActions");
clearBlackjackActions();
if (state && 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.
showWagerWidget();
const deal = buildBlackjackAction(
"casinoBlackjackDeal",
"dealBlackjack",
"Deal",
true
);
actionWrapper.innerHTML = deal;
}
}
function hideWagerWidget() {
document.getElementById("casinoBlackjackBet").disabled = true;
document.getElementById("casinoBlackjackDeal").disabled = true;
document.getElementById("casinoBlackjackWager").style.display = "none";
document.getElementById("casinoBlackjackResult").style.visibility = "hidden";
}
function showWagerWidget() {
document.getElementById("casinoBlackjackWager").style.display = "flex";
document.getElementById("casinoBlackjackBet").disabled = false;
}
// Actions, Requests & Responses
function takeBlackjackAction(action, args = {}) {
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);
for (const [key, value] of Object.entries(args)) {
form.append(key, value);
}
xhr.send(form);
}
const dealBlackjack = () => {
const wager = document.getElementById("casinoBlackjackBet").value;
const currency = document.querySelector('input[name="casinoBlackjackCurrency"]:checked').value;
hideWagerWidget();
takeBlackjackAction("deal", { currency, wager });
}
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", "text-warning");
if (succeeded) {
if (response.game_state) {
updateBlackjack(response.game_state);
}
if (response.gambler) {
updatePlayerCoins(response.gambler);
}
} else {
blackjackResult.style.visibility = "visible";
blackjackResult.innerText = response.error;
blackjackResult.classList.add("text-danger");
console.error(response.error);
}
}

View File

@ -1,7 +1,5 @@
from sqlalchemy import *
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
import time

View File

@ -4,6 +4,7 @@ import pyotp
from files.helpers.discord import remove_user
from files.helpers.media import *
from files.helpers.const import *
from files.helpers.twentyone import get_active_twentyone_game_state
from files.helpers.sorting_and_time import *
from .alts import Alt
from .saves import *
@ -892,4 +893,9 @@ class User(Base):
if self.truecoins >= 5000: return True
if self.agendaposter: return True
if self.patron: return True
return False
return False
@property
@lazy
def active_blackjack_game(self):
return json.dumps(get_active_twentyone_game_state(self))

View File

@ -1,327 +0,0 @@
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 files.classes.user import User
from flask import g
deck_count = 4
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("S", "H", "C", "D")
minimum_bet = 5
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,
Casino_Game.kind == 'blackjack',
Casino_Game.user_id == gambler.id).one_or_none()
if game:
game_state = json.loads(game.game_state)
return game, game_state, get_safe_game_state(game_state)
else:
return None, None, None
def get_safe_game_state(game_state):
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']
}
def apply_blackjack_result(gambler):
game, game_state, _ = get_active_game(gambler)
if game:
result = game_state['status']
if result == 'push' or result == 'insured_loss':
reward = game.wager
elif result == 'won':
reward = game.wager * 2
elif result == 'blackjack':
reward = floor(game.wager * 5/2)
else:
reward = 0
if reward:
currency_value = int(getattr(gambler, game.currency, 0))
setattr(gambler, game.currency, currency_value + reward)
gambler.winnings += reward
game.winnings += reward
game.active = False
g.db.add(game)
# region Actions
def gambler_dealt(gambler, currency, wager):
existing_game, _, _ = get_active_game(gambler)
if not existing_game:
over_min = wager >= minimum_bet
under_max = wager <= maximum_bet
using_dramacoin = currency == "dramacoin"
using_marseybux = not using_dramacoin
has_proper_funds = (using_dramacoin and gambler.coins >= wager) or (
using_marseybux and gambler.procoins >= wager)
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):
# Start the game.
build_game(gambler, currency_prop, wager)
game, game_state, safe_state = get_active_game(gambler)
player_value = get_hand_value(game_state['player'])
dealer_value = get_hand_value(game_state['dealer'])
# Charge the gambler for the game, reduce their winnings.
setattr(gambler, currency_prop, currency_value - wager)
gambler.winnings -= wager
game.winnings -= wager
# In two cases, the game is instantly over.
instantly_over = False
if player_value == 21 and dealer_value == 21:
instantly_over = True
game_state["status"] = 'push'
save_game_state(game, game_state)
apply_blackjack_result(gambler)
elif player_value == 21:
instantly_over = True
game_state["status"] = 'blackjack'
save_game_state(game, game_state)
apply_blackjack_result(gambler)
g.db.flush()
if instantly_over:
return True, game_state
else:
return True, safe_state
def gambler_hit(gambler):
game, game_state, safe_state = get_active_game(gambler)
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:
_, _, safe_state = get_active_game(gambler)
return True, safe_state
else:
return False, safe_state
def gambler_stayed(gambler):
game, game_state, safe_state = get_active_game(gambler)
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, safe_state
def gambler_doubled_down(gambler):
game, game_state, safe_state = get_active_game(gambler)
if game and not game_state['doubled_down']:
currency_value = getattr(gambler, game.currency, 0)
if (currency_value < game.wager):
return False, game_state
setattr(gambler, game.currency, currency_value - game.wager)
gambler.winnings -= game.wager
game.winnings -= 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, safe_state
def gambler_purchased_insurance(gambler):
game, game_state, safe_state = get_active_game(gambler)
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)
gambler.winnings -= insurance_cost
game.winnings -= insurance_cost
game_state['insurance'] = True
game_state['actions'] = determine_actions(game_state)
save_game_state(game, game_state)
_, _, safe_state = get_active_game(gambler)
return True, safe_state
else:
return False, safe_state
# endregion
# region Utilities
def shuffle(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
def get_card_value(card):
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 = []
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)
def purge_bad_games():
# If for whatever reason a game is marked as active but has concluded, this will clear it up.
games = g.db.query(Casino_Game) \
.filter(Casino_Game.active == True,
Casino_Game.kind == 'blackjack').all()
for game in games:
game_state = json.loads(game.game_state)
if (game_state['status'] != "active"):
game.active = False
g.db.add(game)
# Victims of this status should have their currency refunded.
user = g.db.query(User).filter(User.id == game.user_id).one()
user.winnings += game.wager
currencyBeforeFix = getattr(user, game.currency, 0)
setattr(user, game.currency, currencyBeforeFix + game.wager)
g.db.add(user)
g.db.commit()
# endregion

View File

@ -0,0 +1,84 @@
from files.__main__ import app, limiter, db_session
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 *
def get_game_feed(game):
games = g.db.query(Casino_Game) \
.filter(Casino_Game.active == False, Casino_Game.kind == game) \
.order_by(Casino_Game.created_utc.desc()).limit(30).all()
def format_game(game):
user = g.db.query(User).filter(User.id == game.user_id).one()
wonlost = 'lost' if game.winnings < 0 else 'won'
return {
"user": user.username,
"won_or_lost": wonlost,
"amount": abs(game.winnings),
"currency": game.currency
}
return list(map(format_game, games))
def get_game_leaderboard(game):
timestamp_24h_ago = time.time() - 86400
biggest_win_all_time = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.desc()).filter(Casino_Game.kind == game).limit(1).one_or_none()
biggest_win_last_24h = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.desc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_24h_ago).limit(1).one_or_none()
biggest_loss_all_time = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.asc()).filter(Casino_Game.kind == game).limit(1).one_or_none()
biggest_loss_last_24h = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.asc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_24h_ago).limit(1).one_or_none()
if not biggest_win_all_time:
biggest_win_all_time = [None, None, None, 0]
if not biggest_win_last_24h:
biggest_win_last_24h = [None, None, None, 0]
if not biggest_loss_all_time:
biggest_loss_all_time = [None, None, None, 0]
if not biggest_loss_last_24h:
biggest_loss_last_24h = [None, None, None, 0]
return {
"all_time": {
"biggest_win": {
"user": biggest_win_all_time[1],
"currency": biggest_win_all_time[2],
"amount": biggest_win_all_time[3]
},
"biggest_loss": {
"user": biggest_loss_all_time[1],
"currency": biggest_loss_all_time[2],
"amount": abs(biggest_loss_all_time[3])
}
},
"last_24h": {
"biggest_win": {
"user": biggest_win_last_24h[1],
"currency": biggest_win_last_24h[2],
"amount": biggest_win_last_24h[3]
},
"biggest_loss": {
"user": biggest_loss_last_24h[1],
"currency": biggest_loss_last_24h[2],
"amount": abs(biggest_loss_last_24h[3])
}
}
}
def get_game_stats_for_player(player):
pass

View File

@ -18,24 +18,19 @@ payout_to_symbols = {
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)
currency_value = getattr(gambler, currency)
has_proper_funds = currency_value >= wager_value
if (over_min and under_max and has_proper_funds):
setattr(gambler, currency_prop, currency_value - wager_value)
setattr(gambler, currency, currency_value - wager_value)
gambler.winnings -= wager_value
payout = determine_payout()
reward = wager_value * payout
currency_value = getattr(gambler, currency_prop, 0)
setattr(gambler, currency_prop, currency_value + reward)
currency_value = getattr(gambler, currency, 0)
setattr(gambler, currency, currency_value + reward)
gambler.winnings += reward
symbols = build_symbols(payout)
text = build_text(wager_value, payout, currency)
@ -46,7 +41,7 @@ def casino_slot_pull(gambler, wager_value, currency):
casino_game = Casino_Game()
casino_game.active = False
casino_game.user_id = gambler.id
casino_game.currency = currency_prop
casino_game.currency = currency
casino_game.wager = wager_value
casino_game.winnings = reward - wager_value
casino_game.kind = 'slots'

View File

@ -0,0 +1,378 @@
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
class BlackjackStatus(str, Enum):
PLAYING = "PLAYING"
STAYED = "STAYED"
PUSHED = "PUSHED"
WON = "WON"
LOST = "LOST"
BLACKJACK = "BLACKJACK"
class BlackjackAction(str, Enum):
DEAL = "DEAL"
HIT = "HIT"
STAY = "STAY"
DOUBLE_DOWN = "DOUBLE_DOWN"
BUY_INSURANCE = "BUY_INSURANCE"
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("S", "H", "C", "D")
deck = [rank + suit for rank in ranks for suit in suits]
deck_count = 4
minimum_bet = 5
def get_initial_state():
return {
"player": [],
"player_value": 0,
"dealer": [],
"dealer_value": 0,
"player_bought_insurance": False,
"player_doubled_down": False,
"status": BlackjackStatus.PLAYING,
"actions": [BlackjackAction.DEAL],
"wager": {
"amount": 0,
"currency": "coins"
},
"payout": 0
}
def build_casino_game(gambler, wager, currency):
initial_state = get_initial_state()
initial_state['wager']['amount'] = wager
initial_state['wager']['currency'] = currency
casino_game = Casino_Game()
casino_game.user_id = gambler.id
casino_game.currency = currency
casino_game.wager = wager
casino_game.winnings = 0
casino_game.kind = 'blackjack'
casino_game.game_state = json.dumps(initial_state)
casino_game.active = True
g.db.add(casino_game)
return casino_game
def get_active_twentyone_game(gambler):
return g.db.query(Casino_Game).filter(
Casino_Game.active == True,
Casino_Game.kind == 'blackjack',
Casino_Game.user_id == gambler.id).one_or_none()
def get_active_twentyone_game_state(gambler):
active_game = get_active_twentyone_game(gambler)
full_state = json.loads(active_game.game_state)
return remove_exploitable_information(full_state)
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 create_new_game(gambler, wager, currency):
existing_game = get_active_twentyone_game(gambler)
over_minimum_bet = wager >= minimum_bet
if existing_game:
raise Exception("Gambler already has a game in progress.")
if not over_minimum_bet:
raise Exception(f"Gambler must bet over {minimum_bet} {currency}.")
try:
charge_gambler(gambler, wager, currency)
new_game = build_casino_game(gambler, wager, currency)
g.db.add(new_game)
g.db.commit()
except:
raise Exception(f"Gambler cannot afford to bet {wager} {currency}.")
def handle_blackjack_deal(state):
deck = build_deck(state)
first = deck.pop()
second = deck.pop()
third = deck.pop()
fourth = deck.pop()
state['player'] = [first, third]
state['dealer'] = [second, fourth]
return state
def handle_blackjack_hit(state):
deck = build_deck(state)
next_card = deck.pop()
state['player'].append(next_card)
return state
def handle_blackjack_stay(state):
state['status'] = BlackjackStatus.STAYED
return state
def handle_blackjack_double_down(state):
state['player_doubled_down'] = True
state = handle_blackjack_hit(state)
state = handle_blackjack_stay(state)
return state
def handle_blackjack_buy_insurance(state):
state['player_bought_insurance'] = True
return state
def check_for_completion(state):
after_initial_deal = len(
state['player']) == 2 and len(state['dealer']) == 2
player_hand_value = get_value_of_hand(state['player'])
dealer_hand_value = get_value_of_hand(state['dealer'])
# Both player and dealer were initially dealt 21: Push.
if after_initial_deal and player_hand_value == 21 and dealer_hand_value == 21:
state['status'] = BlackjackStatus.PUSHED
return True, state
# Player was originally dealt 21, dealer was not: Blackjack.
if after_initial_deal and player_hand_value == 21:
state['status'] = BlackjackStatus.BLACKJACK
return True, state
# Player went bust: Lost.
if player_hand_value == -1:
state['status'] = BlackjackStatus.LOST
return True, state
# Player chose to stay: Deal rest for dealer then determine winner.
if state['status'] == BlackjackStatus.STAYED:
deck = build_deck(state)
while dealer_hand_value < 17 and dealer_hand_value != -1:
next_card = deck.pop()
state['dealer'].append(next_card)
dealer_hand_value = get_value_of_hand(state['dealer'])
if player_hand_value > dealer_hand_value or dealer_hand_value == -1:
state['status'] = BlackjackStatus.WON
elif dealer_hand_value > player_hand_value:
state['status'] = BlackjackStatus.LOST
else:
state['status'] = BlackjackStatus.PUSHED
state['player_value'] = get_value_of_hand(state['player'])
state['dealer_value'] = get_value_of_hand(state['dealer'])
return True, state
return False, state
def does_insurance_apply(state):
dealer = state['dealer']
dealer_hand_value = get_value_of_hand(dealer)
dealer_first_card_ace = dealer[0][0] == 'A'
dealer_never_hit = len(dealer) == 2
return dealer_hand_value == 21 and dealer_first_card_ace and dealer_never_hit
def can_purchase_insurance(state):
dealer = state['dealer']
dealer_first_card_ace = dealer[0][0] == 'A'
dealer_never_hit = len(dealer) == 2
return dealer_first_card_ace and dealer_never_hit and not state['player_bought_insurance']
def can_double_down(state):
player = state['player']
player_hand_value = get_value_of_hand(player)
player_never_hit = len(player) == 2
return player_hand_value in (10, 11) and player_never_hit
def handle_payout(gambler, state, game):
status = state['status']
payout = 0
if status == BlackjackStatus.BLACKJACK:
game.winnings = floor(game.wager * 3/2)
payout = game.wager + game.winnings
elif status == BlackjackStatus.WON:
game.winnings = game.wager
payout = game.wager * 2
elif status == BlackjackStatus.LOST:
dealer = state['dealer']
dealer_first_card_ace = dealer[0][0] == 'A'
dealer_never_hit = len(dealer) == 2
dealer_hand_value = get_value_of_hand(dealer) == 21
insurance_applies = dealer_hand_value == 21 and dealer_first_card_ace and dealer_never_hit
if insurance_applies and state['player_bought_insurance']:
game.winnings = 0
payout = game.wager
else:
game.winnings = -game.wager
payout = 0
elif status == BlackjackStatus.PUSHED:
game.winnings = 0
payout = game.wager
else:
raise Exception("Attempted to payout a game that has not finished.")
currency_gambler_holds = getattr(gambler, game.currency)
setattr(gambler, game.currency, currency_gambler_holds + payout)
game.active = False
g.db.add(game)
g.db.add(gambler)
return payout
def remove_exploitable_information(state):
safe_state = state
safe_state['dealer'][1] = '?'
safe_state['dealer_value'] = '?'
return safe_state
action_handlers = {
BlackjackAction.DEAL: handle_blackjack_deal,
BlackjackAction.HIT: handle_blackjack_hit,
BlackjackAction.STAY: handle_blackjack_stay,
BlackjackAction.DOUBLE_DOWN: handle_blackjack_double_down,
BlackjackAction.BUY_INSURANCE: handle_blackjack_buy_insurance,
}
def dispatch_action(gambler, action):
game = get_active_twentyone_game(gambler)
handler = action_handlers[action]
if not game:
raise Exception(
'Gambler has no active blackjack game.')
if not handler:
raise Exception(
f'Illegal action {action} passed to Blackjack#dispatch_action.')
state = json.loads(game.game_state)
if action == BlackjackAction.BUY_INSURANCE:
if not can_purchase_insurance(state):
raise Exception("Insurance cannot be purchased.")
charge_gambler(gambler, floor(game.wager / 2), game.currency)
if action == BlackjackAction.DOUBLE_DOWN:
if not can_double_down(state):
raise Exception("Cannot double down.")
charge_gambler(gambler, game.wager, game.currency)
game.wager *= 2
new_state = handler(state)
new_state['player_value'] = get_value_of_hand(new_state['player'])
new_state['dealer_value'] = get_value_of_hand(new_state['dealer'])
new_state['actions'] = get_available_actions(new_state)
game.game_state = json.dumps(new_state)
g.db.add(game)
game_over, final_state = check_for_completion(new_state)
if game_over:
payout = handle_payout(gambler, final_state, game)
final_state['actions'] = [BlackjackAction.DEAL]
final_state['payout'] = payout
return final_state
else:
safe_state = remove_exploitable_information(new_state)
return safe_state
def shuffle(collection):
random.shuffle(collection)
return collection
def build_deck(state):
card_counts = {}
for card in deck:
card_counts[card] = deck_count
cards_already_dealt = state['player'].copy()
cards_already_dealt.extend(state['dealer'].copy())
for card in cards_already_dealt:
card_counts[card] = card_counts[card] - 1
deck_without_already_dealt_cards = []
for card in deck:
amount = card_counts[card]
for _ in range(amount):
deck_without_already_dealt_cards.append(card)
return shuffle(deck_without_already_dealt_cards)
def get_value_of_card(card):
rank = card[0]
return 0 if rank == "A" else min(ranks.index(rank) + 2, 10)
def get_value_of_hand(hand):
without_aces = sum(map(get_value_of_card, 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)
return max(possibilities)
def get_available_actions(state):
actions = []
if state['status'] == BlackjackStatus.PLAYING:
actions.append(BlackjackAction.HIT)
actions.append(BlackjackAction.STAY)
if can_double_down(state):
actions.append(BlackjackAction.DOUBLE_DOWN)
if can_purchase_insurance(state):
actions.append(BlackjackAction.BUY_INSURANCE)
return actions

View File

@ -1,140 +1,148 @@
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 *
from files.helpers.casino import *
from files.helpers.twentyone import *
@app.get("/casino")
@auth_required
def casino(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
if v.rehab: return render_template("casino/rehab.html", v=v)
return render_template("casino.html", v=v)
@app.get("/lottershe")
@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)
participants = get_users_participating_in_lottery()
return render_template("casino.html", v=v, participants=participants)
@app.get("/casino/<game>")
@auth_required
def casino_game_page(v, game):
if v.rehab: return {"error": "You are under Rehab award effect!"}
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))
return render_template(
f"casino/{game}_screen.html",
v=v,
game=game
)
return render_template(
f"casino/{game}_screen.html",
v=v,
game=game,
feed=feed,
leaderboard=leaderboard
)
@app.get("/casino/<game>/feed")
@limiter.limit("1/second;50/minute;600/hour;12000/day")
@auth_required
def casino_game_feed(v, game):
feed = get_game_feed(game)
return {"feed": feed}
@app.post("/casino/slots")
@limiter.limit("3/second;30/minute;600/hour;12000/day")
@auth_required
def pull_slots(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
if v.rehab: return {"error": "You are under Rehab award effect!"}
try:
wager = int(request.values.get("wager"))
except:
return {"error": "Invalid wager."}
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')."}
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."}
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)
success, game_state = casino_slot_pull(v, wager, currency)
if success:
return {"game_state": game_state, "gambler": { "coins": v.coins, "procoins": v.procoins }}
else:
return {"error": f"Wager must be more than 5 {currency}."}
if success:
return {"game_state": game_state, "gambler": {"coins": v.coins, "procoins": v.procoins}}
else:
return {"error": f"Wager must be more than 5 {currency}."}
@app.get("/casino/blackjack/status")
@limiter.limit("3/second;30/minute;600/hour;12000/day")
@app.post("/casino/twentyone/deal")
@auth_required
def get_player_blackjack_status(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
def blackjack_deal_to_player(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
game, _, safe_state = get_active_game(v)
try:
wager = int(request.values.get("wager"))
currency = request.values.get("currency")
create_new_game(v, wager, currency)
state = dispatch_action(v, BlackjackAction.DEAL)
feed = get_game_feed('blackjack')
if game:
return { "active": True, "game_state": safe_state }
else:
return { "active": False }
return {"success": True, "state": state, "feed": feed}
except Exception as error:
print(error)
return {"error": "Unable to deal a new game."}
@app.post("/casino/blackjack/action")
@limiter.limit("3/second;30/minute;600/hour;12000/day")
@app.post("/casino/twentyone/hit")
@auth_required
def player_took_blackjack_action(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
def blackjack_player_hit(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
try:
state = dispatch_action(v, BlackjackAction.HIT)
feed = get_game_feed('blackjack')
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except:
return {"error": "Unable to hit."}
try:
action = request.values.get("action")
except:
return { "error": "Invalid action." }
was_successful = False
state = None
if action == 'deal':
try:
currency = request.values.get("currency")
wager = int(request.values.get("wager"))
except:
return { "error": "Missing either currency or wager values." }
existing_game, _, _ = get_active_game(v)
if (currency == "dramacoin" and wager > v.coins) or (currency == "marseybux" and wager > v.procoins):
return {"error": f"Not enough {currency} to make that bet."}
elif existing_game:
return { "error": "Cannot start a new game while an existing game persists." }
else:
deal = gambler_dealt(v, currency, wager)
if not deal: return { "error": "Cannot start a new game while an existing game persists." }
success, game_state = deal
was_successful = success
state = game_state
elif 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,
"gambler": { "coins": v.coins, "procoins": v.procoins }
}
else:
return { "active": False }
@app.post("/casino/blackjack/purge")
@app.post("/casino/twentyone/stay")
@auth_required
def fix_blackjack_games(v):
if v.admin_level < 3:
return { "success": False, "error": "Insufficient permissions." }
else:
purge_bad_games()
return { "success": True, "message": "Successfully purged bad blackjack games." }
def blackjack_player_stay(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
try:
state = dispatch_action(v, BlackjackAction.STAY)
feed = get_game_feed('blackjack')
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except:
return {"error": "Unable to stay."}
@app.post("/casino/twentyone/double-down")
@auth_required
def blackjack_player_doubled_down(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
try:
state = dispatch_action(v, BlackjackAction.DOUBLE_DOWN)
feed = get_game_feed('blackjack')
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except:
return {"error": "Unable to double down."}
@app.post("/casino/twentyone/buy-insurance")
@auth_required
def blackjack_player_bought_insurance(v):
if v.rehab: return {"error": "You are under Rehab award effect!"}
try:
state = dispatch_action(v, BlackjackAction.BUY_INSURANCE)
feed = get_game_feed('blackjack')
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except:
return {"error": "Unable to buy insurance."}

View File

@ -4,7 +4,6 @@ from files.helpers.media import *
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.slots import *
from files.helpers.blackjack import *
from files.helpers.treasure import *
from files.helpers.actions import *
from files.helpers.get import *
@ -329,11 +328,6 @@ def comment(v):
g.db.add(c)
g.db.flush()
if blackjack and any(i in c.body.lower() for i in blackjack.split()):
v.shadowbanned = 'AutoJanny'
notif = Notification(comment_id=c.id, user_id=CARP_ID)
g.db.add(notif)
if c.level == 1: c.top_comment_id = c.id
else: c.top_comment_id = parent.top_comment_id
@ -693,14 +687,6 @@ def edit_comment(cid, v):
c.body = body[:10000]
c.body_html = body_html
if blackjack and any(i in c.body.lower() for i in blackjack.split()):
v.shadowbanned = 'AutoJanny'
g.db.add(v)
notif = g.db.query(Notification).filter_by(comment_id=c.id, user_id=CARP_ID).one_or_none()
if not notif:
notif = Notification(comment_id=c.id, user_id=CARP_ID)
g.db.add(notif)
if v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower() and c.post.sub != 'chudrama':
return {"error": f'You have to include "{AGENDAPOSTER_PHRASE}" in your comment!'}, 403

View File

@ -1,244 +1,43 @@
{% extends "default.html" %} {% block content %}
<link rel="stylesheet" href="/assets/css/casino.css?v=4000" />
<script defer src="/assets/js/casino.js?v=4001"></script>
{% extends "default.html" %}
<!-- New -->
<div class="casino-games">
<!-- Slots -->
<div id="slots-block" class="casino-block">
<div class="casino-block-title">
Slots
<hr style="flex: 1; margin-left: 1rem" />
</div>
<div class="casino-block-inner">
<div class="casino-block-left">
<!-- Game -->
<div class="casino-block-game">
<div>
<div class="casino-slots-results" style="flex: 1">
<div class="reel">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" />
</div>
<div class="reel">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" />
</div>
<div class="reel">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" />
</div>
</div>
<div class="casino-slots-outcome" id="casinoSlotsResult">
&nbsp;
</div>
</div>
</div>
<!-- Bet -->
<div class="casino-block-bet">
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys" style="margin-right: 1rem">
<div>Enter Bet</div>
<div>
<input
id="casinoSlotsBet"
class="form-control"
autocomplete="off"
value="5"
min="5"
step="1"
aria-label="Bet"
name="casinoSlotsBet"
type="number"
style="flex: 1; max-width: 200px; text-align: right"
/>
</div>
</div>
{# Title (~25char max), Description (~80char max),
Icon (fa-foo-bar), Color (#ff0000), URL (/post/12345/) #}
{%- set GAME_INDEX = [] -%}
<div class="lottery-page--stat-values">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="casinoSlotsCurrency"
id="casinoSlotsCurrencyDramacoin"
value="dramacoin"
checked
/>
<label
class="form-check-label"
for="casinoSlotsCurrencyDramacoin"
>
<img
src="/i/rDrama/coins.webp?v=3009"
alt="coin"
width="40"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Dramacoin"
aria-label="Dramacoin"
/>
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="casinoSlotsCurrency"
id="casinoSlotsCurrencyMarseybux"
value="marseybux"
/>
<label
class="form-check-label"
for="casinoSlotsCurrencyMarseybux"
>
<img
src="/i/marseybux.webp?v=2000"
alt="marseybux"
width="40"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Marseybux"
aria-label="Marseybux"
/>
</label>
</div>
</div>
</div>
{%- if SITE_NAME == 'rDrama' -%}
{%- do GAME_INDEX.extend([
(
'Slots',
'Today\'s your lucky day',
'fa-dollar-sign', '#666',
'/casino/slots',
),
(
'Blackjack',
'Twenty one ways to change your life',
'fa-cards', '#333',
'/casino/blackjack',
),
(
'Lottershe',
'Can\'t win if you don\'t play.',
'fa-ticket', '#888',
'/lottershe',
)
])-%}
{%- endif -%}
<button
type="button"
class="btn btn-success lottery-page--action"
id="casinoSlotsPull"
style="width: 100%"
onclick="pullSlots()"
>
Pull
</button>
{% block content %}
<h2 style="text-transform: uppercase; text-align: center; letter-spacing: 3px; margin-top: 2rem;">rDrama.net Casino</h2>
<div id="directory--wrapper">
{% for game in GAME_INDEX %}
<a role="button" class="directory--link" href="{{game[4]}}">
<div class="directory--link-content">
<i class="directory--link--icon fas {{game[2]}}" style="color:{{game[3]}}"></i>
<div class="directory--link--title">{{game[0]|safe}}</div>
<div class="directory--link--description">{{game[1]|safe}}</div>
</div>
</div>
</div>
</div>
<!-- Blackjack -->
<div id="blackjack-block" class="casino-block">
<div class="casino-block-title">
Blackjack
<hr style="flex: 1; margin-left: 1rem" />
</div>
<div class="casino-block-inner">
<div class="casino-block-left">
<!-- Game -->
<div class="casino-block-game">
<div class="casino-game">
<div style="flex: 1">
<div class="blackjack-table">
<div style="display: flex; align-items: center">
<small style="margin-right: 0.5rem">Dealer</small>
<hr style="flex: 1" />
</div>
<div class="hand" id="casinoBlackjackDealerHand">
<div class="playing-card" data-who="dealer"></div>
<div class="playing-card" data-who="dealer"></div>
<div class="playing-card" data-who="dealer"></div>
<div class="playing-card" data-who="dealer"></div>
<div class="playing-card" data-who="dealer"></div>
</div>
<hr />
<div class="hand" id="casinoBlackjackPlayerHand">
<div class="playing-card" data-who="player"></div>
<div class="playing-card" data-who="player"></div>
<div class="playing-card" data-who="player"></div>
<div class="playing-card" data-who="player"></div>
<div class="playing-card" data-who="player"></div>
</div>
<div style="display: flex; align-items: center">
<hr style="flex: 1; margin-right: 0.5rem" />
<small>Player</small>
</div>
</div>
<div
id="casinoBlackjackResult"
class="casino-blackjack-outcome"
></div>
</div>
</div>
</div>
<!-- Bet -->
<div class="casino-block-bet">
<div id="casinoBlackjackWager" class="lottery-page--stat">
<div class="lottery-page--stat-keys" style="margin-right: 1rem">
<div>Enter Bet</div>
<div>
<input
id="casinoBlackjackBet"
class="form-control"
autocomplete="off"
value="5"
min="5"
step="1"
aria-label="Bet"
name="casinoBlackjackBet"
type="number"
style="flex: 1; max-width: 200px; text-align: right"
/>
</div>
</div>
<div class="lottery-page--stat-values">
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="casinoBlackjackCurrency"
id="casinoBlackjackCurrencyDramacoin"
value="dramacoin"
checked
/>
<label
class="form-check-label"
for="casinoBlackjackCurrencyDramacoin"
>
<img
src="/i/rDrama/coins.webp?v=3009"
alt="coin"
width="40"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Dramacoin"
aria-label="Dramacoin"
/>
</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="casinoBlackjackCurrency"
id="casinoBlackjackCurrencyMarseybux"
value="marseybux"
/>
<label
class="form-check-label"
for="casinoBlackjackCurrencyMarseybux"
>
<img
src="/i/marseybux.webp?v=2000"
alt="marseybux"
width="40"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Marseybux"
aria-label="Marseybux"
/>
</label>
</div>
</div>
</div>
<div id="casinoBlackjackActions" class="casino-blackjack-actions">
</div>
</div>
</div>
</div>
</div>
<div class="casino-lottery">{% include "lottery.html" %}</div>
{% endblock %}
</a>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,304 @@
{% extends "casino/game_screen.html" %}
{% block script %}
<script>
function makeBlackjackRequest(action) {
const xhr = new XMLHttpRequest();
xhr.open("post", `/casino/twentyone/${action}`);
xhr.onload = handleBlackjackResponse.bind(null, xhr);
xhr.blackjackAction = action;
return xhr;
}
function handleBlackjackResponse(xhr) {
try {
const response = JSON.parse(xhr.response);
const succeeded = xhr.status >= 200 &&
xhr.status < 300 &&
response &&
!response.error;
clearResult();
if (succeeded) {
updateBlackjackTable(response.state);
updateFeed(response.feed);
} else {
console.error("Error: ", response.error);
throw new Error("Error")
}
} catch (error) {
const results = {
deal: "Unable to deal a new hand. Is one in progress?",
hit: "Unable to hit.",
stay: "Unable to stay.",
"double-down": "Unable to double down.",
"buy-insurance": "Unable to buy insurance."
};
updateResult(results[xhr.blackjackAction], "danger");
}
}
function updateBlackjackActions(state) {
const actions = Array.from(document.querySelectorAll('.twentyone-btn'));
// Hide all actions.
actions.forEach(action => action.style.display = 'none');
if (state) {
// Show the correct ones.
state.actions.forEach(action => document.getElementById(`twentyone-${action}`).style.display = 'inline-block');
} else {
const dealButton = document.getElementById(`twentyone-DEAL`);
setTimeout(() => {
const dealButton = document.getElementById(`twentyone-DEAL`);
})
if (dealButton) {
dealButton.style.display = 'inline-block'
}
}
}
function updateBlackjackTable(state) {
const table = document.getElementById('blackjack-table');
const charactersToRanks = {
X: "10"
};
const charactersToSuits = {
S: "♠️",
H: "♥️",
C: "♣️",
D: "♦️",
};
const makeCardset = (from, who, value) => `
<div class="blackjack-cardset">
<div class="blackjack-cardset-value">
${value === -1 ? `${who} went bust` : `${who} has ${value}`}
</div>
${from
.filter(card => card !== "?")
.map(([rankCharacter, suitCharacter]) => {
const rank = charactersToRanks[rankCharacter] || rankCharacter;
const suit = charactersToSuits[suitCharacter] || suitCharacter;
return buildPlayingCard(rank, suit);
})
.join('')}
</div>
`;
const dealerCards = makeCardset(state.dealer, 'Dealer', state.dealer_value);
const playerCards = makeCardset(state.player, 'Player', state.player_value);
updateBlackjackActions(state);
table.innerHTML = `
<div style="position: relative;">
${dealerCards}
</div>
${playerCards}
`;
const currency = state.wager.currency === 'coins' ? 'dramacoins' : 'marseybux';
switch (state.status) {
case 'BLACKJACK':
updateResult(`Blackjack: Received ${state.payout} ${currency}`, "warning");
break;
case 'WON':
updateResult(`Won: Received ${state.payout} ${currency}`, "success");
break;
case 'PUSHED':
updateResult(`Pushed: Received ${state.wager.amount} ${currency}`, "success");
break;
case 'LOST':
updateResult(`Lost ${state.wager.amount} ${currency}`, "danger");
break;
default:
break;
}
updateCardsetBackgrounds(state);
if (state.status === 'PLAYING') {
updateResult(`${state.wager.amount} ${currency} are at stake`, "success");
} else {
enableWager();
}
}
function updateCardsetBackgrounds(state) {
const cardsets = Array.from(document.querySelectorAll('.blackjack-cardset'));
for (const cardset of cardsets) {
['PLAYING', 'LOST', 'PUSHED', 'WON', 'BLACKJACK'].forEach(status => cardset.classList.remove(`blackjack-cardset__${status}`));
cardset.classList.add(`blackjack-cardset__${state.status}`)
}
}
function deal() {
const request = makeBlackjackRequest('deal');
const { amount, currency } = getWager();
const form = new FormData();
form.append("formkey", formkey());
form.append("wager", amount);
form.append("currency", currency);
request.send(form);
clearResult();
disableWager();
drawFromDeck();
}
function hit() {
const request = makeBlackjackRequest('hit');
const form = new FormData();
form.append("formkey", formkey());
request.send(form);
drawFromDeck();
}
function stay() {
const request = makeBlackjackRequest('stay');
const form = new FormData();
form.append("formkey", formkey());
request.send(form);
}
function doubleDown() {
const request = makeBlackjackRequest('double-down');
const form = new FormData();
form.append("formkey", formkey());
request.send(form);
drawFromDeck();
}
function buyInsurance() {
const request = makeBlackjackRequest('buy-insurance');
const form = new FormData();
form.append("formkey", formkey());
request.send(form);
}
function buildBlackjackDeck() {
document.getElementById('blackjack-table-deck').innerHTML = `
<div style="position: absolute; top: 150px; left: -100px;">
${buildPlayingCardDeck()}
</div>
`;
}
function initializeBlackjack() {
buildBlackjackDeck();
try {
const passed = document.getElementById('blackjack-table').dataset.state;
const state = JSON.parse(passed);
updateBlackjackTable(state);
} catch (error) {
updateBlackjackActions();
}
}
if (
document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)
) {
initializeBlackjack();
} else {
document.addEventListener("DOMContentLoaded", initializeBlackjack);
}
</script>
{% endblock %}
{% block screen %}
<div id="blackjack-table-deck"></div>
<div id="blackjack-table" data-state="{{v.active_blackjack_game}}" style="position: relative;">
</div>
{% endblock %}
{% block actions %}
<style>
.blackjack-cardset {
position: relative;
display: flex;
align-items: center;
margin-bottom: 1rem;
max-width: 470px;
overflow: auto;
box-shadow: 1px 1px 5px 1px rgba(60, 60, 60, 0.81) inset;
-webkit-box-shadow: 1px 1px 5px 1px rgba(60, 60, 60, 0.81) inset;
-moz-box-shadow: 1px 1px 5px 1px rgba(60, 60, 60, 0.81) inset;
}
.blackjack-cardset__PLAYING {
background-image: radial-gradient(circle farthest-corner at 10% 20%, rgba(14, 174, 87, 1) 0%, rgba(12, 116, 117, 1) 90%);
}
.blackjack-cardset__LOST {
background-image: linear-gradient(109.6deg, rgba(14, 11, 56, 1) 11.2%, rgba(239, 37, 37, 1) 91.1%);
}
.blackjack-cardset__PUSHED {
background-image: linear-gradient(110.3deg, rgba(73, 93, 109, 1) 4.3%, rgba(49, 55, 82, 1) 96.7%);
}
.blackjack-cardset__WON {
background-image: radial-gradient( circle farthest-corner at -0.6% 44.4%, rgba(142,252,152,1) 0%, rgba(107,214,250,1) 90% );
}
.blackjack-cardset__BLACKJACK {
background-image: linear-gradient(64.3deg, rgba(254, 122, 152, 0.81) 17.7%, rgba(255, 206, 134, 1) 64.7%, rgba(172, 253, 163, 0.64) 112.1%);
}
.blackjack-cardset .playing-card {
margin-right: -3rem;
min-width: 100px;
}
.blackjack-cardset-value {
z-index: 3;
top: 0;
right: 0;
text-transform: uppercase;
letter-spacing: 2px;
text-align: right;
position: absolute;
background-color: rgba(70, 70, 70, 0.6);
padding: 0.5rem;
color: #DDD;
}
.twentyone-btn {
margin-right: 1rem;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
</style>
<div role="group" class="btn-group">
<button id="twentyone-DEAL" class="btn btn-primary twentyone-btn" onclick="deal()">Deal</button>
<button id="twentyone-HIT" class="btn btn-primary twentyone-btn" onclick="hit()" style="display: none;">Hit</button>
<button id="twentyone-STAY" class="btn btn-primary twentyone-btn" onclick="stay()"
style="display: none;">Stay</button>
<button id="twentyone-DOUBLE_DOWN" class="btn btn-primary twentyone-btn" onclick="doubleDown()"
style="display: none;">Double Down</button>
<button id="twentyone-BUY_INSURANCE" class="btn btn-primary twentyone-btn" onclick="buyInsurance()"
style="display: none;">Buy
Insurance</button>
</div>
{% endblock %}
{% block leaders %}
Blackjack
{% endblock %}
{% block feed %}
Blackjack
{% endblock %}

View File

@ -0,0 +1,468 @@
{% extends "default.html" %} {% block title %}
<title>{{game}}</title>
{% endblock %} {% block content %}
<style>
.game_screen-title {
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
opacity: 0.6;
}
.game_screen-title hr {
flex: 1;
margin-left: 0.5rem;
}
#casinoGameResult {
visibility: hidden;
margin-top: 1rem;
}
#casinoGameFeedList {
max-height: 110px;
overflow: auto;
list-style-type: none;
}
</style>
<script type="text/javascript">
/**
* This script block contains generic helper function usable across casino games:
* - Wagers
* - Feed
* - Leaderboard
*/
if (
document.readyState === "complete" ||
(document.readyState !== "loading" && !document.documentElement.doScroll)
) {
initializeGame();
} else {
document.addEventListener("DOMContentLoaded", initializeGame);
}
function initializeGame() {
updateFeed();
updateLeaderboard();
}
function updatePlayerCurrencies(updated) {
if (updated.coins) {
document.getElementById("user-coins-amount").innerText = updated.coins;
}
if (updated.procoins) {
document.getElementById("user-bux-amount").innerText = updated.procoins;
}
}
function getWager() {
const amount = document.getElementById("wagerAmount").value;
const currency = document.querySelector(
'input[name="wagerCurrency"]:checked'
).value;
const genericCurrency = currency == 'marseybux' ? 'procoins' : 'coins';
return { amount, currency: genericCurrency };
}
function disableWager() {
document.getElementById("wagerAmount").disabled = true;
document.getElementById("wagerCoins").disabled = true;
document.getElementById("wagerProcoins").disabled = true;
}
function enableWager() {
document.getElementById("wagerAmount").disabled = false;
document.getElementById("wagerCoins").disabled = false;
document.getElementById("wagerProcoins").disabled = false;
}
function updateResult(text, className) {
const result = document.getElementById("casinoGameResult");
result.style.visibility = "visible";
result.innerText = text;
result.classList.add(`alert-${className}`);
}
function clearResult() {
const result = document.getElementById("casinoGameResult");
result.style.visibility = "hidden";
result.innerText = "N/A";
result.classList.remove("alert-success", "alert-danger", "alert-warning");
}
function updateFeed(newFeed) {
let feed;
if (newFeed) {
feed = newFeed;
} else {
const gameFeed = document.getElementById("casinoGameFeed");
feed = gameFeed.dataset.feed;
feed = JSON.parse(feed);
gameFeed.dataset.feed = "";
}
const feedHtml = feed
.map(
(entry) =>
`
<li
style="display: flex; align-items: center; justify-content: space-between;"
class="${entry.won_or_lost === "won" ? "text-success" : "text-danger"}">
<div>
<a href="/@${entry.user}">@${entry.user}</a> ${entry.won_or_lost} ${entry.amount
} ${entry.currency}
</div>
</li>
`
)
.join("");
document.getElementById("casinoGameFeedList").innerHTML = feedHtml;
}
function reloadFeed() {
const game = document.getElementById('casino-game-wrapper').dataset.game;
const xhr = new XMLHttpRequest();
xhr.open("get", `/casino/${game}/feed`);
xhr.onload = handleFeedResponse.bind(null, xhr);
xhr.send();
}
function handleFeedResponse(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) {
document.getElementById("casinoGameFeed").dataset.feed = JSON.stringify(response.feed);
updateFeed();
} else {
console.error("error");
}
}
function updateLeaderboard() {
const leaderboardContainer = document.getElementById("gameLeaderboard");
const leaderboardData = JSON.parse(leaderboardContainer.dataset.leaderboard);
const [biggestWinnerAllTime, biggestWinner24h, biggestLoser24h, biggestLoserAllTime] = [
'biggestWinnerAllTime', 'biggestWinner24h', 'biggestLoser24h', 'biggestLoserAllTime'
].map(id => document.getElementById(id));
const formatLocalCurrencyName = currency => ({ coins: 'dramacoins', procoins: 'marseybux' })[currency];
biggestWinnerAllTime.innerHTML = `
<a href="/@${leaderboardData.all_time.biggest_win.user}">${leaderboardData.all_time.biggest_win.user}</a> <br /><small>${leaderboardData.all_time.biggest_win.amount} ${formatLocalCurrencyName(leaderboardData.all_time.biggest_win.currency)}</small>
`;
biggestWinner24h.innerHTML = `
<a href="/@${leaderboardData.last_24h.biggest_win.user}">${leaderboardData.last_24h.biggest_win.user}</a> <br /> <small>${leaderboardData.last_24h.biggest_win.amount} ${formatLocalCurrencyName(leaderboardData.last_24h.biggest_win.currency)}</small>
`;
biggestLoser24h.innerHTML = `
<a href="/@${leaderboardData.last_24h.biggest_loss.user}">${leaderboardData.last_24h.biggest_loss.user}</a> <br /> <small>${leaderboardData.last_24h.biggest_loss.amount} ${formatLocalCurrencyName(leaderboardData.last_24h.biggest_loss.currency)}</small>
`;
biggestLoserAllTime.innerHTML = `
<a href="/@${leaderboardData.all_time.biggest_loss.user}">${leaderboardData.all_time.biggest_loss.user}</a> <br /> <small>${leaderboardData.all_time.biggest_loss.amount} ${formatLocalCurrencyName(leaderboardData.all_time.biggest_loss.currency)}</small>
`;
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomCardAngle() {
const skew = 10
return getRandomInt(-skew, skew);
}
function buildPlayingCard(rank, suit) {
return `
<div
style="transform: scale(0.7) rotateZ(${getRandomCardAngle()}deg)"
class="playing-card playing-card_${["♥️", "♦️"].includes(suit) ? 'red' : 'black'}">
<div class="playing-card_small playing-card_topright">${rank}${suit}</div>
<div class="playing-card_large">${rank}${suit}</div>
<div class="playing-card_small playing-card_bottomleft">${rank}${suit}</div>
</div>
`;
}
function buildPlayingCardDeck(size = 14) {
const cards = Array.from({ length: size }, (_, index) => `
<div
style="bottom: ${index}px; left: ${-index}px"
class="flipped-playing-card"></div>
`).join('\n');
return `
<div id="playingCardDeck" class="playing-card-deck">
${cards}
</div>
`;
}
function drawFromDeck() {
try {
const [topCard] = Array.from(document.querySelectorAll("#playingCardDeck > *")).reverse();
topCard.classList.add('drawing-a-card');
setTimeout(() => {
topCard.classList.remove('drawing-a-card');
}, 600);
} catch { }
}
</script>
{% block script %} {% endblock %}
<style>
@keyframes drawing {
from {
left: 0;
opacity: 1;
}
to {
left: 100px;
opacity: 0;
}
}
.drawing-a-card {
animation: drawing 1s ease-in-out;
}
.playing-card-deck {
position: relative;
z-index: 3;
box-shadow: -5px 5px 5px 0px rgba(60, 60, 60, 0.56);
-webkit-box-shadow: -5px 5px 5px 0px rgba(60, 60, 60, 0.56);
-moz-box-shadow: -5px 5px 5px 0px rgba(60, 60, 60, 0.56);
}
.flipped-playing-card {
position: absolute;
width: 100px;
height: 150px;
border-radius: 4px;
border: 1px solid #21262C;
background-color: #FF66AC;
transform: scale(0.7);
}
.playing-card {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 150px;
border-radius: 4px;
border: 1px solid #21262C;
background-color: #FFF;
box-shadow: -5px 5px 5px 0px rgba(60, 60, 60, 0.56);
-webkit-box-shadow: -5px 5px 5px 0px rgba(60, 60, 60, 0.56);
-moz-box-shadow: -5px 5px 5px 0px rgba(60, 60, 60, 0.56);
}
.playing-card_red {
color: #ff0000;
}
.playing-card_black {
color: #333;
}
.playing-card_small {
font-size: 18px;
position: absolute;
}
.playing-card_large {
font-size: 48px;
text-align: center;
}
.playing-card_topright {
top: 6px;
right: 6px;
}
.playing-card_bottomleft {
bottom: 6px;
left: 6px;
transform: scaleX(-1) scaleY(-1);
}
#casinoGameResult {
text-transform: uppercase;
text-align: center;
letter-spacing: 3px;
}
.casino-game-leaderboard {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.casino-game-leaderboard-info {
text-align: right;
text-transform: uppercase;
letter-spacing: 2px;
}
.leaderboard-marsey-trophy {
position: relative;
width: 100px;
height: 100px;
}
.leaderboard-marsey-trophy__marsey {
position: relative;
z-index: 1;
width: 100px;
height: 100px;
}
.leaderboard-marsey-trophy__trophy {
position: absolute;
right: 0;
bottom: 0;
z-index: 2;
font-size: 48px;
}
</style>
<div id="casino-game-wrapper" data-game="{{game}}" class="container-fluid" style="max-width: 500px">
<div class="row row-cols-1">
<div class="col game_screen-title">
<h3>{{game}}</h3>
<hr />
</div>
<div class="col">{% block screen %} {% endblock %}</div>
<div class="col">
<div id="casinoGameResult" class="alert" role="alert">
{% block result %} {% endblock %}
</div>
</div>
<div class="col">
<div class="row row-cols-2">
<div class="col">
<div class="game_screen-title">
<h5>Wager</h5>
<hr />
</div>
<input id="wagerAmount" type="number" min="5" step="1" value="5" class="form-control" />
</div>
<div class="col">
<div class="game_screen-title">
<h5>Currency</h5>
<hr />
</div>
<div class="btn-group" role="group" aria-label="Select a currency.">
<input type="radio" class="btn-check" name="wagerCurrency" autocomplete="off" id="wagerCoins"
value="dramacoin" checked />
<label for="wagerCoins" class="btn btn-primary">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" width="32" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="Dramacoin" aria-label="Dramacoin" />
</label>
<input type="radio" class="btn-check" name="wagerCurrency" autocomplete="off" id="wagerProcoins"
value="marseybux" />
<label for="wagerProcoins" class="btn btn-primary">
<img src="/i/marseybux.webp?v=2000" alt="marseybux" width="32" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="Marseybux" aria-label="Marseybux" />
</label>
</div>
</div>
</div>
</div>
<div class="col">
<div class="game_screen-title">
<h5>Actions</h5>
<hr />
</div>
{% block actions %} {% endblock %}
</div>
<div id="casinoGameFeed" data-feed="{{feed}}" class="col">
<div class="game_screen-title">
<h5>Feed</h5>
<hr />
</div>
<ul id="casinoGameFeedList"></ul>
<button type="button" class="btn btn-secondary" style="width: 100%" onclick="reloadFeed()">
Reload Feed
</button>
</div>
<div class="col">
<div class="game_screen-title">
<h5>Leaders</h5>
<hr />
</div>
<div id="gameLeaderboard" data-leaderboard="{{leaderboard}}">
<!-- Biggest Winner All Time -->
<div class="casino-game-leaderboard">
<div class="leaderboard-marsey-trophy">
<img class="leaderboard-marsey-trophy__marsey" src="/e/marseyhappytears.webp" />
<i class="fas fa-trophy leaderboard-marsey-trophy__trophy" style="color: gold;"></i>
</div>
<div class="casino-game-leaderboard-info">
<small>Biggest Winner (All Time)</small>
<h3 id="biggestWinnerAllTime">-</h3>
</div>
</div>
<!-- Biggest Winner 24h -->
<div class="casino-game-leaderboard">
<div class="leaderboard-marsey-trophy">
<img class="leaderboard-marsey-trophy__marsey" src="/e/marseyexcited.webp" />
<i class="fas fa-trophy leaderboard-marsey-trophy__trophy" style="color: gold;"></i>
</div>
<div class="casino-game-leaderboard-info">
<small>Biggest Winner (Last 24h)</small>
<h3 id="biggestWinner24h">-</h3>
</div>
</div>
<!-- Biggest Loser 24h -->
<div class="casino-game-leaderboard">
<div class="leaderboard-marsey-trophy">
<img class="leaderboard-marsey-trophy__marsey" src="/e/marseycry.webp" />
<i class="fas fa-trophy leaderboard-marsey-trophy__trophy" style="color: darkred;"></i>
</div>
<div class="casino-game-leaderboard-info">
<small>Biggest Loser (Last 24h)</small>
<h3 id="biggestLoser24h">-</h3>
</div>
</div>
<!-- Biggest Loser All Time -->
<div class="casino-game-leaderboard">
<div class="leaderboard-marsey-trophy">
<img class="leaderboard-marsey-trophy__marsey" src="/e/marseyrain.webp" />
<i class="fas fa-trophy leaderboard-marsey-trophy__trophy" style="color: darkred;"></i>
</div>
<div class="casino-game-leaderboard-info">
<small>Biggest Loser (All Time)</small>
<h3 id="biggestLoserAllTime">-</h3>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "default.html" %} {% block content %}
<div style="text-transform: uppercase; letter-spacing: 2px; display: flex; flex-direction: column; align-items: center;">
<div style="margin-top: 3rem;">
<h2>No one was there for Britney…</h2>
</div>
<div style="text-align: right; margin-bottom: 3rem;">
<h2>…but were here for you. Youve been checked into Rehab.</h2>
</div>
<img src="/i/rDrama/brit.webp" style="text-align: center" />
</div>
{% endblock %}

View File

@ -0,0 +1,125 @@
{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %}
{% block script %}
<script type="text/javascript">
function pullSlots() {
const { amount, currency } = getWager();
console.log({amount, currency})
disableWager();
clearResult();
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", amount);
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;
if (succeeded) {
const { game_state, gambler } = response;
const state = JSON.parse(game_state);
const reels = Array.from(document.querySelectorAll(".slots_reel"));
const symbols = state.symbols.split(",");
for (let i = 0; i < 3; i++) {
reels[i].innerHTML = symbols[i];
}
let className;
if (state.text.includes("Jackpot")) {
className = "warning";
} else if (state.text.includes("Won")) {
className = "success";
} else if (state.text.includes("Lost")) {
className = "danger";
} else {
className = "success";
}
updateResult(state.text, className);
updatePlayerCurrencies(gambler);
reloadFeed()
} else {
updateResult(response.error, "danger");
console.error(response.error);
}
enableWager();
document.getElementById("casinoSlotsPull").disabled = false;
}
</script>
{% endblock %}
{% block screen %}
<style>
.slots_reels {
display: flex;
align-items: center;
justify-content: center;
}
.slots_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;
}
.slots_reel:nth-child(2) {
margin: 0 1rem;
}
</style>
<div class="slots_reels">
<div class="slots_reel">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" />
</div>
<div class="slots_reel">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" />
</div>
<div class="slots_reel">
<img src="/i/rDrama/coins.webp?v=3009" alt="coin" />
</div>
</div>
{% endblock %}
{% block actions %}
<div class="btn-group" role="group">
<button
id="casinoSlotsPull"
type="button"
class="btn btn-primary"
style="width: 100%"
onclick="pullSlots()"
>
Pull
</button>
</div>
{% endblock %}

View File

@ -1,4 +1,5 @@
<div>
{% extends "default.html" %} {% block content %}
<div style="margin-top: 5rem">
<div class="lottery-page--wrapper">
<div class="lottery-page--image">
<img src="/i/{{SITE_NAME}}/lottery.webp?v=2000" />
@ -187,3 +188,5 @@
<script defer src="{{asset('js/lottery.js')}}"></script>
</div>
{% endblock %}