自動で迷路を生成し自動で迷路を解いてくれる迷路自己解決器をつくりました。人間のすることはなにもありません。迷路を解く方は以前の記事(【Ruby】 A*(A-star)アルゴリズムを可視化してみた)にて紹介したものに入力しているだけなので今回は迷路生成部について書きます。
ソースコード
「穴掘り法」で検索すれば図解を混じえたすばらしい解説がたくさん見つかると思うので、そこは良記事に譲るとしてここでは実装にあたっての工夫をば。
穴掘り法は再帰でかなりきれいに簡単に書けるので私はすきなんですが、生成された迷路が単調になりやすいという問題があります。特にこれ以上掘れないってなったとき掘れる地点まで戻るという方法は、きれいに再帰が書ける反面かなり単調なものになります。そこで、できるだけいろいろな地点で枝分かれしてもらうために穴掘りを開始できる地点を記憶しておき、行き止まりになったらそこからランダムに選ばれるようにしました。これは終了判定にも流用できるので一石二鳥です。
長くなりましたがちょいちょいコメントを書いたのであとはソースコードを確認してください。
module Maze
PATH = 0
BLOCK = 1
class Generator
attr_reader :nodes, :row, :column
def initialize(row = 13, column = 13)
raise if row.even? || column.even?
@row, @column = row, column
end
# 掘った穴をリセットする
def reset
@nodes = Array.new(@row) do |i|
Array.new(@column) do |j|
wall = (i == 0 || i == @row-1 || j == 0 || j == @column-1)
wall ? PATH : BLOCK
end
end
# 候補点の配列
# ここから穴を掘り始められる
@paths = @nodes.each_with_index.map do |row, i|
row.each_with_index.map do |node, j|
[i, j] if (i.even? && j.even?) && node == BLOCK
end
end.flatten(1).compact
# はじめにひとつ空ける穴
# ここを @paths.last にすると右下から掘り始める
i, j = @paths.sample
@nodes[i][j] = PATH
end
# 迷路を生成して返す
def generate
print "\e[?25l"
reset
loop do
i, j = @paths.sample
next if @nodes[i][j] == BLOCK
dig(i, j)
break if @paths.empty?
end
self
rescue Interrupt
exit
ensure
print "\e[?25h"
end
# 穴掘り法
def dig(i, j)
puts self
print "\e[#{@row}A"; STDOUT.flush; sleep 0.01
# 2マス先がブロックであるなら
# 掘り進めることが可能な方向となる
dir = [[1,0], [0,1], [-1,0], [0,-1]].map do |di, dj|
[di, dj] if @nodes[i+2*di][j+2*dj] == BLOCK
end.compact
# 掘り進めることが可能な方向の数で場合分け
case dir.size
# 行き止まりなので
# 現在点を候補点から削除し再帰しない
when 0
@paths.delete [i, j]
# 一方向しか掘り進められないので
# 現在点を候補点から削除するが再帰はする
when 1
di, dj = dir.first
@nodes[i+di][j+dj] = PATH
@nodes[i+2*di][j+2*dj] = PATH
@paths.delete [i, j]
dig(i+2*di, j+2*dj)
# 二方向以上掘り進められるので
# 現在点を候補点から削除せず再帰する
else
di, dj = dir.sample
@nodes[i+di][j+dj] = PATH
@nodes[i+2*di][j+2*dj] = PATH
dig(i+2*di, j+2*dj)
end
end
def to_s
@nodes.map do |row|
row.map do |node|
case node
when BLOCK then "\e[47m \e[0m"
when PATH then " "
end
end.join
end.join("\n")
end
end
end
row = 21
column = 31
generator = Maze::Generator.new(row, column)
begin
maze = generator.generate
puts maze
print "\e[#{row}A"; STDOUT.flush; sleep 1
rescue Interrupt
exit
end while true実行すると迷路を生成し続けます。
あとがき
去年から年明けにかけてとても忙しかったため半年ほどブログを更新できずにいましたが、ようやくいろいろ一段落したのでとりあえず落書きスクリプトを投稿しました。その間も一応Qiitaにはちょっと投稿していたのですが。
今後もどれ程のペースで投稿が続けられるかわかりませんが、なにかつくったらできるだけ紹介していきたいと思うのでどうぞ今年もよろしくお願いします。
0 件のコメント:
コメントを投稿