メインコンテンツへスキップ
【VBA】穴掘り法で迷路を自動生成

【VBA】穴掘り法で迷路を自動生成

··6 分
Programming VBA アルゴリズム
かずさプログラマー
著者
かずさプログラマー
業務の作業自動化を行っています。Go、VBA、Pythonを主に使用しています。過去にはC#、VB.Net、JavaScriptも使用していました。
目次

迷路を自動で生成してみる
#

以前、Excelのシートに入力した文字を迷路に見立てて幅優先探索で迷路を解くプログラムをVBAで作成しました。

【VBA】迷路を解く
··6 分
Programming VBA アルゴリズム

前回のプログラムでは迷路を手動で入力する必要がありました。
大きいサイズの迷路を作成するのは大変なので、自動で迷路を作成する方法を探してみました。

穴掘り法
#

迷路を作成するアルゴリズムで「穴掘り法」というものがあるので
今回は「穴掘り法」アルゴリズムを使用して、VBAで迷路を作成してみます。

穴掘り法アルゴリズムは下記のページの内容を参考にさせていただきました。

迷路を解くアルゴリズムと組み合わせると、迷路の生成から迷路の解決まで一気にできます。

迷路作成プログラムの作成
#

実装内容
#

以下の内容で実装してみます。

  • mazeという名前のシートに迷路を作成する。
  • スタート位置とゴール位置の自動生成はしない。
記号 内容
S スタート位置です
G ゴール位置です
空白 通ることができます
# 壁なので通ることができません
  • 正方形の迷路を生成する。長方形や矩形の迷路は生成しない。
  • 迷路のサイズを入力できるインプットボックスを表示する。入力する数値は5以上99以下の奇数とする。
  • 数値のサイズが大きいとスタック領域不足エラーが発生するため99以下としました。

コード
#

VBAの穴掘り法のコードは以下になります。

Option Explicit
Sub createMaze()
  
  Dim sizeStr As String
  
  sizeStr = InputBox("5以上99以下の奇数を入力してください")
  If sizeStr = "" Then Exit Sub
  
  If checkInputNumber(sizeStr) = False Then Exit Sub
  
  Dim msgboxVal As Long
  Dim displayFlag As Boolean
  msgboxVal = MsgBox("迷路作成の途中経過を表示しますか?", vbYesNo + vbQuestion, "確認")
  If msgboxVal = vbYes Then
    displayFlag = True
  Else
    displayFlag = False
    Application.ScreenUpdating = False
  End If
  
  Dim size As Long
  size = CLng(sizeStr)
  
  size = size - 1 '配列数を奇数にする
  Dim maze() As String
  ReDim maze(size, size)
  
  Dim mazeSht As Worksheet
  Set mazeSht = ThisWorkbook.Worksheets("maze")
  mazeSht.UsedRange.Clear
  
  Dim i As Long
  Dim j As Long
    
  '外周以外を壁に設定
  For i = 1 To size - 1
    For j = 1 To size - 1
      maze(i, j) = "#"
    Next j
  Next i
  mazeSht.Activate
  If displayFlag Then Call displayMaze(maze, size)
  
  Dim y As Long
  Dim x As Long
  y = getOdd(size)
  x = getOdd(size)
  maze(y, x) = "" '探索スタート地点を通路に変更
  Call digMaze(maze, y, x, size, displayFlag)
  
  '外周を壁に設定する
  For i = 0 To size
    For j = 0 To size
      If i = 0 Or i = size Or j = 0 Or j = size Then
        maze(i, j) = "#"
      End If
    Next j
  Next i
  
  Call displayMaze(maze, size)
  mazeSht.UsedRange.Columns.AutoFit
  
  Application.ScreenUpdating = True
  MsgBox "迷路を作成しました", vbInformation, "迷路作成完了"
  
End Sub
Function checkInputNumber(sizeStr As String) As Boolean
  'インプットボックスに入力された数値のチェック
  
  If IsNumeric(sizeStr) = False Then
    MsgBox "数値を入力してください", vbCritical, "数値以外が入力されました"
    Exit Function
  End If
  Dim size As Long
  size = CLng(sizeStr)
  If size Mod 2 = 0 Then
    MsgBox "奇数を入力してください", vbCritical, "奇数が入力されました"
    Exit Function
  End If
  If size < 5 And 99 < size Then
    MsgBox "5以上99以下の数値を入力してください", vbCritical, "入力値を確認してください"
    Exit Function
  End If

  checkInputNumber = True

End Function

Function getOdd(size As Long) As Long
  
  '1以上size未満の奇数を取得する
  
  Dim v As Long
  Do While v Mod 2 = 0
    Randomize
    v = Int(size * Rnd + 1)
  Loop
  
  getOdd = v
  
End Function
Sub displayMaze(maze() As String, size As Long)
  '迷路をシートに表示
  
  Dim sht As Worksheet
  Set sht = ThisWorkbook.Worksheets("maze")
  
  Dim i As Long
  Dim j As Long
  
  For i = 0 To size
    For j = 0 To size
      sht.Cells(i + 1, j + 1) = maze(i, j)
    Next j
  Next i
  
  Set sht = Nothing
  DoEvents
  
End Sub

Sub digMaze(maze() As String, y As Long, x As Long, _
            size As Long, displayFlag As Boolean)
  '穴掘り法による迷路作成
  Dim directions(3) As String
  directions(0) = "N"
  directions(1) = "E"
  directions(2) = "S"
  directions(3) = "W"
  
  Dim v As Variant
  v = shuffleAry(directions)
  
  Dim i As Long
  Dim c As Collection
  For i = 0 To UBound(directions)
    Dim mY As Long
    Dim mX As Long
    If directions(i) = "N" Then
      mY = y - 2
      mX = x
      '座標を北に2つ移動してみる。
      '移動先の座標が外周内に収まり、かつ通路でなければ
      '現在の座標から北の座標2つ分を通路にする
      If checkMazeCell(maze, mY, mX, size) Then
        maze(y - 1, x) = ""
        maze(y - 2, x) = ""
        If displayFlag Then Call displayMaze(maze, size)
        '移動先の座標で再帰処理
        Call digMaze(maze, mY, mX, size, displayFlag)
      End If
    ElseIf directions(i) = "E" Then
      mY = y
      mX = x + 2
      '座標を東に2つ移動してみる。
      '移動先の座標が外周内に収まり、かつ通路でなければ
      '現在の座標から東の座標2つ分を通路にする
      If checkMazeCell(maze, mY, mX, size) Then
        maze(y, x + 1) = ""
        maze(y, x + 2) = ""
        If displayFlag Then Call displayMaze(maze, size)
        '移動先の座標で再帰処理
        Call digMaze(maze, mY, mX, size, displayFlag)
      End If
    ElseIf directions(i) = "S" Then
      mY = y + 2
      mX = x
      '座標を南に2つ移動してみる。
      '移動先の座標が外周内に収まり、かつ通路でなければ
      '現在の座標から南の座標2つ分を通路にする
      If checkMazeCell(maze, mY, mX, size) Then
        maze(y + 1, x) = ""
        maze(y + 2, x) = ""
        If displayFlag Then Call displayMaze(maze, size)
        '移動先の座標で再帰処理
        Call digMaze(maze, mY, mX, size, displayFlag)
      End If
    Else
      mY = y
      mX = x - 2
      '座標を西に2つ移動してみる。
      '移動先の座標が外周内に収まり、かつ通路でなければ
      '現在の座標から西の座標2つ分を通路にする
      If checkMazeCell(maze, mY, mX, size) Then
        maze(y, x - 1) = ""
        maze(y, x - 2) = ""
        If displayFlag Then Call displayMaze(maze, size)
        '移動先の座標で再帰処理
        Call digMaze(maze, mY, mX, size, displayFlag)
      End If
    End If
  Next i
  
End Sub

Function checkMazeCell(maze() As String, mY As Long, mX As Long, _
                       size As Long) As Boolean
  '移動先の座標が配列のサイズに収まり、さらに通路でないことを確認する
  
  '移動先のyが配列内に収まるか確認
  If mY < 0 Or size < mY Then
    Exit Function
  End If
  
  '移動先のxが配列内に収まるか確認
  If mX < 0 Or size < mX Then
    Exit Function
  End If
  
  '移動先の座標が通路かどうか確認する
  If maze(mY, mX) = "" Then
    Exit Function
  End If
  
  checkMazeCell = True
  
End Function

' 配列をシャッフルする関数
'http://techoh.net/vba-shuffle-array/
Function shuffleAry(list() As String)
    
  Dim i As Long
  Dim rn As Long
  Dim tmp As String
    
  For i = 0 To UBound(list)
    Randomize
    rn = Int(UBound(list) * Rnd)
    tmp = list(i)
    list(i) = list(rn)
    list(rn) = tmp
  Next
    
  shuffleAry = list
End Function

穴掘り法で壁を壊す方向をランダムに決める必要があったので
東西南北の方角を配列に格納し、配列をシャッフルして破壊方向をランダムに出してます。

配列をシャッフルする関数は以下のサイトを参考にさせていただきました。

穴掘り法で迷路を生成してみる
#

createMazeを実行するとインプットボックスが表示されます。
5~99以外の数値や文字を入力するとエラーになります。

15を入力してOKを押してみます。

「迷路作成の途中経過を表示しますか?」とメッセージボックスが表示されます。

「はい」を押すと迷路の作成経過をmazeシートに表示します。

迷路のサイズが大きいと表示処理に時間がかかるので注意してください。

「いいえ」を押すと、作成経過を表示せず迷路を表示します。

迷路の作成が完了すると完了メッセージが表示されます。

迷路作成後、SとGの文字をシートに書き込みます。
以前作成した迷路を探索するアルゴリズムを使用するとスタートからゴールまでの経路を表示します。

エラー
#

実行時エラー"28":スタック領域が不足しています
#

迷路作成時に再帰処理を行っているため、スタックの容量を超えてしまうとエラーが発生します。 この場合はインプットボックスに入力する迷路のサイズを小さくしてみてください。

関連記事

【VBA】迷路を解く
··6 分
Programming VBA アルゴリズム
【VBA】UsedRangeで最終行・最終列を取得する
·2 分
Programming VBA
【VBA】セルに色を塗る ColorIndexとColor一覧
·1 分
Programming VBA