diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..e03c8e2 --- /dev/null +++ b/config.yaml @@ -0,0 +1,44 @@ +# Catacombs Configuration +# All values shown are defaults. Uncomment and modify as needed. + +server: + # SSH port for game client connections + ssh_port: 2222 + # HTTP port for web interface + http_port: 8080 + +game: + # Seconds allowed per player turn + turn_timeout_sec: 5 + # Maximum players per game session + max_players: 4 + # Maximum dungeon floors + max_floors: 20 + # Cooperative play bonus multiplier + coop_bonus: 0.10 + # Maximum items a player can carry + inventory_limit: 10 + # Number of skill uses per floor + skill_uses: 3 + +combat: + # Probability of successfully fleeing combat + flee_chance: 0.50 + # Monster stat scaling per floor + monster_scaling: 1.15 + # HP reduction when playing solo + solo_hp_reduction: 0.50 + +dungeon: + # Map dimensions in tiles + map_width: 60 + map_height: 20 + # Room count range per floor + min_rooms: 5 + max_rooms: 8 + +backup: + # Minutes between automatic backups + interval_min: 60 + # Directory for backup files + dir: "./data/backup" diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..d9b3e54 --- /dev/null +++ b/config/config.go @@ -0,0 +1,75 @@ +package config + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Server ServerConfig `yaml:"server"` + Game GameConfig `yaml:"game"` + Combat CombatConfig `yaml:"combat"` + Dungeon DungeonConfig `yaml:"dungeon"` + Backup BackupConfig `yaml:"backup"` +} + +type ServerConfig struct { + SSHPort int `yaml:"ssh_port"` + HTTPPort int `yaml:"http_port"` +} + +type GameConfig struct { + TurnTimeoutSec int `yaml:"turn_timeout_sec"` + MaxPlayers int `yaml:"max_players"` + MaxFloors int `yaml:"max_floors"` + CoopBonus float64 `yaml:"coop_bonus"` + InventoryLimit int `yaml:"inventory_limit"` + SkillUses int `yaml:"skill_uses"` +} + +type CombatConfig struct { + FleeChance float64 `yaml:"flee_chance"` + MonsterScaling float64 `yaml:"monster_scaling"` + SoloHPReduction float64 `yaml:"solo_hp_reduction"` +} + +type DungeonConfig struct { + MapWidth int `yaml:"map_width"` + MapHeight int `yaml:"map_height"` + MinRooms int `yaml:"min_rooms"` + MaxRooms int `yaml:"max_rooms"` +} + +type BackupConfig struct { + IntervalMin int `yaml:"interval_min"` + Dir string `yaml:"dir"` +} + +func defaults() Config { + return Config{ + Server: ServerConfig{SSHPort: 2222, HTTPPort: 8080}, + Game: GameConfig{ + TurnTimeoutSec: 5, MaxPlayers: 4, MaxFloors: 20, + CoopBonus: 0.10, InventoryLimit: 10, SkillUses: 3, + }, + Combat: CombatConfig{FleeChance: 0.50, MonsterScaling: 1.15, SoloHPReduction: 0.50}, + Dungeon: DungeonConfig{MapWidth: 60, MapHeight: 20, MinRooms: 5, MaxRooms: 8}, + Backup: BackupConfig{IntervalMin: 60, Dir: "./data/backup"}, + } +} + +func Load(path string) (*Config, error) { + cfg := defaults() + if path == "" { + return &cfg, nil + } + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, err + } + return &cfg, nil +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..4468167 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,85 @@ +package config + +import ( + "os" + "testing" +) + +func TestLoadDefaults(t *testing.T) { + cfg, err := Load("") + if err != nil { + t.Fatal(err) + } + if cfg.Server.SSHPort != 2222 { + t.Errorf("expected SSH port 2222, got %d", cfg.Server.SSHPort) + } + if cfg.Server.HTTPPort != 8080 { + t.Errorf("expected HTTP port 8080, got %d", cfg.Server.HTTPPort) + } + if cfg.Game.TurnTimeoutSec != 5 { + t.Errorf("expected turn timeout 5, got %d", cfg.Game.TurnTimeoutSec) + } + if cfg.Game.MaxPlayers != 4 { + t.Errorf("expected max players 4, got %d", cfg.Game.MaxPlayers) + } + if cfg.Game.MaxFloors != 20 { + t.Errorf("expected max floors 20, got %d", cfg.Game.MaxFloors) + } + if cfg.Game.CoopBonus != 0.10 { + t.Errorf("expected coop bonus 0.10, got %f", cfg.Game.CoopBonus) + } + if cfg.Game.InventoryLimit != 10 { + t.Errorf("expected inventory limit 10, got %d", cfg.Game.InventoryLimit) + } + if cfg.Combat.FleeChance != 0.50 { + t.Errorf("expected flee chance 0.50, got %f", cfg.Combat.FleeChance) + } + if cfg.Combat.MonsterScaling != 1.15 { + t.Errorf("expected monster scaling 1.15, got %f", cfg.Combat.MonsterScaling) + } +} + +func TestLoadFromFile(t *testing.T) { + content := []byte(` +server: + ssh_port: 3333 + http_port: 9090 +game: + turn_timeout_sec: 10 + max_players: 2 +`) + f, err := os.CreateTemp("", "config-*.yaml") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + f.Write(content) + f.Close() + + cfg, err := Load(f.Name()) + if err != nil { + t.Fatal(err) + } + if cfg.Server.SSHPort != 3333 { + t.Errorf("expected SSH port 3333, got %d", cfg.Server.SSHPort) + } + if cfg.Server.HTTPPort != 9090 { + t.Errorf("expected HTTP port 9090, got %d", cfg.Server.HTTPPort) + } + if cfg.Game.TurnTimeoutSec != 10 { + t.Errorf("expected turn timeout 10, got %d", cfg.Game.TurnTimeoutSec) + } + if cfg.Game.MaxPlayers != 2 { + t.Errorf("expected max players 2, got %d", cfg.Game.MaxPlayers) + } + // Unset fields should have defaults + if cfg.Game.MaxFloors != 20 { + t.Errorf("expected default max floors 20, got %d", cfg.Game.MaxFloors) + } + if cfg.Combat.FleeChance != 0.50 { + t.Errorf("expected default flee chance 0.50, got %f", cfg.Combat.FleeChance) + } + if cfg.Dungeon.MapWidth != 60 { + t.Errorf("expected default map width 60, got %d", cfg.Dungeon.MapWidth) + } +} diff --git a/go.mod b/go.mod index 8a21088..4c8d1d7 100644 --- a/go.mod +++ b/go.mod @@ -41,4 +41,5 @@ require ( golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.23.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5d09ced..6bbf631 100644 --- a/go.sum +++ b/go.sum @@ -79,5 +79,6 @@ golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=