GitHub CopilotとペアプロTDDでローグライクRPGを作る本メモ
Unityの本だけどドメイン部分のロジック実装ということでGoで書きながら読んだでみた。
4章ダンジョンの作成
ダンジョンの作成方法を聞いてみる。
ローグライクRPGのダンジョンを自動生成する方法を教えて
ローグライクRPGのダンジョン自動生成には様々なアルゴリズムがありますが、一般的な手法の一つに「部屋と通路」のアプローチがあります。以下にその基本的な手順を説明します。
- マップを初期化:最初に、全てのタイルを壁に設定します。
- 部屋を生成:ランダムな位置、大きさで部屋を生成します。部屋が他の部屋と重ならないことを確認します。
- 部屋をつなげる通路を生成:部屋と部屋をつなげる通路を生成します。これには、例えば最短経路探索アルゴリズムなどが使えます。
-(オプション)追加の特徴を生成:階段、トラップ、宝箱などの特徴をランダムな位置に生成します。
-(オプション)モンスターやアイテムを配置:部屋や通路にモンスターやアイテムをランダムに配置します。
この手順は基本的なもので、より複雑なダンジョンを生成するためには、迷路生成アルゴリズム、パーリンノイズ、セルオートマトンなどの他のアルゴリズムを組み合わせることもあります。
また、ダンジョン生成の結果はゲームのバランスに大きく影響しますので、生成されたダンジョンがプレイヤーにとって適切な難易度となるように、パラメータの調整も重要です。
マップチップの定義
「ローグライク RPG のダンジョンに使用するマップチップの enum を作ってください。壁、部屋、通路、ドア、登り階段、降り階段を含めてくだ さい」
type MapTile int
const (
Wall MapTile = iota
Room
Corridor
Door
UpStairs
DownStairs
)
マップの初期化
マップ初期化処理のTODOリストは以下
- 指定サイズのマップ配列が生成されること
- マップ配列が壁で埋められていること
テストを書く
package dungeon_test
import (
"roguelike-tdd-go/dungeon"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMapGenerator(t *testing.T) {
testcases := map[string]struct {
width int
height int
}{
"20x10: 指定サイズのマップ配列が生成されること": {
width: 20,
height: 10,
},
"30x15: 指定サイズのマップ配列が生成されること": {
width: 30,
height: 15,
},
}
for name, tt := range testcases {
t.Run(name, func(t *testing.T) {
// Act
sut := dungeon.NewMapGenerator(tt.width, tt.height)
// Assert
if len(sut.Map) != tt.height {
t.Errorf("Map height: got %v, want %v", len(sut.Map), tt.height)
}
for _, row := range sut.Map {
if len(row) != tt.width {
t.Errorf("Map width: got %v, want %v", len(row), tt.width)
}
}
})
}
}
ポイントは以下
- なるべくマジックナンバーなどは避け、Copilotが正しいコードを推測しやすいようにヒントを与えるイメージ
- 入力と出力が明確になるようにテスト関数名なども注意する
- 自然言語に近いアサーション
- テストが書けたらテストファイルは開きっぱなしにしておく
次にマップのタイルが全てWallで初期化されている必要があるのでテストを追加する。
func TestMapGenerator_InitTile(t *testing.T) {
// Arrange
width, height := 20, 10
expected := make([][]dungeon.MapTile, height)
for i := range expected {
row := make([]dungeon.MapTile, width)
for j := range row {
row[j] = dungeon.Wall
}
expected[i] = row
}
// Act
sut := dungeon.NewMapGenerator(width, height)
// Assert
assert.Exactly(t, expected, sut.Map)
}
このテストをもとに書いてもらった実装が以下
package dungeon
type MapGenerator struct {
Map [][]MapTile
}
// NewMapGenerator は、指定されたサイズでマップタイルは全てWallで初期化されたMapGeneratorを生成します。
func NewMapGenerator(width, height int) *MapGenerator {
mg := &MapGenerator{
Map: make([][]MapTile, height),
}
for y := range mg.Map {
mg.Map[y] = make([]MapTile, width)
for x := range mg.Map[y] {
mg.Map[y][x] = Wall
}
}
return mg
}
- Wallで初期化する処理を書いてくれなかったのでコメントを追加した
- テストは全てパスしたのでとりあえずok
部屋の生成
部屋の生成処理のTODOリストは以下
- Room構造体の定義
- 指定した区画内にランダムな位置・サイズの部屋を生成するメソッド
- 指定した区画内に収まる位置・サイズの部屋を返すこと
- 部屋の最小サイズを指定できること
- 指定した区画と部屋のパディングを空けられること
- 部屋の位置・サイズがランダムであること
- マップを9区画に区切って9つの部屋を生成するメソッド
- 部屋が9つ生成されること
- 各部屋は9つの区画内に生成されること
- 部屋をマップ配列に書き込めること
Room構造体を作っていく
dungeonパッケージ配下に部屋を定義する構造体を定義してください。
package dungeon
type Room struct {
X int // Room's left top x-coordinate
Y int // Room's left top y-coordinate
Width int // Width of the room
Height int // Height of the room
}
ファクトリ関数も作って欲しかったので
ファクトリ関数も作成してください。
package dungeon
// NewRoom は、指定された位置とサイズで新しいRoomを生成します。
func NewRoom(x, y, width, height int) *Room {
return &Room{
X: x,
Y: y,
Width: width,
Height: height,
}
}
ここで最初に定義したMapTile定数のRoomと構造体のRoomの命名が衝突してコンパイルエラーとなってしまいました。
MapTile定数のRoomと構造体のRoomが名前衝突してしまいました。どちらかの命名を修正してください。
確かに、MapTile の定数と Room 構造体が名前衝突を起こしています。この問題を解決するために、MapTile の Room 定数の名前を RoomTile に変更します。
以下に修正後のコードを示します。
package dungeon
type MapTile int
const (
Undefined MapTile = iota
Wall
RoomTile
Passage
Door
UpStairs
DownStairs
)
これで、RoomTile はマップチップを表し、Room 構造体は部屋の位置とサイズを表すようになりました。
指定した区画内に収まる位置・サイズの部屋を返すことを検証するテストを書く
package dungeon_test
import (
"fmt"
"roguelike-tdd-go/dungeon"
"testing"
"github.com/stretchr/testify/assert"
)
type testCase struct {
left int
top int
right int
bottom int
}
func (tt *testCase) name(s string) string {
return fmt.Sprintf("%s: left=%d, top=%d, right=%d, bottom=%d", s, tt.left, tt.top, tt.right, tt.bottom)
}
func TestNewRoomInBounds(t *testing.T) {
testcases := map[string]testCase{
"部屋が指定区画内に作られること": {
left: 0,
top: 0,
right: 5,
bottom: 5,
},
"部屋が指定区画内に作られること2": {
left: 20,
top: 10,
right: 39,
bottom: 19,
},
}
for name, tt := range testcases {
t.Run(tt.name(name), func(t *testing.T) {
// Arrange
roomMaxWidth := tt.right - tt.left + 1
roomMaxHeight := tt.bottom - tt.top + 1
// Act
sut := dungeon.NewRoomInBounds(tt.left, tt.top, tt.right, tt.bottom)
// Assert
assert.True(t, tt.left <= sut.X && sut.X <= tt.right)
assert.True(t, tt.top <= sut.Y && sut.Y <= tt.bottom)
assert.True(t, sut.Width <= roomMaxWidth)
assert.True(t, sut.Height <= roomMaxHeight)
})
}
}
ポイント
- 実際のコードは疑似乱数を使いランダム性を持たせる必要がある
- が、テストコードではそれは表現されておらず、匂わせることが大事
- ランダム性のあるテストなので本書では100回くらい繰り返し実行をしていた
- こういうときはPBTの使いどきかもしれない
生成されたコード
package dungeon
import (
"math/rand"
)
type Room struct {
X int // Room's left top x-coordinate
Y int // Room's left top y-coordinate
Width int // Width of the room
Height int // Height of the room
}
// NewRoom は、指定された位置とサイズで新しいRoomを生成します。
func NewRoom(x, y, width, height int) *Room {
return &Room{
X: x,
Y: y,
Width: width,
Height: height,
}
}
// 指定された区画内にランダムな位置とサイズで新しいRoomを生成します。
// Roomの最小サイズは3x3です。
// rand.Intn(n)のnは1以上の整数が指定されるようにし、関数内でpanicは起きないものとする。
func NewRoomInBounds(left, top, right, bottom int) *Room {
roomMaxWidth := right - left + 1
roomMaxHeight := bottom - top + 1
// Roomの最小サイズは3x3
width := 3 + rand.Intn(roomMaxWidth-3)
height := 3 + rand.Intn(roomMaxHeight-3)
x := left + rand.Intn(roomMaxWidth-width)
y := top + rand.Intn(roomMaxHeight-height)
return &Room{
X: x,
Y: y,
Width: width,
Height: height,
}
}
コメント書いて頑張ったりしたけど正しいコードにならない。最小サイズが3x3で固定なのは本書もそうなのでいいけど、生成される乱数の範囲が微妙に違いそう。
と思ったけど正しそう。疑似乱数生成のseedの初期化だけ漏れているので追記。外部からseedは指定できたほうがテストしやすいのかもしれないがどっちがいいかまだわからないので一旦関数内で初期化。
// 指定された区画内にランダムな位置とサイズで新しいRoomを生成します。
// Roomの最小サイズは3x3です。
func NewRoomInBounds(left, top, right, bottom int) *Room {
roomMaxWidth := right - left + 1
roomMaxHeight := bottom - top + 1
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Roomの最小サイズは3x3
width := 3 + r.Intn(roomMaxWidth-3)
height := 3 + r.Intn(roomMaxHeight-3)
x := left + r.Intn(roomMaxWidth-width)
y := top + r.Intn(roomMaxHeight-height)
return &Room{
X: x,
Y: y,
Width: width,
Height: height,
}
}
と思ったけどなんか違う気がしたので以下のように修正。Copilotのみ編集の縛り破ってしまった。
// 指定された区画内にランダムな位置とサイズで新しいRoomを生成します。
// Roomの最小サイズは3x3です。
func NewRoomInBounds(left, top, right, bottom int) *Room {
roomMaxWidth := right - left + 1
roomMaxHeight := bottom - top + 1
r := rand.New(rand.NewSource(time.Now().UnixNano()))
const minimumRoomSize = 3
var (
width = minimumRoomSize
height = minimumRoomSize
x = left
y = top
)
// Roomの最小サイズは3x3
if roomMaxWidth > minimumRoomSize {
width = minimumRoomSize + r.Intn(roomMaxWidth-minimumRoomSize)
}
if roomMaxHeight > minimumRoomSize {
height = minimumRoomSize + r.Intn(roomMaxHeight-minimumRoomSize)
}
if right-width+1-left > 0 {
x = left + r.Intn(right-width+1-left)
}
if bottom-height+1-top > 0 {
y = top + r.Intn(bottom-height+1-top)
}
return &Room{
X: x,
Y: y,
Width: width,
Height: height,
}
}
一応テストは通るので先に進む。
こういう境界値が重要なやつ難しい。。
部屋の最小サイズの指定
テストを修正
func TestNewRoomInBounds(t *testing.T) {
testcases := map[string]testCase{
"部屋が指定区画内に作られること": {
left: 0,
top: 0,
right: 5,
bottom: 5,
minRoomSize: 1,
},
"部屋が指定区画内に作られること2": {
left: 20,
top: 10,
right: 39,
bottom: 19,
minRoomSize: 5,
},
}
for name, tt := range testcases {
t.Run(tt.name(name), func(t *testing.T) {
// Arrange
roomMaxWidth := tt.right - tt.left + 1
roomMaxHeight := tt.bottom - tt.top + 1
// Act
sut := dungeon.NewRoomInBounds(tt.left, tt.top, tt.right, tt.bottom, tt.minRoomSize)
// Assert
assert.True(t, tt.left <= sut.X && sut.X <= tt.right)
assert.True(t, tt.top <= sut.Y && sut.Y <= tt.bottom)
assert.True(t, tt.minRoomSize <= sut.Width && sut.Width <= roomMaxWidth)
assert.True(t, tt.minRoomSize <= sut.Height && sut.Height <= roomMaxHeight)
})
}
}
実装も作り直してみる
// 指定された区画内にランダムな位置とサイズで新しいRoomを生成します。
func NewRoomInBounds(left, top, right, bottom, minRoomSize int) *Room {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
roomMaxWidth := right - left + 1
roomMaxHeight := bottom - top + 1
width := r.Intn(roomMaxWidth-minRoomSize) + minRoomSize
height := r.Intn(roomMaxHeight-minRoomSize) + minRoomSize
x := r.Intn(right-width+1-left) + left
y := r.Intn(bottom-height+1-top) + top
return NewRoom(x, y, width, height)
}
- 乱数の初期化をしてくれない
- x, yの生成がなんか違いそうなの提案してくる
- r.Intn()に指定する値が0以下だとpanicするが、なんか怪しい匂いがする
- panicしそうな値の組み合わせのテストを作って安心したくなる
- その際はpanicさせるよりもerrorを返すようにしたほうがテストしやすいかも
- そうなると関数内でバリデーションロジックを書く必要がある
指定した区画と部屋のパディングを空けられるようにする
マップの外周を壁にするためと通路を通すことを考え、パディングを指定できるようにする。
テスト修正
func TestNewRoomInBounds(t *testing.T) {
testcases := map[string]testCase{
"部屋が指定区画内に作られること": {
left: 0,
top: 0,
right: 5,
bottom: 5,
minRoomSize: 1,
padding: 0,
},
"部屋が指定区画内に作られること2": {
left: 20,
top: 10,
right: 39,
bottom: 19,
minRoomSize: 5,
padding: 2,
},
}
for name, tt := range testcases {
t.Run(tt.name(name), func(t *testing.T) {
// Arrange
roomMaxWidth := tt.right - tt.left + 1 - tt.padding*2
roomMaxHeight := tt.bottom - tt.top + 1 - tt.padding*2
roomLeft := tt.left + tt.padding
roomTop := tt.top + tt.padding
roomRight := tt.right - tt.padding
roomBottom := tt.bottom - tt.padding
// Act
sut := dungeon.NewRoomInBounds(
tt.left,
tt.top,
tt.right,
tt.bottom,
func(opt *dungeon.Option) {
opt.MinRoomSize = tt.minRoomSize
opt.Padding = tt.padding
},
)
// Assert
assert.True(t, roomLeft <= sut.X && sut.X <= roomRight)
assert.True(t, roomTop <= sut.Y && sut.Y <= roomBottom)
assert.True(t, tt.minRoomSize <= sut.Width && sut.Width <= roomMaxWidth)
assert.True(t, tt.minRoomSize <= sut.Height && sut.Height <= roomMaxHeight)
})
}
}
ついでに最小roomサイズとパディングの指定はオプションとするように変更してみた。実装の作り直しは以下。
type Option struct {
MinRoomSize int
Padding int
}
type Options func(*Option)
// 指定された区画内にランダムな位置とサイズで新しいRoomを生成します。
// minRoomSizeとpaddingはデフォルト値を持ち、省略可能
// minRooSizeとpaddingはfunctionally optionとして扱う
func NewRoomInBounds(left, top, right, bottom int, options ...Options) *Room {
opt := &Option{
MinRoomSize: 1,
Padding: 0,
}
for _, o := range options {
o(opt)
}
roomMaxWidth := right - left + 1 - opt.Padding*2
roomMaxHeight := bottom - top + 1 - opt.Padding*2
roomLeft := left + opt.Padding
roomTop := top + opt.Padding
// roomRight := right - opt.Padding
// roomBottom := bottom - opt.Padding
r := rand.New(rand.NewSource(time.Now().UnixNano()))
roomWidth := r.Intn(roomMaxWidth-opt.MinRoomSize) + opt.MinRoomSize
roomHeight := r.Intn(roomMaxHeight-opt.MinRoomSize) + opt.MinRoomSize
roomX := r.Intn(roomMaxWidth-roomWidth) + roomLeft
roomY := r.Intn(roomMaxHeight-roomHeight) + roomTop
return NewRoom(roomX, roomY, roomWidth, roomHeight)
}
- widthとheightを+1しないと最大値を生成できない
- 相変わらずxとyのロジックが合ってるのかわからない
- 一応テストは通るから進んじゃう
部屋の位置、サイズがランダムであること
100回Room作成し、RoomのX座標を保存しておく。100回終わったあとに保存していた件数が1件よりも多いことを確認して、ランダム生成されていることを確認する。
func TestNewRoomInBounds_RoomSizeRandom(t *testing.T) {
left, top, right, bottom := 0, 0, 19, 9
xDistribution := make(map[int]struct{})
for range 100 {
sut := dungeon.NewRoomInBounds(left, top, right, bottom)
xDistribution[sut.X] = struct{}{}
}
assert.True(t, len(xDistribution) > 1)
}
- ランダム性のあるテストだとこういうことしなきゃいけない
- 難しい
- PBTでなんか疑似乱数のテストの話あったようななかったような
指定した最大サイズの部屋が作られること
テスト
func TestNewRoomInBounds_MakeMaxSizeRoom(t *testing.T) {
// Arrange 固定値
left, top, right, bottom := 0, 0, 4, 4
minRoomSize := 2
padding := 1
// 生成Roomは9パターン
expected := map[int]dungeon.Room{
1122: {X: 1, Y: 1, Width: 2, Height: 2},
2122: {X: 2, Y: 1, Width: 2, Height: 2},
1222: {X: 1, Y: 2, Width: 2, Height: 2},
2222: {X: 2, Y: 2, Width: 2, Height: 2},
1132: {X: 1, Y: 1, Width: 3, Height: 2},
1232: {X: 1, Y: 2, Width: 3, Height: 2},
1123: {X: 1, Y: 1, Width: 2, Height: 3},
2123: {X: 2, Y: 1, Width: 2, Height: 3},
1133: {X: 1, Y: 1, Width: 3, Height: 3},
}
roomDistribution := make(map[int]dungeon.Room)
// Act
for range 100 {
sut := dungeon.NewRoomInBounds(left, top, right, bottom, func(opt *dungeon.Option) {
opt.MinRoomSize = minRoomSize
opt.Padding = padding
})
roomDistribution[sut.X*1000+sut.Y*100+sut.Width*10+sut.Height] = *sut
}
// Assert
assert.Equal(t, expected, roomDistribution)
}
- SetがGoにはないのでMapで表現
- 100回実行して生成ルームパターンを全部洗い出す
- それが想定通りの9パターンか検証するテスト
- しかし、これは失敗する
これまでの実装もやはりおかしかったのでもろもろ修正
func NewRoomInBounds(left, top, right, bottom int, options ...Options) *Room {
opt := &Option{
MinRoomSize: 1,
Padding: 0,
}
for _, o := range options {
o(opt)
}
roomMaxWidth := right - left + 1 - opt.Padding*2
roomMaxHeight := bottom - top + 1 - opt.Padding*2
roomLeft := left + opt.Padding
roomTop := top + opt.Padding
roomRight := right - opt.Padding
roomBottom := bottom - opt.Padding
r := rand.New(rand.NewSource(time.Now().UnixNano()))
roomWidth := r.Intn(roomMaxWidth+1-opt.MinRoomSize) + opt.MinRoomSize
roomHeight := r.Intn(roomMaxHeight+1-opt.MinRoomSize) + opt.MinRoomSize
roomX, roomY := roomLeft, roomTop
if roomRight-roomWidth+2-roomLeft > 0 {
roomX = r.Intn(roomRight-roomWidth+2-roomLeft) + roomLeft
}
if roomBottom-roomHeight+2-roomTop > 0 {
roomY = r.Intn(roomBottom-roomHeight+2-roomTop) + roomTop
}
return NewRoom(roomX, roomY, roomWidth, roomHeight)
}
乱数生成するときに0以下を指定するとpanicするのでガード節が必要。ここ微妙なのでなんとかしたい。validationロジックを最低でもいれたほうがいいのかもしれない。
部屋の座標計算プロパティの追加
テスト
func TestRight(t *testing.T) {
// Arrange
testcases := map[string]struct {
x int
width int
right int
}{
"when x: 0 width: 10, expected right: 9": {x: 0, width: 10, right: 9},
"when x: 5 width: 15, expected right: 19": {x: 5, width: 15, right: 19},
}
for name, tt := range testcases {
t.Run(name, func(t *testing.T) {
// Act
sut := dungeon.NewRoom(tt.x, 0, tt.width, 0)
// Assert
assert.Equal(t, tt.right, sut.Right())
})
}
}
提案された実装
func (r *Room) Right() int {
return r.X + r.Width - 1
}
テストもパスした。こういう入力と出力がわかりやすいのはテストも書きやすいし、実装も提案されやすそう。
部屋をマップ配列に書き込めることを検証
本書ではroom構造体の関数として実装していたが、MapGeneratorがマップ配列を状態として保持しているので状態を書き換えるような処理はMapGenerator自身に持たせることにした。
テストは以下
func TestMapGenerator_Write(t *testing.T) {
// Arrange
mapWidth, mapHeight := 4, 2
roomX, roomY, roomWidth, roomHeight := 1, 0, 2, 1
expected := [][]dungeon.MapTile{
{dungeon.Wall, dungeon.RoomTile, dungeon.RoomTile, dungeon.Wall},
{dungeon.Wall, dungeon.Wall, dungeon.Wall, dungeon.Wall},
}
room := dungeon.NewRoom(roomX, roomY, roomWidth, roomHeight)
// Act
sut := dungeon.NewMapGenerator(mapWidth, mapHeight)
sut.Write(room)
// Assert
assert.Exactly(t, expected, sut.Map)
}
提案された実装
func (mg *MapGenerator) Write(room *Room) {
for y := room.Y; y < room.Y+room.Height; y++ {
for x := room.X; x < room.X+room.Width; x++ {
mg.Map[y][x] = RoomTile
}
}
}
テストも通った
部屋が9つ生成されることの検証
テスト
func TestCreateRooms(t *testing.T) {
// Arrange
minRooSize := 1
padding := 0
testcases := map[string]struct {
width int
height int
}{
"15 x12のマップが9つ作成されること": {width: 15, height: 12},
"18 x 15のマップが9つ作成されること": {width: 18, height: 15},
}
for name, tt := range testcases {
t.Run(name, func(t *testing.T) {
// Act
sut := dungeon.CreateRooms(tt.width, tt.height, minRooSize, padding)
// Assert
assert.Len(t, sut, 9)
})
}
}
提案された実装(うまく生成されなかったのでコメント書いた)
// CreateRoomsは、指定した区画内に9つの部屋を生成します。
func CreateRooms(width, height, minRoomSize, padding int) []*Room {
rooms := make([]*Room, 9)
for i := range rooms {
rooms[i] = NewRoomInBounds(0, 0, width-1, height-1, func(opt *Option) {
opt.MinRoomSize = minRoomSize
opt.Padding = padding
})
}
return rooms
}
9つの部屋は3x3に区切られて生成されて欲しいのでテストを修正する
func TestCreateRooms(t *testing.T) {
// Arrange
width, height := 15, 12
minRooSize := 2
padding := 1
testcases := map[string]struct {
index int
left int
top int
right int
bottom int
}{
"部屋1": {index: 0, left: 1, top: 1, right: 3, bottom: 2},
"部屋2": {index: 1, left: 6, top: 1, right: 9, bottom: 2},
"部屋3": {index: 2, left: 11, top: 1, right: 13, bottom: 2},
"部屋4": {index: 3, left: 1, top: 5, right: 3, bottom: 6},
"部屋5": {index: 4, left: 6, top: 5, right: 9, bottom: 6},
"部屋6": {index: 5, left: 11, top: 5, right: 13, bottom: 6},
"部屋7": {index: 6, left: 1, top: 9, right: 3, bottom: 11},
"部屋8": {index: 7, left: 6, top: 9, right: 9, bottom: 11},
"部屋9": {index: 8, left: 11, top: 9, right: 13, bottom: 11},
}
for name, tt := range testcases {
t.Run(name, func(t *testing.T) {
// Act
sut := dungeon.CreateRooms(width, height, minRooSize, padding)
// Assert
assert.True(t, tt.left <= sut[tt.index].X && sut[tt.index].X <= tt.right)
assert.True(t, tt.top <= sut[tt.index].Y && sut[tt.index].Y <= tt.bottom)
assert.True(t, tt.left <= sut[tt.index].Right() && sut[tt.index].Right() <= tt.right)
})
}
}
実装も修正。テストが通るまでコメント追記していく。
// CreateRoomsは、指定した区画内に9つの部屋を生成します。
// 生成される部屋は指定した区画内に重ならないように配置されます。
// 部屋のwidthは指定されたwidthを3分割することで決定されます。
// 部屋のheightは指定されたheightを3分割することで決定されます。
// 部屋はNewRoomInBounds関数を使用して生成されます。
func CreateRooms(width, height, minRoomSize, padding int) []*Room {
rooms := make([]*Room, 0, 9)
for y := 0; y < 3; y++ {
for x := 0; x < 3; x++ {
roomWidth := width / 3
roomHeight := height / 3
room := NewRoomInBounds(
roomWidth*x,
roomHeight*y,
roomWidth*(x+1)-1,
roomHeight*(y+1)-1,
func(opt *Option) {
opt.MinRoomSize = minRoomSize
opt.Padding = padding
},
)
rooms = append(rooms, room)
}
}
return rooms
}
部屋を通路でつなぐ
TODOリストは以下
- 2つの部屋をつなぐ通路を生成できること
- 通路をマップ配列に書き込めること
- 外周の8つの部屋は環状に通路で繋がること
- 中央の部屋はランダムな部屋と通路でつながること