tensor_predictors/dataAnalysis/chess/Rchess/inst/include/SchachHoernchen/Board.cpp

1194 lines
47 KiB
C++

#include <vector>
#include <array>
#include <string>
#include <sstream>
#include <cassert>
#include "types.h"
#include "utils.h"
#include "Move.h"
#include "Board.h"
inline enum piece Board::sqPiece(const u64 sqMask) const {
if (!(sqMask & (_bitBoard[white] | _bitBoard[black]))) {
return static_cast<enum piece>(0);
}
return static_cast<enum piece>(
( pawn * static_cast<bool>(sqMask & _bitBoard[pawn]) )
| (knight * static_cast<bool>(sqMask & _bitBoard[knight]))
| (bishop * static_cast<bool>(sqMask & _bitBoard[bishop]))
| ( rook * static_cast<bool>(sqMask & _bitBoard[rook]) )
| ( queen * static_cast<bool>(sqMask & _bitBoard[queen]) )
| ( king * static_cast<bool>(sqMask & _bitBoard[king]) )
);
}
enum piece Board::piece(Index sq) const {
return sqPiece(bitMask<Square>(sq));
}
enum piece Board::color(Index sq) const {
u64 sqMask = bitMask<Square>(sq);
if (sqMask & _bitBoard[white]) {
return white;
} else if (sqMask & _bitBoard[black]) {
return black;
}
return static_cast<enum piece>(-1);
}
bool Board::castleRight(enum piece color, enum location side) const {
assert((side == KingSide) || (side == QueenSide));
assert((color == white) || (color == black));
if (color == white) {
if (side == KingSide) { return _castle.whiteKingSide; }
else { return _castle.whiteQueenSide; }
} else {
if (side == KingSide) { return _castle.blackKingSide; }
else { return _castle.blackQueenSide; }
}
}
char Board::formatPiece(Index fileIndex, Index rankIndex) const {
assert(fileIndex < 8);
assert(rankIndex < 8);
return formatPiece(8 * rankIndex + fileIndex);
}
char Board::formatPiece(Index sq) const {
assert(sq < 64);
u64 sqMask = bitMask<Square>(sq);
if ((_bitBoard[white] | _bitBoard[black]) & sqMask) {
enum piece type = piece(sq);
switch (type) {
case rook: return (_bitBoard[white] & sqMask) ? 'R' : 'r';
case knight: return (_bitBoard[white] & sqMask) ? 'N' : 'n';
case bishop: return (_bitBoard[white] & sqMask) ? 'B' : 'b';
case queen: return (_bitBoard[white] & sqMask) ? 'Q' : 'q';
case king: return (_bitBoard[white] & sqMask) ? 'K' : 'k';
case pawn: return (_bitBoard[white] & sqMask) ? 'P' : 'p';
default:
return '?';
}
}
return ' ';
}
std::string Board::fen(const bool withCounts) const {
std::ostringstream out;
// Part 1 - Piece Placement
for (Index rankIndex = 0, count = 0; rankIndex < 8; rankIndex++) {
for (Index fileIndex = 0; fileIndex < 8; fileIndex++) {
char c = formatPiece(fileIndex, rankIndex);
if (c == ' ') {
count++;
} else if (count) {
out << static_cast<unsigned>(count) << c;
count = 0;
} else {
out << c;
}
}
if (count) {
out << static_cast<unsigned>(count);
count = 0;
}
out << ((rankIndex < 7) ? '/' : ' ');
}
// Part 2 - Side to Move
out << (isWhiteTurn() ? 'w' : 'b') << ' ';
// Part 3 - Castling Rights
if (!(_castle.whiteKingSide | _castle.whiteQueenSide
| _castle.blackKingSide | _castle.blackQueenSide)) {
out << "- ";
} else {
if (_castle.whiteKingSide) { out << 'K'; };
if (_castle.whiteQueenSide) { out << 'Q'; };
if (_castle.blackKingSide) { out << 'k'; };
if (_castle.blackQueenSide) { out << 'q'; };
out << ' ';
}
// Part 4 - En passant target square
if (_enPassant > 63) {
out << '-';
} else {
out << static_cast<char>('a' + (_enPassant % 8)) // file
<< static_cast<char>('8' - (_enPassant / 8)); // rank
}
if (withCounts) {
// Part 5 - Half Move Clock
out << ' ' << _halfMoveClock << ' ';
// Part 6 - Full Move counter
out << ((_plyCount + 1U) / 2U);
}
return out.str();
}
/**
* The hash lookup consists of
* - 6 pieces for each color and each square -> 6 * 2 * 64 = 768
* - en passant target file (only file) -> 8
* - king and queen side castling rights for each color -> 2 * 2 = 4
* - and the side to move -> 1
* resulting in 781 table entries sum = 781
* the castling rights are index by 776 + [ 0 | 1 | 2 | 3 ]
* and the side to move is just 780
*/
u64 Board::rehash() const {
u64 hash = 0;
// Hash all the pieces on the board
for (enum piece piece : {pawn, rook, knight, bishop, queen, king}) {
for (u64 sq = _bitBoard[piece]; sq; sq &= sq - 1) {
Index sqIndex = bitScanLS(sq);
hash ^= hashTable[hashIndex(this->piece(sqIndex), color(sqIndex), sqIndex)];
}
}
// E.p. target file hash
if (_enPassant < static_cast<Index>(64)) {
hash ^= hashTable[hashIndex(_enPassant)];
}
// Next, the castling rights
if (_castle.whiteKingSide) { hash ^= hashTable[776]; }
if (_castle.whiteQueenSide) { hash ^= hashTable[776 + 1]; }
if (_castle.blackKingSide) { hash ^= hashTable[776 + 2]; }
if (_castle.blackQueenSide) { hash ^= hashTable[776 + 3]; }
// Finally, side to move (for white to move)
if (isWhiteTurn()) {
hash ^= hashTable[780];
}
return hash;
}
void Board::init(const std::string& fen, bool& parseError) {
// first check length (conservative boarders, TODO: tighten)
if ((fen.length() < 10) || (200 < fen.length())) {
parseError = true; return;
}
// split FEN into its parts
std::vector<std::string> parts = split(fen);
// check if there are exactly 6 parts
if (parts.size() != 6) {
parseError = true; return;
}
// (re)set all bit boards to zero
for (Index i = 0; i < 8; i++) {
_bitBoard[i] = static_cast<u64>(0);
}
// Part 1 - Piece Placement
Index rankIndex = 0, fileIndex = 0;
Index whiteKing = 0, blackKing = 0;
for (char c : parts[0]) {
if (c == '/') {
if (fileIndex != 8) { parseError = true; return; }
rankIndex++;
fileIndex = 0;
continue;
} else if ('0' < c && c < '9') {
fileIndex += static_cast<Index>(c - '0');
continue;
}
enum piece piece_color = isUpper(c) ? white : black;
enum piece piece_type;
switch (toUpper(c)) {
case 'P': piece_type = pawn; break;
case 'R': piece_type = rook; break;
case 'B': piece_type = bishop; break;
case 'N': piece_type = knight; break;
case 'K': piece_type = king; break;
case 'Q': piece_type = queen; break;
default: parseError = true; return;
}
if (piece_type == king) {
if (piece_color == white) whiteKing++;
if (piece_color == black) blackKing++;
}
_bitBoard[piece_color] |= bitMask<Square>(fileIndex, rankIndex);
_bitBoard[piece_type] |= bitMask<Square>(fileIndex, rankIndex);
fileIndex++;
}
if ((rankIndex != 7) || (whiteKing != 1) || (blackKing != 1)) {
parseError = true; return;
}
// Part 2 - Side to Move (ply being odd/even)
if (parts[1] == "w") { _plyCount = 1; }
else if (parts[1] == "b") { _plyCount = 2; }
else {
parseError = true; return;
}
// Part 3 - Castling ability
_castle.whiteKingSide = false;
_castle.whiteQueenSide = false;
_castle.blackKingSide = false;
_castle.blackQueenSide = false;
if (parts[2] != "-") {
for (char c : parts[2]) {
switch (c) {
case 'K': _castle.whiteKingSide = true; break;
case 'Q': _castle.whiteQueenSide = true; break;
case 'k': _castle.blackKingSide = true; break;
case 'q': _castle.blackQueenSide = true; break;
default: parseError = true; return;
}
}
}
// Part 4 - En passant target square
if (parts[3] == "-") {
_enPassant = static_cast<Index>(64);
} else {
// _enPassant = UCI::parseSquare(parts[3], parseError);
if (parts[3].length() != 2) {
parseError = true;
return;
}
if (parts[3][0] < 'a' || 'h' < parts[3][0]
|| parts[3][1] < '1' || '8' < parts[3][1]) {
parseError = true;
return;
}
Index fileIndex = static_cast<Index>(parts[3][0] - 'a');
Index rankIndex = static_cast<Index>('8' - parts[3][1]);
_enPassant = 8 * rankIndex + fileIndex;
}
// Part 5 - Half Move Clock
_halfMoveClock = parseUnsigned<unsigned>(parts[4], parseError);
if (parseError) { return; }
// Part 6 - Full Move counter
unsigned moveCounter = parseUnsigned<unsigned>(parts[5], parseError);
if (parseError || (moveCounter < 1)) { parseError = true; return; }
_plyCount += 2U * (moveCounter - 1U);
// Set new hash
_hash = rehash();
}
Move Board::isLegal(const Move move) const {
// First check the move itself (null move check)
if (move.from() == move.to()) { return Move(0); }
// Now, simple check if the move is in the set of legal moves
MoveList allLegalMoves;
moves(allLegalMoves);
for (Move legalMove : allLegalMoves) {
if ((move.from() == legalMove.from())
&& (move.to() == legalMove.to())
&& (move.promote() == legalMove.promote()))
{
return legalMove;
}
}
// move not found in the legal moves -> NOT legal
return Move(0);
}
// NOTE: Assumes a legal move, otherwise undefined behavior!
void Board::make(const Move move) {
assert(this->piece(move.from()) == move.piece());
assert((
(move.piece() == pawn)
&& (move.to() == _enPassant)
&& (move.victim() == pawn)
) || (
this->piece(move.to()) == move.victim()
));
// move piece (and perform capture)
const u64 from = bitMask<Square>(move.from());
const u64 to = bitMask<Square>(move.to());
const enum piece piece = move.piece();
const enum piece color = isWhiteTurn() ? white : black;
const enum piece enemy = (color == white) ? black : white;
bool isCapture;
if (isWhiteTurn()) {
// check if is a capture (for half move clock, not considering e.p.
// capture since irrelevant, half move clock is reset on pawn move)
isCapture = static_cast<bool>(_bitBoard[black] & to);
// move pieces
_bitBoard[white] = (_bitBoard[white] & ~from) | to;
_bitBoard[black] &= ~to;
// finish castling (king already moved -> move rook)
if (piece == king) {
if ((from >> 1) & (to << 1)) { // queen side castling
_bitBoard[rook] ^= (bitMask<Square>(a1) | bitMask<Square>(d1));
_bitBoard[white] ^= (bitMask<Square>(a1) | bitMask<Square>(d1));
_hash ^= hashTable[hashIndex(rook, white, a1)];
_hash ^= hashTable[hashIndex(rook, white, d1)];
}
if ((from << 1) & (to >> 1)) { // king side castling
_bitBoard[rook] ^= (bitMask<Square>(f1) | bitMask<Square>(h1));
_bitBoard[white] ^= (bitMask<Square>(f1) | bitMask<Square>(h1));
_hash ^= hashTable[hashIndex(rook, white, f1)];
_hash ^= hashTable[hashIndex(rook, white, h1)];
}
}
// finish en passant capture (remove captured pawn)
if ((piece == pawn) && (move.to() == _enPassant)) {
_bitBoard[pawn] ^= bitMask<Square>(_enPassant) << 8;
_bitBoard[black] ^= bitMask<Square>(_enPassant) << 8;
_hash ^= hashTable[hashIndex(pawn, black, _enPassant + 8)];
}
} else { // black's turn
// check if is a capture (for half move clock, not considering e.p.
// capture since irrelevant, half move clock is reset on pawn move)
isCapture = static_cast<bool>(_bitBoard[white] & to);
// move pieces
_bitBoard[black] = (_bitBoard[black] & ~from) | to;
_bitBoard[white] &= ~to;
// finish castling (king already moved -> move rook)
if (piece == king) {
if ((from >> 1) & (to << 1)) { // queen side castling
_bitBoard[rook] ^= bitMask<Square>(a8) | bitMask<Square>(d8);
_bitBoard[black] ^= bitMask<Square>(a8) | bitMask<Square>(d8);
_hash ^= hashTable[hashIndex(rook, black, a8)];
_hash ^= hashTable[hashIndex(rook, black, d8)];
}
if ((from << 1) & (to >> 1)) { // king side castling
_bitBoard[rook] ^= bitMask<Square>(f8) | bitMask<Square>(h8);
_bitBoard[black] ^= bitMask<Square>(f8) | bitMask<Square>(h8);
_hash ^= hashTable[hashIndex(rook, black, f8)];
_hash ^= hashTable[hashIndex(rook, black, h8)];
}
}
// finish en passant capture (remove captured pawn)
if ((piece == pawn) && (move.to() == _enPassant)) {
_bitBoard[pawn] ^= bitMask<Square>(_enPassant) >> 8;
_bitBoard[white] ^= bitMask<Square>(_enPassant) >> 8;
_hash ^= hashTable[hashIndex(pawn, white, _enPassant - 8)];
}
}
// update hash
_hash ^= hashTable[hashIndex(piece, color, move.from())];
_hash ^= hashTable[hashIndex(piece, color, move.to())];
if (isCapture) {
_hash ^= hashTable[hashIndex(move.victim(), enemy, move.to())];
}
// and move (including capture) the piece
_bitBoard[rook] &= ~(from | to);
_bitBoard[knight] &= ~(from | to);
_bitBoard[bishop] &= ~(from | to);
_bitBoard[queen] &= ~(from | to);
_bitBoard[king] &= ~(from | to);
_bitBoard[pawn] &= ~(from | to);
_bitBoard[piece] |= to;
// increment half move clock (+1 iff no pawn advance or capture)
_halfMoveClock = (_halfMoveClock + 1) * (piece != pawn) * !isCapture;
// write en passant target square (and remove previous from hash)
if (_enPassant < static_cast<Index>(64)) {
_hash ^= hashTable[hashIndex(_enPassant)];
}
if (piece == pawn) {
u64 epMask = ((from >> 8) & (to << 8)) | ((from << 8) & (to >> 8));
_enPassant = static_cast<Index>(epMask ? bitScanLS(epMask) : 64);
if (_enPassant < static_cast<Index>(64)) {
_hash ^= hashTable[hashIndex(_enPassant)];
}
} else {
_enPassant = static_cast<Index>(64);
}
// handle promotions
if (move.promote()) {
_bitBoard[pawn] &= ~to;
_bitBoard[move.promote()] |= to;
_hash ^= hashTable[hashIndex(pawn, color, move.to())];
_hash ^= hashTable[hashIndex(move.promote(), color, move.to())];
}
// revoke castling rights (rook captures revoke castling rights as well)
if (_castle.whiteKingSide
&& ((from | to) & (bitMask<Square>(e1) | bitMask<Square>(h1)))) {
_castle.whiteKingSide = false;
_hash ^= hashTable[776];
}
if (_castle.whiteQueenSide
&& ((from | to) & (bitMask<Square>(a1) | bitMask<Square>(e1)))) {
_castle.whiteQueenSide = false;
_hash ^= hashTable[776 + 1];
}
if (_castle.blackKingSide
&& ((from | to) & (bitMask<Square>(e8) | bitMask<Square>(h8)))) {
_castle.blackKingSide = false;
_hash ^= hashTable[776 + 2];
}
if (_castle.blackQueenSide
&& ((from | to) & (bitMask<Square>(a8) | bitMask<Square>(e8)))) {
_castle.blackQueenSide = false;
_hash ^= hashTable[776 + 3];
}
// (last operation, isWhiteTurn() depends on this)
_plyCount++;
// and toggle side to move bit string
_hash ^= hashTable[780];
}
// BitMask of all squares the king is NOT allowed to be
//
// @param enemy opponents color
// @param xRayEmpty empty bitboard with king (of moving color) removed
u64 Board::attacks(const enum piece enemy, const u64 empty) const {
u64 attackeMask = 0;
// begin with pawn attacks
u64 attacker = _bitBoard[enemy] & _bitBoard[pawn];
if (enemy == white) {
attackeMask |= shift<RightUp>(attacker);
attackeMask |= shift<LeftUp> (attacker);
} else {
attackeMask |= shift<RightDown>(attacker);
attackeMask |= shift<LeftDown>(attacker);
}
// add knight attacks
attacker = _bitBoard[enemy] & _bitBoard[knight];
for (; attacker; attacker &= attacker - 1) {
attackeMask |= knightMoveLookup[bitScanLS(attacker)];
}
// Add left-right up-down sliding piece attacks (rooks, queens)
attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]);
attackeMask |= fill7dump<Up> (attacker, empty)
| fill7dump<Down> (attacker, empty)
| fill7dump<Left> (attacker, empty)
| fill7dump<Right>(attacker, empty);
// and the diagonal sliding attacks
attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]);
attackeMask |= fill7dump<LeftUp> (attacker, empty)
| fill7dump<LeftDown> (attacker, empty)
| fill7dump<RightUp> (attacker, empty)
| fill7dump<RightDown>(attacker, empty);
// finally, the enemy king
attackeMask |= kingMoveLookup[bitScanLS(_bitBoard[enemy] & _bitBoard[king])];
return attackeMask;
}
u64 Board::pinned(const enum piece enemy, const u64 cKing, const u64 empty) const {
const enum piece color = (enemy == white) ? black : white;
u64 pinMask = 0;
u64 ray, attacker;
// rook & queen sliding attacks
attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]);
ray = fill7dump<Up>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<Down>(attacker, empty);
ray = fill7dump<Down>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<Up>(attacker, empty);
ray = fill7dump<Left>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<Right>(attacker, empty);
ray = fill7dump<Right>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<Left>(attacker, empty);
// bishop & queen sliding attacks
attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]);
ray = fill7dump<LeftUp>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<RightDown>(attacker, empty);
ray = fill7dump<RightDown>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<LeftUp>(attacker, empty);
ray = fill7dump<LeftDown>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<RightUp>(attacker, empty);
ray = fill7dump<RightUp>(cKing, empty);
pinMask |= ray & _bitBoard[color] & fill7dump<LeftDown>(attacker, empty);
return pinMask;
}
u64 Board::checkers(const enum piece enemy, const u64 cKing, const u64 empty) const {
// Initialize checkers with knights giving check
u64 checkers = knightMoveLookup[bitScanLS(cKing)] & _bitBoard[enemy] & _bitBoard[knight];
u64 ray, attacker;
// rook & queen sliding attacks
attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]);
ray = fill7dump<Up>(cKing, empty);
checkers |= ray & attacker;
ray = fill7dump<Down>(cKing, empty);
checkers |= ray & attacker;
ray = fill7dump<Left>(cKing, empty);
checkers |= ray & attacker;
ray = fill7dump<Right>(cKing, empty);
checkers |= ray & attacker;
// bishop & queen sliding attacks
attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]);
ray = fill7dump<LeftUp>(cKing, empty);
checkers |= ray & attacker;
ray = fill7dump<RightDown>(cKing, empty);
checkers |= ray & attacker;
ray = fill7dump<LeftDown>(cKing, empty);
checkers |= ray & attacker;
ray = fill7dump<RightUp>(cKing, empty);
checkers |= ray & attacker;
// Finish checkers by pawns (if the king would be a pawn, he attacks
// check giving enemy pawns)
attacker = _bitBoard[enemy] & _bitBoard[pawn];
if (enemy == black) {
checkers |= shift<RightUp>(cKing) & attacker;
checkers |= shift<LeftUp>(cKing) & attacker;
} else {
checkers |= shift<RightDown>(cKing) & attacker;
checkers |= shift<LeftDown>(cKing) & attacker;
}
return checkers;
}
void Board::moves(MoveList& moveList, bool capturesOnly) const {
moves(moveList, isWhiteTurn() ? white : black, capturesOnly);
}
void Board::moves(MoveList& moveList, const enum piece color,
bool capturesOnly) const
{
assert(!(_bitBoard[white] & _bitBoard[black]));
assert(bitCount(_bitBoard[black] & _bitBoard[king]) == 1);
assert(bitCount(_bitBoard[white] & _bitBoard[king]) == 1);
assert((
_bitBoard[white] | _bitBoard[black]
) == (
_bitBoard[pawn]
^ _bitBoard[rook]
^ _bitBoard[bishop]
^ _bitBoard[knight]
^ _bitBoard[queen]
^ _bitBoard[king]
));
// Get the opponents color
const enum piece enemy = (color == white) ? black : white;
const u64 cKing = _bitBoard[color] & _bitBoard[king];
const Index cKingSq = bitScanLS(cKing);
const u64 occupied = _bitBoard[white] | _bitBoard[black];
const u64 empty = ~occupied;
/****** Compute danger squares for the king (when moving to in check) ******/
// All fields under attack of an enemy piece (or pawn) with a see-through king
u64 dangerMask = attacks(enemy, ~(occupied ^ cKing));
// Restricts possible move target squares (except king movement). Also used
// in check to restrict moves to check evading moves for non-king pieces.
u64 toMask = capturesOnly ? _bitBoard[enemy] : -1;
// Pinned pieces (all pieces from the kings perspective which by see through
// discover enemy sliding pieces in that direction)
u64 pinMask = pinned(enemy, cKing, empty);
// Initialize checkers with none, iff in check compute checkers
u64 checkers = 0;
/***** Determine (Double) Check and check giving pieces (aka checkers) *****/
// If the king is on a danger square he is in check
bool isCheck = static_cast<bool>(cKing & dangerMask);
bool isDoubleCheck = false;
if (isCheck) {
// Compute checkers (bitmask of check giving pieces)
checkers = this->checkers(enemy, cKing, empty);
// Check for double check (by testing for at least two bits in checkers)
isDoubleCheck = static_cast<bool>(checkers & (checkers - 1));
// compute move target square mask
if (!isDoubleCheck) {
toMask &= checkers; // one square
// if check giving piece is a sliding piece, it can be blocked
if (checkers & (_bitBoard[rook] | _bitBoard[bishop] | _bitBoard[queen])) {
u64 attacker = fill7dump<Up> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<Down> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<Left> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<Right> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<LeftUp> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<LeftDown> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<RightUp> (cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
attacker = fill7dump<RightDown>(cKing, empty);
toMask |= attacker * static_cast<bool>(attacker & checkers);
}
}
}
/***************************** Generate moves *****************************/
{ // King Moves
u64 to = kingMoveLookup[cKingSq] &~ (dangerMask | _bitBoard[color]);
// King moves can not be handled with the toMask trick
if (capturesOnly) {
to &= _bitBoard[enemy];
}
for (; to; to &= to - 1) {
moveList.emplace_back(color, king, cKingSq, bitScanLS(to),
sqPiece(to & -to));
}
}
// if in double check, the king must move (-> stop move generation)
if (isDoubleCheck) { return; }
// and castling
if (!isCheck && !capturesOnly) {
// king is not allowed to move through or to an attacked square,
// but the rook is allowed to and might also land on an attacked square
// (at the b file when queen side while castling)
u64 castlingDanger = (dangerMask &~ bitMask<File>(b1)) | occupied;
if (color == white) {
constexpr u64 kingSide = bitMask<Square>(f1) | bitMask<Square>(g1);
constexpr u64 queenSide = bitMask<Square>(b1) | bitMask<Square>(c1)
| bitMask<Square>(d1);
if (_castle.whiteKingSide && !(castlingDanger & kingSide)) {
moveList.emplace_back(white, king, e1, g1, none);
}
if (_castle.whiteQueenSide && !(castlingDanger & queenSide)) {
moveList.emplace_back(white, king, e1, c1, none);
}
} else {
constexpr u64 kingSide = bitMask<Square>(f8) | bitMask<Square>(g8);
constexpr u64 queenSide = bitMask<Square>(b8) | bitMask<Square>(c8)
| bitMask<Square>(d8);
if (_castle.blackKingSide && !(castlingDanger & kingSide)) {
moveList.emplace_back(black, king, e8, g8, none);
}
if (_castle.blackQueenSide && !(castlingDanger & queenSide)) {
moveList.emplace_back(black, king, e8, c8, none);
}
}
}
{ // Rook Moves
u64 rooks = _bitBoard[color] & _bitBoard[rook];
for (; rooks; rooks &= rooks - 1) {
Index fromSq = bitScanLS(rooks);
u64 from = rooks & -rooks;
u64 to = fill7dump<Left> (from, empty)
| fill7dump<Right>(from, empty)
| fill7dump<Up> (from, empty)
| fill7dump<Down> (from, empty);
// check if pinned and restrict `to` accordingly
if (from & pinMask) {
to &= lineMask(cKingSq, fromSq);
}
to &= toMask & ~_bitBoard[color];
for (; to; to &= to - 1) {
moveList.emplace_back(color, rook, fromSq, bitScanLS(to),
sqPiece(to & -to));
}
}
}
{ // Bishop Moves
u64 bishops = _bitBoard[color] & _bitBoard[bishop];
for (; bishops; bishops &= bishops - 1) {
Index fromSq = bitScanLS(bishops);
u64 from = bishops & -bishops;
u64 to = fill7dump<LeftUp> (from, empty)
| fill7dump<RightUp> (from, empty)
| fill7dump<LeftDown> (from, empty)
| fill7dump<RightDown>(from, empty);
// check if pinned and restrict `to` accordingly
if (from & pinMask) {
to &= lineMask(cKingSq, fromSq);
}
to &= toMask & ~_bitBoard[color];
for (; to; to &= to - 1) {
moveList.emplace_back(color, bishop, fromSq, bitScanLS(to),
sqPiece(to & -to));
}
}
}
{ // Queen Moves
u64 queens = _bitBoard[color] & _bitBoard[queen];
for (; queens; queens &= queens - 1) {
Index fromSq = bitScanLS(queens);
u64 from = queens & -queens;
u64 to = fill7dump<Left> (from, empty)
| fill7dump<Right> (from, empty)
| fill7dump<Up> (from, empty)
| fill7dump<Down> (from, empty)
| fill7dump<LeftUp> (from, empty)
| fill7dump<RightUp> (from, empty)
| fill7dump<LeftDown> (from, empty)
| fill7dump<RightDown>(from, empty);
// check if pinned and restrict `to` accordingly
if (from & pinMask) {
to &= lineMask(cKingSq, fromSq);
}
to &= toMask & ~_bitBoard[color];
for (; to; to &= to - 1) {
moveList.emplace_back(color, queen, fromSq, bitScanLS(to),
sqPiece(to & -to));
}
}
}
{ // Knight Moves
u64 knights = _bitBoard[color] & _bitBoard[knight];
for (; knights; knights &= knights - 1) {
u64 from = knights & -knights;
// check if pinned (pinned knights can't move)
if (from & pinMask) { continue; }
Index fromSq = bitScanLS(knights);
u64 to = toMask & knightMoveLookup[fromSq] & ~_bitBoard[color];
for (; to; to &= to - 1) {
moveList.emplace_back(color, knight, fromSq, bitScanLS(to),
sqPiece(to & -to));
}
}
}
{ // Pawn moves
u64 pawns = _bitBoard[color] & _bitBoard[pawn]; // for all pawns
u64 targets = _bitBoard[enemy]; // capture targets
for (; pawns; pawns &= pawns - 1) {
Index fromSq = bitScanLS(pawns);
u64 from = pawns & -pawns;
// Compute pawn push, double pawn push as well as left and right
// capture target squares. Also set a promotion flag if reaching
// the final rank (rank 8 for white and rank 1 for black)
u64 to = 0;
bool isPromotion = false;
if (color == white) {
to = (from >> 8) & empty;
to |= ( (to & bitMask<Rank>(a3)) >> 8) & empty;
to |= ((from &~ bitMask<File>(a1)) >> 9) & targets;
to |= ((from &~ bitMask<File>(h1)) >> 7) & targets;
isPromotion = static_cast<bool>(from & bitMask<Rank>(a7));
} else {
to = (from << 8) & empty;
to |= ( (to & bitMask<Rank>(a6)) << 8) & empty;
to |= ((from &~ bitMask<File>(a1)) << 7) & targets;
to |= ((from &~ bitMask<File>(h1)) << 9) & targets;
isPromotion = static_cast<bool>(from & bitMask<Rank>(a2));
}
to &= toMask;
if (from & pinMask) {
to &= lineMask(cKingSq, fromSq);
}
if (isPromotion) {
for (; to; to &= to - 1) {
Index toSq = bitScanLS(to);
enum piece victim = sqPiece(to & -to);
moveList.emplace_back(color, pawn, fromSq, toSq, victim, rook);
moveList.emplace_back(color, pawn, fromSq, toSq, victim, knight);
moveList.emplace_back(color, pawn, fromSq, toSq, victim, bishop);
moveList.emplace_back(color, pawn, fromSq, toSq, victim, queen);
}
} else {
for (; to; to &= to - 1) {
moveList.emplace_back(color, pawn, fromSq, bitScanLS(to),
sqPiece(to & -to));
}
}
}
// and en passant captures (handled separately, cause of special cases.
// Most of them are simply handled by adding the e.p. target square to
// targets, but it misinterprets some situations)
if (_enPassant < 64) {
u64 to = bitMask<Square>(_enPassant);
u64 target;
pawns = 0;
if (color == white) {
pawns |= shift<LeftDown> (to);
pawns |= shift<RightDown>(to);
target = shift<Down>(to);
} else {
pawns |= shift<LeftUp>(to);
pawns |= shift<RightUp>(to);
target = shift<Up>(to);
}
pawns &= _bitBoard[color] & _bitBoard[pawn];
// now a few situations can occur
// 1. No check
// a. legal e.p. capture
// b. illegal due to discovered check
// 2. In check (case of double check already handled)
// a. e.p. capture resolved check (e.p. target pawn gives check)
// but can still lead to a discovered check
// b. illegal, e.p. does not resolve check
// In ether case, we perform an check test if the move is legal
// which should be OK with the hope that e.p. captures are rare
// enough (does not slows down computation too much^^)
if (!isCheck || (isCheck && (target & checkers))) {
for (; pawns; pawns &= pawns - 1) { // max twice
Index fromSq = bitScanLS(pawns);
u64 from = pawns & -pawns;
// perform reduced check test, through an e.p. capture, only
// sliding pieces could are involved in discovered check (and
// double check is was handled already)
u64 futureOccupied = occupied ^ (from | target | to);
bool discoveredCheck = false;
u64 attacker = _bitBoard[enemy] & (_bitBoard[rook] | _bitBoard[queen]);
discoveredCheck |= static_cast<bool>(attacker & fill7dump<Up> (cKing, ~futureOccupied));
discoveredCheck |= static_cast<bool>(attacker & fill7dump<Down> (cKing, ~futureOccupied));
discoveredCheck |= static_cast<bool>(attacker & fill7dump<Left> (cKing, ~futureOccupied));
discoveredCheck |= static_cast<bool>(attacker & fill7dump<Right>(cKing, ~futureOccupied));
attacker = _bitBoard[enemy] & (_bitBoard[bishop] | _bitBoard[queen]);
discoveredCheck |= static_cast<bool>(attacker & fill7dump<LeftUp> (cKing, ~futureOccupied));
discoveredCheck |= static_cast<bool>(attacker & fill7dump<LeftDown> (cKing, ~futureOccupied));
discoveredCheck |= static_cast<bool>(attacker & fill7dump<RightUp> (cKing, ~futureOccupied));
discoveredCheck |= static_cast<bool>(attacker & fill7dump<RightDown>(cKing, ~futureOccupied));
if (!discoveredCheck) {
moveList.emplace_back(color, pawn, fromSq, _enPassant, pawn);
}
}
}
}
}
}
bool Board::isCheck() const {
const enum piece color = isWhiteTurn() ? white : black;
const enum piece enemy = (color == white) ? black : white;
const u64 cKing = _bitBoard[color] & _bitBoard[king];
u64 empty = ~(_bitBoard[white] | _bitBoard[black]);
return static_cast<bool>(attacks(enemy, empty) & cKing);
}
bool Board::isQuiet() const {
const enum piece color = sideToMove();
const enum piece enemy = static_cast<enum piece>(!color);
const u64 empty = ~(_bitBoard[white] | _bitBoard[black]);
return !static_cast<bool>(attacks(color, empty) & _bitBoard[enemy]);
}
bool Board::isTerminal() const {
// check 50 move rule
if (_halfMoveClock >= 100) {
return true;
}
// check if there are any moves, then the game is NOT over
MoveList moveList;
moves(moveList);
return moveList.empty();
}
bool Board::isInsufficient() const {
switch (bitCount(_bitBoard[white] | _bitBoard[black])) {
case 2: return true;
case 3: return _bitBoard[knight] | _bitBoard[bishop];
case 4:
// Two opposite bishops of the same color (ignoring promoted bishops)
return (
(_bitBoard[bishop] == (_bitBoard[bishop] & bitMask<WhiteSquares>()))
||
(_bitBoard[bishop] == (_bitBoard[bishop] & bitMask<BlackSquares>()))
);
default: break;
};
return false;
}
Score Board::evalMaterial(enum piece color) const {
Score material = 0;
for (enum piece piece : { pawn, rook, knight, bishop, queen }) {
material += bitCount(_bitBoard[color] & _bitBoard[piece]) * pieceValues[piece];
}
return material;
}
Score Board::evalPawns(enum piece color) const {
// Penalties/Bonuses
constexpr Score doubledPenalty = 10;
constexpr Score isolatedPenalty = 20;
constexpr Score backwardsPenalty = 8;
constexpr Score passedBonus = 20;
// Pawn position and structure score
Score score = 0;
// Pawn of color BitBoard
u64 pawns = _bitBoard[color] & _bitBoard[pawn];
// Compute doubled pawns (all pawns behind another pawn)
// For white count less advanced and for black more advanced pawns -> equiv
u64 doubled = pawns & shift<Down>(fill<Down>(pawns));
score -= bitCount(doubled) * doubledPenalty;
// Isolated pawns (pawns without any friendly pawns on adjacent files)
u64 isolated = pawns;
isolated &= shift<Left>( ~fill<File>(pawns)) | bitMask<File>(h1);
isolated &= shift<Right>(~fill<File>(pawns)) | bitMask<File>(a1);
score -= bitCount(isolated) * isolatedPenalty;
// Sum piece square table and evaluate pawn structure
if (color == white) {
for (u64 sq = pawns; sq; sq &= sq - 1) {
Index i = bitScanLS(sq);
score += PSQT[pawn][i];
}
// Backwards pawns (not isolated but behind all adjacent friendly pawns)
u64 backwards = pawns & ~isolated;
backwards &= ~fill<Up>(shift<Left>(pawns) | shift<Right>(pawns));
score -= bitCount(backwards) * backwardsPenalty;
// Passed Pawns (Pawns without enemy pawns to stop it from advancing)
u64 enemy = _bitBoard[black] & _bitBoard[pawn];
u64 passed = pawns & ~fill<Down>(shift<LeftDown>(enemy)
| enemy
| shift<RightDown>(enemy));
for (u64 sq = passed; sq; sq &= sq - 1) {
score += (7 - rankIndex(bitScanLS(sq))) * passedBonus;
}
} else { // color == black
for (u64 sq = pawns; sq; sq &= sq - 1) {
Index i = bitScanLS(sq);
score += PSQT[pawn][63 - i];
}
// Backwards pawns (not isolated but behind all adjacent friendly pawns)
u64 backwards = pawns & ~isolated;
backwards &= ~fill<Down>(shift<Left>(pawns) | shift<Right>(pawns));
score -= bitCount(backwards) * backwardsPenalty;
// Passed Pawns (Pawns without enemy pawns to stop it from advancing)
u64 enemy = _bitBoard[white] & _bitBoard[pawn];
u64 passed = pawns & ~fill<Up>(shift<LeftUp>(enemy)
| enemy
| shift<RightUp>(enemy));
for (u64 sq = passed; sq; sq &= sq - 1) {
score += rankIndex(bitScanLS(sq)) * passedBonus;
}
}
return score;
}
Score Board::evalKingSafety(enum piece color) const {
// Kings position
Index kingSq = (color == white)
? bitScanLS( _bitBoard[white] & _bitBoard[king])
: bitScanLS(bitFlip<Rank>(_bitBoard[black] & _bitBoard[king]));
Score score = PSQT[king][kingSq];
if ((fileIndex(kingSq) < 3) || (4 < fileIndex(kingSq))) { // King is castled
// Pawn shields are the least advanced pawns per file
u64 pawnShield = (color == white)
? _bitBoard[white] & _bitBoard[pawn]
: bitFlip<Rank>(_bitBoard[black] & _bitBoard[pawn]);
pawnShield &= fill7dump<Up>(bitMask<Rank>(a1), ~pawnShield);
// hack to sub-select the three king/queen side border files
Index kingSide = (kingSq < 3) ? 1 : 6; // ether b1 or g1
pawnShield &= fill<File>(kingMoveLookup[kingSide]);
for (u64 mask : { bitMask<File>(a1) | bitMask<File>(h1),
bitMask<File>(b1) | bitMask<File>(g1),
bitMask<File>(c1) | bitMask<File>(f1) }) {
u64 pawn = pawnShield & mask;
if (pawn) {
switch (rankIndex(bitScanLS(pawn))) {
case 6: break; // intact shield
case 5: score -= 10; break; // pawn moved once
default: score -= 20; // moved more than once
}
} else {
score -= 25; // no shield (no pawn)
}
}
} else { // not castled, penalize open files
// Select squares of open files
u64 openFiles = ~fill<File>(_bitBoard[pawn]);
// hack to sub select the kings and its adjacent files
openFiles &= fill<File>(kingMoveLookup[kingSq]);
// Penalty for number of open files
score -= 10 * bitCount(openFiles & bitMask<Rank>(a1));
}
return score;
}
Score Board::evalRooks(enum piece color) const {
constexpr Score rookOpenFileBonus = 15;
constexpr Score rookSemiOpenFileBonus = 10;
u64 rooks = (color == white)
? (_bitBoard[white] & _bitBoard[rook])
: bitFlip<Rank>(_bitBoard[black] & _bitBoard[rook]);
// Open and semi-open files
u64 openFiles = ~fill<File>(_bitBoard[pawn]);
u64 semiOpenFiles = ~fill<File>(_bitBoard[color] & _bitBoard[pawn]);
Score score = 0;
for (u64 sq = rooks; sq; sq &= sq - 1) {
Index sqIndex = bitScanLS(sq);
// Piece square table (accounts for rook on seventh bonus)
score += PSQT[rook][sqIndex];
// Add bonuses for semi-open and open files
if (bitMask<Square>(sqIndex) & openFiles) {
score += rookOpenFileBonus;
} else if (bitMask<Square>(sqIndex) & semiOpenFiles) {
score += rookSemiOpenFileBonus;
}
}
return score;
}
// position fen 3rr1k1/pp3pp1/1qn2np1/8/3p4/PP1R1P2/2P1NQPP/R1B3K1 b - - 0 1
// position fen 1k3b1r/ppqn1p2/2p1r1pp/4P3/8/1PN2NQ1/1PP3PP/1K1RR3 w - - 0 1
// position fen r1bq1rk1/pp2p1bp/2np4/5p2/nP3P2/N1P2N2/2B3PP/R1B1QRK1 w - - 0 4
// position fen r1bq1rk1/pp2p1bp/2np4/5B2/nP3P2/N1P2N2/6PP/R1B1QRK1 b - - 0 4
Score Board::evaluate() const {
constexpr Score maxMaterial = 8 * pieceValues[pawn]
+ 2 * (pieceValues[rook] + pieceValues[knight] + pieceValues[bishop])
+ pieceValues[queen];
// Start score with material values
Score whiteMaterial = evalMaterial(white);
Score blackMaterial = evalMaterial(black);
Score score = whiteMaterial - blackMaterial;
// Add piece square table values of pieces without specialized eval function
// Note: assumes left/right symmetry of the piece square tables
for (enum piece type : { queen, bishop, knight }) {
// White pieces
for (u64 sq = _bitBoard[white] & _bitBoard[type]; sq; sq &= sq - 1) {
score += PSQT[type][bitScanLS(sq)];
}
// and black pieces
for (u64 sq = _bitBoard[black] & _bitBoard[type]; sq; sq &= sq - 1) {
score -= PSQT[type][63 - bitScanLS(sq)];
}
}
// Pawn structure
score += evalPawns(white) - evalPawns(black);
// Rooks
score += evalRooks(white) - evalRooks(black);
// First the white king
if (blackMaterial <= 1200) {
// Endgame:
// No king safety, but more king mobility in the center
score += PSQT[kingEG][bitScanLS(_bitBoard[white] & _bitBoard[king])];
} else {
// Middle Game:
// King safety weighted by opponents material (The less pieces the enemy
// possesses, the less important the king safety. In other words, with
// fewer pieces, its harder to attack.)
score += (5 * blackMaterial * evalKingSafety(white)) / (4 * maxMaterial);
}
// and the same for the black king with opposite sign
if (whiteMaterial <= 1200) {
score -= PSQT[kingEG][63 - bitScanLS(_bitBoard[black] & _bitBoard[king])];
} else {
score -= (5 * whiteMaterial * evalKingSafety(black)) / (4 * maxMaterial);
}
return score;
}
// Static Exchange Evaluation
Score Board::see(const Move move) const {
// Only apply to captures
assert(!!move && move.victim());
// Target square
Index sq = move.to();
u64 to = bitMask<Square>(sq);
u64 from = bitMask<Square>(move.from());
// Set of all empty squares (without initial attack)
u64 empty = from | ~(_bitBoard[white] | _bitBoard[black]);
// Compute all attackers (of the target square)
u64 attacker = (knightMoveLookup[sq] & _bitBoard[knight])
| ( kingMoveLookup[sq] & _bitBoard[king]);
// add sliding pieces
attacker |= fill7dump<Plus> (to, empty) & (_bitBoard[queen] | _bitBoard[rook]);
attacker |= fill7dump<Cross>(to, empty) & (_bitBoard[queen] | _bitBoard[bishop]);
// and pawns
attacker |= _bitBoard[pawn] &
( ((shift<LeftUp> (to) | shift<RightUp> (to)) & _bitBoard[black])
| ((shift<LeftDown>(to) | shift<RightDown>(to)) & _bitBoard[white]));
// Remove initial attacker (perform given move)
attacker &= ~empty;
// Setup swap list (there are max 32 pieces)
Score swapList[32];
// move count in the capture sequence
int ply = 1;
// Setup initial capture (aka move to evaluate)
swapList[0] = pieceValues[move.victim()];
swapList[1] = pieceValues[move.piece()] - swapList[0];
// Side to move piece set
u64 side = _bitBoard[move.color()];
// Perform alternating sequence of captures untill there are no attackers
// of the current side to move left
while (u64 candidate = (attacker & (side = ~side))) {
// Get least valuable attacker piece from candidates
enum piece piece = none;
for (enum piece pieceType : {pawn, knight, bishop, rook, queen, king}) {
// Skip untill the first candidate (garantied to exist)
if ((from = (candidate & _bitBoard[pieceType]))) {
piece = pieceType;
from = from & -from; // pick arbitrary first (TODO: find a better
break; // solution! For now, quick and durty)
}
}
// If a king attempts a capture, check for check
if ((piece == king) && (attacker &~ side)) {
break; // King would move into check -> no pieces left
}
// Append speculative score under the assumption beeing defended
ply++;
swapList[ply] = pieceValues[piece] - swapList[ply - 1];
// perform piece movement
attacker ^= from;
empty ^= from;
// Update attackers with discovered sliding pieces
attacker |= fill7dump<Plus> (to, empty) & (_bitBoard[queen] | _bitBoard[rook]);
attacker |= fill7dump<Cross>(to, empty) & (_bitBoard[queen] | _bitBoard[bishop]);
attacker &= ~empty;
}
// Final SEE evaluation by dropping loosing sub-sequences (allow stand pat)
// Note: Ignore last entry in swapList since the last entry corresponds to
// the piece at the end of the capture sequence (not captures)
while (--ply) {
swapList[ply - 1] = -std::max(-swapList[ply - 1], swapList[ply]);
}
return swapList[0];
}