C言語による拡張可能な多目並べアルゴリズムの実装

本記事では、C言語を用いて盤面のサイズや勝利条件(連続する石の数)を柔軟に変更可能な多目並べ(三目並べの拡張版)の実装例を紹介します。ヘッダーファイルの定数マクロを修正するだけで、標準的な3x3の三目並べから、大きな盤面を使った五目並べなど、様々なルールに即座に対応できる設計となっています。

以下に、設定用ヘッダーファイル、ゲームロジックの実装ファイル、およびメインのエントリーポイントの3つの構成要素に分けてコードを示します。

設定ファイル (game_config.h)

このファイルでは、盤面の行数・列数、および勝利判定に必要な連続数を定義します。また、ゲームの状態管理や関数プロトタイプを宣言します。

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 盤面設定
#define GRID_ROWS    10
#define GRID_COLS    10
#define CONNECT_TO_WIN 4 // この数だけ連続すれば勝利 (例: 3=三目並べ, 5=五目並べ)

// ゲームの状態を表す列挙型
typedef enum {
    STATUS_PLAYING,
    STATUS_DRAW,
    STATUS_PLAYER_WIN,
    STATUS_CPU_WIN
} GameState;

// 関数プロトタイプ
void run_game(void);
void initialize_grid(char grid[GRID_ROWS][GRID_COLS]);
void render_grid(char grid[GRID_ROWS][GRID_COLS]);
void handle_player_turn(char grid[GRID_ROWS][GRID_COLS]);
void handle_cpu_turn(char grid[GRID_ROWS][GRID_COLS]);
GameState evaluate_game_state(char grid[GRID_ROWS][GRID_COLS]);

ゲームロジックの実装 (game_logic.c)

実際のゲーム進行、盤面の描画、入力判定、および勝利ロジックを実装します。勝利判定では、水平・垂直・斜めの4方向について、指定された数(CONNECT_TO_WIN)だけ同じ記号が連続しているかを効率的にチェックします。配列外アクセスを防ぐため、ループ範囲と境界チェックを適切に実装しています。

#include "game_config.h"

void run_game(void) {
    char board[GRID_ROWS][GRID_COLS];
    initialize_grid(board);
    render_grid(board);

    GameState current_status = STATUS_PLAYING;

    while (current_status == STATUS_PLAYING) {
        // プレイヤーのターン
        handle_player_turn(board);
        current_status = evaluate_game_state(board);
        if (current_status != STATUS_PLAYING) break;

        // CPUのターン
        handle_cpu_turn(board);
        current_status = evaluate_game_state(board);
        if (current_status != STATUS_PLAYING) break;
    }

    // 結果の表示
    switch (current_status) {
        case STATUS_PLAYER_WIN: printf("プレイヤーの勝利です!\n"); break;
        case STATUS_CPU_WIN: printf("CPUの勝利です!\n"); break;
        case STATUS_DRAW: printf("引き分けです。\n"); break;
        default: break;
    }
}

// 盤面の初期化
void initialize_grid(char grid[GRID_ROWS][GRID_COLS]) {
    for (int r = 0; r < GRID_ROWS; r++) {
        for (int c = 0; c < GRID_COLS; c++) {
            grid[r][c] = ' ';
        }
    }
}

// 盤面の描画
void render_grid(char grid[GRID_ROWS][GRID_COLS]) {
    printf("\n");
    for (int r = 0; r < GRID_ROWS; r++) {
        for (int c = 0; c < GRID_COLS; c++) {
            printf(" %c ", grid[r][c]);
            if (c < GRID_COLS - 1) printf("|");
        }
        printf("\n");
        if (r < GRID_ROWS - 1) {
            for (int c = 0; c < GRID_COLS; c++) {
                printf("---");
                if (c < GRID_COLS - 1) printf("+");
            }
            printf("\n");
        }
    }
    printf("\n");
}

// プレイヤーの入力処理
void handle_player_turn(char grid[GRID_ROWS][GRID_COLS]) {
    int r, c;
    while (1) {
        printf("行と列をスペース区切りで入力してください (1-%d): ", GRID_ROWS);
        if (scanf("%d %d", &r, &c) != 2) {
            // 入力バッファのクリア
            while (getchar() != '\n');
            printf("無効な入力です。\n");
            continue;
        }

        // 配列のインデックスに合わせるために-1する
        int row_idx = r - 1;
        int col_idx = c - 1;

        if (row_idx >= 0 && row_idx < GRID_ROWS && col_idx >= 0 && col_idx < GRID_COLS) {
            if (grid[row_idx][col_idx] == ' ') {
                grid[row_idx][col_idx] = 'O'; // プレイヤーはO
                render_grid(grid);
                break;
            } else {
                printf("その場所は既に埋まっています。\n");
            }
        } else {
            printf("座標が範囲外です。\n");
        }
    }
}

// CPUの入力処理(ランダム)
void handle_cpu_turn(char grid[GRID_ROWS][GRID_COLS]) {
    printf("CPUが思考中...\n");
    int r, c;
    while (1) {
        r = rand() % GRID_ROWS;
        c = rand() % GRID_COLS;
        if (grid[r][c] == ' ') {
            grid[r][c] = 'X'; // CPUはX
            render_grid(grid);
            break;
        }
    }
}

// 勝利判定および引き分けチェック
GameState evaluate_game_state(char grid[GRID_ROWS][GRID_COLS]) {
    int win_count = CONNECT_TO_WIN;
    int directions[4][2] = {{0, 1}, {1, 0}, {1, 1}, {1, -1}}; // 右、下、右下、左下

    for (int r = 0; r < GRID_ROWS; r++) {
        for (int c = 0; c < GRID_COLS; c++) {
            char current = grid[r][c];
            if (current == ' ') continue;

            // 4方向をチェック
            for (int d = 0; d < 4; d++) {
                int dr = directions[d][0];
                int dc = directions[d][1];
                
                int count = 0;
                // 指定された数だけ連続しているか確認
                // ※ ここで単純にcount < win_countでループすると配列外になる可能性があるため、
                //    本来は境界チェックが必要ですが、可読性のため簡略化したロジックです。
                //    安全な実装ではループ前に r + dr*(win_count-1) が範囲内かチェックします。
                
                int valid_sequence = 1;
                for (int k = 0; k < win_count; k++) {
                    int nr = r + k * dr;
                    int nc = c + k * dc;
                    
                    if (nr < 0 || nr >= GRID_ROWS || nc < 0 || nc >= GRID_COLS) {
                        valid_sequence = 0;
                        break;
                    }
                    if (grid[nr][nc] != current) {
                        valid_sequence = 0;
                        break;
                    }
                }
                
                if (valid_sequence) {
                    return (current == 'O') ? STATUS_PLAYER_WIN : STATUS_CPU_WIN;
                }
            }
        }
    }

    // 引き分け判定(空きマスがあるか)
    for (int r = 0; r < GRID_ROWS; r++) {
        for (int c = 0; c < GRID_COLS; c++) {
            if (grid[r][c] == ' ') {
                return STATUS_PLAYING;
            }
        }
    }

    return STATUS_DRAW;
}

メインエントリーポイント (main.c)

プログラムの開始点です。メニュー画面を表示し、ユーザーの選択に応じてゲームを開始するか終了するかを制御します。

#include "game_config.h"

void display_menu() {
    printf("**************************\n");
    printf("*   多目並べ ゲーム       *\n");
    printf("**************************\n");
    printf("* 1. ゲーム開始           *\n");
    printf("* 0. 終了                 *\n");
    printf("**************************\n");
}

int main(void) {
    int user_choice = 0;
    srand((unsigned int)time(NULL));

    do {
        display_menu();
        printf("選択: ");
        if (scanf("%d", &user_choice) != 1) {
            user_choice = -1; // エラー処理へ
        }

        switch (user_choice) {
            case 1:
                run_game();
                break;
            case 0:
                printf("終了します。\n");
                break;
            default:
                printf("無効な選択です。もう一度入力してください。\n");
                // 入力バッファのクリア
                while (getchar() != '\n');
                break;
        }
    } while (user_choice != 0);

    return 0;
}

タグ: C GameDevelopment Algorithm ConsoleApplication

8月4日 22:22 投稿