Day4：python初心者がダンジョンゲーム作成してみる

# [Day 4: レベルの生成](https://qiita.com/tsuyoron515/items/78cc6d949123beea6f71#day-4-%E3%83%AC%E3%83%99%E3%83%AB%E3%81%AE%E7%94%9F%E6%88%90:~:text=%E3%82%92%E5%AD%A6%E3%81%B3%E3%81%BE%E3%81%99%E3%80%82-,Day%204%3A%20%E3%83%AC%E3%83%99%E3%83%AB%E3%81%AE%E7%94%9F%E6%88%90,-%E3%83%AC%E3%83%99%E3%83%AB%E7%94%9F%E6%88%90%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0)

## 本日の成果
開くたびに自動でダンジョンの地形が変わるものを作成
![Python 3.11 2023_07_09 20_21_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2778907/06adf7bb-36af-3ffa-5ba8-4d887a7a13bd.png)


## レベル生成アルゴリズムの学習:
[Procedural Generation in Game Design by Tanya X. Short]()
[RogueBasin](https://www.gamedeveloper.com/search?q=Procedural%20Generation%20in%20Game%20Design)
[Dungeon Generation](https://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/)
[ヘロンの数学ちゃんねる](https://www.youtube.com/watch?v=LdwHSGDMKGY) で概要を確認

## アルゴリズムの概要
このコードは、ランダムなダンジョンを生成するためのアルゴリズムを利用しています。以下にアルゴリズムの概要を説明します。

1. ダンジョンの要素を表す定数 `WALL`（壁）、`FLOOR`（床）、`PLAYER`（プレイヤー）を定義します。

2. `create_dungeon` 関数では、指定された幅と高さを持つダンジョンを作成します。初期状態ではすべてのセルが壁で埋められています。

3. `create_room` 関数では、指定された範囲内でランダムな幅と高さの部屋を生成し、ダンジョンに部屋を配置します。部屋は床で埋められます。

4. `place_rooms` 関数では、指定された数の部屋をダンジョン内に配置します。部屋の配置はランダムです。

5. `create_corridor` 関数では、2つの部屋の中間点を結ぶ通路を生成し、ダンジョンに床を配置します。

6. `generate_dungeon` 関数では、ダンジョンの生成と部屋の配置、通路の生成を行います。また、プレイヤーの初期位置もランダムな部屋内に設定されます。

7. `draw_dungeon` 関数では、ダンジョンをグラフィックスウィンドウに描画します。各セルは矩形で表示され、壁、床、プレイヤーは異なる色で描画されます。

8. メインのゲームループでは、Pygame のイベントを処理し、プレイヤーの移動を制御します。プレイヤーが移動するたびに、ダンジョン内のセルの状態が更新されます。

9. ゲームループの中で `draw_dungeon` 関数を呼び出してダンジョンを描画し、`pygame.display.flip()` でウィンドウを更新します。

このアルゴリズムにより、部屋と通路からなるランダムなダンジョンが生成され、プレイヤーがキーボード入力に応じて移動することができます。

## コードの詳細
### モジュールのインポート:

   ```python
   import pygame
   import random
   ```

   - `pygame` モジュール: ゲーム開発用の Python ライブラリです。
   - `random` モジュール: 乱数生成のための Python ライブラリです。

### ダンジョンの要素を表す定数の定義:

   ```python
   WALL = 0
   FLOOR = 1
   PLAYER = 2
   ```

   - `WALL`: 壁を表す定数
   - `FLOOR`: 床を表す定数
   - `PLAYER`: プレイヤーを表す定数

### ダンジョン生成関連の関数の定義:

   ```python
    def create_dungeon(width, height):
        dungeon = [[WALL for _ in range(width)] for _ in range(height)]
        return dungeon
   ```
   - `create_dungeon(width, height)`: 指定された幅と高さのダンジョンを作成し、壁で初期化して返します。

   ```python
    def create_room(min_width, max_width, min_height, max_height, dungeon):
        width = random.randint(min_width, max_width)
        height = random.randint(min_height, max_height)
        x = random.randint(1, len(dungeon[0]) - width - 1)
        y = random.randint(1, len(dungeon) - height - 1)
        
        for i in range(y, y + height):
            for j in range(x, x + width):
                dungeon[i][j] = FLOOR
        
        return (x, y, width, height)
   ```
   - `create_room(min_width, max_width, min_height, max_height, dungeon)`: 指定された範囲内でランダムなサイズの部屋を作成し、ダンジョン上の床に変換します。部屋の位置とサイズの情報をタプルとして返します。

   ```python
    def place_rooms(num_rooms, min_width, max_width, min_height, max_height, dungeon):
        rooms = []
        
        for _ in range(num_rooms):
            room = create_room(min_width, max_width, min_height, max_height, dungeon)
            rooms.append(room)
        
        return rooms
   ```
   - `place_rooms(num_rooms, min_width, max_width, min_height, max_height, dungeon)`: 指定された数の部屋を生成し、ダンジョン内に配置します。各部屋の位置とサイズの情報をリストとして返します。

   ```python
    def create_corridor(start, end, dungeon):
        x1, y1 = start
        x2, y2 = end
        
        while x1 != x2 or y1 != y2:
            if x1 < x2:
                x1 += 1
            elif x1 > x2:
                x1 -= 1
            elif y1 < y2:
                y1 += 1
            elif y1 > y2:
                y1 -= 1
            
            dungeon[y1][x1] = FLOOR
   ```

   - `create_corridor(start, end, dungeon)`: 部屋の中心座標を指定して通路を生成し、ダンジョン上の床に変換します。

### ダンジョン全体の生成関数:

   ```python
   def generate_dungeon(width, height, num_rooms, min_width, max_width, min_height, max_height):
       dungeon = create_dungeon(width, height)
       rooms = place_rooms(num_rooms, min_width, max_width, min_height, max_height, dungeon)

       for i in range(len(rooms) - 1):
           start = (rooms[i][0] + rooms[i][2] // 2, rooms[i][1] + rooms[i][3] // 2)
           end = (rooms[i+1][0] + rooms[i+1][2] // 2, rooms[i+1][1] + rooms[i+1][3] // 2)
           create_corridor(start, end, dungeon)

       player_room = random.choice(rooms)
       player_x = random.randint(player_room[0], player_room[0] + player_room[2] - 1)
       player_y = random.randint(player_room[1], player_room[1] + player_room[3] - 1)
       dungeon[player_y][player_x] = PLAYER

       return dungeon, rooms, player_x, player_y
   ```

   - `generate_dungeon` 関数: 指定されたパラメータに基づいてダンジョン全体を生成します。ダンジョンの作成、部屋の配置、通路の作成、プレイヤーの初期位置の設定を行います。ダンジョン、部屋のリスト、プレイヤーの初期位置を返します。

### ダンジョンの描画関数:

   ```python
   def draw_dungeon(dungeon):
       for y in range(len(dungeon)):
           for x in range(len(dungeon[y])):
               cell = dungeon[y][x]
               cell_rect = pygame.Rect(x * cell_size, y * cell_size, cell_size, cell_size)
            
               if cell == WALL:
                   pygame.draw.rect(window, (0, 0, 0), cell_rect)
               elif cell == FLOOR:
                   pygame.draw.rect(window, (255, 255, 255), cell_rect)
               elif cell == PLAYER:
                   pygame.draw.rect(window, (255, 0, 0), cell_rect)
   ```

   - `draw_dungeon` 関数: ダンジョンの状態を描画します。ダンジョンの各セルに対応する矩形を作成し、セルの値に応じて色を設定して描画します。
### Pygame の初期化とウィンドウの作成:

   ```python
   dungeon_width = 800
   dungeon_height = 600
   cell_size = 20

   pygame.init()
   window = pygame.display.set_mode((dungeon_width, dungeon_height))
   pygame.display_set_caption("Dungeon Game")
   ```

- `dungeon_width` と `dungeon_height` はそれぞれウィンドウの幅と高さを表します。
- `cell_size` はダンジョン内のセル（部屋や通路のタイル）のサイズを表します。
- `pygame.init()` は Pygame ライブラリを初期化します。
- `window` 変数に `pygame.display.set_mode((dungeon_width, dungeon_height))` を用いてウィンドウを作成します。
- `pygame.display.set_caption("Dungeon Game")` はウィンドウのキャプション（タイトル）を設定します。

### プレイヤーの初期位置の設定:

   ```python
   player_x = 0
   player_y = 0
   ```

   - `player_x` と `player_y` はプレイヤーの初期位置の座標を表します。ここでは 0 で初期化されています。

### ダンジョンの生成とプレイヤーの初期位置の取得:

   ```python
   dungeon, rooms, player_x, player_y = generate_dungeon(40, 30, 15, 3, 10, 3, 10)
   ```

   - `generate_dungeon` 関数を呼び出して、ダンジョンを生成し、部屋のリストとプレイヤーの初期位置を取得します。引数として、ダンジョンの幅と高さ、部屋の数、部屋の最小幅と最大幅、部屋の最小高さと最大高さが指定されています。

### ゲームループ:

   ```python
   running = True
   while running:
       for event in pygame.event.get():
           if event.type == pygame.QUIT:
               running = False

           if event.type == pygame.KEYDOWN:
               # プレイヤーの移動処理
               if event.key == pygame.K_UP:
                   # 上キーが押された場合の処理
               elif event.key == pygame.K_DOWN:
                   # 下キーが押された場合の処理
               elif event.key == pygame.K_LEFT:
                   # 左キーが押された場合の処理
               elif event.key == pygame.K_RIGHT:
                   # 右キーが押された場合の処理

       # ダンジョンの描画処理
       draw_dungeon(dungeon)

       # 画面の更新
       pygame.display.flip()

   pygame.quit()
   ```

   - `running` 変数が `True` の間、ゲームループが実行されます。
   - イベントループ内でキーボードイベントを処理し、プレイヤーの移動を制御します。
   - `draw_dungeon` 関数を呼び出してダンジョンを描画します。
   - `pygame.display.flip()` でウィンドウの内容を更新します。
   - ゲームループが終了した後、`pygame.quit()` を呼び出して Pygame を終了します。

# まとめ
今回作成したものを基準に、前回までに学んだ内容を追加したら敵キャラやアイテムを作れないかな～

アルゴリズムについては、正直理解できなかった…
英語と数学、アルゴリズムの勉強しなければ…
とりあえずは、手を動かして完成までもっていこう！