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