0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C++でリバーシ(オセロ)をポインタやBitボード等を使わずに、文字列配列等でゴリ押しで作ってみる

Last updated at Posted at 2022-12-20

C++でリバーシを作ってみた

今までリバーシを作ったことが無かったので練習がてら作ってみました。

割とやっつけで、200行程度のプログラムを作成しました。

プログラムについての概要

起動すると8x8の盤面がコンソール上に出現します。

0はプレイヤーの石。1は対戦相手(AI)の石です。

列,行の順で数字を打つと、石が盤面に配置されコンソールに描画します。

そのあと、対戦相手(AI)が適当に石を打ち、その盤面もコンソールに描画します。

打つことができない場合は盤面を表示させずにプレイヤーに入力のやり直しを要求します。

例えば、リバーシのルールに則ると相手の石を自分の石に変える手でなければ打つことはできないので、相手の石を自分の石で挟めないときは盤面を反映させずにやり直しを要求する必要があります。

リバーシに必要な要素

リバーシのソフトを実現するのに必要な要素を分解してみました。

  • ディスプレイに文字を表示する機能
  • 盤面に石を置く機能
    • 置きたい場所を指定する
    • 石を指定の場所に配置する
  • AIの機能
  • 相手の石を自分の石にする機能
    • 左方向から自分の石を探して、間を自分の石に置き換える。
    • 同様に右方向
    • 同様に上下方向
    • 同様に斜め方向

ソースコードについての解説

ソースコード全体は一番最後に載せています。

ここでは、重要な箇所について解説するだけに止めます。

文字列配列について

文字列配列
string board[] =
{
    "********",
    "********",
    "********",
    "***10***",
    "***01***",
    "********",
    "********",
    "********"
};

この文字列配列を使うことで、盤面の保存や閲覧を行えるようにしています。

例えば、入力において4列目、3行目を指定した場合はboard[4-1][3-1]の値を置き換えたりといったことができます。

プロトタイプ宣言

プロトタイプ宣言
int ReplaceBoard(int col,int row,char koma);
void DisplayBoard();

ReplaceBoardは盤面に石を置いて相手の石を自分の石に変える機能、DisplayBoardはコンソールに盤面を表示させる機能を実現しています。

列と行を指定して文字を置き換える

列と行を指定して文字を置き換える
// 置き換え
if(ReplaceBoard(col,row,'0') == 0){
    cout << "This position cannot replace other koma." << endl << endl;
    continue;
}

ReplaceBoard()を呼び出すことで、board[]を書き換えます。

ReplaceBoard()はintを返していますが、これは置き換えれるかどうかの判断をするためのものです。

書き換えられないことを通知することにより、プレイヤーに入力のやり直しを要求することができます。

相手の石を自分の石にする機能

相手の石を自分の石にする機能(右)
// 右
for(int k = i+1; k< 8 ; k++){
    if(board[k][j]==koma){
        for(int l = i+1; l < k;l++){
            board[l][j] = koma;
            flag = 1;
        }
        break;
    }
    else if(board[k][j]=='*'){
        break;
    }
}

このコードでリバーシの核心である相手の石を自分の石に変える機能を実現させています。

まず、自分の位置から列をi+1だけしたところから探索を開始し、8列目(board[8-1][j])までループさせます。

board[k][j]が自分の石だった場合は、自分の石の手前までループをさせて石を置き換えていきます。

*の場合、つまり空所だった場合はループを直ちに終了させます。

無題.png

同様に左、上下、斜め方向でも似たような処理を行っています。

補足

相手の石を自分の石にする機能についてですが、8つの機能を関数化せずに直に書いているため、

コードが長くしてしまっています。

条件文さえ書き換えることができれば関数化できそうです。

今後改善させる点があるなら

このコードを改善させる予定はないですが、一応あげるとすれば、

  • 関数化を推し進める
  • ポインタを駆使する
  • bitボードを使う

等を行えば、もっとコードが短くなるような気がします。

ソースコード

自分が見て分かりやすいからということで、石に関する変数をkomaと書いています。ご容赦ください。

reversi1.cpp
// 
//  リバーシ
//

#include <iostream>
#include <cstdlib>
using namespace std;

int ReplaceBoard(int col,int row,char koma);
void DisplayBoard();

string board[] =
{
    "********",
    "********",
    "********",
    "***01***",
    "***10***",
    "********",
    "********",
    "********"
};

int main(){
    DisplayBoard();
    while(1){
        int col,row,finish=0;
        while(1){
            cout << "player input" << endl;
            cin >> col >> row;
            // 強制終了
            if(col < 0 || row < 0){
                finish = 1;
                break;
            }
            if(col < 1 || col > 8 || row < 1 || row > 8){
                cout << "This position is not exist." << endl << endl;
                continue;
            }
            if(board[col-1][row-1] != '*'){
                cout << "This position is already placed." << endl << endl;
                continue;
            }
            // 置き換え
            if(ReplaceBoard(col,row,'0') == 0){
                cout << "This position cannot replace other koma." << endl << endl;
                continue;
            }
            break;
        }
        if(finish == 1) break;
        // 表示
        DisplayBoard();
        
        cout << "enemy turn!" << endl;
        cout << "enemy thinking..." << endl;

        // 1000回考えてダメだったら抜けます
        int e = 0;
        while(1){
            if(e >= 1000){
                cout << "Enemy[I'm out of control....]" << endl;
                break;
            }
            e++;
            col = (rand () % 9) + 1;
            row = (rand () % 9) + 1;
            if(col < 1 || col > 8 || row < 1 || row > 8)continue;
            if(board[col-1][row-1] != '*')continue;
            // 置き換え
            if(ReplaceBoard(col,row,'1') == 0)continue;
            break;
        }
        // 表示
        DisplayBoard();
    }
    
    // End
    string str;
    cout << "Press EOF to exit.";
    while(scanf("%s",str)!=EOF){
    }

    return 0;
}
int ReplaceBoard(int col,int row,char koma){
    // 駒を置く
    board[col-1][row-1]=koma;
    int flag = 0;
    int i= col-1,j=row-1;
    // [][]列、行
    // 駒をひっくり返す
    // 右
    for(int k = i+1; k< 8 ; k++){
        if(board[k][j]==koma){
            for(int l = i+1; l < k;l++){
                board[l][j] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[k][j]=='*'){
            break;
        }
    }
    // 左
    for(int k = i-1; k> 0 ; k--){
        if(board[k][j]==koma){
            for(int l = i-1; l > k;l--){
                board[l][j] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[k][j]=='*'){
            break;
        }
    }
    // 下
    for(int k = j+1; k< 8 ; k++){
        if(board[i][k]==koma){
            for(int l = j+1; l < k;l++){
                board[i][l] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[i][k]=='*'){
            break;
        }
    }
    // 上
    for(int k = j-1; k> 0 ; k--){
        if(board[i][k]==koma){
            for(int l = j-1; l > k;l--){
                board[i][l] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[i][k]=='*'){
            break;
        }
    }
    // 右下
    for(int k1 = i+1,k2 = j+1 ;k1 < 8 && k2 < 8 ; k1++,k2++){
        if(board[k1][k2]==koma){
            int l1,l2;
            for(int l1 = i+1, l2=j+1 ; l1 < k1 && l2 < k2 ; l1++,l2++){
                board[l1][l2] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[k1][k2]=='*'){
            break;
        }
    }
    // 右上
    for(int k1 = i+1,k2 = j-1 ;k1 < 8 && k2 > 0 ; k1++,k2--){
        if(board[k1][k2]==koma){
            int l1,l2;
            for(int l1 = i+1, l2=j-1 ; l1 < k1 && l2 > k2 ; l1++,l2--){
                board[l1][l2] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[k1][k2]=='*'){
            break;
        }
    }
    // 左下
    for(int k1 = i-1,k2 = j+1 ;k1 > 0 && k2 < 8 ; k1--,k2++){
        if(board[k1][k2]==koma){
            int l1,l2;
            for(int l1 = i-1, l2=j+1 ; l1 > k1 && l2 < k2 ; l1--,l2++){
                board[l1][l2] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[k1][k2]=='*'){
            break;
        }
    }
    // 左上
    for(int k1 = i-1,k2 = j-1 ;k1 > 0 && k2 > 0 ; k1--,k2--){
        if(board[k1][k2]==koma){
            int l1,l2;
            for(int l1 = i-1, l2=j-1 ; l1 > k1 && l2 > k2 ; l1--,l2--){
                board[l1][l2] = koma;
            }
            flag = 1;
            break;
        }
        else if(board[k1][k2]=='*'){
            break;
        }
    }
    // 何も置き換えれなかったら駒を戻す
    if(flag == 0)   board[col-1][row-1]='*';
    return flag;
}

void DisplayBoard(){

    cout << "   ";
    for(int i = 0; i< 8;i++){
        cout << i+1 << " ";
    }
    cout << endl;
    // [][]列、行
    for(int j = 0; j< 8;j++){
        cout << j+1 << "  ";
        for(int i = 0; i< 8;i++){
            cout << board[i][j] << " ";
        }
        cout << endl;
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?