Files
tipibot/core/economy/gambling.py
2026-07-27 00:49:34 +03:00

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"]}