motu*2

DIV1目指して問題を解き続ける

C言語で作る五目並べ

初めに

本稿はSLP KBIT Advent Calendar 2014 の12日目の記事です。

前書き

最近競技プログラミングしかやっていなかったので、たまにはGMVらしく
ゲームを作りたいと思います。
今回は比較的簡単に作れそうな五目並べC言語で作ってみたいと思います。
配列と関数の知識があれば作れるので、プログラミング初学者におすすめです!

ルール確認

まず、ゲームのルール・仕様を決めていきましょう。
ここを曖昧にしたままコーディングを始めると、後々修正が大変になります。

  • 盤面の大きさは10×10
  • 縦横斜めのいずれかに5つ自分の石を揃えれば勝ち
  • 6連以上も勝利とする
  • 三三などの禁止手はなし
  • 先手は黒の石

今回はシンプルにこんな感じにしましょう。

フロー

次に、ゲームの流れを確認しましょう。

  • ゲーム情報初期化
  • ゲーム開始
  • 順番に空マスに石を置いていく
  • 五連がそろえば結果を出力して終了

コーディング

仕様と大まかな流れを確認したので、実際にプログラムを書いていきましょう!

まず盤面の大きさと駒の色を定数として定義しておきましょう。

#define BOARD_SIZE 10      // 盤面サイズ 10 * 10
#define STONE_SPACE 0      // 盤面にある石 なし
#define STONE_BLACK 1      // 盤面にある石 黒
#define STONE_WHITE 2      // 盤面にある石 白

コード内で1や2などのマジックナンバーを書いてしまうと、
プログラムが読みずらくなってしまいます。
盤面サイズを定数として定義しておけば、この値を変えるだけで
盤面の大きさを自由に変えることができます。

盤面の石情報を管理する配列と、現在どちらのターンなのかを
格納する配列をmain文で宣言します。

int main()
{
    int board[BOARD_SIZE][BOARD_SIZE];
    int which_turn;
    return 0;
}

ここからいろいろ関数を宣言していきます。

盤面初期化

引数で渡した盤面の配列を、二重ループで回して
全て「STONE_SPACE」にしてやります。

void boardInit(int board[][BOARD_SIZE])
{
    int i, j;
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            board[i][j] = STONE_SPACE;
        }
    }
}

ゲーム初期化

盤面を初期化する関数の呼び出しと、which_turnを初期化してやります。
先手は黒なので、「STONE_BLACK」を格納します。

void gameInit(int board[][BOARD_SIZE], int *which_turn)
{
    boardInit(board);
    *which_turn = STONE_BLACK;
}

盤面出力

二重ループを回し、board[i][j]に対応する駒を出力します。
分かりやすいように、左と上に番号を出力しておきます。

void boardPrint(int board[][BOARD_SIZE])
{
    int i, j;
    printf("  ");
    for (i = 0; i < BOARD_SIZE; i++) {
        printf("%d ", i);
    }
    puts("");
    for (i = 0; i < BOARD_SIZE; i++) {
        printf("%d ", i);
        for (j = 0; j < BOARD_SIZE; j++) {
            switch (board[i][j]) {
            case STONE_SPACE: printf("・"); break;
            case STONE_BLACK: printf("●"); break;
            case STONE_WHITE: printf("○"); break;
            }
        }
        puts("");
    }
    puts("");
}

入力処理

正しい入力がくるまでループを回して入力を促します。
正しくない入力というのは、盤面の範囲外か
すでに石が置いてあるかです。
正しい入力がきたらループを抜け、その場所に石を置きます。

void inputPutPos(int board[][BOARD_SIZE], int which)
{
    int pos_x, pos_y;
    printf("%s", (which == 1) ? "●" : "○");
    printf("の番です。どこに置きますか\n> ");
    while (1) {
        scanf("%d %d", &pos_x, &pos_y);
        if (checkOutPos(pos_x, pos_y) && board[pos_y][pos_x] == STONE_SPACE) { break; }
        printf("不正な入力です\n> ");
    }
    board[pos_y][pos_x] = which;
}

手番交代処理

黒なら白に、白なら黒に変えます。

void changeTurn(int *which_turn)
{
    *which_turn = (*which_turn == STONE_BLACK) ? STONE_WHITE : STONE_BLACK;
}

範囲外チェック

引数で座標を渡し、範囲内なら1を、範囲外なら0を返します。

int checkOutPos(int x, int y)
{
    return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE);
}

5連確認

引数で渡した座標から、下、右、斜め右下の順で盤面を見ていきます。
指定した座標の石が5連続いていれば1を返します。

int lenCheck(int board[][BOARD_SIZE], int x, int y)
{
    int i, j, len_flag;
    int dx[] = { 0, 1, 1 };
    int dy[] = { 1, 0, 1 };
    for (i = 0; i < 3; i++) {
        for (j = 1, len_flag = 1; j <= 4; j++) {
            if (board[y][x] != board[y+j*dy[i]][x+j*dx[i]]) {
                len_flag = 0;
                break;
            }
        }
        if (len_flag == 1) { return 1; }
    }
    return 0;
}

ゲーム終了処理

盤面を見て、5連があるか確認します。
あれば、結果を出力し、1を返します。

int gameEndProcess(int board[][BOARD_SIZE])
{
    int i, j, len_flag;
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            if (board[i][j] == STONE_SPACE) { continue; }
            if (lenCheck(board, j, i)) {
                printf("%sの勝ちです。\n", (board[i][j] == STONE_BLACK) ? "●" : "○");
                return 1;
            }
        }
    }
    return 0;
}

main文

必要な関数の定義がし終わったので、プロトタイプ宣言をして、
main文で使っていきます。

int main()
{
    // 変数宣言
    int board[BOARD_SIZE][BOARD_SIZE];
    int which_turn;
    
    // 初期処理
    gameInit(board, &which_turn);
    boardPrint(board);
    
    //---- メインループ
    while (1) {
        //--- 入力処理
        inputPutPos(board, which_turn);

        //--- 演算処理
        changeTurn(&which_turn);
        
        //--- 出力処理
        boardPrint(board);
        
        //--- 終了判定
        if (gameEndProcess(board)) { break; }
    }
    return 0;
}

コード

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define BOARD_SIZE 10      // 盤面サイズ 10 * 10
#define STONE_SPACE 0      // 盤面にある石 なし
#define STONE_BLACK 1      // 盤面にある石 黒
#define STONE_WHITE 2      // 盤面にある石 白

void inputPutPos(int board[][BOARD_SIZE], int which);
void changeTurn(int *which_turn);
int checkOutPos(int x, int y);
void gameInit(int board[][BOARD_SIZE], int *which_turn);
void boardInit(int board[][BOARD_SIZE]);
void boardPrint(int board[][BOARD_SIZE]);
int gameEndProcess(int board[][BOARD_SIZE]);
int lenCheck(int board[][BOARD_SIZE], int x, int y);

//=======================================================
// main
//=======================================================
int main()
{
    // 変数宣言
    int board[BOARD_SIZE][BOARD_SIZE];
    int which_turn;
    
    // 初期処理
    gameInit(board, &which_turn);
    boardPrint(board);
    
    //---- メインループ
    while (1) {
        //--- 入力処理
        inputPutPos(board, which_turn);

        //--- 演算処理
        changeTurn(&which_turn);
        
        //--- 出力処理
        boardPrint(board);
        
        //--- 終了判定
        if (gameEndProcess(board)) { break; }
    }
    return 0;
}

//-------------------------------------------------
// 置く場所入力
//-------------------------------------------------
void inputPutPos(int board[][BOARD_SIZE], int which)
{
    int pos_x, pos_y;
    
    printf("%s", (which == 1) ? "●" : "○");
    printf("の番です。どこに置きますか\n> ");
    while (1) {
        scanf("%d %d", &pos_x, &pos_y);
        if (checkOutPos(pos_x, pos_y) && board[pos_y][pos_x] == STONE_SPACE) { break; }
        printf("不正な入力です\n> ");
    }
    board[pos_y][pos_x] = which;
}

//-------------------------------------------------
// 手番交代処理
//-------------------------------------------------
void changeTurn(int *which_turn)
{
    *which_turn = (*which_turn == STONE_BLACK) ? STONE_WHITE : STONE_BLACK;
}

//-------------------------------------------------
// 範囲外チェック
//-------------------------------------------------
int checkOutPos(int x, int y)
{
    return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE);
}

//-------------------------------------------------
// ゲーム情報初期化
//-------------------------------------------------
void gameInit(int board[][BOARD_SIZE], int *which_turn)
{
    boardInit(board);
    *which_turn = STONE_BLACK;
}

//-------------------------------------------------
// 盤面初期化
//-------------------------------------------------
void boardInit(int board[][BOARD_SIZE])
{
    int i, j;
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            board[i][j] = STONE_SPACE;
        }
    }
}

//-------------------------------------------------
// 盤面出力
//-------------------------------------------------
void boardPrint(int board[][BOARD_SIZE])
{
    int i, j;
    
    printf("  ");
    for (i = 0; i < BOARD_SIZE; i++) {
        printf("%d ", i);
    }
    puts("");
    for (i = 0; i < BOARD_SIZE; i++) {
        printf("%d ", i);
        for (j = 0; j < BOARD_SIZE; j++) {
            switch (board[i][j]) {
            case STONE_SPACE: printf("・"); break;
            case STONE_BLACK: printf("●"); break;
            case STONE_WHITE: printf("○"); break;
            }
        }
        puts("");
    }
    puts("");
}

//-------------------------------------------------
// ゲーム終了処理
//-------------------------------------------------
int gameEndProcess(int board[][BOARD_SIZE])
{
    int i, j, len_flag;
    
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            if (board[i][j] == STONE_SPACE) { continue; }
            if (lenCheck(board, j, i)) {
                printf("%sの勝ちです。\n", (board[i][j] == STONE_BLACK) ? "●" : "○");
                return 1;
            }
        }
    }
    return 0;
}

//-------------------------------------------------
// 5連確認
//-------------------------------------------------
int lenCheck(int board[][BOARD_SIZE], int x, int y)
{
    int i, j, len_flag;
    int dx[] = { 0, 1, 1 };
    int dy[] = { 1, 0, 1 };
    
    for (i = 0; i < 3; i++) {
        for (j = 1, len_flag = 1; j <= 4; j++) {
            if (board[y][x] != board[y+j*dy[i]][x+j*dx[i]]) {
                len_flag = 0;
                break;
            }
        }
        if (len_flag == 1) { return 1; }
    }
    return 0;
}

実行

f:id:mo2dx:20141210210733j:plain


完成!

これでコマンドプロンプト版の五目並べは完成です!
やったね!
でも、コマンドプロンプトだと操作しずらいし、
文字だけだと寂しいですよね・・・

じゃあ、GUIに移植してみよう!


DXライブラリ

GUIのゲームを作るとなるとライブラリが必要になります。
有名なものに、DirectXOpenGLなどがあります。
今回は使いやすいDXライブラリを使ってみましょう!

ダウンロード
DXライブラリ置き場 ダウンロードページ

設定
DXライブラリ置き場 使い方説明

↑でがんばって設定します。


プロジェクトの準備が終わったらコードを書いていきます。
内部処理はだいたい一緒なので、入力、出力の部分を変更します。
画像は自分で用意します。

コード

#include "DxLib.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define BOARD_SIZE 10      // 盤面サイズ 10 * 10
#define STONE_SPACE 0      // 盤面にある石 なし
#define STONE_BLACK 1      // 盤面にある石 黒
#define STONE_WHITE 2      // 盤面にある石 白

int processLoop();
void loadImage();
int updateMouse();
int inputPutPos(int board[][BOARD_SIZE], int which);
void changeTurn(int *which_turn);
int checkOutPos(int x, int y);
void gameInit(int board[][BOARD_SIZE], int *which_turn);
void boardInit(int board[][BOARD_SIZE]);
void boardPrint(int board[][BOARD_SIZE]);
int gameEndProcess(int board[][BOARD_SIZE]);
int lenCheck(int board[][BOARD_SIZE], int x, int y);

// 画像ハンドル
int image_stone_w;
int image_stone_b;
int image_board;

// クリック状態
int click;

//=======================================================
// main
//=======================================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    ChangeWindowMode(TRUE);      // ウィンドウモードで起動する
    SetGraphMode(540, 540, 16);  // ウィンドウサイズ(540×540) 使用カラー16bit(65536色)
    SetWindowText("五目並べ");   // タイトル名
    if (DxLib_Init() == -1) { return -1; }   // DXライブラリ初期化
    if (SetDrawScreen(DX_SCREEN_BACK) != 0) { return -1; }  // 描画先を裏画面に

    int board[BOARD_SIZE][BOARD_SIZE];
    int which_turn;

    // デバッグ用コマンドプロンプト
    /*
    AllocConsole();
    FILE* fp;
    freopen_s(&fp, "CONOUT$", "w", stdout);
    freopen_s(&fp, "CONIN$", "r", stdin);
    */

    // 初期処理
    gameInit(board, &which_turn);
    loadImage();

    //---- メインループ
    while (processLoop() == 0) {
        //--- 入力処理
        if (inputPutPos(board, which_turn)) {
            changeTurn(&which_turn);
        }

        //--- 出力処理
        boardPrint(board);
        ScreenFlip();

        //--- 終了判定
        if (gameEndProcess(board)) { break; }
    }
    
    DxLib_End();
    return 0;
}

//-------------------------------------------------
// ループ処理
//-------------------------------------------------
int processLoop()
{
    if (ProcessMessage() != 0) { return -1; }
    if (ClearDrawScreen() != 0) { return -1; }
    if (updateMouse() != 0) { return -1; }
    return 0;
}

//-------------------------------------------------
// マウス入力状態更新
//-------------------------------------------------
int updateMouse()
{
    if (GetMouseInput() & MOUSE_INPUT_LEFT) {
        click++;
    }
    else {
        click = 0;
    }
    return 0;
}

//-------------------------------------------------
// 画像読み込み処理
//-------------------------------------------------
void loadImage()
{
    image_stone_w = LoadGraph("white_stone.png");
    image_stone_b = LoadGraph("black_stone.png");
    image_board = LoadGraph("board.png");
}

//-------------------------------------------------
// 置く場所入力
//-------------------------------------------------
int inputPutPos(int board[][BOARD_SIZE], int which)
{
    int pos_x, pos_y;

    if (click == 1) {
        GetMousePoint(&pos_x, &pos_y);
        pos_x = (pos_x - 25) / 50;
        pos_y = (pos_y - 25) / 50;
        if (checkOutPos(pos_x, pos_y) && board[pos_y][pos_x] == 0) {
            board[pos_y][pos_x] = which;
            return 1;
        }
    }
    return 0;
}

//-------------------------------------------------
// 手番交代処理
//-------------------------------------------------
void changeTurn(int *which_turn)
{
    *which_turn = (*which_turn == STONE_BLACK) ? STONE_WHITE : STONE_BLACK;
}

//-------------------------------------------------
// 範囲外チェック
//-------------------------------------------------
int checkOutPos(int x, int y)
{
    return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE);
}

//-------------------------------------------------
// ゲーム情報初期化
//-------------------------------------------------
void gameInit(int board[][BOARD_SIZE], int *which_turn)
{
    boardInit(board);
    *which_turn = STONE_BLACK;
}

//-------------------------------------------------
// 盤面初期化
//-------------------------------------------------
void boardInit(int board[][BOARD_SIZE])
{
    int i, j;
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            board[i][j] = STONE_SPACE;
        }
    }
}

//-------------------------------------------------
// 盤面出力
//-------------------------------------------------
void boardPrint(int board[][BOARD_SIZE])
{
    int i, j;
    DrawGraph(0, 0, image_board, TRUE);
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            if (board[i][j] == STONE_BLACK) {
                DrawGraph(j * 49, i * 49, image_stone_b, TRUE);
            }
            else if (board[i][j] == STONE_WHITE) {
                DrawGraph(j * 49, i * 49, image_stone_w, TRUE);
            }
        }
    }
}

//-------------------------------------------------
// ゲーム終了処理
//-------------------------------------------------
int gameEndProcess(int board[][BOARD_SIZE])
{
    int i, j;
    for (i = 0; i < BOARD_SIZE; i++) {
        for (j = 0; j < BOARD_SIZE; j++) {
            if (board[i][j] == STONE_SPACE) { continue; }
            if (lenCheck(board, j, i)) {
                return 1;
            }
        }
    }
    return 0;
}

//-------------------------------------------------
// 5連確認
//-------------------------------------------------
int lenCheck(int board[][BOARD_SIZE], int x, int y)
{
    int i, j, len_flag;
    int dx[] = { 0, 1, 1 };
    int dy[] = { 1, 0, 1 };

    for (i = 0; i < 3; i++) {
        for (j = 1, len_flag = 1; j <= 4; j++) {
            if (board[y][x] != board[y + j*dy[i]][x + j*dx[i]]) {
                len_flag = 0;
                break;
            }
        }
        if (len_flag == 1) { return 1; }
    }
    return 0;
}

実行結果


f:id:mo2dx:20141212001201p:plain


おわり

みんなゲーム作ろう!