276 lines
10 KiB
Python
276 lines
10 KiB
Python
"""Casino games: roulette, slots, RPS bets and escrow, blackjack."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import random
|
|
|
|
from ..pb_client import DatabaseError
|
|
from ..emoji import EMOJI as E
|
|
from .store import _commit, _is_jailed, _locked_by, _txn, get_user
|
|
from .house import _credit_house
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /roulette
|
|
# ---------------------------------------------------------------------------
|
|
@_locked_by(0)
|
|
async def do_roulette(user_id: int, bet: int, colour: str) -> dict:
|
|
try:
|
|
user = await get_user(user_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
if user.get("eco_banned"):
|
|
return {"ok": False, "reason": "banned"}
|
|
if jail := _is_jailed(user):
|
|
return {"ok": False, "reason": "jailed", "remaining": jail}
|
|
|
|
if user["balance"] < bet:
|
|
return {"ok": False, "reason": "insufficient"}
|
|
|
|
# Wheel: 18 red, 18 black, 1 green (37 slots - real roulette proportions)
|
|
result = random.choices(["punane", "must", "roheline"], weights=[18, 18, 1])[0]
|
|
won = result == colour
|
|
mult = 14 if colour == "roheline" else 1
|
|
change = bet * mult if won else -bet
|
|
user["balance"] = max(0, user["balance"] + change)
|
|
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
|
if won:
|
|
user["lifetime_earned"] = user.get("lifetime_earned", 0) + abs(change)
|
|
user["biggest_win"] = max(user.get("biggest_win", 0), abs(change))
|
|
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
|
else:
|
|
user["lifetime_lost"] = user.get("lifetime_lost", 0) + bet
|
|
user["biggest_loss"] = max(user.get("biggest_loss", 0), bet)
|
|
await _commit(user_id, user)
|
|
if not won:
|
|
await _credit_house(bet)
|
|
_txn("ROULETTE_" + ("WIN" if won else "LOSE"), user=user_id, bet=bet, colour=colour, result=result, mult=mult, bal=user["balance"])
|
|
|
|
return {
|
|
"ok": True, "won": won,
|
|
"result": result, "change": abs(change), "mult": mult,
|
|
"balance": user["balance"],
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /rps (bet resolution)
|
|
# ---------------------------------------------------------------------------
|
|
@_locked_by(0)
|
|
async def do_game_bet(user_id: int, bet: int, outcome: str) -> dict:
|
|
"""Settle a simple win/tie/lose bet. outcome: 'win' | 'tie' | 'lose'."""
|
|
try:
|
|
user = await get_user(user_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
if user.get("eco_banned"):
|
|
return {"ok": False, "reason": "banned"}
|
|
if jail := _is_jailed(user):
|
|
return {"ok": False, "reason": "jailed", "remaining": jail}
|
|
if user["balance"] < bet:
|
|
return {"ok": False, "reason": "insufficient"}
|
|
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
|
if outcome == "win":
|
|
user["balance"] += bet
|
|
user["lifetime_earned"] = user.get("lifetime_earned", 0) + bet
|
|
user["biggest_win"] = max(user.get("biggest_win", 0), bet)
|
|
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
|
elif outcome == "lose":
|
|
user["balance"] = max(0, user["balance"] - bet)
|
|
user["lifetime_lost"] = user.get("lifetime_lost", 0) + bet
|
|
user["biggest_loss"] = max(user.get("biggest_loss", 0), bet)
|
|
# tie: no change
|
|
await _commit(user_id, user)
|
|
if outcome == "lose" and bet > 0:
|
|
await _credit_house(bet)
|
|
_txn("RPS_" + outcome.upper(), user=user_id, bet=bet, bal=user["balance"])
|
|
return {"ok": True, "balance": user["balance"]}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /rps PvP escrow (deposit/payout/refund)
|
|
# ---------------------------------------------------------------------------
|
|
@_locked_by(0)
|
|
async def do_rps_pvp_deposit(user_id: int, bet: int) -> dict:
|
|
"""Hold `bet` coins from a player as escrow for a PvP RPS duel."""
|
|
try:
|
|
user = await get_user(user_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
if user.get("eco_banned"):
|
|
return {"ok": False, "reason": "banned"}
|
|
if jail := _is_jailed(user):
|
|
return {"ok": False, "reason": "jailed", "remaining": jail}
|
|
if user["balance"] < bet:
|
|
return {"ok": False, "reason": "insufficient"}
|
|
user["balance"] -= bet
|
|
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
|
try:
|
|
await _commit(user_id, user)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
_txn("RPS_PVP_DEPOSIT", user=user_id, bet=bet, bal=user["balance"])
|
|
return {"ok": True, "balance": user["balance"]}
|
|
|
|
|
|
@_locked_by(0)
|
|
async def do_rps_pvp_payout(winner_id: int, bet: int) -> dict:
|
|
"""Credit the duel winner with 2*bet (their stake back + opponent's)."""
|
|
try:
|
|
user = await get_user(winner_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
payout = bet * 2
|
|
user["balance"] = user.get("balance", 0) + payout
|
|
user["lifetime_earned"] = user.get("lifetime_earned", 0) + bet
|
|
user["biggest_win"] = max(user.get("biggest_win", 0), bet)
|
|
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
|
try:
|
|
await _commit(winner_id, user)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
_txn("RPS_PVP_PAYOUT", user=winner_id, payout=f"+{payout}", bal=user["balance"])
|
|
return {"ok": True, "balance": user["balance"]}
|
|
|
|
|
|
@_locked_by(0)
|
|
async def do_rps_pvp_refund(user_id: int, bet: int) -> dict:
|
|
"""Refund a previously escrowed bet (tie / timeout / cancel)."""
|
|
try:
|
|
user = await get_user(user_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
user["balance"] = user.get("balance", 0) + bet
|
|
user["total_wagered"] = max(0, user.get("total_wagered", 0) - bet)
|
|
try:
|
|
await _commit(user_id, user)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
_txn("RPS_PVP_REFUND", user=user_id, bet=bet, bal=user["balance"])
|
|
return {"ok": True, "balance": user["balance"]}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /slots
|
|
# ---------------------------------------------------------------------------
|
|
_SLOTS_SYMBOLS: list[tuple[str, int]] = [
|
|
(E["TipiHEART"], 27),
|
|
(E["TipiFIRE"], 22),
|
|
(E["TipiTROLL"], 18),
|
|
(E["TipICRY"], 15),
|
|
(E["TipiSKULL"], 10),
|
|
(E["TipiKARIKAS"], 8),
|
|
]
|
|
_SLOTS_JACKPOT = E["TipiKARIKAS"]
|
|
_SLOTS_TRIPLE_MULT: dict[str, int] = {
|
|
E["TipiHEART"]: 4,
|
|
E["TipiFIRE"]: 5,
|
|
E["TipiTROLL"]: 7,
|
|
E["TipICRY"]: 10,
|
|
E["TipiSKULL"]: 15,
|
|
E["TipiKARIKAS"]: 25, # jackpot
|
|
}
|
|
|
|
|
|
def _spin() -> str:
|
|
symbols, weights = zip(*_SLOTS_SYMBOLS)
|
|
return random.choices(list(symbols), weights=list(weights), k=1)[0]
|
|
|
|
|
|
@_locked_by(0)
|
|
async def do_slots(user_id: int, bet: int) -> dict:
|
|
try:
|
|
user = await get_user(user_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
if user.get("eco_banned"):
|
|
return {"ok": False, "reason": "banned"}
|
|
if jail := _is_jailed(user):
|
|
return {"ok": False, "reason": "jailed", "remaining": jail}
|
|
if user["balance"] < bet:
|
|
return {"ok": False, "reason": "insufficient"}
|
|
|
|
reels = [_spin(), _spin(), _spin()]
|
|
a, b, c = reels
|
|
|
|
has_360 = "monitor_360" in user["items"]
|
|
if a == b == c:
|
|
tier = "jackpot" if a == _SLOTS_JACKPOT else "triple"
|
|
base_mult = _SLOTS_TRIPLE_MULT.get(a, 4)
|
|
mult = int(base_mult * 1.5) if has_360 else base_mult
|
|
change = bet * (mult - 1)
|
|
elif a == b or b == c or a == c:
|
|
tier = "pair"
|
|
change = bet // 2
|
|
else:
|
|
tier = "miss"
|
|
change = -bet
|
|
|
|
user["balance"] = max(0, user["balance"] + change)
|
|
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
|
if tier in ("jackpot", "triple", "pair"):
|
|
user["lifetime_earned"] = user.get("lifetime_earned", 0) + change
|
|
user["biggest_win"] = max(user.get("biggest_win", 0), change)
|
|
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
|
if tier == "jackpot":
|
|
user["slots_jackpots"] = user.get("slots_jackpots", 0) + 1
|
|
else:
|
|
user["lifetime_lost"] = user.get("lifetime_lost", 0) + bet
|
|
user["biggest_loss"] = max(user.get("biggest_loss", 0), bet)
|
|
await _commit(user_id, user)
|
|
if tier == "miss":
|
|
await _credit_house(bet)
|
|
_txn("SLOTS_" + tier.upper(), user=user_id, bet=bet, change=change, bal=user["balance"])
|
|
|
|
return {
|
|
"ok": True,
|
|
"reels": reels,
|
|
"tier": tier,
|
|
"change": change,
|
|
"balance": user["balance"],
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# /blackjack
|
|
# ---------------------------------------------------------------------------
|
|
@_locked_by(0)
|
|
async def do_blackjack_bet(user_id: int, bet: int) -> dict:
|
|
"""Deduct the initial blackjack bet. Returns ok/fail."""
|
|
try:
|
|
user = await get_user(user_id)
|
|
except DatabaseError:
|
|
return {"ok": False, "reason": "db_error"}
|
|
if user.get("eco_banned"):
|
|
return {"ok": False, "reason": "banned"}
|
|
if jail := _is_jailed(user):
|
|
return {"ok": False, "reason": "jailed", "remaining": jail}
|
|
if user["balance"] < bet:
|
|
return {"ok": False, "reason": "insufficient", "balance": user["balance"]}
|
|
user["balance"] -= bet
|
|
await _commit(user_id, user)
|
|
return {"ok": True, "balance": user["balance"]}
|
|
|
|
|
|
@_locked_by(0)
|
|
async def do_blackjack_payout(user_id: int, payout: int, total_invested: int = 0) -> dict:
|
|
"""Credit the net payout. House receives the difference when payout < total_invested."""
|
|
user = await get_user(user_id)
|
|
user["balance"] += payout
|
|
user["balance"] = max(0, user["balance"])
|
|
user["total_wagered"] = user.get("total_wagered", 0) + total_invested
|
|
net = payout - total_invested
|
|
if net > 0:
|
|
user["lifetime_earned"] = user.get("lifetime_earned", 0) + net
|
|
user["biggest_win"] = max(user.get("biggest_win", 0), net)
|
|
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
|
elif net < 0:
|
|
user["lifetime_lost"] = user.get("lifetime_lost", 0) + abs(net)
|
|
user["biggest_loss"] = max(user.get("biggest_loss", 0), abs(net))
|
|
await _commit(user_id, user)
|
|
house_gain = total_invested - payout
|
|
if house_gain > 0:
|
|
await _credit_house(house_gain)
|
|
_txn("BLACKJACK", user=user_id, payout=f"{payout:+}", net=f"{net:+}", bal=user["balance"])
|
|
return {"ok": True, "balance": user["balance"]}
|