314 lines
No EOL
11 KiB
C++
314 lines
No EOL
11 KiB
C++
// © 2025 A.M. Rowsell <amr@frzn.dev>
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
// This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
// defined by the Mozilla Public License, v. 2.0.
|
|
|
|
#include "inc/Board.hpp"
|
|
#include "inc/strFuncs.hpp"
|
|
|
|
/*
|
|
* This is how the chessboard is organized in memory
|
|
* This handy dandy chart should help me from getting totally
|
|
* confused!
|
|
*
|
|
* FILE 0 1 2 3 4 5 6 7
|
|
* 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h'
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+
|
|
* RANK 0 | A1 | B1 | C1 | D1 | E1 | F1 | G1 | H1 | boardGrid[0][0] to boardGrid[0][7]
|
|
* RANK 1 | A2 | B2 | C2 | D2 | E2 | F2 | G2 | H2 | boardGrid[1][0] to boardGrid[1][7]
|
|
* RANK 2 | A3 | B3 | C3 | D3 | E3 | F3 | G3 | H3 | ...
|
|
* RANK 3 | A4 | B4 | C4 | D4 | E4 | F4 | G4 | H4 |
|
|
* RANK 4 | A5 | B5 | C5 | D5 | E5 | F5 | G5 | H5 |
|
|
* RANK 5 | A6 | B6 | C6 | D6 | E6 | F6 | G6 | H6 |
|
|
* RANK 6 | A7 | B7 | C7 | D7 | E7 | F7 | G7 | H7 |
|
|
* RANK 7 | A8 | B8 | C8 | D8 | E8 | F8 | G8 | H8 | boardGrid[7][0] to boardGrid[7][7]
|
|
* +-----+-----+-----+-----+-----+-----+-----+-----+
|
|
*
|
|
*/
|
|
|
|
Board::Board() {
|
|
boardGrid.resize(8);
|
|
for(int i = 0; i < 8; i++)
|
|
boardGrid[i].resize(8);
|
|
}
|
|
|
|
Board::~Board() {
|
|
}
|
|
|
|
void Board::setupInitialPosition() {
|
|
// set up the board grid with smart pointers
|
|
// initialize each square with a Piece or nullptr
|
|
playerTurn = PL_WHITE;
|
|
for(int i = 0; i < 8; i++) {
|
|
switch(i) {
|
|
case 0:
|
|
// white back rank
|
|
boardGrid[i][0] = std::make_unique<Rook>(PIECE_WHITE);
|
|
boardGrid[i][1] = std::make_unique<Knight>(PIECE_WHITE);
|
|
boardGrid[i][2] = std::make_unique<Bishop>(PIECE_WHITE);
|
|
boardGrid[i][3] = std::make_unique<Queen>(PIECE_WHITE);
|
|
boardGrid[i][4] = std::make_unique<King>(PIECE_WHITE);
|
|
boardGrid[i][5] = std::make_unique<Bishop>(PIECE_WHITE);
|
|
boardGrid[i][6] = std::make_unique<Knight>(PIECE_WHITE);
|
|
boardGrid[i][7] = std::make_unique<Rook>(PIECE_WHITE);
|
|
break;
|
|
case 1:
|
|
// white pawn rank
|
|
for(int j = 0; j <= 8; j++)
|
|
boardGrid[i][j] = std::make_unique<Pawn>(PIECE_WHITE);
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
for(int j = 0; j <= 8; j++)
|
|
boardGrid[i][j] = nullptr;
|
|
break;
|
|
case 6:
|
|
// black pawn rank
|
|
for(int j = 0; j <= 8; j++)
|
|
boardGrid[i][j] = std::make_unique<Pawn>(PIECE_BLACK);
|
|
break;
|
|
case 7:
|
|
// black back rank
|
|
boardGrid[i][0] = std::make_unique<Rook>(PIECE_BLACK);
|
|
boardGrid[i][1] = std::make_unique<Knight>(PIECE_BLACK);
|
|
boardGrid[i][2] = std::make_unique<Bishop>(PIECE_BLACK);
|
|
boardGrid[i][3] = std::make_unique<Queen>(PIECE_BLACK);
|
|
boardGrid[i][4] = std::make_unique<King>(PIECE_BLACK);
|
|
boardGrid[i][5] = std::make_unique<Bishop>(PIECE_BLACK);
|
|
boardGrid[i][6] = std::make_unique<Knight>(PIECE_BLACK);
|
|
boardGrid[i][7] = std::make_unique<Rook>(PIECE_BLACK);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
/**
|
|
* Sets entire board to nullptr
|
|
*/
|
|
void Board::clearBoard() {
|
|
for(int i = 0; i < 8; i++) {
|
|
for(int j = 0; j < 8; j++) {
|
|
boardGrid[i][j] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes a FEN string and creates a board to that spec
|
|
* @param strFEN A full FEN string
|
|
* @return Returns -1 on failure, 0 on success
|
|
*/
|
|
|
|
int Board::setupFromFEN(std::string strFEN) {
|
|
std::vector<std::string> splitFEN = split(strFEN, ' ');
|
|
if(splitFEN.size() != 6) // a valid FEN *must* contain 6 fields
|
|
return -1;
|
|
// ====== START OF FIELD 1 ======
|
|
std::vector<std::string> splitField1 = split(splitFEN[0], '/');
|
|
int rank = 0, file = 0;
|
|
int skip = 0;
|
|
Square whiteKing, blackKing;
|
|
bool wKingPlaced = false, bKingPlaced = false;
|
|
for(auto &ranks : splitField1) {
|
|
for(auto &sym : ranks) {
|
|
switch(sym) {
|
|
case 'R':
|
|
// white rook
|
|
boardGrid[rank][file] = std::make_unique<Rook>(PIECE_WHITE);
|
|
file++;
|
|
break;
|
|
case 'r':
|
|
// black rook
|
|
boardGrid[rank][file] = std::make_unique<Rook>(PIECE_BLACK);
|
|
file++;
|
|
break;
|
|
case 'N':
|
|
// white knight
|
|
boardGrid[rank][file] = std::make_unique<Knight>(PIECE_WHITE);
|
|
file++;
|
|
break;
|
|
case 'n':
|
|
// black knight
|
|
boardGrid[rank][file] = std::make_unique<Knight>(PIECE_BLACK);
|
|
file++;
|
|
break;
|
|
case 'B':
|
|
// white bishop
|
|
boardGrid[rank][file] = std::make_unique<Bishop>(PIECE_WHITE);
|
|
file++;
|
|
break;
|
|
case 'b':
|
|
// black bishop
|
|
boardGrid[rank][file] = std::make_unique<Bishop>(PIECE_BLACK);
|
|
file++;
|
|
break;
|
|
case 'Q':
|
|
// white queen
|
|
boardGrid[rank][file] = std::make_unique<Queen>(PIECE_WHITE);
|
|
file++;
|
|
break;
|
|
case 'q':
|
|
// black queen;
|
|
boardGrid[rank][file] = std::make_unique<Queen>(PIECE_BLACK);
|
|
file++;
|
|
break;
|
|
case 'K':
|
|
// white king
|
|
if(wKingPlaced) {
|
|
// invalid FEN, more than 1 white king
|
|
return -1;
|
|
} else {
|
|
boardGrid[rank][file] = std::make_unique<King>(PIECE_WHITE);
|
|
wKingPlaced = true;
|
|
// store king position for later
|
|
whiteKing.file = file;
|
|
whiteKing.rank = rank;
|
|
file++;
|
|
}
|
|
break;
|
|
case 'k':
|
|
// black king
|
|
if(bKingPlaced) {
|
|
// invalid FEN, more than 1 black king
|
|
return -1;
|
|
} else {
|
|
boardGrid[rank][file] = std::make_unique<King>(PIECE_BLACK);
|
|
bKingPlaced = true;
|
|
// store king position for later
|
|
blackKing.file = file;
|
|
blackKing.rank = rank;
|
|
file++;
|
|
}
|
|
break;
|
|
case 'P':
|
|
// white pawn
|
|
boardGrid[rank][file] = std::make_unique<Pawn>(PIECE_WHITE);
|
|
file++;
|
|
break;
|
|
case 'p':
|
|
// black pawn
|
|
boardGrid[rank][file] = std::make_unique<Pawn>(PIECE_BLACK);
|
|
file++;
|
|
break;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8': {
|
|
std::string skipStr(1, sym);
|
|
skip = atoi(skipStr.c_str());
|
|
//skip--; // fix off by 1 error
|
|
while(skip--) {
|
|
boardGrid[rank][file++] = nullptr; // remove reference
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
// invalid character?
|
|
return -1;
|
|
break;
|
|
}
|
|
if(file > 7) {
|
|
file = 0;
|
|
break;
|
|
}
|
|
}
|
|
rank++;
|
|
if(rank > 7)
|
|
break;
|
|
}
|
|
// ======= END OF FIELD 1 ======
|
|
// ======= START OF FIELD 2 ======
|
|
std::string w = "w";
|
|
std::string b = "b";
|
|
if(splitFEN[1] == w)
|
|
playerTurn = PL_WHITE;
|
|
else if(splitFEN[1] == b)
|
|
playerTurn = PL_BLACK;
|
|
else {
|
|
// invalid FEN
|
|
return -1;
|
|
}
|
|
// ====== END OF FIELD 2 ======
|
|
// ====== START OF FIELD 3 ======
|
|
std::string k = "k", K = "K", q = "q", Q = "Q";
|
|
auto &bKing = this->getPieceAt(blackKing);
|
|
auto &wKing = this->getPieceAt(whiteKing);
|
|
if(splitFEN[2] == "-") {
|
|
// nobody can castle, either side
|
|
bKing->setCastleKS(false);
|
|
bKing->setCastleQS(false);
|
|
wKing->setCastleKS(false);
|
|
wKing->setCastleQS(false);
|
|
}
|
|
if(splitFEN[2].find(k) != std::string::npos) {
|
|
// black king can castle kingside
|
|
bKing->setCastleKS(true);
|
|
}
|
|
if(splitFEN[2].find(K) != std::string::npos) {
|
|
// white king can castle kingside
|
|
wKing->setCastleKS(true);
|
|
}
|
|
if(splitFEN[2].find(q) != std::string::npos) {
|
|
// black king can castle queenside
|
|
bKing->setCastleQS(true);
|
|
}
|
|
if(splitFEN[2].find(Q) != std::string::npos) {
|
|
// white king can castle queenside
|
|
wKing->setCastleQS(true);
|
|
}
|
|
// ====== END OF FIELD 3 ======
|
|
// ====== START OF FIELD 4 ======
|
|
// if a pawn has just moved and can be attacked enPassant, it will be listed here
|
|
|
|
|
|
#ifdef DEBUG
|
|
for(auto &ranks : boardGrid) {
|
|
for(auto &files : ranks) {
|
|
if(files)
|
|
std::cout << files->getPieceName() << " ";
|
|
else
|
|
std::cout << "E ";
|
|
}
|
|
std::cout << "\n";
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void Board::nextTurn() {
|
|
playerTurn = (playerTurn == PL_WHITE) ? PL_BLACK : PL_WHITE; // switch to other player's turn
|
|
// do initial checks -- is their king in check? do they have any legal moves?
|
|
// stalemate check - do we really need to call getLegalMoves() on every single Piece?
|
|
}
|
|
|
|
/**
|
|
* This function should only be called after you have confirmed the move is legal!
|
|
* This will execute the move and assume you've already checked it.
|
|
* @param from The Square on which the Piece is departing
|
|
* @param to The Square to which the Piece is moving to
|
|
*/
|
|
void Board::movePiece(Square from, Square to) {
|
|
return;
|
|
}
|
|
|
|
void Board::deserializeBoard(uint64_t incomingBoard) {
|
|
uint8_t boardRows[8];
|
|
for(int i = 0; i < 8; i++)
|
|
boardRows[i] = (incomingBoard >> (8 * i)) & 0xFF;
|
|
// how do we then figure out what has moved?
|
|
return;
|
|
}
|
|
|
|
std::unique_ptr<Piece> &Board::getPieceAt(Square square) {
|
|
return boardGrid[square.rank][square.file];
|
|
} |