本記事では、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;
}