diff --git a/.travis.yml b/.travis.yml index c4f68ae5..1d56a23e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,9 @@ script: - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref + + # Verify bench number is ONE_PLY independent by doubling its value + - sed -i'.bak' 's/.*\(ONE_PLY = [0-9]*\),.*/\1 * 2,/g' types.h - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref # # Check perft and reproducible search diff --git a/src/Makefile b/src/Makefile index 599bc4ab..17c6a8c5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -490,7 +490,7 @@ config-sanity: @echo "Testing config sanity. If this fails, try 'make help' ..." @echo "" @test "$(debug)" = "yes" || test "$(debug)" = "no" - @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "no" + @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7" diff --git a/src/bitbase.cpp b/src/bitbase.cpp index 097da917..8c8c4702 100644 --- a/src/bitbase.cpp +++ b/src/bitbase.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include #include diff --git a/src/bitboard.cpp b/src/bitboard.cpp index f66d971b..0ab244c7 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -18,8 +18,8 @@ along with this program. If not, see . */ -#include #include +#include #include "bitboard.h" #include "misc.h" @@ -27,13 +27,10 @@ uint8_t PopCnt16[1 << 16]; uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; -Bitboard SquareBB[SQUARE_NB]; -Bitboard ForwardRanksBB[COLOR_NB][RANK_NB]; -Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; -Bitboard DistanceRingBB[SQUARE_NB][8]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; +Bitboard SquareBB[SQUARE_NB]; Bitboard KingFlank[FILE_NB] = { QueenSide ^ FileDBB, QueenSide, QueenSide, @@ -85,10 +82,7 @@ void Bitboards::init() { for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - { SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - DistanceRingBB[s1][SquareDistance[s1][s2]] |= s2; - } int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } }; @@ -122,10 +116,7 @@ void Bitboards::init() { for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) if (PseudoAttacks[pt][s1] & s2) - { 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]); - } } } diff --git a/src/bitboard.h b/src/bitboard.h index af7b592a..53e96f44 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -68,13 +68,11 @@ constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; -extern Bitboard SquareBB[SQUARE_NB]; -extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; -extern Bitboard DistanceRingBB[SQUARE_NB][8]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; extern Bitboard KingFlank[FILE_NB]; +extern Bitboard SquareBB[SQUARE_NB]; /// Magic holds all magic bitboards relevant data for a single square @@ -102,34 +100,19 @@ struct Magic { extern Magic RookMagics[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; +inline Bitboard square_bb(Square s) { + assert(s >= SQ_A1 && s <= SQ_H8); + return SquareBB[s]; +} /// Overloads of bitwise operators between a Bitboard and a Square for testing /// whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&(Bitboard b, Square s) { - assert(s >= SQ_A1 && s <= SQ_H8); - return b & SquareBB[s]; -} - -inline Bitboard operator|(Bitboard b, Square s) { - assert(s >= SQ_A1 && s <= SQ_H8); - return b | SquareBB[s]; -} - -inline Bitboard operator^(Bitboard b, Square s) { - assert(s >= SQ_A1 && s <= SQ_H8); - return b ^ SquareBB[s]; -} - -inline Bitboard& operator|=(Bitboard& b, Square s) { - assert(s >= SQ_A1 && s <= SQ_H8); - return b |= SquareBB[s]; -} - -inline Bitboard& operator^=(Bitboard& b, Square s) { - assert(s >= SQ_A1 && s <= SQ_H8); - return b ^= SquareBB[s]; -} +inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } constexpr bool more_than_one(Bitboard b) { return b & (b - 1); @@ -200,13 +183,12 @@ inline Bitboard adjacent_files_bb(File f) { } -/// between_bb() returns a bitboard representing all the squares between the two -/// given ones. For instance, between_bb(SQ_C4, SQ_F7) returns a bitboard with -/// the bits for square d5 and e6 set. If s1 and s2 are not on the same rank, -/// file or diagonal, 0 is returned. +/// between_bb() returns squares that are linearly between the given squares +/// If the given squares are not on a same file/rank/diagonal, return 0. inline Bitboard between_bb(Square s1, Square s2) { - return BetweenBB[s1][s2]; + return LineBB[s1][s2] & ( (AllSquares << (s1 + (s1 < s2))) + ^(AllSquares << (s2 + !(s1 < s2)))); } @@ -254,15 +236,16 @@ inline bool aligned(Square s1, Square s2, Square s3) { /// distance() functions return the distance between x and y, defined as the -/// number of steps for a king in x to reach y. Works with squares, ranks, files. +/// number of steps for a king in x to reach y. -template inline int distance(T x, T y) { return std::abs(x - y); } +template inline int distance(Square x, Square y); +template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } +template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } -template inline int distance(T2 x, T2 y); -template<> inline int distance(Square x, Square y) { return distance(file_of(x), file_of(y)); } -template<> inline int distance(Square x, Square y) { return distance(rank_of(x), rank_of(y)); } - +template constexpr const T& clamp(const T& v, const T& lo, const T& hi) { + return v < lo ? lo : v > hi ? hi : v; +} /// attacks_bb() returns a bitboard representing all the squares attacked by a /// piece of type Pt (bishop or rook) placed on 's'. diff --git a/src/endgame.cpp b/src/endgame.cpp index 3e572205..5958e633 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include "bitboard.h" @@ -77,10 +76,7 @@ namespace { if (file_of(pos.square(strongSide)) >= FILE_E) sq = Square(sq ^ 7); // Mirror SQ_H1 -> SQ_A1 - if (strongSide == BLACK) - sq = ~sq; - - return sq; + return strongSide == WHITE ? sq : ~sq; } } // namespace @@ -286,18 +282,18 @@ Value Endgame::operator()(const Position& pos) const { } -/// KNN vs KP. Simply push the opposing king to the corner. +/// KNN vs KP. Simply push the opposing king to the corner template<> Value Endgame::operator()(const Position& pos) const { - assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - Value result = 2 * KnightValueEg - - PawnValueEg - + PushToEdges[pos.square(weakSide)]; + Value result = 2 * KnightValueEg + - PawnValueEg + + PushToEdges[pos.square(weakSide)]; - return strongSide == pos.side_to_move() ? result : -result; + return strongSide == pos.side_to_move() ? result : -result; } @@ -639,8 +635,6 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square ksq = pos.square(weakSide); Square psq1 = pos.squares(strongSide)[0]; Square psq2 = pos.squares(strongSide)[1]; - Rank r1 = rank_of(psq1); - Rank r2 = rank_of(psq2); Square blockSq1, blockSq2; if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2)) @@ -674,7 +668,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { && opposite_colors(ksq, wbsq) && ( bbsq == blockSq2 || (pos.attacks_from(blockSq2) & pos.pieces(weakSide, BISHOP)) - || distance(r1, r2) >= 2)) + || distance(psq1, psq2) >= 2)) return SCALE_FACTOR_DRAW; else if ( ksq == blockSq2 diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 27e1dd3d..7e6260c8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include // For std::memset #include @@ -229,6 +228,8 @@ namespace { const Square ksq = pos.square(Us); + Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); + // Find our pawns that are blocked or on the first two ranks Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); @@ -240,7 +241,8 @@ namespace { attackedBy[Us][KING] = pos.attacks_from(ksq); 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]; + attackedBy2[Us] = (attackedBy[Us][KING] & attackedBy[Us][PAWN]) + | dblAttackByPawn; // Init our king safety tables kingRing[Us] = attackedBy[Us][KING]; @@ -257,7 +259,7 @@ namespace { kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; // Remove from kingRing[] the squares defended by two pawns - kingRing[Us] &= ~pawn_double_attacks_bb(pos.pieces(Us, PAWN)); + kingRing[Us] &= ~dblAttackByPawn; } @@ -326,7 +328,7 @@ namespace { // bishop, bigger when the center files are blocked with pawns. Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); - score -= BishopPawns * pe->pawns_on_same_color_squares(Us, s) + score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s) * (1 + popcount(blocked & CenterFiles)); // Bonus for bishop on a long diagonal which can "see" both center squares @@ -356,8 +358,8 @@ namespace { score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); // Bonus for rook on an open or semi-open file - if (pe->semiopen_file(Us, file_of(s))) - score += RookOnFile[bool(pe->semiopen_file(Them, file_of(s)))]; + if (pos.semiopen_file(Us, file_of(s))) + score += RookOnFile[bool(pos.semiopen_file(Them, file_of(s)))]; // Penalty when trapped by the king, even more if the king cannot castle else if (mob <= 3) @@ -465,15 +467,16 @@ namespace { + 69 * kingAttacksCount[Them] + 185 * popcount(kingRing[Us] & weak) - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) + - 35 * bool(attackedBy[Us][BISHOP] & attackedBy[Us][KING]) + 150 * popcount(pos.blockers_for_king(Us) | unsafeChecks) - 873 * !pos.count(Them) - 6 * mg_value(score) / 8 + mg_value(mobility[Them] - mobility[Us]) + 5 * kingFlankAttacks * kingFlankAttacks / 16 - - 25; + - 7; // Transform the kingDanger units into a Score, and subtract it from the evaluation - if (kingDanger > 0) + if (kingDanger > 100) score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); // Penalty when our king is on a pawnless flank @@ -718,7 +721,7 @@ namespace { int bonus = popcount(safe) + popcount(behind & safe); int weight = pos.count(Us) - - 2 * popcount(pe->semiopenFiles[WHITE] & pe->semiopenFiles[BLACK]); + - (16 - pos.count()) / 4; Score score = make_score(bonus * weight * weight / 16, 0); @@ -743,12 +746,12 @@ namespace { && (pos.pieces(PAWN) & KingSide); // Compute the initiative bonus for the attacking side - int complexity = 9 * pe->pawn_asymmetry() + int complexity = 9 * pe->passed_count() + 11 * pos.count() + 9 * outflanking + 18 * pawnsOnBothFlanks + 49 * !pos.non_pawn_material() - -121 ; + -103 ; // Now apply the bonus: note that we find the attacking side by extracting // the sign of the endgame value, and that we carefully cap the bonus so @@ -776,7 +779,7 @@ namespace { if ( pos.opposite_bishops() && pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) - sf = 8 + 4 * pe->pawn_asymmetry(); + sf = 16 + 4 * pe->passed_count(); else sf = std::min(40 + (pos.opposite_bishops() ? 2 : 7) * pos.count(strongSide), sf); diff --git a/src/main.cpp b/src/main.cpp index c566bd2a..1bc60cb1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,7 +44,6 @@ int main(int argc, char* argv[]) { Position::init(); Bitbases::init(); Search::init(); - Pawns::init(); Threads.set(Options["Threads"]); Search::clear(); // After threads are up diff --git a/src/material.cpp b/src/material.cpp index 294744f4..ee5d4bce 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include // For std::min #include #include // For std::memset @@ -70,14 +69,12 @@ namespace { bool is_KBPsK(const Position& pos, Color us) { return pos.non_pawn_material(us) == BishopValueMg - && pos.count(us) == 1 && pos.count(us) >= 1; } bool is_KQKRPs(const Position& pos, Color us) { return !pos.count(us) && pos.non_pawn_material(us) == QueenValueMg - && pos.count(us) == 1 && pos.count(~us) == 1 && pos.count(~us) >= 1; } @@ -132,7 +129,7 @@ Entry* probe(const Position& pos) { Value npm_w = pos.non_pawn_material(WHITE); Value npm_b = pos.non_pawn_material(BLACK); - Value npm = std::max(EndgameLimit, std::min(npm_w + npm_b, MidgameLimit)); + Value npm = clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); @@ -152,9 +149,9 @@ Entry* probe(const Position& pos) { // OK, we didn't find any special evaluation function for the current material // configuration. Is there a suitable specialized scaling function? - const EndgameBase* sf; + const auto* sf = pos.this_thread()->endgames.probe(key); - if ((sf = pos.this_thread()->endgames.probe(key)) != nullptr) + if (sf) { e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned return e; diff --git a/src/misc.h b/src/misc.h index 3cba4867..4b238df5 100644 --- a/src/misc.h +++ b/src/misc.h @@ -53,7 +53,7 @@ struct HashTable { Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } private: - std::vector table = std::vector(Size); + std::vector table = std::vector(Size); // Allocate on the heap }; diff --git a/src/movepick.cpp b/src/movepick.cpp index 86eb98aa..a70785e7 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -139,12 +139,12 @@ Move MovePicker::select(Pred filter) { if (T == Best) std::swap(*cur, *std::max_element(cur, endMoves)); - move = *cur++; + if (*cur != ttMove && filter()) + return *cur++; - if (move != ttMove && filter()) - return move; + cur++; } - return move = MOVE_NONE; + return MOVE_NONE; } /// MovePicker::next_move() is the most important method of the MovePicker class. It @@ -174,10 +174,10 @@ top: case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(move, Value(-55 * (cur-1)->value / 1024)) ? + return pos.see_ge(*cur, Value(-55 * cur->value / 1024)) ? // Move losing capture to endBadCaptures to be tried later - true : (*endBadCaptures++ = move, false); })) - return move; + true : (*endBadCaptures++ = *cur, false); })) + return *(cur - 1); // Prepare the pointers to loop over the refutations array cur = std::begin(refutations); @@ -192,10 +192,10 @@ top: /* fallthrough */ case REFUTATION: - if (select([&](){ return move != MOVE_NONE - && !pos.capture(move) - && pos.pseudo_legal(move); })) - return move; + if (select([&](){ return *cur != MOVE_NONE + && !pos.capture(*cur) + && pos.pseudo_legal(*cur); })) + return *(cur - 1); ++stage; /* fallthrough */ @@ -210,10 +210,10 @@ top: case QUIET: if ( !skipQuiets - && select([&](){return move != refutations[0] - && move != refutations[1] - && move != refutations[2];})) - return move; + && select([&](){return *cur != refutations[0].move + && *cur != refutations[1].move + && *cur != refutations[2].move;})) + return *(cur - 1); // Prepare the pointers to loop over the bad captures cur = moves; @@ -237,12 +237,12 @@ top: return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(move, threshold); }); + return select([&](){ return pos.see_ge(*cur, threshold); }); case QCAPTURE: if (select([&](){ return depth > DEPTH_QS_RECAPTURES - || to_sq(move) == recaptureSquare; })) - return move; + || to_sq(*cur) == recaptureSquare; })) + return *(cur - 1); // If we did not find any move and we do not try checks, we have finished if (depth != DEPTH_QS_CHECKS) diff --git a/src/movepick.h b/src/movepick.h index d9ecba99..e916514d 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -142,7 +142,6 @@ private: Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; - Move move; Square recaptureSquare; Value threshold; Depth depth; diff --git a/src/pawns.cpp b/src/pawns.cpp index 6072745b..095126d4 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include "bitboard.h" @@ -36,8 +35,8 @@ namespace { constexpr Score Doubled = S(11, 56); constexpr Score Isolated = S( 5, 15); - // Connected pawn bonus by opposed, phalanx, #support and rank - Score Connected[2][2][3][RANK_NB]; + // Connected pawn bonus + constexpr int Connected[RANK_NB] = { 0, 13, 17, 24, 59, 96, 171 }; // Strength of pawn shelter for our king by [distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. @@ -78,11 +77,8 @@ namespace { Bitboard theirPawns = pos.pieces(Them, PAWN); e->passedPawns[Us] = e->pawnAttacksSpan[Us] = e->weakUnopposed[Us] = 0; - e->semiopenFiles[Us] = 0xFF; e->kingSquares[Us] = SQ_NONE; e->pawnAttacks[Us] = pawn_attacks_bb(ourPawns); - e->pawnsOnSquares[Us][BLACK] = popcount(ourPawns & DarkSquares); - e->pawnsOnSquares[Us][WHITE] = pos.count(Us) - e->pawnsOnSquares[Us][BLACK]; // Loop through all pawns of the current color and score each pawn while ((s = *pl++) != SQ_NONE) @@ -90,8 +86,8 @@ namespace { assert(pos.piece_on(s) == make_piece(Us, PAWN)); File f = file_of(s); + Rank r = relative_rank(Us, s); - e->semiopenFiles[Us] &= ~(1 << f); e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); // Flag the pawn @@ -114,12 +110,11 @@ namespace { // which could become passed after one or two pawn pushes when are // not attacked more times than defended. if ( !(stoppers ^ lever ^ leverPush) - && popcount(support) >= popcount(lever) - 1 + && (support || !more_than_one(lever)) && popcount(phalanx) >= popcount(leverPush)) e->passedPawns[Us] |= s; - else if ( stoppers == SquareBB[s + Up] - && relative_rank(Us, s) >= RANK_5) + else if (stoppers == square_bb(s + Up) && r >= RANK_5) { b = shift(support) & ~theirPawns; while (b) @@ -129,8 +124,11 @@ namespace { // Score this pawn if (support | phalanx) - score += Connected[opposed][bool(phalanx)][popcount(support)][relative_rank(Us, s)]; - + { + int v = (phalanx ? 3 : 2) * Connected[r]; + v = 17 * popcount(support) + (v >> (opposed + 1)); + score += make_score(v, v * (r - 2) / 4); + } else if (!neighbours) score -= Isolated, e->weakUnopposed[Us] += !opposed; @@ -148,27 +146,6 @@ namespace { namespace Pawns { -/// Pawns::init() initializes some tables needed by evaluation. Instead of using -/// hard-coded tables, when makes sense, we prefer to calculate them with a formula -/// to reduce independent parameters and to allow easier tuning and better insight. - -void init() { - - static constexpr int Seed[RANK_NB] = { 0, 13, 24, 18, 65, 100, 175, 330 }; - - for (int opposed = 0; opposed <= 1; ++opposed) - for (int phalanx = 0; phalanx <= 1; ++phalanx) - for (int support = 0; support <= 2; ++support) - for (Rank r = RANK_2; r < RANK_8; ++r) - { - int v = 17 * support; - v += (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; - - Connected[opposed][phalanx][support][r] = make_score(v, v * (r - 2) / 4); - } -} - - /// Pawns::probe() looks up the current position's pawns configuration in /// the pawns hash table. It returns a pointer to the Entry if the position /// is found. Otherwise a new Entry is computed and stored there, so we don't @@ -185,8 +162,6 @@ Entry* probe(const Position& pos) { e->key = key; e->scores[WHITE] = evaluate(pos, e); e->scores[BLACK] = evaluate(pos, e); - e->asymmetry = popcount( (e->passedPawns[WHITE] | e->passedPawns[BLACK]) - | (e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK])); return e; } @@ -209,7 +184,7 @@ Value Entry::evaluate_shelter(const Position& pos, Square ksq) { Value safety = (shift(theirPawns) & (FileABB | FileHBB) & BlockRanks & ksq) ? Value(374) : Value(5); - File center = std::max(FILE_B, std::min(FILE_G, file_of(ksq))); + File center = clamp(file_of(ksq), FILE_B, FILE_G); for (File f = File(center - 1); f <= File(center + 1); ++f) { b = ourPawns & file_bb(f); @@ -237,11 +212,15 @@ Score Entry::do_king_safety(const Position& pos) { Square ksq = pos.square(Us); kingSquares[Us] = ksq; castlingRights[Us] = pos.castling_rights(Us); - int minKingPawnDistance = 0; Bitboard pawns = pos.pieces(Us, PAWN); - if (pawns) - while (!(DistanceRingBB[ksq][++minKingPawnDistance] & pawns)) {} + int minPawnDist = pawns ? 8 : 0; + + if (pawns & PseudoAttacks[KING][ksq]) + minPawnDist = 1; + + else while (pawns) + minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns))); Value bonus = evaluate_shelter(pos, ksq); @@ -252,7 +231,7 @@ Score Entry::do_king_safety(const Position& pos) { if (pos.can_castle(Us | QUEEN_SIDE)) bonus = std::max(bonus, evaluate_shelter(pos, relative_square(Us, SQ_C1))); - return make_score(bonus, -16 * minKingPawnDistance); + return make_score(bonus, -16 * minPawnDist); } // Explicit template instantiation diff --git a/src/pawns.h b/src/pawns.h index 67f966ad..88b55545 100644 --- a/src/pawns.h +++ b/src/pawns.h @@ -38,15 +38,7 @@ struct Entry { Bitboard passed_pawns(Color c) const { return passedPawns[c]; } Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } int weak_unopposed(Color c) const { return weakUnopposed[c]; } - int pawn_asymmetry() const { return asymmetry; } - - int semiopen_file(Color c, File f) const { - return semiopenFiles[c] & (1 << f); - } - - int pawns_on_same_color_squares(Color c, Square s) const { - return pawnsOnSquares[c][bool(DarkSquares & s)]; - } + int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }; template Score king_safety(const Position& pos) { @@ -69,14 +61,11 @@ struct Entry { Score kingSafety[COLOR_NB]; int weakUnopposed[COLOR_NB]; int castlingRights[COLOR_NB]; - int semiopenFiles[COLOR_NB]; int pawnsOnSquares[COLOR_NB][COLOR_NB]; // [color][light/dark squares] - int asymmetry; }; typedef HashTable Table; -void init(); Entry* probe(const Position& pos); } // namespace Pawns diff --git a/src/position.cpp b/src/position.cpp index 812eabca..ada03fbc 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include // For offsetof() #include // For std::memset, std::memcmp @@ -341,13 +340,8 @@ void Position::set_castling_right(Color c, Square rfrom) { Square kto = relative_square(c, cs == KING_SIDE ? SQ_G1 : SQ_C1); Square rto = relative_square(c, cs == KING_SIDE ? SQ_F1 : SQ_D1); - for (Square s = std::min(rfrom, rto); s <= std::max(rfrom, rto); ++s) - if (s != kfrom && s != rfrom) - castlingPath[cr] |= s; - - for (Square s = std::min(kfrom, kto); s <= std::max(kfrom, kto); ++s) - if (s != kfrom && s != rfrom) - castlingPath[cr] |= s; + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) + & ~(square_bb(kfrom) | rfrom); } @@ -629,7 +623,7 @@ 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 ((Rank8BB | Rank1BB) & to) return false; if ( !(attacks_from(from, us) & pieces(~us) & to) // Not a capture diff --git a/src/position.h b/src/position.h index b65522b7..1078e03e 100644 --- a/src/position.h +++ b/src/position.h @@ -95,6 +95,7 @@ public: template int count() const; template const Square* squares(Color c) const; template Square square(Color c) const; + int semiopen_file(Color c, File f) const; // Castling int castling_rights(Color c) const; @@ -128,6 +129,7 @@ public: // Piece specific bool pawn_passed(Color c, Square s) const; bool opposite_bishops() const; + int pawns_on_same_color_squares(Color c, Square s) const; // Doing and undoing moves void do_move(Move m, StateInfo& newSt); @@ -260,6 +262,10 @@ inline Square Position::ep_square() const { return st->epSquare; } +inline int Position::semiopen_file(Color c, File f) const { + return !(pieces(c, PAWN) & file_bb(f)); +} + inline bool Position::can_castle(CastlingRight cr) const { return st->castlingRights & cr; } @@ -318,6 +324,10 @@ inline bool Position::advanced_pawn_push(Move m) const { && relative_rank(sideToMove, from_sq(m)) > RANK_4; } +inline int Position::pawns_on_same_color_squares(Color c, Square s) const { + return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); +} + inline Key Position::key() const { return st->key; } @@ -413,7 +423,7 @@ inline void Position::move_piece(Piece pc, Square from, Square to) { // index[from] is not updated and becomes stale. This works as long as index[] // is accessed just by known occupied squares. - Bitboard fromTo = SquareBB[from] ^ SquareBB[to]; + Bitboard fromTo = square_bb(from) | square_bb(to); byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; byColorBB[color_of(pc)] ^= fromTo; diff --git a/src/search.cpp b/src/search.cpp index 064be66d..5f042c1a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include #include // For std::memset @@ -69,10 +68,10 @@ namespace { } // Reductions lookup table, initialized at startup - int Reductions[64]; // [depth or moveNumber] + int Reductions[MAX_MOVES]; // [depth or moveNumber] template Depth reduction(bool i, Depth d, int mn) { - int r = Reductions[std::min(d / ONE_PLY, 63)] * Reductions[std::min(mn, 63)] / 1024; + int r = Reductions[d / ONE_PLY] * Reductions[mn] / 1024; return ((r + 512) / 1024 + (!i && r > 1024) - PvNode) * ONE_PLY; } @@ -86,11 +85,10 @@ namespace { return d > 17 ? 0 : 29 * d * d + 138 * d - 134; } - // Add a small random component to draw evaluations to keep search dynamic - // and to avoid 3fold-blindness. + // Add a small random component to draw evaluations to avoid 3fold-blindness Value value_draw(Depth depth, Thread* thisThread) { - return depth < 4 ? VALUE_DRAW - : VALUE_DRAW + Value(2 * (thisThread->nodes.load(std::memory_order_relaxed) % 2) - 1); + return depth < 4 * ONE_PLY ? VALUE_DRAW + : VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); } // Skill structure is used to implement strength limit @@ -117,13 +115,6 @@ namespace { void update_quiet_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietCount, int bonus); void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCount, int bonus); - inline bool gives_check(const Position& pos, Move move) { - Color us = pos.side_to_move(); - return type_of(move) == NORMAL && !(pos.blockers_for_king(~us) & pos.pieces(us)) - ? pos.check_squares(type_of(pos.moved_piece(move))) & to_sq(move) - : pos.gives_check(move); - } - // perft() is our utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. template @@ -157,7 +148,7 @@ namespace { void Search::init() { - for (int i = 1; i < 64; ++i) + for (int i = 1; i < MAX_MOVES; ++i) Reductions[i] = int(1024 * std::log(i) / std::sqrt(1.95)); } @@ -171,12 +162,12 @@ void Search::clear() { Time.availableNodes = 0; TT.clear(); Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free up mapped files + Tablebases::init(Options["SyzygyPath"]); // Free mapped files } -/// MainThread::search() is called by the main thread when the program receives -/// the UCI 'go' command. It searches from the root position and outputs the "bestmove". +/// MainThread::search() is started when the program receives the UCI 'go' +/// command. It searches from the root position and outputs the "bestmove". void MainThread::search() { @@ -203,8 +194,11 @@ void MainThread::search() { else { for (Thread* th : Threads) + { + th->bestMoveChanges = 0; if (th != this) th->start_searching(); + } Thread::search(); // Let's start searching! } @@ -235,8 +229,9 @@ void MainThread::search() { if (Limits.npmsec) Time.availableNodes += Limits.inc[us] - Cluster::nodes_searched(); - // Check if there are threads with a better score than main thread Thread* bestThread = this; + + // Check if there are threads with a better score than main thread if ( Options["MultiPV"] == 1 && !Limits.depth && !Skill(Options["Skill Level"]).enabled() @@ -310,9 +305,9 @@ void MainThread::search() { void Thread::search() { - // To allow access to (ss-5) up to (ss+2), the stack must be oversized. + // To allow access to (ss-7) up to (ss+2), the stack must be oversized. // The former is needed to allow update_continuation_histories(ss-1, ...), - // which accesses its argument at ss-4, also near the root. + // which accesses its argument at ss-6, also near the root. // The latter is needed for statScores and killer initialization. Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; @@ -320,7 +315,7 @@ void Thread::search() { Move lastBestMove = MOVE_NONE; Depth lastBestMoveDepth = DEPTH_ZERO; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1.0; + double timeReduction = 1, totBestMoveChanges = 0; Color us = rootPos.side_to_move(); std::memset(ss-7, 0, 10 * sizeof(Stack)); @@ -331,9 +326,6 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - if (mainThread) - mainThread->bestMoveChanges = 0; - size_t multiPV = Options["MultiPV"]; Skill skill(Options["Skill Level"]); @@ -354,7 +346,7 @@ void Thread::search() { : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct : ct; - // In evaluate.cpp the evaluation is from the white point of view + // Evaluation score is from the white point of view contempt = (us == WHITE ? make_score(ct, ct / 2) : -make_score(ct, ct / 2)); @@ -365,7 +357,7 @@ void Thread::search() { { // Age out PV variability metric if (mainThread) - mainThread->bestMoveChanges *= 0.517; + totBestMoveChanges /= 2; // Save the last iteration's scores before first PV line is searched and @@ -504,15 +496,20 @@ void Thread::search() { && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (306 + 9 * (mainThread->previousScore - bestValue)) / 581.0; - fallingEval = std::max(0.5, std::min(1.5, fallingEval)); + double fallingEval = (314 + 9 * (mainThread->previousScore - bestValue)) / 581.0; + fallingEval = clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 10 * ONE_PLY < completedDepth ? 1.95 : 1.0; double reduction = std::pow(mainThread->previousTimeReduction, 0.528) / timeReduction; // Use part of the gained time from a previous stable move for the current move - double bestMoveInstability = 1.0 + mainThread->bestMoveChanges; + for (Thread* th : Threads) + { + totBestMoveChanges += th->bestMoveChanges; + th->bestMoveChanges = 0; + } + double bestMoveInstability = 1 + totBestMoveChanges / Threads.size(); // Stop the search if we have only one legal move, or if available time elapsed if ( rootMoves.size() == 1 @@ -578,7 +575,7 @@ namespace { Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, pureStaticEval; + Value bestValue, value, ttValue, eval, maxValue; bool ttHit, ttPv, inCheck, givesCheck, improving; bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture; Piece movedPiece; @@ -624,8 +621,7 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); (ss+1)->ply = ss->ply + 1; - ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; - ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0]; + (ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; Square prevSq = to_sq((ss-1)->currentMove); @@ -647,6 +643,15 @@ namespace { : ttHit ? tte->move() : MOVE_NONE; ttPv = (ttHit && tte->is_pv()) || (PvNode && depth > 4 * ONE_PLY); + // If position has been searched at higher depths and we are shuffling, + // return value_draw. + if ( pos.rule50_count() > 36 - 6 * (pos.count() > 14) + && ss->ply > 36 - 6 * (pos.count() > 14) + && ttHit + && tte->depth() > depth + && pos.count() > 0) + return VALUE_DRAW; + // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ttHit @@ -663,9 +668,8 @@ namespace { if (!pos.capture_or_promotion(ttMove)) update_quiet_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth)); - // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted - if ( ((ss-1)->moveCount == 1 || (ss-1)->currentMove == (ss-1)->killers[0]) - && !pos.captured_piece()) + // Extra penalty for early quiet moves of the previous ply + if ((ss-1)->moveCount <= 2 && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); } // Penalty for a quiet ttMove that fails low @@ -734,16 +738,16 @@ namespace { // Step 6. Static evaluation of the position if (inCheck) { - ss->staticEval = eval = pureStaticEval = VALUE_NONE; + ss->staticEval = eval = VALUE_NONE; improving = false; goto moves_loop; // Skip early pruning when in check } else if (ttHit) { // Never assume anything on values stored in TT - ss->staticEval = eval = pureStaticEval = tte->eval(); + ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = pureStaticEval = evaluate(pos); + ss->staticEval = eval = evaluate(pos); // Can ttValue be used as a better position evaluation? if ( ttValue != VALUE_NONE @@ -756,15 +760,14 @@ namespace { { int bonus = -(ss-1)->statScore / 512; - pureStaticEval = evaluate(pos); - ss->staticEval = eval = pureStaticEval + bonus; + ss->staticEval = eval = evaluate(pos) + bonus; } else - ss->staticEval = eval = pureStaticEval = -(ss-1)->staticEval + 2 * Eval::Tempo; + ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::Tempo; Cluster::save(thisThread, tte, posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, - pureStaticEval); + eval); } // Step 7. Razoring (~2 Elo) @@ -788,7 +791,7 @@ namespace { && (ss-1)->currentMove != MOVE_NULL && (ss-1)->statScore < 23200 && eval >= beta - && pureStaticEval >= beta - 36 * depth / ONE_PLY + 225 + && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -820,7 +823,7 @@ namespace { // Do verification search at high depths, with null move pruning disabled // for us, until ply exceeds nmpMinPly. - thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; + thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / (4 * ONE_PLY); thisThread->nmpColor = us; Value v = search(pos, ss, beta-1, beta, depth-R, false); @@ -871,8 +874,7 @@ namespace { } // Step 11. Internal iterative deepening (~2 Elo) - if ( depth >= 8 * ONE_PLY - && !ttMove) + if (depth >= 8 * ONE_PLY && !ttMove) { search(pos, ss, alpha, beta, depth - 7 * ONE_PLY, cutNode); @@ -886,6 +888,7 @@ moves_loop: // When in check, search starts from here const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr, (ss-4)->continuationHistory, nullptr, (ss-6)->continuationHistory }; + Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, @@ -893,8 +896,8 @@ moves_loop: // When in check, search starts from here contHist, countermove, ss->killers); - value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc + value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc moveCountPruning = false; ttCapture = ttMove && pos.capture_or_promotion(ttMove); @@ -927,7 +930,7 @@ moves_loop: // When in check, search starts from here extension = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); movedPiece = pos.moved_piece(move); - givesCheck = gives_check(pos, move); + givesCheck = pos.gives_check(move); // Step 13. Extensions (~70 Elo) @@ -947,8 +950,9 @@ moves_loop: // When in check, search starts from here && pos.legal(move)) { Value singularBeta = ttValue - 2 * depth / ONE_PLY; + Depth halfDepth = depth / (2 * ONE_PLY) * ONE_PLY; // ONE_PLY invariant ss->excludedMove = move; - value = search(pos, ss, singularBeta - 1, singularBeta, depth / 2, cutNode); + value = search(pos, ss, singularBeta - 1, singularBeta, halfDepth, cutNode); ss->excludedMove = MOVE_NONE; if (value < singularBeta) @@ -972,6 +976,20 @@ moves_loop: // When in check, search starts from here else if (type_of(move) == CASTLING) extension = ONE_PLY; + // Shuffle extension + else if ( PvNode + && pos.rule50_count() > 18 + && ss->ply > 18 + && depth < 3 * ONE_PLY + && ss->ply < 3 * thisThread->rootDepth / ONE_PLY) // To avoid infinite loops + extension = ONE_PLY; + + // Passed pawn extension + else if ( move == ss->killers[0] + && pos.advanced_pawn_push(move) + && pos.pawn_passed(us, to_sq(move))) + extension = ONE_PLY; + // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; @@ -981,7 +999,7 @@ moves_loop: // When in check, search starts from here && bestValue > VALUE_MATED_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold - moveCountPruning = moveCount >= futility_move_count(improving,depth / ONE_PLY); + moveCountPruning = moveCount >= futility_move_count(improving, depth / ONE_PLY); if ( !captureOrPromotion && !givesCheck @@ -992,7 +1010,8 @@ moves_loop: // When in check, search starts from here continue; // Reduced depth of the next LMR search - int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; + int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO); + lmrDepth /= ONE_PLY; // Countermoves based pruning (~20 Elo) if ( lmrDepth < 3 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) @@ -1137,8 +1156,8 @@ moves_loop: // When in check, search starts from here // We record how often the best move has been changed in each // iteration. This information is used for time management: When // the best move changes frequently, we allocate some more time. - if (moveCount > 1 && thisThread == Threads.main()) - ++static_cast(thisThread)->bestMoveChanges; + if (moveCount > 1) + ++thisThread->bestMoveChanges; } else // All other moves but the PV are set to the lowest value: this @@ -1225,7 +1244,7 @@ moves_loop: // When in check, search starts from here posKey, value_to_tt(bestValue, ss->ply), ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, pureStaticEval); + depth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1264,8 +1283,7 @@ moves_loop: // When in check, search starts from here Thread* thisThread = pos.this_thread(); (ss+1)->ply = ss->ply + 1; - ss->currentMove = bestMove = MOVE_NONE; - ss->continuationHistory = &thisThread->continuationHistory[NO_PIECE][0]; + bestMove = MOVE_NONE; inCheck = pos.checkers(); moveCount = 0; @@ -1355,7 +1373,7 @@ moves_loop: // When in check, search starts from here { assert(is_ok(move)); - givesCheck = gives_check(pos, move); + givesCheck = pos.gives_check(move); moveCount++; diff --git a/src/thread.cpp b/src/thread.cpp index 75c2bf6a..750ea1f6 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include // For std::count #include #include "movegen.h" @@ -32,7 +31,7 @@ ThreadPool Threads; // Global object /// Thread constructor launches the thread and waits until it goes to sleep -/// in idle_loop(). Note that 'searching' and 'exit' should be alredy set. +/// in idle_loop(). Note that 'searching' and 'exit' should be already set. Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { diff --git a/src/thread.h b/src/thread.h index 147f592c..aa0c0d71 100644 --- a/src/thread.h +++ b/src/thread.h @@ -64,7 +64,7 @@ public: size_t pvIdx, pvLast; int selDepth, nmpMinPly; Color nmpColor; - std::atomic nodes, tbHits, TTsaves; + std::atomic nodes, tbHits, TTsaves, bestMoveChanges; Position rootPos; Search::RootMoves rootMoves; @@ -93,7 +93,7 @@ struct MainThread : public Thread { void search() override; void check_time(); - double bestMoveChanges, previousTimeReduction; + double previousTimeReduction; Value previousScore; int callsCnt; bool stopOnPonderhit; diff --git a/src/timeman.cpp b/src/timeman.cpp index 484aaa65..fafde2aa 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 813a0890..54f33c9a 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -18,7 +18,6 @@ along with this program. If not, see . */ -#include #include #include #include