package towns import ( "dk/internal/store" "fmt" "math" "slices" "sort" "strconv" "strings" "dk/internal/helpers" ) // Town represents a town in the game type Town struct { ID int `json:"id"` Name string `json:"name"` X int `json:"x"` Y int `json:"y"` InnCost int `json:"inn_cost"` MapCost int `json:"map_cost"` TPCost int `json:"tp_cost"` ShopList string `json:"shop_list"` } func (t *Town) Save() error { return GetStore().UpdateWithRebuild(t.ID, t) } func (t *Town) Delete() error { GetStore().RemoveWithRebuild(t.ID) return nil } // Creates a new Town with sensible defaults func New() *Town { return &Town{ Name: "", X: 0, // Default coordinates Y: 0, InnCost: 50, // Default inn cost MapCost: 100, // Default map cost TPCost: 25, // Default teleport cost ShopList: "", // No items by default } } // Validate checks if town has valid values func (t *Town) Validate() error { if t.Name == "" { return fmt.Errorf("town name cannot be empty") } if t.InnCost < 0 { return fmt.Errorf("town InnCost cannot be negative") } if t.MapCost < 0 { return fmt.Errorf("town MapCost cannot be negative") } if t.TPCost < 0 { return fmt.Errorf("town TPCost cannot be negative") } return nil } // coordsKey creates a key for coordinate-based lookup func coordsKey(x, y int) string { return strconv.Itoa(x) + "," + strconv.Itoa(y) } // TownStore with enhanced BaseStore type TownStore struct { *store.BaseStore[Town] } // Global store with singleton pattern var GetStore = store.NewSingleton(func() *TownStore { ts := &TownStore{BaseStore: store.NewBaseStore[Town]()} // Register indices ts.RegisterIndex("byName", store.BuildCaseInsensitiveLookupIndex(func(t *Town) string { return t.Name })) ts.RegisterIndex("byCoords", store.BuildStringLookupIndex(func(t *Town) string { return coordsKey(t.X, t.Y) })) ts.RegisterIndex("byInnCost", store.BuildIntGroupIndex(func(t *Town) int { return t.InnCost })) ts.RegisterIndex("byTPCost", store.BuildIntGroupIndex(func(t *Town) int { return t.TPCost })) ts.RegisterIndex("allByID", store.BuildSortedListIndex(func(a, b *Town) bool { return a.ID < b.ID })) return ts }) // Enhanced CRUD operations func (ts *TownStore) AddTown(town *Town) error { return ts.AddWithRebuild(town.ID, town) } func (ts *TownStore) RemoveTown(id int) { ts.RemoveWithRebuild(id) } func (ts *TownStore) UpdateTown(town *Town) error { return ts.UpdateWithRebuild(town.ID, town) } // Data persistence func LoadData(dataPath string) error { ts := GetStore() return ts.BaseStore.LoadData(dataPath) } func SaveData(dataPath string) error { ts := GetStore() return ts.BaseStore.SaveData(dataPath) } // Query functions using enhanced store func Find(id int) (*Town, error) { ts := GetStore() town, exists := ts.Find(id) if !exists { return nil, fmt.Errorf("town with ID %d not found", id) } return town, nil } func All() ([]*Town, error) { ts := GetStore() return ts.AllSorted("allByID"), nil } func ByName(name string) (*Town, error) { ts := GetStore() town, exists := ts.LookupByIndex("byName", strings.ToLower(name)) if !exists { return nil, fmt.Errorf("town with name '%s' not found", name) } return town, nil } func ByMaxInnCost(maxCost int) ([]*Town, error) { ts := GetStore() return ts.FilterByIndex("allByID", func(t *Town) bool { return t.InnCost <= maxCost }), nil } func ByMaxTPCost(maxCost int) ([]*Town, error) { ts := GetStore() return ts.FilterByIndex("allByID", func(t *Town) bool { return t.TPCost <= maxCost }), nil } func ByCoords(x, y int) (*Town, error) { ts := GetStore() town, exists := ts.LookupByIndex("byCoords", coordsKey(x, y)) if !exists { return nil, nil // Return nil if not found (like original) } return town, nil } func ExistsAt(x, y int) bool { ts := GetStore() _, exists := ts.LookupByIndex("byCoords", coordsKey(x, y)) return exists } func ByDistance(fromX, fromY, maxDistance int) ([]*Town, error) { ts := GetStore() maxDistance2 := float64(maxDistance * maxDistance) result := ts.FilterByIndex("allByID", func(t *Town) bool { return t.DistanceFromSquared(fromX, fromY) <= maxDistance2 }) // Sort by distance, then by ID sort.Slice(result, func(i, j int) bool { distI := result[i].DistanceFromSquared(fromX, fromY) distJ := result[j].DistanceFromSquared(fromX, fromY) if distI == distJ { return result[i].ID < result[j].ID } return distI < distJ }) return result, nil } // Insert with ID assignment func (t *Town) Insert() error { ts := GetStore() if t.ID == 0 { t.ID = ts.GetNextID() } return ts.AddTown(t) } // Helper methods func (t *Town) GetShopItems() []int { return helpers.StringToInts(t.ShopList) } func (t *Town) SetShopItems(items []int) { t.ShopList = helpers.IntsToString(items) } func (t *Town) HasShopItem(itemID int) bool { return slices.Contains(t.GetShopItems(), itemID) } func (t *Town) DistanceFromSquared(x, y int) float64 { dx := float64(t.X - x) dy := float64(t.Y - y) return dx*dx + dy*dy // Return squared distance for performance } func (t *Town) DistanceFrom(x, y int) float64 { return math.Sqrt(t.DistanceFromSquared(x, y)) } func (t *Town) IsStartingTown() bool { return t.X == 0 && t.Y == 0 } func (t *Town) CanAffordInn(gold int) bool { return gold >= t.InnCost } func (t *Town) CanAffordMap(gold int) bool { return gold >= t.MapCost } func (t *Town) CanAffordTeleport(gold int) bool { return gold >= t.TPCost } func (t *Town) HasShop() bool { return len(t.GetShopItems()) > 0 } func (t *Town) GetPosition() (int, int) { return t.X, t.Y } func (t *Town) SetPosition(x, y int) { t.X = x t.Y = y }