第5章:マップパースと実装

はじめに

本章では、.cubファイルの解析とゲーム実装の詳細を学びます。

---

1. .cubファイル形式

1.1 ファイル構造

NO ./path_to_north_texture.xpm
SO ./path_to_south_texture.xpm
WE ./path_to_west_texture.xpm
EA ./path_to_east_texture.xpm

F 220,100,0
C 225,30,0

        1111111111111111111111111
        1000000000110000000000001
        1011000001110000000000001
111111111011000001110000000000001
100000000011000001110111111111111
11110111111111011100000010001
11110111111111011101010010001
11000000110101011100000010001
10000000000000001100000010001
10000000000000001101010010001
11000001110101011111011110N0111
11110111 1110101101111010001
11111111 1111111111111111111

1.2 識別子

| 識別子 | 意味 | 形式 | |--------|------|------| | NO | 北向き壁テクスチャ | パス | | SO | 南向き壁テクスチャ | パス | | WE | 西向き壁テクスチャ | パス | | EA | 東向き壁テクスチャ | パス | | F | 床の色 | R,G,B | | C | 天井の色 | R,G,B |

1.3 マップ文字

| 文字 | 意味 | |------|------| | 0 | 空きスペース(歩ける) | | 1 | 壁 | | N | プレイヤー開始位置(北向き) | | S | プレイヤー開始位置(南向き) | | E | プレイヤー開始位置(東向き) | | W | プレイヤー開始位置(西向き) | | 空白 | マップ外(壁としても機能) |

---

2. パーサーの実装

2.1 データ構造

typedef struct s_config {
    char    *north_path;
    char    *south_path;
    char    *west_path;
    char    *east_path;
    int     floor_color;
    int     ceiling_color;
    char    **map;
    int     map_width;
    int     map_height;
    double  player_x;
    double  player_y;
    char    player_dir;
} t_config;

2.2 ファイル読み込み

int parse_cub_file(t_config *config, char *filename) {
    int     fd;
    char    *line;

    fd = open(filename, O_RDONLY);
    if (fd < 0)
        return (error_exit("Cannot open file"));

    // テクスチャと色のパース
    while ((line = get_next_line(fd)) != NULL) {
        if (is_empty_line(line)) {
            free(line);
            continue;
        }
        if (is_map_line(line)) {
            // マップのパース開始
            parse_map(config, fd, line);
            break;
        }
        parse_config_line(config, line);
        free(line);
    }

    close(fd);
    return (validate_config(config));
}

2.3 設定行のパース

int parse_config_line(t_config *config, char *line) {
    char    *trimmed;
    char    *value;

    trimmed = ft_strtrim(line, " \t\n");
    if (!trimmed)
        return (1);

    if (ft_strncmp(trimmed, "NO ", 3) == 0)
        config->north_path = ft_strdup(trimmed + 3);
    else if (ft_strncmp(trimmed, "SO ", 3) == 0)
        config->south_path = ft_strdup(trimmed + 3);
    else if (ft_strncmp(trimmed, "WE ", 3) == 0)
        config->west_path = ft_strdup(trimmed + 3);
    else if (ft_strncmp(trimmed, "EA ", 3) == 0)
        config->east_path = ft_strdup(trimmed + 3);
    else if (ft_strncmp(trimmed, "F ", 2) == 0)
        config->floor_color = parse_color(trimmed + 2);
    else if (ft_strncmp(trimmed, "C ", 2) == 0)
        config->ceiling_color = parse_color(trimmed + 2);
    else
        return (error_exit("Unknown identifier"));

    free(trimmed);
    return (0);
}

2.4 マップのパース

int parse_map(t_config *config, int fd, char *first_line) {
    t_list  *lines;
    char    *line;

    lines = NULL;
    ft_lstadd_back(&lines, ft_lstnew(ft_strdup(first_line)));

    while ((line = get_next_line(fd)) != NULL) {
        if (is_empty_line(line) && is_map_started(lines)) {
            free(line);
            break;  // マップ終了
        }
        ft_lstadd_back(&lines, ft_lstnew(ft_strdup(line)));
        free(line);
    }

    return (convert_list_to_map(config, lines));
}

int convert_list_to_map(t_config *config, t_list *lines) {
    t_list  *current;
    int     i;
    int     max_width;

    config->map_height = ft_lstsize(lines);
    max_width = get_max_line_width(lines);
    config->map_width = max_width;

    config->map = malloc(sizeof(char *) * (config->map_height + 1));
    if (!config->map)
        return (1);

    i = 0;
    current = lines;
    while (current) {
        config->map[i] = pad_line((char *)current->content, max_width);
        current = current->next;
        i++;
    }
    config->map[i] = NULL;

    ft_lstclear(&lines, free);
    return (find_player_position(config));
}

---

3. マップ検証

3.1 壁で囲まれているか確認

int validate_map(t_config *config) {
    int x, y;

    // 各空きスペースが壁で囲まれているか確認
    for (y = 0; y < config->map_height; y++) {
        for (x = 0; x < config->map_width; x++) {
            if (is_floor(config->map[y][x])) {
                if (!is_surrounded(config, x, y))
                    return (error_exit("Map is not closed"));
            }
        }
    }

    return (0);
}

int is_surrounded(t_config *config, int x, int y) {
    // 8方向をチェック
    int dirs[8][2] = {
        {-1, -1}, {0, -1}, {1, -1},
        {-1, 0},           {1, 0},
        {-1, 1},  {0, 1},  {1, 1}
    };

    for (int i = 0; i < 8; i++) {
        int nx = x + dirs[i][0];
        int ny = y + dirs[i][1];

        // 境界外
        if (nx < 0 || nx >= config->map_width ||
            ny < 0 || ny >= config->map_height)
            return (0);

        // 空白(マップ外)
        if (config->map[ny][nx] == ' ')
            return (0);
    }
    return (1);
}

3.2 プレイヤー位置の検出

int find_player_position(t_config *config) {
    int x, y;
    int player_count = 0;

    for (y = 0; y < config->map_height; y++) {
        for (x = 0; x < config->map_width; x++) {
            char c = config->map[y][x];
            if (c == 'N' || c == 'S' || c == 'E' || c == 'W') {
                config->player_x = x + 0.5;
                config->player_y = y + 0.5;
                config->player_dir = c;
                config->map[y][x] = '0';  // 床に置き換え
                player_count++;
            }
        }
    }

    if (player_count != 1)
        return (error_exit("Invalid player count"));

    return (0);
}

---

4. プレイヤーの初期化

4.1 方向の設定

void init_player_direction(t_player *player, char dir) {
    switch (dir) {
        case 'N':
            player->dirX = 0;
            player->dirY = -1;
            player->planeX = 0.66;
            player->planeY = 0;
            break;
        case 'S':
            player->dirX = 0;
            player->dirY = 1;
            player->planeX = -0.66;
            player->planeY = 0;
            break;
        case 'E':
            player->dirX = 1;
            player->dirY = 0;
            player->planeX = 0;
            player->planeY = 0.66;
            break;
        case 'W':
            player->dirX = -1;
            player->dirY = 0;
            player->planeX = 0;
            player->planeY = -0.66;
            break;
    }
}

---

5. 入力処理

5.1 キー入力

#define KEY_W       13   // macOS
#define KEY_A       0
#define KEY_S       1
#define KEY_D       2
#define KEY_LEFT    123
#define KEY_RIGHT   124
#define KEY_ESC     53

typedef struct s_keys {
    int w;
    int a;
    int s;
    int d;
    int left;
    int right;
} t_keys;

int key_press(int keycode, t_game *game) {
    if (keycode == KEY_ESC)
        exit_game(game);
    if (keycode == KEY_W)
        game->keys.w = 1;
    if (keycode == KEY_A)
        game->keys.a = 1;
    if (keycode == KEY_S)
        game->keys.s = 1;
    if (keycode == KEY_D)
        game->keys.d = 1;
    if (keycode == KEY_LEFT)
        game->keys.left = 1;
    if (keycode == KEY_RIGHT)
        game->keys.right = 1;
    return (0);
}

int key_release(int keycode, t_game *game) {
    if (keycode == KEY_W)
        game->keys.w = 0;
    if (keycode == KEY_A)
        game->keys.a = 0;
    if (keycode == KEY_S)
        game->keys.s = 0;
    if (keycode == KEY_D)
        game->keys.d = 0;
    if (keycode == KEY_LEFT)
        game->keys.left = 0;
    if (keycode == KEY_RIGHT)
        game->keys.right = 0;
    return (0);
}

5.2 移動処理

#define MOVE_SPEED  0.05
#define ROT_SPEED   0.03

void update_player(t_game *game) {
    t_player *p = &game->player;

    // 前進
    if (game->keys.w) {
        double newX = p->posX + p->dirX * MOVE_SPEED;
        double newY = p->posY + p->dirY * MOVE_SPEED;
        if (is_walkable(game, newX, p->posY))
            p->posX = newX;
        if (is_walkable(game, p->posX, newY))
            p->posY = newY;
    }

    // 後退
    if (game->keys.s) {
        double newX = p->posX - p->dirX * MOVE_SPEED;
        double newY = p->posY - p->dirY * MOVE_SPEED;
        if (is_walkable(game, newX, p->posY))
            p->posX = newX;
        if (is_walkable(game, p->posX, newY))
            p->posY = newY;
    }

    // 左移動(ストレイフ)
    if (game->keys.a) {
        double newX = p->posX - p->planeX * MOVE_SPEED;
        double newY = p->posY - p->planeY * MOVE_SPEED;
        if (is_walkable(game, newX, p->posY))
            p->posX = newX;
        if (is_walkable(game, p->posX, newY))
            p->posY = newY;
    }

    // 右移動(ストレイフ)
    if (game->keys.d) {
        double newX = p->posX + p->planeX * MOVE_SPEED;
        double newY = p->posY + p->planeY * MOVE_SPEED;
        if (is_walkable(game, newX, p->posY))
            p->posX = newX;
        if (is_walkable(game, p->posX, newY))
            p->posY = newY;
    }

    // 左回転
    if (game->keys.left)
        rotate_player(p, -ROT_SPEED);

    // 右回転
    if (game->keys.right)
        rotate_player(p, ROT_SPEED);
}

5.3 衝突判定

int is_walkable(t_game *game, double x, double y) {
    int mapX = (int)x;
    int mapY = (int)y;

    // 境界チェック
    if (mapX < 0 || mapX >= game->map_width ||
        mapY < 0 || mapY >= game->map_height)
        return (0);

    // 壁チェック
    if (game->map[mapY][mapX] == '1')
        return (0);

    return (1);
}

---

6. ウィンドウ管理

6.1 終了処理

int close_window(t_game *game) {
    cleanup_game(game);
    exit(0);
    return (0);
}

void cleanup_game(t_game *game) {
    // テクスチャ解放
    if (game->tex_north.img)
        mlx_destroy_image(game->mlx, game->tex_north.img);
    // ... 他のテクスチャも同様

    // イメージ解放
    if (game->img)
        mlx_destroy_image(game->mlx, game->img);

    // ウィンドウ解放
    if (game->win)
        mlx_destroy_window(game->mlx, game->win);

    // マップ解放
    free_map(game->map);
}

6.2 イベント登録

void setup_hooks(t_game *game) {
    mlx_hook(game->win, 2, 1L<<0, key_press, game);    // KeyPress
    mlx_hook(game->win, 3, 1L<<1, key_release, game);  // KeyRelease
    mlx_hook(game->win, 17, 0, close_window, game);    // DestroyNotify
    mlx_loop_hook(game->mlx, game_loop, game);
}

---

まとめ

本章で学んだこと:

  • .cubファイル形式: 識別子とマップ
  • パーサー: 設定行とマップの解析
  • マップ検証: 壁で囲まれているか確認
  • 入力処理: キー入力と移動
  • ウィンドウ管理: 終了処理とイベント

次章では、最適化とボーナス機能を学びます。