Skip to content

Instantly share code, notes, and snippets.

@rahwang
Created October 10, 2018 07:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rahwang/69740780cbc3538f7bc419a1ee2a3773 to your computer and use it in GitHub Desktop.
Save rahwang/69740780cbc3538f7bc419a1ee2a3773 to your computer and use it in GitHub Desktop.
Simple game of checkers!
#include "board.h"
Board::Board() {}
char Board::pieceChar(const PieceType &pieceType) const
{
switch(pieceType)
{
case BLANK:
return '.';
case BLACK:
return 'x';
case WHITE:
return 'o';
case BLACK_KING:
return 'X';
case WHITE_KING:
return 'O';
case INVALID:
return ' ';
}
}
bool Board::inBounds(const int &row, const int &col)
{
return (row >= 0 && row < N && col >= 0 && col < N);
}
Board::Board(const int &n)
{
N = n;
// Generate all the initial pieces in order
int num_pieces = (N * N / 4 - N / 2);
std::vector<PieceType> pieces(num_pieces, BLACK);
std::vector<PieceType> white(num_pieces, WHITE);
std::vector<PieceType> blank(N, BLANK);
pieces.insert(pieces.end(), blank.begin(), blank.end());
pieces.insert(pieces.end(), white.begin(), white.end());
int piece_idx = 0;
// Initialize the board cells
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < N; ++j)
{
// If it's a playable cell (the checkerboard pattern!)
// add the initial piece, else make an invalid cell.
if ((i + j) % 2 == 0)
{
cells.push_back(new Cell(pieces[piece_idx++], i, j));
}
else
{
cells.push_back(new Cell(INVALID, i, j));
}
}
}
// Initalize all the board cell neighbors, saving pointers
// to diagonals cells for each cell if in bounds.
// We can essentially treat the board like a graph.
for (int i = 0; i < N; ++i)
{
for (int j = i % 2; j < N; j += 2)
{
Cell *curr = cells[flatIdx(i, j)];
// Set the top left (diagonal) cell
if (inBounds(i + 1, j - 1))
{
curr->TL = getCell(i + 1, j - 1);
}
// Set the top right (diagonal) cell
if (inBounds(i + 1, j + 1))
{
curr->TR = getCell(i + 1, j + 1);
}
// Set the bottom left (diagonal) cell
if (inBounds(i - 1, j - 1))
{
curr->BL = getCell(i - 1, j - 1);
}
// Set the bottom right (diagonal) cell
if (inBounds(i - 1, j + 1))
{
curr->BR = getCell(i - 1, j + 1);
}
}
}
// Initialize piece lists, storing pointers to the initial pieces
for (int i = 0; i < N / 2 - 1; ++i)
{
for (int j = i % 2; j < N; j += 2)
{
black_pieces.insert(getCell(i, j));
}
}
for (int i = N-1; i > N / 2; --i)
{
for (int j = i % 2; j < N; j += 2)
{
white_pieces.insert(getCell(i, j));
}
}
// Generate column guide
col_guide = " ";
for (int i = 0; i < N; ++i) {
col_guide += (char)A_ASCII_VAL + i;
col_guide += " ";
}
col_guide += " \n";
header = "The game has just begun!";
}
int Board::flatIdx(const int &i, const int &j) const
{
return i * N + j;
}
void Board::print() const
{
std::system("clear");
std::cout << header << "\n\n";
std::cout << col_guide;
// print rows
for (int i = N; i > 0; --i)
{
// print row guide
std::string row_guide = std::to_string(i);
std::cout << row_guide << ' ';
// offset even rows by one space
std::string row_string = "";
// print row cells with spaces inbetween
for (int j = 0; j < N; ++j)
{
row_string += pieceChar(cells[flatIdx(i-1, j)]->type);
row_string += " ";
}
// insert row guide
std::cout << row_string << row_guide << "\n";
}
std::cout << col_guide << "\n\n";
}
bool Board::areEnemies(const PieceType &piece1, const PieceType &piece2) const
{
// Since the enum values alternate color, black pieces are odd, and
// the white pieces are even. Make sure neither piece is blank or invalid.
return (piece1 != BLANK) && (piece2 != BLANK)
&& (piece1 != INVALID) && (piece2 != INVALID)
&& (piece1 % 2 != piece2 % 2);
}
bool Board::sameColor(const PieceType &piece1, const PieceType &piece2)
{
// Since the enum values alternate color, black pieces are odd, and
// the white pieces are even. Make sure neither piece is blank or invalid.
return (piece1 != BLANK) && (piece2 != BLANK)
&& (piece1 != INVALID) && (piece2 != INVALID)
&& (piece1 % 2 == piece2 % 2);
}
bool Board::isUpwardCapture(const Cell *src, const Cell *dst) const
{
// If dst is a diagonal jump from src, and the jumped space is
// an opponent piece, then its a valid capture.
return (((src->TL && dst == src->TL->TL)
&& areEnemies(src->type, src->TL->type))
|| ((src->TR && dst == src->TR->TR)
&& areEnemies(src->type, src->TR->type)));
}
bool Board::isDownwardCapture(const Cell *src, const Cell *dst) const
{
// If dst is a diagonal jump from src, and the jumped space is
// an opponent piece, then its a valid capture.
return (((src->BL && dst == src->BL->BL)
&& areEnemies(src->type, src->BL->type))
|| ((src->BR && dst == src->BR->BR)
&& areEnemies(src->type, src->BR->type)));
}
bool Board::hasUpwardCapture(const Cell *src) const
{
// If there's an empty space diagonally past
// an opponent piece, then its a valid capture.
return (((src->TL && src->TL->TL && src->TL->TL->type == BLANK)
&& areEnemies(src->type, src->TL->type))
|| ((src->TR && src->TR->TR && src->TR->TR->type == BLANK)
&& areEnemies(src->type, src->TR->type)));
}
bool Board::hasUpwardBasicMove(const Cell *src) const
{
// If there's an empty space diagonally adjacent,
// then there's a valid basic move.
return ((src->TL && src->TL->type == BLANK)
|| (src->TR && src->TR->type == BLANK));
}
bool Board::hasDownwardBasicMove(const Cell *src) const
{
// If there's an empty space diagonally adjacent,
// then there's a valid basic move.
return ((src->BL && src->BL->type == BLANK)
|| (src->BR && src->BR->type == BLANK));
}
bool Board::hasDownwardCapture(const Cell *src) const
{
// If there's an empty space diagonally past
// an opponent piece, then its a valid capture.
return (((src->BL && src->BL->BL && src->BL->BL->type == BLANK)
&& areEnemies(src->type, src->BL->type))
|| ((src->BR && src->BR->BR && src->BR->BR->type == BLANK)
&& areEnemies(src->type, src->BR->type)));
}
bool Board::hasCapture(const PieceType &player_color) const
{
const std::unordered_set<Cell *> *pieces;
if (player_color == BLACK)
{
pieces = &black_pieces;
}
else
{
pieces = &white_pieces;
}
bool result = false;
for (auto& piece: *pieces)
{
switch(piece->type)
{
case (BLACK):
result |= hasUpwardCapture(piece);
break;
case (WHITE):
result |= hasDownwardCapture(piece);
break;
case (BLACK_KING):
case (WHITE_KING):
result |= hasDownwardCapture(piece) || hasUpwardCapture(piece);
default:
// We'll never get here. Just making g++ happy.
return result;
}
}
return result;
}
bool Board::hasMove(const PieceType &player_color) const
{
const std::unordered_set<Cell *> *pieces;
if (player_color == BLACK)
{
pieces = &black_pieces;
}
else
{
pieces = &white_pieces;
}
if (pieces->empty())
{
return false;
}
bool result = false;
for (auto& piece: *pieces)
{
switch(piece->type)
{
case (BLACK):
result |= hasUpwardCapture(piece) || hasUpwardBasicMove(piece);
break;
case (WHITE):
result |= hasDownwardCapture(piece) || hasDownwardBasicMove(piece);;
break;
case (BLACK_KING):
case (WHITE_KING):
result |= hasDownwardCapture(piece) || hasUpwardCapture(piece)
|| hasDownwardBasicMove(piece) || hasUpwardBasicMove(piece);
default:
// We'll never get here. Just making g++ happy.
return result;
}
}
return result;
}
Cell *Board::getCell(const int &row, const int &col)
{
return cells[flatIdx(row, col)];
}
void Board::update(const PieceType &player_color, const std::vector<Cell *> &move)
{
Cell *src = move[0];
Cell *dst = move.back();
// Update the types of the move source and destination to
// move the src piece on the board.
dst->type = src->type;
src->type = BLANK;
// Get the piece list
// Also check for kinging!
std::unordered_set<Cell *> *our_pieces;
std::unordered_set<Cell *> *enemy_pieces;
if (player_color == BLACK)
{
// If black reaches the last row, king the piece!
dst->type = (dst->row == N - 1) ? BLACK_KING : dst->type;
our_pieces = &black_pieces;
enemy_pieces = &white_pieces;
}
else
{
// If white reaches the last row, king the piece!
dst->type = (dst->row == 0) ? WHITE_KING : dst->type;
our_pieces = &white_pieces;
enemy_pieces = &black_pieces;
}
// Update the piece list
our_pieces->erase(src);
our_pieces->insert(dst);
// If the src and row are more than a move apart, there was a capture!
if (abs(src->row - dst->row) > 1)
{
// Until we reach the end of the move, update board and piece lists
// to reflect captures. Double jump, etc.
for (int i = 0; i < move.size() - 1; ++i)
{
src = move[i];
dst = move[i + 1];
// The captured piece is diagonally between the src and dst cells
// so we can just average their indices to get the captured piece's cell.
int captured_row = (src->row + dst->row) / 2;
int captured_col = (src->col + dst->col) / 2;
Cell *captured = getCell(captured_row, captured_col);
captured->type = BLANK;
enemy_pieces->erase(captured);
}
}
}
bool Board::isBasicMove(Cell *src, Cell *dst) const
{
switch(src->type)
{
case (BLACK):
return (dst == src->TL || dst == src->TR);
case (WHITE):
return (dst == src->BL || dst == src->BR);
case (BLACK_KING):
case (WHITE_KING):
return (dst == src->BL || dst == src->BR || dst == src->TL || dst == src->TR);
default:
// We'll never get here. Just making g++ happy.
return false;
}
return false;
}
bool Board::isCaptureMove(Cell *src, Cell *dst) const
{
switch(src->type)
{
case (BLACK):
return isUpwardCapture(src, dst);
case (WHITE):
return isDownwardCapture(src, dst);
case (BLACK_KING):
case (WHITE_KING):
return isUpwardCapture(src, dst) || isDownwardCapture(src, dst);
default:
// We'll never get here. Just making g++ happy.
return false;
}
return false;
}
bool Board::validateMove(const PieceType &player_color, const std::vector<Cell *> &move)
{
header = "That play was illegal! Try again!";
// Make sure the player is moving a piece of their color
Cell *src = move[0];
if (!sameColor(player_color, src->type))
{
header += " That piece wasn't your color.";
return false;
}
// Make sure the destination cell is unoccupied
Cell *dst = move[1];
if (dst->type != BLANK)
{
header += " The destination space was full.";
return false;
}
// Make sure the move is either a valid basic move or capture
bool is_capture = isCaptureMove(src, dst);
if (!isBasicMove(src, dst) && !is_capture)
{
return false;
}
// If this was a multiple cell move, it must be a capture
if (!is_capture && move.size() > 2)
{
header += " You can only jump multiple spaces if you capture!";
return false;
}
// Keep checking for valid jumps if we have multi-stage move
for (int i = 1; i < move.size() - 1; ++i)
{
src = move[i];
dst = move[i + 1];
// Set the intermediate cell type to the original src piece type
src->type = move[0]->type;
if (!isCaptureMove(src, dst))
{
header += " Your multi-cell play was invalid.";
src->type = BLANK;
return false;
}
src->type = BLANK;
}
// If there's an available capture, the player
// must make a capture play!
if (!is_capture && hasCapture(player_color))
{
header += " You must capture.";
return false;
}
header = "Make the next move!";
return true;
}
#ifndef BOARD_H
#define BOARD_H
#include <iostream>
#include <stdio.h>
#include <string>
#include <unordered_set>
#include <vector>
# define A_ASCII_VAL 97
# define ZERO_ASCII_VAL 48
// All the possible cell states.
// BLANK is an empty, playable cell.
// INVALID is not playable.
enum PieceType {BLANK, BLACK, WHITE, BLACK_KING, WHITE_KING, INVALID};
// All the possible move states
enum MoveStatus {ILLEGAL, LEGAL, CAPTURE};
typedef int CellNum;
// A single cell on the board, storing connections to diagonal neighbors.
struct Cell {
PieceType type;
Cell *TL;
Cell *TR;
Cell *BL;
Cell *BR;
int row;
int col;
Cell(PieceType piece_type, int i, int j) : type(piece_type), TL(nullptr), TR(nullptr),
BL(nullptr), BR(nullptr), row(i), col(j) {}
};
class Board
{
public:
int N; // board dimension
// stores board cell states
std::vector<Cell *> cells;
// white piece locations, stored as idx to cells
std::unordered_set<Cell *> white_pieces;
// black piece locations, stored as idx to cells
std::unordered_set<Cell *> black_pieces;
// message displayed at the board top
std::string header;
// column guide displayed at board sides
std::string col_guide;
// Constructor initializing cells and list of pieces
// Board size if flexible, works for 4-9 (made for debugging)
// These size constraints are because of how input is parsed and
// board is printed. Both assume single character row/col.
Board();
Board(const int &n);
// Determines whether two pieces are of opposing colors
bool areEnemies(const PieceType &piece1, const PieceType &piece2) const;
// Compute flat index into cells from given row (0-7) and column (0-7) indices.
int flatIdx(const int &i, const int &j) const;
// Return a pointer to the cell at given row and index.
Cell *getCell(const int &row, const int &col);
// Determines whether the given player has a basic move
bool hasBasicMove(const Cell *src) const;
// Determines whether the given player has a capture
bool hasCapture(const PieceType &player_color) const;
// Determines whether the given src piece has a basic move,
// moving towards the bottom of the board.
bool hasDownwardBasicMove(const Cell *src) const;
// Determines whether the given src piece has a capture
// moving towards the bottom of the board.
bool hasDownwardCapture(const Cell *src) const;
// Determines whether the player has any valid moves at all.
bool hasMove(const PieceType &player_color) const;
// Determines whether the given src piece has a basic move,
// moving towards the top of the board.
bool hasUpwardBasicMove(const Cell *src) const;
// Determines whether the given src piece has a capture
// moving towards the top of the board.
bool hasUpwardCapture(const Cell *src) const;
// Determines if the given src to dst is a valid basic move (no capture)
// by checking the appropriate graph edges.
bool isBasicMove(Cell *src, Cell *dst) const;
// Determines whether the given row (0-7) and column (0-7) indices are valid.
bool inBounds(const int &row, const int &col);
// Determines if the given src to dst is a valid capture move
// by checking the appropriate graph edges.
bool isCaptureMove(Cell *src, Cell *dst) const;
// Determines whether the given src to dst move is a capture
// moving towards the bottom of the board.
bool isDownwardCapture(const Cell *src, const Cell *dst) const;
// Determines whether the given src to dst move is a capture
// moving towards the top of the board.
bool isUpwardCapture(const Cell *src, const Cell *dst) const;
// Given a piece type, return the char representation for printing
char pieceChar(const PieceType &pieceType) const;
// Pretty print the board state
void print() const;
// Determine whether two pieces are of the same color type (BLANK and INVALID aren't colors!)
bool sameColor(const PieceType &piece1, const PieceType &piece2);
// Given a move, update the state of the board and pieces to reflect the move
void update(const PieceType &player_color, const std::vector<Cell *> &move);
// Given a move, determine whether it's a legal play according to the game rules.
bool validateMove(const PieceType &player_color, const std::vector<Cell *> &move);
};
#endif // BOARD_H
#include "checkersgame.h"
CheckersGame::CheckersGame()
{
board = Board(8);
player1 = Player(BLACK, &board);
player2 = Player(WHITE, &board);
}
CheckersGame::CheckersGame(int N)
{
board = Board(N);
player1 = Player(BLACK, &board);
player2 = Player(WHITE, &board);
}
int CheckersGame::checkWin(const Player &player)
{
// If the player's opponent has no pieces or no moves,
// then this player wins the game!
PieceType opponent = (PieceType)((player.color + 1) % 2);
if (!board.hasMove(opponent))
{
board.header = "The game is over!";
board.print();
std::cout << "Player \'" << board.pieceChar(player.color) << "\' wins !!!\n\n";
return player.color;
}
return 0;
}
int CheckersGame::takeTurn(Player player)
{
std::vector<Cell *> move_results;
player.getMove(move_results);
board.update(player.color, move_results);
return checkWin(player);
}
int CheckersGame::play()
{
// Stores the player number.
int winner = 0;
while (!winner) {
// Player 1 moves
if ((winner = takeTurn(player1))) {break;}
// Player 2 moves
if ((winner = takeTurn(player2))) {break;}
}
return winner;
}
#ifndef CHECKERSGAME_H
#define CHECKERSGAME_H
#include "board.h"
#include "player.h"
class CheckersGame
{
public:
Board board;
Player player1;
Player player2;
// Create a checkers game, optionally change the board size.
// Only works for boards of size 4 - 9, see board constructor.
CheckersGame();
CheckersGame(int N);
// Check if the game is over. If so, return the winner's
// number. Otherwise return 0;
int checkWin(const Player &player);
// For the given player, get their move, update the board
// and check if they win. Return 0 for no win, the winning
// player's number otherwise.
int takeTurn(Player player);
// Do a round of checkers, in which each player moves
int play();
};
#endif // CHECKERSGAME_H
#include "checkersgame.h"
// throw in some tests for now
bool test(bool pass, std::string id)
{
if (!pass)
{
std::cout << "TEST FAILED : " << id << "\n";
}
return pass;
}
// Just for ease of testing, a string to move converter
std::vector<Cell *> move(std::string move_string, Board &b)
{
std::vector<Cell *> move_vec;
Player player(WHITE, &b);
player.parseInput(move_string, move_vec);
return move_vec;
}
// Also for testing, string -> actual board update
void doMove(std::string move_string, PieceType color, Board &b)
{
std::vector<Cell *> move_results = move(move_string, b);
b.update(color, move_results);
}
bool testParseInput()
{
Board b = Board(4);
Player player(WHITE, &b);
bool status = true;
std::vector<Cell *> moves;
status &= test(player.parseInput("a1 a1", moves) == true, "valid input 1");
status &= test(player.parseInput("a1 a1 a1", moves) == true, "valid input 2");
status &= test(player.parseInput("aa a1", moves) == false, "invalid input 1");
status &= test(player.parseInput("11 a1", moves) == false, "invalid input 2");
status &= test(player.parseInput("a1aa1", moves) == false, "invalid input 3");
status &= test(player.parseInput("a1 a11", moves) == false, "invalid input 4");
status &= test(player.parseInput("a1", moves) == false, "invalid input 5");
return status;
}
bool testBoardInit()
{
Board b = Board(4);
bool status = true;
// test board cell initialization
status &= test(b.getCell(0, 0)->type == BLACK, "init cell 0");
status &= test(b.getCell(0, 2)->type == BLACK, "init cell 1");
status &= test(b.getCell(1, 1)->type == BLANK, "init cell 2");
status &= test(b.getCell(1, 3)->type == BLANK, "init cell 3");
status &= test(b.getCell(2, 0)->type == BLANK, "init cell 4");
status &= test(b.getCell(2, 2)->type == BLANK, "init cell 5");
status &= test(b.getCell(3, 1)->type == WHITE, "init cell 6");
status &= test(b.getCell(3, 3)->type == WHITE, "init cell 7");
// test cell neighbor initialization
status &= test(b.cells[0]->TL == nullptr, "out of bounds 1");
status &= test(b.cells[0]->BL == nullptr, "out of bounds 2");
status &= test(b.cells[15]->TR == nullptr, "out of bounds 3");
status &= test(b.cells[15]->BR == nullptr, "out of bounds 4");
status &= test(b.cells[0]->TR == b.cells[5], "TR set correctly");
status &= test(b.cells[2]->TL == b.cells[5], "TL set correctly");
status &= test(b.cells[13]->BR == b.cells[10], "BR set correctly");
status &= test(b.cells[15]->BL == b.cells[10], "BL set correctly");
// test piece list initialization
status &= test(b.black_pieces.size() == 2, "black pieces num correct");
status &= test(b.black_pieces.find(b.cells[0]) != b.black_pieces.end(), "black piece 1 correct");
status &= test(b.black_pieces.find(b.cells[2]) != b.black_pieces.end(), "black piece 2 correct");
status &= test(b.white_pieces.size() == 2, "white pieces num correct");
status &= test(b.white_pieces.find(b.cells[13]) != b.white_pieces.end(), "white piece 1 correct");
status &= test(b.white_pieces.find(b.cells[15]) != b.white_pieces.end(), "white piece 2 correct");
return status;
}
bool testUpdateBoard()
{
Board b = Board(4);
bool status = true;
// Check a basic move
doMove("a1 b2", BLACK, b);
status &= test(b.getCell(0, 0)->type == BLANK, "update board basic 1");
status &= test(b.getCell(1, 1)->type == BLACK, "update board basic 2");
status &= test(b.black_pieces.find(b.getCell(0, 0)) == b.black_pieces.end(), "update piecelist after basic move 1");
status &= test(b.black_pieces.find(b.getCell(1, 1)) != b.black_pieces.end(), "update piecelist after basic move 2");
status &= test(b.black_pieces.size() == 2, "update piecelist after basic move 3");
// Check a capture
doMove("d4 c3", WHITE, b);
doMove("b2 d4", BLACK, b);
status &= test(b.getCell(2, 2)->type == BLANK, "update board after capture");
status &= test(b.white_pieces.find(b.getCell(2, 2)) == b.white_pieces.end(), "update piecelist after capture 1");
status &= test(b.white_pieces.size() == 1, "update piecelist after capture 2");
return status;
}
bool testValidateMove()
{
Board b = Board(4);
bool status = true;
// Check basic moves
status &= test(b.validateMove(BLACK, move("c1 d2", b)) == true, "basic move 1");
doMove("c1 d2", BLACK, b);
status &= test(b.validateMove(WHITE, move("b4 a3", b)) == true, "basic move 2");
doMove("b4 a3", WHITE, b);
// Check all the invalid move types
status &= test(b.validateMove(BLACK, move("a3 b2", b)) == false, "wrong color piece");
status &= test(b.validateMove(WHITE, move("d2 d3", b)) == false, "out of bounds 1");
status &= test(b.validateMove(WHITE, move("d2 e3", b)) == false, "out of bounds 2");
status &= test(b.validateMove(WHITE, move("a3 b4", b)) == false, "wrong direction 1");
status &= test(b.validateMove(WHITE, move("d2 c1", b)) == false, "wrong direction 2");
status &= test(b.validateMove(BLACK, move("a3 c1", b)) == false, "jump without capture");
status &= test(b.validateMove(WHITE, move("a3 b2", b)) == true, "basic move 3");
doMove("a3 b2", WHITE, b);
status &= test(b.validateMove(BLACK, move("d2 c3", b)) == false, "must capture");
status &= test(b.validateMove(BLACK, move("a1 c3", b)) == true, "make capture");
// Check king movement
b.getCell(1, 1)->type = WHITE_KING;
status &= test(b.validateMove(WHITE, move("b2 a3", b)) == true, "king move");
// Check double jump
b = Board(8);
doMove("e3 d4", BLACK, b);
doMove("d6 c5", WHITE, b);
doMove("d2 e3", BLACK, b);
doMove("c7 d6", WHITE, b);
doMove("g3 h4", BLACK, b);
doMove("c5 b4", WHITE, b);
doMove("e3 d4", BLACK, b);
status &= test(b.validateMove(BLACK, move("c3 a5 c7", b)) == true, "make double capture");
// Check king triple double (last jump backwards)
b.getCell(2, 2)->type = BLACK_KING;
status &= test(b.validateMove(BLACK, move("c3 a5 c7 e5", b)) == true, "make king triple capture");
return status;
}
bool testCheckWin()
{
CheckersGame game = CheckersGame(4);
bool status = true;
// Check player out of pieces win condition
game.board.black_pieces.clear();
status &= test(game.checkWin(game.player2) == WHITE, "check win, player1 out of pieces");
game = CheckersGame(4);
game.board.white_pieces.clear();
status &= test(game.checkWin(game.player1) == BLACK, "check win, player2 out of pieces");
// Check player out of moves win condition
game = CheckersGame(4);
Board &b = game.board;
b.black_pieces = {b.getCell(0, 2)};
doMove("c1 a3", BLACK, b);
status &= test(game.checkWin(game.player2) == WHITE, "check win, player1 out of moves");
game = CheckersGame(4);
b = game.board;
b.white_pieces.erase(b.getCell(3, 3));
doMove("b4 d2", WHITE, b);
status &= test(game.checkWin(game.player1) == BLACK, "check win, player2 out of moves");
return status;
}
bool testAll()
{
return testBoardInit()
&& testParseInput()
&& testUpdateBoard()
&& testValidateMove()
&& testCheckWin();
}
int main(int argc, char *argv[])
{
if (!testAll())
{
// Tests failing! Oh no!
std::cout << "\nNo checkers until tests are passing!\n\n";
return -1;
}
CheckersGame game = CheckersGame();
int winner = game.play();
return winner;
}
#include "board.h"
#include "player.h"
Player::Player() {}
Player::Player(PieceType player_color, Board *board) : color(player_color), board(board) {}
bool Player::parseInput(std::string input, std::vector<Cell *> &result)
{
board->header = "Your play input was malformed! Try again!";
// Enforce length requirements
// Should have at least two moves + space seperation (5 chars)
// with optionally any number of addition space + move (3 chars)
int len = input.length();
if (len < 5 || ((len - 2) % 3) != 0)
{
return false;
}
int src_col = (int)input[0] - A_ASCII_VAL;
int src_row = (int)input[1] - ZERO_ASCII_VAL - 1;
// If valid, add src move to move list
if (!board->inBounds(src_row, src_col))
{
return false;
}
result.push_back(board->getCell(src_row, src_col));
// Loop over the rest of the input, adding valid moves
int input_idx = 2;
while (input_idx < len)
{
char space = input[input_idx++];
int dst_col = (int)input[input_idx++] - A_ASCII_VAL;
int dst_row = (int)input[input_idx++] - ZERO_ASCII_VAL - 1;
// If the input is invalid, empty the moves list and return
if (space != ' ' || !board->inBounds(dst_row, dst_col))
{
result.clear();
return false;
}
result.push_back(board->getCell(dst_row, dst_col));
}
return true;
}
void Player::getMove(std::vector<Cell *> &moves)
{
std::string input;
bool valid_play = false;
while (!valid_play) {
board->print();
std::cout << "Player \'" << board->pieceChar(color) << "\'>";
// Get player input
input = "";
std::getline(std::cin, input);
if (parseInput(input, moves))
{
valid_play = board->validateMove(color, moves);
}
}
}
#ifndef PLAYER_H
#define PLAYER_H
#include "board.h"
class Player
{
public:
PieceType color;
Board *board;
// std::vector<CellNum> &pieces;
Player();
Player(PieceType player_color, Board *board);
// Given a player move input string, parse and store in result
// Return true for valid move, false for invalid.
bool parseInput(std::string input, std::vector<Cell *> &result);
// Get input from the player and populate result vector of moves,
// moves specify play in the format src then any number of dst cells
void getMove(std::vector<Cell *> &moves);
};
#endif // PLAYER_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment