ゲームロジックと移動

ゲーム状態の管理

ゲームの現在の状態を追跡するため、適切なデータ構造を使用します。

ゲーム状態の種類

typedef enum e_game_state
{
    STATE_PLAYING,
    STATE_WON,
    STATE_LOST,
    STATE_PAUSED
}   t_game_state;

typedef struct s_game
{
    void            *mlx_ptr;
    void            *win_ptr;
    t_map           map;
    t_textures      textures;
    int             moves;
    t_game_state    state;
}   t_game;

状態の初期化

void init_game_state(t_game *game)
{
    game->moves = 0;
    game->map.collected = 0;
    game->state = STATE_PLAYING;
}

プレイヤーの移動システム

移動の基本フロー

1. キー入力を受信
2. 新しい位置を計算
3. 移動の有効性をチェック
4. 移動を実行
5. ゲーム状態を更新
6. 画面を再描画

移動方向の定義

typedef enum e_direction
{
    DIR_UP,
    DIR_DOWN,
    DIR_LEFT,
    DIR_RIGHT
}   t_direction;

void get_new_position(t_position *pos, t_direction dir, int *new_x, int *new_y)
{
    *new_x = pos->x;
    *new_y = pos->y;

    if (dir == DIR_UP)
        (*new_y)--;
    else if (dir == DIR_DOWN)
        (*new_y)++;
    else if (dir == DIR_LEFT)
        (*new_x)--;
    else if (dir == DIR_RIGHT)
        (*new_x)++;
}

キー入力からの移動処理

int handle_input(int keycode, t_game *game)
{
    t_direction dir;
    int         new_x;
    int         new_y;

    // ゲームが終了している場合は入力を無視
    if (game->state != STATE_PLAYING)
        return (0);

    // キーコードを方向に変換
    if (keycode == KEY_W || keycode == KEY_UP)
        dir = DIR_UP;
    else if (keycode == KEY_S || keycode == KEY_DOWN)
        dir = DIR_DOWN;
    else if (keycode == KEY_A || keycode == KEY_LEFT)
        dir = DIR_LEFT;
    else if (keycode == KEY_D || keycode == KEY_RIGHT)
        dir = DIR_RIGHT;
    else if (keycode == KEY_ESC)
    {
        close_game(game);
        return (0);
    }
    else
        return (0);

    // 新しい位置を計算
    get_new_position(&game->map.player, dir, &new_x, &new_y);

    // 移動を試みる
    if (can_move_to(game, new_x, new_y))
    {
        execute_move(game, new_x, new_y);
    }

    return (0);
}

衝突検出

移動可能性のチェック

int can_move_to(t_game *game, int x, int y)
{
    char tile;

    // 境界チェック
    if (x < 0 || x >= game->map.width ||
        y < 0 || y >= game->map.height)
        return (0);

    tile = game->map.grid[y][x];

    // 壁チェック
    if (tile == WALL)
        return (0);

    // 出口チェック(収集物がすべて集まっていない場合)
    if (tile == EXIT && game->map.collected < game->map.collectibles)
        return (0);

    return (1);
}

タイルの種類による処理

typedef enum e_tile_type
{
    TILE_WALL,
    TILE_FLOOR,
    TILE_COLLECTIBLE,
    TILE_EXIT,
    TILE_PLAYER
}   t_tile_type;

t_tile_type get_tile_type(char c)
{
    if (c == '1')
        return (TILE_WALL);
    else if (c == '0')
        return (TILE_FLOOR);
    else if (c == 'C')
        return (TILE_COLLECTIBLE);
    else if (c == 'E')
        return (TILE_EXIT);
    else if (c == 'P')
        return (TILE_PLAYER);
    return (TILE_FLOOR);
}

int is_walkable(t_game *game, int x, int y)
{
    t_tile_type type;

    type = get_tile_type(game->map.grid[y][x]);

    if (type == TILE_WALL)
        return (0);

    if (type == TILE_EXIT)
        return (game->map.collected == game->map.collectibles);

    return (1);
}

移動の実行

基本的な移動処理

void execute_move(t_game *game, int new_x, int new_y)
{
    int old_x;
    int old_y;
    char target_tile;

    old_x = game->map.player.x;
    old_y = game->map.player.y;
    target_tile = game->map.grid[new_y][new_x];

    // 移動先のタイルに応じた処理
    handle_tile_interaction(game, target_tile);

    // マップの更新
    game->map.grid[old_y][old_x] = FLOOR;
    game->map.grid[new_y][new_x] = PLAYER;

    // プレイヤー位置の更新
    game->map.player.x = new_x;
    game->map.player.y = new_y;

    // 移動回数をインクリメント
    game->moves++;
    print_moves(game->moves);

    // 画面の更新
    update_tile(game, old_x, old_y);
    update_tile(game, new_x, new_y);
}

タイルとの相互作用

void handle_tile_interaction(t_game *game, char tile)
{
    if (tile == COLLECTIBLE)
        collect_item(game);
    else if (tile == EXIT)
        try_exit(game);
}

void collect_item(t_game *game)
{
    game->map.collected++;

    printf("Collectible gathered! [%d/%d]\n",
           game->map.collected, game->map.collectibles);

    // すべて集めた場合
    if (game->map.collected == game->map.collectibles)
    {
        printf("All collectibles gathered! Find the exit!\n");
        // 出口の見た目を変更するため、出口のタイルを再描画
        update_tile(game, game->map.exit.x, game->map.exit.y);
    }
}

void try_exit(t_game *game)
{
    if (game->map.collected == game->map.collectibles)
    {
        game->state = STATE_WON;
        handle_victory(game);
    }
    else
    {
        printf("Collect all items first! [%d/%d]\n",
               game->map.collected, game->map.collectibles);
    }
}

勝利条件の処理

勝利時の処理

void handle_victory(t_game *game)
{
    printf("\n");
    printf("════════════════════════════════\n");
    printf("     CONGRATULATIONS!          \n");
    printf("     You completed the level!  \n");
    printf("════════════════════════════════\n");
    printf("Total moves: %d\n", game->moves);
    printf("════════════════════════════════\n");
    printf("\n");

    // ボーナス: 勝利画面を表示
    display_victory_screen(game);

    // 少し待ってから終了
    sleep(3);
    close_game(game);
}

スコアリングシステム(ボーナス)

typedef struct s_score
{
    int moves;
    int collectibles;
    int time_seconds;
    int rating;  // 1-5 stars
}   t_score;

int calculate_rating(t_score *score)
{
    int optimal_moves;
    int rating;

    // 最適な移動回数を推定
    optimal_moves = score->collectibles * 5;

    if (score->moves <= optimal_moves)
        rating = 5;
    else if (score->moves <= optimal_moves * 1.2)
        rating = 4;
    else if (score->moves <= optimal_moves * 1.5)
        rating = 3;
    else if (score->moves <= optimal_moves * 2.0)
        rating = 2;
    else
        rating = 1;

    return (rating);
}

void display_score(t_game *game)
{
    t_score score;

    score.moves = game->moves;
    score.collectibles = game->map.collectibles;
    score.rating = calculate_rating(&score);

    printf("Score: ");
    print_stars(score.rating);
    printf(" (%d moves)\n", score.moves);
}

void print_stars(int count)
{
    int i;

    i = 0;
    while (i < count)
    {
        printf("★");
        i++;
    }
    while (i < 5)
    {
        printf("☆");
        i++;
    }
}

移動回数の表示

ターミナルへの出力

void print_moves(int moves)
{
    ft_putstr("Moves: ");
    ft_putnbr(moves);
    ft_putstr("\n");
}

ウィンドウ上への表示(ボーナス)

void render_move_counter(t_game *game)
{
    char *moves_str;
    char *text;

    moves_str = ft_itoa(game->moves);
    text = ft_strjoin("Moves: ", moves_str);

    mlx_string_put(game->mlx_ptr, game->win_ptr,
                   10, 20, 0xFFFFFF, text);

    free(moves_str);
    free(text);
}

より洗練されたUI表示

void render_ui(t_game *game)
{
    int y_offset;

    y_offset = 10;

    // 移動回数
    render_text(game, 10, y_offset, "MOVES", 0xFFFFFF);
    y_offset += 20;
    render_number(game, 10, y_offset, game->moves, 0x00FF00);
    y_offset += 30;

    // 収集物
    render_text(game, 10, y_offset, "ITEMS", 0xFFFFFF);
    y_offset += 20;
    render_collectibles(game, 10, y_offset);
}

void render_collectibles(t_game *game, int x, int y)
{
    char *collected_str;
    char *total_str;
    char *text;

    collected_str = ft_itoa(game->map.collected);
    total_str = ft_itoa(game->map.collectibles);
    text = ft_strjoin(collected_str, "/");
    text = ft_strjoin_free(text, total_str);

    mlx_string_put(game->mlx_ptr, game->win_ptr, x, y,
                   game->map.collected == game->map.collectibles ?
                   0x00FF00 : 0xFFFF00, text);

    free(collected_str);
    free(total_str);
    free(text);
}

アンドゥ機能(ボーナス)

移動履歴の保存

typedef struct s_move_history
{
    t_position          position;
    char                tile_was;
    int                 collectibles;
    struct s_move_history *next;
}   t_move_history;

void save_move(t_game *game, int old_x, int old_y, char old_tile)
{
    t_move_history *move;

    move = malloc(sizeof(t_move_history));
    if (!move)
        return;

    move->position.x = old_x;
    move->position.y = old_y;
    move->tile_was = old_tile;
    move->collectibles = game->map.collected;
    move->next = game->history;

    game->history = move;
}

void undo_move(t_game *game)
{
    t_move_history *last_move;
    int curr_x;
    int curr_y;

    if (!game->history)
    {
        printf("No moves to undo\n");
        return;
    }

    last_move = game->history;

    // 現在の位置を保存
    curr_x = game->map.player.x;
    curr_y = game->map.player.y;

    // プレイヤーを元の位置に戻す
    game->map.player.x = last_move->position.x;
    game->map.player.y = last_move->position.y;

    // タイルを復元
    game->map.grid[curr_y][curr_x] = last_move->tile_was;
    game->map.grid[last_move->position.y][last_move->position.x] = PLAYER;

    // 収集物の数を復元
    game->map.collected = last_move->collectibles;

    // 移動回数を減らす
    game->moves--;

    // 履歴から削除
    game->history = last_move->next;
    free(last_move);

    // 画面を再描画
    render_game(game);

    printf("Move undone. Moves: %d\n", game->moves);
}

デバッグモード

デバッグ情報の表示

void render_debug_info(t_game *game)
{
    int y;

    y = game->map.height * TILE_SIZE + 20;

    printf("=== DEBUG INFO ===\n");
    printf("Player: (%d, %d)\n", game->map.player.x, game->map.player.y);
    printf("Moves: %d\n", game->moves);
    printf("Collected: %d/%d\n", game->map.collected, game->map.collectibles);
    printf("State: %d\n", game->state);

    // グリッド座標を表示
    draw_grid(game);
}

void draw_grid(t_game *game)
{
    int x;
    int y;
    int i;

    // 縦線
    i = 0;
    while (i <= game->map.width)
    {
        x = i * TILE_SIZE;
        y = 0;
        while (y < game->map.height * TILE_SIZE)
        {
            mlx_pixel_put(game->mlx_ptr, game->win_ptr, x, y, 0x404040);
            y += 4;
        }
        i++;
    }

    // 横線
    i = 0;
    while (i <= game->map.height)
    {
        y = i * TILE_SIZE;
        x = 0;
        while (x < game->map.width * TILE_SIZE)
        {
            mlx_pixel_put(game->mlx_ptr, game->win_ptr, x, y, 0x404040);
            x += 4;
        }
        i++;
    }
}

ゲームのリセット

リセット機能

void reset_game(t_game *game, char *map_file)
{
    // 現在の状態をクリア
    free_map(&game->map);
    destroy_textures(game);

    // ゲームを再初期化
    if (!init_game(game, map_file))
    {
        printf("Failed to reset game\n");
        close_game(game);
    }

    // 画面を再描画
    render_game(game);

    printf("Game reset\n");
}

// Rキーでリセット
int handle_input(int keycode, t_game *game)
{
    // ... 他のキー処理 ...

    if (keycode == KEY_R)
    {
        reset_game(game, game->map_file);
        return (0);
    }

    // ... 続き ...
}

ゲームオーバー条件(ボーナス)

敵キャラクターを追加する場合、ゲームオーバー条件が必要になります。

void handle_enemy_collision(t_game *game)
{
    game->state = STATE_LOST;
    handle_game_over(game);
}

void handle_game_over(t_game *game)
{
    printf("\n");
    printf("════════════════════════════════\n");
    printf("       GAME OVER!              \n");
    printf("════════════════════════════════\n");
    printf("You were caught!\n");
    printf("Moves: %d\n", game->moves);
    printf("Press R to restart or ESC to quit\n");
    printf("════════════════════════════════\n");
    printf("\n");

    // ゲームオーバー画面を表示
    display_game_over_screen(game);
}

int handle_input_game_over(int keycode, t_game *game)
{
    if (keycode == KEY_R)
        reset_game(game, game->map_file);
    else if (keycode == KEY_ESC)
        close_game(game);

    return (0);
}

パフォーマンス測定

FPS(フレーム/秒)の計算

typedef struct s_performance
{
    struct timeval  last_time;
    int             frame_count;
    float           fps;
}   t_performance;

void update_fps(t_performance *perf)
{
    struct timeval  current_time;
    long            elapsed_us;
    float           elapsed_sec;

    perf->frame_count++;

    gettimeofday(&current_time, NULL);

    elapsed_us = (current_time.tv_sec - perf->last_time.tv_sec) * 1000000
                 + (current_time.tv_usec - perf->last_time.tv_usec);
    elapsed_sec = elapsed_us / 1000000.0;

    if (elapsed_sec >= 1.0)
    {
        perf->fps = perf->frame_count / elapsed_sec;
        perf->frame_count = 0;
        perf->last_time = current_time;

        printf("FPS: %.2f\n", perf->fps);
    }
}

入力のバッファリング

複数のキーを同時に処理する必要がある場合(ボーナス)。

typedef struct s_input_buffer
{
    int keys[256];
    int last_key;
}   t_input_buffer;

int handle_keypress(int keycode, t_game *game)
{
    game->input.keys[keycode] = 1;
    game->input.last_key = keycode;
    return (0);
}

int handle_keyrelease(int keycode, t_game *game)
{
    game->input.keys[keycode] = 0;
    return (0);
}

void process_input(t_game *game)
{
    if (game->input.keys[KEY_W] || game->input.keys[KEY_UP])
        try_move(game, DIR_UP);
    else if (game->input.keys[KEY_S] || game->input.keys[KEY_DOWN])
        try_move(game, DIR_DOWN);
    else if (game->input.keys[KEY_A] || game->input.keys[KEY_LEFT])
        try_move(game, DIR_LEFT);
    else if (game->input.keys[KEY_D] || game->input.keys[KEY_RIGHT])
        try_move(game, DIR_RIGHT);
}

まとめ

この章では、ゲームロジックと移動処理について学びました:

  • ゲーム状態管理: 状態の種類と遷移
  • 移動システム: キー入力から移動の実行まで
  • 衝突検出: 壁や障害物との衝突チェック
  • アイテム収集: 収集物の処理と追跡
  • 勝利条件: ゲームクリアの判定と表示
  • ボーナス機能: スコアリング、アンドゥ、リセット

次の章では、アニメーション、ボーナス機能、そしてゲームの仕上げについて学びます。スプライトアニメーション、敵キャラクター、音響効果など、ゲームをより魅力的にする要素を実装していきます。