first commit
This commit is contained in:
247
api/app/game_logic/dice.py
Normal file
247
api/app/game_logic/dice.py
Normal file
@@ -0,0 +1,247 @@
|
||||
"""
|
||||
Dice mechanics module for Code of Conquest.
|
||||
|
||||
This module provides core dice rolling functionality using a D20 + modifier vs DC system.
|
||||
All game chance mechanics (searches, skill checks, etc.) use these functions to determine
|
||||
outcomes before passing results to AI for narration.
|
||||
"""
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Difficulty(Enum):
|
||||
"""Standard difficulty classes for skill checks."""
|
||||
TRIVIAL = 5
|
||||
EASY = 10
|
||||
MEDIUM = 15
|
||||
HARD = 20
|
||||
VERY_HARD = 25
|
||||
NEARLY_IMPOSSIBLE = 30
|
||||
|
||||
|
||||
class SkillType(Enum):
|
||||
"""
|
||||
Skill types and their associated base stats.
|
||||
|
||||
Each skill maps to a core stat for modifier calculation.
|
||||
"""
|
||||
# Wisdom-based
|
||||
PERCEPTION = "wisdom"
|
||||
INSIGHT = "wisdom"
|
||||
SURVIVAL = "wisdom"
|
||||
MEDICINE = "wisdom"
|
||||
|
||||
# Dexterity-based
|
||||
STEALTH = "dexterity"
|
||||
ACROBATICS = "dexterity"
|
||||
SLEIGHT_OF_HAND = "dexterity"
|
||||
LOCKPICKING = "dexterity"
|
||||
|
||||
# Charisma-based
|
||||
PERSUASION = "charisma"
|
||||
DECEPTION = "charisma"
|
||||
INTIMIDATION = "charisma"
|
||||
PERFORMANCE = "charisma"
|
||||
|
||||
# Strength-based
|
||||
ATHLETICS = "strength"
|
||||
|
||||
# Intelligence-based
|
||||
ARCANA = "intelligence"
|
||||
HISTORY = "intelligence"
|
||||
INVESTIGATION = "intelligence"
|
||||
NATURE = "intelligence"
|
||||
RELIGION = "intelligence"
|
||||
|
||||
# Constitution-based
|
||||
ENDURANCE = "constitution"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckResult:
|
||||
"""
|
||||
Result of a dice check.
|
||||
|
||||
Contains all information needed for UI display (dice roll animation)
|
||||
and game logic (success/failure determination).
|
||||
|
||||
Attributes:
|
||||
roll: The natural d20 roll (1-20)
|
||||
modifier: Total modifier from stats
|
||||
total: roll + modifier
|
||||
dc: Difficulty class that was checked against
|
||||
success: Whether the check succeeded
|
||||
margin: How much the check succeeded or failed by (total - dc)
|
||||
skill_type: The skill used for this check (if applicable)
|
||||
"""
|
||||
roll: int
|
||||
modifier: int
|
||||
total: int
|
||||
dc: int
|
||||
success: bool
|
||||
margin: int
|
||||
skill_type: Optional[str] = None
|
||||
|
||||
@property
|
||||
def is_critical_success(self) -> bool:
|
||||
"""Natural 20 - only relevant for combat."""
|
||||
return self.roll == 20
|
||||
|
||||
@property
|
||||
def is_critical_failure(self) -> bool:
|
||||
"""Natural 1 - only relevant for combat."""
|
||||
return self.roll == 1
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Serialize for API response."""
|
||||
return {
|
||||
"roll": self.roll,
|
||||
"modifier": self.modifier,
|
||||
"total": self.total,
|
||||
"dc": self.dc,
|
||||
"success": self.success,
|
||||
"margin": self.margin,
|
||||
"skill_type": self.skill_type,
|
||||
}
|
||||
|
||||
|
||||
def roll_d20() -> int:
|
||||
"""
|
||||
Roll a standard 20-sided die.
|
||||
|
||||
Returns:
|
||||
Integer from 1 to 20 (inclusive)
|
||||
"""
|
||||
return random.randint(1, 20)
|
||||
|
||||
|
||||
def calculate_modifier(stat_value: int) -> int:
|
||||
"""
|
||||
Calculate the D&D-style modifier from a stat value.
|
||||
|
||||
Formula: (stat - 10) // 2
|
||||
|
||||
Examples:
|
||||
- Stat 10 = +0 modifier
|
||||
- Stat 14 = +2 modifier
|
||||
- Stat 18 = +4 modifier
|
||||
- Stat 8 = -1 modifier
|
||||
|
||||
Args:
|
||||
stat_value: The raw stat value (typically 1-20)
|
||||
|
||||
Returns:
|
||||
The modifier value (can be negative)
|
||||
"""
|
||||
return (stat_value - 10) // 2
|
||||
|
||||
|
||||
def skill_check(
|
||||
stat_value: int,
|
||||
dc: int,
|
||||
skill_type: Optional[SkillType] = None,
|
||||
bonus: int = 0
|
||||
) -> CheckResult:
|
||||
"""
|
||||
Perform a skill check: d20 + modifier vs DC.
|
||||
|
||||
Args:
|
||||
stat_value: The relevant stat value (e.g., character's wisdom for perception)
|
||||
dc: Difficulty class to beat
|
||||
skill_type: Optional skill type for logging/display
|
||||
bonus: Additional bonus (e.g., from equipment or proficiency)
|
||||
|
||||
Returns:
|
||||
CheckResult with full details of the roll
|
||||
"""
|
||||
roll = roll_d20()
|
||||
modifier = calculate_modifier(stat_value) + bonus
|
||||
total = roll + modifier
|
||||
success = total >= dc
|
||||
margin = total - dc
|
||||
|
||||
return CheckResult(
|
||||
roll=roll,
|
||||
modifier=modifier,
|
||||
total=total,
|
||||
dc=dc,
|
||||
success=success,
|
||||
margin=margin,
|
||||
skill_type=skill_type.name if skill_type else None
|
||||
)
|
||||
|
||||
|
||||
def get_stat_for_skill(skill_type: SkillType) -> str:
|
||||
"""
|
||||
Get the base stat name for a skill type.
|
||||
|
||||
Args:
|
||||
skill_type: The skill to look up
|
||||
|
||||
Returns:
|
||||
The stat name (e.g., "wisdom", "dexterity")
|
||||
"""
|
||||
return skill_type.value
|
||||
|
||||
|
||||
def perception_check(wisdom: int, dc: int, bonus: int = 0) -> CheckResult:
|
||||
"""
|
||||
Convenience function for perception checks (searching, spotting).
|
||||
|
||||
Args:
|
||||
wisdom: Character's wisdom stat
|
||||
dc: Difficulty class
|
||||
bonus: Additional bonus
|
||||
|
||||
Returns:
|
||||
CheckResult
|
||||
"""
|
||||
return skill_check(wisdom, dc, SkillType.PERCEPTION, bonus)
|
||||
|
||||
|
||||
def stealth_check(dexterity: int, dc: int, bonus: int = 0) -> CheckResult:
|
||||
"""
|
||||
Convenience function for stealth checks (sneaking, hiding).
|
||||
|
||||
Args:
|
||||
dexterity: Character's dexterity stat
|
||||
dc: Difficulty class
|
||||
bonus: Additional bonus
|
||||
|
||||
Returns:
|
||||
CheckResult
|
||||
"""
|
||||
return skill_check(dexterity, dc, SkillType.STEALTH, bonus)
|
||||
|
||||
|
||||
def persuasion_check(charisma: int, dc: int, bonus: int = 0) -> CheckResult:
|
||||
"""
|
||||
Convenience function for persuasion checks (convincing, negotiating).
|
||||
|
||||
Args:
|
||||
charisma: Character's charisma stat
|
||||
dc: Difficulty class
|
||||
bonus: Additional bonus
|
||||
|
||||
Returns:
|
||||
CheckResult
|
||||
"""
|
||||
return skill_check(charisma, dc, SkillType.PERSUASION, bonus)
|
||||
|
||||
|
||||
def lockpicking_check(dexterity: int, dc: int, bonus: int = 0) -> CheckResult:
|
||||
"""
|
||||
Convenience function for lockpicking checks.
|
||||
|
||||
Args:
|
||||
dexterity: Character's dexterity stat
|
||||
dc: Difficulty class
|
||||
bonus: Additional bonus
|
||||
|
||||
Returns:
|
||||
CheckResult
|
||||
"""
|
||||
return skill_check(dexterity, dc, SkillType.LOCKPICKING, bonus)
|
||||
Reference in New Issue
Block a user