Skip to content

Instantly share code, notes, and snippets.

@jrepp
Created June 8, 2018 17:01
Show Gist options
  • Select an option

  • Save jrepp/207276971d98f410f1c8a5a0bcff2d7d to your computer and use it in GitHub Desktop.

Select an option

Save jrepp/207276971d98f410f1c8a5a0bcff2d7d to your computer and use it in GitHub Desktop.
// Checkers.cpp : Defines the entry point for the console application.
//
#include "CheckersGame.h"
#include <iostream>
int main()
{
std::cout << "Welcome to checkers" << endl;
uint numPlayers = 0;
do
{
std::cout << "How many people are playing?" << endl << ">";
std::cin >> numPlayers;
} while (numPlayers > 2);
CheckersGame::createInstance(numPlayers);
CheckersGame* checkers = CheckersGame::instance();
// Ignore first new line for input
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
while (!checkers->isGameComplete())
{
checkers->update();
}
std::cout << "Winning player is " << checkers->getWinningPlayer() << endl;
CheckersGame::destroyInstance();
return 0;
}// Checkers.cpp : Defines the entry point for the console application.
//
#include "CheckersGame.h"
#include <iostream>
int main()
{
std::cout << "Welcome to checkers" << endl;
uint numPlayers = 0;
do
{
std::cout << "How many people are playing?" << endl << ">";
std::cin >> numPlayers;
} while (numPlayers > 2);
CheckersGame::createInstance(numPlayers);
CheckersGame* checkers = CheckersGame::instance();
// Ignore first new line for input
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
while (!checkers->isGameComplete())
{
checkers->update();
}
std::cout << "Winning player is " << checkers->getWinningPlayer() << endl;
CheckersGame::destroyInstance();
return 0;
}
#pragma once
#include <iostream>
#include <vector>
#include "stdafx.h"
// Square board size
#define BOARD_SIZE 8
using namespace std;
class Player;
struct BoardPosition
{
uint row;
uint column;
friend ostream& operator<<(ostream& os, const BoardPosition& pos);
};
// TODO: Use a better data structure to avoid allocations
struct PlayerMove
{
bool isJump = false;
vector<BoardPosition> moves;
};
class CheckersBoard
{
public:
CheckersBoard();
void initialize(Player** players);
void drawBoard();
enum ValidateMove_Reason
{
Validate_Success,
Validate_Invalid,
Validate_InvalidPosition,
Validate_InvalidMove,
Validate_InvalidJump,
};
ValidateMove_Reason validateMove(Player* player, PlayerMove& move);
bool isPositionFree(BoardPosition& position);
bool isPositionOccupied(BoardPosition& position, uint& playerId);
void applyMove(Player* player, PlayerMove& move);
bool isValidPosition(BoardPosition& position);
bool canMovePiece(Player* player, BoardPosition& pos1, BoardPosition& pos2, bool& isJump);
uint getBoardPieces(Player* player, vector<BoardPosition>& pieces);
private:
bool getIntermediatePosition(BoardPosition& pos1, BoardPosition& pos2, BoardPosition& intermediatePos);
void setIdAtPos(int id, BoardPosition& position);
char getDisplayChar(uint playerId);
// Could store half the number of columns since only some squares are being used
int m_board[BOARD_SIZE][BOARD_SIZE];
// Default board definitions
// TODO: Make data-driven instead of hard-coded
const uint PlayerStartingRows[MAX_NUM_PLAYERS] = { 0, 5 };
const uint NumStartingRows = 3;
const int EmptyBoardPosId = -1;
};
#include <iostream>
#include "CheckersBoard.h"
#include "CheckersGame.h"
#include "Player/Player.h"
ostream& operator<<(ostream& os, const BoardPosition& pos)
{
char rowChar = 'A' + pos.row;
os << rowChar << pos.column;
return os;
}
CheckersBoard::CheckersBoard()
{
}
void CheckersBoard::initialize(Player** players)
{
memset(m_board, EmptyBoardPosId, BOARD_SIZE * BOARD_SIZE * sizeof(int));
bool oddColumns = true;
for (uint i = 0; i < MAX_NUM_PLAYERS; ++i)
{
int playerId = EmptyBoardPosId;
Player* player = players[i];
if (player != nullptr)
{
playerId = player->getPlayerId();
}
uint startingRow = PlayerStartingRows[i];
uint lastRow = startingRow + NumStartingRows;
// if (lastRow >= BOARD_SIZE) - ERROR
uint numPieces = 0;
for (; startingRow < lastRow; ++startingRow)
{
uint column = oddColumns ? 1 : 0;
for (; column < BOARD_SIZE; column += 2)
{
m_board[startingRow][column] = playerId;
++numPieces;
}
oddColumns = !oddColumns;
}
if (player)
{
player->setNumPieces(numPieces);
}
}
}
void CheckersBoard::drawBoard()
{
// Top legend
cout << " ";
for (int column = 1; column < BOARD_SIZE + 1; ++column)
{
cout << " " << column;
}
cout << endl;
char row = 'A';
for (int i = 0; i < BOARD_SIZE; ++i)
{
// Row legend
cout << row;
row++;
// Board
for (int j = 0; j < BOARD_SIZE; ++j)
{
cout << '|' << getDisplayChar(m_board[i][j]);
}
cout << "|" << endl;
}
}
char CheckersBoard::getDisplayChar(uint playerId)
{
// Could cache
Player* player = CheckersGame::instance()->getPlayer(playerId);
if (player)
{
return player->getPlayerSymbol();
}
return '.';
}
void CheckersBoard::applyMove(Player* player, PlayerMove& move)
{
uint numMoves = move.moves.size();
BoardPosition initialPos = move.moves[0];
BoardPosition finalPos = move.moves[numMoves - 1];
for (uint i = 0; i < numMoves - 1; ++i)
{
BoardPosition intermediatePos;
if (getIntermediatePosition(move.moves[i], move.moves[i + 1], intermediatePos))
{
if (Player* player = CheckersGame::instance()->getPlayer(m_board[intermediatePos.row][intermediatePos.column]))
{
player->setNumPieces(player->getNumRemainingPieces() - 1);
}
setIdAtPos(EmptyBoardPosId, intermediatePos);
}
}
setIdAtPos(EmptyBoardPosId, initialPos);
setIdAtPos(player->getPlayerId(), finalPos);
}
CheckersBoard::ValidateMove_Reason CheckersBoard::validateMove(Player* player, PlayerMove& move)
{
if (!player)
{
//ERROR
return CheckersBoard::Validate_Invalid;
}
uint numPositions = move.moves.size();
if (numPositions < 2)
{
return CheckersBoard::Validate_Invalid;
}
uint playerId = player->getPlayerId();
BoardPosition pos1 = move.moves[0];
if (!isValidPosition(pos1))
{
return CheckersBoard::Validate_InvalidPosition;
}
if (m_board[pos1.row][pos1.column] != playerId)
{
return CheckersBoard::Validate_InvalidMove;
}
bool isJumpMove = false;
for (uint i = 1; i < numPositions; ++i)
{
BoardPosition pos2 = move.moves[i];
if (!canMovePiece(player, pos1, pos2, isJumpMove))
{
return CheckersBoard::Validate_InvalidMove;
}
pos1 = pos2;
// Not allowed more than one move between positions if not jumping another piece
if (!isJumpMove && i != numPositions - 1)
{
return CheckersBoard::Validate_InvalidJump;
}
}
return CheckersBoard::Validate_Success;
}
bool CheckersBoard::canMovePiece(Player* player, BoardPosition& pos1, BoardPosition& pos2, bool& isJump)
{
if (!isValidPosition(pos1) || !isValidPosition(pos2))
{
return false;
}
uint playerId = -1;
if (isPositionOccupied(pos1, playerId) && player->getPlayerId() != playerId)
{
return false;
}
if (!isPositionFree(pos2))
{
return false;
}
int rowMove = pos2.row - pos1.row;
int columnMove = pos2.column - pos1.column;
uint rowMoveDist = abs(rowMove);
// If expecting a jump, ensure that that the move is 2 spaces
if (isJump && rowMoveDist != 2)
{
return false;
}
if (rowMoveDist != abs(columnMove))
{
return false;
}
// Validate direction for the player
if ((rowMove) * player->getMovementDirection() < 0)
{
return false;
}
// Validate that a different player is in between for jumps
if (rowMoveDist == 2)
{
BoardPosition intermediatePos;
getIntermediatePosition(pos1, pos2, intermediatePos);
if (!isPositionOccupied(intermediatePos, playerId) || playerId == player->getPlayerId())
{
return false;
}
isJump = true;
}
return true;
}
bool CheckersBoard::getIntermediatePosition(BoardPosition& pos1, BoardPosition& pos2, BoardPosition& intermediatePos)
{
int rowMove = pos2.row - pos1.row;
int columnMove = pos2.column - pos1.column;
if (abs(rowMove) == 2 && abs(columnMove) == 2)
{
intermediatePos = { pos1.row + (rowMove / 2), pos1.column + (columnMove / 2) };
return true;
}
return false;
}
bool CheckersBoard::isValidPosition(BoardPosition& position)
{
return position.row >= 0 && position.row < BOARD_SIZE && position.column >= 0 && position.column < BOARD_SIZE;
}
void CheckersBoard::setIdAtPos(int id, BoardPosition& position)
{
m_board[position.row][position.column] = id;
}
bool CheckersBoard::isPositionFree(BoardPosition& position)
{
return m_board[position.row][position.column] == EmptyBoardPosId;
}
bool CheckersBoard::isPositionOccupied(BoardPosition& position, uint& playerId)
{
int player = m_board[position.row][position.column];
if (player != EmptyBoardPosId)
{
playerId = player;
return true;
}
return false;
}
uint CheckersBoard::getBoardPieces(Player* player, vector<BoardPosition>& pieces)
{
uint playerId = player->getPlayerId();
uint numPieces = 0;
for (uint i = 0; i < BOARD_SIZE; ++i)
{
for (uint j = 0; j < BOARD_SIZE; ++j)
{
if (m_board[i][j] == playerId)
{
BoardPosition boardPiece = { i, j };
pieces.push_back(boardPiece);
numPieces++;
}
}
}
return numPieces;
}
#pragma once
#include "stdafx.h"
#include "CheckersBoard.h"
#include "Player\Player.h"
class CheckersGame
{
public:
~CheckersGame();
static CheckersGame* instance() { return s_instance; }
static void createInstance(unsigned int numHumanPlayers);
static void destroyInstance();
void update();
bool isGameComplete();
int getWinningPlayer();
Player* getPlayer(uint playerId);
protected:
CheckersGame(uint numHumanPlayers);
private:
CheckersBoard * m_board;
Player* m_players[MAX_NUM_PLAYERS];
uint m_turn = 0;
uint m_playerTurn = 0;
int m_winningPlayer = -1;
static CheckersGame* s_instance;
};
#include <stdlib.h>
#include <time.h>
#include "CheckersGame.h"
#include "Player\AIPlayer.h"
#include "Player\HumanPlayer.h"
CheckersGame* CheckersGame::s_instance = nullptr;
void CheckersGame::createInstance(uint numHumanPlayers)
{
if (s_instance == nullptr)
{
s_instance = new CheckersGame(numHumanPlayers);
}
else
{
//ERROR
}
}
void CheckersGame::destroyInstance()
{
delete s_instance;
s_instance = nullptr;
}
CheckersGame::CheckersGame(uint numHumanPlayers)
{
uint playerId = 0;
for (; playerId < numHumanPlayers; ++playerId)
{
m_players[playerId] = new HumanPlayer(playerId);
}
for (; playerId < MAX_NUM_PLAYERS; ++playerId)
{
m_players[playerId] = new AIPlayer(playerId);
}
m_board = new CheckersBoard();
m_board->initialize(m_players);
srand((unsigned)time(NULL));
}
CheckersGame::~CheckersGame()
{
delete m_board;
}
void CheckersGame::update()
{
std::cout << "Turn: " << m_turn + 1 << ", Player " << m_players[m_playerTurn]->getPlayerSymbol() << " with " << m_players[m_playerTurn]->getNumRemainingPieces() << " pieces left " << endl;
m_board->drawBoard();
bool hasRemainingMoves = m_players[m_playerTurn]->playTurn(m_board);
++m_turn;
m_playerTurn = m_turn % MAX_NUM_PLAYERS;
if (!hasRemainingMoves)
{
m_winningPlayer = m_playerTurn;
}
// If other player has no pieces remaining, game is complete - Could wait for next update loop
else if (m_players[m_playerTurn]->getNumRemainingPieces() == 0)
{
m_winningPlayer = (m_playerTurn + 1) % MAX_NUM_PLAYERS;
}
}
Player* CheckersGame::getPlayer(uint playerId)
{
if (playerId >= 0 && playerId <= MAX_NUM_PLAYERS)
{
return m_players[playerId];
}
// Invalid ID
return nullptr;
}
bool CheckersGame::isGameComplete()
{
// No remaining moves
return m_winningPlayer != -1;
}
int CheckersGame::getWinningPlayer()
{
return m_winningPlayer;
}
#pragma once
#include <vector>
#include "../stdafx.h"
#include "../CheckersBoard.h"
using namespace std;
class Player
{
public:
Player(uint playerId);
virtual ~Player() {}
uint getPlayerId() { return m_playerId; }
virtual char getPlayerSymbol();
bool playTurn(CheckersBoard* board);
virtual bool getNextMove(CheckersBoard* board, PlayerMove& nextMove) = 0;
void setNumPieces(uint numPieces) { m_numPieces = numPieces; }
uint getNumRemainingPieces() { return m_numPieces; }
enum BoardMovementDirection
{
BoardMoveDir_Down = 1,
BoardMoveDir_Up = -1,
};
BoardMovementDirection getMovementDirection() { return m_boardDirection; }
protected:
void calculateValidMoves(CheckersBoard* board, vector<PlayerMove>& validMoves);
void buildJumpMoveSet(CheckersBoard* board, BoardPosition& position, vector<PlayerMove>& validMoves, PlayerMove& move);
uint m_playerId;
uint m_numPieces = 0;
BoardMovementDirection m_boardDirection;
};
#include "../stdafx.h"
#include "Player.h"
Player::Player(uint playerId)
: m_playerId(playerId)
{
if ((m_playerId % 2) == 0)
{
m_boardDirection = BoardMoveDir_Down;
}
else
{
m_boardDirection = BoardMoveDir_Up;
}
}
char Player::getPlayerSymbol()
{
if ((m_playerId % 2) == 0)
{
return 'X';
}
else
{
return 'O';
}
}
bool Player::playTurn(CheckersBoard* board)
{
PlayerMove nextMove;
if (getNextMove(board, nextMove))
{
board->applyMove(this, nextMove);
return true;
}
return false;
}
void Player::calculateValidMoves(CheckersBoard* board, vector<PlayerMove>& validMoves)
{
vector<BoardPosition> pieces;
board->getBoardPieces(this, pieces);
int rowDir = getMovementDirection();
for (auto piece : pieces)
{
// Possible moves
bool isJump = false;
// Try single move
BoardPosition possibleMove = { piece.row + rowDir, piece.column + 1 };
if (board->canMovePiece(this, piece, possibleMove, isJump))
{
PlayerMove move;
move.moves.push_back(piece);
move.moves.push_back(possibleMove);
validMoves.push_back(move);
}
possibleMove = { piece.row + rowDir, piece.column - 1 };
if (board->canMovePiece(this, piece, possibleMove, isJump))
{
PlayerMove move;
move.moves.push_back(piece);
move.moves.push_back(possibleMove);
validMoves.push_back(move);
}
// Test Jump
PlayerMove jumpMove;
jumpMove.isJump = true;
jumpMove.moves.push_back(piece);
buildJumpMoveSet(board, piece, validMoves, jumpMove);
}
}
void Player::buildJumpMoveSet(CheckersBoard* board, BoardPosition& position, vector<PlayerMove>& validMoves, PlayerMove& move)
{
uint jumpRowPos = position.row + getMovementDirection() * 2;
bool isJump = true;
BoardPosition jumpPos = { jumpRowPos, position.column - 2 };
if (board->canMovePiece(this, position, jumpPos, isJump))
{
// Copy move
PlayerMove newMove = move;
newMove.moves.push_back(jumpPos);
validMoves.push_back(newMove);
buildJumpMoveSet(board, jumpPos, validMoves, newMove);
}
jumpPos = { jumpRowPos, position.column + 2 };
if (board->canMovePiece(this, position, jumpPos, isJump))
{
// Copy move
PlayerMove newMove = move;
newMove.moves.push_back(jumpPos);
validMoves.push_back(newMove);
buildJumpMoveSet(board, jumpPos, validMoves, newMove);
}
}
#pragma once
#include "../stdafx.h"
#include "Player.h"
#include <string>
class HumanPlayer : public Player
{
public:
HumanPlayer(uint playerId);
protected:
virtual bool getNextMove(CheckersBoard* board, PlayerMove& nextMove) override;
private:
void readMove(CheckersBoard* board, PlayerMove& move);
void convertString(std::string stringPos, BoardPosition& pos);
bool m_showHint = true;
};
#include "../stdafx.h"
#include "HumanPlayer.h"
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <locale>
#include <cctype>
HumanPlayer::HumanPlayer(uint playerId)
: Player(playerId)
{
}
bool HumanPlayer::getNextMove(CheckersBoard* board, PlayerMove& nextMove)
{
if (m_showHint)
{
std::cout << "Checker moves can done by entering in a series of positions separated by commas. (e.g. A1,B2) Additional positions can be entered for series of jumps (e.g. A1,C3,E5)" << endl;
std::cout << "To see this again, type help" << endl;
m_showHint = false;
}
bool hasJumpMovesAvailable = false;
vector<PlayerMove> validMoves;
calculateValidMoves(board, validMoves);
for (auto validMove : validMoves)
{
if (validMove.isJump)
{
hasJumpMovesAvailable = true;
break;
}
}
do
{
readMove(board, nextMove);
CheckersBoard::ValidateMove_Reason validateResult = board->validateMove(this, nextMove);
bool jumpUsed = nextMove.isJump == hasJumpMovesAvailable;
if (validateResult == CheckersBoard::Validate_Success && jumpUsed)
{
break;
}
else
{
switch (validateResult)
{
case CheckersBoard::Validate_Invalid:
std::cout << "Invalid data passed in" << endl;
break;
case CheckersBoard::Validate_InvalidPosition:
std::cout << "Invalid board position" << endl;
break;
case CheckersBoard::Validate_InvalidMove:
std::cout << "Move is invalid or blocked" << endl;
break;
case CheckersBoard::Validate_InvalidJump:
std::cout << "Invalid jump specified" << endl;
break;
default:
if (!jumpUsed)
{
std::cout << "Invalid move. Jump move available but not taken" << endl;
}
else
{
std::cout << "Invalid move" << endl;
}
break;
}
nextMove.moves.clear();
}
} while (true);
return true;
}
void HumanPlayer::readMove(CheckersBoard* board, PlayerMove& move)
{
// TODO: Handle non-ascii
std::cout << "> Move: ";
std::string playerInput;
std::getline(std::cin, playerInput);
std::size_t pos = playerInput.find("help");
if (pos != -1)
{
std::cout << "Checker moves can done by entering in a series of positions separated by commas. (e.g. A1,B2) Additional positions can be entered for series of jumps (e.g. A1,C3,E5)" << endl;
}
else
{
std::stringstream ss(playerInput);
std::string token;
while (std::getline(ss, token, ','))
{
BoardPosition pos;
convertString(token, pos);
if (board->isValidPosition(pos))
{
move.moves.push_back(pos);
if (move.moves.size() > 1)
{
int rowMove = move.moves[1].row - move.moves[0].row;
move.isJump = (abs(rowMove) == 2);
}
}
else
{
std::cout << "Invalid position : " << token << endl;
break;
}
}
}
}
string trim(const string& str)
{
size_t first = str.find_first_not_of(' ');
if (string::npos == first)
{
return str;
}
size_t last = str.find_last_not_of(' ');
return str.substr(first, (last - first + 1));
}
void HumanPlayer::convertString(std::string stringPos, BoardPosition& pos)
{
string trimmedString = trim(stringPos);
// Convert string to upper case
std::transform(trimmedString.begin(), trimmedString.end(), trimmedString.begin(),
[](unsigned char c) { return std::toupper(c); });
// Only handling a-z
char rowChar = trimmedString.c_str()[0];
pos.row = rowChar - 'A';
string columnString = trimmedString.substr(1);
pos.column = uint(stoi(columnString)) - 1;
}
#pragma once
#include "../stdafx.h"
#include "Player.h"
class AIPlayer : public Player
{
public:
AIPlayer(uint playerId);
virtual bool getNextMove(CheckersBoard* board, PlayerMove& nextMove) override;
private:
void chooseMove(CheckersBoard* board, vector<PlayerMove>& validMoves, PlayerMove& move);
};
#include "AIPlayer.h"
#include <algorithm>
AIPlayer::AIPlayer(uint playerId)
: Player(playerId)
{
}
bool AIPlayer::getNextMove(CheckersBoard* board, PlayerMove& nextMove)
{
vector<PlayerMove> validMoves;
calculateValidMoves(board, validMoves);
if (validMoves.size() > 0)
{
chooseMove(board, validMoves, nextMove);
// Print out move
std::cout << "AI Move: ";
for (auto& position : nextMove.moves)
{
std::cout << position << ",";
}
std::cout << endl;
return true;
}
return false;
}
void AIPlayer::chooseMove(CheckersBoard* board, vector<PlayerMove>& validMoves, PlayerMove& move)
{
// TODO: AI for evaluating state of the board to pick a good choice
vector<PlayerMove> jumpMoves;
for (auto validMove : validMoves)
{
if (validMove.isJump)
jumpMoves.push_back(validMove);
}
if (jumpMoves.size() > 0)
{
auto moveSizeSort = [](const PlayerMove& move1, const PlayerMove& move2) -> bool
{
return move1.moves.size() > move2.moves.size();
};
sort(jumpMoves.begin(), jumpMoves.end(), moveSizeSort);
move = jumpMoves[0];
}
else
{
move = validMoves[rand() % validMoves.size()];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment