# =============================
# src/mechanics.py
# =============================
"""
Core Pokemon battle mechanics and game rule implementations.
Contains functions for damage modifiers, move selection, and stat calculations.
"""
from __future__ import annotations
from .pokemon_types import Move, PokemonRecord
from .type_chart import type_effectiveness
def stab_multiplier(move_type: str, user_types: list[str]) -> float:
"""
Calculate Same Type Attack Bonus (STAB) multiplier.
In Pokemon, when a Pokemon uses a move that matches one of its types,
the move gets a 1.5x damage bonus. This is called STAB.
Args:
move_type: The elemental type of the move being used
user_types: List of the attacking Pokemon's types (1-2 types)
Returns:
float: 1.5 if move type matches any user type, 1.0 otherwise
Example:
Charizard (Fire/Flying) using Flamethrower (Fire) = 1.5x STAB
Charizard using Thunderbolt (Electric) = 1.0x (no STAB)
"""
return 1.5 if move_type in user_types else 1.0
def select_default_moves(mon: PokemonRecord) -> list[Move]:
"""
Select up to 4 reasonable moves for a Pokemon when no custom moveset is provided.
Strategy: Prefer moves that deal damage (have power > 0) since they're more
useful in battles. Falls back to any available moves if no damaging moves exist.
Args:
mon: Pokemon record containing available moves
Returns:
list[Move]: List of up to 4 moves selected for battle use
Note:
In real Pokemon, each Pokemon can only know 4 moves at a time.
This function simulates move selection from a Pokemon's full moveset.
"""
# Filter for moves that deal damage (have power rating)
damaging = [m for m in mon.moves if (m.power or 0) > 0]
# Return first 4 damaging moves, or first 4 moves if no damaging moves exist
return (damaging[:4] or mon.moves[:4])
def atk_def_for_category(mon: PokemonRecord, category: str) -> tuple[int, int]:
"""
Map move category to appropriate attack and defense stats.
Pokemon has separate offensive and defensive stats for physical and special moves:
- Physical moves use Attack vs Defense
- Special moves use Special Attack vs Special Defense
Args:
mon: Pokemon record containing base stats
category: Move category - "attack" for physical, anything else for special
Returns:
tuple[int, int]: (attack_stat, defense_stat) for damage calculations
Example:
Physical move: returns (mon.atk, mon.def_)
Special move: returns (mon.spa, mon.spd)
"""
if category == "attack": # Physical moves
return mon.base_stats.atk, mon.base_stats.def_
else: # Special moves (category == "special")
return mon.base_stats.spa, mon.base_stats.spd
def compute_type_effect(move: Move, defender: PokemonRecord) -> float:
"""
Calculate type effectiveness multiplier for a move against a defending Pokemon.
This is a wrapper around the type chart that keeps the battle engine
logic clean and separates type chart concerns.
Args:
move: The move being used, containing the attacking type
defender: The defending Pokemon with its types
Returns:
float: Type effectiveness multiplier
- 0.0 = No effect (immune)
- 0.5 = Not very effective
- 1.0 = Normal effectiveness
- 2.0 = Super effective
- 4.0 = Super effective against dual-type (2.0 × 2.0)
Example:
Fire move vs Grass Pokemon = 2.0x (super effective)
Water move vs Fire/Rock Pokemon = 4.0x (2.0 × 2.0, super effective vs both types)
Electric move vs Ground Pokemon = 0.0x (no effect)
"""
return type_effectiveness(move.type, defender.types)