Go back to Richel Bilderbeek's homepage.

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

 

 

 

 

 

(C++) ConnectThree

 

ConnectThree is a class for a ConnectThree game.

 

 

 

 

 

connectthree.h

 

//---------------------------------------------------------------------------
/*
ConnectThree. A connect-three class.
Copyright (C) 2010 Richel Bilderbeek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
//From http://www.richelbilderbeek.nl/CppConnectThree.htm
//---------------------------------------------------------------------------
#ifndef CONNECTTHREE_H
#define CONNECTTHREE_H
//---------------------------------------------------------------------------
#include <bitset>
#include <string>
#include <vector>
//---------------------------------------------------------------------------
#include <boost/checked_delete.hpp>
#include <boost/tuple/tuple.hpp>
//---------------------------------------------------------------------------
struct ConnectThree
{
  typedef boost::tuple<int,int,int> Move;
  typedef std::vector<Move> Moves;
  enum { no_player = -1 };
  enum { player1   =  0 };
  enum { player2   =  1 };
  enum { player3   =  2 };
  enum { draw      =  3 };

  ConnectThree(
    const int n_cols = 16,
    const int n_rows = 12);

  bool CanDoMove(const int x, const int y) const;
  void DoMove(const int x, const int y);
  void DoMove(const Move& p);
  int GetActivePlayer() const { return m_player; }
  int GetCols() const;
  int GetRows() const;
  int GetSquare(const int x, const int y) const { return m_area[x][y]; }
  static const std::string GetVersion();
  static const std::vector<std::string> GetVersionHistory();
  int GetWinner() const;
  bool IsInvalidMove(const Move& p) const;
  const Move SuggestMove(const std::bitset<3>& is_player_human) const;
  void Restart();

private:
  ~ConnectThree() {}
  friend void boost::checked_delete<>(ConnectThree*);

  //X-Y-ordered 2D std::vector
  std::vector<std::vector<int> > m_area;
  int m_player;

  bool CanDoMove(const Move& p) const;
  const Move CheckOneOther(const std::bitset<3>& is_player_human) const;
  const Move CheckTwoDiagonally() const;
  const Move CheckTwoHorizontalOwn() const;
  const Move CheckTwoOther(const std::bitset<3>& is_player_human) const;
  const Move CheckTwoVerticalOwn() const;
  const Move CreateInvalidMove() const;
  const Moves GetAllPossibleMoves() const;
  int GetNextPlayer() const;
  int GetNextPlayer(const int player) const;
  static double GetRandomUniform();
  const Moves GetTwoHorizontalOtherMoves() const;
  const Moves GetTwoVerticalOtherMoves() const;
  const Move MakeRandomMove() const;

};
//---------------------------------------------------------------------------
#endif // CONNECTTHREE_H

 

 

 

 

 

connectthree.cpp

 

//---------------------------------------------------------------------------
/*
ConnectThree. A connect-three class.
Copyright (C) 2010 Richel Bilderbeek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
//From http://www.richelbilderbeek.nl/CppConnectThree.htm
//---------------------------------------------------------------------------
#include <algorithm>
#include <ctime>
//---------------------------------------------------------------------------
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
//---------------------------------------------------------------------------
#include "connectthree.h"
//---------------------------------------------------------------------------
//Enable debugging
#undef NDEBUG
#include <cassert>
//---------------------------------------------------------------------------
ConnectThree::ConnectThree(
  const int n_cols,
  const int n_rows)
  : m_area(n_cols, std::vector<int>(n_rows,no_player)),
    m_player(ConnectThree::player1)
{
  Restart();
  //assert(m_is_player_human.size() == 3);
  assert(player1 == 0);
  assert(player2 == 1);
  assert(player3 == 2);
  assert(GetCols() == n_cols);
  assert(GetRows() == n_rows);
}
//---------------------------------------------------------------------------
bool ConnectThree::CanDoMove(const int x, const int y) const
{
  return (
       x >= 0
    && x <  GetCols()
    && y >= 0
    && y <  GetCols()
    && m_area[x][y] == no_player);
}
//---------------------------------------------------------------------------
bool ConnectThree::CanDoMove(const Move& p) const
{
  return CanDoMove(
    boost::tuples::get<0>(p),
    boost::tuples::get<1>(p));
}
//---------------------------------------------------------------------------
void ConnectThree::DoMove(const int x, const int y)
{
  assert(
    (CreateInvalidMove().get<0>() == x
      && CreateInvalidMove().get<1>() == y)
    || CanDoMove(x,y));
  if (CreateInvalidMove().get<0>() == x
    && CreateInvalidMove().get<1>() == y)
  {
    return;
  }
  m_area[x][y] = m_player;
  m_player = GetNextPlayer();
}
//---------------------------------------------------------------------------
void ConnectThree::DoMove(const Move& p)
{
  DoMove(
    boost::tuples::get<0>(p),
    boost::tuples::get<1>(p));
}
//---------------------------------------------------------------------------
const std::string ConnectThree::GetVersion()
{
  return "1.1";
}
//---------------------------------------------------------------------------
const std::vector<std::string> ConnectThree::GetVersionHistory()
{
  std::vector<std::string> v;
  v.push_back("2010-12-28: version 0.1: initial seperation of game logic from GUI");
  v.push_back("2011-01-09: version 0.2: converted square values to enum constant, fixed small constructor bug");
  v.push_back("2011-01-11: version 1.0: added that the game can end in a draw. First tested and debugged version");
  v.push_back("2011-04-19: version 1.1: added Restart method, removed m_is_player_human");
  return v;
}
//---------------------------------------------------------------------------
///GetWinner returns the index of the winner.
int ConnectThree::GetWinner() const
{
  const int n_rows = GetRows();
  for (int y=0; y!=n_rows; ++y)
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols; ++x)
    {
      if (m_area[x][y] == no_player) continue;
      //Horizontal
      if (x + 2 < n_cols
        && m_area[x  ][y] == m_area[x+1][y]
        && m_area[x+1][y] == m_area[x+2][y])
      {
        return m_area[x][y];
      }
      //Vertical
      if (y + 2 < n_rows
        && m_area[x][y  ] == m_area[x][y+1]
        && m_area[x][y+1] == m_area[x][y+2])
      {
        return m_area[x][y];
      }
    }
  }
  //Check for draw
  {
    const Move m1 = MakeRandomMove();
    const Move m2 = CreateInvalidMove();
    if ( m1.get<0>() == m2.get<0>()
      && m1.get<1>() == m2.get<1>()
      && m1.get<2>() == m2.get<2>())
    {
      return draw;
    }
  }
  return no_player;
}
//---------------------------------------------------------------------------
//bool ConnectThree::IsComputerTurn() const
//{
//  return !IsHuman(GetActivePlayer());
//}
//---------------------------------------------------------------------------
bool ConnectThree::IsInvalidMove(const Move& p) const
{
  const Move q = CreateInvalidMove();
  return
       p.get<0>() == q.get<0>()
    && p.get<1>() == q.get<1>()
    && p.get<2>() == q.get<2>();
}
//---------------------------------------------------------------------------
///SuggestMove suggests a good move. If the game is a draw,
///it returns an invalid move.
const ConnectThree::Move ConnectThree::SuggestMove(const std::bitset<3>& is_player_human) const
{
  if (CanDoMove(CheckTwoHorizontalOwn())) return CheckTwoHorizontalOwn();
  if (CanDoMove(CheckTwoVerticalOwn()  )) return CheckTwoVerticalOwn();
  if (CanDoMove(CheckTwoOther(is_player_human))) return CheckTwoOther(is_player_human);
  if (CanDoMove(CheckTwoDiagonally()   )) return CheckTwoDiagonally();
  if (CanDoMove(CheckOneOther(is_player_human)        )) return CheckOneOther(is_player_human);
  return MakeRandomMove();
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::CheckTwoHorizontalOwn() const
{
  const int n_rows = GetRows();
  for (int y=0; y!=n_rows; ++y)
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols-1; ++x) //-1 to prevent out of range
    {
      //Two consequtive selfs
      if (m_area[x][y] == m_player && m_area[x+1][y] == m_player)
      {
        if (x >= 1)
        {
          if (m_area[x-1][y] == no_player)
          {
            const Move p(x-1,y);
            assert(CanDoMove(p));
            return p;
          }
        }
        if (x < n_cols-2 && m_area[x+2][y] == no_player)
        {
          const Move p(x+2,y);
          assert(CanDoMove(p));
          return p;
        }
      }
      //Perhaps a gap?
      if (x < n_cols-2)
      {
        if (m_area[x][y] == m_player && m_area[x+1][y] == no_player && m_area[x+2][y] == m_player)
        {
          const Move p(x+1,y);
          assert(CanDoMove(p));
          return p;
        }
      }
    }
  }
  return CreateInvalidMove();
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::CheckTwoVerticalOwn() const
{
  const int n_rows = GetRows();
  for (int y=0; y!=n_rows-1; ++y) //-1 to prevent out of range
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols; ++x)
    {
      //Two consequtive selfs?
      if (m_area[x][y] == m_player && m_area[x][y+1] == m_player)
      {
        if (y >= 1)
        {
          if (m_area[x][y-1] == no_player)
          {
            const Move p(x,y-1);
            assert(CanDoMove(p));
            return p;
          }
        }
        if (y < n_rows-2)
        {
          if (m_area[x][y+2] == no_player)
          {
            const Move p(x,y+2);
            assert(CanDoMove(p));
            return p;
          }
        }
      }
      //Perhaps a gap?
      if (y < n_rows-2)
      {
        if (m_area[x][y] == m_player && m_area[x][y+1] == no_player && m_area[x][y+2] == m_player)
        {
          const Move p(x,y+1);
          assert(CanDoMove(p));
          return p;
        }
      }
    }
  }
  return CreateInvalidMove();
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::CheckTwoOther(const std::bitset<3>& is_player_human) const
{
  const Moves moves(GetAllPossibleMoves());

  const int nMoves = moves.size();
  if (nMoves==0) return CreateInvalidMove();
  {
    //Get anti-human moves
    Moves v;
    BOOST_FOREACH(const Move& m, moves)
    {
      assert(CanDoMove(m));
      //Player is human
      if (is_player_human[boost::tuples::get<2>(m)])
        v.push_back(m);
    }
    //If there are anti-player moves, choose one at random
    if (!v.empty())
    {
      const Move m = v[std::rand() % v.size()];
      assert(CanDoMove(m));
      return
        boost::tuples::make_tuple(
          boost::tuples::get<0>(m),
          boost::tuples::get<1>(m),
          boost::tuples::get<2>(m));
    }
  }
  {
    //Get moves anti-next-player
    const int next_player_index = GetNextPlayer();
    Moves v;
    BOOST_FOREACH(const Move& m, moves)
    {
      assert(CanDoMove(m));
      if (boost::tuples::get<2>(m) == next_player_index)
        v.push_back(m);
    }
    //If there are anti-next-player moves, choose one at random
    if (!v.empty())
    {
      const Move m = v[std::rand() % v.size()];
      assert(CanDoMove(m));
      return
        boost::tuples::make_tuple(
          boost::tuples::get<0>(m),
          boost::tuples::get<1>(m),
          boost::tuples::get<2>(m));
    }
  }
  //Choose a move at random
  {
    const Move m = moves[std::rand() % moves.size()];
    assert(CanDoMove(m));
    return
      boost::tuples::make_tuple(
        boost::tuples::get<0>(m),
        boost::tuples::get<1>(m),
        boost::tuples::get<2>(m));
  }
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::CreateInvalidMove() const
{
  ConnectThree::Move p(-1,-1,ConnectThree::no_player);
  assert(!CanDoMove(p));
  return p;
}
//---------------------------------------------------------------------------
///GetAllPossibleMoves returns all possible moves.
///* boost::get<0>: x coordinat
///* boost::get<1>: y coordinat
///* boost::get<2>: player that would dislike this move
const ConnectThree::Moves
  ConnectThree::GetAllPossibleMoves() const
{
  ConnectThree::Moves v(GetTwoHorizontalOtherMoves());
  const ConnectThree::Moves w(GetTwoVerticalOtherMoves());
  std::copy(w.begin(),w.end(),std::back_inserter(v));
  return v;
}
//---------------------------------------------------------------------------
const ConnectThree::Moves ConnectThree::GetTwoHorizontalOtherMoves() const
{
  const int n_rows = GetRows();
  Moves moves;
  for (int y=0; y!=n_rows; ++y)
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols-1; ++x) //-1 to prevent out of range
    {
      //Check consequtive
      if (m_area[x][y]!=no_player && m_area[x][y] == m_area[x+1][y])
      {
        //Check A X B
        if (x > 0 && m_area[x-1][y] == no_player)
        {
          const Move m
            = boost::tuples::make_tuple(x-1,y,m_area[x][y]);
          assert(CanDoMove(m));
          moves.push_back(m);
        }
        //Check X B C
        if (x < n_cols-2 && m_area[x+2][y] == no_player)
        {
          const Move m
            = boost::tuples::make_tuple(x+2,y,m_area[x][y]);
          assert(CanDoMove(m));
          moves.push_back(m);
        }
      }
      //Check gap, also X B C
      if (m_area[x][y] != no_player && x + 2 < n_cols && m_area[x+1][y] == no_player && m_area[x][y] == m_area[x+2][y])
      {
        const Move m
          = boost::tuples::make_tuple(x+1,y,m_area[x][y]);
        assert(CanDoMove(m));
        moves.push_back(m);
      }

    }
  }
  return moves;
}
//---------------------------------------------------------------------------
//A X B C (x is focus of for loop)
const ConnectThree::Moves ConnectThree::GetTwoVerticalOtherMoves() const
{
  const int n_rows = GetRows();
  ConnectThree::Moves v;

  for (int y=0; y!=n_rows-1; ++y) //-1 to prevent out of range
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols; ++x)
    {
      //Check consequtive
      if (m_area[x][y] != no_player && m_area[x][y] == m_area[x][y+1])
      {
        //Check A X B
        if (y > 0 && m_area[x][y-1] == no_player)
        {
          const Move m = boost::tuples::make_tuple(x,y-1,m_area[x][y]);
          assert(CanDoMove(m));
          v.push_back(m);
        }
        //Check X B C
        if (y < n_rows-2 && m_area[x][y+2] == no_player)
        {
          const Move m = boost::tuples::make_tuple(x,y+2,m_area[x][y]);
          assert(CanDoMove(m));
          v.push_back(m);
        }
      }
      //Check gap, also X B C
      if (m_area[x][y] != no_player && y < n_rows && m_area[x][y+1] == no_player && m_area[x][y] == m_area[x][y+2])
      {
        const Move m = boost::tuples::make_tuple(x,y+1,m_area[x][y]);
        assert(CanDoMove(m));
        v.push_back(m);
      }
    }
  }
  return v;
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::CheckTwoDiagonally() const
{
  ConnectThree::Moves v;

  const int n_rows = GetRows();
  for (int y=0; y!=n_rows-1; ++y) //-1 To prevent out of range
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols-1; ++x) //-1 to prevent out of range
    {
      if (m_area[x][y] == m_player && m_area[x+1][y+1] == m_player)
      {
        if (m_area[x+1][y] == no_player)
        {
          const Move m = boost::tuples::make_tuple(x+1,y,m_area[x][y]);
          assert(CanDoMove(m));
          v.push_back(m);
        }
        if (m_area[x][y+1] == no_player)
        {
          const Move m = boost::tuples::make_tuple(x,y+1,m_area[x][y]);
          assert(CanDoMove(m));
          v.push_back(m);
        }
      }
    }
  }
  if (v.empty()) return CreateInvalidMove();
  const Move m = v[std::rand() % v.size()];
  assert(CanDoMove(m));
  return m;
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::CheckOneOther(const std::bitset<3>& is_player_human) const
{
  ConnectThree::Moves v;

  const int n_rows = GetRows();

  for (int y=0; y!=n_rows; ++y)
  {
    const int n_cols = GetCols();
    for (int x=0; x!=n_cols; ++x)
    {
      if (m_area[x][y] != no_player)
      {
        if (y >= 1 && m_area[x][y-1] == no_player)
        {
          const Move m = boost::tuples::make_tuple(x,y-1,m_area[x][y]);
          assert(CanDoMove(m));
          v.push_back(m);
        }
        if (y < n_rows-1)
        {
          if (m_area[x][y+1] == no_player)
          {
            const Move m = boost::tuples::make_tuple(x,y+1,m_area[x][y]);
            assert(CanDoMove(m));
            v.push_back(m);
          }
        }
        if (x >= 1)
        {
          if (m_area[x-1][y] == no_player)
          {
            const Move m = boost::tuples::make_tuple(x-1,y,m_area[x][y]);
            assert(CanDoMove(m));
            v.push_back(m);
          }
        }
        if (x < n_cols-1)
        {
          if (m_area[x+1][y] == no_player)
          {
            const Move m = boost::tuples::make_tuple(x+1,y,m_area[x][y]);
            assert(CanDoMove(m));
            v.push_back(m);
          }
        }
      }
    }
  }
  if (v.empty()) return CreateInvalidMove();

  {
    //Get anti-human moves
    Moves w;
    BOOST_FOREACH(const Move& m, v)
    {
      assert(CanDoMove(m));
      if (is_player_human[boost::tuples::get<2>(m)])
        w.push_back(m);
    }
    //If there are anti-player moves, choose one at random
    if (!w.empty())
    {
      const Move m = w[std::rand() % w.size()]; //ex-bug ('w.size()' was 'v.size()')
      assert(CanDoMove(m));
      return
        boost::tuples::make_tuple(
          boost::tuples::get<0>(m),
          boost::tuples::get<1>(m),
          boost::tuples::get<2>(m));
    }
  }

  {
    //Get moves anti-next-player
    const int next_player_index = GetNextPlayer();
    Moves w;
    BOOST_FOREACH(const Move& m, v)
    {
      assert(CanDoMove(m));
      if (boost::tuples::get<2>(m) == next_player_index)
        w.push_back(m);
    }
    //If there are anti-next-player moves, choose one at random
    if (!w.empty())
    {
      const Move m = w[std::rand() % w.size()];
      assert(CanDoMove(m));
      return
        boost::tuples::make_tuple(
          boost::tuples::get<0>(m),
          boost::tuples::get<1>(m),
          boost::tuples::get<2>(m));
    }
  }
  //Choose a move at random
  {
    const Move m = v[std::rand() % v.size()];
    assert(CanDoMove(m));
    return
      boost::tuples::make_tuple(
        boost::tuples::get<0>(m),
        boost::tuples::get<1>(m),
        boost::tuples::get<2>(m));
  }
}
//---------------------------------------------------------------------------
const ConnectThree::Move ConnectThree::MakeRandomMove() const
{
  std::vector<boost::tuple<int,int,int> > v;
  const int n_cols = GetCols();
  const int n_rows = GetRows();

  for (int y=0; y!=n_rows; ++y)
  {
    for (int x=0; x!=n_cols; ++x)
    {
      if (this->GetSquare(x,y) == no_player)
      {
        v.push_back(boost::tuples::make_tuple(
          x,
          y,
          static_cast<int>(no_player)));
      }
    }
  }
  if (v.empty())
  {
    return this->CreateInvalidMove();
  }
  const int index = std::rand() % v.size();
  return v[index];
}
//---------------------------------------------------------------------------
int ConnectThree::GetCols() const
{
  return m_area.size();
}
//---------------------------------------------------------------------------
int ConnectThree::GetNextPlayer(const int player) const
{
  assert(player!=no_player);
  switch (player)
  {
    case player1: return player2;
    case player2: return player3;
    case player3: return player1;
  }
  assert(!"Should not get here");
  return no_player;
}
//---------------------------------------------------------------------------
int ConnectThree::GetNextPlayer() const
{
  return GetNextPlayer(m_player);
}
//---------------------------------------------------------------------------
//From http://www.richelbilderbeek.nl/CppGetRandomUniform.htm
double ConnectThree::GetRandomUniform()
{
  return static_cast<double>(std::rand())/static_cast<double>(RAND_MAX);
}
//---------------------------------------------------------------------------
int ConnectThree::GetRows() const
{
  assert(!m_area.empty());
  return m_area[0].size();
}
//---------------------------------------------------------------------------
void ConnectThree::Restart()
{
  m_area = std::vector<std::vector<int> >(GetCols(),
    std::vector<int>(GetRows(),no_player));
  m_player = ConnectThree::player1;
}
//---------------------------------------------------------------------------

 

 

 

 

 

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