0
3

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 5 years have passed since last update.

ゲームプログラミング:C++でオセロを作ってみた(OOP編)

0
Posted at

概要

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

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

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

スクリーンショット (117).png
今回はアルファベットと数字の組み合わせでフィールドを管理している

スクリーンショット (118).png
それでは試しに"e6"と入力してみよう

スクリーンショット (119).png
お!黒い石が置けた!
石の数もカウントしてくれていて助かるなぁ

スクリーンショット (120).png
いや、ちょっと待てよ・・・
ここに置くのはやめておこうかな
"u"でundoできるんだった

スクリーンショット (117).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;
}
0
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?