WIP: Add Casino (Do Not Merge) (#341)

* Add new /casino route and template

* Consolidate lottery into casino and add initial template for slots

* Change /lottery route to /casino and replace icon with usd symbol and change sitewide const to reflect change

* Hook up new slots method to casino

* Enable Marseybux spending in casino slots

* Add UI for playing blackjack in casino

* First connection of blackjack UI to backend

* Add protective clause thanks to help from carpathianflorist.

* Create new Casino_Game relation and persist inside of blackjack

* Connect new slots behavior to Casino_Game table

* Create UI action management logic

* Add blackjack game status checker which adds persistence for blackjack

* Gonna handle this better, hold on

* Reorganize blackjack helper methods

* Reorganize casino.js to account for new changes

* Connect up to frontend

* Little changes ya know

* Display a message when winning in Blackjack

* Fix some issues with double down and insure

* Revert "remove owoify-py from requirements"

This reverts commit 4454648ea2.

* A little casino styling change

* Reorganize into a casino block

* Smallenize the card'

* Remove references to old game data on comments

* Add sql migration file

* Remove logic to drop old columns

* Fix two forgotten conflicts
remotes/1693045480750635534/spooky-22
outruncolors 2022-09-04 15:53:34 -05:00 committed by GitHub
parent bfaedc99fc
commit 5e1d98a3bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1453 additions and 606 deletions

View File

@ -0,0 +1,137 @@
.casino-games {
display: flex;
align-items: flex-start;
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: 5rem;
}
/* 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;
}

View File

@ -5383,7 +5383,6 @@ audio, video {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.lottery-page--wrapper > div {
@ -5987,6 +5986,7 @@ g {
.fa-snowflake:before{content:"\f2dc"}
.fa-sparkles:before{content:"\f890"}
.fa-ticket:before{content:"\f145"}
.fa-usd:before{content:"\f155"}
.fa-spider:before{content:"\f717"}
.fa-square:before{content:"\f0c8"}
.fa-stocking:before{content:"\f7d5"}

View File

@ -0,0 +1,328 @@
// 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 } = 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");
}
} 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
// 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");
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 {
console.error("error");
}
}
// 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.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.
document.getElementById("casinoBlackjackWager").style.display = "flex";
const deal = buildBlackjackAction(
"casinoBlackjackDeal",
"dealBlackjack",
"Deal",
true
);
actionWrapper.innerHTML = deal;
}
}
function dealBlackjack() {
const wager = document.getElementById("casinoBlackjackBet").value;
const currency = document.querySelector(
'input[name="casinoBlackjackCurrency"]:checked'
).value;
document.getElementById("casinoBlackjackBet").disabled = true;
document.getElementById("casinoBlackjackDeal").disabled = true;
document.getElementById("casinoBlackjackWager").style.display = "none";
document.getElementById("casinoBlackjackResult").style.visibility = "hidden";
const xhr = new XMLHttpRequest();
xhr.open("post", "/casino/blackjack");
xhr.onload = handleBlackjackResponse.bind(null, xhr);
const form = new FormData();
form.append("formkey", formkey());
form.append("wager", wager);
form.append("currency", currency);
xhr.send(form);
}
function takeBlackjackAction(action) {
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);
xhr.send(form);
}
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");
if (succeeded) {
if (response.game_state) {
updateBlackjack(response.game_state);
}
} else {
blackjackResult.style.visibility = "visible";
blackjackResult.innerText = response.error;
blackjackResult.classList.add("text-danger");
console.error(response.error);
}
}

View File

@ -23,4 +23,5 @@ from .views import *
from .notifications import *
from .follows import *
from .lottery import *
from .hats import *
from .casino_game import *
from .hats import *

View File

@ -0,0 +1,27 @@
from sqlalchemy import *
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
import time
class Casino_Game(Base):
__tablename__ = "casino_games"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
created_utc = Column(Integer)
active = Column(Boolean, default=True)
currency = Column(String)
wager = Column(Integer)
winnings = Column(Integer)
kind = Column(String)
game_state = Column(JSON)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs:
kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<CasinoGame(id={self.id})>"

View File

@ -60,8 +60,6 @@ class Comment(Base):
body = Column(String)
body_html = Column(String)
ban_reason = Column(String)
slots_result = Column(String)
blackjack_result = Column(String)
wordle_result = Column(String)
treasure_amount = Column(String)
@ -412,7 +410,7 @@ class Comment(Base):
if self.is_banned: return True
if (self.slots_result or self.blackjack_result or self.wordle_result) and (not self.body or len(self.body_html) <= 100) and 9 > self.level > 1: return True
if (self.wordle_result) and (not self.body or len(self.body_html) <= 100) and 9 > self.level > 1: return True
if v and v.filter_words and self.body and any(x in self.body for x in v.filter_words): return True
@ -448,73 +446,5 @@ class Comment(Base):
elif wordle_status == 'lost':
body += f"<strong class='ml-2'>Lost. The answer was: {wordle_answer}</strong>"
body += '</span>'
return body
@lazy
def blackjack_html(self, v):
if not self.blackjack_result: return ''
split_result = self.blackjack_result.split('_')
blackjack_status = split_result[3]
player_hand = split_result[0].replace('X', '10')
dealer_hand = split_result[1].split('/')[0] if blackjack_status == 'active' else split_result[1]
dealer_hand = dealer_hand.replace('X', '10')
wager = int(split_result[4])
try: kind = split_result[5]
except: kind = "coins"
currency_kind = "Coins" if kind == "coins" else "Marseybux"
try: is_insured = split_result[6]
except: is_insured = "0"
body = f"<span id='blackjack-{self.id}' class='ml-2'><em>{player_hand} vs. {dealer_hand}</em>"
if blackjack_status == 'active' and v and v.id == self.author_id:
body += f'''
<button
class="action-{self.id} btn btn-success small"
style="text-transform: uppercase; padding: 2px"
onclick="handle_action('blackjack','{self.id}','hit')">
Hit
</button>
<button
class="action-{self.id} btn btn-danger small"
style="text-transform: uppercase; padding: 2px"
onclick="handle_action('blackjack','{self.id}','stay')">
Stay
</button>
<button
class="action-{self.id} btn btn-secondary small"
style="text-transform: uppercase; padding: 2px"
onclick="handle_action('blackjack','{self.id}','doubledown')">
Double Down
</button>
'''
if dealer_hand[0][0] == 'A' and not is_insured == "1":
body += f'''
<button
class="action-{self.id} btn btn-secondary small"
style="text-transform: uppercase; padding: 2px"
onclick="handle_action('blackjack','{self.id}','insurance')">
Insure
</button>
'''
elif blackjack_status == 'push':
body += f"<strong class='ml-2'>Pushed. Refunded {wager} {currency_kind}.</strong>"
elif blackjack_status == 'bust':
body += f"<strong class='ml-2'>Bust. Lost {wager} {currency_kind}.</strong>"
elif blackjack_status == 'lost':
body += f"<strong class='ml-2'>Lost {wager} {currency_kind}.</strong>"
elif blackjack_status == 'won':
body += f"<strong class='ml-2'>Won {wager} {currency_kind}.</strong>"
elif blackjack_status == 'blackjack':
body += f"<strong class='ml-2'>Blackjack! Won {floor(wager * 3/2)} {currency_kind}.</strong>"
if is_insured == "1":
body += f" <em class='text-success'>Insured.</em>"
body += '</span>'
return body

View File

@ -167,9 +167,6 @@ def execute_snappy(post, v):
snappy.comment_count += 1
snappy.coins += 1
g.db.add(snappy)
if body.startswith('!slots'):
check_for_slots_command(body, snappy, c)
if FEATURES['PINS'] and (body.startswith(':#marseypin:') or body.startswith(':#marseypin2:')):
post.stickied = "Snappy"

View File

@ -1,188 +1,297 @@
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 flask import g
deck_count = 4
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("♠️", "♥️", "♣️", "♦️")
coins_command_word = "!blackjack"
marseybux_command_word = "!blackjackmb"
suits = ("S", "H", "C", "D")
minimum_bet = 100
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 and
Casino_Game.kind == 'blackjack' and
Casino_Game.user_id == gambler.id).first()
if game:
return game, json.loads(game.game_state)
else:
return None, None
def get_safe_game_state(gambler):
game, game_state = get_active_game(gambler)
if game:
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']
}
else:
return None
def apply_blackjack_result(gambler):
game, game_state = get_active_game(gambler)
if game and game.active:
result = game_state['status']
if result == 'push' or result == 'insured_loss':
reward = 0
elif result == 'won':
reward = game.wager
elif result == 'blackjack':
reward = floor(game.wager * 3/2)
else:
reward = -game.wager
gambler.winnings += reward
if (reward > -1):
currency_value = int(getattr(gambler, game.currency, 0))
setattr(gambler, game.currency,
currency_value + game.wager + reward)
game.active = False
game.winnings = reward
g.db.add(game)
def deal_blackjack_game(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)
if (over_min and under_max and has_proper_funds):
# Charge the gambler for the game, reduce their winnings, and start the game.
setattr(gambler, currency_prop, currency_value - wager_value)
gambler.winnings -= wager_value
build_game(gambler, currency_prop, wager_value)
game, game_state = get_active_game(gambler)
player_value = get_hand_value(game_state['player'])
dealer_value = get_hand_value(game_state['dealer'])
if player_value == 21 and dealer_value == 21:
game_state["status"] = 'push'
save_game_state(game, game_state)
apply_blackjack_result(gambler)
elif player_value == 21:
game_state["status"] = 'blackjack'
save_game_state(game, game_state)
apply_blackjack_result(gambler)
g.db.flush()
return True
else:
return False
# region Actions
def gambler_hit(gambler):
game, game_state = get_active_game(gambler.id)
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:
return True, game_state
else:
return False, game_state
def gambler_stayed(gambler):
game, game_state = get_active_game(gambler.id)
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, game_state
def gambler_doubled_down(gambler):
game, game_state = get_active_game(gambler.id)
if game and not game_state['doubled_down']:
currency_value = getattr(gambler, game.currency, 0)
if (currency_value < game.wager):
return False
setattr(gambler, game.currency, currency_value - 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, game_state
def gambler_purchased_insurance(gambler):
game, game_state = get_active_game(gambler.id)
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)
game_state['insurance'] = True
game_state['actions'] = determine_actions(game_state)
save_game_state(game, game_state)
return True, game_state
else:
return False, game_state
# endregion
# region Utilities
def shuffle(x):
random.shuffle(x)
return 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
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)
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 = []
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)
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)
return max(possibilities)
def format_cards(hand):
return map(lambda x: "".join(x), hand)
def format_all(player_hand, dealer_hand, deck, status, wager, kind, is_insured=0):
formatted_player_hand = format_cards(player_hand)
formatted_dealer_hand = format_cards(dealer_hand)
formatted_deck = format_cards(deck)
return f'{"/".join(formatted_player_hand)}_{"/".join(formatted_dealer_hand)}_{"/".join(formatted_deck)}_{status}_{wager}_{kind}_{str(is_insured)}'
def check_for_blackjack_commands(in_text, from_user, from_comment):
if not FEATURES['GAMBLING']: return
for command_word in (coins_command_word, marseybux_command_word):
currency_prop = "coins" if command_word == coins_command_word else "procoins"
currency_value = getattr(from_user, currency_prop, 0)
if command_word in in_text:
for word in in_text.split():
if command_word in word:
try:
wager = word[len(command_word):]
wager_value = int(wager)
except: break
if (wager_value < minimum_bet): break
elif (wager_value > maximum_bet): break
elif (wager_value <= currency_value):
setattr(from_user, currency_prop, currency_value - wager_value)
player_hand, dealer_hand, rest_of_deck = deal_initial_cards()
status = 'active'
player_value = get_hand_value(player_hand)
dealer_value = get_hand_value(dealer_hand)
if player_value == 21 and dealer_value == 21:
status = 'push'
apply_game_result(from_comment, wager, status, currency_prop)
elif player_value == 21:
status = 'blackjack'
apply_game_result(from_comment, wager, status, currency_prop)
from_comment.blackjack_result = format_all(player_hand, dealer_hand, rest_of_deck, status, wager, currency_prop, 0)
def player_hit(from_comment, did_double_down=False):
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
player_hand = player_hand.split("/")
dealer_hand = dealer_hand.split("/")
deck = deck.split("/")
player_hand.append(deck.pop(0))
player_value = get_hand_value(player_hand)
if player_value == -1:
status = 'bust'
apply_game_result(from_comment, wager, status, kind)
elif len(player_hand) >= 5:
status = 'won'
apply_game_result(from_comment, wager, status, kind)
from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, wager, kind, int(is_insured))
if (did_double_down or player_value == 21) and status == 'active':
player_stayed(from_comment)
def player_stayed(from_comment):
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
player_hand = player_hand.split("/")
player_value = get_hand_value(player_hand)
dealer_hand = dealer_hand.split("/")
dealer_value = get_hand_value(dealer_hand)
deck = deck.split("/")
if dealer_value == 21 and is_insured == "1":
currency_value = getattr(from_comment.author, kind, 0)
setattr(from_comment.author, kind, currency_value + int(wager))
else:
while dealer_value < 17 and dealer_value != -1:
next = deck.pop(0)
dealer_hand.append(next)
dealer_value = get_hand_value(dealer_hand)
if player_value > dealer_value or dealer_value == -1: status = 'won'
elif dealer_value > player_value: status = 'lost'
else: status = 'push'
from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, wager, kind, int(is_insured))
apply_game_result(from_comment, wager, status, kind)
def player_doubled_down(from_comment):
# When doubling down, the player receives one additional card (a "hit") and their initial bet is doubled.
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
wager_value = int(wager)
currency_value = getattr(from_comment.author, kind, 0)
# Gotsta have enough coins
if (currency_value < wager_value): return
# Double the initial wager
setattr(from_comment.author, kind, currency_value - wager_value)
wager_value *= 2
# Apply the changes to the stored hand.
player_hand = player_hand.split("/")
dealer_hand = dealer_hand.split("/")
deck = deck.split("/")
from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, str(wager_value), kind, int(is_insured))
player_hit(from_comment, True)
def player_bought_insurance(from_comment):
# When buying insurance, the player pays a side bet equal to 1/2 the original bet.
# In the event the dealer actually had a blackjack, they receive a 2:1 payout limiting the negative effect.
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
wager_value = int(wager)
insurance_cost = wager_value / 2
currency_value = getattr(from_comment.author, kind, 0)
# Gotsta have enough coins
if (currency_value < insurance_cost): return
# Charge for (and grant) insurance
setattr(from_comment.author, kind, currency_value - insurance_cost)
is_insured = 1
# Apply the changes to the stored hand.
player_hand = player_hand.split("/")
dealer_hand = dealer_hand.split("/")
deck = deck.split("/")
from_comment.blackjack_result = format_all(player_hand, dealer_hand, deck, status, str(wager_value), kind, int(is_insured))
def apply_game_result(from_comment, wager, result, kind):
wager_value = int(wager)
user = from_comment.author
if result == 'push': reward = 0
elif result == 'won': reward = wager_value
elif result == 'blackjack': reward = floor(wager_value * 3/2)
else: reward = -wager_value
user.winnings += reward
if (reward > -1):
currency_value = int(getattr(user, kind, 0))
setattr(user, kind, currency_value + wager_value + reward)
# endregion

View File

@ -206,7 +206,7 @@ POLL_THREAD = 0
WELCOME_MSG = f"Welcome to {SITE_NAME}!"
ROLES={}
LOTTERY_ENABLED = True
CASINO_ENABLED = True
LOTTERY_TICKET_COST = 12
LOTTERY_SINK_RATE = 3
LOTTERY_DURATION = 60 * 60 * 24 * 7

View File

@ -55,7 +55,7 @@ def inject_constants():
"PIZZASHILL_ID":PIZZASHILL_ID, "DEFAULT_COLOR":DEFAULT_COLOR,
"COLORS":COLORS, "time":time, "PERMS":PERMS, "FEATURES":FEATURES,
"HOLE_NAME":HOLE_NAME, "HOLE_STYLE_FLAIR":HOLE_STYLE_FLAIR, "HOLE_REQUIRED":HOLE_REQUIRED,
"LOTTERY_ENABLED":LOTTERY_ENABLED, "GUMROAD_LINK":GUMROAD_LINK,
"LOTTERY_ENABLED":CASINO_ENABLED, "GUMROAD_LINK":GUMROAD_LINK,
"DEFAULT_THEME":DEFAULT_THEME, "DESCRIPTION":DESCRIPTION,
"has_sidebar":has_sidebar, "has_logo":has_logo, "has_app":has_app,
"FP":FP, "NOTIF_MODACTION_JL_MIN":NOTIF_MODACTION_JL_MIN, "cache":cache, "ONLINE_STR":ONLINE_STR, "patron":patron, "approved_embed_hosts":approved_embed_hosts, "dues":dues}

View File

@ -1,119 +1,123 @@
import json
from json.encoder import INFINITY
import random
from .const import *
from files.classes.casino_game import Casino_Game
from flask import g
command_word = "!slots"
casino_word = "!slotsmb"
if SITE_NAME == 'rDrama': minimum_bet = 100
else: minimum_bet = 10
if SITE_NAME == 'rDrama':
minimum_bet = 100
else:
minimum_bet = 10
maximum_bet = INFINITY
payout_to_symbols = {
2: ["👣", "🍀", "🌈", "⭐️"],
3: ["🍎", "🔞", "⚛️", "☢️"],
5: ["✡️", "⚔️", "🍆", "🍒"],
12: ["🐱"]
2: ["👣", "🍀", "🌈", "⭐️"],
3: ["🍎", "🔞", "⚛️", "☢️"],
5: ["✡️", "⚔️", "🍆", "🍒"],
12: ["🐱"]
}
def shuffle(stuff):
random.shuffle(stuff)
return stuff
def check_for_slots_command(in_text, from_user, from_comment):
if not FEATURES['GAMBLING']: return
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)
in_text = in_text.lower()
if command_word in in_text:
for word in in_text.split():
if command_word in word:
try:
wager = word[len(command_word):]
wager_value = int(wager)
except: break
if (over_min and under_max and has_proper_funds):
setattr(gambler, currency_prop, currency_value - wager_value)
gambler.winnings -= wager_value
if (wager_value < minimum_bet): break
elif (wager_value > maximum_bet): break
elif (wager_value > from_user.coins): break
payout = determine_payout()
reward = wager_value * payout
from_user.coins -= wager_value
from_user.winnings -= wager_value
setattr(gambler, currency_prop, currency_value + reward)
gambler.winnings += reward
payout = determine_payout()
symbols = build_symbols(payout)
text = build_text(wager_value, payout, from_user, "Coins")
reward = wager_value * payout
symbols = build_symbols(payout)
text = build_text(wager_value, payout, currency)
from_user.coins += reward
from_user.winnings += reward
game_state = {
"symbols": symbols,
"text": text
}
casino_game = Casino_Game()
casino_game.active = False
casino_game.user_id = gambler.id
casino_game.currency = currency_prop
casino_game.wager = wager_value
casino_game.winnings = reward - wager_value
casino_game.kind = 'slots'
casino_game.game_state = json.dumps(game_state)
g.db.add(casino_game)
from_comment.slots_result = f'{symbols} {text}'
return True, casino_game.game_state
else:
return False, "{}"
if casino_word in in_text:
for word in in_text.split():
if casino_word in word:
try:
wager = word[len(casino_word):]
wager_value = int(wager)
except: break
if (wager_value < minimum_bet): break
elif (wager_value > maximum_bet): break
elif (wager_value > from_user.procoins): break
def build_symbols(for_payout):
all_symbols = []
from_user.procoins -= wager_value
from_user.winnings -= wager_value
for payout in payout_to_symbols:
for symbol in payout_to_symbols[payout]:
all_symbols.append(symbol)
payout = determine_payout()
symbols = build_symbols(payout)
text = build_text(wager_value, payout, from_user, "Marseybux")
reward = wager_value * payout
shuffle(all_symbols)
from_user.procoins += reward
from_user.winnings += reward
if for_payout == 0:
return "".join([all_symbols[0], ",", all_symbols[1], ",", all_symbols[2]])
elif for_payout == 1:
indices = shuffle([0, 1, 2])
symbol_set = ["", "", ""]
match_a = indices[0]
match_b = indices[1]
nonmatch = indices[2]
matching_symbol = all_symbols[0]
other_symbol = all_symbols[1]
symbol_set[match_a] = matching_symbol
symbol_set[match_b] = matching_symbol
symbol_set[nonmatch] = other_symbol
from_comment.slots_result = f'{symbols} {text}'
return "".join([symbol_set[0], ",", symbol_set[1], ",", symbol_set[2]])
else:
relevantSymbols = shuffle(payout_to_symbols[for_payout])
symbol = relevantSymbols[0]
return "".join([symbol, ",", symbol, ",", symbol])
def build_text(wager_value, result, currency):
if result == 0:
return f'Lost {wager_value} {currency}'
elif result == 1:
return 'Broke Even'
elif result == 12:
return f'Jackpot! Won {wager_value * (result-1)} {currency}'
else:
return f'Won {wager_value * (result-1)} {currency}'
def determine_payout():
value = random.randint(1, 100)
if value == 100: return 12
elif value >= 96: return 5
elif value >= 88: return 3
elif value >= 72: return 2
elif value >= 61: return 1
else: return 0
value = random.randint(1, 100)
if value == 100:
return 12
elif value >= 96:
return 5
elif value >= 88:
return 3
elif value >= 72:
return 2
elif value >= 61:
return 1
else:
return 0
def build_symbols(for_payout):
all_symbols = []
for payout in payout_to_symbols:
for symbol in payout_to_symbols[payout]:
all_symbols.append(symbol)
shuffle(all_symbols)
if for_payout == 0:
return "".join([all_symbols[0], all_symbols[1], all_symbols[2]])
elif for_payout == 1:
indices = shuffle([0, 1, 2])
symbol_set = ["", "", ""]
match_a = indices[0]
match_b = indices[1]
nonmatch = indices[2]
matching_symbol = all_symbols[0]
other_symbol = all_symbols[1]
symbol_set[match_a] = matching_symbol
symbol_set[match_b] = matching_symbol
symbol_set[nonmatch] = other_symbol
return "".join(symbol_set)
else:
relevantSymbols = shuffle(payout_to_symbols[for_payout])
symbol = relevantSymbols[0]
return "".join([symbol, symbol, symbol])
def build_text(wager_value, result, user, currency):
if result == 0: return f'Lost {wager_value} {currency}'
elif result == 1: return 'Broke Even'
elif result == 12: return f'Jackpot! Won {wager_value * (result-1)} {currency}'
else: return f'Won {wager_value * (result-1)} {currency}'
def shuffle(stuff):
random.shuffle(stuff)
return stuff

View File

@ -14,7 +14,7 @@ def check_for_treasure(in_text, from_comment):
if not FEATURES['GAMBLING']: return
if '!slots' not in in_text and '!blackjack' not in in_text and '!wordle' not in in_text:
if '!wordle' not in in_text:
seed = randint(1, 1000)
is_special = seed == 1000
is_standard = seed >= 990

View File

@ -170,7 +170,7 @@ def lottery_required(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not LOTTERY_ENABLED: abort(404)
if not CASINO_ENABLED: abort(404)
return make_response(f(v=v))

View File

@ -17,6 +17,7 @@ from .awards import *
from .giphy import *
from .subs import *
from .lottery import *
from .casino import *
from .polls import *
from .notifications import *
from .hats import *
from .hats import *

View File

@ -0,0 +1,118 @@
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 *
@app.get("/casino")
@auth_required
def casino(v):
participants = get_users_participating_in_lottery()
return render_template("casino.html", v=v, participants=participants)
@app.post("/casino/slots")
@auth_required
def pull_slots(v):
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')."}
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)
if success:
return {"game_state": game_state}
else:
return {"error": "Wager must be more than 100 {currency}."}
@app.get("/casino/blackjack")
@auth_required
def get_player_blackjack_status(v):
game, game_state = get_active_game(v)
if game and game.active:
safe_state = get_safe_game_state(v)
return {"active": True, "game_state": safe_state}
else:
return {"active": False, "game_state": game_state}
@app.post("/casino/blackjack")
@auth_required
def deal_blackjack(v):
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')."}
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 = deal_blackjack_game(v, wager, currency)
if success:
game, game_state = get_active_game(v)
if game and game.active:
safe_state = get_safe_game_state(v)
return {"game_state": safe_state}
else:
return {"game_state": game_state}
else:
return {"error": "Wager must be more than 100 {currency}."}
@app.post("/casino/blackjack/action")
@auth_required
def player_took_blackjack_action(v):
try:
action = request.values.get("action")
except:
return {"error": "Invalid action."}
was_successful = False
state = None
if 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}
else:
return {"active": False, "game_state": None}

View File

@ -338,7 +338,7 @@ def comment(v):
body_html = sanitize(body_for_sanitize, limit_pings=5)
if parent_post.id not in ADMIGGERS and '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
if parent_post.id not in ADMIGGERS and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
existing = g.db.query(Comment.id).filter(Comment.author_id == v.id,
Comment.deleted_utc == 0,
Comment.parent_comment_id == parent_comment_id,
@ -655,12 +655,7 @@ def comment(v):
c.upvotes += 3
g.db.add(c)
if not v.rehab:
check_for_slots_command(body, v, c)
check_for_blackjack_commands(body, v, c)
if not c.slots_result and not c.blackjack_result and v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html):
if v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html):
return {"error":"You can only type marseys!"}, 403
check_for_treasure(body, c)
@ -669,7 +664,7 @@ def comment(v):
answer = random.choice(WORDLE_LIST)
c.wordle_result = f'_active_{answer}'
if not c.slots_result and not c.blackjack_result and not c.wordle_result and not rts:
if not c.wordle_result and not rts:
parent_post.comment_count += 1
g.db.add(parent_post)
@ -980,29 +975,6 @@ def unsave_comment(cid, v):
return {"message": "Comment unsaved!"}
@app.post("/blackjack/<cid>")
@limiter.limit("1/second;30/minute;200/hour;2500/day")
@limiter.limit("1/second;30/minute;200/hour;2500/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}')
@auth_required
def handle_blackjack_action(cid, v):
comment = get_comment(cid)
if v.id != comment.author_id:
abort(403)
if 'active' in comment.blackjack_result:
try: action = request.values.get("thing").strip().lower()
except: abort(400)
if action == 'hit': player_hit(comment)
elif action == 'stay': player_stayed(comment)
elif action == 'doubledown': player_doubled_down(comment)
elif action == 'insurance': player_bought_insurance(comment)
g.db.add(comment)
g.db.add(v)
return {"response" : comment.blackjack_html(v)}
def diff_words(answer, guess):
"""

View File

@ -50,13 +50,6 @@ def lottery_active(v):
return {"message": "", "stats": {"user": v.lottery_stats, "lottery": lottery, "participants": participants}}
@app.get("/lottery")
@auth_required
@lottery_required
def lottery(v):
lottery_stats, participant_stats = get_active_lottery_stats()
return render_template("lottery.html", v=v, lottery_stats=lottery_stats, participant_stats=participant_stats)
@app.get("/admin/lottery/participants")
@admin_level_required(2)
@lottery_required

View File

@ -176,8 +176,8 @@ def post_id(pid, anything=None, v=None, sub=None):
comments = sort_comments(sort, comments)
first = [c[0] for c in comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()]
second = [c[0] for c in comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
first = [c[0] for c in comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()]
second = [c[0] for c in comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
comments = first + second
else:
pinned = g.db.query(Comment).filter(Comment.parent_submission == post.id, Comment.stickied != None).all()
@ -186,8 +186,8 @@ def post_id(pid, anything=None, v=None, sub=None):
comments = sort_comments(sort, comments)
first = comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()
second = comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()
first = comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()
second = comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()
comments = first + second
offset = 0
@ -302,16 +302,16 @@ def viewmore(v, pid, sort, offset):
comments = sort_comments(sort, comments)
first = [c[0] for c in comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()]
second = [c[0] for c in comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
first = [c[0] for c in comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()]
second = [c[0] for c in comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
comments = first + second
else:
comments = g.db.query(Comment).join(Comment.author).filter(User.shadowbanned == None, Comment.parent_submission == pid, Comment.level == 1, Comment.stickied == None, Comment.id.notin_(ids))
comments = sort_comments(sort, comments)
first = comments.filter(or_(and_(Comment.slots_result == None, Comment.blackjack_result == None, Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()
second = comments.filter(or_(Comment.slots_result != None, Comment.blackjack_result != None, Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()
first = comments.filter(or_(and_(Comment.wordle_result == None), func.length(Comment.body_html) > 100)).all()
second = comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()
comments = first + second
comments = comments[offset:]

View File

@ -53,7 +53,7 @@
</ul>
{%- endif %}
{% if LOTTERY_ENABLED -%}
{% if CASINO_ENABLED -%}
<h4>Lottery</h4>
<ul>
<li><a href="/admin/lottery/participants">Participants</a></li>

View File

@ -0,0 +1,251 @@
{% extends "default.html" %} {% block content %}
<link rel="stylesheet" href="/assets/css/casino.css?v=11" />
<script src="/assets/js/casino.js?v=2001"></script>
<!-- New -->
<!-- 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="100"
min="100"
step="1"
aria-label="Bet"
name="casinoSlotsBet"
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="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>
<button
type="button"
class="btn btn-success lottery-page--action"
id="casinoSlotsPull"
style="width: 100%"
onclick="pullSlots()"
>
Pull
</button>
</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="100"
min="100"
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">
<button
type="button"
class="btn btn-success lottery-page--action"
id="casinoBlackjackDeal"
style="width: 100%"
onclick="dealBlackjack()"
>
Deal
</button>
</div>
</div>
</div>
</div>
</div>
<div class="casino-lottery">{% include "lottery.html" %}</div>
{% endblock %}

View File

@ -208,14 +208,6 @@
<em>Found {{c.treasure_amount}} Coins!</em>
{% endif %}
{% endif %}
{% if c.slots_result %}
<em style="position: relative; top: 2px; margin-left: 0.5rem">{{c.slots_result}}</em>
{% endif %}
{% if c.blackjack_result %}
{{c.blackjack_html(v) | safe}}
{% endif %}
{% if c.wordle_result %}
{{c.wordle_html(v) | safe}}

View File

@ -606,28 +606,6 @@ line breaks
</tr>
</thead>
<tbody>
{% if FEATURES['GAMBLING'] %}
<tr>
<td>!slots100</td>
<td>Play slots using coins - minimum 100 coins</td>
</tr>
{% if FEATURES['PROCOINS'] %}
<tr>
<td>!slotsmb100</td>
<td>Play slots using marseybux - minimum 100 marseybux</td>
</tr>
{% endif %}
<tr>
<td>!blackjack100</td>
<td>Play blackjack using coins - minimum 100 coins</td>
</tr>
{% if FEATURES['PROCOINS'] %}
<tr>
<td>!blackjackmb100</td>
<td>Play blackjack using marseybux - minimum 100 marseybux</td>
</tr>
{% endif %}
{% endif %}
{% if FEATURES['WORDLE'] %}
<tr>
<td>!wordle</td>

View File

@ -123,9 +123,9 @@
<a class="mobile-nav-icon d-md-none" href="/random_user"><i class="fas fa-music align-middle text-gray-500 black"></i></a>
{% if v and LOTTERY_ENABLED %}
<a class="mobile-nav-icon d-md-none" href="/lottery">
<i class="fas fa-ticket align-middle text-gray-500 black"></i>
{% if v and CASINO_ENABLED %}
<a class="mobile-nav-icon d-md-none" href="/casino">
<i class="fas fa-usd align-middle text-gray-500 black"></i>
</a>
{% endif %}
@ -179,9 +179,9 @@
<a class="nav-link" href="/random_user" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Random User"><i class="fas fa-music"></i></a>
</li>
{% if LOTTERY_ENABLED %}
{% if CASINO_ENABLED %}
<li class="nav-item d-flex align-items-center justify-content-center text-center mx-1">
<a class="nav-link" href="/lottery" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Lottery"><i class="fas fa-ticket"></i></a>
<a class="nav-link" href="/casino" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Casino"><i class="fas fa-usd"></i></a>
</li>
{% endif %}

View File

@ -1,191 +1,189 @@
{% extends "default.html" %} {% block content %}
<div>
<div class="lottery-page--wrapper">
<div class="lottery-page--image">
<img src="/i/{{SITE_NAME}}/lottery.webp?v=2000" />
<img
id="lotteryTicketPulled"
src="/i/{{SITE_NAME}}/lottery_active.webp?v=2000"
style="display: none"
/>
</div>
<div class="lottery-page--stats">
{% if v.admin_level > 2 %}
<div
class="lottery-page--stat"
style="position: relative; padding-top: 1rem; overflow: hidden"
>
<i
class="fas fa-broom"
style="
position: absolute;
bottom: -4px;
right: -4px;
font-size: 50px;
color: var(--gray-600);
"
>
</i>
<button
type="button"
class="btn btn-danger"
id="endLotterySession"
style="display: none"
onclick="endLotterySession()"
>
End Current Session
</button>
<button
type="button"
class="btn btn-success"
id="startLotterySession"
style="display: none"
onclick="startLotterySession()"
>
Start New Session
</button>
</div>
{% endif %}
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys">
<div>Prize</div>
<div>Time Left</div>
<div>Tickets Sold</div>
<div>Participants</div>
</div>
<div class="lottery-page--stat-values">
<div>
<img
id="prize-image"
alt="coins"
class="mr-1 ml-1"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
height="13"
src="{{asset_siteimg('coins.webp')}}"
aria-label="coins"
title="coins"
style="display: none; position: relative; top: -2px"
/>
<span id="prize">-</span>
</div>
<div id="timeLeft">-</div>
<div id="ticketsSoldThisSession">-</div>
<div id="participantsThisSession">-</div>
</div>
</div>
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys">
<div>Your Held Tickets</div>
<div>Lifetime Held Tickets</div>
<div>Lifetime Winnings</div>
</div>
<div class="lottery-page--stat-values">
<div id="ticketsHeldCurrent">-</div>
<div id="ticketsHeldTotal">-</div>
<div id="winnings">-</div>
</div>
</div>
<div class="lottery-page--image">
<img src="/i/{{SITE_NAME}}/lottery.webp?v=2000" />
<img
id="lotteryTicketPulled"
src="/i/{{SITE_NAME}}/lottery_active.webp?v=2000"
style="display: none"
/>
</div>
<div class="lottery-page--stats">
{% if v.admin_level > 2 %}
<div
class="lottery-page--stat"
style="position: relative; padding-top: 1rem; overflow: hidden"
>
<i
class="fas fa-broom"
style="
position: absolute;
bottom: -4px;
right: -4px;
font-size: 50px;
color: var(--gray-600);
"
>
</i>
<button
type="button"
class="btn btn-danger"
id="endLotterySession"
style="display: none"
onclick="endLotterySession()"
>
End Current Session
</button>
<button
type="button"
class="btn btn-success"
id="startLotterySession"
style="display: none"
onclick="startLotterySession()"
>
Start New Session
</button>
</div>
{% endif %}
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys">
<div>Prize</div>
<div>Time Left</div>
<div>Tickets Sold</div>
<div>Participants</div>
</div>
<div class="lottery-page--stat-values">
<div>
<img
id="prize-image"
alt="coins"
class="mr-1 ml-1"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
height="13"
src="{{asset_siteimg('coins.webp')}}"
aria-label="coins"
title="coins"
style="display: none; position: relative; top: -2px"
/>
<span id="prize">-</span>
</div>
<div id="timeLeft">-</div>
<div id="ticketsSoldThisSession">-</div>
<div id="participantsThisSession">-</div>
</div>
</div>
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys">
<div>Your Held Tickets</div>
<div>Lifetime Held Tickets</div>
<div>Lifetime Winnings</div>
</div>
<div class="lottery-page--stat-values">
<div id="ticketsHeldCurrent">-</div>
<div id="ticketsHeldTotal">-</div>
<div id="winnings">-</div>
</div>
</div>
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys">
<div>Purchase Quantity</div>
</div>
<div class="lottery-page--stat-values">
<div>
<input
id="ticketPurchaseQuantity"
class="form-control"
autocomplete="off"
value="1"
min="1"
step="1"
aria-label="Quantity"
name="ticketPurchaseQuantity"
type="number"
style="flex: 1; max-width: 100px; text-align: center;"
/>
</div>
</div>
</div>
<div class="lottery-page--stat">
<div class="lottery-page--stat-keys">
<div>Purchase Quantity</div>
</div>
<div class="lottery-page--stat-values">
<div>
<input
id="ticketPurchaseQuantity"
class="form-control"
autocomplete="off"
value="1"
min="1"
step="1"
aria-label="Quantity"
name="ticketPurchaseQuantity"
type="number"
style="flex: 1; max-width: 100px; text-align: center"
/>
</div>
</div>
</div>
<button
type="button"
class="btn btn-success lottery-page--action"
id="purchaseTicket"
onclick="purchaseLotteryTicket()"
>
Purchase <span id="totalQuantityOfTickets">1</span> for
<img
alt="coins"
class="mr-1 ml-1"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
height="13"
src="{{asset_siteimg('coins.webp')}}"
aria-label="coins"
title="coins"
/>
<span id="totalCostOfTickets">12</span>
</button>
</div>
<button
type="button"
class="btn btn-success lottery-page--action"
id="purchaseTicket"
onclick="purchaseLotteryTicket()"
>
Purchase <span id="totalQuantityOfTickets">1</span> for
<img
alt="coins"
class="mr-1 ml-1"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
height="13"
src="{{asset_siteimg('coins.webp')}}"
aria-label="coins"
title="coins"
/>
<!-- Success -->
<div
class="toast"
id="lottery-post-success"
style="
position: fixed;
bottom: 1.5rem;
margin: 0 auto;
left: 0;
right: 0;
width: unset;
z-index: 1000;
height: auto !important;
"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-animation="true"
data-bs-autohide="true"
data-bs-delay="5000"
>
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i
><span id="lottery-post-success-text"></span>
</div>
</div>
<span id="totalCostOfTickets">12</span>
</button>
</div>
<!-- Error -->
<div
class="toast"
id="lottery-post-error"
style="
position: fixed;
bottom: 1.5rem;
margin: 0 auto;
left: 0;
right: 0;
width: unset;
z-index: 1000;
height: auto !important;
"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-animation="true"
data-bs-autohide="true"
data-bs-delay="5000"
>
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i
><span id="lottery-post-error-text"></span>
</div>
</div>
<!-- Success -->
<div
class="toast"
id="lottery-post-success"
style="
position: fixed;
bottom: 1.5rem;
margin: 0 auto;
left: 0;
right: 0;
width: unset;
z-index: 1000;
height: auto !important;
"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-animation="true"
data-bs-autohide="true"
data-bs-delay="5000"
>
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i
><span id="lottery-post-success-text"></span>
</div>
</div>
<!-- Error -->
<div
class="toast"
id="lottery-post-error"
style="
position: fixed;
bottom: 1.5rem;
margin: 0 auto;
left: 0;
right: 0;
width: unset;
z-index: 1000;
height: auto !important;
"
role="alert"
aria-live="assertive"
aria-atomic="true"
data-bs-animation="true"
data-bs-autohide="true"
data-bs-delay="5000"
>
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i
><span id="lottery-post-error-text"></span>
</div>
</div>
</div>
<script src="{{asset('js/lottery.js')}}"></script>
</div>
{% endblock %}

View File

@ -356,8 +356,6 @@ CREATE TABLE public.comments (
top_comment_id integer,
stickied_utc integer,
ghost boolean DEFAULT false NOT NULL,
slots_result character varying(36),
blackjack_result character varying(860),
treasure_amount character varying(10),
wordle_result character varying(115)
);

View File

@ -598,8 +598,6 @@ Ayoo, you're fat as fuck!
{[para]}
AVOCADO NIGGER!
{[para]}
!slots
{[para]}
You're poor, figure it out. Imagine being at the super market looking at the cost of nectors and shit. Fuck you, dude.
{[para]}
Want to go rape some fire hydrants?

View File

@ -0,0 +1,15 @@
CREATE TYPE casino_game_kind AS ENUM ('blackjack', 'slots');
CREATE TYPE casino_game_currency AS ENUM ('coins', 'procoins');
CREATE TABLE casino_games (
id SERIAL PRIMARY KEY,
user_id integer NOT NULL REFERENCES users(id),
created_utc integer NOT NULL,
active boolean NOT NULL DEFAULT true,
currency casino_game_currency NOT NULL,
wager integer NOT NULL,
winnings integer NOT NULL,
kind casino_game_kind NOT NULL,
game_state jsonb NOT NULL
);