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.
This commit is contained in:
117
app/slots/base.py
Normal file
117
app/slots/base.py
Normal file
@@ -0,0 +1,117 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user