ゲームプログラミング：C++でオセロを作ってみた（OOP編）

#概要

あれ？今回もオセロ？
ネタなくなってきちゃった？

いいえ違うんです。
今回は「オブジェクト指向開発っぽい」オセロ制作になります。
以下を参考にしながらオセロを作成しました。
著者の方が運営されているHPも、興味のある方はご覧になってみて下さい。
　https://www.kohgakusha.co.jp/books/detail/978-4-87593-428-8
　http://sealsoft.jp/thell/

とりあえずうまく動作していそうです。

![スクリーンショット (117).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634905/18db514d-4f6f-ae44-3031-c0345485bed6.png)
今回はアルファベットと数字の組み合わせでフィールドを管理している

![スクリーンショット (118).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634905/2bf58c8b-8f87-b988-f0e4-2102f44ab8d1.png)
それでは試しに"e6"と入力してみよう

![スクリーンショット (119).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634905/b981bcf9-41d9-bf26-f62a-4d964c564b47.png)
お！黒い石が置けた！
石の数もカウントしてくれていて助かるなぁ

![スクリーンショット (120).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634905/ab861f72-3451-9de5-a2fc-a881148b2f10.png)
いや、ちょっと待てよ・・・
ここに置くのはやめておこうかな
"u"でundoできるんだった

![スクリーンショット (117).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634905/8cfde78f-2107-673f-3e45-04614fb78221.png)
お！戻った！
さてさて、じっくり考えて最高の一手を打とうじゃないか・・・！

因みにundoは探索アルゴリズムを組むための布石だったりして・・・ぐふふ

#コード

せっかくなので、コードを公開します。
ご自由にお試し下さい。

※コードレビューコメント頂けますと幸いです

```Main.cpp
#include "ConsoleBoard.h"
#include <string>

int main()
{
    ConsoleBoard board;
    
    while (true)
    {
        board.display();

        std::cout << "Black Disc:" << board.countDisc(BLACK) << std::endl;
        std::cout << "White Disc:" << board.countDisc(WHITE) << std::endl;
        std::cout << "Empty Disc:" << board.countDisc(EMPTY) << std::endl;

        std::cout << std::endl;

        std::cout << "input..." << std::endl;

        Point p;
        std::string in;
        std::cin >> in;

        if ("p" == in)
        {
            if (!board.pass())
            {
                std::cerr << "can't pass!!" << std::endl;
            }
            continue;
        }

        if ("u" == in)
        {
            board.undo();
            continue;
        }

        if (in.length() >= 2)
        {
            p.setX(in[0] - 'a' + 1);
            p.setY(in[1] - '1' + 1);
        }
        else
        {
            std::cerr << "invalid input!!" << std::endl;
            continue;
        }

        if (false == board.move(p))
        {
            std::cerr << "can't put the disc there!!" << std::endl;
            continue;
        }

        if (board.isGameOver())
        {
            std::cout << "Game Over..." << std::endl;
            return 0;
        }
    }

    return 0;
}
```

```ConsoleBoard.h
#include "Board.h"
#include <iostream>

class ConsoleBoard :public Board
{
public:
    void display();
};
```

```ConsoleBoard.cpp
#include "ConsoleBoard.h"
#include <stdlib.h>

void ConsoleBoard::display()
{
    system("cls");
    std::cout << "  a b c d e f g h " << std::endl;

    for (int y = 1; y <= BOARD_SIZE; ++y)
    {
        std::cout << " " << y;

        for (int x = 1; x <= BOARD_SIZE; ++x)
        {
            switch (getColor(Point(x, y)))
            {
            case BLACK:
                std::cout << "●";
                break;
            case WHITE:
                std::cout << "〇";
                break;
            default:
                std::cout << "  ";
                break;
            }
        }
        std::cout << std::endl;
    }
}
```

```Board.h
#include "Disc.h"
#include <vector>

static const int BOARD_SIZE = 8;
static const int MAX_TURNS = 60;

class Board
{
public:
    Board();

    void init();
    bool move(const Point& point);
    bool pass();
    bool undo();
    bool isGameOver() const;

    unsigned int countDisc(Color color) const;
    Color getColor(const Point& point) const;
    const std::vector<Point>& getMovablePosition() const;
    std::vector<Disc> getUpdate() const;
    Color getCurrentColor() const;
    unsigned int getTurns() const;

private:
    void flipDiscs(const Point& point);
    unsigned int checkMobility(const Disc& disc) const;
    void initMovable();

    // 方向. 
    enum Direction
    {
        NONE        = 0,
        UPPER       = 1,
        UPPER_LEFT  = 2,
        LEFT        = 4,
        LOWER_LEFT  = 8,
        LOWER       = 16,
        LOWER_RIGHT = 32,
        RIGHT       = 64,
        UPPER_RIGHT = 128
    };

    // ボード上の石. 
    Color rawBoard_[BOARD_SIZE + 2][BOARD_SIZE + 2];

    // 手数. 
    unsigned int turns_;
    
    // 現在のプレイヤー. 
    Color currentPlayerColor_;

    // 打ち手の履歴. 
    std::vector<std::vector<Disc>> updateLog_;

    // 石を置ける位置. 
    std::vector<Point> movablePosition_[MAX_TURNS + 1];

    // 石をひっくり返せる方向. 
    unsigned int movableDir_[MAX_TURNS + 1][BOARD_SIZE + 2][BOARD_SIZE + 2];

    // 各色の石の数. 
    ColorStorage<unsigned int> Discs_;
};
```

```Board.cpp
#include "Board.h"

Board::Board()
{
    init();
}

void Board::init()
{
    // ボード上の全マスを空きマスに設定する. 
    for (int y = 1; y <= BOARD_SIZE; ++y)
    {
        for (int x = 1; x <= BOARD_SIZE; ++x)
        {
            rawBoard_[x][y] = EMPTY;
        }
    }

    // 壁を設定する. 
    for (int y = 0; y < BOARD_SIZE + 2; ++y)
    {
        rawBoard_[0][y] = WALL;
        rawBoard_[BOARD_SIZE + 1][y] = WALL;
    }

    for (int x = 0; x < BOARD_SIZE + 2; ++x)
    {
        rawBoard_[x][0] = WALL;
        rawBoard_[x][BOARD_SIZE + 1] = WALL;
    }

    // 石の初期配置を設定する. 
    rawBoard_[4][4] = WHITE;
    rawBoard_[5][5] = WHITE;
    rawBoard_[4][5] = BLACK;
    rawBoard_[5][4] = BLACK;

    // 石の数を初期設定にする. 
    Discs_[BLACK] = 2;
    Discs_[WHITE] = 2;
    Discs_[EMPTY] = BOARD_SIZE*BOARD_SIZE - Discs_[BLACK] - Discs_[WHITE];

    // 手数は0から数える. 
    turns_ = 0;

    // 先手は黒. 
    currentPlayerColor_ = BLACK;

    // updateは全て削除する. 
    updateLog_.clear();

    // 石を置ける位置を初期化する. 
    initMovable();
}

bool Board::move(const Point& point)
{
    // ボード外に石は置けない. 
    if (point.getX() < 0 || point.getX() >= BOARD_SIZE)
    {
        return false;
    }

    if (point.getY() < 0 || point.getY() >= BOARD_SIZE)
    {
        return false;
    }

    // 石を置いた時、異なる色の石を挟むことができない場合、その位置に石を置くことはできない. 
    if (NONE == movableDir_[turns_][point.getX()][point.getY()])
    {
        return false;
    }

    // 自分の石で挟んだ石をひっくり返す. 
    flipDiscs(point);

    // 相手のターンに移る. 
    ++turns_;
    currentPlayerColor_ = -currentPlayerColor_;

    // データ更新処理. 
    initMovable();

    return true;
}

bool Board::pass()
{
    // 打ち手がある場合、パスはできない. 
    if (!movablePosition_[turns_].empty())
    {
        return false;
    }

    // ゲームが終了している場合、パスできない. 
    if (isGameOver())
    {
        return false;
    }

    // パスできる
    currentPlayerColor_ = -currentPlayerColor_;
    updateLog_.push_back(std::vector<Disc>());
    initMovable();

    return true;
}

bool Board::undo()
{
    // ゲーム開始時の場合、戻すことはできない. 
    if (0 == turns_)
    {
        return false;
    }

    // 一手前に戻す. 
    currentPlayerColor_ = -currentPlayerColor_;
    const std::vector<Disc>& update = updateLog_.back();

    // 前のターンにパスをした場合. 
    if (update.empty())
    {
        movablePosition_[turns_].clear();
        for (int x = 1; x <= BOARD_SIZE; ++x)
        {
            for (int y = 1; y <= BOARD_SIZE; ++y)
            {
                movableDir_[turns_][x][y] = NONE;
            }
        }
    }
    // 前のターンにパスをしなかった場合. 
    else
    {
        --turns_;

        // ボード上の石を前の状態に戻す. 
        rawBoard_[update.front().getX()][update.front().getY()] = EMPTY;
        for (int i = 1; i < update.size(); ++i)
        {
            rawBoard_[update.at(i).getX()][update.at(i).getY()] = -currentPlayerColor_;
        }

        // ボード上の石の数を更新する. 
        unsigned int discCount = static_cast<unsigned int>(update.size());
        Discs_[currentPlayerColor_] -= discCount;
        Discs_[-currentPlayerColor_] += discCount - 1;
        ++Discs_[EMPTY];
    }

    // 不要になったupdateを1つ削除する. 
    updateLog_.pop_back();

    return true;
}

bool Board::isGameOver() const
{
    // 最大の手数に達していたらゲーム終了. 
    if (MAX_TURNS == turns_)
    {
        return true;
    }

    // 打ち手がある場合はゲーム終了ではない. 
    if (!movablePosition_[turns_].empty())
    {
        return false;
    }

    // 現在のターンと逆の色に打ち手があるか判定する. 
    Disc disc;
    disc.setColor(-currentPlayerColor_);

    for (int x = 0; x < BOARD_SIZE; ++x)
    {
        disc.setX(x);
        for (int y = 0; y < BOARD_SIZE; ++y)
        {
            disc.setY(y);
            if (NONE != checkMobility(disc))
            {
                return false;
            }
        }
    }

    return true;
}

unsigned int Board::countDisc(Color color) const
{
    return Discs_[color];
}

Color Board::getColor(const Point& point) const
{
    return rawBoard_[point.getX()][point.getY()];
}

const std::vector<Point>& Board::getMovablePosition() const
{
    return movablePosition_[turns_];
}

std::vector<Disc> Board::getUpdate() const
{
    if (updateLog_.empty())
    {
        return std::vector<Disc>();
    }
    else
    {
        return updateLog_.back();
    }
}

Color Board::getCurrentColor() const
{
    return currentPlayerColor_;
}

unsigned int Board::getTurns() const
{
    return turns_;
}

void Board::flipDiscs(const Point& point)
{
    int x = 0;
    int y = 0;

    Disc operationDisc(point.getX(), point.getY(), currentPlayerColor_);

    int direction = movableDir_[turns_][point.getX()][point.getY()];

    std::vector<Disc> update;

    // pointで指定された位置に自分の石を置く. 
    rawBoard_[point.getX()][point.getY()] = currentPlayerColor_;
    update.push_back(operationDisc);

    // 指定された位置の上方向に、ひっくり返せる石が存在する. 
    if (UPPER & direction)
    {
        y = point.getY();
        operationDisc.setX(point.getX());

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[point.getX()][--y])
        {
            rawBoard_[point.getX()][y] = currentPlayerColor_;
            operationDisc.setY(y);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の下方向に、ひっくり返せる石が存在する. 
    if (LOWER & direction)
    {
        y = point.getY();
        operationDisc.setX(point.getX());

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[point.getX()][++y])
        {
            rawBoard_[point.getX()][y] = currentPlayerColor_;
            operationDisc.setY(y);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の右方向に、ひっくり返せる石が存在する. 
    if (RIGHT & direction)
    {
        x = point.getX();
        operationDisc.setY(point.getY());

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[++x][point.getY()])
        {
            rawBoard_[x][point.getY()] = currentPlayerColor_;
            operationDisc.setX(x);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の左方向に、ひっくり返せる石が存在する. 
    if (LEFT & direction)
    {
        x = point.getX();
        operationDisc.setY(point.getY());

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[--x][point.getY()])
        {
            rawBoard_[x][point.getY()] = currentPlayerColor_;
            operationDisc.setX(x);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の右上方向に、ひっくり返せる石が存在する. 
    if (UPPER_RIGHT & direction)
    {
        x = point.getX();
        y = point.getY();

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[++x][--y])
        {
            rawBoard_[x][y] = currentPlayerColor_;
            operationDisc.setX(x);
            operationDisc.setY(y);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の右下方向に、ひっくり返せる石が存在する. 
    if (LOWER_RIGHT & direction)
    {
        x = point.getX();
        y = point.getY();

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[++x][++y])
        {
            rawBoard_[x][y] = currentPlayerColor_;
            operationDisc.setX(x);
            operationDisc.setY(y);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の左上方向に、ひっくり返せる石が存在する. 
    if (UPPER_LEFT & direction)
    {
        x = point.getX();
        y = point.getY();

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[--x][--y])
        {
            rawBoard_[x][y] = currentPlayerColor_;
            operationDisc.setX(x);
            operationDisc.setY(y);
            update.push_back(operationDisc);
        }
    }

    // 指定された位置の左下方向に、ひっくり返せる石が存在する. 
    if (LOWER_LEFT & direction)
    {
        x = point.getX();
        y = point.getY();

        // 自分の石の色で挟んだ石をひっくり返す. 
        while (currentPlayerColor_ != rawBoard_[--x][++y])
        {
            rawBoard_[x][y] = currentPlayerColor_;
            operationDisc.setX(x);
            operationDisc.setY(y);
            update.push_back(operationDisc);
        }
    }
    
    // ボード上の石の数を更新する. 
    unsigned int flippedDiscCount = static_cast<unsigned int>(update.size());

    Discs_[currentPlayerColor_] += flippedDiscCount;
    Discs_[-currentPlayerColor_] -= flippedDiscCount - 1;
    --Discs_[EMPTY];

    updateLog_.push_back(update);
}

unsigned int Board::checkMobility(const Disc& disc) const
{
    // 指定位置に既に石が置いてある場合、そこに石を置くことはできない
    if (EMPTY != rawBoard_[disc.getX()][disc.getY()])
    {
        return NONE;
    }

    // 指定位置に石を置けるか判定する.
    unsigned int direction = 0;
    int x = 0;
    int y = 0;

    // 指定位置の上方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX()][disc.getY() - 1])
    {
        x = disc.getX();
        y = disc.getY() - 2;

        while (-disc.getColor() == rawBoard_[x][y])
        {
            --y;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= UPPER;
        }
    }

    // 指定位置の下方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX()][disc.getY() + 1])
    {
        x = disc.getX();
        y = disc.getY() + 2;

        while (-disc.getColor() == rawBoard_[x][y])
        {
            ++y;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= LOWER;
        }
    }

    // 指定位置の右方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX() + 1][disc.getY()])
    {
        x = disc.getX() + 2;
        y = disc.getY();

        while (-disc.getColor() == rawBoard_[x][y])
        {
            ++x;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= RIGHT;
        }
    }

    // 指定位置の左方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX() - 1][disc.getY()])
    {
        x = disc.getX() - 2;
        y = disc.getY();

        while (-disc.getColor() == rawBoard_[x][y])
        {
            --x;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= LEFT;
        }
    }

    // 指定位置の右上方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX() + 1][disc.getY() - 1])
    {
        x = disc.getX() + 2;
        y = disc.getY() - 2;

        while (-disc.getColor() == rawBoard_[x][y])
        {
            ++x;
            --y;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= UPPER_RIGHT;
        }
    }

    // 指定位置の左上方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX() - 1][disc.getY() - 1])
    {
        x = disc.getX() - 2;
        y = disc.getY() - 2;

        while (-disc.getColor() == rawBoard_[x][y])
        {
            --x;
            --y;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= UPPER_LEFT;
        }
    }

    // 指定位置の右下方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX() + 1][disc.getY() + 1])
    {
        x = disc.getX() + 2;
        y = disc.getY() + 2;

        while (-disc.getColor() == rawBoard_[x][y])
        {
            ++x;
            ++y;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= LOWER_RIGHT;
        }
    }

    // 指定位置の左下方向に置く石と逆の色の石が存在するか判定する. 
    if (-disc.getColor() == rawBoard_[disc.getX() - 1][disc.getY() + 1])
    {
        x = disc.getX() - 2;
        y = disc.getY() + 2;

        while (-disc.getColor() == rawBoard_[x][y])
        {
            --x;
            ++y;
        }
        if (disc.getColor() == rawBoard_[x][y])
        {
            direction |= LOWER_LEFT;
        }
    }

    return direction;
}

void Board::initMovable()
{
    Disc disc(0, 0, currentPlayerColor_);

    movablePosition_[turns_].clear();

    // ボード上の1マス1マスに対して石が置けるか判定する. 
    for (int x = 1; x <= BOARD_SIZE; ++x)
    {
        disc.setX(x);

        for (int y = 1; y <= BOARD_SIZE; ++y)
        {
            disc.setY(y);

            movableDir_[turns_][x][y] = checkMobility(disc);

            if (NONE != movableDir_[turns_][x][y])
            {
                movablePosition_[turns_].push_back(disc);
            }
        }
    }

    return;
}
```

```Disc.h
#include "Point.h"

typedef int Color;
static const Color EMPTY = 0;
static const Color WHITE = -1;
static const Color BLACK = 1;
static const Color WALL = 2;

// 色別の情報を格納するためのクラス. 
template<typename T> class ColorStorage
{
public:
    T& operator[](Color color)
    {
        return data[color + 1];
    }

    const T& operator[](Color color) const
    {
        return data[color + 1];
    }

private:
    T data[3];
};

// ボードに置かれる石. 
class Disc : public Point
{
public:
    Disc() : Point(0, 0)
    {
        color_ = EMPTY;
    }

    Disc(int x, int y, Color color) : Point(x, y)
    {
        color_ = color;
    }

    Color getColor() const;
    void setColor(const Color& color);

private:
    // 石の色. 
    Color color_;
};
```

```Disc.cpp
#include "Disc.h"

Color Disc::getColor() const
{
    return color_;
}

void Disc::setColor(const Color& color)
{
    color_ = color;
}
```

```Point.h
#include <string>
#include <sstream>
#include <stdexcept>
#include <limits>
#include <iosfwd>


// 座標. 
class Point
{
public:
    Point()
    {
        Point(0, 0);
    }

    Point(int x, int y)
    {
        x_ = x;
        y_ = y;
    }

    int getX() const;
    int getY() const;

    void setX(int x);
    void setY(int y);

private:
    int x_;
    int y_;
};
```

```Point.cpp
#include "Point.h"

int Point::getX() const
{
    return x_;
}

int Point::getY() const
{
    return y_;
}

void Point::setX(int x)
{
    x_ = x;

    return;
}

void Point::setY(int y)
{
    y_ = y;

    return;
}
```