【C言語】五目並べゲームの作り方

C言語での五目並べの作り方の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、C言語での「五目並べ」の作り方について解説していきます。

開発する「五目並べ」

五目並べとは、囲碁の石と碁盤を使用し、2人のプレイヤーが交互に黒い石 or 白い石を1つずつ置いていくゲームとなります。

五目並べのルールの説明図1

そして、先に自身の色の石を5つ以上連続して並べることができると、そのプレイヤーの勝ちとなります。

五目並べのルールの説明図2

ただし、今回開発する五目並べはコンソール上での文字出力のみで実現する簡単なものであり、実際の五目並べとは次のような点が異なります。

まず、前述の通り、実際の五目並べでは黒い石と白い石が使用されるのですが、今回開発する五目並べでは、黒い石と白い石の代わりに ox の文字を利用して表示するようにしていきたいと思います。

さらに、実際の五目並べでは囲碁同様に碁盤上の縦線と横線の交点に石を置いていくことになるのですが、今回開発する五目並べでは縦線と横線で囲まれるマス内に石を置いていくようにしたいと思います。

プレイ人数に関しても、今回はプレイヤーは1人とし、対戦相手はコンピューターとしたいと思います。ひとまずプレイヤーが o を、コンピューターが x の石を置くものとしてプログラムを開発していきます。

具体的に、今回開発する五目並べの実行画面をお見せすると下記のようなものになります(スマホだと変な改行が入る可能性があるので注意してください)。

  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | | | | | | | |
2| | | | | | | | |
3| | | | | | | | |
4| | | | | | | | |
5| | | | | | | | |
6| | | | | | | | |
7| | | | | | | | |

どこに石を置きますか?
xの入力(0 - 7):2
yの入力(0 - 7):2
  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | | | | | | | |
2| | |o| | | | | |
3| | | | | | | | |
4| | | | | | | | |
5| | | | | | | | |
6| | | | | | | | |
7| | | | | | | | |

  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | |x| | | | | |
2| | |o| | | | | |
3| | | | | | | | |
4| | | | | | | | |
5| | | | | | | | |
6| | | | | | | | |
7| | | | | | | | |

どこに石を置きますか?
xの入力(0 - 7):

ご覧の通り、ちょっと実際の五目並べとは見た目が異なりますが、ゲームとしてはしっかり五目並べとして成立していますのでご安心ください。

五目並べゲームの作り方

では、前述で紹介したような「五目並べ」の作り方について解説していきます。

スポンサーリンク

五目並べはマルバツゲームを拡張すれば作れる

開発する「五目並べ」 で紹介したプログラムの実行画面を見て察した方もおられるかもしれませんが、実は五目並べはマルバツゲームと同じような作りで開発することができます。

また、マルバツゲームの開発は五目並べの開発に比べると難易度は低いため、まずマルバツゲームの作り方を理解して開発した上で、そのマルバツゲームを「五目並べに拡張していく」のが楽だと思います。

ですので、ここまで読んできてくださった方には少し申し訳ないのですが、五目並べを開発するために、まずは下記ページで紹介しているマルバツゲームの作り方のページを読んで事前にマルバツゲームを開発しておくことをオススメします。

C言語でのマルバツゲームの作り方解説ページアイキャッチ 【C言語】マルバツゲーム(三目並べ)の作り方

ここからは、上記ページで開発したマルバツゲームを拡張していくことを前提に、五目並べの作り方について解説していきます。

マルバツゲームを五目並べ用に拡張する

今回開発する五目並べはマルバツゲームに似ているものの、異なる点も多くあります。

文言の変更

まず、文言的な話になりますが、マルバツゲームでは「○× を記入する」ことでターンを進めていくのに対し(一手進めていくのに対し)、五目並べでは「石を置く」ことでターンを進めていくことになります。

前述でも紹介した下記ページのソースコードでは printf で “どこに印を記入しますか?” という文字列の出力を行なっているので、その文字列は “どこに石を置きますか?” に変更した方が五目並べらしいゲームに仕立てられます。

C言語でのマルバツゲームの作り方解説ページアイキャッチ 【C言語】マルバツゲーム(三目並べ)の作り方

同様に、関数名でも writeMark のように印を記入することを意味するものを利用しているため、putStone などの石を置くことを意味するものに変更してあげた方がソースコードも五目並べっぽくなります。

定数マクロの変更

また、マルバツゲームでは「1方向のマス数(横方向・縦方向)」と「並べたら勝ちになる(印の)数」が同じであるのに対し、五目並べでは「1方向のマス数(横方向・縦方向)」と「並べたら勝ちになる(石の)数」が一般的に異なります。

五目並べとマルバツゲームの違いの説明図

上記ページのソースコードでは定数マクロ NUM を「1方向のマス数」として、さらに定数マクロ TARGET を「並べたら勝ちになる数」として扱っており、TARGETNUM は同じ値の 3 に設定しています。

ですが、今回開発するのは五目並べですので、五目並べのルールに合わせて TARGET5 に設定してやる必要がありますし、NUM に関しては碁盤上の横線&縦線の数に合わせて設定する必要があります。

今回は NUM8 と設定しますが、この値は想定する碁盤に応じて変更しても良いです(ただし、ゲームとして成立させるためには NUM ≧ TARGET を満たす必要がありますので注意してください)。

これらの値を変更してやることで、見た目としては五目並べっぽくなると思います。が、実はまだ五目並べとしてのゲームが成立していません。

これは、TARGETNUM が異なる値になったために「勝負の勝敗を判断する関数」がうまく動作しなくなってしまうからです。上記ページで紹介している judgeResult 関数は、”TARGETNUM が同じ値であること” を前提とした作りになっています。

そのため、judgeResult 関数を五目並べように作り直す必要があります。これに関しては、次の 五目並べ用に勝敗の判断を行う関数を作る で解説をしたいと思います。

五目並べ用に拡張したソースコード

ひとまず、上記のページで紹介しているマルバツゲームのソースコードを五目並べ用に「出力する文字列や関数名の文言」&「TARGETNUM」を変更したソースコードを下記に紹介しておきます。

五目並べ用に拡張したソースコード
#include <stdio.h> // scanf/printf
#include <stdlib.h> // rand/srand
#include <time.h> // time

#define NUM 8 // マス数
#define TARGET 5 // 並べたら勝ちになる石の数

#define true 1
#define false 0
typedef int bool;

typedef enum {
    RESULT_WIN, // プレイヤーの勝ち
    RESULT_LOSE, // プレイヤーの負け
    RESULT_DRAW, // 引き分け
    RESULT_NONE // 結果が決まっていない
} RESULT;

typedef enum {
    TURN_PLAYER, // プレイヤーのターン
    TURN_COM, // コンピューターのターン
} TURN;

char board[NUM][NUM]; // マスに置かれた石を管理する配列

void init(void) {
    srand(time(NULL));

    for (int x = 0; x < NUM; x++) {
        for (int y = 0; y < NUM; y++) {
            board[x][y] = ' ';
        }
    }
}

void decidePlayerPosition(int *x, int *y) {
    printf("どこに石を置きますか?\n");
    printf("xの入力(0 - %d):", NUM - 1);
    scanf("%d", x);
    printf("yの入力(0 - %d):", NUM - 1);
    scanf("%d", y);
}

void decideComPosition(int *x, int *y) {
    *x = rand() % NUM;
    *y = rand() % NUM;
}

bool isPutable(int x, int y) {
    if (x < 0 || x > NUM - 1 || y < 0 || y > NUM - 1) {
        printf("入力した値が不正です...\n");
        return false;
    }

    if (board[x][y] != ' ') {
        printf("そのマスはすでに埋まってマス...\n");
        return false;
    }

    return true;
}

void decidePosition(int *x, int *y, TURN turn) {
    while (true) {
        if (turn == TURN_PLAYER) {
            decidePlayerPosition(x, y);
        } else {
            decideComPosition(x, y);
        }

        if (isPutable(*x, *y)) {
            break;
        }
    }
}

void putStone(int x, int y, TURN turn) {

    if (turn == TURN_PLAYER) {
        board[x][y] = 'o';
    } else {
        board[x][y] = 'x';
    }
}

void printBoard(void) {
    printf(" ");
    for (int x = 0; x < NUM; x++) {
        printf("%2d", x);
    }
    printf("\n");
    for (int y = 0; y < NUM; y++) {
        printf("%d", y);
        for (int x = 0; x < NUM; x++) {
            printf("|%c", board[x][y]);
        }
        printf("|\n");
    }
    printf("\n");
}

bool judgeFull(void) {
    for (int x = 0; x < NUM; x++) {
        for (int y = 0; y < NUM; y++) {
            if (board[x][y] == ' ') {
                return false;
            }
        }
    }
    
    return true;
}

TURN nextTurn(TURN now) {
    return now == TURN_PLAYER ? TURN_COM : TURN_PLAYER;
}

void printResult(RESULT result) {

    if (result == RESULT_WIN) {
        printf("あなたの勝ちです!!!\n");
    } else if (result == RESULT_LOSE) {
        printf("あなたの負けです!!!\n");
    } else if (result == RESULT_DRAW) {
        printf("引き分けです\n");
    }
}

int main(void) {

    int x, y;
    RESULT result = RESULT_NONE;
    TURN turn = TURN_PLAYER;

    init();
    printBoard();

    do {

        // 1.石を置く位置を決める(○×を記入する位置を決める)
        decidePosition(&x, &y, turn);

        // 2.石を置く(○×を記入する)
        putStone(x, y , turn);

        // 3.(NUMxNUMのマスを表示する)3x3のマスを表示する
        printBoard();

        // 4.勝負の結果を判断する
        result = judgeResult(turn);
        
        // 5.ターンを進める
        turn = nextTurn(turn);

    // 6.勝負の結果が決まっていない場合は1.に戻る
    } while (result == RESULT_NONE);

    // 7.勝負の結果を表示する
    printResult(result);
}

上記のソースコードでは judgeResult 関数の定義は載せていませんが、これは五目並べ用に作り直す必要があるためであり、五目並べ用に勝敗の判断を行う関数を作る で勝敗の判断の考え方を説明した後に judgeResult 関数の定義も紹介していきたいと思います。

先に言っておくと、judgeResult 関数の引数を変更するため、judgeResult 関数の呼び出し部分も後から変更する必要があります。

逆に言えば「judgeResult 関数の定義& judgeResult 関数呼び出し部分」以外については上記のソースコードで五目並べとして完成していると言えます。そのため、あとは judgeResult 関数の定義& judgeResult 関数呼び出し部分の変更を行えば、ソースコード全体として五目並べが完成することになります(必要に応じて judgeResult 関数から呼び出される関数の定義も追加する)。

このことからも、マルバツゲームから拡張することで割と簡単に五目並べを作れることを実感していただけると思います。

五目並べ用に勝敗の判断を行う関数を作る

ただし、マルバツゲームから五目並べに拡張するためには、五目並べ用の「勝敗の判断を行う関数」を作成する必要があります(五目並べに拡張できるように最初からマルバツゲームを作っても良いのですが…)。

勝敗の判断の仕方の違い

前述でも少し触れましたが、下記ページで紹介している勝敗の判断を行う関数 judgeResult は「1方向のマス数」と「並べたら勝ちになる数」が同じであることを前提にして作っています。

C言語でのマルバツゲームの作り方解説ページアイキャッチ 【C言語】マルバツゲーム(三目並べ)の作り方

上記の  judgeResult では、下の図の8方向に対して同じ印(○ or ×)がいくつ存在するかを確認し、同じ印が3つ存在する場合にプレイヤーの勝ち or プレイヤーの負けと判断するようになっています。

マルバツゲームにおける勝敗の結果の判断の仕方の説明図

ポイントは、ある方向に対して同じ印が3つ存在すれば良いだけであり、同じ印が「3つ連続して並んでいるか」については考慮していないという点になります。

マルバツゲームにおいては「1方向のマス数」と「並べたら勝ちになる数」が両方とも3であるため、ある方向に同じ印が3つ存在する場合、その方向のマスが全て同じ印で埋まっていることになるので、必ずその3つの印は連続して並んでいることになります。

五目並べとマルバツゲームの勝敗の判断の仕方の違いの説明図1

それに対し、五目並べの場合は、基本的には「1方向のマス数」は「並べたら勝ちになる数」よりも大きいです。したがって、ある方向に同じ色の石が「並べたら勝ちになる数」だけ存在するとしても、その方向のマスが全て同じ色の石で埋まっているとは限らず、それらの石が飛び飛びで「並べたら勝ちになる数」の分だけ並んでいるという可能性があります。

五目並べとマルバツゲームの勝敗の判断の仕方の違いの説明図2

つまり、五目並べでは同じ色の石が連続して5個以上並んでいる場合のみ勝敗が決定することになるため、ある方向に対して同じ色の石が5個以上存在するだけでは勝敗が決定しているとは限りません。

そのため、五目並べの「勝敗の判断を行う関数」においては、マルバツゲームのように各方向に対して同じ色の石が5個以上存在するかどうかを調べるのではなく、同じ色の石が “連続して5個以上並んでいるかどうか” を調べる必要があります。

五目並べにおける勝敗の判断の仕方

では、具体的に五目並べの「勝敗の判断を行う関数」はどうやって作れば良いでしょうか?

この点について解説していきます。

まず前提として、五目並べにおいて勝敗が決定するタイミングは、基本的には「石が置かれたタイミング」となります(途中で勝敗が自明になる場合もありますが、その場合の考慮は今回は行いません)。そして、勝敗が決定するのは、その置かれた石と同じ色の石が、置かれた石を中心として下図の各色の矢印で示した方向のいずれかで5つ以上連続して並んだ時となります。

石が置かれた際に同じ色の石が5つ以上並ぶ可能性のある方向を示す図

これは、直前に置かれた石を中心としない方向や位置でいきなり同じ色の石が5つ以上並ぶことはあり得ないということを意味しています。

つまり、五目並べで勝敗が決定したかどうかを判断する際には、置かれた石を中心とした上図の4つの方向、すなわち、「左・右方向」「上・下方向」「左上・右下方向」「左下・右上方向」のそれぞれに対して、置かれた石と同じ色の石が “置かれた石も含めて5つ以上” 並んだかどうかを判断すれば良いということになります。

また、マルバツゲームと同様に、碁盤上の各マスに置かれた石は2次元配列 board で管理を行なっており、board[x][y] において、x は横方向のマスの位置、y は縦方向のマスの位置を示す添え字となっています。x の正方向は右方向、y の正方向は下方向となります(特に y は数学等で用いる座標とは正方向が逆になっているので注意してください)。

  x方向とy方向の説明図

より具体的には、位置 (x, y) に置かれた石に応じて board[x][y] には下記の文字が格納されていることになります。

  • 黒い石:'o'
  • 白い石:'x'
  • まだ石が置かれていない:' '

ですので、石が置かれた位置を (x, y) とすれば、(x, y) を中心とする「方向」に同じ色の石がいくつ連続して並んでいるかどうかは、i-1 から 1 つずつ減少させるループの中で board[x][y] == board[x + i][y] を満たす個数をカウントすることで調べることができます(board[x][y] == board[x + i][y] を満たさなくなった際にはループを終了させます)。

左方向に対して直前に置かれた石と同じ色の石が連続して並んでいる個数をカウントする様子

同様に、i1 から 1 つずつ増加させるループの中で board[x][y] == board[x + i][y] を満たす個数をカウントすることで、(x, y) を中心とする「方向」に置いた石と同じ色の石がいくつ連続して並んでるかを調べることができます。

右方向に対して直前に置かれた石と同じ色の石が連続して並んでいる個数をカウントする様子

さらに、「方向」でのカウント数と「方向」でのカウント数に +1 した値が、直前に置かれた石を中心として「左・右方向」に同じ色の石が連続して並んでいる個数となります。この個数が 5 以上の場合は勝敗が決定したこととなります。

+1 は直前に置かれた石をカウントするための加算となります。

"+1"の意味合いを示す図

実際に上記で説明した処理を実装すれば次のようになります。

左・右方向に対する勝敗の判断
// 左方向
count1 = 0;
i = -1;
while (isValidPos(x + i, y) && board[x + i][y] == board[x][y]) {
    count1++;
    i--;
}

// 右方向
count2 = 0;
i = 1;
while (isValidPos(x + i, y) && board[x + i][y] == board[x][y]) {
    count2++;
    i++;
}

if (count1 + count2 + 1 >= TARGET) {
    return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
}

一応ここまでの説明に合わせて実装しているのですが、x 方向の位置の増減を行うことで碁盤の範囲外のマスの位置が指定される可能性があるため、指定された位置が有効なマスの位置であるかどうかを判断する isValidPos 関数を利用するようにしています。この関数の定義は後述で紹介します。

また、count1 + count2 + 1 >= TARGET が成立した際には、直前に石を置いた相手に応じて結果を返却するようにしています。具体的には、turnTURN_PLAYER(直前に石を置いたのがプレイヤーであることを示す値)である場合は RESULT_WIN、それ以外は RESULT_LOSE を返却するようにしています。

この辺りの定義値などは下記ページのマルバツゲームでも利用しているため、詳細は下記ページをご参照ください。

C言語でのマルバツゲームの作り方解説ページアイキャッチ 【C言語】マルバツゲーム(三目並べ)の作り方

また、五目並べの場合は5つを超える石が同時に並ぶ可能性もあるため、count1 + count2 + 1 == TARGET== を用いて比較を行うと上手く勝敗が判断できません。この点にはご注意ください。

このように、x 方向の位置を増減させていくことで、「左・右方向」に対して連続して並んでいる同じ色の石の個数を調べることができます。

他の方向も同様で、「上・下方向」に関しては y 方向の位置を増減させていくことで調べることができ、

上・下方向に対して直前に置かれた石と同じ色の石が連続して並んでいる個数をカウントする様子

左上・右下方向」に関しては x 方向と y 方向の両方の位置を同じ値だけ増減させていくことで調べることができます。

左上・右下方向に対して直前に置かれた石と同じ色の石が連続して並んでいる個数をカウントする様子

さらに「左下・右上方向」に関しては x 方向と y 方向の両方の位置をそれぞれで “符号を逆にした値” だけ増減させていくことで調べることができます。

左下・右上方向に対して直前に置かれた石と同じ色の石が連続して並んでいる個数をカウントする様子

このように、board に対する添字を上手く増減させながら個数をカウントしていけば、五目並べとしての「勝敗を判断する関数」を実現することができます。

引き分け用の判断も必要

また、マルバツゲーム同様に、五目並べにおいても引き分けかどうかの判断が必要になります。

この引き分けの判断に関してはマルバツゲームと処理が全く同じなので、説明は省略させていただきます(勝敗が決まっていない状態で全てのマスが石で埋まった場合に引き分けとする)。

五目並べにおける「勝敗を判断する関数」

ここまで説明してきた考え方に基づいて実装した場合、勝敗を判断する関数 judgeResult 関数は下記のようになります。

五目並べ用のjudgeResult関数
RESULT judgeResult(int x, int y, TURN turn) {

    int count1, count2;
    int i, j, k;

    // 左方向
    count1 = 0;
    i = -1;
    while (isValidPos(x + i, y) && board[x + i][y] == board[x][y]) {
        count1++;
        i--;
    }

    // 右方向
    count2 = 0;
    i = 1;
    while (isValidPos(x + i, y) && board[x + i][y] == board[x][y]) {
        count2++;
        i++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }

    // 下方向
    count1 = 0;
    j = -1;
    while (isValidPos(x, y + j) && board[x][y + j] == board[x][y]) {
        count1++;
        j--;
    }

    // 上方向
    count2 = 0;
    j = 1;
    while (isValidPos(x, y + j) && board[x][y + j] == board[x][y]) {
        count2++;
        j++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }

    // 左上方向
    count1 = 0;
    k = -1;
    while (isValidPos(x + k, y + k) && board[x + k][y + k] == board[x][y]) {
        count1++;
        k--;
    }

    // 右下方向
    count2 = 0;
    k = 1;
    while (isValidPos(x + k, y + k) && board[x + k][y + k] == board[x][y]) {
        count2++;
        k++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }

    // 左下方向
    count1 = 0;
    k = -1;
    while (isValidPos(x + k, y - k) && board[x + k][y - k] == board[x][y]) {
        count1++;
        k--;
    }

    // 右上方向
    count2 = 0;
    k = 1;
    while (isValidPos(x + k, y - k) && board[x + k][y - k] == board[x][y]) {
        count2++;
        k++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }
    

    // マスが全て埋まったかどうかを確認
    if (judgeFull()) {
        return RESULT_DRAW;
    }

    // まだ勝敗が決定していない
    return RESULT_NONE;
}

引数としては、直前に石が置かれた位置を示す xy を、さらに直前に石を置いた相手を判別するための turn を受け取るようにしています。

五目並べ用に拡張したソースコード で紹介したソースコードでは judgeResult 関数呼び出し時には引数 turn しか指定していないため、上記の judgeResult 関数を使用するためには turn だけでなく xy も引数に指定するようにする必要があります。

また、上記の judgeResult 関数から呼び出しを行なっている isValidPos 関数は引数で指定された位置が有効なマスの位置かどうかを判断する関数であり、この関数の定義例は下記のようになります。

isValidPos
bool isValidPos(int x, int y) {
    if (x < 0 && x >= NUM) {
        return false;
    }

    if (y < 0 && y >= NUM) {
        return false;
    }

    return true;
}

あとは、五目並べ用に拡張したソースコード で紹介したソースコードに上記の isValidPos 関数と judgeResult 関数の定義を追加し、さらに judgeResult 関数の呼び出し時の引数に xy を追加すれば、五目並べのプログラムが完成することになります。

スポンサーリンク

五目並べのサンプルプログラム

最後に、五目並べのサンプルプログラムの全ソースコードをまとめて紹介しておきます。

ソースコード

五目並べのサンプルプログラムのソースコードは下記のようになります。

五目並べ
#include <stdio.h> // scanf/printf
#include <stdlib.h> // rand/srand
#include <time.h> // time

#define NUM 8 // マス数
#define TARGET 5 // 並べたら勝ちになる石の数

#define true 1
#define false 0
typedef int bool;

typedef enum {
    RESULT_WIN, // プレイヤーの勝ち
    RESULT_LOSE, // プレイヤーの負け
    RESULT_DRAW, // 引き分け
    RESULT_NONE // 結果が決まっていない
} RESULT;

typedef enum {
    TURN_PLAYER, // プレイヤーのターン
    TURN_COM, // コンピューターのターン
} TURN;

char board[NUM][NUM]; // マスに置かれた石を管理する配列

void init(void) {
    srand(time(NULL));

    for (int x = 0; x < NUM; x++) {
        for (int y = 0; y < NUM; y++) {
            board[x][y] = ' ';
        }
    }
}

void decidePlayerPosition(int *x, int *y) {
    printf("どこに石を置きますか?\n");
    printf("xの入力(0 - %d):", NUM - 1);
    scanf("%d", x);
    printf("yの入力(0 - %d):", NUM - 1);
    scanf("%d", y);
}

void decideComPosition(int *x, int *y) {
    *x = rand() % NUM;
    *y = rand() % NUM;
}

bool isPutable(int x, int y) {
    if (x < 0 || x > NUM - 1 || y < 0 || y > NUM - 1) {
        printf("入力した値が不正です...\n");
        return false;
    }

    if (board[x][y] != ' ') {
        printf("そのマスはすでに埋まってマス...\n");
        return false;
    }

    return true;
}

void decidePosition(int *x, int *y, TURN turn) {
    while (true) {
        if (turn == TURN_PLAYER) {
            decidePlayerPosition(x, y);
        } else {
            decideComPosition(x, y);
        }

        if (isPutable(*x, *y)) {
            break;
        }
    }
}

void putStone(int x, int y, TURN turn) {

    if (turn == TURN_PLAYER) {
        board[x][y] = 'o';
    } else {
        board[x][y] = 'x';
    }
}

void printBoard(void) {
    printf(" ");
    for (int x = 0; x < NUM; x++) {
        printf("%2d", x);
    }
    printf("\n");
    for (int y = 0; y < NUM; y++) {
        printf("%d", y);
        for (int x = 0; x < NUM; x++) {
            printf("|%c", board[x][y]);
        }
        printf("|\n");
    }
    printf("\n");
}

bool judgeFull(void) {
    for (int x = 0; x < NUM; x++) {
        for (int y = 0; y < NUM; y++) {
            if (board[x][y] == ' ') {
                return false;
            }
        }
    }
    
    return true;
}

bool isValidPos(int x, int y) {
    if (x < 0 && x >= NUM) {
        return false;
    }

    if (y < 0 && y >= NUM) {
        return false;
    }

    return true;
}

RESULT judgeResult(int x, int y, TURN turn) {

    int count1, count2;
    int i, j, k;

    // 左方向
    count1 = 0;
    i = -1;
    while (isValidPos(x + i, y) && board[x + i][y] == board[x][y]) {
        count1++;
        i--;
    }

    // 右方向
    count2 = 0;
    i = 1;
    while (isValidPos(x + i, y) && board[x + i][y] == board[x][y]) {
        count2++;
        i++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }

    // 下方向
    count1 = 0;
    j = -1;
    while (isValidPos(x, y + j) && board[x][y + j] == board[x][y]) {
        count1++;
        j--;
    }

    // 上方向
    count2 = 0;
    j = 1;
    while (isValidPos(x, y + j) && board[x][y + j] == board[x][y]) {
        count2++;
        j++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }

    // 左上方向
    count1 = 0;
    k = -1;
    while (isValidPos(x + k, y + k) && board[x + k][y + k] == board[x][y]) {
        count1++;
        k--;
    }

    // 右下方向
    count2 = 0;
    k = 1;
    while (isValidPos(x + k, y + k) && board[x + k][y + k] == board[x][y]) {
        count2++;
        k++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }

    // 左下方向
    count1 = 0;
    k = -1;
    while (isValidPos(x + k, y - k) && board[x + k][y - k] == board[x][y]) {
        count1++;
        k--;
    }

    // 右上方向
    count2 = 0;
    k = 1;
    while (isValidPos(x + k, y - k) && board[x + k][y - k] == board[x][y]) {
        count2++;
        k++;
    }

    if (count1 + count2 + 1 >= TARGET) {
        return turn == TURN_PLAYER ? RESULT_WIN : RESULT_LOSE;
    }
    

    // マスが全て埋まったかどうかを確認
    if (judgeFull()) {
        return RESULT_DRAW;
    }

    // まだ勝敗が決定していない
    return RESULT_NONE;
}

TURN nextTurn(TURN now) {
    return now == TURN_PLAYER ? TURN_COM : TURN_PLAYER;
}

void printResult(RESULT result) {

    if (result == RESULT_WIN) {
        printf("あなたの勝ちです!!!\n");
    } else if (result == RESULT_LOSE) {
        printf("あなたの負けです!!!\n");
    } else if (result == RESULT_DRAW) {
        printf("引き分けです\n");
    }
}

int main(void) {

    int x, y;
    RESULT result = RESULT_NONE;
    TURN turn = TURN_PLAYER;

    init();
    printBoard();

    do {

        // 1.石を置く位置を決める(○×を記入する位置を決める)
        decidePosition(&x, &y, turn);

        // 2.石を置く(○×を記入する)
        putStone(x, y , turn);

        // 3.(NUMxNUMのマスを表示する)3x3のマスを表示する
        printBoard();

        // 4.勝負の結果を判断する
        result = judgeResult(x, y, turn);
        
        // 5.ターンを進める
        turn = nextTurn(turn);

    // 6.勝負の結果が決まっていない場合は1.に戻る
    } while (result == RESULT_NONE);

    // 7.勝負の結果を表示する
    printResult(result);
}

動作確認

プログラムの動作確認も行っておきましょう!

上記のソースコードをコンパイルして実行すれば、下記のように碁盤に見立てた文字が表示されるはずです。

  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | | | | | | | |
2| | | | | | | | |
3| | | | | | | | |
4| | | | | | | | |
5| | | | | | | | |
6| | | | | | | | |
7| | | | | | | | |

どこに石を置きますか?
xの入力(0 - 7):

また、上記のように石を置く位置を聞かれるので、x 方向の位置(横方向の位置)と y 方向の位置(縦方向の位置)を指定すれば、その位置に石が置かれたことになり、碁盤上に o が表示されるようになります。

さらに、プレイヤーが石を置いた後にはコンピューターのターンとなり、コンピューターによって自動的に石が置かれてその位置に x が表示されるようになります。そして、再びプレイヤーのターンとなって位置の入力受付が行われます。

どこに石を置きますか?
xの入力(0 - 7):3
yの入力(0 - 7):4
  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | | | | | | | |
2| | | | | | | | |
3| | | | | | | | |
4| | | |o| | | | |
5| | | | | | | | |
6| | | | | | | | |
7| | | | | | | | |

  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | | | | | | | |
2| | | | | | | | |
3| | | | | | | | |
4| | |x|o| | | | |
5| | | | | | | | |
6| | | | | | | | |
7| | | | | | | | |

どこに石を置きますか?
xの入力(0 - 7):

これらの操作を繰り返し、o or x のどちらかが連続して5つ並べられる or マスが全て o or x で埋まると結果が表示されてゲームが終了することになります。

どこに石を置きますか?
xの入力(0 - 7):5
yの入力(0 - 7):5
  0 1 2 3 4 5 6 7
0| | | | | | | | |
1| | | | | | | |x|
2| | |o| | | | | |
3| | | |o| | |x| |
4| | |x|o|o| | | |
5| | | | |x|o| |x|
6| | | | | | |o| |
7| | |x| | | | |o|

あなたの勝ちです!!!

このような動作から、マス内に石を置くように表示されているなど見た目的にはイマイチなのですが、五目並べのゲームとしては成立していることを確認していただければと思います。

ゲームとしては成立しているため、上記ソースコードを変更して更に見た目も五目並べに似せるようなことも可能だと思いますので、興味があれば見た目にも拘ってプログラミングしてみてください!

ちなみにですが、ソースコードの NUMTARGET の定義値をそれぞれ 3 に設定すればマルバツゲームとして動作することになります。

スポンサーリンク

まとめ

このページでは、C言語での「五目並べ」の作り方について解説しました!

ちょっと見た目は悪いですが、マルバツゲームを拡張することで五目並べのプログラムは簡単に作ることができます。ただし、勝敗の結果の判断方法はマルバツゲームの時から変更必要な可能性もあるので注意してください。

特に勝敗の結果の判断時には2次元配列の添字を巧みに指定しながら実装していく必要があるため、特に2次元配列に慣れていない方には難易度が高く感じられるかもしれません。ですが、この辺りも2次元配列を積極的に使っていけばすぐに慣れることは出来ると思います。

今回紹介した五目並べゲームのような「碁盤上で遊ぶゲーム」や「ボード上で遊ぶゲーム」を開発する際には、五目並べ同様に碁盤上・ボード上の状態を2次元配列で管理することになり、こういったゲームを開発してみると2次元配列にも慣れることができるのではないかと思います。

オセロに関しては下記ページで紹介していますので、是非こちらも参考にしてみてください。

オセロゲームのC言語ソースコード紹介ページのアイキャッチ C言語でオセロゲームを作成

また、今回紹介したソースコードでは五目並べのゲームとしては成立していると思いますが、コンピューターがランダムな位置に石を置くだけになっているのでめちゃめちゃ弱いです。

decideComPosition 関数でコンピューターが石を置く位置を決めていますので、コンピューターの石の置き方を工夫してもっと強いコンピューターを実現してみるのも面白いと思います!

同じカテゴリのページ一覧を表示

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です