diff --git a/src/Makefile b/src/Makefile index 4e4d3d91853..8f1e05c3a76 100644 --- a/src/Makefile +++ b/src/Makefile @@ -232,6 +232,8 @@ ifneq ($(comp),mingw) endif ### 3.4 Debugging +CXXFLAGS += -DATOMIC -DHORDE -DKOTH -DTHREECHECK +#CXXFLAGS += -DKOTH_DISTANCE_BONUS ifeq ($(debug),no) CXXFLAGS += -DNDEBUG else diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 33e266898e4..1ab40f405f8 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -146,7 +146,27 @@ void benchmark(const Position& current, istream& is) { for (size_t i = 0; i < fens.size(); ++i) { - Position pos(fens[i], Options["UCI_Chess960"], Threads.main()); + Position pos; + int variant = STANDARD_VARIANT; + if (Options["UCI_Chess960"]) + variant |= CHESS960_VARIANT; +#ifdef ATOMIC + if (Options["UCI_Atomic"]) + variant |= ATOMIC_VARIANT; +#endif +#ifdef HORDE + if (Options["UCI_Horde"]) + variant |= HORDE_VARIANT; +#endif +#ifdef KOTH + if (Options["UCI_KingOfTheHill"]) + variant |= KOTH_VARIANT; +#endif +#ifdef THREECHECK + if (Options["UCI_3Check"]) + variant |= THREECHECK_VARIANT; +#endif + pos.set(fens[i], variant, Threads.main()); cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; diff --git a/src/build.sh b/src/build.sh new file mode 100644 index 00000000000..7ce1f5c7234 --- /dev/null +++ b/src/build.sh @@ -0,0 +1,3 @@ +make clean +make build ARCH=x86-64 +#make build ARCH=x86-64 debug=yes optimize=no diff --git a/src/endgame.cpp b/src/endgame.cpp index b64b3d1bff3..5721377bf62 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -99,7 +99,7 @@ namespace { string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; - return Position(fen, false, nullptr).material_key(); + return Position(fen, nullptr).material_key(); } } // namespace diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9c83eb6fe3c..5c977638223 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -178,6 +178,15 @@ namespace { { V(7), V(14), V(37), V(63), V(134), V(189) } }; +#ifdef THREECHECK + const Score ChecksGivenBonus[CHECKS_NB] = { + S(0, 0), + S(2 * PawnValueMg, 2 * PawnValueEg), + S(5 * PawnValueMg, 4 * PawnValueEg), + S(9 * PawnValueMg, 9 * PawnValueEg) + }; +#endif + // PassedFile[File] contains a bonus according to the file of a passed pawn. const Score PassedFile[] = { S( 12, 10), S( 3, 10), S( 1, -8), S(-27, -12), @@ -349,7 +358,9 @@ namespace { // Bonus when on an open or semi-open file if (ei.pi->semiopen_file(Us, file_of(s))) + { score += ei.pi->semiopen_file(Them, file_of(s)) ? RookOnOpenFile : RookOnSemiOpenFile; + } // Penalize when trapped by the king, even more if king cannot castle if (mob <= 3 && !ei.pi->semiopen_file(Us, file_of(s))) @@ -430,6 +441,10 @@ namespace { // Analyse the enemy's safe distance checks for sliders and knights safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them)); +#ifdef THREECHECK + if (pos.is_three_check() && pos.checks_taken()) + safe = ~pos.pieces(Them); +#endif b1 = pos.attacks_from(ksq) & safe; b2 = pos.attacks_from(ksq) & safe; @@ -452,6 +467,7 @@ namespace { // Enemy bishops safe checks b = b2 & ei.attackedBy[Them][BISHOP]; + if (b) { attackUnits += BishopCheck * popcount(b); @@ -468,6 +484,19 @@ namespace { // Finally, extract the king danger score from the KingDanger[] // array and subtract the score from evaluation. +#ifdef THREECHECK + if (pos.is_three_check()) + { + switch(pos.checks_taken()) + { + case CHECKS_NB: + case CHECKS_3: + case CHECKS_2: attackUnits += RookCheck; break; + case CHECKS_1: attackUnits += KnightCheck + attackUnits / 2; break; + case CHECKS_0: attackUnits += BishopCheck + attackUnits; break; + } + } +#endif score -= KingDanger[std::max(std::min(attackUnits, 399), 0)]; } @@ -611,6 +640,12 @@ namespace { if (!(pos.pieces(Us) & bb)) defendedSquares &= ei.attackedBy[Us][ALL_PIECES]; +#ifdef ATOMIC + // Consider most squares safe since capturing costs a piece + if (pos.is_atomic()) + unsafeSquares &= pos.square(Them); + else +#endif if (!(pos.pieces(Them) & bb)) unsafeSquares &= ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them); @@ -637,6 +672,10 @@ namespace { score += make_score(mbonus, ebonus) + PassedFile[file_of(s)]; } +#ifdef ATOMIC + if (pos.is_atomic()) + score += score; +#endif if (DoTrace) Trace::add(PASSED, Us, score * Weights[PassedPawns]); @@ -667,19 +706,48 @@ namespace { & ~pos.pieces(Us, PAWN) & ~ei.attackedBy[Them][PAWN] & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); +#ifdef HORDE + if (pos.is_horde()) + safe = ~ei.attackedBy[Them][PAWN] + & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); +#endif // Find all squares which are at most three squares behind some friendly pawn Bitboard behind = pos.pieces(Us, PAWN); behind |= (Us == WHITE ? behind >> 8 : behind << 8); behind |= (Us == WHITE ? behind >> 16 : behind << 16); +#ifdef HORDE + if (pos.is_horde()) + behind |= (Us == WHITE ? behind >> 24 : behind << 24); +#endif // Since SpaceMask[Us] is fully on our half of the board... +#ifdef HORDE + assert(pos.is_horde() || unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0); +#else assert(unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0); +#endif // ...count safe + (behind & safe) with a single popcount int bonus = popcount((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe)); +#ifdef HORDE + if (pos.is_horde()) + bonus = popcount(safe | behind); +#endif int weight = pos.count(Us) + pos.count(Us) + pos.count(Them) + pos.count(Them); +#ifdef THREECHECK + if (pos.is_three_check()) + weight -= pos.checks_count(); +#endif +#ifdef HORDE + if (pos.is_horde()) + { + weight += pos.count(Us) + pos.count(Us) + pos.count(Us) + + pos.count(Them) + pos.count(Them) + pos.count(Them); + return make_score(bonus * (bonus + 2 * weight) * weight, 0); + } +#endif return make_score(bonus * weight * weight, 0); } @@ -742,7 +810,6 @@ namespace { } // namespace - /// evaluate() is the main evaluation function. It returns a static evaluation /// of the position from the point of view of the side to move. @@ -759,12 +826,66 @@ Value Eval::evaluate(const Position& pos) { // internally from the white point of view. score = pos.psq_score(); +#ifdef KOTH + // Possibly redundant static evaluator + if (pos.is_koth()) + { + if (pos.is_koth_win()) + return VALUE_MATE; + if (pos.is_koth_loss()) + return -VALUE_MATE; + } +#endif +#ifdef THREECHECK + if (pos.is_three_check()) + { + // Possibly redundant static evaluator + if (pos.is_three_check_win()) + return VALUE_MATE; + if (pos.is_three_check_loss()) + return -VALUE_MATE; + + score += ChecksGivenBonus[pos.checks_given()]; + score -= ChecksGivenBonus[pos.checks_taken()]; + } +#endif +#ifdef HORDE + // Possibly redundant static evaluator + if (pos.is_horde()) + { + if (pos.is_horde_loss()) + return -VALUE_MATE; + } +#endif +#ifdef ATOMIC + // Possibly redundant static evaluator + if (pos.is_atomic()) + { + if (pos.is_atomic_win()) + return VALUE_MATE; + if (pos.is_atomic_loss()) + return -VALUE_MATE; + } +#endif + // Probe the material hash table ei.me = Material::probe(pos); score += ei.me->imbalance(); // If we have a specialized evaluation function for the current material // configuration, call it and return. +#ifdef KOTH + if (pos.is_koth()) {} else +#endif +#ifdef THREECHECK + if (pos.is_three_check()) {} else +#endif +#ifdef HORDE + if (pos.is_horde()) {} else +#endif +#ifdef ATOMIC + if (pos.is_atomic()) {} else +#endif if (ei.me->specialized_eval_exists()) return ei.me->evaluate(pos); @@ -819,12 +940,25 @@ Value Eval::evaluate(const Position& pos) { } // Evaluate space for both sides, only during opening +#ifdef HORDE + if (pos.is_horde()) + { + if (pos.non_pawn_material(BLACK) + pos.non_pawn_material(BLACK) >= 12222) + score += ( evaluate_space(pos, ei) + - evaluate_space(pos, ei)) * Weights[Space]; + } + else + { +#endif if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222) score += ( evaluate_space(pos, ei) - evaluate_space(pos, ei)) * Weights[Space]; // Evaluate position potential for the winning side score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score)); +#ifdef HORDE + } +#endif // Evaluate scale factor for the winning side ScaleFactor sf = evaluate_scale_factor(pos, ei, score); diff --git a/src/material.cpp b/src/material.cpp index bf0d060fde3..b87d6bcafc0 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -175,12 +175,36 @@ Entry* probe(const Position& pos) { { if (!pos.count(BLACK)) { +#ifdef KOTH + if (pos.is_koth()) {} else +#endif +#ifdef THREECHECK + if (pos.is_three_check()) {} else +#endif +#ifdef HORDE + if (pos.is_horde()) {} else +#endif +#ifdef ATOMIC + if (pos.is_atomic()) {} else +#endif assert(pos.count(WHITE) >= 2); e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; } else if (!pos.count(WHITE)) { +#ifdef KOTH + if (pos.is_koth()) {} else +#endif +#ifdef THREECHECK + if (pos.is_three_check()) {} else +#endif +#ifdef HORDE + if (pos.is_horde()) {} else +#endif +#ifdef ATOMIC + if (pos.is_atomic()) {} else +#endif assert(pos.count(BLACK) >= 2); e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; diff --git a/src/material.h b/src/material.h index 67d555a4619..929533d92a8 100644 --- a/src/material.h +++ b/src/material.h @@ -49,6 +49,18 @@ struct Entry { // the position. For instance, in KBP vs K endgames, the scaling function looks // for rook pawns and wrong-colored bishops. ScaleFactor scale_factor(const Position& pos, Color c) const { +#ifdef KOTH + if (pos.is_koth()) return SCALE_FACTOR_NORMAL; +#endif +#ifdef THREECHECK + if (pos.is_three_check()) return SCALE_FACTOR_NORMAL; +#endif +#ifdef HORDE + if (pos.is_horde()) return SCALE_FACTOR_NORMAL; +#endif +#ifdef ATOMIC + if (pos.is_atomic()) return SCALE_FACTOR_NORMAL; +#endif return !scalingFunction[c] || (*scalingFunction[c])(pos) == SCALE_FACTOR_NONE ? ScaleFactor(factor[c]) : (*scalingFunction[c])(pos); diff --git a/src/movegen.cpp b/src/movegen.cpp index 274412f4081..f864c83825d 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -99,6 +99,9 @@ namespace { const Color Them = (Us == WHITE ? BLACK : WHITE); const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); +#ifdef HORDE + const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB); +#endif const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); const Square Up = (Us == WHITE ? DELTA_N : DELTA_S); const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW); @@ -119,6 +122,10 @@ namespace { Bitboard b1 = shift_bb(pawnsNotOn7) & emptySquares; Bitboard b2 = shift_bb(b1 & TRank3BB) & emptySquares; +#ifdef HORDE + if (pos.is_horde()) + b2 = shift_bb(b1 & (TRank2BB | TRank3BB)) & emptySquares; +#endif if (Type == EVASIONS) // Consider only blocking squares { @@ -162,7 +169,14 @@ namespace { if (pawnsOn7 && (Type != EVASIONS || (target & TRank8BB))) { if (Type == CAPTURES) + { emptySquares = ~pos.pieces(); +#ifdef ATOMIC + // Promotes only if promotion wins or explodes checkers + if (pos.is_atomic() && pos.checkers()) + emptySquares &= target; +#endif + } if (Type == EVASIONS) emptySquares &= target; @@ -201,7 +215,12 @@ namespace { if (pos.ep_square() != SQ_NONE) { +#ifdef HORDE + assert((pos.is_horde() && rank_of(pos.ep_square()) == RANK_2) || + rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); +#else assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); +#endif // An en passant capture can be an evasion only if the checking piece // is the double pushed pawn and so is in the target. Otherwise this @@ -315,6 +334,10 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { Bitboard target = Type == CAPTURES ? pos.pieces(~us) : Type == QUIETS ? ~pos.pieces() : Type == NON_EVASIONS ? ~pos.pieces(us) : 0; +#ifdef ATOMIC + if (pos.is_atomic() && Type == CAPTURES) + target &= ~pos.attacks_from(pos.square(us)); +#endif return us == WHITE ? generate_all(pos, moveList, target) : generate_all(pos, moveList, target); @@ -370,6 +393,33 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { Square ksq = pos.square(us); Bitboard sliderAttacks = 0; Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN); +#ifdef ATOMIC + Bitboard kingAttacks = pos.is_atomic() ? pos.attacks_from(pos.square(~us)) : 0; +#endif + +#ifdef ATOMIC + if (pos.is_atomic()) + { + // Blasts that explode the opposing king or explode all checkers + // are counted among evasive moves. + Bitboard target = pos.checkers(), b1 = pos.checkers(); + while (b1) + target |= pos.attacks_from(pop_lsb(&b1)); + if (more_than_one(pos.checkers())) + { + b1 = pos.checkers(); + while (b1) + { + Square s = pop_lsb(&b1); + target &= pos.attacks_from(s) | s; + } + } + target |= kingAttacks; + target &= pos.pieces(~us) & ~pos.attacks_from(ksq); + moveList = (us == WHITE ? generate_all(pos, moveList, target) + : generate_all(pos, moveList, target)); + } +#endif // 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 @@ -381,7 +431,13 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { } // Generate evasions for king, capture and non capture moves - Bitboard b = pos.attacks_from(ksq) & ~pos.pieces(us) & ~sliderAttacks; + Bitboard b; +#ifdef ATOMIC + if (pos.is_atomic()) + b = pos.attacks_from(ksq) & ~pos.pieces() & ~(sliderAttacks & ~kingAttacks); + else +#endif + b = pos.attacks_from(ksq) & ~pos.pieces(us) & ~sliderAttacks; while (b) *moveList++ = make_move(ksq, pop_lsb(&b)); @@ -391,6 +447,10 @@ 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; +#ifdef ATOMIC + if (pos.is_atomic() && (pos.attacks_from(ksq) & checksq)) + target ^= checksq; +#endif return us == WHITE ? generate_all(pos, moveList, target) : generate_all(pos, moveList, target); @@ -401,11 +461,39 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { +#ifdef ATOMIC + if (pos.is_atomic() && (pos.is_atomic_win() || pos.is_atomic_loss())) + return moveList; +#endif +#ifdef HORDE + if (pos.is_horde() && pos.is_horde_loss()) + return moveList; +#endif +#ifdef KOTH + if (pos.is_koth() && (pos.is_koth_win() || pos.is_koth_loss())) + return moveList; +#endif +#ifdef THREECHECK + if (pos.is_three_check() && (pos.is_three_check_win() || pos.is_three_check_loss())) + return moveList; +#endif Bitboard pinned = pos.pinned_pieces(pos.side_to_move()); Square ksq = pos.square(pos.side_to_move()); ExtMove* cur = moveList; +#ifdef KOTH + if (pos.is_koth() && (pos.is_koth_win() || pos.is_koth_loss())) + return moveList; +#endif +#ifdef THREECHECK + if (pos.is_three_check() && (pos.is_three_check_win() || pos.is_three_check_loss())) + return moveList; +#endif +#ifdef ATOMIC + if (pos.is_atomic() && (pos.is_atomic_win() || pos.is_atomic_loss())) + return moveList; +#endif moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) diff --git a/src/pawns.cpp b/src/pawns.cpp index 1653e23ad51..48f062f7d71 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -89,6 +89,15 @@ namespace { // in front of the king and no enemy pawn on the horizon. const Value MaxSafetyBonus = V(258); +#ifdef KOTH_DISTANCE_BONUS + const Score KOTHDistanceBonus[4] = { + S(1*PawnValueMg + PawnValueMg/2, 9*PawnValueEg), + S(1*PawnValueMg , 4*PawnValueEg), + S( PawnValueMg/2, 1*PawnValueEg), + S(0, 0) + }; +#endif + #undef S #undef V @@ -169,6 +178,10 @@ namespace { // Passed pawns will be properly scored in evaluation because we need // full attack info to evaluate passed pawns. Only the frontmost passed // pawn on each file is considered a true passed pawn. +#ifdef HORDE + if (pos.is_horde()) + passed = !opposed; +#endif if (passed && !doubled) e->passedPawns[Us] |= s; @@ -291,13 +304,35 @@ Score Entry::do_king_safety(const Position& pos, Square ksq) { kingSquares[Us] = ksq; castlingRights[Us] = pos.can_castle(Us); int minKingPawnDistance = 0; +#ifdef THREECHECK + Checks checks = pos.is_three_check() ? pos.checks_taken() : CHECKS_0; +#endif + +#ifdef KOTH_DISTANCE_BONUS + Score kothBonus = SCORE_ZERO; + if (pos.is_koth()) + { + // Initial attempt to adjust score based on KOTH distance + // TODO: account for attacked and blocked squares + kothBonus = KOTHDistanceBonus[pos.koth_distance(Us)]; + } +#endif Bitboard pawns = pos.pieces(Us, PAWN); if (pawns) while (!(DistanceRingBB[ksq][minKingPawnDistance++] & pawns)) {} if (relative_rank(Us, ksq) > RANK_4) +#ifdef THREECHECK + // Decrease score when checks have been taken + return make_score(0, -16 * minKingPawnDistance - checks); +#else +#ifdef KOTH_DISTANCE_BONUS + return kothBonus + make_score(0, -16 * minKingPawnDistance); +#else return make_score(0, -16 * minKingPawnDistance); +#endif +#endif Value bonus = shelter_storm(pos, ksq); @@ -308,7 +343,16 @@ Score Entry::do_king_safety(const Position& pos, Square ksq) { if (pos.can_castle(MakeCastling::right)) bonus = std::max(bonus, shelter_storm(pos, relative_square(Us, SQ_C1))); +#ifdef THREECHECK + // Decrease score when checks have been taken + return make_score(bonus, (-16 * minKingPawnDistance) + (-2 * checks)); +#else +#ifdef KOTH_DISTANCE_BONUS + return kothBonus + make_score(bonus, -16 * minKingPawnDistance); +#else return make_score(bonus, -16 * minKingPawnDistance); +#endif +#endif } // Explicit template instantiation diff --git a/src/position.cpp b/src/position.cpp index 0be3301a9e0..191a05f4c8a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -44,6 +44,9 @@ namespace Zobrist { Key castling[CASTLING_RIGHT_NB]; Key side; Key exclusion; +#ifdef THREECHECK + Key checks[COLOR_NB][CHECKS_NB]; +#endif } Key Position::exclusion_key() const { return st->key ^ Zobrist::exclusion; } @@ -94,6 +97,28 @@ CheckInfo::CheckInfo(const Position& pos) { pinned = pos.pinned_pieces(pos.side_to_move()); dcCandidates = pos.discovered_check_candidates(); +#ifdef HORDE + if (pos.is_horde() && ksq == SQ_NONE) { + checkSquares[PAWN] = 0; + checkSquares[KNIGHT] = 0; + checkSquares[BISHOP] = 0; + checkSquares[ROOK] = 0; + checkSquares[QUEEN] = 0; + checkSquares[KING] = 0; + return; + } +#endif +#ifdef ATOMIC + if (pos.is_atomic() && ksq == SQ_NONE) { + checkSquares[PAWN] = 0; + checkSquares[KNIGHT] = 0; + checkSquares[BISHOP] = 0; + checkSquares[ROOK] = 0; + checkSquares[QUEEN] = 0; + checkSquares[KING] = 0; + return; + } +#endif checkSquares[PAWN] = pos.attacks_from(ksq, them); checkSquares[KNIGHT] = pos.attacks_from(ksq); checkSquares[BISHOP] = pos.attacks_from(ksq); @@ -155,6 +180,12 @@ void Position::init() { Zobrist::side = rng.rand(); Zobrist::exclusion = rng.rand(); + +#ifdef THREECHECK + for (Color c = WHITE; c <= BLACK; ++c) + for (Checks n = CHECKS_0; n <= CHECKS_3; ++n) + Zobrist::checks[c][n] = rng.rand(); +#endif } @@ -183,9 +214,15 @@ void Position::clear() { startState.epSquare = SQ_NONE; st = &startState; +#ifdef HORDE + for (int i = 0; i < PIECE_TYPE_NB; ++i) + for (int j = 0; j < SQUARE_NB; ++j) + pieceList[WHITE][i][j] = pieceList[BLACK][i][j] = SQ_NONE; +#else for (int i = 0; i < PIECE_TYPE_NB; ++i) for (int j = 0; j < 16; ++j) pieceList[WHITE][i][j] = pieceList[BLACK][i][j] = SQ_NONE; +#endif } @@ -193,7 +230,7 @@ void Position::clear() { /// This function is not very robust - make sure that input FENs are correct, /// this is assumed to be the responsibility of the GUI. -void Position::set(const string& fenStr, bool isChess960, Thread* th) { +void Position::set(const string& fenStr, int var, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -266,43 +303,93 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) { { Square rsq; Color c = islower(token) ? BLACK : WHITE; + Rank rank = relative_rank(c, RANK_1); + Square ksq = square(c); + if (rank_of(ksq) != rank) + continue; Piece rook = make_piece(c, ROOK); token = char(toupper(token)); if (token == 'K') - for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + for (rsq = relative_square(c, SQ_H1); rsq != ksq && piece_on(rsq) != rook; --rsq) {} else if (token == 'Q') - for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + for (rsq = relative_square(c, SQ_A1); rsq != ksq && piece_on(rsq) != rook; ++rsq) {} else if (token >= 'A' && token <= 'H') - rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + rsq = make_square(File(token - 'A'), rank); else continue; - set_castling_right(c, rsq); + if (rsq != ksq) + set_castling_right(c, rsq); } // 4. En passant square. Ignore if no pawn capture is possible - if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == '3' || row == '6'))) +#ifdef HORDE + if (((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (sideToMove ? (((var & HORDE_VARIANT) && row == '2') || row == '3') : row == '6'))) +#else + if (((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (sideToMove ? row == '3' : row == '6'))) +#endif { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) st->epSquare = SQ_NONE; + else if (SquareBB[st->epSquare] & pieces()) + st->epSquare = SQ_NONE; + else if (sideToMove == WHITE && (shift_bb(SquareBB[st->epSquare]) & pieces())) + st->epSquare = SQ_NONE; + else if (sideToMove == BLACK && (shift_bb(SquareBB[st->epSquare]) & pieces())) + st->epSquare = SQ_NONE; + else if (sideToMove == WHITE && !(shift_bb(SquareBB[st->epSquare]) & pieces(BLACK, PAWN))) + st->epSquare = SQ_NONE; + else if (sideToMove == BLACK && !(shift_bb(SquareBB[st->epSquare]) & pieces(WHITE, PAWN))) + st->epSquare = SQ_NONE; } // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; +#ifdef THREECHECK + st->checksGiven[WHITE] = CHECKS_0; + st->checksGiven[BLACK] = CHECKS_0; + if ((var & THREECHECK_VARIANT) != 0) + { + // 7. Checks given counter for Three-Check positions + if ((ss >> std::skipws >> token) && token == '+') + { + ss >> token; + switch(token - '0') + { + case 0: st->checksGiven[WHITE] = CHECKS_0; break; + case 1: st->checksGiven[WHITE] = CHECKS_1; break; + case 2: st->checksGiven[WHITE] = CHECKS_2; break; + case 3: st->checksGiven[WHITE] = CHECKS_3; break; + default: st->checksGiven[WHITE] = CHECKS_NB; + } + ss >> token; // skip '+' + switch(token - '0') + { + case 0: st->checksGiven[BLACK] = CHECKS_0; break; + case 1: st->checksGiven[BLACK] = CHECKS_1; break; + case 2: st->checksGiven[BLACK] = CHECKS_2; break; + case 3: st->checksGiven[BLACK] = CHECKS_3; break; + default : st->checksGiven[BLACK] = CHECKS_NB; + } + } + } +#endif + // Convert from fullmove starting from 1 to ply starting from 0, // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); - chess960 = isChess960; + variant = var; thisThread = th; set_state(st); @@ -344,11 +431,24 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_state(StateInfo* si) const { - si->key = si->pawnKey = si->materialKey = 0; + si->key = si->pawnKey = si->materialKey = variant; si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; si->psq = SCORE_ZERO; - si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); +#ifdef HORDE + if (is_horde() && square(sideToMove) == SQ_NONE) + si->checkersBB = 0; + else +#endif +#ifdef ATOMIC + if (is_atomic() && (square(sideToMove) == SQ_NONE || + (attacks_from(square(sideToMove)) & square(~sideToMove)))) + si->checkersBB = 0; + else +#endif + { + si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + } for (Bitboard b = pieces(); b; ) { @@ -380,6 +480,12 @@ void Position::set_state(StateInfo* si) const { for (Color c = WHITE; c <= BLACK; ++c) for (PieceType pt = KNIGHT; pt <= QUEEN; ++pt) si->nonPawnMaterial[c] += pieceCount[c][pt] * PieceValue[MG][pt]; + +#ifdef THREECHECK + for (Color c = WHITE; c <= BLACK; ++c) + for (Checks n = CHECKS_1; n <= si->checksGiven[c]; ++n) + si->key ^= Zobrist::checks[c][n]; +#endif } @@ -411,6 +517,7 @@ const string Position::fen() const { ss << (sideToMove == WHITE ? " w " : " b "); + bool chess960 = is_chess960(); if (can_castle(WHITE_OO)) ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE | KING_SIDE))) : 'K'); @@ -429,6 +536,11 @@ const string Position::fen() const { ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; +#ifdef THREECHECK + if (is_three_check()) + ss << " +" << st->checksGiven[WHITE] << "+" << st->checksGiven[BLACK]; +#endif + return ss.str(); } @@ -439,6 +551,14 @@ const string Position::fen() const { Phase Position::game_phase() const { Value npm = st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; +#ifdef HORDE + if (is_horde()) + npm = st->nonPawnMaterial[BLACK] + st->nonPawnMaterial[BLACK]; +#endif +#ifdef ATOMIC + if (is_atomic()) + npm += npm; +#endif npm = std::max(EndgameLimit, std::min(npm, MidgameLimit)); @@ -457,6 +577,9 @@ Bitboard Position::check_blockers(Color c, Color kingColor) const { Bitboard b, pinners, result = 0; Square ksq = square(kingColor); +#ifdef HORDE + if (is_horde() && ksq == SQ_NONE) return result; +#endif // Pinners are sliders that give check when a pinned piece is removed pinners = ( (pieces( ROOK, QUEEN) & PseudoAttacks[ROOK ][ksq]) @@ -498,7 +621,47 @@ bool Position::legal(Move m, Bitboard pinned) const { Square from = from_sq(m); assert(color_of(moved_piece(m)) == us); +#ifdef HORDE + assert(is_horde() && us == WHITE ? square(us) == SQ_NONE : piece_on(square(us)) == make_piece(us, KING)); +#else assert(piece_on(square(us)) == make_piece(us, KING)); +#endif + +#ifdef HORDE + // All pseudo-legal moves by the horde are legal + if (is_horde() && square(us) == SQ_NONE) + return true; +#endif +#ifdef ATOMIC + if (is_atomic()) + { + Square ksq = square(us); + Square to = to_sq(m); + if (capture(m) && (attacks_from(to) & ksq)) + return false; + if (type_of(piece_on(from)) != KING) + { + if (attacks_from(square(~us)) & ksq) + return true; + if (capture(m)) + { + Square capsq = type_of(m) == ENPASSANT ? make_square(file_of(to), rank_of(from)) : to; + Bitboard blast = attacks_from(to) & (pieces() ^ pieces(PAWN)); + if (blast & square(~us)) + return true; + Bitboard b = pieces() ^ ((blast | capsq) | from); + if (checkers() & b) + return false; + if ((attacks_bb< ROOK>(ksq, b) & pieces(~us, QUEEN, ROOK) & b) || + (attacks_bb(ksq, b) & pieces(~us, QUEEN, BISHOP) & b)) + return false; + return true; + } + } + else if (attacks_from(square(~us)) & to) + return true; + } +#endif // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after @@ -519,6 +682,21 @@ bool Position::legal(Move m, Bitboard pinned) const { && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); } +#ifdef ATOMIC + if (is_atomic() && type_of(piece_on(from)) == KING && type_of(m) != CASTLING) + { + Square ksq = square(~us); + Square to = to_sq(m); + if ((attacks_from(ksq) & from) && !(attacks_from(ksq) & to)) + { + if (attackers_to(to) & pieces(~us, KNIGHT, PAWN)) + return false; + Bitboard occupied = (pieces() ^ from) | to; + return !(attacks_bb< ROOK>(to, occupied) & pieces(~us, QUEEN, ROOK)) + && !(attacks_bb(to, occupied) & pieces(~us, QUEEN, BISHOP)); + } + } +#endif // 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. @@ -544,6 +722,51 @@ bool Position::pseudo_legal(const Move m) const { Square to = to_sq(m); Piece pc = moved_piece(m); +#ifdef KOTH + // If the game is already won or lost, further moves are illegal + if (is_koth() && (is_koth_win() || is_koth_loss())) + return false; +#endif +#ifdef HORDE + // If the game is already won or lost, further moves are illegal + if (is_horde() && is_horde_loss()) + return false; +#endif +#ifdef ATOMIC + if (is_atomic()) + { + // If the game is already won or lost, further moves are illegal + if (is_atomic_win() || is_atomic_loss()) + return false; + if (pc == NO_PIECE || color_of(pc) != us) + return false; + if (capture(m)) + { + if (type_of(pc) == KING) + return false; + Square ksq = square(us); + if ((pieces(us) & to) || (attacks_from(ksq) & to)) + return false; + if (!(attacks_from(square(~us)) & ksq)) + { + // Illegal pawn capture generated by killer move heuristic + if (type_of(pc) == PAWN && file_of(from) == file_of(to)) + return false; + Square capsq = type_of(m) == ENPASSANT ? make_square(file_of(to), rank_of(from)) : to; + Bitboard blast = attacks_from(to) & (pieces() ^ pieces(PAWN)); + if (blast & square(~us)) + return true; + Bitboard b = pieces() ^ ((blast | capsq) | from); + if (checkers() & b) + return false; + if ((attacks_bb< ROOK>(ksq, b) & pieces(~us, QUEEN, ROOK) & b) || + (attacks_bb(ksq, b) & pieces(~us, QUEEN, BISHOP) & b)) + return false; + } + } + } +#endif + // Use a slower but simpler function for uncommon cases if (type_of(m) != NORMAL) return MoveList(*this).contains(m); @@ -583,6 +806,10 @@ bool Position::pseudo_legal(const Move m) const { // Evasions generator already takes care to avoid some kind of illegal moves // and legal() relies on this. We therefore have to take care that the same // kind of moves are filtered out here. +#ifdef ATOMIC + if (is_atomic() && (attacks_from(square(~us)) & (type_of(pc) == KING ? to : square(us)))) + return true; +#endif if (checkers()) { if (type_of(pc) != KING) @@ -616,6 +843,47 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const { Square from = from_sq(m); Square to = to_sq(m); +#ifdef HORDE + if (is_horde() && ci.ksq == SQ_NONE) + return false; +#endif +#ifdef ATOMIC + if (is_atomic()) + { + if (is_horde() && ci.ksq == SQ_NONE) + return false; + // If kings are adjacent, there is no check + // If kings were adjacent, there may be direct checks + if (type_of(piece_on(from)) == KING) + { + if (attacks_from(ci.ksq) & to) + return false; + else if (attacks_from(ci.ksq) & from) + { + if (attackers_to(ci.ksq) & pieces(sideToMove, KNIGHT, PAWN)) + return true; + Bitboard occupied = (pieces() ^ from) | to; + return (attacks_bb< ROOK>(ci.ksq, occupied) & pieces(sideToMove, QUEEN, ROOK)) + || (attacks_bb(ci.ksq, occupied) & pieces(sideToMove, QUEEN, BISHOP)); + } + } + else if (attacks_from(ci.ksq) & square(sideToMove)) + return false; + } + if (is_atomic() && capture(m)) + { + // Do blasted pieces discover checks? + Square capsq = type_of(m) == ENPASSANT ? make_square(file_of(to), rank_of(from)) : to; + Bitboard blast = attacks_from(to) & (pieces() ^ pieces(PAWN)); + if (blast & ci.ksq) // Variant ending + return false; + Bitboard b = pieces() ^ ((blast | capsq) | from); + + return (attacks_bb< ROOK>(ci.ksq, b) & pieces(sideToMove, QUEEN, ROOK) & b) + || (attacks_bb(ci.ksq, b) & pieces(sideToMove, QUEEN, BISHOP) & b); + } +#endif + // Is there a direct check? if (ci.checkSquares[type_of(piece_on(from))] & to) return true; @@ -662,7 +930,6 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const { } } - /// Position::do_move() makes a move, and saves all information necessary /// to a StateInfo object. The move is assumed to be legal. Pseudo-legal /// moves should be filtered out before this function is called. @@ -725,7 +992,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(pt == PAWN); assert(to == st->epSquare); +#ifdef HORDE + assert((is_horde() && rank_of(to) == RANK_2) || + relative_rank(us, to) == RANK_6); +#else assert(relative_rank(us, to) == RANK_6); +#endif assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); @@ -743,6 +1015,44 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[them][captured][capsq]; st->materialKey ^= Zobrist::psq[them][captured][pieceCount[them][captured]]; +#ifdef ATOMIC + if (is_atomic()) // Remove the blast piece(s) + { + Bitboard blast = attacks_from(to); + while (blast) + { + Square bsq = pop_lsb(&blast); + if (bsq == from) + continue; + st->blast[bsq] = piece_on(bsq); + PieceType bpt = type_of(st->blast[bsq]); + if (bpt != NO_PIECE_TYPE && bpt != PAWN) + { + Color bc = color_of(st->blast[bsq]); + st->nonPawnMaterial[bc] -= PieceValue[MG][bpt]; + + // Update board and piece lists + remove_piece(bc, bpt, bsq); + + // Update material hash key + k ^= Zobrist::psq[bc][bpt][bsq]; + st->materialKey ^= Zobrist::psq[bc][bpt][pieceCount[bc][bpt]]; + + // Update incremental scores + st->psq -= PSQT::psq[bc][bpt][bsq]; + + // Update castling rights if needed + if (st->castlingRights && castlingRightsMask[bsq]) + { + int cr = castlingRightsMask[bsq]; + k ^= Zobrist::castling[st->castlingRights & cr]; + st->castlingRights &= ~cr; + } + } + } + } +#endif + prefetch(thisThread->materialTable[st->materialKey]); // Update incremental scores @@ -752,6 +1062,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->rule50 = 0; } +#ifdef ATOMIC + if (is_atomic() && captured) + k ^= Zobrist::psq[us][pt][from]; + else +#endif // Update hash key k ^= Zobrist::psq[us][pt][from] ^ Zobrist::psq[us][pt][to]; @@ -770,6 +1085,28 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->castlingRights &= ~cr; } +#ifdef THREECHECK + if (is_three_check() && givesCheck) + { + ++(st->checksGiven[sideToMove]); + Checks checksGiven = checks_given(); + assert(checksGiven < CHECKS_NB); + k ^= Zobrist::checks[sideToMove][checksGiven]; + } +#endif + +#ifdef ATOMIC + if (is_atomic() && captured) // Remove the blast piece(s) + { + st->blast[from] = piece_on(from); + remove_piece(us, pt, from); + // Update material (hash key already updated) + st->materialKey ^= Zobrist::psq[us][pt][pieceCount[us][pt]]; + if (pt != PAWN) + st->nonPawnMaterial[us] -= PieceValue[MG][pt]; + } + else +#endif // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) != CASTLING) move_piece(us, pt, from, to); @@ -784,7 +1121,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->epSquare = (from + to) / 2; k ^= Zobrist::enpassant[file_of(st->epSquare)]; } - +#ifdef ATOMIC + else if (is_atomic() && captured) + { + } +#endif else if (type_of(m) == PROMOTION) { PieceType promotion = promotion_type(m); @@ -808,6 +1149,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[MG][promotion]; } +#ifdef ATOMIC + if (is_atomic() && captured) + st->pawnKey ^= Zobrist::psq[us][PAWN][from]; + else +#endif // Update pawn hash key and prefetch access to pawnsTable st->pawnKey ^= Zobrist::psq[us][PAWN][from] ^ Zobrist::psq[us][PAWN][to]; prefetch(thisThread->pawnsTable[st->pawnKey]); @@ -816,6 +1162,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->rule50 = 0; } +#ifdef ATOMIC + if (is_atomic() && captured) + st->psq -= PSQT::psq[us][pt][from]; + else +#endif // Update incremental scores st->psq += PSQT::psq[us][pt][to] - PSQT::psq[us][pt][from]; @@ -825,6 +1176,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update the key with the final value st->key = k; +#ifdef ATOMIC + if (is_atomic() && captured && is_atomic_win()) + givesCheck = false; +#endif // Calculate checkers bitboard (if move gives check) st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; @@ -847,18 +1202,30 @@ void Position::undo_move(Move m) { Square from = from_sq(m); Square to = to_sq(m); PieceType pt = type_of(piece_on(to)); +#ifdef ATOMIC + if (is_atomic() && st->capturedType) // Restore the blast piece(s) + pt = type_of(st->blast[from]); +#endif + assert(empty(to) || color_of(piece_on(to)) == us); assert(empty(from) || type_of(m) == CASTLING); assert(st->capturedType != KING); if (type_of(m) == PROMOTION) { assert(relative_rank(us, to) == RANK_8); +#ifdef ATOMIC + if (!is_atomic() || !st->capturedType) + { +#endif assert(pt == promotion_type(m)); assert(pt >= KNIGHT && pt <= QUEEN); remove_piece(us, pt, to); put_piece(us, PAWN, to); +#ifdef ATOMIC + } +#endif pt = PAWN; } @@ -869,6 +1236,11 @@ void Position::undo_move(Move m) { } else { +#ifdef ATOMIC + if (is_atomic() && st->capturedType) // Restore the blast piece(s) + put_piece(us, pt, from); + else +#endif move_piece(us, pt, to, from); // Put the piece back at the source square if (st->capturedType) @@ -886,6 +1258,21 @@ void Position::undo_move(Move m) { assert(st->capturedType == PAWN); } +#ifdef ATOMIC + if (is_atomic() && st->capturedType) // Restore the blast piece(s) + { + Bitboard blast = attacks_from(to); // squares in blast radius + while (blast) + { + Square bsq = pop_lsb(&blast); + if (bsq == from) + continue; + PieceType bpt = type_of(st->blast[bsq]); + if (bpt != NO_PIECE_TYPE && bpt != PAWN) + put_piece(color_of(st->blast[bsq]), bpt, bsq); + } + } +#endif put_piece(~us, st->capturedType, capsq); // Restore the captured piece } } @@ -969,7 +1356,21 @@ Key Position::key_after(Move m) const { Key k = st->key ^ Zobrist::side; if (captured) + { k ^= Zobrist::psq[~us][captured][to]; +#ifdef ATOMIC + if (is_atomic()) + { + Bitboard blast = (attacks_from(to) & (pieces() ^ pieces(PAWN))) - from; + while (blast) + { + Square bsq = pop_lsb(&blast); + PieceType bpt = type_of(st->blast[bsq]); + k ^= Zobrist::psq[~us][bpt][bsq]; + } + } +#endif + } return k ^ Zobrist::psq[us][pt][to] ^ Zobrist::psq[us][pt][from]; } @@ -995,7 +1396,11 @@ Value Position::see(Move m) const { Square from, to; Bitboard occupied, attackers, stmAttackers; +#ifdef HORDE + Value swapList[SQUARE_NB]; +#else Value swapList[32]; +#endif int slIndex = 1; PieceType captured; Color stm; @@ -1039,7 +1444,11 @@ Value Position::see(Move m) const { captured = type_of(piece_on(from)); do { +#ifdef HORDE + assert(slIndex < SQUARE_NB); +#else assert(slIndex < 32); +#endif // Add the new entry to the swap list swapList[slIndex] = -swapList[slIndex - 1] + PieceValue[MG][captured]; @@ -1070,12 +1479,12 @@ bool Position::is_draw() const { return true; StateInfo* stp = st; - for (int i = 2, e = std::min(st->rule50, st->pliesFromNull); i <= e; i += 2) + for (int i = 2, rep = 1, e = std::min(st->rule50, st->pliesFromNull); i <= e; i += 2) { stp = stp->previous->previous; - if (stp->key == st->key) - return true; // Draw at first repetition + if (stp->key == st->key && (++rep >= 2 + (gamePly - i < thisThread->rootPly))) + return true; // Draw at first repetition in search, and second repetition in game tree. } return false; @@ -1111,7 +1520,7 @@ void Position::flip() { std::getline(ss, token); // Half and full moves f += token; - set(f, is_chess960(), this_thread()); + set(f, variant, this_thread()); assert(pos_is_ok()); } @@ -1132,18 +1541,62 @@ bool Position::pos_is_ok(int* failedStep) const { *failedStep = step; if (step == Default) + { + Square wksq = square(WHITE), bksq = square(BLACK); if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING +#ifdef HORDE +#ifdef ATOMIC + || (is_horde() ? wksq != SQ_NONE : ((!is_atomic() || wksq != SQ_NONE) && piece_on(wksq) != W_KING)) +#else + || (is_horde() ? wksq != SQ_NONE : piece_on(wksq) != W_KING) +#endif +#else +#ifdef ATOMIC + || ((!is_atomic() || wksq != SQ_NONE) && piece_on(wksq) != W_KING) +#else + || piece_on(wksq) != W_KING +#endif +#endif +#ifdef ATOMIC + || ((!is_atomic() || bksq != SQ_NONE) && piece_on(bksq) != B_KING) +#else + || piece_on(bksq) != B_KING +#endif || ( ep_square() != SQ_NONE +#ifdef HORDE + && (!is_horde() || relative_rank(sideToMove, ep_square()) != RANK_7) +#endif && relative_rank(sideToMove, ep_square()) != RANK_6)) return false; + } if (step == King) + { +#ifdef HORDE + if (is_horde()) + { + if ( std::count(board, board + SQUARE_NB, W_KING) != 0 + || std::count(board, board + SQUARE_NB, B_KING) != 1 + || (sideToMove == WHITE && attackers_to(square(~sideToMove)) & pieces(sideToMove))) + return false; + } else +#endif +#ifdef ATOMIC + if (is_atomic() && (is_atomic_win() || is_atomic_loss())) + { + if (std::count(board, board + SQUARE_NB, W_KING) + + std::count(board, board + SQUARE_NB, B_KING) != 1) + return false; + } + else if (is_atomic() && (attacks_from(square(~sideToMove)) & square(sideToMove))) + { + } else +#endif if ( std::count(board, board + SQUARE_NB, W_KING) != 1 || std::count(board, board + SQUARE_NB, B_KING) != 1 || attackers_to(square(~sideToMove)) & pieces(sideToMove)) return false; + } if (step == Bitboards) { diff --git a/src/position.h b/src/position.h index 5d7cb372df6..e6929b71cdc 100644 --- a/src/position.h +++ b/src/position.h @@ -27,6 +27,13 @@ #include "bitboard.h" #include "types.h" +#define STANDARD_VARIANT 0 +#define CHESS960_VARIANT 1 << 1 +#define KOTH_VARIANT 1 << 2 +#define THREECHECK_VARIANT 1 << 3 +#define HORDE_VARIANT 1 << 4 +#define ATOMIC_VARIANT 1 << 5 + class Position; class Thread; @@ -64,6 +71,9 @@ struct StateInfo { int castlingRights; int rule50; int pliesFromNull; +#ifdef THREECHECK + Checks checksGiven[COLOR_NB]; +#endif Score psq; Square epSquare; @@ -71,6 +81,9 @@ struct StateInfo { Key key; Bitboard checkersBB; PieceType capturedType; +#ifdef ATOMIC + Piece blast[SQUARE_NB]; +#endif StateInfo* previous; }; @@ -88,11 +101,15 @@ class Position { Position() = default; // To define the global object RootPos Position(const Position&) = delete; Position(const Position& pos, Thread* th) { *this = pos; thisThread = th; } - Position(const std::string& f, bool c960, Thread* th) { set(f, c960, th); } + + Position(const std::string& f, Thread* t) { set(f, STANDARD_VARIANT, t); } + Position(const std::string& f, int var, Thread* t) { set(f, var, t); } + Position& operator=(const Position&); // To assign RootPos from UCI // FEN string input/output - void set(const std::string& fenStr, bool isChess960, Thread* th); + void set(const std::string& fenStr, int var, Thread* th); + const std::string fen() const; // Position representation @@ -163,6 +180,29 @@ class Position { Phase game_phase() const; int game_ply() const; bool is_chess960() const; +#ifdef ATOMIC + bool is_atomic() const; + bool is_atomic_win() const; + bool is_atomic_loss() const; +#endif +#ifdef HORDE + bool is_horde() const; + bool is_horde_loss() const; +#endif +#ifdef KOTH + bool is_koth() const; + bool is_koth_win() const; + bool is_koth_loss() const; + int koth_distance(Color c) const; +#endif +#ifdef THREECHECK + bool is_three_check() const; + bool is_three_check_win() const; + bool is_three_check_loss() const; + int checks_count() const; + Checks checks_given() const; + Checks checks_taken() const; +#endif Thread* this_thread() const; uint64_t nodes_searched() const; void set_nodes_searched(uint64_t n); @@ -194,7 +234,11 @@ class Position { Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard byColorBB[COLOR_NB]; int pieceCount[COLOR_NB][PIECE_TYPE_NB]; +#ifdef HORDE + Square pieceList[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +#else Square pieceList[COLOR_NB][PIECE_TYPE_NB][16]; +#endif int index[SQUARE_NB]; int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; @@ -205,7 +249,8 @@ class Position { Color sideToMove; Thread* thisThread; StateInfo* st; - bool chess960; + int variant; + }; extern std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -259,10 +304,47 @@ template inline const Square* Position::squares(Color c) const { } template inline Square Position::square(Color c) const { +#ifdef HORDE + if (is_horde() && c == WHITE) + { + assert(pieceCount[c][Pt] == 0); + return SQ_NONE; + } +#endif +#ifdef ATOMIC + if (is_atomic() && pieceCount[c][Pt] == 0) + return SQ_NONE; +#endif assert(pieceCount[c][Pt] == 1); return pieceList[c][Pt][0]; } +#ifdef THREECHECK +inline bool Position::is_three_check() const { + return variant & THREECHECK_VARIANT; +} + +inline bool Position::is_three_check_win() const { + return st->checksGiven[sideToMove] == CHECKS_3; +} + +inline bool Position::is_three_check_loss() const { + return st->checksGiven[~sideToMove] == CHECKS_3; +} + +inline int Position::checks_count() const { + return st->checksGiven[WHITE] + st->checksGiven[BLACK]; +} + +inline Checks Position::checks_given() const { + return st->checksGiven[sideToMove]; +} + +inline Checks Position::checks_taken() const { + return st->checksGiven[~sideToMove]; +} +#endif + inline Square Position::ep_square() const { return st->epSquare; } @@ -316,6 +398,10 @@ inline Bitboard Position::pinned_pieces(Color c) const { } inline bool Position::pawn_passed(Color c, Square s) const { +#ifdef HORDE + if (is_horde()) + return !(pieces(~c, PAWN) & forward_bb(c, s)); +#endif return !(pieces(~c, PAWN) & passed_pawn_mask(c, s)); } @@ -366,8 +452,62 @@ inline bool Position::opposite_bishops() const { && opposite_colors(square(WHITE), square(BLACK)); } +#ifdef ATOMIC +inline bool Position::is_atomic() const { + return variant & ATOMIC_VARIANT; +} + +// Loss if king is captured (Atomic) +inline bool Position::is_atomic_win() const { + return count(~sideToMove) == 0; +} + +// Loss if king is captured (Atomic) +inline bool Position::is_atomic_loss() const { + return count(sideToMove) == 0; +} +#endif + +#ifdef HORDE +inline bool Position::is_horde() const { + return variant & HORDE_VARIANT; +} + +// Loss if horde is captured (Horde) +inline bool Position::is_horde_loss() const { + return count(WHITE) == 0; +} +#endif + +#ifdef KOTH +inline bool Position::is_koth() const { + return variant & KOTH_VARIANT; +} + +// Win if king is in the center (KOTH) +inline bool Position::is_koth_win() const { + return koth_distance(sideToMove) == 0; +} + +// Loss if king is in the center (KOTH) +inline bool Position::is_koth_loss() const { + return koth_distance(~sideToMove) == 0; +} + +inline int Position::koth_distance(Color c) const { + Square ksq = square(c); + int sdistance = + distance(ksq, SQ_D4) + + distance(ksq, SQ_E4) + + distance(ksq, SQ_D5) + + distance(ksq, SQ_E5); + // Return 0 if in the center, weighted average distance otherwise + return sdistance < 4 ? 0 : (sdistance + 1) / 4; +} +#endif + inline bool Position::is_chess960() const { - return chess960; + return variant & CHESS960_VARIANT; } inline bool Position::capture_or_promotion(Move m) const { @@ -411,6 +551,10 @@ inline void Position::remove_piece(Color c, PieceType pt, Square s) { byTypeBB[ALL_PIECES] ^= s; byTypeBB[pt] ^= s; byColorBB[c] ^= s; +#ifdef ATOMIC + if (is_atomic()) + board[s] = NO_PIECE; +#endif /* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */ Square lastSquare = pieceList[c][pt][--pieceCount[c][pt]]; index[lastSquare] = index[s]; diff --git a/src/search.cpp b/src/search.cpp index 1e9c5010c97..2a2f8941b64 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -227,7 +227,7 @@ template uint64_t Search::perft(Position&, Depth); void MainThread::search() { Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); + Time.init(Limits, us, rootPly = rootPos.game_ply()); int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns DrawValue[ us] = VALUE_DRAW - Value(contempt); @@ -249,9 +249,20 @@ void MainThread::search() { if (rootMoves.empty()) { rootMoves.push_back(RootMove(MOVE_NONE)); - sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) - << sync_endl; + Value score = rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW; +#ifdef KOTH + if (rootPos.is_koth() && rootPos.is_koth_loss()) + score = -VALUE_MATE; +#endif +#ifdef HORDE + if (rootPos.is_horde() && rootPos.is_horde_loss()) + score = -VALUE_MATE; +#endif +#ifdef ATOMIC + if (rootPos.is_atomic() && rootPos.is_atomic_loss()) + score = -VALUE_MATE; +#endif + sync_cout << "info depth 0 score " << UCI::value(score) << sync_endl; } else { @@ -337,6 +348,7 @@ void MainThread::search() { if (bestThread != this) sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; + // Best move could be MOVE_NONE when searching on a terminal position sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) @@ -575,6 +587,9 @@ namespace { // Step 1. Initialize node Thread* thisThread = pos.this_thread(); inCheck = pos.checkers(); +#ifdef THREECHECK + int checks = pos.is_three_check() ? pos.checks_count() : CHECKS_0; +#endif moveCount = quietCount = ss->moveCount = 0; bestValue = -VALUE_INFINITE; ss->ply = (ss-1)->ply + 1; @@ -599,6 +614,37 @@ namespace { if (!RootNode) { +#ifdef KOTH + // Check for an instant win/loss (King of the Hill) + if (pos.is_koth()) + { + if (pos.is_koth_win()) + return mate_in(ss->ply + 1); + if (pos.is_koth_loss()) + return mated_in(ss->ply); + } +#endif +#ifdef THREECHECK + // Check for an instant win/loss (Three-Check) + if (pos.is_three_check()) + { + if (pos.is_three_check_win()) + return mate_in(ss->ply + 1); + if (pos.is_three_check_loss()) + return mated_in(ss->ply); + } +#endif +#ifdef HORDE + // Check for an instant loss (Horde) + if (pos.is_horde() && pos.is_horde_loss()) + return mated_in(ss->ply); +#endif +#ifdef ATOMIC + // Check for an instant loss (Atomic) + if (pos.is_atomic() && pos.is_atomic_loss()) + return mated_in(ss->ply); +#endif + // Step 2. Check for aborted search and immediate draw if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY) return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) @@ -650,6 +696,18 @@ namespace { } // Step 4a. Tablebase probe +#ifdef KOTH + if (pos.is_koth()) {} else +#endif +#ifdef THREECHECK + if (pos.is_three_check()) {} else +#endif +#ifdef HORDE + if (pos.is_horde()) {} else +#endif +#ifdef ATOMIC + if (pos.is_atomic()) {} else +#endif if (!RootNode && TB::Cardinality) { int piecesCnt = pos.count(WHITE) + pos.count(BLACK); @@ -713,14 +771,26 @@ namespace { // Step 6. Razoring (skipped when in check) if ( !PvNode && depth < 4 * ONE_PLY +#ifdef THREECHECK + && eval + (razor_margin[depth] * (pos.is_three_check() ? 1 + pos.checks_count() : 1)) <= alpha +#else && eval + razor_margin[depth] <= alpha +#endif && ttMove == MOVE_NONE) { if ( depth <= ONE_PLY +#ifdef THREECHECK + && eval + (razor_margin[3 * ONE_PLY] * (pos.is_three_check() ? 1 + pos.checks_count() : 1)) <= alpha) +#else && eval + razor_margin[3 * ONE_PLY] <= alpha) +#endif return qsearch(pos, ss, alpha, beta, DEPTH_ZERO); +#ifdef THREECHECK + Value ralpha = alpha - (razor_margin[depth] * (pos.is_three_check() ? 1 + pos.checks_count() : 1)); +#else Value ralpha = alpha - razor_margin[depth]; +#endif Value v = qsearch(pos, ss, ralpha, ralpha+1, DEPTH_ZERO); if (v <= ralpha) return v; @@ -779,7 +849,11 @@ namespace { // and a reduced search returns a value much above beta, we can (almost) // safely prune the previous move. if ( !PvNode +#ifdef THREECHECK + && depth >= (5 + checks) * ONE_PLY +#else && depth >= 5 * ONE_PLY +#endif && abs(beta) < VALUE_MATE_IN_MAX_PLY) { Value rbeta = std::min(beta + 200, VALUE_INFINITE); @@ -832,7 +906,11 @@ namespace { ||(ss-2)->staticEval == VALUE_NONE; singularExtensionNode = !RootNode +#ifdef THREECHECK + && depth >= 8 * ONE_PLY - checks +#else && depth >= 8 * ONE_PLY +#endif && ttMove != MOVE_NONE /* && ttValue != VALUE_NONE Already implicit in the next condition */ && abs(ttValue) < VALUE_KNOWN_WIN @@ -875,6 +953,9 @@ namespace { captureOrPromotion = pos.capture_or_promotion(move); givesCheck = type_of(move) == NORMAL && !ci.dcCandidates +#ifdef ATOMIC + && !pos.is_atomic() +#endif ? ci.checkSquares[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) : pos.gives_check(move, ci); @@ -915,7 +996,11 @@ namespace { && bestValue > VALUE_MATED_IN_MAX_PLY) { // Move count based pruning +#ifdef THREECHECK + if ( depth < (16 - checks) * ONE_PLY +#else if ( depth < 16 * ONE_PLY +#endif && moveCount >= FutilityMoveCounts[improving][depth]) continue; @@ -929,7 +1014,11 @@ namespace { predictedDepth = newDepth - reduction(improving, depth, moveCount); // Futility pruning: parent node +#ifdef THREECHECK + if (predictedDepth < (7 - checks) * ONE_PLY) +#else if (predictedDepth < 7 * ONE_PLY) +#endif { futilityValue = ss->staticEval + futility_margin(predictedDepth) + 256; @@ -962,7 +1051,11 @@ namespace { // Step 15. Reduced depth search (LMR). If the move fails high it will be // re-searched at full depth. +#ifdef THREECHECK + if ( depth >= (3 + checks) * ONE_PLY +#else if ( depth >= 3 * ONE_PLY +#endif && moveCount > 1 && !captureOrPromotion && move != ss->killers[0] @@ -1105,8 +1198,15 @@ namespace { // must be mate or stalemate. If we are in a singular extension search then // return a fail low score. if (!moveCount) + { +#ifdef HORDE + if (pos.is_horde() && pos.is_horde_loss()) + bestValue = excludedMove ? alpha : mated_in(ss->ply); + else +#endif bestValue = excludedMove ? alpha : inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()]; + } // Quiet best move: update killers, history and countermoves else if (bestMove && !pos.capture_or_promotion(bestMove)) @@ -1147,7 +1247,11 @@ namespace { const bool PvNode = NT == PV; assert(NT == PV || NT == NonPV); +#ifdef ATOMIC + assert((pos.is_atomic() && pos.is_atomic_loss()) || InCheck == !!pos.checkers()); +#else assert(InCheck == !!pos.checkers()); +#endif assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); @@ -1171,6 +1275,45 @@ namespace { ss->currentMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; +#ifdef KOTH + // Check for an instant win or loss (King of the Hill) + if (pos.is_koth()) + { + if (pos.is_koth_win()) + return mate_in(ss->ply+1); + if (pos.is_koth_loss()) + return mated_in(ss->ply); + } +#endif +#ifdef THREECHECK + // Check for an instant win (Three-Check) + if (pos.is_three_check()) + { + if (pos.is_three_check_win()) + return mate_in(ss->ply + 1); + if (pos.is_three_check_loss()) + return mated_in(ss->ply); + } +#endif +#ifdef HORDE + // Check for an instant win (Horde) + if (pos.is_horde()) + { + if (pos.is_horde_loss()) + return mated_in(ss->ply); + } +#endif +#ifdef ATOMIC + // Check for an instant win (Atomic) + if (pos.is_atomic()) + { + if (pos.is_atomic_win()) + return mate_in(ss->ply + 1); + if (pos.is_atomic_loss()) + return mated_in(ss->ply); + } +#endif + // Check for an instant draw or if the maximum ply has been reached if (pos.is_draw() || ss->ply >= MAX_PLY) return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) @@ -1254,6 +1397,9 @@ namespace { assert(is_ok(move)); givesCheck = type_of(move) == NORMAL && !ci.dcCandidates +#ifdef ATOMIC + && !pos.is_atomic() +#endif ? ci.checkSquares[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) : pos.gives_check(move, ci); @@ -1306,6 +1452,8 @@ namespace { : -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY); pos.undo_move(move); + assert(value > -VALUE_INFINITE); + assert(value < VALUE_INFINITE); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Check for new best move @@ -1597,6 +1745,8 @@ bool RootMove::extract_ponder_from_tt(Position& pos) bool ttHit; assert(pv.size() == 1); + if (pv[0] == MOVE_NONE) // Not pondering + return false; pos.do_move(pv[0], st, pos.gives_check(pv[0], CheckInfo(pos))); TTEntry* tte = TT.probe(pos.key(), ttHit); diff --git a/src/syzygy/tbcore.cpp b/src/syzygy/tbcore.cpp index 71fab6d6437..7129cae875d 100644 --- a/src/syzygy/tbcore.cpp +++ b/src/syzygy/tbcore.cpp @@ -18,6 +18,8 @@ #include #endif #include "tbcore.h" +#include "tbprobe.h" + #define TBMAX_PIECE 254 #define TBMAX_PAWN 256 @@ -53,7 +55,7 @@ static struct TBHashEntry TB_hash[1 << TBHASHBITS][HSHMAX]; static struct DTZTableEntry DTZ_table[DTZ_ENTRIES]; static void init_indices(void); -static uint64 calc_key_from_pcs(int *pcs, int mirror); + static void free_wdl_entry(struct TBEntry *entry); static void free_dtz_entry(struct TBEntry *entry); @@ -201,8 +203,8 @@ static void init_tb(char *str) for (i = 0; i < 8; i++) if (pcs[i] != pcs[i+8]) break; - key = calc_key_from_pcs(pcs, 0); - key2 = calc_key_from_pcs(pcs, 1); + key = Tablebases::calc_key_from_pcs(pcs, 0); + key2 = Tablebases::calc_key_from_pcs(pcs, 1); if (pcs[TB_WPAWN] + pcs[TB_BPAWN] == 0) { if (TBnum_piece == TBMAX_PIECE) { printf("TBMAX_PIECE limit too low!\n"); @@ -224,7 +226,7 @@ static void init_tb(char *str) entry->symmetric = (key == key2); entry->has_pawns = (pcs[TB_WPAWN] + pcs[TB_BPAWN] > 0); if (entry->num > Tablebases::MaxCardinality) - Tablebases::MaxCardinality = entry->num; + Tablebases::MaxCardinality = entry->num; if (entry->has_pawns) { struct TBEntry_pawn *ptr = (struct TBEntry_pawn *)entry; @@ -253,123 +255,6 @@ static void init_tb(char *str) if (key2 != key) add_to_hash(entry, key2); } -void Tablebases::init(const std::string& path) -{ - char str[16]; - int i, j, k, l; - - if (initialized) { - free(path_string); - free(paths); - struct TBEntry *entry; - for (i = 0; i < TBnum_piece; i++) { - entry = (struct TBEntry *)&TB_piece[i]; - free_wdl_entry(entry); - } - for (i = 0; i < TBnum_pawn; i++) { - entry = (struct TBEntry *)&TB_pawn[i]; - free_wdl_entry(entry); - } - for (i = 0; i < DTZ_ENTRIES; i++) - if (DTZ_table[i].entry) - free_dtz_entry(DTZ_table[i].entry); - } else { - init_indices(); - initialized = true; - } - - const char *p = path.c_str(); - if (strlen(p) == 0 || !strcmp(p, "")) return; - path_string = (char *)malloc(strlen(p) + 1); - strcpy(path_string, p); - num_paths = 0; - for (i = 0;; i++) { - if (path_string[i] != SEP_CHAR) - num_paths++; - while (path_string[i] && path_string[i] != SEP_CHAR) - i++; - if (!path_string[i]) break; - path_string[i] = 0; - } - paths = (char **)malloc(num_paths * sizeof(char *)); - for (i = j = 0; i < num_paths; i++) { - while (!path_string[j]) j++; - paths[i] = &path_string[j]; - while (path_string[j]) j++; - } - - LOCK_INIT(TB_mutex); - - TBnum_piece = TBnum_pawn = 0; - MaxCardinality = 0; - - for (i = 0; i < (1 << TBHASHBITS); i++) - for (j = 0; j < HSHMAX; j++) { - TB_hash[i][j].key = 0ULL; - TB_hash[i][j].ptr = NULL; - } - - for (i = 0; i < DTZ_ENTRIES; i++) - DTZ_table[i].entry = NULL; - - for (i = 1; i < 6; i++) { - sprintf(str, "K%cvK", pchr[i]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) { - sprintf(str, "K%cvK%c", pchr[i], pchr[j]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) { - sprintf(str, "K%c%cvK", pchr[i], pchr[j]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) - for (k = 1; k < 6; k++) { - sprintf(str, "K%c%cvK%c", pchr[i], pchr[j], pchr[k]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) - for (k = j; k < 6; k++) { - sprintf(str, "K%c%c%cvK", pchr[i], pchr[j], pchr[k]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) - for (k = i; k < 6; k++) - for (l = (i == k) ? j : k; l < 6; l++) { - sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) - for (k = j; k < 6; k++) - for (l = 1; l < 6; l++) { - sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]); - init_tb(str); - } - - for (i = 1; i < 6; i++) - for (j = i; j < 6; j++) - for (k = j; k < 6; k++) - for (l = k; l < 6; l++) { - sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]); - init_tb(str); - } - - printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn); -} - static const signed char offdiag[] = { 0,-1,-1,-1,-1,-1,-1,-1, 1, 0,-1,-1,-1,-1,-1,-1, @@ -941,14 +826,6 @@ static void calc_symlen(struct PairsData *d, int s, char *tmp) tmp[s] = 1; } -ushort ReadUshort(ubyte* d) { - return ushort(d[0] | (d[1] << 8)); -} - -uint32 ReadUint32(ubyte* d) { - return d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); -} - static struct PairsData *setup_pairs(unsigned char *data, uint64 tb_size, uint64 *size, unsigned char **next, ubyte *flags, int wdl) { struct PairsData *d; @@ -969,12 +846,12 @@ static struct PairsData *setup_pairs(unsigned char *data, uint64 tb_size, uint64 int blocksize = data[1]; int idxbits = data[2]; - int real_num_blocks = ReadUint32(&data[4]); + int real_num_blocks = Tablebases::ReadUint32(&data[4]); int num_blocks = real_num_blocks + *(ubyte *)(&data[3]); int max_len = data[8]; int min_len = data[9]; int h = max_len - min_len + 1; - int num_syms = ReadUshort(&data[10 + 2 * h]); + int num_syms = Tablebases::ReadUshort(&data[10 + 2 * h]); d = (struct PairsData *)malloc(sizeof(struct PairsData) + (h - 1) * sizeof(base_t) + num_syms); d->blocksize = blocksize; d->idxbits = idxbits; @@ -999,7 +876,7 @@ static struct PairsData *setup_pairs(unsigned char *data, uint64 tb_size, uint64 d->base[h - 1] = 0; for (i = h - 2; i >= 0; i--) - d->base[i] = (d->base[i + 1] + ReadUshort((ubyte*)(d->offset + i)) - ReadUshort((ubyte*)(d->offset + i + 1))) / 2; + d->base[i] = (d->base[i + 1] + Tablebases::ReadUshort((ubyte*)(d->offset + i)) - Tablebases::ReadUshort((ubyte*)(d->offset + i + 1))) / 2; for (i = 0; i < h; i++) d->base[i] <<= 64 - (min_len + i); @@ -1299,46 +1176,6 @@ static ubyte decompress_pairs(struct PairsData *d, uint64 idx) return sympat[3 * sym]; } -void load_dtz_table(char *str, uint64 key1, uint64 key2) -{ - int i; - struct TBEntry *ptr, *ptr3; - struct TBHashEntry *ptr2; - - DTZ_table[0].key1 = key1; - DTZ_table[0].key2 = key2; - DTZ_table[0].entry = NULL; - - // find corresponding WDL entry - ptr2 = TB_hash[key1 >> (64 - TBHASHBITS)]; - for (i = 0; i < HSHMAX; i++) - if (ptr2[i].key == key1) break; - if (i == HSHMAX) return; - ptr = ptr2[i].ptr; - - ptr3 = (struct TBEntry *)malloc(ptr->has_pawns - ? sizeof(struct DTZEntry_pawn) - : sizeof(struct DTZEntry_piece)); - - ptr3->data = map_file(str, DTZSUFFIX, &ptr3->mapping); - ptr3->key = ptr->key; - ptr3->num = ptr->num; - ptr3->symmetric = ptr->symmetric; - ptr3->has_pawns = ptr->has_pawns; - if (ptr3->has_pawns) { - struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr3; - entry->pawns[0] = ((struct TBEntry_pawn *)ptr)->pawns[0]; - entry->pawns[1] = ((struct TBEntry_pawn *)ptr)->pawns[1]; - } else { - struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr3; - entry->enc_type = ((struct TBEntry_piece *)ptr)->enc_type; - } - if (!init_table_dtz(ptr3)) - free(ptr3); - else - DTZ_table[0].entry = ptr3; -} - static void free_wdl_entry(struct TBEntry *entry) { unmap_file(entry->data, entry->mapping); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 7bce67eabea..ce73d70ef1b 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -51,12 +51,12 @@ static void prt_str(Position& pos, char *str, int mirror) // Given a position, produce a 64-bit material signature key. // If the engine supports such a key, it should equal the engine's key. -static uint64 calc_key(Position& pos, int mirror) +static uint64_t calc_key(Position& pos, int mirror) { Color color; PieceType pt; int i; - uint64 key = 0; + uint64_t key = 0; color = !mirror ? WHITE : BLACK; for (pt = PAWN; pt <= KING; ++pt) @@ -70,29 +70,6 @@ static uint64 calc_key(Position& pos, int mirror) return key; } -// Produce a 64-bit material key corresponding to the material combination -// defined by pcs[16], where pcs[1], ..., pcs[6] is the number of white -// pawns, ..., kings and pcs[9], ..., pcs[14] is the number of black -// pawns, ..., kings. -static uint64 calc_key_from_pcs(int *pcs, int mirror) -{ - int color; - PieceType pt; - int i; - uint64 key = 0; - - color = !mirror ? 0 : 8; - for (pt = PAWN; pt <= KING; ++pt) - for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[WHITE][pt][i]; - color ^= 8; - for (pt = PAWN; pt <= KING; ++pt) - for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[BLACK][pt][i]; - - return key; -} - bool is_little_endian() { union { int i; @@ -102,7 +79,7 @@ bool is_little_endian() { return x.c[0] == 1; } -static ubyte decompress_pairs(struct PairsData *d, uint64 idx) +static ubyte decompress_pairs(struct PairsData *d, uint64_t idx) { static const bool isLittleEndian = is_little_endian(); return isLittleEndian ? decompress_pairs(d, idx) @@ -114,8 +91,8 @@ static int probe_wdl_table(Position& pos, int *success) { struct TBEntry *ptr; struct TBHashEntry *ptr2; - uint64 idx; - uint64 key; + uint64_t idx; + uint64_t key; int i; ubyte res; int p[TBPIECES]; @@ -204,7 +181,7 @@ static int probe_wdl_table(Position& pos, int *success) (PieceType)(pc[i] & 0x07)); do { p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); + } while (bb && i < TBPIECES); } idx = encode_pawn(entry, entry->file[f].norm[bside], p, entry->file[f].factor[bside]); res = decompress_pairs(entry->file[f].precomp[bside], idx); @@ -216,12 +193,12 @@ static int probe_wdl_table(Position& pos, int *success) static int probe_dtz_table(Position& pos, int wdl, int *success) { struct TBEntry *ptr; - uint64 idx; + uint64_t idx; int i, res; int p[TBPIECES]; // Obtain the position's material signature key. - uint64 key = pos.material_key(); + uint64_t key = pos.material_key(); if (DTZ_table[0].key1 != key && DTZ_table[0].key2 != key) { for (i = 1; i < DTZ_ENTRIES; i++) @@ -247,7 +224,7 @@ static int probe_dtz_table(Position& pos, int wdl, int *success) free_dtz_entry(DTZ_table[DTZ_ENTRIES-1].entry); for (i = DTZ_ENTRIES - 1; i > 0; i--) DTZ_table[i] = DTZ_table[i - 1]; - load_dtz_table(str, calc_key(pos, mirror), calc_key(pos, !mirror)); + Tablebases::load_dtz_table(str, calc_key(pos, mirror), calc_key(pos, !mirror)); } } @@ -314,7 +291,7 @@ static int probe_dtz_table(Position& pos, int wdl, int *success) (PieceType)(pc[i] & 0x07)); do { p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); + } while (bb && i < TBPIECES); } idx = encode_pawn((struct TBEntry_pawn *)entry, entry->file[f].norm, p, entry->file[f].factor); res = decompress_pairs(entry->file[f].precomp, idx); @@ -692,6 +669,18 @@ static Value wdl_to_Value[5] = { bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score) { int success; +#ifdef KOTH + if (pos.is_koth()) return false; +#endif +#ifdef THREECHECK + if (pos.is_three_check()) return false; +#endif +#ifdef HORDE + if (pos.is_horde()) return false; +#endif +#ifdef ATOMIC + if (pos.is_atomic()) return false; +#endif int dtz = probe_dtz(pos, &success); if (!success) return false; @@ -799,6 +788,18 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoveVector& rootMoves, Va bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score) { int success; +#ifdef KOTH + if (pos.is_koth()) return false; +#endif +#ifdef THREECHECK + if (pos.is_three_check()) return false; +#endif +#ifdef HORDE + if (pos.is_horde()) return false; +#endif +#ifdef ATOMIC + if (pos.is_atomic()) return false; +#endif int wdl = Tablebases::probe_wdl(pos, &success); if (!success) return false; @@ -831,3 +832,191 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves return true; } +long long Tablebases::calc_key_from_pcs(int * pcs, int mirror) +{ + int color; + PieceType pt; + int i; + uint64_t key = 0; + + color = !mirror ? 0 : 8; + for (pt = PAWN; pt <= KING; ++pt) + for (i = 0; i < pcs[color + pt]; i++) + key ^= Zobrist::psq[WHITE][pt][i]; + color ^= 8; + for (pt = PAWN; pt <= KING; ++pt) + for (i = 0; i < pcs[color + pt]; i++) + key ^= Zobrist::psq[BLACK][pt][i]; + + return key; +} + +void Tablebases::init(const std::string& path) +{ + char str[16]; + int i, j, k, l; + + if (initialized) { + free(path_string); + free(paths); + struct TBEntry *entry; + for (i = 0; i < TBnum_piece; i++) { + entry = (struct TBEntry *)&TB_piece[i]; + free_wdl_entry(entry); + } + for (i = 0; i < TBnum_pawn; i++) { + entry = (struct TBEntry *)&TB_pawn[i]; + free_wdl_entry(entry); + } + for (i = 0; i < DTZ_ENTRIES; i++) + if (DTZ_table[i].entry) + free_dtz_entry(DTZ_table[i].entry); + } + else { + init_indices(); + initialized = true; + } + + const char *p = path.c_str(); + if (strlen(p) == 0 || !strcmp(p, "")) return; + path_string = (char *)malloc(strlen(p) + 1); + strcpy(path_string, p); + num_paths = 0; + for (i = 0;; i++) { + if (path_string[i] != SEP_CHAR) + num_paths++; + while (path_string[i] && path_string[i] != SEP_CHAR) + i++; + if (!path_string[i]) break; + path_string[i] = 0; + } + paths = (char **)malloc(num_paths * sizeof(char *)); + for (i = j = 0; i < num_paths; i++) { + while (!path_string[j]) j++; + paths[i] = &path_string[j]; + while (path_string[j]) j++; + } + + LOCK_INIT(TB_mutex); + + TBnum_piece = TBnum_pawn = 0; + MaxCardinality = 0; + + for (i = 0; i < (1 << TBHASHBITS); i++) + for (j = 0; j < HSHMAX; j++) { + TB_hash[i][j].key = 0ULL; + TB_hash[i][j].ptr = NULL; + } + + for (i = 0; i < DTZ_ENTRIES; i++) + DTZ_table[i].entry = NULL; + + for (i = 1; i < 6; i++) { + sprintf(str, "K%cvK", pchr[i]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) { + sprintf(str, "K%cvK%c", pchr[i], pchr[j]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) { + sprintf(str, "K%c%cvK", pchr[i], pchr[j]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = 1; k < 6; k++) { + sprintf(str, "K%c%cvK%c", pchr[i], pchr[j], pchr[k]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = j; k < 6; k++) { + sprintf(str, "K%c%c%cvK", pchr[i], pchr[j], pchr[k]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = i; k < 6; k++) + for (l = (i == k) ? j : k; l < 6; l++) { + sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = j; k < 6; k++) + for (l = 1; l < 6; l++) { + sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = j; k < 6; k++) + for (l = k; l < 6; l++) { + sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]); + init_tb(str); + } + + printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn); +} + +void Tablebases::load_dtz_table(char * str, uint64_t key1, uint64_t key2) +{ + int i; + struct TBEntry *ptr, *ptr3; + struct TBHashEntry *ptr2; + + DTZ_table[0].key1 = key1; + DTZ_table[0].key2 = key2; + DTZ_table[0].entry = NULL; + + // find corresponding WDL entry + ptr2 = TB_hash[key1 >> (64 - TBHASHBITS)]; + for (i = 0; i < HSHMAX; i++) + if (ptr2[i].key == key1) break; + if (i == HSHMAX) return; + ptr = ptr2[i].ptr; + + ptr3 = (struct TBEntry *)malloc(ptr->has_pawns + ? sizeof(struct DTZEntry_pawn) + : sizeof(struct DTZEntry_piece)); + + ptr3->data = map_file(str, DTZSUFFIX, &ptr3->mapping); + ptr3->key = ptr->key; + ptr3->num = ptr->num; + ptr3->symmetric = ptr->symmetric; + ptr3->has_pawns = ptr->has_pawns; + if (ptr3->has_pawns) { + struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr3; + entry->pawns[0] = ((struct TBEntry_pawn *)ptr)->pawns[0]; + entry->pawns[1] = ((struct TBEntry_pawn *)ptr)->pawns[1]; + } + else { + struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr3; + entry->enc_type = ((struct TBEntry_piece *)ptr)->enc_type; + } + if (!init_table_dtz(ptr3)) + free(ptr3); + else + DTZ_table[0].entry = ptr3; +} + +short Tablebases::ReadUshort(unsigned char * d) +{ + return ushort(d[0] | (d[1] << 8)); +} + +int Tablebases::ReadUint32(unsigned char * d) +{ + return d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); +} + diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 4233e1aae14..dcab21ac911 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -8,11 +8,14 @@ namespace Tablebases { extern int MaxCardinality; void init(const std::string& path); +void load_dtz_table(char *str, uint64_t key1, uint64_t key2); +short ReadUshort(unsigned char* d); +int ReadUint32(unsigned char* d); int probe_wdl(Position& pos, int *success); int probe_dtz(Position& pos, int *success); bool root_probe(Position& pos, Search::RootMoveVector& rootMoves, Value& score); bool root_probe_wdl(Position& pos, Search::RootMoveVector& rootMoves, Value& score); - +long long calc_key_from_pcs(int *pcs, int mirror); } #endif diff --git a/src/thread.h b/src/thread.h index ee032bf3783..b498bef1e04 100644 --- a/src/thread.h +++ b/src/thread.h @@ -64,6 +64,7 @@ class Thread { Position rootPos; Search::RootMoveVector rootMoves; + int rootPly; Depth rootDepth; HistoryStats history; MovesStats counterMoves; diff --git a/src/types.h b/src/types.h index 772f0ec92fd..927998c2429 100644 --- a/src/types.h +++ b/src/types.h @@ -151,6 +151,12 @@ template struct MakeCastling { : S == QUEEN_SIDE ? BLACK_OOO : BLACK_OO; }; +#ifdef THREECHECK +enum Checks { + CHECKS_0 = 0, CHECKS_1 = 1, CHECKS_2 = 2, CHECKS_3 = 3, CHECKS_NB = 4 +}; +#endif + enum Phase { PHASE_ENDGAME, PHASE_MIDGAME = 128, @@ -299,6 +305,9 @@ ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(PieceType) ENABLE_FULL_OPERATORS_ON(Piece) ENABLE_FULL_OPERATORS_ON(Color) +#ifdef THREECHECK +ENABLE_FULL_OPERATORS_ON(Checks) +#endif ENABLE_FULL_OPERATORS_ON(Depth) ENABLE_FULL_OPERATORS_ON(Square) ENABLE_FULL_OPERATORS_ON(File) diff --git a/src/uci.cpp b/src/uci.cpp index 83a8b930090..bebf7589117 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -35,8 +35,12 @@ extern void benchmark(const Position& pos, istream& is); namespace { - // FEN string of the initial position, normal chess + // FEN string of the initial position, normal variant const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +#ifdef HORDE + // FEN string of the initial position, horde variant + const char* StartFENHorde = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1"; +#endif // Stack to keep track of the position states along the setup moves (from the // start position to the position just before the search starts). Needed by @@ -54,11 +58,34 @@ namespace { Move m; string token, fen; - is >> token; + int variant = STANDARD_VARIANT; + if (Options["UCI_Chess960"]) + variant |= CHESS960_VARIANT; +#ifdef ATOMIC + if (Options["UCI_Atomic"]) + variant |= ATOMIC_VARIANT; +#endif +#ifdef HORDE + if (Options["UCI_Horde"]) + variant |= HORDE_VARIANT; +#endif +#ifdef KOTH + if (Options["UCI_KingOfTheHill"]) + variant |= KOTH_VARIANT; +#endif +#ifdef THREECHECK + if (Options["UCI_3Check"]) + variant |= THREECHECK_VARIANT; +#endif + is >> token; if (token == "startpos") { +#ifdef HORDE + fen = (variant & HORDE_VARIANT) ? StartFENHorde : StartFEN; +#else fen = StartFEN; +#endif is >> token; // Consume "moves" token if any } else if (token == "fen") @@ -66,8 +93,8 @@ namespace { fen += token + " "; else return; - - pos.set(fen, Options["UCI_Chess960"], Threads.main()); + pos.set(fen, variant, Threads.main()); + SetupStates = Search::StateStackPtr(new std::stack); // Parse move list (if any) @@ -85,7 +112,6 @@ namespace { void setoption(istringstream& is) { string token, name, value; - is >> token; // Consume "name" token // Read option name (can contain spaces) @@ -145,7 +171,7 @@ namespace { void UCI::loop(int argc, char* argv[]) { - Position pos(StartFEN, false, Threads.main()); // The root position + Position pos(StartFEN, Threads.main()); // The root position string token, cmd; for (int i = 1; i < argc; ++i) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index f493e8401aa..e2c879862c1 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -69,6 +69,18 @@ void init(OptionsMap& o) { o["Slow Mover"] << Option(80, 10, 1000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); +#ifdef ATOMIC + o["UCI_Atomic"] << Option(false); +#endif +#ifdef HORDE + o["UCI_Horde"] << Option(false); +#endif +#ifdef KOTH + o["UCI_KingOfTheHill"] << Option(false); +#endif +#ifdef THREECHECK + o["UCI_3Check"] << Option(false); +#endif o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true);