12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

子供のために迷路を自動生成する

Last updated at Posted at 2019-02-01

はじめに

娘が迷路に興味を持ち始め、私も強化学習で迷路を自動生成していたので…
折角だからExcelに出力して、印刷して、一緒に遊んでしまおう!という動機です。
また、私のゲーミングPCにはExcelが入っていないので、妻の端末を借りています。
流石に妻の端末をpythonで侵すわけにはいかなかったので、google の colaboratory を使用してみました。

結論

キャプチャ.PNG

自動生成のルール

  • Excelのセルを部屋と考え、inputで1辺のセル数Nを決める。
  • 2つの変数部屋番号を使用する。
    • 部屋番号
      • 初期値[0, 1, 2, 3, ...]のN*Nの連番とする。
      • N個のセルに壁4つを設定する。
      • 壁は[1, 1, 1, 1] = [上, 右, 下, 左]として1:壁/0:壁無しを設定。
      • データのイメージは[[1,1,1,1][1,1,1,1]...]
      • データ数は[N]個(表記方法合ってる?)
  • ランダムに部屋を選択
  • ランダムに突き破る壁を選択
  • 部屋番号を小さい方に統一させる
    • 例えば 3*3 の迷路を想定し、部屋2 の 下向き を突き破る場合
    • 部屋番号 [0, 1, 2, 3, 4, 2, 6, 7, 8]
    • 壁 [[略][略][1, 1, 0, 1][略][略][0, 1, 1, 1][略][略][略]] となる。
  • これを繰り返し、部屋番号が全て0になるまで繰り返す。ただし、同じ部屋番号の場合は処理しない。
  • 全て0になったら、Excelに罫線で壁を記載して完了とする。

コーディング

colaboratoryからgoogleドライブを使えるようにする。

External data: Drive, Sheets, and Cloud Storage にアクセスし、Mounting Google Drive locallyを処理する。

from google.colab import drive
drive.mount('/content/gdrive')

を実行すると、authorization codeを聞いてくる。
Go to this URL in a browser:後ろのURLにアクセスし、authorization codeを取得し、コピペする。
colaboratory の左メニューでファイルを見るとgdriveがある。
あとは通常のファイルのようにアクセスする。
キャプチャ.PNG

ライブラリ

import openpyxl as xl
import numpy as np
from datetime import datetime

進行方向を取得

def direction():
  '''
  進行方向を選択。
  戻り値は順に「現在地の破壊壁方向」「進行部屋の壁破壊方向」「進行部屋の部屋番号」
  '''
  rand = np.random.randint(0, high=4)
  if rand == 0: return 0, 2, -1 * INT_MAZE_SIZE  # 上
  if rand == 1: return 1, -1, 1  # 右
  if rand == 2: return 2, 0, INT_MAZE_SIZE  # 下
  return -1, 1, -1  # 左

進めるかチェック

def check_cell(cells, c_cell, d_cell, dire):
  ''' 進めるかチェック '''
  # 進んだ部屋が迷路外かどうか
  # 上下へのはみ出し
  if d_cell < 0 or INT_MAZE_CELL <= d_cell: return False
  # 部屋番号+1が迷路サイズで割り切れる場合、右端の部屋。右には進めない。
  if (c_cell+1) % INT_MAZE_SIZE == 0 and dire == 1: return False
  # 部屋番号+1が迷路サイズで割り、余り1の場合、左端の部屋。左には進めない。
  if (c_cell+1) % INT_MAZE_SIZE == 1 and dire == -1: return False
  # 現在地部屋番号 = 進んだ部屋番号。同じ部屋の場合は処理しない
  if cells[c_cell] == cells[d_cell]: return False
  # それ以外は進める
  return True

小さい部屋番号を選択

def choice_cell_no(cells, c_cell, d_cell):
  ''' 小さい方の部屋番号を取得 '''
  if cells[c_cell] < cells[d_cell]:
    return cells[c_cell], cells[d_cell]
  return cells[d_cell], cells[c_cell]

迷路を生成する

def create_maze():
  ''' 迷路を生成 '''
  # 初期状態を生成
  # [上, 右, 下, 左] = 1:壁 / 0:通路
  lst_maze = np.ones([INT_MAZE_CELL, 4])
  # 部屋のナンバリング、最終的に全部0にする。
  lst_cells = np.array([i for i in range(0, INT_MAZE_CELL)])
  
  while True:
    # すべての部屋が番号0の時は処理終了
    if np.sum(lst_cells) == 0: break
    
    # 壁を破る元のセルを選択(現在地)
    int_choice_cell = np.random.randint(0, high=INT_MAZE_CELL)
    # 既にスタート地点とつながっている(0)時は処理しない。
    if lst_cells[int_choice_cell] == 0: continue
    
    # 進行方向を選択
    int_now_wall, int_direction_wall, int_direction = direction()
    # 進んだときのセル
    int_direction_cell = int_choice_cell + int_direction
    
    # 値のチェック
    if not check_cell(lst_cells, int_choice_cell, int_direction_cell, int_direction):
      continue
    
    # 部屋番号を確保
    int_min_value, int_max_value = choice_cell_no(lst_cells, int_choice_cell, int_direction_cell)
    
    # 小さい方の部屋番号に統一する
    lst_cells[lst_cells == int_max_value] = int_min_value
    
    # 壁も更新する
    lst_maze[int_choice_cell][int_now_wall] = 0
    lst_maze[int_direction_cell][int_direction_wall] = 0

  return lst_maze 

Excelに出力

def out_excel(maze):
  # 現在時刻をファイル名に使う
  str_now = datetime.now().strftime("%Y%m%d%H%M%S")

  # Excelを開く
  file_pass = '/content/gdrive/My Drive/Colab Notebooks/'
  xlsx_workbook = xl.load_workbook(file_pass+'maze.xlsx')
  xlsx_worksheet = xlsx_workbook.active

  # スタートとゴールを記載する
  cell_font = xl.styles.Font(name='Meiryo UI')
  cell_alig =  xl.styles.Alignment(horizontal='center', \
                                   vertical = 'center', \
                                   wrap_text=True)
  # スタート
  start_cell = xlsx_worksheet.cell(row=1, column=1)
  start_cell.value = 'S'
  start_cell.font = cell_font
  start_cell.alignment = cell_alig
  # ゴール
  end_cell = xlsx_worksheet.cell(row=INT_MAZE_SIZE, column=INT_MAZE_SIZE)
  end_cell.value = 'G'
  end_cell.font = cell_font
  end_cell.alignment = cell_alig

  # Excelに描画する。
  # セル数ループ
  r = 0
  for c, cell in enumerate(maze):
    # 罫線の変数
    border = xl.styles.Border()
    # 壁の数だけループ
    for d, wall in enumerate(cell):
      # 壁だけ処理
      if wall == 1:
        cell_style = xl.styles.Side(border_style='thin', color='000000')
        if d == 0:  # 上
          border.top = cell_style
        elif d == 1:  # 右
          border.right = cell_style
        elif d == 2:  # 下
          border.bottom = cell_style
        else:  # 左
          border.left = cell_style
    # 罫線を引くセルを選択
    xlsx_cell = xlsx_worksheet.cell(row=r+1, column=c-r*INT_MAZE_SIZE+1)
    xlsx_cell.border = border
    if (c+1) % INT_MAZE_SIZE == 0: r += 1
  
  xlsx_workbook.save(file_pass+'maze_'+str_now+'.xlsx')

いざ実行

INT_MAZE_SIZE = int(input('maze size->'))  # 迷路の1辺の長さ
INT_MAZE_CELL = INT_MAZE_SIZE * INT_MAZE_SIZE  # 迷路のセル数

# 迷路を生成
out_maze = create_maze()

out_excel(out_maze)

補足

  • なぜかgoogle driveで開くと罫線が欠ける
  • colaboratoryのファイルをダブルクリックで開くと、ちゃんと罫線が引けている。謎。
  • が強化学習で使用できる。
  • A4でいい感じに印刷できるのはN=33ぐらい?
12
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?