Welcome! This guide will help you understand the coding standards and conventions used in this project.
- Code Standards
- Naming Conventions
- Type Hints
- Documentation
- Project Structure
- Testing
- Development Workflow
- Required: Python 3.12 or higher
- Reason: Modern type hints and performance improvements
- Formatter: Black with default settings (88 char line length)
- Import Sorting: isort with black-compatible profile
- Linter: flake8 with relaxed line length (88 chars)
- Type Checker: mypy in strict mode
All code must pass pre-commit hooks before committing:
- Black formatting
- isort import sorting
- flake8 linting
- mypy type checking
- Trailing whitespace removal
- Be explicit over implicit:
free_spin_countnotfs - Use full words: Avoid abbreviations unless universally understood (e.g.,
rtp,id,rng) - Be consistent: Use the same term throughout the codebase
free_spin_count = 10
total_free_spins = 20
simulation_id = 1
game_mode = GameMode.BASE
current_board = draw_board()
win_multiplier = 5.0fs = 10 # Too abbreviated
tot_fs = 20 # Inconsistent abbreviation
sim = 1 # Unclear
game_type = "base" # Should be enum
bd = draw() # Too cryptic
mult = 5.0 # Ambiguous# Use UPPER_SNAKE_CASE for module-level constants
MAX_FREE_SPINS = 100
DEFAULT_RTP = 0.96
MIN_CLUSTER_SIZE = 5
# Use enums for related constants
class GameMode(Enum):
BASE = "base"
FREE_SPIN = "free_spin"
BONUS = "bonus"- Use
snake_case - Start with a verb that describes the action
- Be descriptive and specific
def calculate_cluster_wins(board: Board) -> list[Win]:
"""Calculate all cluster wins on the board."""
...
def has_free_spin_trigger(symbols: list[Symbol]) -> bool:
"""Check if free spin trigger condition is met."""
...
def reset_simulation_state() -> None:
"""Reset all state variables for a new simulation."""
...
def draw_board() -> Board:
"""Draw a random board from reel strips."""
...def calc(board): # Too abbreviated, unclear what it calculates
...
def check_fs(): # Abbreviated, unclear return value
...
def reset(): # Too generic, reset what?
...
def board(): # Not a verb, unclear action
...- Use
PascalCase - Use descriptive nouns or noun phrases
- Avoid generic names like
Manager,Handler,Processorunless necessary
class GameState:
"""Base class for all game state implementations."""
...
class ClusterWinCalculator:
"""Calculates cluster-based wins."""
...
class WinManager:
"""Manages win tracking and aggregation."""
...
class GameConfig:
"""Game configuration and parameters."""
...class State: # Too generic
...
class GameExecutables: # Not a noun, unclear purpose (removed)
...
class GeneralGameState: # "General" is vague (removed, merged into GameState)
...- Use
snake_casefor all Python files - Name files after their primary class or purpose
- Group related functionality
game_state.py # Contains GameState base class (in src/state/)
cluster_calculator.py # Contains cluster win logic
manager.py # Contains WinManager class (in src/wins/)
game_config.py # Contains GameConfig class
constants.py # Contains EventConstants enum
state.py # Too generic (removed)
executables.py # Unclear purpose (removed)
override.py # What does it override?
games/
<game_name>/
game_state.py # Main game implementation (was game_state.py)
game_config.py # Game configuration
game_optimization.py # Optimization parameters
game_events.py # Custom event generators (optional)
game_calculations.py # Helper calculations (optional)
reels/ # Reel strip CSV files
library/ # Generated output files
tests/ # Game-specific tests
# Use these consistently across all games
self.simulation_id # Current simulation number (was: sim)
self.free_spin_count # Current free spin number (was: fs)
self.total_free_spins # Total free spins awarded (was: tot_fs)
self.game_mode # Current game mode (was: game_type)
self.current_board # Current board state (was: board)
self.win_multiplier # Total win multiplier (was: final_win)
self.is_win_cap_reached # Win cap status (was: wincap_triggered)
self.global_multiplier # Global multiplier (keep as-is)All functions and methods must have type hints for:
- All parameters
- Return values
- Class attributes (if not obvious from
__init__)
from typing import Optional, Union
def calculate_win(
symbol: str,
count: int,
multiplier: float = 1.0
) -> float:
"""Calculate win amount for a symbol combination."""
return count * multiplier
def get_random_symbol(
distribution: dict[str, float]
) -> Optional[str]:
"""Select a random symbol from weighted distribution."""
...from typing import ClassVar
class GameConfig:
"""Game configuration and parameters."""
# Class variable
_instance: ClassVar[Optional['GameConfig']] = None
# Instance variables (type hints in __init__)
def __init__(self) -> None:
self.game_id: str = "tower_treasures"
self.rtp: float = 0.97
self.num_reels: int = 5
self.num_rows: list[int] = [5] * self.num_reelsCreate type aliases for complex types:
from typing import TypeAlias
# Board representation
Board: TypeAlias = list[list[Symbol]]
# Position on the board
Position: TypeAlias = tuple[int, int] # (reel, row)
# Win details
WinDetails: TypeAlias = dict[str, Union[str, int, float, list[Position]]]from typing import Generic, TypeVar
T = TypeVar('T')
class Distribution(Generic[T]):
"""Generic weighted distribution."""
def __init__(self, items: dict[T, float]) -> None:
self.items = items
def sample(self) -> T:
"""Sample a random item from the distribution."""
...Use Google-style docstrings for all public classes, methods, and functions.
"""Cluster win calculation module.
This module provides functionality for detecting and calculating
cluster-based wins in slot games. Clusters are groups of adjacent
matching symbols.
"""class GameState(ABC):
"""Base class for all slot game simulations.
Provides core infrastructure for:
- Random board generation from reel strips
- Event recording and book management
- Win calculation and management
- Free spin triggering and tracking
- RNG seeding for reproducibility
Games should inherit from this class (via Board or Tumble) and implement:
- run_spin(): Main game logic for a single spin
- run_free_spin(): Free spin game logic
- assign_special_symbol_functions(): Special symbol handlers
Attributes:
config: Game configuration instance
simulation_id: Current simulation number
game_mode: Current game mode (base, free_spin, etc.)
current_board: Current board state
Example:
>>> class MyGame(Board):
... def run_spin(self, simulation_id: int) -> None:
... self.reset_seed(simulation_id)
... self.draw_board()
... self.calculate_wins()
"""def calculate_cluster_wins(
self,
board: Board,
min_cluster_size: int = 5
) -> list[WinDetails]:
"""Calculate all cluster wins on the board.
Detects all clusters of adjacent matching symbols and calculates
their win amounts based on the paytable.
Args:
board: 2D board of symbols to analyze
min_cluster_size: Minimum cluster size to count as a win
Returns:
List of win detail dictionaries containing:
- symbol: The winning symbol
- positions: List of (reel, row) positions
- count: Number of symbols in cluster
- amount: Win amount (multiplier × bet)
Raises:
ValueError: If min_cluster_size is less than 1
Example:
>>> wins = self.calculate_cluster_wins(board, min_cluster_size=5)
>>> print(wins[0]['symbol'])
'L1'
"""- Use sparingly - code should be self-documenting
- Explain why, not what
- Keep comments up to date
# ✓ Good - explains why
# Use BFS instead of DFS to ensure clusters are detected in order
clusters = self._find_clusters_bfs(board)
# ✗ Bad - explains what (obvious from code)
# Loop through each reel
for reel in board:
...stake-engine-math/
├── src/ # Core SDK modules
│ ├── calculations/ # Win calculation algorithms
│ │ ├── cluster.py
│ │ ├── lines.py
│ │ ├── ways.py
│ │ └── scatter.py
│ ├── config/ # Configuration classes
│ │ ├── config.py
│ │ ├── bet_mode.py
│ │ └── distributions.py
│ ├── events/ # Event system
│ │ ├── constants.py # EventConstants enum
│ │ ├── filter.py # Event filtering
│ │ ├── core.py # Core events (reveal, win, win_cap)
│ │ ├── free_spins.py # Free spins events
│ │ ├── tumble.py # Tumble/cascade events
│ │ ├── special_symbols.py # Upgrade, prize, multiplier events
│ │ └── helpers.py # Utilities
│ ├── state/ # Core state machine
│ │ ├── game_state.py # GameState base class
│ │ ├── books.py
│ │ └── run_sims.py
│ ├── wins/ # Win management
│ │ └── manager.py
│ └── writers/ # Output generation
│ ├── data.py # Books, lookup tables, force files
│ ├── configs.py # Frontend/backend config generation
│ └── force.py # Force file search/option classes
├── games/ # Individual game implementations
│ ├── template/ # Template for new games
│ ├── tower_treasures/
│ │ ├── game_state.py # (was game_state.py)
│ │ ├── game_config.py
│ │ ├── game_optimization.py
│ │ ├── reels/
│ │ ├── library/
│ │ └── tests/
│ └── template_cluster/
├── optimization_program/ # Rust optimization
│ └── src/
├── tests/ # SDK-wide tests
├── utils/ # Utility scripts
├── docs/ # Documentation
└── scripts/ # Helper scripts
Use isort with the following order:
- Standard library imports
- Third-party imports
- Local application imports
# Standard library
import os
import random
from abc import ABC, abstractmethod
from typing import Optional
# Third-party
import numpy as np
# Local application
from src.config.config import Config
from src.events.constants import EventConstants
from src.wins.manager import WinManagertests/
├── test_cluster_calculator.py
├── test_win_manager.py
├── test_game_state.py
└── win_calculations/
├── test_cluster_pay.py
├── test_lines_pay.py
└── test_ways_pay.py
games/tower_treasures/tests/
├── test_game_state.py
├── test_special_symbols.py
└── integration_test_example.py
class TestClusterCalculator:
"""Tests for ClusterCalculator class."""
def test_finds_single_cluster(self) -> None:
"""Test detection of a single cluster."""
...
def test_ignores_small_clusters(self) -> None:
"""Test that clusters below min size are ignored."""
...
def test_handles_empty_board(self) -> None:
"""Test behavior with empty board."""
...# Run all tests
make test
# Run game-specific tests
make unit-test GAME=tower_treasures
# Run with coverage
pytest --cov=src tests/# Clone repository
git clone <repo-url>
cd stake-engine-math
# Setup virtual environment and install dependencies
make setup
source env/bin/activate
# Install development dependencies
pip install black isort flake8 mypy pytest pytest-cov
# Install pre-commit hooks
pre-commit install# Format code
black .
isort .
# Check types
mypy src/ games/
# Run linter
flake8 src/ games/
# Run tests
pytest tests/-
Copy template:
cp -r games/template games/my_new_game
-
Update
game_config.py:class GameConfig(Config): def __init__(self): super().__init__() self.game_id = "my_new_game" self.game_name = "My New Game" # ... configure game
-
Implement
game_state.py:class GameState(Board): def run_spin(self, simulation_id: int) -> None: # Implement game logic ...
-
Add reel strips to
reels/directory -
Run simulation:
make run GAME=my_new_game
- Code follows naming conventions
- All functions have type hints
- All public methods have docstrings
- Tests added for new functionality
- Pre-commit hooks pass
- No mypy errors
- No flake8 warnings
- Documentation updated
class GameConfig:
"""Game configuration (singleton)."""
_instance: ClassVar[Optional['GameConfig']] = None
def __new__(cls) -> 'GameConfig':
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instancefrom abc import ABC, abstractmethod
class GameState(ABC):
"""Base class for game implementations."""
@abstractmethod
def run_spin(self, simulation_id: int) -> None:
"""Run a single spin simulation.
Must be implemented by subclasses.
"""
...from src.events.constants import EventConstants
# Always use EventConstants, never hardcoded strings
event = {
"index": len(self.book.events),
"type": EventConstants.WIN.value,
"amount": 1000,
"details": [{"symbol": "L1", "positions": [[0, 0], [0, 1]]}],
}
self.book.add_event(event)# Get current bet mode
bet_mode = self.get_bet_mode(self.game_mode)
# Get distributions
distributions = bet_mode.distributions
# Get current distribution conditions
conditions = self.get_current_distribution_conditions()If you have questions about these conventions or encounter edge cases, please open an issue for discussion.
Last Updated: 2026-01-10