ダンジョンを自動生成するアルゴリズム

迷路をダンジョン化させる

ダンジョンは「棒倒し法で迷路を自動生成する」で作成した迷路を素に拡張して作る。コードの迷路作成部分の解説は「棒倒し法で迷路を自動生成する」で行っているため省略。

処理の流れ

ダンジョンで使用する方眼状マップは迷路の3倍の解像度にする。

ダンジョンマップに迷路を1/3の細さで描画。

描画の途中、一定の確率で通路を3倍の太さ(部屋化)し、迷路の所々に大小の部屋があるダンジョンを作成する。

import pygame
import sys
import random

WALL_COLOR = (87, 45, 24)
FLOOR_COLOR = (181, 152, 132)

FLOOR_W = 48
FLOOR_H = 48
MAZE_W = 9
MAZE_H = 9
maze = []
for y in range(MAZE_H):
    maze.append([0] * MAZE_W)

# ①
SCALE = 3                               # ダンジョンは迷路の1マスを3x3マスに変更
DUNGEON_W = MAZE_W * SCALE              # ダンジョンの1マスの幅と高さを指定
DUNGEON_H = MAZE_H * SCALE
dungeon = []                            # 迷路の9x9から27x27になったダンジョンのマスを管理するリストを宣言
for y in range(DUNGEON_H):
    dungeon.append([0] * DUNGEON_W)

def make_dungeon():
    XP = [0, 1, 0, -1]
    YP = [-1, 0, 1, 0]

    for x in range(MAZE_W):
        maze[0][x] = 1
        maze[MAZE_H - 1][x] = 1
    for y in range(1, MAZE_H - 1):
        maze[y][0] = 1
        maze[y][MAZE_W - 1] = 1
    
    for y in range(1, MAZE_H - 1):
        for x in range(1, MAZE_W - 1):
            maze[y][x] = 0
    
    for y in range(2, MAZE_H - 2, 2):
        for x in range(2, MAZE_W - 2, 2):
            maze[y][x] = 1
    
    for y in range(2, MAZE_H - 2, 2):
        for x in range(2, MAZE_W - 2, 2):
            while True:
                d = random.randint(0, 3)
                if x > 2:
                    d = random.randint(0, 2)                
                if maze[y + YP[d]][x + XP[d]] == 1:
                    continue
                maze[y + YP[d]][x + XP[d]] = 1
                break
    
    # ②
    for y in range(DUNGEON_H):                              # 一旦全マスを壁にする
        for x in range(DUNGEON_W):
            dungeon[y][x] = 1                               # 0が通路、1が壁の意
    # ③
    for y in range(1, MAZE_H - 1):                          # 部屋と通路の配置(周囲の壁部分は除外)
        for x in range(1, MAZE_W - 1):
            # 迷路の1マスをダンジョンでは3x3マスに細かくしているため
            # 迷路のマス目をダンジョンのマス目のリストに合わせるには添字も3倍にする
            # 3倍にしただけでは3x3の左上のマス目になるため、+1して3x3の中央マスを示す様にする
            dx = x * SCALE + 1
            dy = y * SCALE + 1
            # ④
            if maze[y][x] == 0:                             # 迷路側が床(通路)なら実行
                if random.randint(0, 99) < 20:              # 20%の確率で部屋を作る
                    for ry in range(-1, 2):                 # 最小で3x3マスの部屋になり、次回も判定でも部屋を作るなら6x6マスと長く(広く)なっていく
                        for rx in range(-1, 2):
                            dungeon[dy + ry][dx + rx] = 0
                else:                                       # ダンジョン側に通路を作る
                    dungeon[dy][dx] = 0                     # 3x3マスの中央マスを通路にする
                    if maze[y - 1][x] == 0:                 # 迷路の上のマス目が通路なら
                        dungeon[dy - 1][dx] = 0             # 3x3マスの中央上のマス目を通路にする
                    if maze[y + 1][x] == 0:                 # 迷路の下のマス目が通路なら
                        dungeon[dy + 1][dx] = 0             # 3x3マスの中央下のマス目を通路にする
                    if maze[y][x - 1] == 0:                 # 迷路の左のマス目が通路なら
                        dungeon[dy][dx - 1] = 0             # 3x3マスの中央左のマス目を通路にする
                    if maze[y][x + 1] == 0:                 # 迷路の右のマス目が通路なら
                        dungeon[dy][dx + 1] = 0             # 3x3マスの中央右のマス目を通路にする

def main():
    pygame.init()
    pygame.display.set_caption("Pygameでダンジョンを自動生成する")
    screen = pygame.display.set_mode((FLOOR_W * MAZE_W, FLOOR_H * MAZE_H))
    clock = pygame.time.Clock()

    make_dungeon()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    make_dungeon()
        
        for y in range(MAZE_H):
            for x in range(MAZE_W):
                W = FLOOR_W
                H = FLOOR_H
                X = x * W
                Y = y * H
        
        # 生成したダンジョンを描画
        for y in range(DUNGEON_H):
            for x in range(DUNGEON_W):
                X = x * (FLOOR_W / SCALE)
                Y = y * (FLOOR_H / SCALE)
                if dungeon[y][x] == 0:
                    pygame.draw.rect(screen, FLOOR_COLOR, [X, Y, W, H])
                if dungeon[y][x] == 1:
                    pygame.draw.rect(screen, WALL_COLOR, [X, Y, W, H])

        pygame.display.update()
        clock.tick(2)

if __name__ == '__main__':
     main()

各所コードの意味

# ①
SCALE = 3                               # ダンジョンは迷路の1マスを3x3マスに変更
DUNGEON_W = MAZE_W * SCALE              # ダンジョンの1マスの幅と高さを指定
DUNGEON_H = MAZE_H * SCALE
dungeon = []                            # 迷路の9x9から27x27になったダンジョンのマスを管理するリストを宣言
for y in range(DUNGEON_H):
    dungeon.append([0] * DUNGEON_W)

①ダンジョン用のメンバを定義。ダンジョンは迷路の1マスを3×3の9マスに細かくする。そのためダンジョンは27×27の二次元リストとなる。

# ②
for y in range(DUNGEON_H):                              # 一旦全マスを壁にする
    for x in range(DUNGEON_W):
        dungeon[y][x] = 1                               # 0が通路、1が壁の意

②ダンジョンの通路と壁を管理するdungeonを一旦全て壁にする。

# ③
for y in range(1, MAZE_H - 1):                          # 部屋と通路の配置(周囲の壁部分は除外)
    for x in range(1, MAZE_W - 1):
        # 迷路の1マスをダンジョンでは3x3マスに細かくしているため
        # 迷路のマス目をダンジョンのマス目のリストに合わせるには添字も3倍にする
        # 3倍にしただけでは3x3の左上のマス目になるため、+1して3x3の中央マスを示す様にする
        dx = x * SCALE + 1
        dy = y * SCALE + 1

③for文を二重ループで迷路情報を取り出す。周囲は全て壁のため除外。

dungeonの添字として利用する変数dx、dyに取得した迷路情報を代入。ダンジョンは迷路の3倍のスケールになっているため3を掛ける。このままでは迷路1マスに該当するダンジョンの3×3マスの左上のマスを表してしまうため、1を足して中央のマスを表すようにする。

# ④
if maze[y][x] == 0:                             # 迷路側が床(通路)なら実行
    if random.randint(0, 99) < 20:              # 20%の確率で部屋を作る
        for ry in range(-1, 2):                 # 最小で3x3マスの部屋になり、次回も判定でも部屋を作るなら6x6マスと長く(広く)なっていく
            for rx in range(-1, 2):
                dungeon[dy + ry][dx + rx] = 0
    else:                                       # ダンジョン側に通路を作る
        dungeon[dy][dx] = 0                     # 3x3マスの中央マスを通路にする
        if maze[y - 1][x] == 0:                 # 迷路の上のマス目が通路なら
            dungeon[dy - 1][dx] = 0             # 3x3マスの中央上のマス目を通路にする
        if maze[y + 1][x] == 0:                 # 迷路の下のマス目が通路なら
            dungeon[dy + 1][dx] = 0             # 3x3マスの中央下のマス目を通路にする
        if maze[y][x - 1] == 0:                 # 迷路の左のマス目が通路なら
            dungeon[dy][dx - 1] = 0             # 3x3マスの中央左のマス目を通路にする
        if maze[y][x + 1] == 0:                 # 迷路の右のマス目が通路なら
            dungeon[dy][dx + 1] = 0             # 3x3マスの中央右のマス目を通路にする

④if文の条件式 random.randint(0, 99) < 20により、80%の確率で通路、20%の確率で部屋(幅広の通路)を作成する。部屋は3×3マス全てを通路にすることで表現。部屋作りが連続すると3×3マスの部屋が連なり、より大きな部屋になる。

通路を作るケース。通路は3×3マスの中央マスを通路にし、迷路情報から上下左右に通路が伸びている場合は、さらに中央マスから伸びている方向に応じて隣接するマスを通路にする。

コメント

タイトルとURLをコピーしました