はじめに
このプロジェクトでは、C 言語でシンプルな三目並べゲームを作成する方法を学びます。このゲームは、2 人のプレイヤーが交互に 3x3 のグリッドの空きマスにマークをつける形で行われます。最初に横、縦、または斜めに 3 つのマークを並べたプレイヤーが勝者となります。すべてのマスが埋まっても誰も 3 つのマークを並べられない場合は、引き分けとなります。
👀 プレビュー

🎯 タスク
このプロジェクトでは、以下のことを学びます。
- ゲームボードを作成し、空きマスで初期化する方法。
- 画面をクリアし、ゲームボードを表示し、ゲームが終了したかどうかをチェックする関数を実装する方法。
- ゲームの勝者を決定する方法。
- プレイヤーが交互に操作し、ゲームとやり取りできるようにメインのゲームループを実装する方法。
🏆 達成目標
このプロジェクトを完了した後、以下のことができるようになります。
- C 言語で配列を作成し、操作すること。
- ループと条件分岐を使ってゲームロジックを実装すること。
- コマンドラインインターフェースを通じてユーザーとやり取りすること。
- コードを関数に分割し、モジュール性と可読性を向上させること。
プロジェクトファイルを作成する
まず、tictactoe.c という名前の新しいファイルを作成し、好みのコードエディタで開きます。
cd ~/project
touch tictactoe.c
定数を定義する
ここで、C 言語のコードを書く必要があります。最初のステップは、ヘッダーファイルをインクルードすることです。
#include <stdio.h>
#include <stdbool.h>
ボードのサイズを宣言します。
char board[3][3];
ゲームボードを初期化する
ゲームボードを空きマスで初期化します。
void initializeBoard() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
board[i][j] = ' ';
}
}
}
最初の for は外側のループで、変数 i を使って 0 から 2 まで繰り返し、ゲームボードの行数を表します。2 番目の for は内側のループで、変数 j を使って 0 から 2 まで繰り返し、順にゲームボードの列数を表します。
board[i][j] = ' ' は内側のループで、ゲームボードの i 行 j 列のマスの内容を空白文字 (' ') に設定します。これは、ゲームボード全体が空の状態に初期化され、まだプレイヤーがボード上でプレイしていないことを意味します。
各ターン後に画面をクリアする
各ターンの後に画面をクリアする関数を実装します。
void clearScreen() {
printf("\033[H\033[J");
}
clearScreen 関数は、主にターミナルまたはコンソール画面の内容をクリアし、各ターンの終了時に前のゲーム状態と出力を消去するために使用されます。これは、画面上のテキスト内容をクリアする特殊なエスケープシーケンスを標準出力ストリーム(通常はターミナルウィンドウ)に出力することで行われます。
printf("\033[H\033[J") は ANSI エスケープシーケンスを使用しています。
\033はASCIIエスケープ文字の 8 進数表現で、シーケンスの開始を表します。[Hはカーソルを画面の左上隅に移動することを示し、画面の 1 行 1 列目にカーソルを位置付けるのと同等です。[Jは画面をクリアすることを意味します。カーソル位置以降のすべてのテキスト内容、つまり前のゲーム状態と出力をクリアします。
これは、clearScreen 関数が呼び出されるたびに、次のゲームセッションの前に新しいゲーム状態を表示するために、現在のターミナルまたはコンソール画面の内容をクリアし、よりクリーンなインターフェイスを実現することを意味します。
ゲームボードを表示する
printBoard 関数を作成して、現在のゲームボードの状態を画面に視覚的に表示し、プレイヤーがボード上の駒の位置を把握できるようにします。これは、三目並べゲームでゲームの状態を表示するために使用されるゲームの一部です。
void printBoard() {
printf("\n");
printf(" 1 2 3\n");
printf("1 %c | %c | %c\n", board[0][0], board[0][1], board[0][2]);
printf(" ---------\n");
printf("2 %c | %c | %c\n", board[1][0], board[1][1], board[1][2]);
printf(" ---------\n");
printf("3 %c | %c | %c\n", board[2][0], board[2][1], board[2][2]);
}
ゲームが終了したかどうかを確認する
三目並べゲームが終了したかどうかを判断し、ブール値 (true または false) を返す isGameOver という関数を実装します。
bool isGameOver() {
// Check rows
for (int i = 0; i < 3; i++) {
if (board[i][0]!= ' ' && board[i][0] == board[i][1] && board[i][0] == board[i][2]) {
return true;
}
}
// Check columns
for (int j = 0; j < 3; j++) {
if (board[0][j]!= ' ' && board[0][j] == board[1][j] && board[0][j] == board[2][j]) {
return true;
}
}
// Check diagonals
if (board[0][0]!= ' ' && board[0][0] == board[1][1] && board[0][0] == board[2][2]) {
return true;
}
if (board[0][2]!= ' ' && board[0][2] == board[1][1] && board[0][2] == board[2][0]) {
return true;
}
// Check for a draw
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j] == ' ') {
return false;
}
}
}
return true;
}
行に勝者がいるかどうかを確認する: ある行の 3 つのセルすべてが同じプレイヤーによって占有されている場合、勝者がいるため、関数は true を返します。
列に勝者がいるかどうかを確認する: ある列の 3 つのセルすべてが同じプレイヤーによって占有されている場合、勝者がいるため、関数は true を返します。
対角線に勝者がいるかどうかを確認する: いずれかの対角線上の 3 つのマスすべてが同じプレイヤーによって占有されている場合、勝者がいるため、関数は true を返します。
引き分けを確認する: 最後に、関数はすべてのセルが占有されているが勝者がいないかどうかを確認します。この場合、ゲームは引き分けとなり、関数は true を返します。
勝者を決定する
getWinner という名前の関数が定義されており、その目的は、三目並べゲームに勝者がいるかどうかを判定し、勝者の記号 (X または O) を返すか、勝者がいない場合は空白文字 (' ') を返すことです。
char getWinner() {
// Check rows
for (int i = 0; i < 3; i++) {
if (board[i][0]!= ' ' && board[i][0] == board[i][1] && board[i][0] == board[i][2]) {
return board[i][0];
}
}
// Check columns
for (int j = 0; j < 3; j++) {
if (board[0][j]!= ' ' && board[0][j] == board[1][j] && board[0][j] == board[2][j]) {
return board[0][j];
}
}
// Check diagonals
if (board[0][0]!= ' ' && board[0][0] == board[1][1] && board[0][0] == board[2][2]) {
return board[0][0];
}
if (board[0][2]!= ' ' && board[0][2] == board[1][1] && board[0][2] == board[2][0]) {
return board[0][2];
}
return ' '; // No winner
}
メインゲームループを実装する
プレイヤーが交互にターンを取り、ゲームと対話できるように、メインゲームループを作成します。
int main() {
initializeBoard();
int currentPlayer = 1;
while (1) {
clearScreen();
printf("Current board state:\n");
printBoard();
int row, col;
printf("Player %d, please enter a row and column (e.g., 1 2):", currentPlayer);
while (scanf("%d %d", &row, &col)!= 2) {
printf("Invalid input, please try again: ");
while (getchar()!= '\n');
}
if (row < 1 || row > 3 || col < 1 || col > 3 || board[row - 1][col - 1]!= ' ') {
printf("Invalid move, please try again.\n");
} else {
if (currentPlayer == 1) {
board[row - 1][col - 1] = 'X';
currentPlayer = 2;
} else {
board[row - 1][col - 1] = 'O';
currentPlayer = 1;
}
}
if (isGameOver()) {
clearScreen();
printf("Game over!\n");
printBoard();
char winner = getWinner();
if (winner!= ' ') {
printf("Player %c wins!\n", winner);
} else {
printf("It's a draw!\n");
}
break;
}
}
return 0;
}
initializeBoard 関数を呼び出してゲームボードを初期化し、すべてのマスを空白に設定します。
整数型変数 currentPlayer を作成し、現在のターンが誰の番かを追跡します。初期設定は 1 で、プレイヤー 1 を示します。
while (1) のメインループでは:
clearScreen関数を呼び出して画面をクリアし、各ターン後に表示を更新します。printBoard関数を呼び出して現在のゲームボードを表示します。
プレイヤー入力: scanf 関数を介して、現在のプレイヤーに行と列の座標を入力するよう促します。例えば、Player 1, please enter a row and column (e.g., 1 2): と表示されます。入力が無効(2 つの整数でない)場合、Invalid input, please try again: というメッセージを表示し、有効な入力が得られるまで入力バッファをクリアします。
入力検証: 次に、入力された行と列の座標が有効(1 から 3 の範囲内)で、選択された位置が空白であることを確認します。入力が無効または選択された位置がすでに占有されている場合、Invalid move, please try again. というメッセージを表示し、プレイヤーに再入力を求めます。
駒を置く: 入力が有効な場合、コードは現在のプレイヤー(1 または 2)に基づいて、ゲームボードの対応する位置に X または O を置きます。
ゲーム終了検出: 次に、コードは isGameOver() 関数を呼び出して、ゲームが終了したかどうかを確認します。ゲームが終了した場合、画面をクリアし、ゲームが終了したことを示すメッセージを表示します。これには、勝者(ある場合)または引き分けの情報が含まれます。
ゲーム結果: 勝者がいる場合、コードは勝者が誰であるかに応じて、Player X wins! または Player O wins! と表示します。勝者がいない場合、コードは It's a draw! と表示して引き分けを示します。
プロジェクトをコンパイルして実行する
gcc コマンドを実行してコンパイルします。
cd ~/project
gcc -o tictactoe tictactoe.c
./tictactoe

まとめ
おめでとうございます!C 言語で簡単な三目並べゲームを作成することに成功しました。プレイヤーは交互にターンを取り、3x3 のグリッド上に自分のマークを置くことができます。ゲームは各ターン後に勝者または引き分けをチェックし、それに応じて結果を表示します。この簡単なプロジェクトは、C 言語プログラミングの基本的な概念を示しており、より複雑なゲーム開発の出発点として役立ちます。



