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; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 2rem;
} }
.lottery-page--wrapper > div { .lottery-page--wrapper > div {
@ -5987,6 +5986,7 @@ g {
.fa-snowflake:before{content:"\f2dc"} .fa-snowflake:before{content:"\f2dc"}
.fa-sparkles:before{content:"\f890"} .fa-sparkles:before{content:"\f890"}
.fa-ticket:before{content:"\f145"} .fa-ticket:before{content:"\f145"}
.fa-usd:before{content:"\f155"}
.fa-spider:before{content:"\f717"} .fa-spider:before{content:"\f717"}
.fa-square:before{content:"\f0c8"} .fa-square:before{content:"\f0c8"}
.fa-stocking:before{content:"\f7d5"} .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 .notifications import *
from .follows import * from .follows import *
from .lottery 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 = Column(String)
body_html = Column(String) body_html = Column(String)
ban_reason = Column(String) ban_reason = Column(String)
slots_result = Column(String)
blackjack_result = Column(String)
wordle_result = Column(String) wordle_result = Column(String)
treasure_amount = Column(String) treasure_amount = Column(String)
@ -412,7 +410,7 @@ class Comment(Base):
if self.is_banned: return True 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 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': elif wordle_status == 'lost':
body += f"<strong class='ml-2'>Lost. The answer was: {wordle_answer}</strong>" 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>' body += '</span>'
return body return body

View File

@ -167,9 +167,6 @@ def execute_snappy(post, v):
snappy.comment_count += 1 snappy.comment_count += 1
snappy.coins += 1 snappy.coins += 1
g.db.add(snappy) 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:')): if FEATURES['PINS'] and (body.startswith(':#marseypin:') or body.startswith(':#marseypin2:')):
post.stickied = "Snappy" post.stickied = "Snappy"

View File

@ -1,188 +1,297 @@
import json
from json.encoder import INFINITY from json.encoder import INFINITY
import random import random
from math import floor from math import floor
from files.helpers.const import * from files.helpers.const import *
from files.classes.casino_game import Casino_Game
from flask import g
deck_count = 4 deck_count = 4
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A") ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("♠️", "♥️", "♣️", "♦️") suits = ("S", "H", "C", "D")
coins_command_word = "!blackjack"
marseybux_command_word = "!blackjackmb"
minimum_bet = 100 minimum_bet = 100
maximum_bet = INFINITY 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): def shuffle(x):
random.shuffle(x) random.shuffle(x)
return 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(): def deal_initial_cards():
deck = shuffle([rank + suit for rank in ranks for suit in suits for _ in range(deck_count)]) deck = shuffle(
p1, d1, p2, d2, *rest_of_deck = deck [rank + suit for rank in ranks for suit in suits for _ in range(deck_count)])
return [p1, p2], [d1, d2], rest_of_deck p1, d1, p2, d2, *rest_of_deck = deck
return [p1, p2], [d1, d2], rest_of_deck
def get_card_value(card): def get_card_value(card):
rank = card[0] rank = card[0]
return 0 if rank == "A" else min(ranks.index(rank) + 2, 10) return 0 if rank == "A" else min(ranks.index(rank) + 2, 10)
def get_hand_value(hand): def get_hand_value(hand):
without_aces = sum(map(get_card_value, hand)) without_aces = sum(map(get_card_value, hand))
ace_count = sum("A" in c for c in hand) ace_count = sum("A" in c for c in hand)
possibilities = [] possibilities = []
for i in range(ace_count + 1): for i in range(ace_count + 1):
value = without_aces + (ace_count - i) + i * 11 value = without_aces + (ace_count - i) + i * 11
possibilities.append(-1 if value > 21 else value) possibilities.append(-1 if value > 21 else value)
return max(possibilities) return max(possibilities)
# endregion
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)

View File

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

View File

@ -55,7 +55,7 @@ def inject_constants():
"PIZZASHILL_ID":PIZZASHILL_ID, "DEFAULT_COLOR":DEFAULT_COLOR, "PIZZASHILL_ID":PIZZASHILL_ID, "DEFAULT_COLOR":DEFAULT_COLOR,
"COLORS":COLORS, "time":time, "PERMS":PERMS, "FEATURES":FEATURES, "COLORS":COLORS, "time":time, "PERMS":PERMS, "FEATURES":FEATURES,
"HOLE_NAME":HOLE_NAME, "HOLE_STYLE_FLAIR":HOLE_STYLE_FLAIR, "HOLE_REQUIRED":HOLE_REQUIRED, "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, "DEFAULT_THEME":DEFAULT_THEME, "DESCRIPTION":DESCRIPTION,
"has_sidebar":has_sidebar, "has_logo":has_logo, "has_app":has_app, "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} "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 from json.encoder import INFINITY
import random import random
from .const import * from .const import *
from files.classes.casino_game import Casino_Game
from flask import g
command_word = "!slots" if SITE_NAME == 'rDrama':
casino_word = "!slotsmb" minimum_bet = 100
if SITE_NAME == 'rDrama': minimum_bet = 100 else:
else: minimum_bet = 10 minimum_bet = 10
maximum_bet = INFINITY maximum_bet = INFINITY
payout_to_symbols = { payout_to_symbols = {
2: ["👣", "🍀", "🌈", "⭐️"], 2: ["👣", "🍀", "🌈", "⭐️"],
3: ["🍎", "🔞", "⚛️", "☢️"], 3: ["🍎", "🔞", "⚛️", "☢️"],
5: ["✡️", "⚔️", "🍆", "🍒"], 5: ["✡️", "⚔️", "🍆", "🍒"],
12: ["🐱"] 12: ["🐱"]
} }
def shuffle(stuff):
random.shuffle(stuff)
return stuff
def check_for_slots_command(in_text, from_user, from_comment): def casino_slot_pull(gambler, wager_value, currency):
if not FEATURES['GAMBLING']: return 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 (over_min and under_max and has_proper_funds):
if command_word in in_text: setattr(gambler, currency_prop, currency_value - wager_value)
for word in in_text.split(): gambler.winnings -= wager_value
if command_word in word:
try:
wager = word[len(command_word):]
wager_value = int(wager)
except: break
if (wager_value < minimum_bet): break payout = determine_payout()
elif (wager_value > maximum_bet): break reward = wager_value * payout
elif (wager_value > from_user.coins): break
from_user.coins -= wager_value setattr(gambler, currency_prop, currency_value + reward)
from_user.winnings -= wager_value gambler.winnings += reward
payout = determine_payout() symbols = build_symbols(payout)
symbols = build_symbols(payout) text = build_text(wager_value, payout, currency)
text = build_text(wager_value, payout, from_user, "Coins")
reward = wager_value * payout
from_user.coins += reward game_state = {
from_user.winnings += reward "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 def build_symbols(for_payout):
elif (wager_value > maximum_bet): break all_symbols = []
elif (wager_value > from_user.procoins): break
from_user.procoins -= wager_value for payout in payout_to_symbols:
from_user.winnings -= wager_value for symbol in payout_to_symbols[payout]:
all_symbols.append(symbol)
payout = determine_payout() shuffle(all_symbols)
symbols = build_symbols(payout)
text = build_text(wager_value, payout, from_user, "Marseybux")
reward = wager_value * payout
from_user.procoins += reward if for_payout == 0:
from_user.winnings += reward 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(): def determine_payout():
value = random.randint(1, 100) value = random.randint(1, 100)
if value == 100: return 12 if value == 100:
elif value >= 96: return 5 return 12
elif value >= 88: return 3 elif value >= 96:
elif value >= 72: return 2 return 5
elif value >= 61: return 1 elif value >= 88:
else: return 0 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) def shuffle(stuff):
else: random.shuffle(stuff)
relevantSymbols = shuffle(payout_to_symbols[for_payout]) return stuff
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}'

View File

@ -14,7 +14,7 @@ def check_for_treasure(in_text, from_comment):
if not FEATURES['GAMBLING']: return 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) seed = randint(1, 1000)
is_special = seed == 1000 is_special = seed == 1000
is_standard = seed >= 990 is_standard = seed >= 990

View File

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

View File

@ -17,6 +17,7 @@ from .awards import *
from .giphy import * from .giphy import *
from .subs import * from .subs import *
from .lottery import * from .lottery import *
from .casino import *
from .polls import * from .polls import *
from .notifications 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) 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, existing = g.db.query(Comment.id).filter(Comment.author_id == v.id,
Comment.deleted_utc == 0, Comment.deleted_utc == 0,
Comment.parent_comment_id == parent_comment_id, Comment.parent_comment_id == parent_comment_id,
@ -655,12 +655,7 @@ def comment(v):
c.upvotes += 3 c.upvotes += 3
g.db.add(c) g.db.add(c)
if not v.rehab: if v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html):
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):
return {"error":"You can only type marseys!"}, 403 return {"error":"You can only type marseys!"}, 403
check_for_treasure(body, c) check_for_treasure(body, c)
@ -669,7 +664,7 @@ def comment(v):
answer = random.choice(WORDLE_LIST) answer = random.choice(WORDLE_LIST)
c.wordle_result = f'_active_{answer}' 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 parent_post.comment_count += 1
g.db.add(parent_post) g.db.add(parent_post)
@ -980,29 +975,6 @@ def unsave_comment(cid, v):
return {"message": "Comment unsaved!"} 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): 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}} 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") @app.get("/admin/lottery/participants")
@admin_level_required(2) @admin_level_required(2)
@lottery_required @lottery_required

View File

@ -176,8 +176,8 @@ def post_id(pid, anything=None, v=None, sub=None):
comments = sort_comments(sort, comments) 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()] 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.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.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
comments = first + second comments = first + second
else: else:
pinned = g.db.query(Comment).filter(Comment.parent_submission == post.id, Comment.stickied != None).all() 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) 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() first = comments.filter(or_(and_(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() second = comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()
comments = first + second comments = first + second
offset = 0 offset = 0
@ -302,16 +302,16 @@ def viewmore(v, pid, sort, offset):
comments = sort_comments(sort, comments) 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()] 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.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.wordle_result != None), func.length(Comment.body_html) <= 100).all()]
comments = first + second comments = first + second
else: 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 = 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) 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() first = comments.filter(or_(and_(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() second = comments.filter(or_(Comment.wordle_result != None), func.length(Comment.body_html) <= 100).all()
comments = first + second comments = first + second
comments = comments[offset:] comments = comments[offset:]

View File

@ -53,7 +53,7 @@
</ul> </ul>
{%- endif %} {%- endif %}
{% if LOTTERY_ENABLED -%} {% if CASINO_ENABLED -%}
<h4>Lottery</h4> <h4>Lottery</h4>
<ul> <ul>
<li><a href="/admin/lottery/participants">Participants</a></li> <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> <em>Found {{c.treasure_amount}} Coins!</em>
{% endif %} {% endif %}
{% 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 %} {% if c.wordle_result %}
{{c.wordle_html(v) | safe}} {{c.wordle_html(v) | safe}}

View File

@ -606,28 +606,6 @@ line breaks
</tr> </tr>
</thead> </thead>
<tbody> <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'] %} {% if FEATURES['WORDLE'] %}
<tr> <tr>
<td>!wordle</td> <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> <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 %} {% if v and CASINO_ENABLED %}
<a class="mobile-nav-icon d-md-none" href="/lottery"> <a class="mobile-nav-icon d-md-none" href="/casino">
<i class="fas fa-ticket align-middle text-gray-500 black"></i> <i class="fas fa-usd align-middle text-gray-500 black"></i>
</a> </a>
{% endif %} {% 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> <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> </li>
{% if LOTTERY_ENABLED %} {% if CASINO_ENABLED %}
<li class="nav-item d-flex align-items-center justify-content-center text-center mx-1"> <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> </li>
{% endif %} {% endif %}

View File

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

View File

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

View File

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