1194 lines
47 KiB
C++
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];
|
|
}
|