はじめに
このプロジェクトでは、C 言語を使って簡単なテキストベースの五目並べゲームを作成します。五目並べは 2 人用の戦略盤ゲームで、横、縦、斜めのいずれかで 5 個の石が連続して並ぶことが目的です。このゲームは 15x15 の盤面を使って開発します。
👀 プレビュー

🎯 タスク
このプロジェクトでは、以下のことを学びます。
- C 言語で二次元配列を使ってチェス盤を設計・実装する方法
- ゲームの流れを制御するメイン関数を作成する方法
- ゲームを初期化し、チェス盤を表示し、プレイヤーに手番を回す機能を実装する方法
- ゲームで勝利条件をチェックする関数を開発する方法
- プログラムをコンパイルして実行する方法
🏆 成果
このプロジェクトを完了すると、以下のことができるようになります。
- C 言語の二次元配列を扱うことができる
- 関数を使ってゲームの流れを設計・実装することができる
- ゲームで勝利条件をチェックすることができる
- C プログラムをコンパイルして実行することができる
プロジェクトファイルを作成する
まず、gomoku.c という名前の新しいファイルを作成し、好きなコードエディタで開きます。
cd ~/project
touch gomoku.c
チェス盤を設計する
駒を「O」と「X」で表す
まず、盤面の各位置の「状況」を記録するために、チェス盤(15×15)が必要です。そこで、配列 chessboard[16][16] を定義できます。なぜ [15][15] ではないのかというと、このようにすると、配列の座標がチェス盤の行と列に正確に対応するため、後続のコードを記述しやすくなるからです。
#include <stdio.h>
#define N 15
// 配列を定義し、各要素に初期値として 0 を代入する。
int chessboard[N + 1][N + 1] = { 0 };
main 関数
main 関数を書き始める前に、ゲームの典型的な流れを簡単に考えてみましょう。まず、ゲームのメイン画面に入り、次に開始ボタンをクリックしてゲームに入り、次にゲーム画面を表示し、勝敗を判定してゲームを終了します。では、五目並べのゲームの流れはどのようなものでしょうか。
まず、ゲームのウェルカム画面に入り、次に Y を入力してゲームを開始します(Y 以外を入力するとゲームを終了します)。ゲーム盤を表示し、2 人のプレイヤーが交互に駒を置き、次に勝敗を判定します(5 個の駒が連続して並んでいるかどうか)。
// プレイヤー1 の番かプレイヤー2 の番かを追跡するために使用され、奇数がプレイヤー1 の番を表し、偶数がプレイヤー2 の番を表します。
int whoseTurn = 0;
int main(void)
{
// ゲームを初期化するカスタム関数、つまりウェルカム画面を表示してゲーム表示盤に入ります。
initGame();
// このループは 2 人のプレイヤーが交互に行動するためのものです。
while (1)
{
// 各サイクルで 1 増やされるので、2 人が交互に行動できます。
whoseTurn++;
// 駒を置く操作を行うカスタム関数。
playChess();
}
return 0;
}
initGame 関数
この関数で実装したい機能は以下の通りです。
- 簡単なウェルカム画面を表示する。
- 'Y' を入力を求め、入力後にチェス盤を表示する。
void initGame(void)
{
char c;
printf("Please input 'y' to enter the game:");
c = getchar();
if ('y'!= c && 'Y'!= c)
exit(0);
//Clear
system("clear");
//ここで再び、チェス盤を表示する機能を持つカスタム関数を呼び出します。
printChessboard();
}
initGame 関数では、exit 関数と system 関数を使用しているため、プログラムの先頭でヘッダファイル stdlib.h をインクルードする必要があります。
#include <stdlib.h>
printChessboard 関数
この関数での目的は以下の通りです。
- 行番号と列番号を表示し、チェス盤を表示する。
- 配列要素の値が 0 の場合、その位置が空であることを示すためにアスタリスク(*)を表示する。
- 配列要素の値が 1 の場合、プレイヤー1 の駒を表す黒丸(X)を表示する。
- 配列要素の値が 2 の場合、プレイヤー2 の駒を表す白丸(O)を表示する。
void printChessboard(void)
{
int i, j;
for (i = 0; i <= N; i++)
{
for (j = 0; j <= N; j++)
{
if (0 == i) //これは列番号を表示します。
printf("%3d", j);
else if (j == 0) //行番号を表示します。
printf("%3d", i);
else if (1 == chessboard[i][j])
printf(" X");
else if (2 == chessboard[i][j])
printf(" O");
else
printf(" *");
}
printf("\n");
}
}
playChess 関数
この関数では、以下のことを達成したいと思っています。
- プレイヤーに駒を置く位置を入力するよう促す。
- 現在がプレイヤー1 の番の場合、配列の対応する要素に 1 の値を代入する。
- 現在がプレイヤー2 の番の場合、配列の対応する要素に 2 の値を代入する。
- 各手番の後、現在のプレイヤーが勝利したかどうかを判定する。
void playChess(void)
{
int i, j, winner;
// プレイヤー1 の番かプレイヤー2 の番かを判定し、その後配列の対応する要素に値を代入する。
if (1 == whoseTurn % 2)
{
printf("Turn to player 1, please input the position, the format is line number + space number + column number: ");
scanf("%d %d", &i, &j);
chessboard[i][j] = 1;
}
else
{
printf("Turn to player 2, please input the position, the format is line number + space number + column number: ");
scanf("%d %d", &i, &j);
chessboard[i][j] = 2;
}
// 盤面を再度表示する。
system("clear");
printChessboard(); // この関数を再度呼び出す。
/*
* 以下のセクションでは、カスタム関数(判定関数)を呼び出します。
* 現在のプレイヤーがその手番で勝利したかどうかを判定するために使用されます。
*/
if (judge(i, j, whoseTurn))
{
if (1 == whoseTurn % 2)
printf("Winner is player 1!\n");
else
printf("Winner is player 2!\n");
}
}
judge 関数
関数のパラメータ:
- x: 現在の手番の行番号
- y: 現在の手番の列番号
返り値:
- 1 または 0。1 は、現在のプレイヤーがその手番で勝利したことを意味する。
int judge(int x, int y)
{
int i, j;
int t = 2 - whoseTurn % 2;
const int step[4][2]={{-1,0},{0,-1},{1,1},{1,0}};
for(int i=0;i<4;++i)
{
const int d[2]={-1,1};
int count=1;
for(int j=0;j<2;++j)
for(int k=1;k<=4;++k){
int row=x+k*d[j]*step[i][0];
int col=y+k*d[j]*step[i][1];
if( row>=1 && row<=N && col>=1 && col<=N &&
chessboard[x][y]==chessboard[row][col])
count+=1;
else
break;
}
if(count>=5)
return 1;
}
return 0;
}
judge関数には 3 つのネストされたforループがあり、その目的は 5 つの連続した駒の列があるかどうかを判定することです。
5 つの駒の列は、横、縦、斜めの方向のいずれかにあり得ます。ここでは、試行錯誤法を使って、水平、垂直、斜めの方向に沿って連続した駒を探します。例を挙げましょう。

上記のチェス盤では、座標 (9, 10) を基に 5 つの駒の列があるかどうかを判定するアルゴリズムを説明します。
まず、(9, 10) から始まる 5 つの駒の斜めの列があるかどうかを確認します。プロセスは以下の通りです。
- (9, 10) から始めて、左上方向を検索します。条件を満たす座標は (8, 9)、(7, 8)、(6, 7) です。(5, 6) が条件を満たさないので、次のステップに進みます。
- 次に、右下方向を検索して (10, 11) を見つけます。これが条件を満たす唯一の座標です。
- 一直線上に 5 つの点が見つかったので、プレイヤー2 の勝ちです。
斜め方向に 5 つの駒の列がない場合、次に垂直と水平方向を確認します。どちらも勝利条件を満たさない場合、現在のプレイヤーは勝てず、ゲームは続きます。
私の駒が「食べられた」
誰かに気付いてもらえたかどうかは分かりませんが、私たちの五目並べゲームでは、すでに占められている位置でも、自分の駒を置く際に元の駒を「食べる」ことができます。
これは、playChess関数を書いたときに、駒を置く位置をチェックしていなかったためです。コードを以下のように修正することができます。
void playChess(void)
{
int i, j, winner;
if (1 == whoseTurn % 2)
{
printf("Turn to player 1, please input the position, the format is line number + space number + column number: ");
scanf("%d %d", &i, &j);
//debug
while(chessboard[i][j]!= 0)
{
printf("This position has been occupied, please input the position again: ");
scanf("%d %d",&i, &j);
}
//debug
chessboard[i][j] = 1;
}
else
{
printf("Turn to player 2, please input the position, the format is line number + space number + column number: ");
scanf("%d %d", &i, &j);
//debug
while(chessboard[i][j]!= 0)
{
printf("This position has been occupied, please input the position again: ");
scanf("%d %d",&i, &j);
}
//debug
chessboard[i][j] = 2;
}
system("clear");
printChessboard();
if (judge(i, j))
{
if (1 == whoseTurn % 2)
printf("The winner is player 1!\n");
else
printf("The winner is player 2!\n");
}
}
関数内にループを追加したので、位置が既に占められている場合、プロンプトを表示して新しい入力を求めます。
なぜずっと勝てないのか?
5 つの駒の列があり、「プレイヤー1」または「プレイヤー2」の勝利が表示された後、「現在はプレイヤー*の番です。駒を置く位置を入力してください...」と表示されます。イライラしませんよね?実際には、コードを 1 行追加するだけです!
if (judge(i, j))
{
if (1 == whoseTurn % 2)
{
printf("Winner is player 1!\n");
exit(0); //debug
}
else
{
printf("Winner is player 2!\n");
exit(0); //debug
}
}
}
勝利したプレイヤーを表示するだけでなく、exit(0) でゲームを終了します。
コンパイルと実行
gccコマンドを実行してコンパイルします。
cd ~/project
gcc -o gomoku gomoku.c
./gomoku

まとめ
おめでとうございます!C 言語を使って簡単な五目並べゲームを作成しました。プレイヤーは 15x15 の盤面で交互に駒を置くことができ、あるプレイヤーが 5 つの連続した駒を並べたとき、プログラムはそのプレイヤーを勝者と宣言します。友人たちと一緒にこのテキストベースのゲームを楽しんでください!



