From 7577399a0b8571d596f4745847117029d0800843 Mon Sep 17 00:00:00 2001 From: ianfab Date: Fri, 22 Jun 2018 21:16:16 +0200 Subject: [PATCH] Initial development for fairy chess variants Generalize code to be able to easily configure new variants with different pieces and rules. Add a few initial variants: - Chess - Makruk - ASEAN - Ai-Wok - Shatranj - Amazon - Hoppel-Poppel --- .gitignore | 3 + src/Makefile | 2 +- src/bitbase.cpp | 10 ++-- src/bitboard.cpp | 188 +++++++++++++++++++++++++++++++++++++----------- src/bitboard.h | 22 +++--- src/endgame.cpp | 12 ++-- src/evaluate.cpp | 17 +++-- src/main.cpp | 3 + src/movegen.cpp | 96 +++++++++++-------------- src/pawns.cpp | 6 +- src/position.cpp | 162 +++++++++++++++++++++-------------------- src/position.h | 73 +++++++++++++++---- src/psqt.cpp | 12 ++- src/search.cpp | 10 ++-- src/syzygy/tbprobe.cpp | 2 +- src/thread.cpp | 2 +- src/types.h | 84 ++++++++++++++++------ src/uci.cpp | 19 ++--- src/uci.h | 5 +- src/ucioption.cpp | 17 ++++- src/variant.cpp | 136 ++++++++++++++++++++++++++++++++++ src/variant.h | 60 +++++++++++++++ 22 files changed, 667 insertions(+), 274 deletions(-) create mode 100644 .gitignore create mode 100644 src/variant.cpp create mode 100644 src/variant.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..417fc40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +stockfish +*.o +.depend diff --git a/src/Makefile b/src/Makefile index 34264e7..862bdb5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -38,7 +38,7 @@ PGOBENCH = ./$(EXE) bench ### Object files OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \ - search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o + search.o thread.o timeman.o tt.o uci.o ucioption.o variant.o syzygy/tbprobe.o ### Establish the operating system name KERNEL = $(shell uname -s) diff --git a/src/bitbase.cpp b/src/bitbase.cpp index 6004828..5ebf444 100644 --- a/src/bitbase.cpp +++ b/src/bitbase.cpp @@ -117,7 +117,7 @@ namespace { if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 || ksq[WHITE] == psq || ksq[BLACK] == psq - || (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK]))) + || (us == WHITE && (PseudoAttacks[WHITE][PAWN][psq] & ksq[BLACK]))) result = INVALID; // Immediate win if a pawn can be promoted without getting captured @@ -125,13 +125,13 @@ namespace { && rank_of(psq) == RANK_7 && ksq[us] != psq + NORTH && ( distance(ksq[~us], psq + NORTH) > 1 - || (PseudoAttacks[KING][ksq[us]] & (psq + NORTH)))) + || (PseudoAttacks[us][KING][ksq[us]] & (psq + NORTH)))) result = WIN; // Immediate draw if it is a stalemate or a king captures undefended pawn else if ( us == BLACK - && ( !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq])) - || (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]]))) + && ( !(PseudoAttacks[us][KING][ksq[us]] & ~(PseudoAttacks[us][KING][ksq[~us]] | PseudoAttacks[~us][PAWN][psq])) + || (PseudoAttacks[us][KING][ksq[us]] & psq & ~PseudoAttacks[us][KING][ksq[~us]]))) result = DRAW; // Position will be classified later @@ -157,7 +157,7 @@ namespace { constexpr Result Bad = (Us == WHITE ? DRAW : WIN); Result r = INVALID; - Bitboard b = PseudoAttacks[KING][ksq[Us]]; + Bitboard b = PseudoAttacks[Us][KING][ksq[Us]]; while (b) r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)] diff --git a/src/bitboard.cpp b/src/bitboard.cpp index b19d401..ae617c8 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -37,8 +37,10 @@ Bitboard DistanceRingBB[SQUARE_NB][8]; Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB]; Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; -Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; +Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Magic RookMagics[SQUARE_NB]; Magic BishopMagics[SQUARE_NB]; @@ -58,6 +60,24 @@ namespace { u = ((u >> 4) + u) & 0x0F0FU; return (u * 0x0101U) >> 8; } + + Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied, int maxDist = 7) { + + Bitboard attack = 0; + + for (int i = 0; directions[i]; ++i) + for (Square s = sq + directions[i]; + is_ok(s) && distance(s, s - directions[i]) == 1 && distance(s, sq) <= maxDist; + s += directions[i]) + { + attack |= s; + + if (occupied & s) + break; + } + + return attack; + } } @@ -119,43 +139,148 @@ void Bitboards::init() { DistanceRingBB[s1][SquareDistance[s1][s2] - 1] |= s2; } - int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } }; + // Piece moves + Direction RookDirections[5] = { NORTH, EAST, SOUTH, WEST }; + Direction BishopDirections[5] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; + + init_magics(RookTable, RookMagics, RookDirections); + init_magics(BishopTable, BishopMagics, BishopDirections); + + int stepsCapture[][13] = { + {}, // NO_PIECE_TYPE + { 7, 9 }, // pawn + { -17, -15, -10, -6, 6, 10, 15, 17 }, // knight + {}, // bishop + {}, // rook + {}, // queen + { -9, -7, 7, 9 }, // fers/met + { -18, -14, 14, 18 }, // alfil + { -9, -7, 7, 8, 9 }, // silver/khon + { -17, -15, -10, -9, -7, -6, 6, 7, 9, 10, 15, 17 }, // aiwok + { -17, -15, -10, -6, 6, 10, 15, 17 }, // amazon + {}, // knibis + { -17, -15, -10, -6, 6, 10, 15, 17 }, // biskni + { -9, -8, -7, -1, 1, 7, 8, 9 } // king + }; + int stepsQuiet[][13] = { + {}, // NO_PIECE_TYPE + { 8 }, // pawn + { -17, -15, -10, -6, 6, 10, 15, 17 }, // knight + {}, // bishop + {}, // rook + {}, // queen + { -9, -7, 7, 9 }, // fers/met + { -18, -14, 14, 18 }, // alfil + { -9, -7, 7, 8, 9 }, // silver/khon + { -17, -15, -10, -9, -7, -6, 6, 7, 9, 10, 15, 17 }, // aiwok + { -17, -15, -10, -6, 6, 10, 15, 17 }, // amazon + { -17, -15, -10, -6, 6, 10, 15, 17 }, // knibis + {}, // biskni + { -9, -8, -7, -1, 1, 7, 8, 9 } // king + }; + Direction sliderCapture[][9] = { + {}, // NO_PIECE_TYPE + {}, // pawn + {}, // knight + { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // bishop + { NORTH, EAST, SOUTH, WEST }, // rook + { NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // queen + {}, // fers/met + {}, // alfil + {}, // silver/khon + { NORTH, EAST, SOUTH, WEST }, // aiwok + { NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // amazon + { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // knibis + {}, // biskni + {} // king + }; + Direction sliderQuiet[][9] = { + {}, // NO_PIECE_TYPE + {}, // pawn + {}, // knight + { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // bishop + { NORTH, EAST, SOUTH, WEST }, // rook + { NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // queen + {}, // fers/met + {}, // alfil + {}, // silver/khon + { NORTH, EAST, SOUTH, WEST }, // aiwok + { NORTH, EAST, SOUTH, WEST, NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // amazon + {}, // knibis + { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }, // biskni + {} // king + }; + int sliderDistCapture[] = { + 0, // NO_PIECE_TYPE + 0, // pawn + 0, // knight + 7, // bishop + 7, // rook + 7, // queen + 0, // fers/met + 0, // alfil + 0, // silver/khon + 7, // aiwok + 7, // amazon + 7, // knibis + 0, // biskni + 0 // king + }; + int sliderDistQuiet[] = { + 0, // NO_PIECE_TYPE + 0, // pawn + 0, // knight + 7, // bishop + 7, // rook + 7, // queen + 0, // fers/met + 0, // alfil + 0, // silver/khon + 7, // aiwok + 7, // amazon + 0, // knibis + 7, // biskni + 0 // king + }; for (Color c = WHITE; c <= BLACK; ++c) - for (PieceType pt : { PAWN, KNIGHT, KING }) + for (PieceType pt = PAWN; pt <= KING; ++pt) for (Square s = SQ_A1; s <= SQ_H8; ++s) - for (int i = 0; steps[pt][i]; ++i) + { + for (int i = 0; stepsCapture[pt][i]; ++i) { - Square to = s + Direction(c == WHITE ? steps[pt][i] : -steps[pt][i]); + Square to = s + Direction(c == WHITE ? stepsCapture[pt][i] : -stepsCapture[pt][i]); - if (is_ok(to) && distance(s, to) < 3) + if (is_ok(to) && distance(s, to) < 4) { - if (pt == PAWN) - PawnAttacks[c][s] |= to; - else - PseudoAttacks[pt][s] |= to; + PseudoAttacks[c][pt][s] |= to; + LeaperAttacks[c][pt][s] |= to; } } + for (int i = 0; stepsQuiet[pt][i]; ++i) + { + Square to = s + Direction(c == WHITE ? stepsQuiet[pt][i] : -stepsQuiet[pt][i]); - Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST }; - Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; - - init_magics(RookTable, RookMagics, RookDirections); - init_magics(BishopTable, BishopMagics, BishopDirections); + if (is_ok(to) && distance(s, to) < 4) + { + PseudoMoves[c][pt][s] |= to; + LeaperMoves[c][pt][s] |= to; + } + } + PseudoAttacks[c][pt][s] |= sliding_attack(sliderCapture[pt], s, 0, sliderDistCapture[pt]); + PseudoMoves[c][pt][s] |= sliding_attack(sliderQuiet[pt], s, 0, sliderDistQuiet[pt]); + } for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { - PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); - PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); - for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) { - if (!(PseudoAttacks[pt][s1] & s2)) + if (!(PseudoAttacks[WHITE][pt][s1] & s2)) continue; - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = attacks_bb(pt, s1, SquareBB[s2]) & attacks_bb(pt, s2, SquareBB[s1]); + LineBB[s1][s2] = (attacks_bb(WHITE, pt, s1, 0) & attacks_bb(WHITE, pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = attacks_bb(WHITE, pt, s1, SquareBB[s2]) & attacks_bb(WHITE, pt, s2, SquareBB[s1]); } } } @@ -163,25 +288,6 @@ void Bitboards::init() { namespace { - Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) { - - Bitboard attack = 0; - - for (int i = 0; i < 4; ++i) - for (Square s = sq + directions[i]; - is_ok(s) && distance(s, s - directions[i]) == 1; - s += directions[i]) - { - attack |= s; - - if (occupied & s) - break; - } - - return attack; - } - - // init_magics() computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we diff --git a/src/bitboard.h b/src/bitboard.h index 7ae1eff..0691e54 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -73,8 +73,10 @@ extern Bitboard DistanceRingBB[SQUARE_NB][8]; extern Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB]; extern Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; extern Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; -extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; +extern Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; /// Magic holds all magic bitboards relevant data for a single square @@ -267,21 +269,17 @@ template<> inline int distance(Square x, Square y) { return distance(rank_ template inline Bitboard attacks_bb(Square s, Bitboard occupied) { + assert(Pt == BISHOP || Pt == ROOK); const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s]; return m.attacks[m.index(occupied)]; } -inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { - - assert(pt != PAWN); +inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) { + return LeaperAttacks[c][pt][s] | (PseudoAttacks[c][pt][s] & (attacks_bb(s, occupied) | attacks_bb(s, occupied))); +} - switch (pt) - { - case BISHOP: return attacks_bb(s, occupied); - case ROOK : return attacks_bb< ROOK>(s, occupied); - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[pt][s]; - } +inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) { + return LeaperMoves[c][pt][s] | (PseudoMoves[c][pt][s] & (attacks_bb(s, occupied) | attacks_bb(s, occupied))); } diff --git a/src/endgame.cpp b/src/endgame.cpp index 3e01259..3cd5e5b 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -403,8 +403,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { && relative_rank(weakSide, pos.square(strongSide)) >= RANK_4 && relative_rank(weakSide, rsq) == RANK_3 && ( pos.pieces(weakSide, PAWN) - & pos.attacks_from(kingSq) - & pos.attacks_from(rsq, strongSide))) + & pos.attacks_from(weakSide, kingSq) + & pos.attacks_from(strongSide, rsq))) return SCALE_FACTOR_DRAW; return SCALE_FACTOR_NONE; @@ -547,7 +547,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // the corner if ( rk == RANK_6 && distance(psq + 2 * push, ksq) <= 1 - && (PseudoAttacks[BISHOP][bsq] & (psq + push)) + && (PseudoAttacks[strongSide][BISHOP][bsq] & (psq + push)) && distance(bsq, psq) >= 2) return ScaleFactor(8); } @@ -687,14 +687,14 @@ ScaleFactor Endgame::operator()(const Position& pos) const { if ( ksq == blockSq1 && opposite_colors(ksq, wbsq) && ( bbsq == blockSq2 - || (pos.attacks_from(blockSq2) & pos.pieces(weakSide, BISHOP)) + || (pos.attacks_from(weakSide, blockSq2) & pos.pieces(weakSide, BISHOP)) || distance(r1, r2) >= 2)) return SCALE_FACTOR_DRAW; else if ( ksq == blockSq2 && opposite_colors(ksq, wbsq) && ( bbsq == blockSq1 - || (pos.attacks_from(blockSq1) & pos.pieces(weakSide, BISHOP)))) + || (pos.attacks_from(weakSide, blockSq1) & pos.pieces(weakSide, BISHOP)))) return SCALE_FACTOR_DRAW; else return SCALE_FACTOR_NONE; @@ -759,7 +759,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // King needs to get close to promoting pawn to prevent knight from blocking. // Rules for this are very tricky, so just approximate. - if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from(bishopSq)) + if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from(weakSide, bishopSq)) return ScaleFactor(distance(weakKingSq, pawnSq)); return SCALE_FACTOR_NONE; diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 01b5aa5..09e51f4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -263,7 +263,7 @@ namespace { mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pe->pawn_attacks(Them)); // Initialise attackedBy bitboards for kings and pawns - attackedBy[Us][KING] = pos.attacks_from(pos.square(Us)); + attackedBy[Us][KING] = pos.attacks_from(Us, pos.square(Us)); attackedBy[Us][PAWN] = pe->pawn_attacks(Us); attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN]; attackedBy2[Us] = attackedBy[Us][KING] & attackedBy[Us][PAWN]; @@ -310,7 +310,8 @@ namespace { // Find attacked squares, including x-ray attacks for bishops and rooks b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) - : pos.attacks_from(s); + : ( (pos.attacks_from(Us, Pt, s) & pos.pieces()) + | (pos.moves_from(Us, Pt, s) & ~pos.pieces())); if (pos.blockers_for_king(Us) & s) b &= LineBB[pos.square(Us)][s]; @@ -381,7 +382,7 @@ namespace { { // Bonus for aligning rook with enemy pawns on the same rank/file if (relative_rank(Us, s) >= RANK_5) - score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); + score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[Us][ROOK][s]); // Bonus for rook on an open or semi-open file if (pe->semiopen_file(Us, file_of(s))) @@ -463,7 +464,7 @@ namespace { unsafeChecks |= b2; // Enemy knights checks - b = pos.attacks_from(ksq) & attackedBy[Them][KNIGHT]; + b = pos.attacks_from(Us, ksq) & attackedBy[Them][KNIGHT]; if (b & safe) kingDanger += KnightSafeCheck; else @@ -603,12 +604,12 @@ namespace { Square s = pos.square(Them); safeThreats = mobilityArea[Us] & ~stronglyProtected; - b = attackedBy[Us][KNIGHT] & pos.attacks_from(s); + b = attackedBy[Us][KNIGHT] & pos.attacks_from(Us, s); score += KnightOnQueen * popcount(b & safeThreats); - b = (attackedBy[Us][BISHOP] & pos.attacks_from(s)) - | (attackedBy[Us][ROOK ] & pos.attacks_from(s)); + b = (attackedBy[Us][BISHOP] & pos.attacks_from(Us, s)) + | (attackedBy[Us][ROOK ] & pos.attacks_from(Us, s)); score += SliderOnQueen * popcount(b & safeThreats & attackedBy2[Us]); } @@ -675,7 +676,7 @@ namespace { // in the pawn's path attacked or occupied by the enemy. defendedSquares = unsafeSquares = squaresToQueen = forward_file_bb(Us, s); - bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(s); + bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(Us, s); if (!(pos.pieces(Us) & bb)) defendedSquares &= attackedBy[Us][ALL_PIECES]; diff --git a/src/main.cpp b/src/main.cpp index c5bf325..e4a5cbc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ #include "thread.h" #include "tt.h" #include "uci.h" +#include "variant.h" #include "syzygy/tbprobe.h" namespace PSQT { @@ -36,6 +37,7 @@ int main(int argc, char* argv[]) { std::cout << engine_info() << std::endl; + variants.init(); UCI::init(Options); PSQT::init(); Bitboards::init(); @@ -50,5 +52,6 @@ int main(int argc, char* argv[]) { UCI::loop(argc, argv); Threads.set(0); + variants.clear_all(); return 0; } diff --git a/src/movegen.cpp b/src/movegen.cpp index 35ca6e7..c68eddf 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -52,7 +52,7 @@ namespace { // Because we generate only legal castling moves we need to verify that // when moving the castling rook we do not discover some hidden checker. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - if (Chess960 && (attacks_bb(kto, pos.pieces() ^ rfrom) & pos.pieces(~us, ROOK, QUEEN))) + if (Chess960 && (pos.attackers_to(kto, pos.pieces() ^ rfrom) & pos.pieces(~us))) return moveList; Move m = make(kfrom, rfrom); @@ -65,25 +65,16 @@ namespace { } - template - ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { + template + ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) { - if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) - *moveList++ = make(to - D, to, QUEEN); + const MoveType T = (D == NORTH_WEST || D == SOUTH_WEST) ? PROMOTION_LEFT + : (D == NORTH_EAST || D == SOUTH_EAST) ? PROMOTION_RIGHT + : PROMOTION_STRAIGHT; - if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) - { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); - } - - // Knight promotion is the only promotion that can give a direct check - // that's not already included in the queen promotion. - if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq)) - *moveList++ = make(to - D, to, KNIGHT); - else - (void)ksq; // Silence a warning under MSVC + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + for (PieceType pt : pos.promotion_piece_types()) + *moveList++ = make(to - D, to, pt); return moveList; } @@ -95,15 +86,16 @@ namespace { // Compute our parametrized parameters at compile time, named according to // the point of view of white side. constexpr Color Them = (Us == WHITE ? BLACK : WHITE); - constexpr Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); - constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); constexpr Direction Up = (Us == WHITE ? NORTH : SOUTH); + constexpr Direction Down = (Us == WHITE ? SOUTH : NORTH); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard emptySquares; + Bitboard TRank8BB = rank_bb(Us == WHITE ? pos.promotion_rank() : ~pos.promotion_rank()); + Bitboard TRank7BB = shift(TRank8BB); Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; @@ -116,7 +108,7 @@ namespace { emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces()); Bitboard b1 = shift(pawnsNotOn7) & emptySquares; - Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; + Bitboard b2 = pos.double_step_enabled() ? shift(b1 & TRank3BB) & emptySquares : 0; if (Type == EVASIONS) // Consider only blocking squares { @@ -128,8 +120,8 @@ namespace { { Square ksq = pos.square(Them); - b1 &= pos.attacks_from(ksq, Them); - b2 &= pos.attacks_from(ksq, Them); + b1 &= pos.attacks_from(Them, ksq); + b2 &= pos.attacks_from(Them, ksq); // Add pawn pushes which give discovered check. This is possible only // if the pawn is not on the same file as the enemy king, because we @@ -172,16 +164,14 @@ namespace { Bitboard b2 = shift(pawnsOn7) & enemies; Bitboard b3 = shift(pawnsOn7) & emptySquares; - Square ksq = pos.square(Them); - while (b1) - moveList = make_promotions(moveList, pop_lsb(&b1), ksq); + moveList = make_promotions(pos, moveList, pop_lsb(&b1)); while (b2) - moveList = make_promotions(moveList, pop_lsb(&b2), ksq); + moveList = make_promotions(pos, moveList, pop_lsb(&b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(&b3), ksq); + moveList = make_promotions(pos, moveList, pop_lsb(&b3)); } // Standard and en-passant captures @@ -212,7 +202,7 @@ namespace { if (Type == EVASIONS && !(target & (pos.ep_square() - Up))) return moveList; - b1 = pawnsNotOn7 & pos.attacks_from(pos.ep_square(), Them); + b1 = pawnsNotOn7 & pos.attacks_from(Them, pos.ep_square()); assert(b1); @@ -225,30 +215,25 @@ namespace { } - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us, + template + ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us, PieceType pt, Bitboard target) { - assert(Pt != KING && Pt != PAWN); + assert(pt != KING && pt != PAWN); - const Square* pl = pos.squares(us); + const Square* pl = pos.squares(us, pt); for (Square from = *pl; from != SQ_NONE; from = *++pl) { - if (Checks) - { - if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) - && !(PseudoAttacks[Pt][from] & target & pos.check_squares(Pt))) - continue; - - if (pos.blockers_for_king(~us) & from) - continue; - } + // Avoid generating discovered checks twice + if (Checks && (pos.blockers_for_king(~us) & from)) + continue; - Bitboard b = pos.attacks_from(from) & target; + Bitboard b = ( (pos.attacks_from(us, pt, from) & pos.pieces()) + | (pos.moves_from(us, pt, from) & ~pos.pieces())) & target; if (Checks) - b &= pos.check_squares(Pt); + b &= pos.check_squares(pt); while (b) *moveList++ = make_move(from, pop_lsb(&b)); @@ -264,20 +249,18 @@ namespace { constexpr bool Checks = Type == QUIET_CHECKS; moveList = generate_pawn_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, Us, target); - moveList = generate_moves(pos, moveList, Us, target); - moveList = generate_moves< ROOK, Checks>(pos, moveList, Us, target); - moveList = generate_moves< QUEEN, Checks>(pos, moveList, Us, target); + for (PieceType pt = PieceType(PAWN + 1); pt < KING; ++pt) + moveList = generate_moves(pos, moveList, Us, pt, target); if (Type != QUIET_CHECKS && Type != EVASIONS) { Square ksq = pos.square(Us); - Bitboard b = pos.attacks_from(ksq) & target; + Bitboard b = pos.attacks_from(Us, ksq) & target; while (b) *moveList++ = make_move(ksq, pop_lsb(&b)); } - if (Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us)) + if (pos.castling_enabled() && Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us)) { if (pos.is_chess960()) { @@ -346,10 +329,10 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { if (pt == PAWN) continue; // Will be generated together with direct checks - Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces(); + Bitboard b = pos.moves_from(us, pt, from) & ~pos.pieces(); if (pt == KING) - b &= ~PseudoAttacks[QUEEN][pos.square(~us)]; + b &= ~PseudoAttacks[~us][QUEEN][pos.square(~us)]; while (b) *moveList++ = make_move(from, pop_lsb(&b)); @@ -370,7 +353,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { Color us = pos.side_to_move(); Square ksq = pos.square(us); Bitboard sliderAttacks = 0; - Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN); + Bitboard sliders = pos.checkers(); // Find all the squares attacked by slider checkers. We will remove them from // the king evasions in order to skip known illegal moves, which avoids any @@ -378,11 +361,11 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { while (sliders) { Square checksq = pop_lsb(&sliders); - sliderAttacks |= LineBB[checksq][ksq] ^ checksq; + sliderAttacks |= attacks_bb(~us, type_of(pos.piece_on(checksq)), checksq, pos.pieces() ^ ksq); } // Generate evasions for king, capture and non capture moves - Bitboard b = pos.attacks_from(ksq) & ~pos.pieces(us) & ~sliderAttacks; + Bitboard b = pos.attacks_from(us, ksq) & ~pos.pieces(us) & ~sliderAttacks; while (b) *moveList++ = make_move(ksq, pop_lsb(&b)); @@ -392,6 +375,9 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { // Generate blocking evasions or captures of the checking piece Square checksq = lsb(pos.checkers()); Bitboard target = between_bb(checksq, ksq) | checksq; + // Leaper attacks can not be blocked + if (LeaperAttacks[~us][type_of(pos.piece_on(checksq))][checksq] & ksq) + target = SquareBB[checksq]; return us == WHITE ? generate_all(pos, moveList, target) : generate_all(pos, moveList, target); diff --git a/src/pawns.cpp b/src/pawns.cpp index 6901d7b..8c5c1dc 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -101,8 +101,8 @@ namespace { // Flag the pawn opposed = theirPawns & forward_file_bb(Us, s); stoppers = theirPawns & passed_pawn_mask(Us, s); - lever = theirPawns & PawnAttacks[Us][s]; - leverPush = theirPawns & PawnAttacks[Us][s + Up]; + lever = theirPawns & PseudoAttacks[Us][PAWN][s]; + leverPush = theirPawns & PseudoAttacks[Us][PAWN][s + Up]; doubled = ourPawns & (s - Up); neighbours = ourPawns & adjacent_files_bb(f); phalanx = neighbours & rank_bb(s); @@ -128,7 +128,7 @@ namespace { { b = shift(supported) & ~theirPawns; while (b) - if (!more_than_one(theirPawns & PawnAttacks[Us][pop_lsb(&b)])) + if (!more_than_one(theirPawns & PseudoAttacks[Us][PAWN][pop_lsb(&b)])) e->passedPawns[Us] |= s; } diff --git a/src/position.cpp b/src/position.cpp index 2573fe8..9ebe3a9 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -50,11 +50,6 @@ namespace Zobrist { namespace { -const string PieceToChar(" PNBRQK pnbrqk"); - -constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; - // min_attacker() is a helper function used by see_ge() to locate the least // valuable attacker for the side to move, remove the attacker we just found // from the bitboards and scan for new X-ray attacks behind it. @@ -101,7 +96,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { for (Rank r = RANK_8; r >= RANK_1; --r) { for (File f = FILE_A; f <= FILE_H; ++f) - os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; + os << " | " << pos.piece_to_char()[pos.piece_on(make_square(f, r))]; os << " |\n +---+---+---+---+---+---+---+---+\n"; } @@ -118,7 +113,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { { StateInfo st; Position p; - p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + p.set(pos.variant(), pos.fen(), pos.is_chess960(), &st, pos.this_thread()); Tablebases::ProbeState s1, s2; Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); int dtz = Tablebases::probe_dtz(p, &s2); @@ -150,9 +145,10 @@ void Position::init() { PRNG rng(1070372); - for (Piece pc : Pieces) - for (Square s = SQ_A1; s <= SQ_H8; ++s) - Zobrist::psq[pc][s] = rng.rand(); + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[make_piece(c, pt)][s] = rng.rand(); for (File f = FILE_A; f <= FILE_H; ++f) Zobrist::enpassant[f] = rng.rand(); @@ -173,10 +169,13 @@ void Position::init() { // Prepare the cuckoo tables int count = 0; - for (Piece pc : Pieces) + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = KNIGHT; pt <= QUEEN || pt == KING; pt != QUEEN ? ++pt : pt = KING) + { + Piece pc = make_piece(c, pt); for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) - if (PseudoAttacks[type_of(pc)][s1] & s2) + if (PseudoAttacks[WHITE][type_of(pc)][s1] & s2) { Move move = make_move(s1, s2); Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; @@ -191,6 +190,7 @@ void Position::init() { } count++; } + } assert(count == 3668); } @@ -199,7 +199,7 @@ void Position::init() { /// This function is not very robust - make sure that input FENs are correct, /// this is assumed to be the responsibility of the GUI. -Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { +Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -243,6 +243,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th std::memset(this, 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE); + var = v; st = si; ss >> std::noskipws; @@ -256,7 +257,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th else if (token == '/') sq += 2 * SOUTH; - else if ((idx = PieceToChar.find(token)) != string::npos) + else if ((idx = piece_to_char().find(token)) != string::npos) { put_piece(Piece(idx), sq); ++sq; @@ -268,6 +269,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th sideToMove = (token == 'w' ? WHITE : BLACK); ss >> token; + // 3-4. Skip parsing castling and en passant flags if not present + st->epSquare = SQ_NONE; + if (!isdigit(ss.peek())) + { // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, // Shredder-FEN that uses the letters of the columns on which the rooks began // the game instead of KQkq and also X-FEN standard that, in case of Chess960, @@ -306,8 +311,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))) st->epSquare = SQ_NONE; } - else - st->epSquare = SQ_NONE; + } // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; @@ -362,11 +366,8 @@ void Position::set_check_info(StateInfo* si) const { Square ksq = square(~sideToMove); - si->checkSquares[PAWN] = attacks_from(ksq, ~sideToMove); - si->checkSquares[KNIGHT] = attacks_from(ksq); - si->checkSquares[BISHOP] = attacks_from(ksq); - si->checkSquares[ROOK] = attacks_from(ksq); - si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; + for (PieceType pt = PAWN; pt < KING; ++pt) + si->checkSquares[pt] = attacks_from(~sideToMove, pt, ksq); si->checkSquares[KING] = 0; } @@ -408,14 +409,16 @@ void Position::set_state(StateInfo* si) const { si->pawnKey ^= Zobrist::psq[piece_on(s)][s]; } - for (Piece pc : Pieces) - { - if (type_of(pc) != PAWN && type_of(pc) != KING) - si->nonPawnMaterial[color_of(pc)] += pieceCount[pc] * PieceValue[MG][pc]; + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + Piece pc = make_piece(c, pt); + if (pt != PAWN && pt != KING) + si->nonPawnMaterial[c] += pieceCount[pc] * PieceValue[MG][pc]; - for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - si->materialKey ^= Zobrist::psq[pc][cnt]; - } + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + si->materialKey ^= Zobrist::psq[pc][cnt]; + } } @@ -436,7 +439,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; - return set(fenStr, false, si, nullptr); + return set(variants.find("chess")->second, fenStr, false, si, nullptr); } @@ -459,7 +462,7 @@ const string Position::fen() const { ss << emptyCnt; if (f <= FILE_H) - ss << PieceToChar[piece_on(make_square(f, r))]; + ss << piece_to_char()[piece_on(make_square(f, r))]; } if (r > RANK_1) @@ -503,8 +506,9 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners pinners = 0; // Snipers are sliders that attack 's' when a piece is removed - Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK)) - | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders; + Bitboard snipers = sliders + & attackers_to(s, 0) + & ~attackers_to(s); while (snipers) { @@ -527,12 +531,11 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners Bitboard Position::attackers_to(Square s, Bitboard occupied) const { - return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) - | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) - | (attacks_from(s) & pieces(KNIGHT)) - | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) - | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_from(s) & pieces(KING)); + Bitboard b = 0; + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + b |= attacks_bb(~c, pt, s, occupied) & pieces(c, pt); + return b; } @@ -544,6 +547,8 @@ bool Position::legal(Move m) const { Color us = sideToMove; Square from = from_sq(m); + Square to = to_sq(m); + Square ksq = square(us); assert(color_of(moved_piece(m)) == us); assert(piece_on(square(us)) == make_piece(us, KING)); @@ -553,8 +558,6 @@ bool Position::legal(Move m) const { // the move is made. if (type_of(m) == ENPASSANT) { - Square ksq = square(us); - Square to = to_sq(m); Square capsq = to - pawn_push(us); Bitboard occupied = (pieces() ^ from ^ capsq) | to; @@ -563,20 +566,17 @@ bool Position::legal(Move m) const { assert(piece_on(capsq) == make_piece(~us, PAWN)); assert(piece_on(to) == NO_PIECE); - return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) - && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); + return !(attackers_to(ksq, occupied) & pieces(~us) & occupied); } // If the moving piece is a king, check whether the destination // square is attacked by the opponent. Castling moves are checked // for legality during move generation. if (type_of(piece_on(from)) == KING) - return type_of(m) == CASTLING || !(attackers_to(to_sq(m)) & pieces(~us)); + return type_of(m) == CASTLING || !(attackers_to(to) & pieces(~us)); - // A non-king move is legal if and only if it is not pinned or it - // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to_sq(m), square(us)); + // A non-king move is legal if the king is not under attack after the move. + return !(attackers_to(ksq, (pieces() ^ from) | to) & pieces(~us) & ~SquareBB[to]); } @@ -596,7 +596,7 @@ bool Position::pseudo_legal(const Move m) const { return MoveList(*this).contains(m); // Is not a promotion, so promotion piece must be empty - if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) + if (promotion_type(m) != NO_PIECE_TYPE) return false; // If the 'from' square is not occupied by a piece belonging to the side to @@ -613,10 +613,10 @@ bool Position::pseudo_legal(const Move m) const { { // We have already handled promotion moves, so destination // cannot be on the 8th/1st rank. - if (rank_of(to) == relative_rank(us, RANK_8)) + if (rank_of(to) == relative_rank(us, promotion_rank())) return false; - if ( !(attacks_from(from, us) & pieces(~us) & to) // Not a capture + if ( !(attacks_from(us, from) & pieces(~us) & to) // Not a capture && !((from + pawn_push(us) == to) && empty(to)) // Not a single push && !( (from + 2 * pawn_push(us) == to) // Not a double push && (rank_of(from) == relative_rank(us, RANK_2)) @@ -624,7 +624,7 @@ bool Position::pseudo_legal(const Move m) const { && empty(to - pawn_push(us)))) return false; } - else if (!(attacks_from(type_of(pc), from) & to)) + else if (!(attacks_from(us, type_of(pc), from) & to)) return false; // Evasions generator already takes care to avoid some kind of illegal moves @@ -639,7 +639,9 @@ bool Position::pseudo_legal(const Move m) const { return false; // Our move must be a blocking evasion or a capture of the checking piece - if (!((between_bb(lsb(checkers()), square(us)) | checkers()) & to)) + Square checksq = lsb(checkers()); + if ( !((between_bb(checksq, square(us)) | checkers()) & to) + || (LeaperAttacks[~us][type_of(piece_on(checksq))][checksq] & square(us))) return false; } // In case of king moves under check we have to remove king so as to catch @@ -668,7 +670,8 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if ( (st->blockersForKing[~sideToMove] & from) - && !aligned(from, to, square(~sideToMove))) + && ( !aligned(from, to, square(~sideToMove)) + || (attackers_to(square(~sideToMove), (pieces() ^ from) | to) & pieces(sideToMove)))) return true; switch (type_of(m)) @@ -677,7 +680,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION: - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(sideToMove, promotion_type(m), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case // of direct checks and ordinary discovered check, so the only case we @@ -688,8 +691,7 @@ bool Position::gives_check(Move m) const { Square capsq = make_square(file_of(to), rank_of(from)); Bitboard b = (pieces() ^ from ^ capsq) | to; - return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) - | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); + return attackers_to(square(~sideToMove), b) & pieces(sideToMove) & b; } case CASTLING: { @@ -698,7 +700,7 @@ bool Position::gives_check(Move m) const { Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1); Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1); - return (PseudoAttacks[ROOK][rto] & square(~sideToMove)) + return (PseudoAttacks[sideToMove][ROOK][rto] & square(~sideToMove)) && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); } default: @@ -825,7 +827,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { // Set en-passant square if the moved pawn can be captured if ( (int(to) ^ int(from)) == 16 - && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) + && (attacks_from(us, to - pawn_push(us)) & pieces(them, PAWN))) { st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; @@ -835,8 +837,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { Piece promotion = make_piece(us, promotion_type(m)); - assert(relative_rank(us, to) == RANK_8); - assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + assert(relative_rank(us, to) == promotion_rank()); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING); remove_piece(pc, to); put_piece(promotion, to); @@ -902,9 +904,9 @@ void Position::undo_move(Move m) { if (type_of(m) == PROMOTION) { - assert(relative_rank(us, to) == RANK_8); + assert(relative_rank(us, to) == promotion_rank()); assert(type_of(pc) == promotion_type(m)); - assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); remove_piece(pc, to); pc = make_piece(us, PAWN); @@ -1250,7 +1252,7 @@ void Position::flip() { std::getline(ss, token); // Half and full moves f += token; - set(f, is_chess960(), st, this_thread()); + set(variant(), f, is_chess960(), st, this_thread()); assert(pos_is_ok()); } @@ -1265,8 +1267,8 @@ bool Position::pos_is_ok() const { constexpr bool Fast = true; // Quick (default) or full check? if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING + || piece_on(square(WHITE)) != make_piece(WHITE, KING) + || piece_on(square(BLACK)) != make_piece(BLACK, KING) || ( ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6)) assert(0 && "pos_is_ok: Default"); @@ -1274,14 +1276,14 @@ bool Position::pos_is_ok() const { if (Fast) return true; - if ( pieceCount[W_KING] != 1 - || pieceCount[B_KING] != 1 + if ( pieceCount[make_piece(WHITE, KING)] != 1 + || pieceCount[make_piece(BLACK, KING)] != 1 || attackers_to(square(~sideToMove)) & pieces(sideToMove)) assert(0 && "pos_is_ok: Kings"); if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) - || pieceCount[W_PAWN] > 8 - || pieceCount[B_PAWN] > 8) + || pieceCount[make_piece(WHITE, PAWN)] > 8 + || pieceCount[make_piece(BLACK, PAWN)] > 8) assert(0 && "pos_is_ok: Pawns"); if ( (pieces(WHITE) & pieces(BLACK)) @@ -1300,16 +1302,18 @@ bool Position::pos_is_ok() const { if (std::memcmp(&si, st, sizeof(StateInfo))) assert(0 && "pos_is_ok: State"); - for (Piece pc : Pieces) - { - if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) - || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) - assert(0 && "pos_is_ok: Pieces"); - - for (int i = 0; i < pieceCount[pc]; ++i) - if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) - assert(0 && "pos_is_ok: Index"); - } + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + Piece pc = make_piece(c, pt); + if ( pieceCount[pc] != popcount(pieces(c, pt)) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); + + for (int i = 0; i < pieceCount[pc]; ++i) + if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) + assert(0 && "pos_is_ok: Index"); + } for (Color c = WHITE; c <= BLACK; ++c) for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) diff --git a/src/position.h b/src/position.h index a40687e..64b4bb5 100644 --- a/src/position.h +++ b/src/position.h @@ -28,6 +28,7 @@ #include "bitboard.h" #include "types.h" +#include "variant.h" /// StateInfo struct stores information needed to restore a Position object to @@ -78,10 +79,18 @@ public: Position& operator=(const Position&) = delete; // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const Variant* v, const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); Position& set(const std::string& code, Color c, StateInfo* si); const std::string fen() const; + // Variant rule properties + const Variant* variant() const; + const std::string piece_to_char() const; + Rank promotion_rank() const; + std::vector promotion_piece_types() const; + bool double_step_enabled() const; + bool castling_enabled() const; + // Position representation Bitboard pieces() const; Bitboard pieces(PieceType pt) const; @@ -95,6 +104,7 @@ public: template int count(Color c) const; template int count() const; template const Square* squares(Color c) const; + const Square* squares(Color c, PieceType pt) const; template Square square(Color c) const; // Castling @@ -111,9 +121,9 @@ public: // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; - Bitboard attacks_from(PieceType pt, Square s) const; - template Bitboard attacks_from(Square s) const; - template Bitboard attacks_from(Square s, Color c) const; + Bitboard attacks_from(Color c, PieceType pt, Square s) const; + template Bitboard attacks_from(Color c, Square s) const; + Bitboard moves_from(Color c, PieceType pt, Square s) const; Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; // Properties of moves @@ -190,11 +200,42 @@ private: Color sideToMove; Thread* thisThread; StateInfo* st; + const Variant* var; bool chess960; }; extern std::ostream& operator<<(std::ostream& os, const Position& pos); +inline const Variant* Position::variant() const { + assert(var != nullptr); + return var; +} + +inline const std::string Position::piece_to_char() const { + assert(var != nullptr); + return var->pieceToChar; +} + +inline Rank Position::promotion_rank() const { + assert(var != nullptr); + return var->promotionRank; +} + +inline std::vector Position::promotion_piece_types() const { + assert(var != nullptr); + return var->promotionPieceTypes; +} + +inline bool Position::double_step_enabled() const { + assert(var != nullptr); + return var->doubleStep; +} + +inline bool Position::castling_enabled() const { + assert(var != nullptr); + return var->castling; +} + inline Color Position::side_to_move() const { return sideToMove; } @@ -247,6 +288,10 @@ template inline const Square* Position::squares(Color c) const { return pieceList[make_piece(c, Pt)]; } +inline const Square* Position::squares(Color c, PieceType pt) const { + return pieceList[make_piece(c, pt)]; +} + template inline Square Position::square(Color c) const { assert(pieceCount[make_piece(c, Pt)] == 1); return pieceList[make_piece(c, Pt)][0]; @@ -273,20 +318,16 @@ inline Square Position::castling_rook_square(CastlingRight cr) const { } template -inline Bitboard Position::attacks_from(Square s) const { - assert(Pt != PAWN); - return Pt == BISHOP || Pt == ROOK ? attacks_bb(s, byTypeBB[ALL_PIECES]) - : Pt == QUEEN ? attacks_from(s) | attacks_from(s) - : PseudoAttacks[Pt][s]; +inline Bitboard Position::attacks_from(Color c, Square s) const { + return attacks_bb(c, Pt, s, byTypeBB[ALL_PIECES]); } -template<> -inline Bitboard Position::attacks_from(Square s, Color c) const { - return PawnAttacks[c][s]; +inline Bitboard Position::attacks_from(Color c, PieceType pt, Square s) const { + return attacks_bb(c, pt, s, byTypeBB[ALL_PIECES]); } -inline Bitboard Position::attacks_from(PieceType pt, Square s) const { - return attacks_bb(pt, s, byTypeBB[ALL_PIECES]); +inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const { + return moves_bb(c, pt, s, byTypeBB[ALL_PIECES]); } inline Bitboard Position::attackers_to(Square s) const { @@ -347,8 +388,8 @@ inline int Position::rule50_count() const { } inline bool Position::opposite_bishops() const { - return pieceCount[W_BISHOP] == 1 - && pieceCount[B_BISHOP] == 1 + return pieceCount[make_piece(WHITE, BISHOP)] == 1 + && pieceCount[make_piece(BLACK, BISHOP)] == 1 && opposite_colors(square(WHITE), square(BLACK)); } diff --git a/src/psqt.cpp b/src/psqt.cpp index e0f15c0..86593c1 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -23,8 +23,10 @@ #include "types.h" Value PieceValue[PHASE_NB][PIECE_NB] = { - { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg }, - { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg } + { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, + FersValueMg, AlfilValueMg, SilverValueMg, AiwokValueMg, AmazonValueMg, KnibisValueMg, BiskniValueMg }, + { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, + FersValueEg, AlfilValueEg, SilverValueEg, AiwokValueEg, AmazonValueEg, KnibisValueMg, BiskniValueMg } }; namespace PSQT { @@ -35,7 +37,7 @@ namespace PSQT { // type on a given square a (middlegame, endgame) score pair is assigned. Table // is defined for files A..D and white side: it is symmetric for black side and // second half of the files. -constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { +constexpr Score Bonus[PIECE_TYPE_NB][RANK_NB][int(FILE_NB) / 2] = { { }, { // Pawn { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, @@ -107,8 +109,10 @@ Score psq[PIECE_NB][SQUARE_NB]; // tables are initialized by flipping and changing the sign of the white scores. void init() { - for (Piece pc = W_PAWN; pc <= W_KING; ++pc) + for (PieceType pt = PAWN; pt <= KING; ++pt) { + Piece pc = make_piece(WHITE, pt); + PieceValue[MG][~pc] = PieceValue[MG][pc]; PieceValue[EG][~pc] = PieceValue[EG][pc]; diff --git a/src/search.cpp b/src/search.cpp index fb5dfd0..8fb4ae4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -137,7 +137,7 @@ namespace { pos.undo_move(m); } if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + sync_cout << UCI::move(pos, m) << ": " << cnt << sync_endl; } return nodes; } @@ -264,10 +264,10 @@ void MainThread::search() { if (bestThread != this) sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + sync_cout << "bestmove " << UCI::move(rootPos, bestThread->rootMoves[0].pv[0]); if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + std::cout << " ponder " << UCI::move(rootPos, bestThread->rootMoves[0].pv[1]); std::cout << sync_endl; } @@ -870,7 +870,7 @@ moves_loop: // When in check, search starts from here if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) sync_cout << "info depth " << depth / ONE_PLY - << " currmove " << UCI::move(move, pos.is_chess960()) + << " currmove " << UCI::move(pos, move) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; if (PvNode) (ss+1)->pv = nullptr; @@ -1607,7 +1607,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { << " pv"; for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); + ss << " " << UCI::move(pos, m); } return ss.str(); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index da6dc49..3951140 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1242,7 +1242,7 @@ void Tablebases::init(const std::string& paths) { if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - if ((PseudoAttacks[KING][s1] | s1) & s2) + if ((PseudoAttacks[WHITE][KING][s1] | s1) & s2) continue; // Illegal position else if (!off_A1H8(s1) && off_A1H8(s2) > 0) diff --git a/src/thread.cpp b/src/thread.cpp index f980927..d0a6336 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -194,7 +194,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, th->nodes = th->tbHits = th->nmpMinPly = 0; th->rootDepth = th->completedDepth = DEPTH_ZERO; th->rootMoves = rootMoves; - th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); + th->rootPos.set(pos.variant(), pos.fen(), pos.is_chess960(), &setupStates->back(), th); } setupStates->back() = tmp; diff --git a/src/types.h b/src/types.h index ab5e1b8..6b0f033 100644 --- a/src/types.h +++ b/src/types.h @@ -122,9 +122,12 @@ enum Move : int { enum MoveType { NORMAL, - PROMOTION = 1 << 14, - ENPASSANT = 2 << 14, - CASTLING = 3 << 14 + ENPASSANT = 1 << 12, + CASTLING = 2 << 12, + PROMOTION = 3 << 12, + PROMOTION_STRAIGHT = PROMOTION, + PROMOTION_LEFT = 4 << 12, + PROMOTION_RIGHT = 5 << 12, }; enum Color { @@ -187,21 +190,33 @@ enum Value : int { BishopValueMg = 826, BishopValueEg = 891, RookValueMg = 1282, RookValueEg = 1373, QueenValueMg = 2500, QueenValueEg = 2670, + FersValueMg = 400, FersValueEg = 400, + AlfilValueMg = 300, AlfilValueEg = 300, + SilverValueMg = 600, SilverValueEg = 600, + AiwokValueMg = 2500, AiwokValueEg = 2500, + AmazonValueMg = 3000, AmazonValueEg = 3000, + KnibisValueMg = 800, KnibisValueEg = 800, + BiskniValueMg = 800, BiskniValueEg = 800, MidgameLimit = 15258, EndgameLimit = 3915 }; +const int PIECE_TYPE_BITS = 6; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS) + enum PieceType { - NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, + QUEEN, FERS, MET = FERS, ALFIL, SILVER, KHON = SILVER, AIWOK, + AMAZON, KNIBIS, BISKNI, KING, ALL_PIECES = 0, - PIECE_TYPE_NB = 8 + + PIECE_TYPE_NB = 1 << PIECE_TYPE_BITS }; +static_assert(PIECE_TYPE_BITS <= 6, "PIECE_TYPE uses more than 6 bit"); +static_assert(!(PIECE_TYPE_NB & (PIECE_TYPE_NB - 1)), "PIECE_TYPE_NB is not a power of 2"); enum Piece { NO_PIECE, - W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, - PIECE_NB = 16 + PIECE_NB = 2 * PIECE_TYPE_NB }; extern Value PieceValue[PHASE_NB][PIECE_NB]; @@ -362,8 +377,12 @@ constexpr File operator~(File f) { return File(f ^ FILE_H); // Horizontal flip FILE_A -> FILE_H } +constexpr Rank operator~(Rank r) { + return Rank(r ^ RANK_8); // Vertical flip Rank_1 -> Rank_8 +} + constexpr Piece operator~(Piece pc) { - return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT + return Piece(pc ^ PIECE_TYPE_NB); // Swap color of piece BLACK KNIGHT -> WHITE KNIGHT } constexpr CastlingRight operator|(Color c, CastlingSide s) { @@ -383,16 +402,16 @@ constexpr Square make_square(File f, Rank r) { } constexpr Piece make_piece(Color c, PieceType pt) { - return Piece((c << 3) + pt); + return Piece((c << PIECE_TYPE_BITS) + pt); } constexpr PieceType type_of(Piece pc) { - return PieceType(pc & 7); + return PieceType(pc & (PIECE_TYPE_NB - 1)); } inline Color color_of(Piece pc) { assert(pc != NO_PIECE); - return Color(pc >> 3); + return Color(pc >> PIECE_TYPE_BITS); } constexpr bool is_ok(Square s) { @@ -428,24 +447,41 @@ constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -constexpr Square from_sq(Move m) { - return Square((m >> 6) & 0x3F); +inline MoveType type_of(Move m) { + MoveType t = MoveType(m & (15 << 12)); + if (t == PROMOTION_STRAIGHT || t == PROMOTION_LEFT || t == PROMOTION_RIGHT) + return PROMOTION; + return t; } constexpr Square to_sq(Move m) { return Square(m & 0x3F); } -constexpr int from_to(Move m) { - return m & 0xFFF; +inline Square from_sq(Move m) { + if (type_of(m) == PROMOTION) + { + Square to = to_sq(m); + MoveType t = MoveType(m & (15 << 12)); + // Assume here that promotion occur only for relative ranks >= RANK_5. + Direction up = (to & 32) ? NORTH : SOUTH; + if (t == PROMOTION_STRAIGHT) + return to - up; + if (t == PROMOTION_LEFT) + return to - up - WEST; + if (t == PROMOTION_RIGHT) + return to - up - EAST; + assert(false); + } + return Square((m >> 6) & 0x3F); } -constexpr MoveType type_of(Move m) { - return MoveType(m & (3 << 14)); +inline int from_to(Move m) { + return to_sq(m) + (from_sq(m) << 6); } -constexpr PieceType promotion_type(Move m) { - return PieceType(((m >> 12) & 3) + KNIGHT); +inline PieceType promotion_type(Move m) { + return type_of(m) == PROMOTION ? PieceType((m >> 6) & 63) : NO_PIECE_TYPE; } inline Move make_move(Square from, Square to) { @@ -453,11 +489,13 @@ inline Move make_move(Square from, Square to) { } template -constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); +inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) { + if (T == PROMOTION_STRAIGHT || T == PROMOTION_LEFT || T == PROMOTION_RIGHT) + return Move(T + (pt << 6) + to); + return Move(T + (from << 6) + to); } -constexpr bool is_ok(Move m) { +inline bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } diff --git a/src/uci.cpp b/src/uci.cpp index 72eda4a..39e539f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -39,10 +39,6 @@ extern vector setup_bench(const Position&, istream&); namespace { - // FEN string of the initial position, normal chess - const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - - // position() is called when engine receives the "position" UCI command. // The function sets up the position described in the given FEN string ("fen") // or the starting position ("startpos") and then makes the moves given in the @@ -57,7 +53,7 @@ namespace { if (token == "startpos") { - fen = StartFEN; + fen = variants.find(Options["UCI_Variant"])->second->startFen; is >> token; // Consume "moves" token if any } else if (token == "fen") @@ -67,7 +63,7 @@ namespace { return; states = StateListPtr(new std::deque(1)); // Drop old and create a new one - pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse move list (if any) while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) @@ -193,7 +189,8 @@ void UCI::loop(int argc, char* argv[]) { StateListPtr states(new std::deque(1)); auto uiThread = std::make_shared(0); - pos.set(StartFEN, false, &states->back(), uiThread.get()); + assert(variants.find(Options["UCI_Variant"])->second != nullptr); + pos.set(variants.find(Options["UCI_Variant"])->second, variants.find(Options["UCI_Variant"])->second->startFen, false, &states->back(), uiThread.get()); for (int i = 1; i < argc; ++i) cmd += std::string(argv[i]) + " "; @@ -277,7 +274,7 @@ std::string UCI::square(Square s) { /// normal chess mode, and in e1h1 notation in chess960 mode. Internally all /// castling moves are always encoded as 'king captures rook'. -string UCI::move(Move m, bool chess960) { +string UCI::move(const Position& pos, Move m) { Square from = from_sq(m); Square to = to_sq(m); @@ -288,13 +285,13 @@ string UCI::move(Move m, bool chess960) { if (m == MOVE_NULL) return "0000"; - if (type_of(m) == CASTLING && !chess960) + if (type_of(m) == CASTLING && !pos.is_chess960()) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); string move = UCI::square(from) + UCI::square(to); if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; + move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))]; return move; } @@ -309,7 +306,7 @@ Move UCI::to_move(const Position& pos, string& str) { str[4] = char(tolower(str[4])); for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) + if (str == UCI::move(pos, m)) return m; return MOVE_NONE; diff --git a/src/uci.h b/src/uci.h index 3ad3a30..2355717 100644 --- a/src/uci.h +++ b/src/uci.h @@ -23,6 +23,7 @@ #include #include +#include #include "types.h" @@ -49,6 +50,7 @@ public: Option(OnChange = nullptr); Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); + Option(const char* v, const std::vector& variants, OnChange = nullptr); Option(double v, int minv, int maxv, OnChange = nullptr); Option(const char* v, const char *cur, OnChange = nullptr); @@ -63,6 +65,7 @@ private: std::string defaultValue, currentValue, type; int min, max; + std::vector comboValues; size_t idx; OnChange on_change; }; @@ -71,7 +74,7 @@ void init(OptionsMap&); void loop(int argc, char* argv[]); std::string value(Value v); std::string square(Square s); -std::string move(Move m, bool chess960); +std::string move(const Position& pos, Move m); std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); Move to_move(const Position& pos, std::string& str); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 5c89142..0374548 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -20,13 +20,14 @@ #include #include -#include +#include #include "misc.h" #include "search.h" #include "thread.h" #include "tt.h" #include "uci.h" +#include "variant.h" #include "syzygy/tbprobe.h" using std::string; @@ -41,6 +42,9 @@ void on_hash_size(const Option& o) { TT.resize(o); } void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(o); } void on_tb_path(const Option& o) { Tablebases::init(o); } +void on_variant_change(const Option &o) { + sync_cout << "info string variant " << (std::string)o << " startpos " << variants.find(o)->second->startFen << sync_endl; +} /// Our case insensitive less() function as required by UCI protocol @@ -72,6 +76,7 @@ void init(OptionsMap& o) { o["Slow Mover"] << Option(84, 10, 1000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); + o["UCI_Variant"] << Option("chess", variants.get_keys(), on_variant_change); o["UCI_AnalyseMode"] << Option(false); o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); @@ -95,6 +100,10 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { if (o.type == "string" || o.type == "check" || o.type == "combo") os << " default " << o.defaultValue; + if (o.type == "combo") + for (string value : o.comboValues) + os << " var " << value; + if (o.type == "spin") os << " default " << int(stof(o.defaultValue)) << " min " << o.min @@ -112,6 +121,9 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) { defaultValue = currentValue = v; } +Option::Option(const char* v, const std::vector& variants, OnChange f) : type("combo"), min(0), max(0), comboValues(variants), on_change(f) +{ defaultValue = currentValue = v; } + Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) { defaultValue = currentValue = (v ? "true" : "false"); } @@ -130,7 +142,7 @@ Option::operator double() const { } Option::operator std::string() const { - assert(type == "string"); + assert(type == "string" || type == "combo"); return currentValue; } @@ -162,6 +174,7 @@ Option& Option::operator=(const string& v) { if ( (type != "button" && v.empty()) || (type == "check" && v != "true" && v != "false") + || (type == "combo" && (std::find(comboValues.begin(), comboValues.end(), v) == comboValues.end())) || (type == "spin" && (stof(v) < min || stof(v) > max))) return *this; diff --git a/src/variant.cpp b/src/variant.cpp new file mode 100644 index 0000000..b0467f4 --- /dev/null +++ b/src/variant.cpp @@ -0,0 +1,136 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "variant.h" + +using std::string; + +VariantMap variants; // Global object + +void VariantMap::init() { + const Variant* chess = [&]{ + Variant* v = new Variant(); + return v; + } (); + const Variant* makruk = [&]{ + Variant* v = new Variant(); + v->reset_pieces(); + v->set_piece(PAWN, 'p'); + v->set_piece(KNIGHT, 'n'); + v->set_piece(KHON, 's'); + v->set_piece(ROOK, 'r'); + v->set_piece(MET, 'm'); + v->set_piece(KING, 'k'); + v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1"; + v->promotionRank = RANK_6; + v->promotionPieceTypes = {MET}; + v->doubleStep = false; + v->castling = false; + return v; + } (); + const Variant* asean = [&]{ + Variant* v = new Variant(); + v->reset_pieces(); + v->set_piece(PAWN, 'p'); + v->set_piece(KNIGHT, 'n'); + v->set_piece(KHON, 'b'); + v->set_piece(ROOK, 'r'); + v->set_piece(MET, 'q'); + v->set_piece(KING, 'k'); + v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1"; + v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET}; + v->doubleStep = false; + v->castling = false; + return v; + } (); + const Variant* aiwok = [&]{ + Variant* v = new Variant(); + v->reset_pieces(); + v->set_piece(PAWN, 'p'); + v->set_piece(KNIGHT, 'n'); + v->set_piece(KHON, 's'); + v->set_piece(ROOK, 'r'); + v->set_piece(AIWOK, 'a'); + v->set_piece(KING, 'k'); + v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1"; + v->promotionRank = RANK_6; + v->promotionPieceTypes = {AIWOK}; + v->doubleStep = false; + v->castling = false; + return v; + } (); + const Variant* shatranj = [&]{ + Variant* v = new Variant(); + v->reset_pieces(); + v->set_piece(PAWN, 'p'); + v->set_piece(KNIGHT, 'n'); + v->set_piece(ALFIL, 'b'); + v->set_piece(ROOK, 'r'); + v->set_piece(FERS, 'q'); + v->set_piece(KING, 'k'); + v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1"; + v->promotionPieceTypes = {FERS}; + v->doubleStep = false; + v->castling = false; + // TODO: bare king, stalemate + return v; + } (); + const Variant* amazon = [&]{ + Variant* v = new Variant(); + v->set_piece(QUEEN, ' '); + v->set_piece(AMAZON, 'a'); + v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1"; + v->promotionPieceTypes = {AMAZON, ROOK, BISHOP, KNIGHT}; + return v; + } (); + const Variant* hoppelpoppel = [&]{ + Variant* v = new Variant(); + v->set_piece(KNIGHT, ' '); + v->set_piece(BISHOP, ' '); + v->set_piece(KNIBIS, 'n'); + v->set_piece(BISKNI, 'b'); + v->promotionPieceTypes = {QUEEN, ROOK, BISKNI, KNIBIS}; + return v; + } (); + insert(std::pair(std::string("chess"), chess)); + insert(std::pair(std::string("makruk"), makruk)); + insert(std::pair(std::string("asean"), asean)); + insert(std::pair(std::string("ai-wok"), aiwok)); + insert(std::pair(std::string("shatranj"), shatranj)); + insert(std::pair(std::string("amazon"), amazon)); + insert(std::pair(std::string("hoppelpoppel"), hoppelpoppel)); +} + +void VariantMap::clear_all() { + for (auto const& element : *this) { + delete element.second; + } + clear(); +} + +std::vector VariantMap::get_keys() { + std::vector keys; + for (auto const& element : *this) { + keys.push_back(element.first); + } + return keys; +} diff --git a/src/variant.h b/src/variant.h new file mode 100644 index 0000000..9b2d925 --- /dev/null +++ b/src/variant.h @@ -0,0 +1,60 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef VARIANT_H_INCLUDED +#define VARIANT_H_INCLUDED + +#include +#include +#include + +#include "types.h" + + +/// Variant struct stores information needed to determine the rules of a variant. + +struct Variant { + std::string pieceToChar = " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ') + + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' '); + std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + Rank promotionRank = RANK_8; + std::vector promotionPieceTypes = {QUEEN, ROOK, BISHOP, KNIGHT}; + bool doubleStep = true; + bool castling = true; + + void set_piece(PieceType pt, char c) { + pieceToChar[make_piece(WHITE, pt)] = toupper(c); + pieceToChar[make_piece(BLACK, pt)] = tolower(c); + } + + void reset_pieces() { + pieceToChar = std::string(PIECE_NB, ' '); + } +}; + +struct VariantMap : public std::map { + void init(); + void clear_all(); + std::vector get_keys(); +}; + +extern VariantMap variants; + +#endif // #ifndef VARIANT_H_INCLUDED -- 1.7.0.4