Skip to content

Commit 815cb78

Browse files
authored
重み付きプランナープールとしてダンジョンを選択できるようにする (#355)
2 parents 748e751 + af662b8 commit 815cb78

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1240
-626
lines changed

docs/design/8.md

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,9 @@ var PlannerTypeCave = PlannerType{
6464
package dungeon
6565

6666
type DungeonDefinition struct {
67-
ID string
6867
Name string
6968
Description string
7069
TotalFloors int // 総階層数
71-
MinLevel int // 解放条件
7270
EnemyTableName string // 敵テーブル
7371
ItemTableName string // アイテムテーブル
7472
PlannerPool []PlannerWeight // 使用するステージ種類
@@ -77,11 +75,6 @@ type DungeonDefinition struct {
7775
type PlannerWeight struct {
7876
PlannerType mapplanner.PlannerType
7977
Weight int
80-
Config PlannerConfig // Planner固有の設定
81-
}
82-
83-
type PlannerConfig struct {
84-
WallType WallType // 壁種類
8578
}
8679
```
8780

@@ -93,7 +86,6 @@ type PlannerConfig struct {
9386

9487
```go
9588
var DungeonForest = DungeonDefinition{
96-
ID: "forest",
9789
Name: "迷いの森",
9890
Description: "木々が生い茂る危険な森",
9991
TotalFloors: 10,
@@ -104,29 +96,19 @@ var DungeonForest = DungeonDefinition{
10496
{
10597
PlannerType: PlannerTypeForest,
10698
Weight: 5,
107-
Config: PlannerConfig{
108-
WallType: WallTypeTree,
109-
},
11099
},
111100
{
112101
PlannerType: PlannerTypeSmallRoom,
113102
Weight: 2,
114-
Config: PlannerConfig{
115-
WallType: WallTypeTree, // 森ダンジョンでも木の壁
116-
},
117103
},
118104
{
119105
PlannerType: PlannerTypeBigRoom,
120106
Weight: 1,
121-
Config: PlannerConfig{
122-
WallType: WallTypeTree,
123-
},
124107
},
125108
},
126109
}
127110

128111
var DungeonCave = DungeonDefinition{
129-
ID: "cave",
130112
Name: "暗い洞窟",
131113
Description: "光の届かない深い洞窟",
132114
TotalFloors: 15,
@@ -137,29 +119,19 @@ var DungeonCave = DungeonDefinition{
137119
{
138120
PlannerType: PlannerTypeCave,
139121
Weight: 6,
140-
Config: PlannerConfig{
141-
WallType: WallTypeRock,
142-
},
143122
},
144123
{
145124
PlannerType: PlannerTypeSmallRoom,
146125
Weight: 1,
147-
Config: PlannerConfig{
148-
WallType: WallTypeRock,
149-
},
150126
},
151127
{
152128
PlannerType: PlannerTypeBigRoom,
153129
Weight: 2,
154-
Config: PlannerConfig{
155-
WallType: WallTypeRock,
156-
},
157130
},
158131
},
159132
}
160133

161134
var DungeonRuins = DungeonDefinition{
162-
ID: "ruins",
163135
Name: "古代遺跡",
164136
Description: "かつて栄えた文明の跡",
165137
TotalFloors: 20,
@@ -170,23 +142,14 @@ var DungeonRuins = DungeonDefinition{
170142
{
171143
PlannerType: PlannerTypeSmallRoom,
172144
Weight: 4,
173-
Config: PlannerConfig{
174-
WallType: WallTypeBrick,
175-
},
176145
},
177146
{
178147
PlannerType: PlannerTypeRuins,
179148
Weight: 3,
180-
Config: PlannerConfig{
181-
WallType: WallTypeStone,
182-
},
183149
},
184150
{
185151
PlannerType: PlannerTypeBigRoom,
186152
Weight: 2,
187-
Config: PlannerConfig{
188-
WallType: WallTypeBrick,
189-
},
190153
},
191154
},
192155
}
@@ -260,20 +223,8 @@ internal/states/
260223
└─────────────────────────────────┘
261224
```
262225

263-
### 7. データ保存
264-
265-
```go
266-
// セーブデータに含める
267-
type DungeonProgress struct {
268-
DungeonID string // 現在挑戦中のダンジョン
269-
CurrentFloor int // 現在階層
270-
Seed uint64 // マップ生成シード(再現性のため)
271-
}
272-
```
273-
274226
### 8. 今後の拡張案
275227

276-
- FloorConfig追加(階層ごとに壁/敵テーブルを変えたい場合)
277228
- ダンジョン固有のギミック(毒沼、溶岩など)
278229
- ランダムイベント(宝箱部屋、商人など)
279230
- 難易度選択

docs/design/9.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# シード管理設計
2+
3+
## 概要
4+
5+
ゲームの乱数を管理し、再現可能なゲームプレイを実現する。
6+
7+
- **ステージ生成**: stageSeed で再現可能(深度に依存しない)
8+
- **ゲームプレイ**: world.Config.RNG を使用(同じ操作なら再現可能)
9+
10+
## 初期化フロー
11+
12+
### 1. Config読み込み時 (`config.Load()`)
13+
14+
```go
15+
// 環境変数 RUINS_SEED が設定されている場合
16+
if os.Getenv("RUINS_SEED") != "" {
17+
cfg.Seed = 環境変数の値
18+
} else {
19+
// 未設定の場合はランダム生成
20+
cfg.Seed = rand.Uint64()
21+
}
22+
23+
// SeedからRNGを生成
24+
cfg.RNG = rand.New(rand.NewPCG(cfg.Seed, 0))
25+
```
26+
27+
### 2. ステージ生成時 (`DungeonState.OnStart()`)
28+
29+
```go
30+
// ステージ用シードをRNGから生成
31+
stageSeed := world.Config.RNG.Uint64()
32+
33+
// SelectPlanner用のRNGはstageSeedから生成
34+
stageRNG := rand.New(rand.NewPCG(stageSeed, 0))
35+
builderType, err := dungeon.SelectPlanner(def, stageRNG)
36+
37+
// PlanにはstageSeedを渡す(内部で独自にRNGを作成)
38+
plan, err := mapplanner.Plan(world, ..., stageSeed, builderType)
39+
```
40+
41+
### 3. ゲームプレイ中
42+
43+
```go
44+
// 戦闘、ドロップなどは world.Config.RNG を使用
45+
weaponName, err := commandTable.SelectByWeight(world.Config.RNG)
46+
materialName, err := dropTable.SelectByWeight(world.Config.RNG)
47+
```
48+
49+
## 再現性
50+
51+
| シナリオ | 再現性 |
52+
|---------|--------|
53+
| 同じ RUINS_SEED + 同じ操作 | 完全再現可能 |
54+
| 同じ stageSeed | 同じステージを再現可能 |
55+
| 異なる操作 | ステージは同じ、以降は異なる |
56+
57+
## RNG使用箇所
58+
59+
| 用途 | 使用するRNG |
60+
|------|------------|
61+
| プランナー選択 | stageSeed から生成した RNG |
62+
| マップ生成 | stageSeed(Plan内部でRNG作成) |
63+
| 攻撃選択 | world.Config.RNG |
64+
| ドロップ選択 | world.Config.RNG |

internal/cmd/generate_enemy_doc.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,7 @@ func calculateEnemyProbabilities(table raw.EnemyTable, depth int) map[string]flo
137137
// 深度範囲内のエントリをフィルタリング
138138
validEntries := make([]raw.EnemyTableEntry, 0, len(table.Entries))
139139
for _, entry := range table.Entries {
140-
// MinDepthチェック(0は制限なし)
141-
if entry.MinDepth > 0 && depth < entry.MinDepth {
142-
continue
143-
}
144-
// MaxDepthチェック(0は制限なし)
145-
if entry.MaxDepth > 0 && depth > entry.MaxDepth {
140+
if depth < entry.MinDepth || depth > entry.MaxDepth {
146141
continue
147142
}
148143
validEntries = append(validEntries, entry)

internal/cmd/generate_item_doc.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,7 @@ func calculateProbabilities(table raw.ItemTable, depth int) map[string]float64 {
137137
// 深度範囲内のエントリをフィルタリング
138138
validEntries := make([]raw.ItemTableEntry, 0, len(table.Entries))
139139
for _, entry := range table.Entries {
140-
// MinDepthチェック(0は制限なし)
141-
if entry.MinDepth > 0 && depth < entry.MinDepth {
142-
continue
143-
}
144-
// MaxDepthチェック(0は制限なし)
145-
if entry.MaxDepth > 0 && depth > entry.MaxDepth {
140+
if depth < entry.MinDepth || depth > entry.MaxDepth {
146141
continue
147142
}
148143
validEntries = append(validEntries, entry)

internal/cmd/play.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ var CmdPlay = &cli.Command{
3232

3333
func runPlay(_ context.Context, _ *cli.Command) error {
3434
// 設定を読み込み
35-
cfg := config.Get()
35+
cfg, err := config.Load()
36+
if err != nil {
37+
return fmt.Errorf("設定の読み込みに失敗: %w", err)
38+
}
3639

3740
// ログ設定を読み込み
3841
logger.LoadFromConfig(cfg.LogLevel, cfg.LogCategories)
@@ -90,7 +93,7 @@ func runPlay(_ context.Context, _ *cli.Command) error {
9093
}()
9194
}
9295

93-
world, err := maingame.InitWorld(cfg.WindowWidth, cfg.WindowHeight)
96+
world, err := maingame.InitWorld(cfg)
9497
if err != nil {
9598
return err
9699
}

internal/cmd/screenshot.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,21 @@ func runScreenshot(_ context.Context, cmd *cli.Command) error {
2727
return fmt.Errorf("引数が不足している。ステート名が必要")
2828
}
2929

30-
// 固定seed値を使用したtown dungeon state
31-
const townSeedVal = 1
32-
townStateFactory := gs.NewTownState(gs.WithSeed(townSeedVal))
30+
townStateFactory := gs.NewTownState()
3331

3432
switch mode {
3533
case gs.CraftMenuState{}.String():
3634
return vrt.RunTestGame(mode, townStateFactory(), &gs.CraftMenuState{})
3735
case "DebugMenu":
3836
return vrt.RunTestGame(mode, townStateFactory(), gs.NewDebugMenuState())
3937
case gs.DungeonState{}.String():
40-
// 固定seed値を使用する
41-
seedVal := uint64(1)
4238
return vrt.RunTestGame(mode, &gs.DungeonState{
4339
Depth: 1,
44-
Seed: &seedVal,
4540
BuilderType: mapplanner.PlannerTypeSmallRoom,
4641
})
4742
case gs.FieldInfoState{}.String():
48-
// 固定seed値を使用したダンジョンの上に視界情報画面を重ねる
49-
seedVal := uint64(1)
5043
return vrt.RunTestGame(mode, &gs.DungeonState{
5144
Depth: 1,
52-
Seed: &seedVal,
5345
BuilderType: mapplanner.PlannerTypeSmallRoom,
5446
}, &gs.FieldInfoState{})
5547
case gs.EquipMenuState{}.String():

internal/config/config.go

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package config
22

33
import (
4+
"math/rand/v2"
45
"os"
5-
6-
"github.com/caarlos0/env/v11"
76
)
87

98
// Profile は設定プロファイルを表す
@@ -41,6 +40,13 @@ type Config struct {
4140
StartingState string `env:"RUINS_STARTING_STATE"`
4241
DisableAnimation bool `env:"RUINS_DISABLE_ANIMATION"`
4342

43+
// 乱数シード。環境変数で指定すると再現可能になる
44+
// 未指定の場合は自動生成される
45+
Seed uint64 `env:"RUINS_SEED"`
46+
47+
// 乱数生成器。Seedから生成される
48+
RNG *rand.Rand
49+
4450
// パフォーマンス設定
4551
TargetFPS int `env:"RUINS_TARGET_FPS"`
4652
ProfileMemory bool `env:"RUINS_PROFILE_MEMORY"`
@@ -50,36 +56,13 @@ type Config struct {
5056
ProfilePath string `env:"RUINS_PROFILE_PATH"`
5157
}
5258

53-
// load は環境変数から設定を読み込む
54-
func load() (*Config, error) {
55-
cfg := &Config{}
56-
57-
// プロファイルを最初に決定(デフォルトはproduction)
58-
profile := os.Getenv("RUINS_PROFILE")
59-
if profile == "" {
60-
cfg.Profile = ProfileProduction
61-
} else {
62-
cfg.Profile = Profile(profile)
63-
}
64-
65-
// プロファイルに基づくデフォルト値を設定
66-
cfg.applyProfileDefaults()
67-
68-
// 環境変数で明示的に設定された値で上書き
69-
if err := env.Parse(cfg); err != nil {
70-
return nil, err
71-
}
72-
73-
return cfg, nil
74-
}
75-
76-
// applyProfileDefaults はプロファイルに基づいてデフォルト値を設定する
77-
func (c *Config) applyProfileDefaults() {
59+
// ApplyProfileDefaults はプロファイルに基づいてデフォルト値を設定する
60+
func (c *Config) ApplyProfileDefaults() {
7861
switch c.Profile {
79-
case ProfileDevelopment:
80-
c.applyDevelopmentDefaults()
8162
case ProfileProduction:
8263
c.applyProductionDefaults()
64+
case ProfileDevelopment:
65+
c.applyDevelopmentDefaults()
8366
default:
8467
// デフォルトは本番設定
8568
c.applyProductionDefaults()

0 commit comments

Comments
 (0)