package monsters import ( "dk/internal/store" "fmt" "sort" "sync" ) // Monster represents a monster in the game type Monster struct { ID int `json:"id"` Name string `json:"name"` MaxHP int `json:"max_hp"` MaxDmg int `json:"max_dmg"` Armor int `json:"armor"` Level int `json:"level"` MaxExp int `json:"max_exp"` MaxGold int `json:"max_gold"` Immune int `json:"immune"` } func (m *Monster) Save() error { monsterStore := GetStore() monsterStore.UpdateMonster(m) return nil } func (m *Monster) Delete() error { monsterStore := GetStore() monsterStore.RemoveMonster(m.ID) return nil } // Creates a new Monster with sensible defaults func New() *Monster { return &Monster{ Name: "", MaxHP: 10, // Default HP MaxDmg: 5, // Default damage Armor: 0, // Default armor Level: 1, // Default level MaxExp: 10, // Default exp reward MaxGold: 5, // Default gold reward Immune: ImmuneNone, // No immunity by default } } // Validate checks if monster has valid values func (m *Monster) Validate() error { if m.Name == "" { return fmt.Errorf("monster name cannot be empty") } if m.MaxHP < 1 { return fmt.Errorf("monster MaxHP must be at least 1") } if m.Level < 1 { return fmt.Errorf("monster Level must be at least 1") } if m.Immune < ImmuneNone || m.Immune > ImmuneSleep { return fmt.Errorf("invalid immunity type: %d", m.Immune) } return nil } // Immunity constants for monster immunity types const ( ImmuneNone = 0 ImmuneHurt = 1 // Immune to Hurt spells ImmuneSleep = 2 // Immune to Sleep spells ) // MonsterStore provides in-memory storage with O(1) lookups and monster-specific indices type MonsterStore struct { *store.BaseStore[Monster] // Embedded generic store byLevel map[int][]int // Level -> []ID byImmunity map[int][]int // Immunity -> []ID allByLevel []int // All IDs sorted by level, then ID mu sync.RWMutex // Protects indices } // Global in-memory store var monsterStore *MonsterStore var storeOnce sync.Once // Initialize the in-memory store func initStore() { monsterStore = &MonsterStore{ BaseStore: store.NewBaseStore[Monster](), byLevel: make(map[int][]int), byImmunity: make(map[int][]int), allByLevel: make([]int, 0), } } // GetStore returns the global monster store func GetStore() *MonsterStore { storeOnce.Do(initStore) return monsterStore } // AddMonster adds a monster to the in-memory store and updates all indices func (ms *MonsterStore) AddMonster(monster *Monster) { ms.mu.Lock() defer ms.mu.Unlock() // Validate monster if err := monster.Validate(); err != nil { return } // Add to base store ms.Add(monster.ID, monster) // Rebuild indices ms.rebuildIndicesUnsafe() } // RemoveMonster removes a monster from the store and updates indices func (ms *MonsterStore) RemoveMonster(id int) { ms.mu.Lock() defer ms.mu.Unlock() // Remove from base store ms.Remove(id) // Rebuild indices ms.rebuildIndicesUnsafe() } // UpdateMonster updates a monster efficiently func (ms *MonsterStore) UpdateMonster(monster *Monster) { ms.mu.Lock() defer ms.mu.Unlock() // Validate monster if err := monster.Validate(); err != nil { return } // Update base store ms.Add(monster.ID, monster) // Rebuild indices ms.rebuildIndicesUnsafe() } // LoadData loads monster data from JSON file, or starts with empty store func LoadData(dataPath string) error { ms := GetStore() // Load from base store, which handles JSON loading if err := ms.BaseStore.LoadData(dataPath); err != nil { return err } // Rebuild indices from loaded data ms.rebuildIndices() return nil } // SaveData saves monster data to JSON file func SaveData(dataPath string) error { ms := GetStore() return ms.BaseStore.SaveData(dataPath) } // rebuildIndicesUnsafe rebuilds all indices from base store data (caller must hold lock) func (ms *MonsterStore) rebuildIndicesUnsafe() { // Clear indices ms.byLevel = make(map[int][]int) ms.byImmunity = make(map[int][]int) ms.allByLevel = make([]int, 0) // Collect all monsters and build indices allMonsters := ms.GetAll() // Build level and immunity indices for id, monster := range allMonsters { ms.byLevel[monster.Level] = append(ms.byLevel[monster.Level], id) ms.byImmunity[monster.Immune] = append(ms.byImmunity[monster.Immune], id) ms.allByLevel = append(ms.allByLevel, id) } // Sort allByLevel by level first, then by ID sort.Slice(ms.allByLevel, func(i, j int) bool { monsterI, _ := ms.GetByID(ms.allByLevel[i]) monsterJ, _ := ms.GetByID(ms.allByLevel[j]) if monsterI.Level == monsterJ.Level { return ms.allByLevel[i] < ms.allByLevel[j] } return monsterI.Level < monsterJ.Level }) // Sort level indices by ID for level := range ms.byLevel { sort.Ints(ms.byLevel[level]) } // Sort immunity indices by level, then ID for immunity := range ms.byImmunity { sort.Slice(ms.byImmunity[immunity], func(i, j int) bool { monsterI, _ := ms.GetByID(ms.byImmunity[immunity][i]) monsterJ, _ := ms.GetByID(ms.byImmunity[immunity][j]) if monsterI.Level == monsterJ.Level { return ms.byImmunity[immunity][i] < ms.byImmunity[immunity][j] } return monsterI.Level < monsterJ.Level }) } } // rebuildIndices rebuilds all monster-specific indices from base store data func (ms *MonsterStore) rebuildIndices() { ms.mu.Lock() defer ms.mu.Unlock() ms.rebuildIndicesUnsafe() } // Retrieves a monster by ID - O(1) lookup func Find(id int) (*Monster, error) { ms := GetStore() monster, exists := ms.GetByID(id) if !exists { return nil, fmt.Errorf("monster with ID %d not found", id) } return monster, nil } // Retrieves all monsters - O(1) lookup (returns pre-sorted slice) func All() ([]*Monster, error) { ms := GetStore() ms.mu.RLock() defer ms.mu.RUnlock() result := make([]*Monster, 0, len(ms.allByLevel)) for _, id := range ms.allByLevel { if monster, exists := ms.GetByID(id); exists { result = append(result, monster) } } return result, nil } // Retrieves monsters by level - O(1) lookup func ByLevel(level int) ([]*Monster, error) { ms := GetStore() ms.mu.RLock() defer ms.mu.RUnlock() ids, exists := ms.byLevel[level] if !exists { return []*Monster{}, nil } result := make([]*Monster, 0, len(ids)) for _, id := range ids { if monster, exists := ms.GetByID(id); exists { result = append(result, monster) } } return result, nil } // Retrieves monsters within a level range (inclusive) - O(k) where k is result size func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) { ms := GetStore() ms.mu.RLock() defer ms.mu.RUnlock() var result []*Monster for level := minLevel; level <= maxLevel; level++ { if ids, exists := ms.byLevel[level]; exists { for _, id := range ids { if monster, exists := ms.GetByID(id); exists { result = append(result, monster) } } } } return result, nil } // Retrieves monsters by immunity type - O(1) lookup func ByImmunity(immunityType int) ([]*Monster, error) { ms := GetStore() ms.mu.RLock() defer ms.mu.RUnlock() ids, exists := ms.byImmunity[immunityType] if !exists { return []*Monster{}, nil } result := make([]*Monster, 0, len(ids)) for _, id := range ids { if monster, exists := ms.GetByID(id); exists { result = append(result, monster) } } return result, nil } // Saves a new monster to the in-memory store and sets the ID func (m *Monster) Insert() error { ms := GetStore() // Validate before insertion if err := m.Validate(); err != nil { return fmt.Errorf("validation failed: %w", err) } // Assign new ID if not set if m.ID == 0 { m.ID = ms.GetNextID() } // Add to store ms.AddMonster(m) return nil } // Returns true if the monster is immune to Hurt spells func (m *Monster) IsHurtImmune() bool { return m.Immune == ImmuneHurt } // Returns true if the monster is immune to Sleep spells func (m *Monster) IsSleepImmune() bool { return m.Immune == ImmuneSleep } // Returns true if the monster has any immunity func (m *Monster) HasImmunity() bool { return m.Immune != ImmuneNone } // Returns the string representation of the monster's immunity func (m *Monster) ImmunityName() string { switch m.Immune { case ImmuneNone: return "None" case ImmuneHurt: return "Hurt Spells" case ImmuneSleep: return "Sleep Spells" default: return "Unknown" } } // Calculates a simple difficulty rating based on stats func (m *Monster) DifficultyRating() float64 { // Simple formula: (HP + Damage + Armor) / Level // Higher values indicate tougher monsters relative to their level if m.Level == 0 { return 0 } return float64(m.MaxHP+m.MaxDmg+m.Armor) / float64(m.Level) } // Returns the experience reward per hit point (efficiency metric) func (m *Monster) ExpPerHP() float64 { if m.MaxHP == 0 { return 0 } return float64(m.MaxExp) / float64(m.MaxHP) } // Returns the gold reward per hit point (efficiency metric) func (m *Monster) GoldPerHP() float64 { if m.MaxHP == 0 { return 0 } return float64(m.MaxGold) / float64(m.MaxHP) }