Created
October 10, 2018 07:09
-
-
Save rahwang/69740780cbc3538f7bc419a1ee2a3773 to your computer and use it in GitHub Desktop.
Simple game of checkers!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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