rabukasim / docs /plans /magic_numbers_enum_analysis.md
trioskosmos's picture
chore: remove large files for HF Space
9bd4ce5

Hardcoded Magic Numbers Analysis Report

Overview

This report identifies hardcoded magic numbers in the Loveca card game engine (Rust codebase) that could be converted to enums for better code maintainability and type safety.

Summary of Findings

The analysis found numerous hardcoded magic numbers across multiple files that are candidates for enum conversion. The codebase already has a good foundation with core/enums.rs and core/generated_constants.rs, but many game-specific constants remain scattered as magic numbers.


Candidate Magic Numbers for Enum Conversion

1. Stage Slot Constants

Current State: Hardcoded 3 used throughout the codebase for stage slots.

Files with magic numbers:

Examples:

// Current usage
for slot in 0..3 { ... }
if slot_idx >= 0 && slot_idx < 3 { ... }
stage: [i32; 3],  // In player.rs

Recommended Enum:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StageSlot {
    Left = 0,
    Center = 1,
    Right = 2,
}

impl StageSlot {
    pub const COUNT: usize = 3;
    pub fn all() -> impl Iterator<Item = Self> { ... }
}

2. Color Constants (Card Colors)

Current State: Colors 0-6 used extensively with magic numbers like color < 7, color == 7 (wildcard).

Files with magic numbers:

Examples:

if color < 7 { ... }
if color == 7 { color = ctx.selected_color as usize; }
// attr 1-7 = colors 0-6. attr 0 = Generic/Any (index 6).
let idx = if attr == 0 || attr == 7 { 6 } else if attr <= 6 { attr - 1 } else { 99 };

Recommended Enum:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum Color {
    #[default]
    Smile = 0,     // Red
    Pure = 1,      // Green
    Cool = 2,      // Blue
    Active = 3,
    Natural = 4,
    Elegant = 5,
    All = 6,       // Wildcard/Any color
}

impl Color {
    pub const COUNT: usize = 7;
    pub fn is_valid(idx: usize) -> bool { idx < Self::COUNT }
}

3. Choice Index Constants

Current State: Special choice indices like 99 (Done/Cancel) and 999 (All) used throughout.

Files with magic numbers:

Examples:

pub const CHOICE_DONE: i16 = 99;
pub const CHOICE_ALL: i16 = 999;
// Usage
if ctx.choice_index == 99 { ... }

Recommendation: These are already defined in constants.rs as CHOICE_DONE and CHOICE_ALL. Consider converting to an enum for stronger typing.


4. Depth Limits (Recursion Guards)

Current State: Magic numbers 5, 10 used for recursion depth limits.

Files with magic numbers:

Examples:

if depth > 5 {
    return 0;
}
if depth > 10 {
    return false;
}

Recommended Enum:

#[derive(Debug, Clone, Copy)]
pub enum RecursionDepth {
    BladeCalculation = 5,
    ConditionCheck = 10,
    Interpreter = 8,  // MAX_DEPTH in interpreter/mod.rs
}

5. Action ID Base Ranges

Current State: Already partially defined in generated_constants.rs but some ranges used directly as magic numbers.

Files with magic numbers:

Examples:

// Already defined but used directly
ACTION_BASE_HAND + (h_idx as i32) * 10 + (slot as i32)
action >= 600 && action <= 602  // Live slot selection
action >= 100 && action < 200   // Discard index
action >= 200 && action < 300   // Hand index

Recommendation: The constants in generated_constants.rs are good. Ensure all code uses these constants instead of magic numbers.


6. Interpreter Limits

Current State: Magic numbers for interpreter step limits.

Files with magic numbers:

Examples:

pub const MAX_DEPTH: usize = 8;
pub const MAX_BYTECODE_LOG_SIZE: usize = 500;
if executor.steps >= 1000 {
    println!("[ERROR] Interpreter infinite loop detected (1000 steps)");
}

Recommendation: Already well-defined. Consider adding MAX_INTERPRETER_STEPS = 1000 to constants.


7. Target Slot Encoding

Current State: Magic numbers for special slot values like 0, 4, 10, -1.

Files with magic numbers:

Examples:

// From suspension.rs
pub fn resolve_target_slot(target_slot: i32, ctx: &AbilityContext) -> usize {
    if target_slot == 0 && ctx.target_slot >= 0 {
        return ctx.target_slot as usize;
    }
    if target_slot == 4 && ctx.area_idx >= 0 {
        ctx.area_idx as usize
    } else if target_slot == -1 || target_slot == 4 {
        // Fallback to 0
    }
}

// From rules.rs - target area encoding
let target_area = s & 0xFF;
if target_area == 1 { targets_us = true; }
else if (target_area == 4 || target_area == 0) && other_slot == slot_idx { ... }
else if target_area == 10 && slot_idx as i16 == ctx.target_slot { ... }

Recommended Enum:

#[derive(Debug, Clone, Copy)]
pub enum TargetSlot {
    ThisSlot = 0,
    AllSlots = 1,
    OpponentSlot = 2,
    AreaIndex = 4,       // Use context.area_idx
    TargetSlot = 10,     // Use context.target_slot
    None = -1,
}

8. Card Type Values

Current State: Magic numbers 1 (Member), 2 (Live) used in filter checks.

Files with magic numbers:

Examples:

if self.card_type == 1 {
    // Member
    if !db.members.contains_key(&cid) { return false; }
} else if self.card_type == 2 {
    // Live
    if !db.lives.contains_key(&cid) { return false; }
}

Recommendation: These should use the existing TargetType enum or create a CardType enum.


9. Zone Values

Current State: Magic numbers for zones like 6 (Hand), 7 (Discard), 4 (Stage), etc.

Files with magic numbers:

Examples:

// Zone encoding
source_zone == 6 || source_zone == 7  // Hand or Discard
source_zone == 15  // Yell
if dest_slot == 6 { ... }  // Hand
else if dest_slot == 7 { ... }  // Discard

Recommendation: The Zone enum already exists in enums.rs. Ensure all code uses it instead of magic numbers.


10. RPS (Rock-Paper-Scissors) Values

Current State: Magic numbers 0, 1, 2 for RPS choices.

Files with magic numbers:

Examples:

let p0_wins = (p0 == 0 && p1 == 2) || (p0 == 1 && p1 == 0) || (p0 == 2 && p1 == 1);

Recommended Enum:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum RpsChoice {
    Rock = 0,
    Paper = 1,
    Scissors = 2,
}

11. Special Card ID Values

Current State: Magic numbers like -1 (empty/no card), special card IDs.

Files with magic numbers:

  • Multiple files

Examples:

if cid == -1 { return false; }
stage[slot] = -1;  // Clear slot

Recommendation: Define constants for special card IDs:

pub const NO_CARD_ID: i32 = -1;
pub const INVALID_CARD_ID: i32 = 0;

Priority Recommendations

High Priority (Most Impactful)

  1. StageSlot Enum - Used in 50+ locations
  2. Color Enum - Central to game mechanics, heart/blade system
  3. Recursion Depth Constants - Safety-critical

Medium Priority

  1. TargetSlot Encoding - Used in interpreter extensively
  2. Card Type Filter Values - Used in filtering logic
  3. Zone Constants - Ensure consistent usage with existing enum

Low Priority (Nice to Have)

  1. RPS Choice Enum - Limited usage
  2. Special Choice Indices - Already partially defined

Implementation Strategy

  1. Create new enum definitions in core/enums.rs or a new core/logic/game_enums.rs
  2. Add constants for special values in core/logic/constants.rs
  3. Systematically replace magic numbers with enum variants or constants
  4. Add tests to verify behavior matches previous magic number behavior
  5. Update documentation to reflect the new type-safe constants

Existing Good Patterns

The codebase already has good patterns in place:

  • Phase enum in core/enums.rs
  • Zone enum in core/enums.rs
  • TriggerType, EffectType, ConditionType enums
  • generated_constants.rs for opcode and action ID bases
  • constants.rs for interpreter constants

The task is to extend this pattern to the remaining magic numbers found in this analysis.