Files
vyryi-back/app/slots/base.py
Redsandy 5125b52583 Add spin session functionality and update game mechanics
- Introduced a new `spin_sessions` table to track user spin activities.
- Updated the API to include a new endpoint for spinning games, allowing users to place bets and specify spin counts.
- Enhanced game information structure and added validation for bets and spin counts.
- Implemented the `Forest Fortune` slot game mechanics, including payline evaluation and scatter functionality.
- Updated dependencies in `pyproject.toml` for development and added `httpx` for HTTP requests.
- Added unit tests for the new slot mechanics and API endpoints to ensure functionality and reliability.
2026-08-12 15:27:24 +03:00

118 lines
3.8 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from decimal import Decimal
from app.slots.rng import Rng, system_rng
from app.slots.types import Grid, SessionMetaState, SessionResult, SpinOutcome, SpinResult
class BaseSlot(ABC):
slug: str
title: str
description: str
status: str = "coming_soon"
min_bet: Decimal = Decimal("1.00")
max_bet: Decimal = Decimal("1000.00")
min_spin_count: int = 1
max_spin_count: int = 1000
@abstractmethod
def build_grid(self, rng: Rng) -> Grid:
raise NotImplementedError
@abstractmethod
def evaluate(self, grid: Grid, bet: Decimal, multiplier: int) -> SpinOutcome:
raise NotImplementedError
@abstractmethod
def session_meta_on_spin(self, state: SessionMetaState, outcome: SpinOutcome) -> SessionMetaState:
raise NotImplementedError
def validate_bet(self, bet: Decimal) -> None:
if bet < self.min_bet or bet > self.max_bet:
raise ValueError(f"Bet must be between {self.min_bet} and {self.max_bet}")
def validate_spin_count(self, spin_count: int) -> None:
if spin_count < self.min_spin_count or spin_count > self.max_spin_count:
raise ValueError(
f"spin_count must be between {self.min_spin_count} and {self.max_spin_count}"
)
def play_session(
self,
bet: Decimal,
spin_count: int,
rng: Rng | None = None,
) -> SessionResult:
if self.status != "available":
raise RuntimeError(f"Game '{self.slug}' is not available")
bet = bet.quantize(Decimal("0.01"))
self.validate_bet(bet)
self.validate_spin_count(spin_count)
rng = rng or system_rng()
meta = SessionMetaState()
spins: list[SpinResult] = []
total_win = Decimal("0.00")
trigger_count = 0
max_level = 1
meta_enabled = spin_count > 1
for index in range(spin_count):
level = meta.level
multiplier = meta.multiplier
grid = self.build_grid(rng)
outcome = self.evaluate(grid, bet, multiplier)
if meta_enabled and outcome.meta_triggered:
next_meta = self.session_meta_on_spin(meta, outcome)
if next_meta.triggered:
trigger_count += 1
meta = SessionMetaState(
level=next_meta.level,
multiplier=next_meta.multiplier,
triggered=False,
)
max_level = max(max_level, meta.level)
triggered = True
else:
triggered = False
spin = SpinResult(
index=index,
grid=outcome.grid,
line_wins=outcome.line_wins,
scatter_count=outcome.scatter_count,
scatter_win=outcome.scatter_win,
total_win=outcome.total_win,
level=level,
multiplier=multiplier,
meta_triggered=triggered,
)
spins.append(spin)
total_win += outcome.total_win
return SessionResult(
bet=bet,
spin_count=spin_count,
total_bet=(bet * spin_count).quantize(Decimal("0.01")),
total_win=total_win.quantize(Decimal("0.01")),
max_level=max_level,
trigger_count=trigger_count,
spins=spins,
)
def info(self) -> dict:
return {
"slug": self.slug,
"title": self.title,
"description": self.description,
"status": self.status,
"min_bet": self.min_bet,
"max_bet": self.max_bet,
"min_spin_count": self.min_spin_count,
"max_spin_count": self.max_spin_count,
}