[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
parent
89a162c2f2
commit
d57a569125
|
@ -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 |
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
@ -893,3 +894,8 @@ class User(Base):
|
|||
if self.agendaposter: return True
|
||||
if self.patron: return True
|
||||
return False
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def active_blackjack_game(self):
|
||||
return json.dumps(get_active_twentyone_game_state(self))
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -1,35 +1,56 @@
|
|||
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("casino.html", v=v, participants=participants)
|
||||
return render_template("lottery.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
|
||||
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
|
||||
|
@ -52,89 +73,76 @@ def pull_slots(v):
|
|||
success, game_state = casino_slot_pull(v, wager, currency)
|
||||
|
||||
if success:
|
||||
return {"game_state": game_state, "gambler": { "coins": v.coins, "procoins": v.procoins }}
|
||||
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!"}
|
||||
|
||||
game, _, safe_state = get_active_game(v)
|
||||
|
||||
if game:
|
||||
return { "active": True, "game_state": safe_state }
|
||||
else:
|
||||
return { "active": False }
|
||||
|
||||
|
||||
@app.post("/casino/blackjack/action")
|
||||
@limiter.limit("3/second;30/minute;600/hour;12000/day")
|
||||
@auth_required
|
||||
def player_took_blackjack_action(v):
|
||||
def blackjack_deal_to_player(v):
|
||||
if v.rehab: return {"error": "You are under Rehab award effect!"}
|
||||
|
||||
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." }
|
||||
currency = request.values.get("currency")
|
||||
create_new_game(v, wager, currency)
|
||||
state = dispatch_action(v, BlackjackAction.DEAL)
|
||||
feed = get_game_feed('blackjack')
|
||||
|
||||
existing_game, _, _ = get_active_game(v)
|
||||
return {"success": True, "state": state, "feed": feed}
|
||||
except Exception as error:
|
||||
print(error)
|
||||
return {"error": "Unable to deal a new game."}
|
||||
|
||||
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/hit")
|
||||
@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_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."}
|
||||
|
||||
|
||||
@app.post("/casino/twentyone/stay")
|
||||
@auth_required
|
||||
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."}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
||||
</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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 we’re here for you. You’ve been checked into Rehab.</h2>
|
||||
</div>
|
||||
<img src="/i/rDrama/brit.webp" style="text-align: center" />
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -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 %}
|
|
@ -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 %}
|
Loading…
Reference in New Issue