C言語で作る!初心者が楽しむ5ステップのオセロゲーム作成ガイド

C言語のコード例を表すスクリーンショットC言語
この記事は約18分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

オセロは戦略と洞察力を問う楽しいゲームです。

今回は、初心者でも楽しく学べるように「C言語で作るオセロゲーム作成ガード」をご紹介します。

C言語の基本からオセロのアルゴリズム、エラーハンドリング、カスタマイズ方法まで詳しく解説します。

●C言語の基礎

まずは、C言語の基礎から学びましょう。

特に変数とデータ型、制御文、関数の理解が重要となります。

詳細については他の記事で紹介していますので、当サイトの検索窓から検索いただければと思います。

○変数とデータ型

C言語では、変数を使ってデータを扱います。変数はデータを一時的に保持するためのもので、データ型によりその変数が保持できるデータの種類とサイズが決まります。

主なデータ型には、int(整数)、float(実数)、char(1文字)、などがあります。

○制御文

制御文はプログラムの流れを制御するための文で、条件分岐(if文、switch文)、ループ(for文、while文)、などがあります。

これらの制御文をうまく使うことで、ゲームのルールや流れを表現することが可能となります。

○関数

関数は特定の処理をまとめたもので、一度定義してしまえば何度でも呼び出すことができます。

オセロゲームを作る際にも、石を置く処理や石をひっくり返す処理など、同じような処理を何度も行う場面があります。

それらの処理を関数として定義しておくと、コードの見通しが良くなり、プログラムも効率的になります。

●オセロゲームの作り方

オセロゲームを作るためのステップは、主に5つあります。

それぞれを順番に見ていきましょう。

○ゲーム盤の表示

まずはゲーム盤を表示することから始めます。

ゲーム盤は8×8の64マスから成り立ちます。このマスを二次元配列で表現することが一般的です。

ゲーム盤を初期化し、表示するコードを紹介します。

#include<stdio.h>

// ゲーム盤を表す二次元配列
int board[8][8];

// ゲーム盤を初期化する関数
void init_board() {
    int i, j;
    for(i = 0; i < 8; i++) {
        for(j = 0; j < 8; j++) {
            board[i][j] = 0;
        }
    }
    board[3][3] = board[4][4] = 1; // 白石を置く
    board[3][4] = board[4][3] = -1; // 黒石を置く
}

// ゲーム盤を表示する関数
void print_board() {
    int i, j;
    for(i = 0; i < 8; i++) {
        for(j = 0; j < 8; j++) {
            if(board[i][j] == 1) printf("W ");
            else if(board[i][j] == -1) printf("B ");
            else printf(". ");
        }
        printf("\n");
    }
}

int main() {
    init_board();
    print_board();
    return 0;
}

このコードでは、init_board関数でゲーム盤を初期化し、print_board関数でゲーム盤を表示しています。

ゲーム盤は二次元配列boardで表現され、各マスには白石を表す1、黒石を表す-1、何もない空きマスを表す0が格納されます。

初期状態では中央に白と黒の石が配置されるように初期化しています。

実行すると、次のような出力が得られます。

. . . . . . . . 
. . . . . . . . 
. . . . . . . . 
. . . W B . . . 
. . . B W . . . 
. . . . . . . . 
. . . . . . . . 
. . . . . . . .

これがオセロのゲーム盤の初期状態です。

.は空きマス、Wは白石、Bは黒石を表しています。

○石を置く処理

次に石を置く処理を実装します。

これはユーザーからの入力を受け取り、適切な場所に石を置くことを指します。

ただし、オセロではただ単に石を置くだけでなく、石を置くことが可能かどうかのチェックが必要です。

これは「自分の石で挟める相手の石があるか」というルールから来ています。

下記のコードは、ユーザーからの入力を受け取り、指定された場所に石が置けるかどうかをチェックし、置ける場合は石を置くという処理を行う関数put_stoneを示しています。

#include<stdio.h>

int board[8][8]; // ゲーム盤

// 石を置く処理
int put_stone(int x, int y, int color) {
    if(board[x][y] != 0) return 0; // すでに石が置かれている場合は置けない
    // 省略: 挟む石があるかのチェック
    board[x][y] = color; // 石を置く
    // 省略: 挟んだ石をひっくり返す処理
    return 1; // 石を置くことができた
}

// 以下は変更なし
// init_board関数、print_board関数、main関数

このコードでは、まず指定された位置にすでに石が置かれていないかをチェックしています。

すでに石が置かれている場合は石を置くことができないため、0を返して処理を終了します。

石が置ける場合は、その位置に石を置き、1を返しています。

なお、このコードでは挟む石があるかのチェックや挟んだ石をひっくり返す処理は省略しています。

○石をひっくり返す処理

石を置くと、その石によって挟まれた敵の石が自分の石に変わります。

この操作はオセロの重要なルールで、これを正しく実装することで初めてオセロのゲームとして成り立ちます。

ここでは、この石をひっくり返す処理をどのようにコーディングするかを学びましょう。

// 石をひっくり返す処理
void flipDiscs(int board[8][8], int x, int y, int color) {
    // 8方向を確認するための変数
    int dx, dy;
    // 敵の色
    int opponent = (color == BLACK) ? WHITE : BLACK;

    // 8方向すべてについて確認を行う
    for (dx = -1; dx <= 1; dx++) {
        for (dy = -1; dy <= 1; dy++) {
            // x,yと同じ座標はスキップ
            if (dx == 0 && dy == 0) continue;

            // 一つ隣の座標
            int nx = x + dx;
            int ny = y + dy;

            // 隣が盤面の内部であり、かつ敵の石がある間ループを続ける
            while (nx >= 0 && nx < 8 && ny >= 0 && ny < 8 && board[ny][nx] == opponent) {
                nx += dx;
                ny += dy;
            }

            // ループが終わった場所が盤面内で、かつ自分の石があるなら
            if (nx >= 0 && nx < 8 && ny >= 0 && ny < 8 && board[ny][nx] == color) {
                // ひっくり返す処理を行う
                while (true) {
                    nx -= dx;
                    ny -= dy;
                    if (nx == x && ny == y) break;
                    board[ny][nx] = color;
                }
            }
        }
    }
}

このコードでは、盤面と現在の位置、そして現在の色を引数として受け取り、対象の座標に石を置くと同時に周囲の石をひっくり返す処理を実行しています。

ループ内で8方向すべてについて確認を行い、それぞれの方向について隣接する敵の石を見つけたら、さらにその方向に自分の石があるかどうかを探します。

もし自分の石を見つけた場合は、その間にある全ての敵の石を自分の石に変えます。

このコードを実行した結果、盤面の状態が以下のように変化します。

例えば、次のような盤面があったとします。

・・・・・・
・・●○・・
・・○○●・・
・・・・・・

このとき、(1, 2)の位置(左上が(0, 0))に白石を置くと、その上の黒石が挟まれて白石になります。

その結果、次のような盤面に変化します。

・・・・・・
・・○○・・
・・○○●・・
・・・・・・

このように、オセロのルールを適切に実装することで、ゲームの進行が実現できます。

このプロセスを理解し、正確にコードに落とし込むことが、オセロゲームを作る上での重要なステップです。

○勝者の判定

オセロゲームの終わりは、全てのマスに石が置かれたとき、あるいは双方がパスをしたときです。

そして、ゲームの勝者は黒と白の石の数を数え、多い方が勝者となります。

それでは、ゲーム盤上の石の数を数えて勝者を判定するコードを見てみましょう。

void countStone(char board[8][8]) {
  int black = 0;
  int white = 0;
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
      if (board[i][j] == '●') {
        black++;
      } else if (board[i][j] == '○') {
        white++;
      }
    }
  }
  printf("●: %d\n", black);
  printf("○: %d\n", white);
}

このコードでは、二次元配列のboardを用いて石の数を数えています。

for文を使ってゲーム盤の全てのマスをチェックし、黒石または白石がある場合はそれぞれのカウンタを増やします。

続いて、黒と白の石の数を比較して勝者を判定する関数を実装しましょう。

void judge(int black, int white) {
  if (black > white) {
    printf("●の勝ちです\n");
  } else if (white > black) {
    printf("○の勝ちです\n");
  } else {
    printf("引き分けです\n");
  }
}

この関数では、引数として受け取った黒石と白石の数を比較し、多い方が勝者となるようにしています。

石の数が同じ場合は引き分けと判定しています。

これらのコードを組み合わせると、次のようにゲーム終了時の勝者を判定できます。

void endGame(char board[8][8]) {
  int black = 0;
  int white = 0;
  countStone(board, &black, &white);
  judge(black, white);
}

この関数endGameは、石の数を数えるcountStone関数と勝者を判定するjudge関数を呼び出すことで、ゲームの終了と勝者の判定を一括して行います。

ただし、このコードでは引数としてゲーム盤を表す二次元配列boardを必要とします。

また、countStone関数から黒石と白石の数を受け取るためにポインタを使用しています。

以上がオセロゲームにおける勝者の判定についての解説となります。

初めてC言語でゲームを作成するときには、このようなゲームのルールをコードに落とし込む作業が必要となります。

○ゲームの進行

それでは次に、オセロゲームの進行を管理する方法について見ていきましょう。

ゲームの進行を管理するコードは大きく二つの部分から構成されます。

一つはプレイヤーからの入力を受け付ける部分、もう一つはゲームの状態を更新する部分です。

まず、プレイヤーからの入力を受け付けるコードを見てみましょう。

プレイヤーが石を置く場所を選択するためのコードを紹介します。

void playerInput(int* x, int* y) {
  printf("石を置く場所を選んでください(行 列):");
  scanf("%d %d", x, y);
}

このコードでは、printfとscanfを使ってプレイヤーからの入力を受け付けています。

scanfは入力された値を指定した変数に代入する関数です。

ここではポインタを引数に取り、そのポインタが指す変数に値を代入しています。

次に、ゲームの状態を更新するコードを見てみましょう。

石を置く処理と石をひっくり返す処理、そして勝者の判定を行うコードを紹介します。

void gameProgress(char board[8][8], char player) {
  int x, y;
  playerInput(&x, &y);
  placeStone(board, x, y, player);
  flipStone(board, x, y, player);
  if (isGameEnd(board)) {
    endGame(board);
  }
}

この関数では、まずプレイヤーからの入力を受け付け、その後石を置く処理と石をひっくり返す処理を行っています。

最後に、ゲームが終了しているかどうかをチェックし、終了している場合は勝者を判定しています。

●エラーハンドリングとその対処法

プログラムが期待通りに動かない場合、その原因を見つけて解決するためのプロセスをエラーハンドリングと呼びます。

エラーハンドリングの目的は、プログラムのバグを発見し、それを解決することであり、また予期しない問題が発生した場合でもプログラムが適切に対処できるようにすることです。

C言語ではエラーハンドリングを行うためのいくつかの方法がありますが、その一つはエラーコードを返す方法です。

例えば、関数が正常に動作した場合には0を、何らかのエラーが発生した場合にはそれ以外の値を返すようにします。

ここではその例を紹介します。

#include <stdio.h>

// 石を置く関数
int placeStone(int x, int y) {
    // ゲーム盤の範囲外かチェック
    if (x < 0 || x > 7 || y < 0 || y > 7) {
        return 1;  // 範囲外エラー
    }
    // すでに石が置かれているかチェック
    if (board[x][y] != 0) {
        return 2;  // 既に石が置かれているエラー
    }
    // ここに石を置く処理を書く
    return 0;  // 正常終了
}

int main() {
    int result = placeStone(10, 10);
    if (result != 0) {
        printf("エラーコード: %d\n", result);
    }
    return 0;
}

このコードでは、placeStone関数を使ってオセロの石を置く処理を行います。

この例では、石を置く座標がゲーム盤の範囲外であるか、既に石が置かれている場合にはエラーコードを返します。

このエラーコードを利用することで、何がエラーの原因であるかをプログラムが理解でき、適切なエラーメッセージを表示したり、適切な対処を行ったりできます。

上記のコードを実行すると、「エラーコード: 1」が出力されます。

これは、指定した座標がゲーム盤の範囲外であるためにエラーが発生したことを示しています。

別のエラーハンドリングの手法として、関数がエラーを起こす可能性があるときは、その関数を呼び出す前に必要なチェックを行う方法があります。

これをエラーチェックと呼びます。

#include <stdio.h>

// 石が置けるかどうかをチェックする関数
int canPlaceStone(int x, int y) {
    // ゲーム盤の範囲外かチェック
    if (x < 0 || x > 7 || y < 0 || y > 7) {
        return 0;  // 石を置けない
    }
    // すでに石が置かれているかチェック
    if (board[x][y] != 0) {
        return 0;  // 石を置けない
    }
    return 1;  // 石を置ける
}

// 石を置く関数
void placeStone(int x, int y) {
    // ここに石を置く処理を書く
}

int main() {
    int x = 10;
    int y = 10;
    if (canPlaceStone(x, y)) {
        placeStone(x, y);
    } else {
        printf("その座標には石を置けません。\n");
    }
    return 0;
}

このコードでは、石を置く前にcanPlaceStone関数を呼び出して石が置けるかどうかをチェックしています。

もし石が置けない場合には、エラーメッセージを表示してからプログラムを終了します。

この方法は、エラーが起きる前にそれを防ぐことができ、プログラムの安全性を高めることができます。

以上のように、エラーハンドリングはプログラムが予期しない動作をしたときに、それを適切に対処するために重要な役割を果たします。

C言語に限らず、どの言語を使用してもエラーハンドリングの基本的な考え方は同じであり、プログラミングスキルを高めるためには、エラーハンドリングの理解と実践が欠かせません。

●オセロゲームのカスタマイズ方法

オセロゲームを自分の好みに合わせてカスタマイズするための方法を紹介します。

例えば、ゲーム盤の大きさを変更したり、ゲームのルールを変えることができます。

しかし、その前にゲームの基本的な動作が理解できていることが重要です。

上記で説明したように、オセロゲームの基本的な動作は、石を置く、石をひっくり返す、勝者を判定するというプロセスで構成されています。

まず、ゲーム盤の大きさを変更するには、ゲーム盤を表現する2次元配列の大きさを変更します。

しかし、この変更に伴って、石を置く座標の範囲チェックや石をひっくり返す処理もそれに合わせて変更する必要があります。

#include <stdio.h>

#define BOARD_SIZE 10  // ゲーム盤の大きさ

int board[BOARD_SIZE][BOARD_SIZE];  // ゲーム盤

// 石が置けるかどうかをチェックする関数
int canPlaceStone(int x, int y) {
    // ゲーム盤の範囲外かチェック
    if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) {
        return 0;  // 石を置けない
    }
    // すでに石が置かれているかチェック
    if (board[x][y] != 0) {
        return 0;  // 石を置けない
    }
    return 1;  // 石を置ける
}

// 石を置く関数
void placeStone(int x, int y) {
    // ここに石を置く処理を書く
}

int main() {
    int x = 9;
    int y = 9;
    if (canPlaceStone(x, y)) {
        placeStone(x, y);
    } else {
        printf("その座標には石を置けません。\n");
    }
    return 0;
}

このコードでは、ゲーム盤の大きさを10×10に変更しています。

また、範囲チェックの部分では、座標が0からBOARD_SIZE-1の範囲内にあることを確認しています。

このように、ゲーム盤の大きさを変えるときには、それに伴う他の処理も合わせて変更する必要があります。

ゲームのルールを変えるカスタマイズも可能です。

例えば、一度に複数の石を置けるようにしたり、特定の条件下でのみ石をひっくり返せるようにするなど、自由な発想でオセロゲームをアレンジすることができます。

ただし、ルールを変えるときには、その変更が全ての処理に適用されていることを確認することが重要です。

まとめ

この記事では、C言語を使用してオセロゲームを作成する方法を5つのステップで解説しました。

C言語の基本からオセロのアルゴリズム、エラーハンドリング、カスタマイズ方法までを詳しく見てきました。

オセロゲームはシンプルなルールながら奥深い戦略を必要とするゲームで、プログラミングの学習にとても適しています。

特に、エラーハンドリングはプログラムが予期しない動作をしたときに、それを適切に対処するために重要なテクニックです。

また、自分の好みに合わせてオセロゲームをカスタマイズすることで、プログラミングの楽しさを実感できるでしょう。

これからもC言語の学習を続け、自分だけのオセロゲームを作ってみてください。

そして、その過程で新たな発見やアイデアが生まれることでしょう。

最後まで読んでいただき、ありがとうございました。