Go back to Richel Bilderbeek's homepage.

Go back to Richel Bilderbeek's C++ page.

 

 

 

 

 

(C++) Reversi

 

Technical facts

 

 

 

 

 

 

./CppReversi/CppReversi.pri

 

INCLUDEPATH += \
    ../../Classes/CppReversi

SOURCES += \
    ../../Classes/CppReversi/reversiwidget.cpp \
    ../../Classes/CppReversi/reversimove.cpp \
    ../../Classes/CppReversi/reversiboard.cpp \
    ../../Classes/CppReversi/reversisquare.cpp \
    ../../Classes/CppReversi/reversiplayer.cpp \
    ../../Classes/CppReversi/reversiwinner.cpp

HEADERS  += \
    ../../Classes/CppReversi/reversiwidget.h \
    ../../Classes/CppReversi/reversimove.h \
    ../../Classes/CppReversi/reversiboard.h \
    ../../Classes/CppReversi/reversifwd.h \
    ../../Classes/CppReversi/reversisquare.h \
    ../../Classes/CppReversi/reversiplayer.h \
    ../../Classes/CppReversi/reversiwinner.h

OTHER_FILES += \
    ../../Classes/CppReversi/Licence.txt

 

 

 

 

 

./CppReversi/reversiboard.h

 

#ifndef REVERSIBOARD_H
#define REVERSIBOARD_H

#include <string>
#include <vector>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#include <boost/shared_ptr.hpp>

#include "reversisquare.h"
#include "reversiplayer.h"
#pragma GCC diagnostic pop

namespace ribi {

struct TextCanvas;

namespace reversi {

///Board is a reversi board with the most level interface as possible.
///Board does not know the current player and Board cannot undo its moves
///For a higher level interface, use Widget
struct Board
{

  Board(const int size = 10);
  //Board(const Board& other);
  //Board& operator=(const Board& other);
  //~Board() noexcept {}

  bool CanDoMove(const int x, const int y, const Player player) const noexcept;

  int Count(const Square square) const noexcept;

  void DoMove(const int x, const int y, const Player player) noexcept;

  const std::vector<std::vector<Square> >& GetBoard() const noexcept { return m_board; }

  Square Get(const int x, const int y) const noexcept;

  int GetSize() const noexcept;

  static std::string GetVersion() noexcept;
  static std::vector<std::string> GetVersionHistory() noexcept;

  const std::vector<std::pair<int,int>> GetValidMoves(const Player player) const noexcept;

  ///Simply sets a square, without any checking
  void Set(const int x, const int y, const Square state) noexcept;

  const boost::shared_ptr<TextCanvas> ToTextCanvas() const noexcept;

  private:
  std::vector<std::vector<Square>> m_board;

  ///Create the delta-x and delta-y to search in the 8 directions
  static const std::vector<std::pair<int,int>> CreateDeltas() noexcept;

  static Player GetOtherPlayer(const Player player) noexcept;

  Square PlayerToSquare(const Player player) const noexcept;

  #ifndef NDEBUG
  static void Test() noexcept;
  #endif

  friend std::ostream& operator<<(std::ostream& os, const Board& r);
};

bool operator==(const Board& lhs, const Board& rhs);
bool operator!=(const Board& lhs, const Board& rhs);
std::ostream& operator<<(std::ostream& os, const Board& r);
std::istream& operator>>(std::istream& is, Board& r);

} //~namespace reversi
} //~namespace ribi

#endif // REVERSIBOARD_H

 

 

 

 

 

./CppReversi/reversiboard.cpp

 

#include "reversiboard.h"

#include <algorithm>
#include <cassert>
#include <stdexcept>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#include "testtimer.h"
#include "textcanvas.h"
#include "trace.h"
#pragma GCC diagnostic pop

ribi::reversi::Board::Board(const int size)
  : m_board(size,std::vector<Square>(size,Square::empty))
{
  #ifndef NDEBUG
  Test();
  #endif
  assert(size > 0);
  assert(size == this->GetSize());
  assert(m_board.size() == m_board[0].size() );

  const int x = size / 2 - 1;
  const int y = size / 2 - 1;
  Set(x  ,y  ,Square::player1);
  Set(x+1,y  ,Square::player2);
  Set(x  ,y+1,Square::player2);
  Set(x+1,y+1,Square::player1);
}

/*
ribi::reversi::Board::Board(const Board& other)
  : m_board(other.m_board)
{

}
ribi::reversi::Board& ribi::reversi::Board::operator=(const Board& other)
{
  m_board = other.m_board;
  return *this;
}
*/

bool ribi::reversi::Board::CanDoMove(const int x, const int y, const Player player) const noexcept
{
  assert(x >= 0);
  assert(y >= 0);
  assert(x < this->GetSize());
  assert(y < this->GetSize());
  if (Get(x,y) != Square::empty) return false;
  for (const std::pair<int,int> d: CreateDeltas())
  {
    int cur_x = x + d.first;
    int cur_y = y + d.second;
    //Must be a valid direction
    if (cur_x < 0 || cur_y < 0 || cur_x >= GetSize() || cur_y >= GetSize()) continue;
    //Adjacent square must be of opponent
    if (Get(cur_x,cur_y) != PlayerToSquare(GetOtherPlayer(player))) continue;
    assert(Get(cur_x,cur_y) != Square::empty);
    //Follow the trail until
    // - player is found -> Can do move
    // - empty is found -> Cannot do move
    // - other player is found -> Search on
    while (1)
    {
      cur_x += d.first;
      cur_y += d.second;
      //Must be a valid direction
      if (cur_x < 0 || cur_y < 0 || cur_x >= GetSize() || cur_y >= GetSize()) break;
      if (Get(cur_x,cur_y) == Square::empty) break;
      if (Get(cur_x,cur_y) == PlayerToSquare(player))
      {
        return true;
      }
    }
  }
  return false;
}

int ribi::reversi::Board::Count(const Square square) const noexcept
{
  const int size = GetSize();

  int sum = 0;

  for (int y=0; y!=size; ++y)
  {
    for (int x=0; x!=size; ++x)
    {
      if (Get(x,y) == square) ++sum;
    }
  }
  return sum;
}

const std::vector<std::pair<int,int>> ribi::reversi::Board::CreateDeltas() noexcept
{
  std::vector<std::pair<int,int>> v;
  for (int dx = -1; dx != 2; ++dx)
  {
    for (int dy = -1; dy != 2; ++dy)
    {
      if (dx != 0 || dy != 0) v.push_back( std::make_pair(dx,dy));
    }
  }
  assert(v.size() == 8);
  return v;
}

void ribi::reversi::Board::DoMove(const int x, const int y, const Player player) noexcept
{
  assert(CanDoMove(x,y,player));
  #ifndef NDEBUG
  const Board before(*this);
  #endif

  //Collect the deltas tomodify the color
  std::vector<std::pair<int,int>> v;
  for (const std::pair<int,int> d: CreateDeltas())
  {
    int cur_x = x + d.first;
    int cur_y = y + d.second;
    //Must be a valid direction
    if (cur_x < 0 || cur_y < 0 || cur_x >= GetSize() || cur_y >= GetSize()) continue;
    //Adjacent square must be of opponent
    if (Get(cur_x,cur_y) != PlayerToSquare(GetOtherPlayer(player))) continue;
    assert(Get(cur_x,cur_y) != Square::empty);
    //Follow the trail until
    // - player is found -> Can do move
    // - empty is found -> Cannot do move
    // - other player is found -> Search on
    while (1)
    {
      cur_x += d.first;
      cur_y += d.second;
      //Must be a valid direction
      if (cur_x < 0 || cur_y < 0 || cur_x >= GetSize() || cur_y >= GetSize()) break;
      if (Get(cur_x,cur_y) == Square::empty) break;
      if (Get(cur_x,cur_y) == PlayerToSquare(player))
      {
        v.push_back(d); //Found delta
        break; //Next delta
      }
    }
  }
  assert(!v.empty());

  for (const std::pair<int,int> d: v)
  {
    int cur_x = x + d.first;
    int cur_y = y + d.second;
    //Adjacent square must be of opponent
    assert(Get(cur_x,cur_y) == PlayerToSquare(GetOtherPlayer(player)));
    Set(cur_x,cur_y,PlayerToSquare(player));
    //Follow the trail until
    // - player is found -> Can do move
    while (1)
    {
      cur_x += d.first;
      cur_y += d.second;
      //Must be a valid direction
      assert(!(cur_x < 0 || cur_y < 0 || cur_x >= GetSize() || cur_y >= GetSize()));
      if (Get(cur_x,cur_y) == PlayerToSquare(player)) break;
      assert(Get(cur_x,cur_y) == PlayerToSquare(GetOtherPlayer(player)));
      Set(cur_x,cur_y,PlayerToSquare(player));
    }
  }
  Set(x,y,PlayerToSquare(player));
}

ribi::reversi::Square ribi::reversi::Board::Get(const int x, const int y) const noexcept
{
  #ifndef NDEBUG
  const int sz = GetSize();
  assert(x >= 0);
  assert(y >= 0);
  assert(x < sz);
  assert(y < sz);
  #endif
  return m_board[y][x];
}

std::string ribi::reversi::Board::GetVersion() noexcept
{
  return "2.1";
}


std::vector<std::string> ribi::reversi::Board::GetVersionHistory() noexcept
{
  return {
    "2007-09-24: version 1.0: initial version developed under C++ Builder, called Reversi",
    "2010-09-24: version 1.1: initial port to Qt Creator",
    "2013-12-19: version 2.0: split interface in reversi::Board and reversi::Widget",
    "2014-02-14: version 2.1: use of enum classes, added ToTextCanvas member function"
  };
}


ribi::reversi::Player ribi::reversi::Board::GetOtherPlayer(const Player player) noexcept
{
  switch (player)
  {
    case Player::player1: return Player::player2;
    case Player::player2: return Player::player1;
    default: assert(!"Should not get here");
  }
  assert(!"Should not get here");
  throw std::logic_error("Board::GetOtherPlayer: unknown player");
}


const std::vector<std::pair<int,int>> ribi::reversi::Board::GetValidMoves(const Player player) const noexcept
{
  const int size = GetSize();
  std::vector< std::pair<int,int> > v;
  for (int y=0; y!=size; ++y)
  {
    for (int x=0; x!=size; ++x)
    {
      if (CanDoMove(x,y,player))
      {
        v.push_back( std::make_pair(x,y) );
      }
    }
  }
  return v;
}

int ribi::reversi::Board::GetSize() const noexcept
{
  return m_board.size();
}

ribi::reversi::Square ribi::reversi::Board::PlayerToSquare(const Player player) const noexcept
{
  switch (player)
  {
    case Player::player1: return Square::player1;
    case Player::player2: return Square::player2;
    default:
      assert(!"Should not get here");
      throw std::logic_error("ribi::reversi::Board::PlayerToSquare: unknown value of player");
  }
}

void ribi::reversi::Board::Set(const int x, const int y, const Square state) noexcept
{
  assert(x>=0 && x < GetSize());
  assert(y>=0 && y < GetSize());
  m_board[y][x] = state;
  assert(Get(x,y)==state);
}

#ifndef NDEBUG
void ribi::reversi::Board::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  const TestTimer test_timer(__func__,__FILE__,1.0);
  {
    Board r(4);
    assert(r.Get(1,1) == Square::player1);
    assert(r.Get(1,2) == Square::player2);
    assert(r.Get(2,1) == Square::player2);
    assert(r.Get(2,2) == Square::player1);
    assert(r.Get(0,0) == Square::empty);
    assert(r.Get(2,0) == Square::empty);
    assert(r.CanDoMove(2,0,Player::player1));
    assert(r.CanDoMove(3,1,Player::player1));
    assert(r.CanDoMove(0,2,Player::player1));
    assert(r.CanDoMove(1,3,Player::player1));
    assert(r.GetValidMoves(Player::player1).size() == 4);
  }
  //operator==
  {
    const Board r(5);
    Board s(5);
    assert(r == s);
    s.Set(0,0,Square::player1);
    assert(r != s);
  }
  //operator<<
  for (int sz=4; sz!=10; ++sz)
  {
    const Board r(sz);
    std::stringstream s;
    s << r;
    Board t;
    s >> t;
    assert(r == t);

  }
  {
    std::stringstream s;
    s << "1112." << '\n'
      << "111.." << '\n'
      << "112.." << '\n'
      << "1.2.." << '\n'
      << "1.2..";
    Board r;
    s >> r;
    assert( r.CanDoMove(4,0,Player::player1));
    assert( r.CanDoMove(3,2,Player::player1));
    assert( r.CanDoMove(3,3,Player::player1));
    assert( r.CanDoMove(3,4,Player::player1));
    assert(!r.CanDoMove(3,1,Player::player1));
  }
  //Play random games
  for (int sz = 4; sz != 10; ++sz)
  {
    Board r(sz);
    Player player = Player::player1;
    while (!r.GetValidMoves(player).empty())
    {
      std::vector<std::pair<int,int>> m {
        r.GetValidMoves(player)
      };
      assert(!m.empty());
      std::random_shuffle(m.begin(),m.end());
      const std::pair<int,int> move = m[0];
      assert(r.CanDoMove(move.first,move.second,player));
      r.DoMove(move.first,move.second,player);
      player = GetOtherPlayer(player);
    }
  }
}
#endif

const boost::shared_ptr<ribi::TextCanvas> ribi::reversi::Board::ToTextCanvas() const noexcept
{
  const int n_rows = static_cast<int>(m_board.size());

  if (n_rows == 0)
  {
    return nullptr;
  }

  const int n_cols = static_cast<int>(m_board[0].size());
  boost::shared_ptr<TextCanvas> canvas {
    new TextCanvas(n_cols,n_rows)
  };

  for(int row=0; row!=n_rows; ++row)
  {
    assert(m_board[row].size() == m_board[0].size());
    for (int col=0; col!=n_cols; ++col)
    {
      const Square square = m_board[row][col];
      char c = ' ';
      switch (square)
      {
        case Square::empty  : c = '.'; break;
        case Square::player1: c = 'O'; break;
        case Square::player2: c = 'X'; break;
        default: assert(!"Should not get here");
      }
      canvas->PutChar(col,row,c);
    }
  }
  return canvas;
}

bool ribi::reversi::operator==(const ribi::reversi::Board& lhs, const ribi::reversi::Board& rhs)
{
  return lhs.GetBoard() == rhs.GetBoard();
}

bool ribi::reversi::operator!=(const ribi::reversi::Board& lhs, const ribi::reversi::Board& rhs)
{
  return !(lhs == rhs);
}

std::ostream& ribi::reversi::operator<<(std::ostream& os, const ribi::reversi::Board& r)
{
  for(const std::vector<Square>& line: r.m_board)
  {
    std::transform(line.begin(),line.end(),
      std::ostream_iterator<std::string>(os,""),
      [](const Square square)
      {
        switch (square)
        {
          case Square::empty  : return ".";
          case Square::player1: return "1";
          case Square::player2: return "2";
          default: assert(!"Should not get here");
        }
        assert(!"Should not get here");
        throw std::logic_error("operator<<(std::ostream& os, const Board& r): Unknown square type");
      }
    );
    os << '\n';
  }
  return os;
}

std::istream& ribi::reversi::operator>>(std::istream& is, ribi::reversi::Board& r)
{
  std::vector<std::string> v;
  {
    //Read first line
    {
      std::string s;
      is >> s;
      assert(is);
      v.push_back(s);
    }
    //Read next lines
    assert(!v.empty());
    const int size = static_cast<int>(v[0].size());
    for (int i=1; i!=size; ++i)
    {
      std::string s;
      is >> s;
      assert(is);
      assert(s.size() == v[0].size());
      v.push_back(s);
    }
    assert(size == static_cast<int>(v.size()));
  }
  r = Board(static_cast<int>(v.size()));
  {
    const int size = static_cast<int>(v.size());
    for (int y=0; y!=size; ++y)
    {
      const std::string& line = v[y];
      for (int x=0; x!=size; ++x)
      {
        const char c = line[x];
        switch (c)
        {
          case '1': r.Set(x,y,Square::player1); break;
          case '2': r.Set(x,y,Square::player2); break;
          case '.': r.Set(x,y,Square::empty); break;
          default: assert(!"Should not get here");
        }
      }
    }
  }
  return is;
}

 

 

 

 

 

./CppReversi/reversifwd.h

 

#ifndef REVERSIFWD_H
#define REVERSIFWD_H

namespace ribi {
namespace reversi {

struct Board;
struct Move;
struct Widget;

} //namespace reversi
} //namespace ribi

#endif // REVERSIFWD_H

 

 

 

 

 

./CppReversi/reversimove.h

 

#ifndef REVERSIMOVE_H
#define REVERSIMOVE_H

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include <boost/shared_ptr.hpp>
#pragma GCC diagnostic pop

namespace ribi {
namespace reversi {

struct Move
{
  virtual ~Move() noexcept {}

  ///Create a Move from a std::string
  ///Returns a nullptr if the Move cannot be parsed
  ///
  ///Notation:
  ///- x,y -> MovePlacePiece
  ///- [empty] -> MovePass
  static boost::shared_ptr<Move> Parse(const std::string& s) noexcept;

  virtual std::string ToStr() const noexcept = 0;

  #ifndef NDEBUG
  static void Test() noexcept;
  #endif
};

struct MovePlacePiece : public Move
{
  MovePlacePiece(const int x, const int y) : m_x(x), m_y(y) {}

  int GetX() const noexcept { return m_x; }
  int GetY() const noexcept { return m_y; }

  std::string ToStr() const noexcept;

  private:
  const int m_x;
  const int m_y;
};

struct MovePass : public Move
{
  MovePass() {}

  std::string ToStr() const noexcept { return "pass"; }
};


} //~namespace reversi
} //~namespace ribi

#endif // REVERSIMOVE_H

 

 

 

 

 

./CppReversi/reversimove.cpp

 

#include "reversimove.h"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#include <boost/lexical_cast.hpp>
#pragma GCC diagnostic pop

std::string ribi::reversi::MovePlacePiece::ToStr() const noexcept
{
  std::stringstream s;
  s << this->GetX() << ',' << this->GetY();
  return s.str();
}

boost::shared_ptr<ribi::reversi::Move> ribi::reversi::Move::Parse(
  const std::string& s) noexcept
{
  boost::shared_ptr<ribi::reversi::Move> move;

  if (s.empty()) return move;
  if (s == "p" || s == "P" || s == "pass" || s == "Pass" || s == "PASS")
  {
    move.reset(new MovePass);
    return move;
  }
  const std::size_t i = s.find(',');
  if (
       i != std::string::npos
    && i != 0
    && i != s.size() - 1
    && std::count(s.begin(),s.end(),',') == 1
  )
  {
    const std::string a = s.substr(0,i);
    const std::string b = s.substr(i + 1,s.size() - i - 1);
    assert(std::count(a.begin(),a.end(),',') == 0);
    assert(std::count(b.begin(),b.end(),',') == 0);
    const int x = boost::lexical_cast<int>(a);
    const int y = boost::lexical_cast<int>(b);
    move.reset(new MovePlacePiece(x,y));
    return move;
  }

  assert(move || !move);
  return move;
}

 

 

 

 

 

./CppReversi/reversiplayer.h

 

#ifndef REVERSIPLAYER_H
#define REVERSIPLAYER_H

#include <string>

namespace ribi {
namespace reversi {

enum class Player { player1, player2 };

std::string PlayerToStr(const Player player) noexcept;

} //~namespace reversi
} //~namespace ribi

#endif // REVERSIPLAYER_H

 

 

 

 

 

./CppReversi/reversiplayer.cpp

 

#include "reversiplayer.h"

#include <cassert>
#include <stdexcept>

std::string ribi::reversi::PlayerToStr(const Player player) noexcept
{
  switch (player)
  {
    case Player::player1: return "player1";
    case Player::player2: return "player2";
    default: assert(!"Should not get here");
      throw std::logic_error("ribi::reversi::PlayerToStr: unknown value of player");
  }
}

 

 

 

 

 

./CppReversi/reversisquare.h

 

#ifndef REVERSISQUARE_H
#define REVERSISQUARE_H

namespace ribi {
namespace reversi {

enum class Square { empty, player1, player2 };

} //~namespace reversi
} //~namespace ribi

#endif // REVERSISQUARE_H

 

 

 

 

 

./CppReversi/reversisquare.cpp

 

#include "reversisquare.h"

 

 

 

 

 

./CppReversi/reversiwidget.h

 

#ifndef REVERSIWIDGET_H
#define REVERSIWIDGET_H

#include <string>
#include <vector>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#include <boost/shared_ptr.hpp>
#include "reversifwd.h"
#include "reversiplayer.h"
#include "reversiwinner.h"
#pragma GCC diagnostic pop

namespace ribi {

struct TextCanvas;

namespace reversi {

///Widget is higher level interface of the Reversi Board:
///Widget keeps track of the current player its turn and allows undoing of moves
struct Widget
{
  Widget(const int size = 10);

  //Need deep copies, due to m_board
  Widget(const Widget& other);

  //Need deep copies, due to m_board
  Widget& operator=(const Widget& other);

  bool CanDoMove(const boost::shared_ptr<const Move> move) const noexcept;

  void DoMove(const boost::shared_ptr<const Move> move) noexcept;

  const boost::shared_ptr<const Board> GetBoard() const noexcept { return m_board; }
  const boost::shared_ptr<      Board> GetBoard()       noexcept { return m_board; }

  Player GetCurrentPlayer() const noexcept { return m_current_player; }

  const std::vector<boost::shared_ptr<Move>> GetValidMoves() const noexcept;

  static std::string GetVersion() noexcept;
  static std::vector<std::string> GetVersionHistory() noexcept;
  Winner GetWinner() const noexcept;

  const boost::shared_ptr<TextCanvas> ToTextCanvas() const noexcept;

  void Undo();

  private:
  boost::shared_ptr<Board> m_board;

  ///The player to do a move; the player to control the selector
  Player m_current_player;

  //The undo stack (use std::vector because it is a true STL container)
  //first: the Widget before the Move
  //second: the last Move done in the game
  std::vector<std::pair<boost::shared_ptr<Widget>,boost::shared_ptr<const Move>>> m_undo;

  ///The x coordinat of the selector
  int m_x;

  ///The y coordinat of the selector
  int m_y;

  bool CanDoMove(const int x, const int y) const noexcept;
  bool CanDoMovePass() const noexcept;

  ///Create the delta-x and delta-y to search in the 8 directions
  static const std::vector<std::pair<int,int>> CreateDeltas() noexcept;

  void DoMove(const int x, const int y) noexcept;
  void DoMovePass() noexcept;

  Player GetOtherPlayer() const noexcept;

  //Simply sets a square
  //void Set(const int x, const int y, const int state) noexcept;

  void TogglePlayer();

  #ifndef NDEBUG
  static void Test() noexcept;
  #endif

  friend bool operator==(const Widget& lhs, const Widget& rhs);
};

bool operator==(const Widget& lhs, const Widget& rhs);
bool operator!=(const Widget& lhs, const Widget& rhs);

std::ostream& operator<<(std::ostream& os, const Widget& r);

} //~namespace reversi
} //~namespace ribi

#endif // REVERSIWIDGET_H

 

 

 

 

 

./CppReversi/reversiwidget.cpp

 

#include "reversiwidget.h"

#include <cassert>
#include <stdexcept>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"


#include "reversimove.h"
#include "reversiboard.h"
#include "reversiplayer.h"
#include "testtimer.h"
#include "textcanvas.h"
#include "trace.h"
#pragma GCC diagnostic pop

ribi::reversi::Widget::Widget(const int size)
  : m_board(new Board(size)),
    m_current_player(Player::player1),
    m_undo{},
    m_x{size/2},
    m_y{size/2}
{
  #ifndef NDEBUG
  Test();
  #endif
  assert(size > 0);
  assert(size == m_board->GetSize());

  #ifndef NDEBUG
  const int x = size / 2 - 1;
  const int y = size / 2 - 1;
  assert(m_board->Get(x  ,y  ) == Square::player1);
  assert(m_board->Get(x+1,y  ) == Square::player2);
  assert(m_board->Get(x  ,y+1) == Square::player2);
  assert(m_board->Get(x+1,y+1) == Square::player1);
  #endif
}

ribi::reversi::Widget::Widget(const Widget& other)
  : m_board(boost::shared_ptr<Board>(new Board(*other.m_board))),
    m_current_player(other.m_current_player),
    m_undo(other.m_undo),
    m_x{other.m_x},
    m_y{other.m_y}
{
  assert(m_board);
  assert(*m_board == *other.m_board && "Must be a copy");
  assert( m_board !=  other.m_board && "Must be a deep copy");
  assert(m_current_player == other.m_current_player);
  assert(*this == other && "Must be a copy");
}

ribi::reversi::Widget& ribi::reversi::Widget::operator=(const Widget& other)
{
  m_board = boost::shared_ptr<Board>(new Board(*other.m_board));
  m_current_player = other.m_current_player;
  m_undo = other.m_undo;

  assert(m_board);
  assert(*m_board == *other.m_board && "Must be a copy");
  assert( m_board !=  other.m_board && "Must be a deep copy");
  assert(m_current_player == other.m_current_player);
  assert(*this == other && "Must be a copy");
  return *this;
}

bool ribi::reversi::Widget::CanDoMove(const boost::shared_ptr<const ribi::reversi::Move> move) const noexcept
{
  assert(move);
  if (boost::dynamic_pointer_cast<const ribi::reversi::MovePass>(move))
  {
    //Can always pass for now
    return true;
  }
  const boost::shared_ptr<const ribi::reversi::MovePlacePiece> place {
    boost::dynamic_pointer_cast<const ribi::reversi::MovePlacePiece>(move)
  };
  assert(place);
  assert(move);
  return CanDoMove(place->GetX(),place->GetY());
}

bool ribi::reversi::Widget::CanDoMove(const int x, const int y) const noexcept
{
  return m_board->CanDoMove(x,y,GetCurrentPlayer());
}

void ribi::reversi::Widget::DoMove(const boost::shared_ptr<const ribi::reversi::Move> move) noexcept
{
  #ifndef NDEBUG
  assert(move);
  if(!CanDoMove(move))
  {
    TRACE("ERROR");
    TRACE(*this);
    TRACE(move->ToStr());
    TRACE("ERROR");
  }
  #endif
  assert(CanDoMove(move));

  //Undo
  {
    const boost::shared_ptr<Widget> prev_widget {
      new Widget(*this)
    };
    m_undo.push_back(std::make_pair(prev_widget,move));
    assert(prev_widget->GetCurrentPlayer() == this->GetCurrentPlayer());
  }
  //Actually do the move
  assert(move);
  if (boost::dynamic_pointer_cast<const ribi::reversi::MovePass>(move))
  {
    DoMovePass();
  }
  else
  {
    const boost::shared_ptr<const ribi::reversi::MovePlacePiece> place {
      boost::dynamic_pointer_cast<const ribi::reversi::MovePlacePiece>(move)
    };
    assert(place);
    assert(CanDoMove(place->GetX(),place->GetY()));
    DoMove(place->GetX(),place->GetY());
  }
}

void ribi::reversi::Widget::DoMove(const int x, const int y) noexcept
{
  assert(GetBoard()->CanDoMove(x,y,GetCurrentPlayer()));
  m_board->DoMove(x,y,GetCurrentPlayer());
  TogglePlayer();
}

void ribi::reversi::Widget::DoMovePass() noexcept
{
  TogglePlayer();
}

ribi::reversi::Player ribi::reversi::Widget::GetOtherPlayer() const noexcept
{
  switch (GetCurrentPlayer())
  {
    case Player::player1: return Player::player2;
    case Player::player2: return Player::player1;
    default: assert(!"Should not get here");
  }
  assert(!"Should not get here");
  throw std::logic_error("ribi::reversi::Widget::GetOtherPlayer: invalid player");
}

const std::vector<boost::shared_ptr<ribi::reversi::Move>> ribi::reversi::Widget::GetValidMoves() const noexcept
{
  std::vector<boost::shared_ptr<Move>> moves;
  for (const std::pair<int,int> p: m_board->GetValidMoves(GetCurrentPlayer()))
  {
    const boost::shared_ptr<Move> move {
      new MovePlacePiece(p.first,p.second)
    };
    assert(move);
    moves.push_back(move);
  }
  const boost::shared_ptr<Move> move_pass {
    new MovePass
  };
  moves.push_back(move_pass);
  return moves;
}

std::string ribi::reversi::Widget::GetVersion() noexcept
{
  return "1.1";
}


std::vector<std::string> ribi::reversi::Widget::GetVersionHistory() noexcept
{
  return {
    "2013-12-19: version 1.0: split off from Reversi",
    "2014-02-14: version 1.1: use enum classes, added ToTextCanvas"
  };
}

ribi::reversi::Winner ribi::reversi::Widget::GetWinner() const noexcept
{
  //static_assert(std::is_same<ribi::reversi::Widget::player1,Board::player1>(),"");
  //If both players cannot do moves, count the tiles
  if (GetBoard()->GetValidMoves(GetCurrentPlayer()).empty())
  {
    Board r(*m_board);
    const Player other_player = GetOtherPlayer();
    if (!r.GetValidMoves(other_player).empty())
    {
      const int n_1 { r.Count(Square::player1) };
      const int n_2 { r.Count(Square::player2) };
      if (n_1 > n_2) return Winner::player1;
      if (n_2 > n_1) return Winner::player2;
      assert(n_1 == n_2);
      return Winner::draw;
    }
  }
  return Winner::no_winner;
}

void ribi::reversi::Widget::TogglePlayer()
{
  switch (GetCurrentPlayer())
  {
    case Player::player1: m_current_player = Player::player2; return;
    case Player::player2: m_current_player = Player::player1; return;
    default: assert(!"Should not get here");
  }
}

const boost::shared_ptr<ribi::TextCanvas> ribi::reversi::Widget::ToTextCanvas() const noexcept
{
  const int n_rows = m_board->GetSize();

  if (n_rows == 0)
  {
    return nullptr;
  }

  boost::shared_ptr<TextCanvas> canvas {
    m_board->ToTextCanvas()
  };

  const char c = canvas->GetChar(m_x,m_y);
  char d = ' ';
  switch (c)
  {
    case ' ': d = '.'; break;
    case '.': d = ' '; break;
    case 'O': d = 'o'; break;
    case 'X': d = 'x'; break;
    case 'o': d = 'O'; break;
    case 'x': d = 'X'; break;
  }
  assert(canvas->IsInRange(m_x,m_y));
  canvas->PutChar(m_x,m_y,d);
  return canvas;
}



#ifndef NDEBUG
void ribi::reversi::Widget::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  const TestTimer test_timer(__func__,__FILE__,1.0);
  const bool verbose{false};
  {
    ribi::reversi::Widget r(4);
    assert(r.GetCurrentPlayer() == Player::player1);
    assert(r.GetValidMoves().size() == 5); //4 place moves and one pass
  }
  /*
  if (verbose) { TRACE("Play random games") }
  for (int sz = 4; sz != 6; ++sz)
  {
    ribi::reversi::Widget r(sz);
    while (r.GetValidMoves().size() > 1) //Pass is always allowed
    {
      assert(r.GetWinner() == Winner::no_winner);
      std::vector<boost::shared_ptr<ribi::reversi::Move>> m {
        r.GetValidMoves()
      };
      assert(!m.empty());
      std::random_shuffle(m.begin(),m.end());
      const boost::shared_ptr<ribi::reversi::Move> move = m[0];
      assert(r.CanDoMove(move));
      r.DoMove(move);
    }
  }
  */
  if (verbose) { TRACE("Test copy constructor and operator== and operator!="); }
  {
    const int sz = 4;
    ribi::reversi::Widget r(sz);
    while (r.GetValidMoves().size() > 1) //Pass is always allowed
    {
      assert(r.GetWinner() == Winner::no_winner);
      assert(r == r);
      std::vector<boost::shared_ptr<ribi::reversi::Move>> m {
        r.GetValidMoves()
      };
      assert(!m.empty());
      std::random_shuffle(m.begin(),m.end());
      const boost::shared_ptr<ribi::reversi::Move> move = m[0];
      assert(move);
      assert(r.CanDoMove(move));
      Widget before(r);
      assert(r == before);

      r.DoMove(move);

      assert(before != r);
      assert(before.CanDoMove(move));

      before.DoMove(move);

      assert(before == r);
    }
  }
  if (verbose) { TRACE("Test undo functionality in a single game"); }
  {
    const int sz = 4;
    ribi::reversi::Widget r(sz);
    while (r.GetValidMoves().size() > 1) //Pass is always allowed
    {
      assert(r.GetWinner() == Winner::no_winner);
      std::vector<boost::shared_ptr<ribi::reversi::Move>> m {
        r.GetValidMoves()
      };
      assert(!m.empty());
      std::random_shuffle(m.begin(),m.end());
      const boost::shared_ptr<ribi::reversi::Move> move = m[0];
      assert(move);
      assert(r.CanDoMove(move));
      const Widget before(r);
      assert(before.CanDoMove(move));
      r.DoMove(move);
      assert(before != r);
      r.Undo();
      assert(before.GetCurrentPlayer() == r.GetCurrentPlayer());
      assert(*before.GetBoard() == *r.GetBoard());
      assert(before == r);
      assert(before.CanDoMove(move));
      assert(r.CanDoMove(move));
      r.DoMove(move);
    }
  }
}
#endif

void ribi::reversi::Widget::Undo()
{
  assert(!m_undo.empty());
  this->m_board = (m_undo.back().first)->GetBoard();
  assert(*m_board == *(m_undo.back().first)->GetBoard());
  #ifndef NDEBUG
  if (this->m_current_player == (m_undo.back().first)->GetCurrentPlayer())
  {
    TRACE("ERROR");
    std::clog << "\n" << *this << std::endl;
    TRACE(PlayerToStr(m_current_player));
  }
  #endif
  assert(this->m_current_player != (m_undo.back().first)->GetCurrentPlayer());
  this->m_current_player = (m_undo.back().first)->GetCurrentPlayer();
  m_undo.pop_back();
}

bool ribi::reversi::operator==(const ribi::reversi::Widget& lhs, const ribi::reversi::Widget& rhs)
{
  if (*lhs.GetBoard() != *rhs.GetBoard()) return false;
  if (lhs.GetCurrentPlayer() != rhs.GetCurrentPlayer()) return false;
  if (lhs.m_undo.size() != rhs.m_undo.size()) return false;
  return std::equal(
    std::begin(lhs.m_undo),
    std::end(lhs.m_undo),
    std::begin(rhs.m_undo),
    [](
      const std::pair<boost::shared_ptr<Widget>,boost::shared_ptr<const Move>> lhs,
      const std::pair<boost::shared_ptr<Widget>,boost::shared_ptr<const Move>> rhs
    )
    {
      return *(lhs.first) == *(rhs.first)
        && lhs.second->ToStr() == rhs.second->ToStr();
    }
  );
}

bool ribi::reversi::operator!=(const ribi::reversi::Widget& lhs, const ribi::reversi::Widget& rhs)
{
  return !(lhs == rhs);
}

std::ostream& ribi::reversi::operator<<(std::ostream& os, const ribi::reversi::Widget& r)
{
  os << *r.GetBoard()
    << '\n'
    << PlayerToStr(r.GetCurrentPlayer());

  return os;
}

 

 

 

 

 

./CppReversi/reversiwinner.h

 

#ifndef REVERSIWINNER_H
#define REVERSIWINNER_H

namespace ribi {
namespace reversi {

enum class Winner { no_winner, player1, player2, draw };

} //~namespace reversi
} //~namespace ribi

#endif // REVERSIWINNER_H

 

 

 

 

 

./CppReversi/reversiwinner.cpp

 

#include "reversiwinner.h"

 

 

 

 

 

Go back to Richel Bilderbeek's C++ page.

Go back to Richel Bilderbeek's homepage.

 

Valid XHTML 1.0 Strict

This page has been created by the tool CodeToHtml