feat: add configuration package with YAML loading and defaults

Add config package that loads game settings from YAML files with
sensible defaults for server, game, combat, dungeon, and backup
settings. Includes config.yaml with all defaults documented.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 12:59:44 +09:00
parent 089d5c76ed
commit 0f524779c0
5 changed files with 206 additions and 0 deletions

75
config/config.go Normal file
View File

@@ -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
}

85
config/config_test.go Normal file
View File

@@ -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)
}
}