読者です 読者をやめる 読者になる 読者になる

セルオートマトンで洞窟を作る

セルオートマトンを使うと、手軽に洞窟のようなマップを生成することができます。

ランダムに埋める

ある座標が壁かどうかをboolで表現した二次元配列をマップとします。まずマップをランダムに埋め尽くします。今回の例では壁のセルと壁でないセルの比率が同じぐらいになるように設定しました。もちろん壁のセルを多め、もしくは少なめにしてもかまいません。

滑らかに

先ほど作ったランダムなマップをセルオートマトンを使って滑らかにしていきます。有名なセルオートマトンであるライフゲームでは周囲のセルの状況と、自身のセルの状況から次のセルが決定されます。今回は単純に周囲8セルに存在する壁のセルを数え、4セルより多い場合は壁のセル、4セルより少ない場合は壁でないセル、それ以外はそのままとしました。隅っこのセルを調べる際、領域外にアクセスしますが、領域外は壁として扱いました。実際に滑らかになっていく様子を見てみましょう。
n=0
f:id:usiatan2:20170405164427p:plain
n=1
f:id:usiatan2:20170405164439p:plain
n=2
f:id:usiatan2:20170405164447p:plain
n=3
f:id:usiatan2:20170405164454p:plain
n=4
f:id:usiatan2:20170405164529p:plain

繰り返しセルオートマトンを実行することで孤立した小さな壁が消え、ギザギザした壁を滑らかになっています。

ソースコード

import random

class Cave:
    def __init__(self, w, h, n_smooth):
        self.w = w
        self.h = h
        self.data = []
        for y in range(self.h):
            row = []
            for x in range(self.w):
                if x == 0 or y == 0 or x == w - 1 or y == h - 1:
                    row.append(True)
                else:
                    row.append(random.random() < 0.5)
            self.data.append(row)
        
        for i in range(n_smooth):
            self.smooth()
        
    def smooth(self):
        new_data = self.data[:]
        for y in range(self.h):
            for x in range(self.w):
                count = self.wall_count(x, y)
                if count > 4:
                    new_data[y][x] = True
                elif count < 4:
                    new_data[y][x] = False
                else:
                    new_data[y][x] = self.data[y][x]
        self.data = new_data
    
    def wall_count(self, cx, cy):
        count = 0
        for y in range(cy - 1, cy + 2, 1):
            for x in range(cx - 1, cx + 2, 1):
                if x == cx and y == cy:
                    continue
                if self.in_range(x, y):
                    if self.data[y][x]:
                        count += 1
                else:
                    count += 1
        return count
    
    def in_range(self, x, y):
        return 0 <= x < self.w and 0 <= y < self.h

まとめ

手軽にそれっぽい洞窟をつくることができました。ただしゲーム等に使うには、閉塞空間をどうにかする必要があります。それについてはまたの機会に記事を書きたいと思います。