diff --git a/DroidFish/jni/stockfish/benchmark.cpp b/DroidFish/jni/stockfish/benchmark.cpp index ba05ac7..b0b9e16 100644 --- a/DroidFish/jni/stockfish/benchmark.cpp +++ b/DroidFish/jni/stockfish/benchmark.cpp @@ -19,12 +19,14 @@ #include #include +#include #include #include "misc.h" #include "position.h" #include "search.h" #include "thread.h" +#include "tt.h" #include "ucioption.h" using namespace std; @@ -57,34 +59,39 @@ static const char* Defaults[] = { /// format (defaults are the positions defined above) and the type of the /// limit value: depth (default), time in secs or number of nodes. -void benchmark(int argc, char* argv[]) { +void benchmark(const Position& current, istream& is) { - vector fens; + string token; Search::LimitsType limits; - int time; - int64_t nodes = 0; + vector fens; // Assign default values to missing arguments - string ttSize = argc > 2 ? argv[2] : "128"; - string threads = argc > 3 ? argv[3] : "1"; - string valStr = argc > 4 ? argv[4] : "12"; - string fenFile = argc > 5 ? argv[5] : "default"; - string valType = argc > 6 ? argv[6] : "depth"; + string ttSize = (is >> token) ? token : "128"; + string threads = (is >> token) ? token : "1"; + string limit = (is >> token) ? token : "12"; + string fenFile = (is >> token) ? token : "default"; + string limitType = (is >> token) ? token : "depth"; Options["Hash"] = ttSize; Options["Threads"] = threads; - Options["OwnBook"] = false; + TT.clear(); - if (valType == "time") - limits.maxTime = 1000 * atoi(valStr.c_str()); // maxTime is in ms + if (limitType == "time") + limits.movetime = 1000 * atoi(limit.c_str()); // movetime is in ms - else if (valType == "nodes") - limits.maxNodes = atoi(valStr.c_str()); + else if (limitType == "nodes") + limits.nodes = atoi(limit.c_str()); else - limits.maxDepth = atoi(valStr.c_str()); + limits.depth = atoi(limit.c_str()); - if (fenFile != "default") + if (fenFile == "default") + fens.assign(Defaults, Defaults + 16); + + else if (fenFile == "current") + fens.push_back(current.to_fen()); + + else { string fen; ifstream file(fenFile.c_str()); @@ -101,34 +108,34 @@ void benchmark(int argc, char* argv[]) { file.close(); } - else - fens.assign(Defaults, Defaults + 16); - time = system_time(); + int64_t nodes = 0; + Time time = Time::current_time(); for (size_t i = 0; i < fens.size(); i++) { - Position pos(fens[i], false, 0); + Position pos(fens[i], Options["UCI_Chess960"], Threads.main_thread()); cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; - if (valType == "perft") + if (limitType == "perft") { - int64_t cnt = Search::perft(pos, limits.maxDepth * ONE_PLY); - cerr << "\nPerft " << limits.maxDepth << " leaf nodes: " << cnt << endl; + int64_t cnt = Search::perft(pos, limits.depth * ONE_PLY); + cerr << "\nPerft " << limits.depth << " leaf nodes: " << cnt << endl; nodes += cnt; } else { - Threads.start_thinking(pos, limits); + Threads.start_searching(pos, limits, vector()); + Threads.wait_for_search_finished(); nodes += Search::RootPosition.nodes_searched(); } } - time = system_time() - time; + int e = time.elapsed(); cerr << "\n===========================" - << "\nTotal time (ms) : " << time + << "\nTotal time (ms) : " << e << "\nNodes searched : " << nodes - << "\nNodes/second : " << int(nodes / (time / 1000.0)) << endl; + << "\nNodes/second : " << int(nodes / (e / 1000.0)) << endl; } diff --git a/DroidFish/jni/stockfish/bitbase.cpp b/DroidFish/jni/stockfish/bitbase.cpp index 402f3fb..ceb94a0 100644 --- a/DroidFish/jni/stockfish/bitbase.cpp +++ b/DroidFish/jni/stockfish/bitbase.cpp @@ -25,43 +25,47 @@ namespace { enum Result { - RESULT_UNKNOWN, - RESULT_INVALID, - RESULT_WIN, - RESULT_DRAW + INVALID = 0, + UNKNOWN = 1, + DRAW = 2, + WIN = 4 }; + inline Result& operator|=(Result& r, Result v) { return r = Result(r | v); } + struct KPKPosition { - Result classify_knowns(int index); - Result classify(int index, Result db[]); + + Result classify_leaf(int idx); + Result classify(int idx, Result db[]); private: - void from_index(int index); - Result classify_white(const Result db[]); - Result classify_black(const Result db[]); - Bitboard wk_attacks() const { return StepAttacksBB[W_KING][whiteKingSquare]; } - Bitboard bk_attacks() const { return StepAttacksBB[B_KING][blackKingSquare]; } - Bitboard pawn_attacks() const { return StepAttacksBB[W_PAWN][pawnSquare]; } + template Result classify(const Result db[]) const; - Square whiteKingSquare, blackKingSquare, pawnSquare; - Color sideToMove; + template Bitboard k_attacks() const { + return Us == WHITE ? StepAttacksBB[W_KING][wksq] : StepAttacksBB[B_KING][bksq]; + } + + Bitboard p_attacks() const { return StepAttacksBB[W_PAWN][psq]; } + void decode_index(int idx); + + Square wksq, bksq, psq; + Color stm; }; // The possible pawns squares are 24, the first 4 files and ranks from 2 to 7 - const int IndexMax = 2 * 24 * 64 * 64; // color * wp_sq * wk_sq * bk_sq = 196608 + const int IndexMax = 2 * 24 * 64 * 64; // stm * wp_sq * wk_sq * bk_sq = 196608 // Each uint32_t stores results of 32 positions, one per bit uint32_t KPKBitbase[IndexMax / 32]; - int compute_index(Square wksq, Square bksq, Square wpsq, Color stm); + int index(Square wksq, Square bksq, Square psq, Color stm); } uint32_t probe_kpk_bitbase(Square wksq, Square wpsq, Square bksq, Color stm) { - int index = compute_index(wksq, bksq, wpsq, stm); - - return KPKBitbase[index / 32] & (1 << (index & 31)); + int idx = index(wksq, bksq, wpsq, stm); + return KPKBitbase[idx / 32] & (1 << (idx & 31)); } @@ -69,24 +73,23 @@ void kpk_bitbase_init() { Result db[IndexMax]; KPKPosition pos; - int index, bit, repeat = 1; + int idx, bit, repeat = 1; - // Initialize table - for (index = 0; index < IndexMax; index++) - db[index] = pos.classify_knowns(index); + // Initialize table with known win / draw positions + for (idx = 0; idx < IndexMax; idx++) + db[idx] = pos.classify_leaf(idx); // Iterate until all positions are classified (30 cycles needed) while (repeat) - for (repeat = index = 0; index < IndexMax; index++) - if ( db[index] == RESULT_UNKNOWN - && pos.classify(index, db) != RESULT_UNKNOWN) + for (repeat = idx = 0; idx < IndexMax; idx++) + if (db[idx] == UNKNOWN && (db[idx] = pos.classify(idx, db)) != UNKNOWN) repeat = 1; // Map 32 position results into one KPKBitbase[] entry - for (index = 0; index < IndexMax / 32; index++) + for (idx = 0; idx < IndexMax / 32; idx++) for (bit = 0; bit < 32; bit++) - if (db[32 * index + bit] == RESULT_WIN) - KPKBitbase[index] |= (1 << bit); + if (db[32 * idx + bit] == WIN) + KPKBitbase[idx] |= 1 << bit; } @@ -102,170 +105,126 @@ namespace { // bit 13-14: white pawn file (from FILE_A to FILE_D) // bit 15-17: white pawn rank - 1 (from RANK_2 - 1 to RANK_7 - 1) - int compute_index(Square wksq, Square bksq, Square wpsq, Color stm) { + int index(Square w, Square b, Square p, Color c) { - assert(file_of(wpsq) <= FILE_D); + assert(file_of(p) <= FILE_D); - int p = file_of(wpsq) + 4 * (rank_of(wpsq) - 1); - int r = stm + 2 * bksq + 128 * wksq + 8192 * p; - - assert(r >= 0 && r < IndexMax); - - return r; + return c + (b << 1) + (w << 7) + (file_of(p) << 13) + ((rank_of(p) - 1) << 15); } - void KPKPosition::from_index(int index) { + void KPKPosition::decode_index(int idx) { - int s = index >> 13; - sideToMove = Color(index & 1); - blackKingSquare = Square((index >> 1) & 63); - whiteKingSquare = Square((index >> 7) & 63); - pawnSquare = make_square(File(s & 3), Rank((s >> 2) + 1)); + stm = Color(idx & 1); + bksq = Square((idx >> 1) & 63); + wksq = Square((idx >> 7) & 63); + psq = make_square(File((idx >> 13) & 3), Rank((idx >> 15) + 1)); } - Result KPKPosition::classify_knowns(int index) { + Result KPKPosition::classify_leaf(int idx) { - from_index(index); + decode_index(idx); - // Check if two pieces are on the same square - if ( whiteKingSquare == pawnSquare - || whiteKingSquare == blackKingSquare - || blackKingSquare == pawnSquare) - return RESULT_INVALID; + // Check if two pieces are on the same square or if a king can be captured + if ( wksq == psq || wksq == bksq || bksq == psq + || (k_attacks() & bksq) + || (stm == WHITE && (p_attacks() & bksq))) + return INVALID; - // Check if a king can be captured - if ( bit_is_set(wk_attacks(), blackKingSquare) - || (bit_is_set(pawn_attacks(), blackKingSquare) && sideToMove == WHITE)) - return RESULT_INVALID; - - // The position is an immediate win if it is white to move and the - // white pawn can be promoted without getting captured. - if ( rank_of(pawnSquare) == RANK_7 - && sideToMove == WHITE - && whiteKingSquare != pawnSquare + DELTA_N - && ( square_distance(blackKingSquare, pawnSquare + DELTA_N) > 1 - || bit_is_set(wk_attacks(), pawnSquare + DELTA_N))) - return RESULT_WIN; + // The position is an immediate win if it is white to move and the white + // pawn can be promoted without getting captured. + if ( rank_of(psq) == RANK_7 + && stm == WHITE + && wksq != psq + DELTA_N + && ( square_distance(bksq, psq + DELTA_N) > 1 + ||(k_attacks() & (psq + DELTA_N)))) + return WIN; // Check for known draw positions // // Case 1: Stalemate - if ( sideToMove == BLACK - && !(bk_attacks() & ~(wk_attacks() | pawn_attacks()))) - return RESULT_DRAW; + if ( stm == BLACK + && !(k_attacks() & ~(k_attacks() | p_attacks()))) + return DRAW; - // Case 2: King can capture pawn - if ( sideToMove == BLACK - && bit_is_set(bk_attacks(), pawnSquare) && !bit_is_set(wk_attacks(), pawnSquare)) - return RESULT_DRAW; + // Case 2: King can capture undefended pawn + if ( stm == BLACK + && (k_attacks() & psq & ~k_attacks())) + return DRAW; // Case 3: Black king in front of white pawn - if ( blackKingSquare == pawnSquare + DELTA_N - && rank_of(pawnSquare) < RANK_7) - return RESULT_DRAW; + if ( bksq == psq + DELTA_N + && rank_of(psq) < RANK_7) + return DRAW; - // Case 4: White king in front of pawn and black has opposition - if ( whiteKingSquare == pawnSquare + DELTA_N - && blackKingSquare == pawnSquare + DELTA_N + DELTA_N + DELTA_N - && rank_of(pawnSquare) < RANK_5 - && sideToMove == WHITE) - return RESULT_DRAW; + // Case 4: White king in front of pawn and black has opposition + if ( stm == WHITE + && wksq == psq + DELTA_N + && bksq == wksq + DELTA_N + DELTA_N + && rank_of(psq) < RANK_5) + return DRAW; // Case 5: Stalemate with rook pawn - if ( blackKingSquare == SQ_A8 - && file_of(pawnSquare) == FILE_A) - return RESULT_DRAW; + if ( bksq == SQ_A8 + && file_of(psq) == FILE_A) + return DRAW; - return RESULT_UNKNOWN; + // Case 6: White king trapped on the rook file + if ( file_of(wksq) == FILE_A + && file_of(psq) == FILE_A + && rank_of(wksq) > rank_of(psq) + && bksq == wksq + 2) + return DRAW; + + return UNKNOWN; } - Result KPKPosition::classify(int index, Result db[]) { + template + Result KPKPosition::classify(const Result db[]) const { - from_index(index); - db[index] = (sideToMove == WHITE ? classify_white(db) : classify_black(db)); - return db[index]; - } - - Result KPKPosition::classify_white(const Result db[]) { - - // If one move leads to a position classified as RESULT_WIN, the result - // of the current position is RESULT_WIN. If all moves lead to positions - // classified as RESULT_DRAW, the current position is classified RESULT_DRAW - // otherwise the current position is classified as RESULT_UNKNOWN. - - bool unknownFound = false; - Bitboard b; - Square s; - Result r; - - // King moves - b = wk_attacks(); - while (b) - { - s = pop_1st_bit(&b); - r = db[compute_index(s, blackKingSquare, pawnSquare, BLACK)]; - - if (r == RESULT_WIN) - return RESULT_WIN; - - if (r == RESULT_UNKNOWN) - unknownFound = true; - } - - // Pawn moves - if (rank_of(pawnSquare) < RANK_7) - { - s = pawnSquare + DELTA_N; - r = db[compute_index(whiteKingSquare, blackKingSquare, s, BLACK)]; - - if (r == RESULT_WIN) - return RESULT_WIN; - - if (r == RESULT_UNKNOWN) - unknownFound = true; - - // Double pawn push - if (rank_of(s) == RANK_3 && r != RESULT_INVALID) - { - s += DELTA_N; - r = db[compute_index(whiteKingSquare, blackKingSquare, s, BLACK)]; - - if (r == RESULT_WIN) - return RESULT_WIN; - - if (r == RESULT_UNKNOWN) - unknownFound = true; - } - } - return unknownFound ? RESULT_UNKNOWN : RESULT_DRAW; - } - - Result KPKPosition::classify_black(const Result db[]) { - - // If one move leads to a position classified as RESULT_DRAW, the result - // of the current position is RESULT_DRAW. If all moves lead to positions - // classified as RESULT_WIN, the position is classified as RESULT_WIN. + // White to Move: If one move leads to a position classified as RESULT_WIN, + // the result of the current position is RESULT_WIN. If all moves lead to + // positions classified as RESULT_DRAW, the current position is classified + // RESULT_DRAW otherwise the current position is classified as RESULT_UNKNOWN. + // + // Black to Move: If one move leads to a position classified as RESULT_DRAW, + // the result of the current position is RESULT_DRAW. If all moves lead to + // positions classified as RESULT_WIN, the position is classified RESULT_WIN. // Otherwise, the current position is classified as RESULT_UNKNOWN. - bool unknownFound = false; - Bitboard b; - Square s; - Result r; + Result r = INVALID; + Bitboard b = k_attacks(); - // King moves - b = bk_attacks(); while (b) { - s = pop_1st_bit(&b); - r = db[compute_index(whiteKingSquare, s, pawnSquare, WHITE)]; + r |= Us == WHITE ? db[index(pop_1st_bit(&b), bksq, psq, BLACK)] + : db[index(wksq, pop_1st_bit(&b), psq, WHITE)]; - if (r == RESULT_DRAW) - return RESULT_DRAW; + if (Us == WHITE && (r & WIN)) + return WIN; - if (r == RESULT_UNKNOWN) - unknownFound = true; + if (Us == BLACK && (r & DRAW)) + return DRAW; } - return unknownFound ? RESULT_UNKNOWN : RESULT_WIN; + + if (Us == WHITE && rank_of(psq) < RANK_7) + { + Square s = psq + DELTA_N; + r |= db[index(wksq, bksq, s, BLACK)]; // Single push + + if (rank_of(s) == RANK_3 && s != wksq && s != bksq) + r |= db[index(wksq, bksq, s + DELTA_N, BLACK)]; // Double push + + if (r & WIN) + return WIN; + } + + return r & UNKNOWN ? UNKNOWN : Us == WHITE ? DRAW : WIN; + } + + Result KPKPosition::classify(int idx, Result db[]) { + + decode_index(idx); + return stm == WHITE ? classify(db) : classify(db); } } diff --git a/DroidFish/jni/stockfish/bitboard.cpp b/DroidFish/jni/stockfish/bitboard.cpp index 5c6b6e0..1bae684 100644 --- a/DroidFish/jni/stockfish/bitboard.cpp +++ b/DroidFish/jni/stockfish/bitboard.cpp @@ -25,30 +25,29 @@ #include "bitcount.h" #include "rkiss.h" +CACHE_LINE_ALIGNMENT + Bitboard RMasks[64]; Bitboard RMagics[64]; Bitboard* RAttacks[64]; -int RShifts[64]; +unsigned RShifts[64]; Bitboard BMasks[64]; Bitboard BMagics[64]; Bitboard* BAttacks[64]; -int BShifts[64]; - -Bitboard SetMaskBB[65]; -Bitboard ClearMaskBB[65]; +unsigned BShifts[64]; +Bitboard SquareBB[64]; Bitboard FileBB[8]; Bitboard RankBB[8]; -Bitboard NeighboringFilesBB[8]; -Bitboard ThisAndNeighboringFilesBB[8]; +Bitboard AdjacentFilesBB[8]; +Bitboard ThisAndAdjacentFilesBB[8]; Bitboard InFrontBB[2][8]; Bitboard StepAttacksBB[16][64]; Bitboard BetweenBB[64][64]; -Bitboard SquaresInFrontMask[2][64]; +Bitboard ForwardBB[2][64]; Bitboard PassedPawnMask[2][64]; Bitboard AttackSpanMask[2][64]; - Bitboard PseudoAttacks[6][64]; uint8_t BitCount8Bit[256]; @@ -59,31 +58,16 @@ namespace { CACHE_LINE_ALIGNMENT int BSFTable[64]; - Bitboard RookTable[0x19000]; // Storage space for rook attacks - Bitboard BishopTable[0x1480]; // Storage space for bishop attacks + int MS1BTable[256]; + Bitboard RTable[0x19000]; // Storage space for rook attacks + Bitboard BTable[0x1480]; // Storage space for bishop attacks - void init_magic_bitboards(PieceType pt, Bitboard* attacks[], Bitboard magics[], - Bitboard masks[], int shifts[]); + typedef unsigned (Fn)(Square, Bitboard); + + void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], + Bitboard masks[], unsigned shifts[], Square deltas[], Fn index); } - -/// print_bitboard() prints a bitboard in an easily readable format to the -/// standard output. This is sometimes useful for debugging. - -void print_bitboard(Bitboard b) { - - for (Rank r = RANK_8; r >= RANK_1; r--) - { - std::cout << "+---+---+---+---+---+---+---+---+" << '\n'; - for (File f = FILE_A; f <= FILE_H; f++) - std::cout << "| " << (bit_is_set(b, make_square(f, r)) ? "X " : " "); - - std::cout << "|\n"; - } - std::cout << "+---+---+---+---+---+---+---+---+" << std::endl; -} - - /// first_1() finds the least significant nonzero bit in a nonzero bitboard. /// pop_1st_bit() finds and clears the least significant nonzero bit in a /// nonzero bitboard. @@ -108,87 +92,103 @@ Square first_1(Bitboard b) { return Square(BSFTable[(fold * 0x783A9B23) >> 26]); } -// Use type-punning -union b_union { +Square pop_1st_bit(Bitboard* b) { - Bitboard b; - struct { -#if defined (BIGENDIAN) - uint32_t h; - uint32_t l; -#else - uint32_t l; - uint32_t h; -#endif - } dw; -}; + Bitboard bb = *b; + *b = bb & (bb - 1); + bb ^= (bb - 1); + uint32_t fold = unsigned(bb) ^ unsigned(bb >> 32); + return Square(BSFTable[(fold * 0x783A9B23) >> 26]); +} -Square pop_1st_bit(Bitboard* bb) { +Square last_1(Bitboard b) { - b_union u; - Square ret; + unsigned b32; + int result = 0; - u.b = *bb; + if (b > 0xFFFFFFFF) + { + b >>= 32; + result = 32; + } - if (u.dw.l) - { - ret = Square(BSFTable[((u.dw.l ^ (u.dw.l - 1)) * 0x783A9B23) >> 26]); - u.dw.l &= (u.dw.l - 1); - *bb = u.b; - return ret; - } - ret = Square(BSFTable[((~(u.dw.h ^ (u.dw.h - 1))) * 0x783A9B23) >> 26]); - u.dw.h &= (u.dw.h - 1); - *bb = u.b; - return ret; + b32 = unsigned(b); + + if (b32 > 0xFFFF) + { + b32 >>= 16; + result += 16; + } + + if (b32 > 0xFF) + { + b32 >>= 8; + result += 8; + } + + return Square(result + MS1BTable[b32]); } #endif // !defined(USE_BSFQ) -/// bitboards_init() initializes various bitboard arrays. It is called during +/// Bitboards::print() prints a bitboard in an easily readable format to the +/// standard output. This is sometimes useful for debugging. + +void Bitboards::print(Bitboard b) { + + for (Rank rank = RANK_8; rank >= RANK_1; rank--) + { + std::cout << "+---+---+---+---+---+---+---+---+" << '\n'; + + for (File file = FILE_A; file <= FILE_H; file++) + std::cout << "| " << ((b & make_square(file, rank)) ? "X " : " "); + + std::cout << "|\n"; + } + std::cout << "+---+---+---+---+---+---+---+---+" << std::endl; +} + + +/// Bitboards::init() initializes various bitboard arrays. It is called during /// program initialization. -void bitboards_init() { +void Bitboards::init() { + + for (int k = 0, i = 0; i < 8; i++) + while (k < (2 << i)) + MS1BTable[k++] = i; for (Bitboard b = 0; b < 256; b++) BitCount8Bit[b] = (uint8_t)popcount(b); for (Square s = SQ_A1; s <= SQ_H8; s++) - { - SetMaskBB[s] = 1ULL << s; - ClearMaskBB[s] = ~SetMaskBB[s]; - } - - ClearMaskBB[SQ_NONE] = ~0ULL; + SquareBB[s] = 1ULL << s; FileBB[FILE_A] = FileABB; RankBB[RANK_1] = Rank1BB; - for (int f = FILE_B; f <= FILE_H; f++) + for (int i = 1; i < 8; i++) { - FileBB[f] = FileBB[f - 1] << 1; - RankBB[f] = RankBB[f - 1] << 8; + FileBB[i] = FileBB[i - 1] << 1; + RankBB[i] = RankBB[i - 1] << 8; } - for (int f = FILE_A; f <= FILE_H; f++) + for (File f = FILE_A; f <= FILE_H; f++) { - NeighboringFilesBB[f] = (f > FILE_A ? FileBB[f - 1] : 0) | (f < FILE_H ? FileBB[f + 1] : 0); - ThisAndNeighboringFilesBB[f] = FileBB[f] | NeighboringFilesBB[f]; + AdjacentFilesBB[f] = (f > FILE_A ? FileBB[f - 1] : 0) | (f < FILE_H ? FileBB[f + 1] : 0); + ThisAndAdjacentFilesBB[f] = FileBB[f] | AdjacentFilesBB[f]; } - for (int rw = RANK_7, rb = RANK_2; rw >= RANK_1; rw--, rb++) - { - InFrontBB[WHITE][rw] = InFrontBB[WHITE][rw + 1] | RankBB[rw + 1]; - InFrontBB[BLACK][rb] = InFrontBB[BLACK][rb - 1] | RankBB[rb - 1]; - } + for (Rank r = RANK_1; r < RANK_8; r++) + InFrontBB[WHITE][r] = ~(InFrontBB[BLACK][r + 1] = InFrontBB[BLACK][r] | RankBB[r]); for (Color c = WHITE; c <= BLACK; c++) for (Square s = SQ_A1; s <= SQ_H8; s++) { - SquaresInFrontMask[c][s] = in_front_bb(c, s) & file_bb(s); - PassedPawnMask[c][s] = in_front_bb(c, s) & this_and_neighboring_files_bb(file_of(s)); - AttackSpanMask[c][s] = in_front_bb(c, s) & neighboring_files_bb(file_of(s)); + ForwardBB[c][s] = InFrontBB[c][rank_of(s)] & FileBB[file_of(s)]; + PassedPawnMask[c][s] = InFrontBB[c][rank_of(s)] & ThisAndAdjacentFilesBB[file_of(s)]; + AttackSpanMask[c][s] = InFrontBB[c][rank_of(s)] & AdjacentFilesBB[file_of(s)]; } for (Square s1 = SQ_A1; s1 <= SQ_H8; s1++) @@ -216,56 +216,52 @@ void bitboards_init() { { Square to = s + Square(c == WHITE ? steps[pt][k] : -steps[pt][k]); - if (square_is_ok(to) && square_distance(s, to) < 3) - set_bit(&StepAttacksBB[make_piece(c, pt)][s], to); + if (is_ok(to) && square_distance(s, to) < 3) + StepAttacksBB[make_piece(c, pt)][s] |= to; } - init_magic_bitboards(ROOK, RAttacks, RMagics, RMasks, RShifts); - init_magic_bitboards(BISHOP, BAttacks, BMagics, BMasks, BShifts); + Square RDeltas[] = { DELTA_N, DELTA_E, DELTA_S, DELTA_W }; + Square BDeltas[] = { DELTA_NE, DELTA_SE, DELTA_SW, DELTA_NW }; + + init_magics(RTable, RAttacks, RMagics, RMasks, RShifts, RDeltas, magic_index); + init_magics(BTable, BAttacks, BMagics, BMasks, BShifts, BDeltas, magic_index); for (Square s = SQ_A1; s <= SQ_H8; s++) { - PseudoAttacks[BISHOP][s] = bishop_attacks_bb(s, 0); - PseudoAttacks[ROOK][s] = rook_attacks_bb(s, 0); - PseudoAttacks[QUEEN][s] = queen_attacks_bb(s, 0); + PseudoAttacks[QUEEN][s] = PseudoAttacks[BISHOP][s] = attacks_bb(s, 0); + PseudoAttacks[QUEEN][s] |= PseudoAttacks[ ROOK][s] = attacks_bb< ROOK>(s, 0); } for (Square s1 = SQ_A1; s1 <= SQ_H8; s1++) for (Square s2 = SQ_A1; s2 <= SQ_H8; s2++) - if (bit_is_set(PseudoAttacks[QUEEN][s1], s2)) + if (PseudoAttacks[QUEEN][s1] & s2) { Square delta = (s2 - s1) / square_distance(s1, s2); for (Square s = s1 + delta; s != s2; s += delta) - set_bit(&BetweenBB[s1][s2], s); + BetweenBB[s1][s2] |= s; } } namespace { - Bitboard sliding_attacks(PieceType pt, Square sq, Bitboard occupied) { + Bitboard sliding_attack(Square deltas[], Square sq, Bitboard occupied) { - Square deltas[][4] = { { DELTA_N, DELTA_E, DELTA_S, DELTA_W }, - { DELTA_NE, DELTA_SE, DELTA_SW, DELTA_NW } }; - Bitboard attacks = 0; - Square* delta = (pt == ROOK ? deltas[0] : deltas[1]); + Bitboard attack = 0; for (int i = 0; i < 4; i++) - { - Square s = sq + delta[i]; - - while (square_is_ok(s) && square_distance(s, s - delta[i]) == 1) + for (Square s = sq + deltas[i]; + is_ok(s) && square_distance(s, s - deltas[i]) == 1; + s += deltas[i]) { - set_bit(&attacks, s); + attack |= s; - if (bit_is_set(occupied, s)) + if (occupied & s) break; - - s += delta[i]; } - } - return attacks; + + return attack; } @@ -291,22 +287,22 @@ namespace { } - // init_magic_bitboards() computes all rook and bishop magics at startup. - // Magic bitboards are used to look up attacks of sliding pieces. As reference - // see chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we + // init_magics() computes all rook and bishop attacks at startup. Magic + // bitboards are used to look up attacks of sliding pieces. As a reference see + // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we // use the so called "fancy" approach. - void init_magic_bitboards(PieceType pt, Bitboard* attacks[], Bitboard magics[], - Bitboard masks[], int shifts[]) { + void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], + Bitboard masks[], unsigned shifts[], Square deltas[], Fn index) { int MagicBoosters[][8] = { { 3191, 2184, 1310, 3618, 2091, 1308, 2452, 3996 }, { 1059, 3608, 605, 3234, 3326, 38, 2029, 3043 } }; RKISS rk; Bitboard occupancy[4096], reference[4096], edges, b; - int i, size, index, booster; + int i, size, booster; // attacks[s] is a pointer to the beginning of the attacks table for square 's' - attacks[SQ_A1] = (pt == ROOK ? RookTable : BishopTable); + attacks[SQ_A1] = table; for (Square s = SQ_A1; s <= SQ_H8; s++) { @@ -318,15 +314,15 @@ namespace { // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. - masks[s] = sliding_attacks(pt, s, 0) & ~edges; + masks[s] = sliding_attack(deltas, s, 0) & ~edges; shifts[s] = (Is64Bit ? 64 : 32) - popcount(masks[s]); // Use Carry-Rippler trick to enumerate all subsets of masks[s] and - // store the corresponding sliding attacks bitboard in reference[]. + // store the corresponding sliding attack bitboard in reference[]. b = size = 0; do { occupancy[size] = b; - reference[size++] = sliding_attacks(pt, s, b); + reference[size++] = sliding_attack(deltas, s, b); b = (b - masks[s]) & masks[s]; } while (b); @@ -349,14 +345,12 @@ namespace { // effect of verifying the magic. for (i = 0; i < size; i++) { - index = (pt == ROOK ? rook_index(s, occupancy[i]) - : bishop_index(s, occupancy[i])); + Bitboard& attack = attacks[s][index(s, occupancy[i])]; - if (!attacks[s][index]) - attacks[s][index] = reference[i]; - - else if (attacks[s][index] != reference[i]) + if (attack && attack != reference[i]) break; + + attack = reference[i]; } } while (i != size); } diff --git a/DroidFish/jni/stockfish/bitboard.h b/DroidFish/jni/stockfish/bitboard.h index ce198c2..cc03939 100644 --- a/DroidFish/jni/stockfish/bitboard.h +++ b/DroidFish/jni/stockfish/bitboard.h @@ -23,62 +23,67 @@ #include "types.h" +namespace Bitboards { + +extern void init(); +extern void print(Bitboard b); + +} + +CACHE_LINE_ALIGNMENT + +extern Bitboard RMasks[64]; +extern Bitboard RMagics[64]; +extern Bitboard* RAttacks[64]; +extern unsigned RShifts[64]; + +extern Bitboard BMasks[64]; +extern Bitboard BMagics[64]; +extern Bitboard* BAttacks[64]; +extern unsigned BShifts[64]; + +extern Bitboard SquareBB[64]; extern Bitboard FileBB[8]; -extern Bitboard NeighboringFilesBB[8]; -extern Bitboard ThisAndNeighboringFilesBB[8]; extern Bitboard RankBB[8]; +extern Bitboard AdjacentFilesBB[8]; +extern Bitboard ThisAndAdjacentFilesBB[8]; extern Bitboard InFrontBB[2][8]; - -extern Bitboard SetMaskBB[65]; -extern Bitboard ClearMaskBB[65]; - extern Bitboard StepAttacksBB[16][64]; extern Bitboard BetweenBB[64][64]; - -extern Bitboard SquaresInFrontMask[2][64]; +extern Bitboard ForwardBB[2][64]; extern Bitboard PassedPawnMask[2][64]; extern Bitboard AttackSpanMask[2][64]; - -extern uint64_t RMagics[64]; -extern int RShifts[64]; -extern Bitboard RMasks[64]; -extern Bitboard* RAttacks[64]; - -extern uint64_t BMagics[64]; -extern int BShifts[64]; -extern Bitboard BMasks[64]; -extern Bitboard* BAttacks[64]; - extern Bitboard PseudoAttacks[6][64]; -extern uint8_t BitCount8Bit[256]; +/// 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. -/// Functions for testing whether a given bit is set in a bitboard, and for -/// setting and clearing bits. - -inline Bitboard bit_is_set(Bitboard b, Square s) { - return b & SetMaskBB[s]; +inline Bitboard operator&(Bitboard b, Square s) { + return b & SquareBB[s]; } -inline void set_bit(Bitboard* b, Square s) { - *b |= SetMaskBB[s]; +inline Bitboard& operator|=(Bitboard& b, Square s) { + return b |= SquareBB[s]; } -inline void clear_bit(Bitboard* b, Square s) { - *b &= ClearMaskBB[s]; +inline Bitboard& operator^=(Bitboard& b, Square s) { + return b ^= SquareBB[s]; +} + +inline Bitboard operator|(Bitboard b, Square s) { + return b | SquareBB[s]; +} + +inline Bitboard operator^(Bitboard b, Square s) { + return b ^ SquareBB[s]; } -/// Functions used to update a bitboard after a move. This is faster -/// then calling a sequence of clear_bit() + set_bit() +/// more_than_one() returns true if in 'b' there is more than one bit set -inline Bitboard make_move_bb(Square from, Square to) { - return SetMaskBB[from] | SetMaskBB[to]; -} - -inline void do_move_bb(Bitboard* b, Bitboard move_bb) { - *b ^= move_bb; +inline bool more_than_one(Bitboard b) { + return b & (b - 1); } @@ -102,19 +107,19 @@ inline Bitboard file_bb(Square s) { } -/// neighboring_files_bb takes a file as input and returns a bitboard representing -/// all squares on the neighboring files. +/// adjacent_files_bb takes a file as input and returns a bitboard representing +/// all squares on the adjacent files. -inline Bitboard neighboring_files_bb(File f) { - return NeighboringFilesBB[f]; +inline Bitboard adjacent_files_bb(File f) { + return AdjacentFilesBB[f]; } -/// this_and_neighboring_files_bb takes a file as input and returns a bitboard -/// representing all squares on the given and neighboring files. +/// this_and_adjacent_files_bb takes a file as input and returns a bitboard +/// representing all squares on the given and adjacent files. -inline Bitboard this_and_neighboring_files_bb(File f) { - return ThisAndNeighboringFilesBB[f]; +inline Bitboard this_and_adjacent_files_bb(File f) { + return ThisAndAdjacentFilesBB[f]; } @@ -133,71 +138,30 @@ inline Bitboard in_front_bb(Color c, Square s) { } -/// Functions for computing sliding attack bitboards. rook_attacks_bb(), -/// bishop_attacks_bb() and queen_attacks_bb() all take a square and a -/// bitboard of occupied squares as input, and return a bitboard representing -/// all squares attacked by a rook, bishop or queen on the given square. +/// between_bb returns a bitboard representing all squares between two squares. +/// 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 line, file or diagonal, +/// 0 is returned. -#if defined(IS_64BIT) - -FORCE_INLINE unsigned rook_index(Square s, Bitboard occ) { - return unsigned(((occ & RMasks[s]) * RMagics[s]) >> RShifts[s]); -} - -FORCE_INLINE unsigned bishop_index(Square s, Bitboard occ) { - return unsigned(((occ & BMasks[s]) * BMagics[s]) >> BShifts[s]); -} - -#else // if !defined(IS_64BIT) - -FORCE_INLINE unsigned rook_index(Square s, Bitboard occ) { - Bitboard b = occ & RMasks[s]; - return unsigned(int(b) * int(RMagics[s]) ^ int(b >> 32) * int(RMagics[s] >> 32)) >> RShifts[s]; -} - -FORCE_INLINE unsigned bishop_index(Square s, Bitboard occ) { - Bitboard b = occ & BMasks[s]; - return unsigned(int(b) * int(BMagics[s]) ^ int(b >> 32) * int(BMagics[s] >> 32)) >> BShifts[s]; -} -#endif - -inline Bitboard rook_attacks_bb(Square s, Bitboard occ) { - return RAttacks[s][rook_index(s, occ)]; -} - -inline Bitboard bishop_attacks_bb(Square s, Bitboard occ) { - return BAttacks[s][bishop_index(s, occ)]; -} - -inline Bitboard queen_attacks_bb(Square s, Bitboard blockers) { - return rook_attacks_bb(s, blockers) | bishop_attacks_bb(s, blockers); -} - - -/// squares_between returns a bitboard representing all squares between -/// two squares. For instance, squares_between(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 line, file or diagonal, EmptyBoardBB is returned. - -inline Bitboard squares_between(Square s1, Square s2) { +inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -/// squares_in_front_of takes a color and a square as input, and returns a -/// bitboard representing all squares along the line in front of the square, -/// from the point of view of the given color. Definition of the table is: -/// SquaresInFrontOf[c][s] = in_front_bb(c, s) & file_bb(s) +/// forward_bb takes a color and a square as input, and returns a bitboard +/// representing all squares along the line in front of the square, from the +/// point of view of the given color. Definition of the table is: +/// ForwardBB[c][s] = in_front_bb(c, s) & file_bb(s) -inline Bitboard squares_in_front_of(Color c, Square s) { - return SquaresInFrontMask[c][s]; +inline Bitboard forward_bb(Color c, Square s) { + return ForwardBB[c][s]; } /// passed_pawn_mask takes a color and a square as input, and returns a /// bitboard mask which can be used to test if a pawn of the given color on /// the given square is a passed pawn. Definition of the table is: -/// PassedPawnMask[c][s] = in_front_bb(c, s) & this_and_neighboring_files_bb(s) +/// PassedPawnMask[c][s] = in_front_bb(c, s) & this_and_adjacent_files_bb(s) inline Bitboard passed_pawn_mask(Color c, Square s) { return PassedPawnMask[c][s]; @@ -207,7 +171,7 @@ inline Bitboard passed_pawn_mask(Color c, Square s) { /// attack_span_mask takes a color and a square as input, and returns a bitboard /// representing all squares that can be attacked by a pawn of the given color /// when it moves along its file starting from the given square. Definition is: -/// AttackSpanMask[c][s] = in_front_bb(c, s) & neighboring_files_bb(s); +/// AttackSpanMask[c][s] = in_front_bb(c, s) & adjacent_files_bb(s); inline Bitboard attack_span_mask(Color c, Square s) { return AttackSpanMask[c][s]; @@ -219,7 +183,7 @@ inline Bitboard attack_span_mask(Color c, Square s) { inline bool squares_aligned(Square s1, Square s2, Square s3) { return (BetweenBB[s1][s2] | BetweenBB[s1][s3] | BetweenBB[s2][s3]) - & ( SetMaskBB[s1] | SetMaskBB[s2] | SetMaskBB[s3]); + & ( SquareBB[s1] | SquareBB[s2] | SquareBB[s3]); } @@ -227,8 +191,32 @@ inline bool squares_aligned(Square s1, Square s2, Square s3) { /// the same color of the given square. inline Bitboard same_color_squares(Square s) { - return bit_is_set(0xAA55AA55AA55AA55ULL, s) ? 0xAA55AA55AA55AA55ULL - : ~0xAA55AA55AA55AA55ULL; + return Bitboard(0xAA55AA55AA55AA55ULL) & s ? 0xAA55AA55AA55AA55ULL + : ~0xAA55AA55AA55AA55ULL; +} + + +/// Functions for computing sliding attack bitboards. Function attacks_bb() takes +/// a square and a bitboard of occupied squares as input, and returns a bitboard +/// representing all squares attacked by Pt (bishop or rook) on the given square. +template +FORCE_INLINE unsigned magic_index(Square s, Bitboard occ) { + + Bitboard* const Masks = Pt == ROOK ? RMasks : BMasks; + Bitboard* const Magics = Pt == ROOK ? RMagics : BMagics; + unsigned* const Shifts = Pt == ROOK ? RShifts : BShifts; + + if (Is64Bit) + return unsigned(((occ & Masks[s]) * Magics[s]) >> Shifts[s]); + + unsigned lo = unsigned(occ) & unsigned(Masks[s]); + unsigned hi = unsigned(occ >> 32) & unsigned(Masks[s] >> 32); + return (lo * unsigned(Magics[s]) ^ hi * unsigned(Magics[s] >> 32)) >> Shifts[s]; +} + +template +inline Bitboard attacks_bb(Square s, Bitboard occ) { + return (Pt == ROOK ? RAttacks : BAttacks)[s][magic_index(s, occ)]; } @@ -241,9 +229,15 @@ inline Bitboard same_color_squares(Square s) { #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) FORCE_INLINE Square first_1(Bitboard b) { - unsigned long index; - _BitScanForward64(&index, b); - return (Square) index; + unsigned long index; + _BitScanForward64(&index, b); + return (Square) index; +} + +FORCE_INLINE Square last_1(Bitboard b) { + unsigned long index; + _BitScanReverse64(&index, b); + return (Square) index; } #else @@ -252,6 +246,12 @@ FORCE_INLINE Square first_1(Bitboard b) { // Assembly code by Heinz van Saanen __asm__("bsfq %1, %0": "=r"(dummy): "rm"(b) ); return (Square) dummy; } + +FORCE_INLINE Square last_1(Bitboard b) { + Bitboard dummy; + __asm__("bsrq %1, %0": "=r"(dummy): "rm"(b) ); + return (Square) dummy; +} #endif FORCE_INLINE Square pop_1st_bit(Bitboard* b) { @@ -263,12 +263,9 @@ FORCE_INLINE Square pop_1st_bit(Bitboard* b) { #else // if !defined(USE_BSFQ) extern Square first_1(Bitboard b); +extern Square last_1(Bitboard b); extern Square pop_1st_bit(Bitboard* b); #endif - -extern void print_bitboard(Bitboard b); -extern void bitboards_init(); - #endif // !defined(BITBOARD_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/book.cpp b/DroidFish/jni/stockfish/book.cpp index d5bab90..40b647c 100644 --- a/DroidFish/jni/stockfish/book.cpp +++ b/DroidFish/jni/stockfish/book.cpp @@ -306,25 +306,23 @@ namespace { const Key* ZobEnPassant = PolyGlotRandoms + 772; const Key* ZobTurn = PolyGlotRandoms + 780; - // PieceOffset is calculated as 64 * (PolyPiece ^ 1) where PolyPiece - // is: BP = 0, WP = 1, BN = 2, WN = 3 ... BK = 10, WK = 11 - const int PieceOffset[] = { 0, 64, 192, 320, 448, 576, 704, 0, - 0, 0, 128, 256, 384, 512, 640 }; - // book_key() returns the PolyGlot hash key of the given position uint64_t book_key(const Position& pos) { uint64_t key = 0; - Bitboard b = pos.occupied_squares(); + Bitboard b = pos.pieces(); while (b) { + // Piece offset is at 64 * polyPiece where polyPiece is defined as: + // BP = 0, WP = 1, BN = 2, WN = 3, ... BK = 10, WK = 11 Square s = pop_1st_bit(&b); - key ^= ZobPiece[PieceOffset[pos.piece_on(s)] + s]; + Piece p = pos.piece_on(s); + int polyPiece = 2 * (type_of(p) - 1) + (color_of(p) == WHITE); + key ^= ZobPiece[64 * polyPiece + s]; } - b = (pos.can_castle(WHITE_OO) << 0) | (pos.can_castle(WHITE_OOO) << 1) - | (pos.can_castle(BLACK_OO) << 2) | (pos.can_castle(BLACK_OOO) << 3); + b = pos.can_castle(ALL_CASTLES); while (b) key ^= ZobCastle[pop_1st_bit(&b)]; @@ -342,7 +340,7 @@ namespace { Book::Book() : size(0) { - for (int i = abs(system_time() % 10000); i > 0; i--) + for (int i = Time::current_time().msec() % 10000; i > 0; i--) RKiss.rand(); // Make random number generation less deterministic } @@ -380,10 +378,13 @@ bool Book::open(const char* fName) { ifstream::open(fName, ifstream::in | ifstream::binary | ios::ate); if (!is_open()) + { + clear(); return false; // Silently fail if the file is not found + } // Get the book size in number of entries, we are already at the end of file - size = tellg() / sizeof(BookEntry); + size = (size_t)tellg() / sizeof(BookEntry); if (!good()) { @@ -421,7 +422,7 @@ Move Book::probe(const Position& pos, const string& fName, bool pickBest) { // Choose book move according to its score. If a move has a very // high score it has higher probability to be choosen than a move // with lower score. Note that first entry is always chosen. - if ( (RKiss.rand() % sum < e.count) + if ( (sum && RKiss.rand() % sum < e.count) || (pickBest && e.count == best)) move = Move(e.move); } diff --git a/DroidFish/jni/stockfish/endgame.cpp b/DroidFish/jni/stockfish/endgame.cpp index 27fbe44..feaf59a 100644 --- a/DroidFish/jni/stockfish/endgame.cpp +++ b/DroidFish/jni/stockfish/endgame.cpp @@ -22,10 +22,9 @@ #include "bitcount.h" #include "endgame.h" -#include "pawns.h" +#include "movegen.h" using std::string; -using namespace std; extern uint32_t probe_kpk_bitbase(Square wksq, Square wpsq, Square bksq, Color stm); @@ -73,12 +72,12 @@ namespace { string sides[] = { code.substr(code.find('K', 1)), // Weaker code.substr(0, code.find('K', 1)) }; // Stronger - transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); string fen = sides[0] + char('0' + int(8 - code.length())) + sides[1] + "/8/8/8/8/8/8/8 w - - 0 10"; - return Position(fen, false, 0).material_key(); + return Position(fen, false, NULL).material_key(); } template @@ -117,10 +116,8 @@ Endgames::~Endgames() { template void Endgames::add(const string& code) { - typedef typename eg_family::type T; - - map((T*)0)[key(code, WHITE)] = new Endgame(WHITE); - map((T*)0)[key(code, BLACK)] = new Endgame(BLACK); + map((Endgame*)0)[key(code, WHITE)] = new Endgame(WHITE); + map((Endgame*)0)[key(code, BLACK)] = new Endgame(BLACK); } @@ -134,6 +131,13 @@ Value Endgame::operator()(const Position& pos) const { assert(pos.non_pawn_material(weakerSide) == VALUE_ZERO); assert(pos.piece_count(weakerSide, PAWN) == VALUE_ZERO); + // Stalemate detection with lone king + if ( pos.side_to_move() == weakerSide + && !pos.in_check() + && !MoveList(pos).size()) { + return VALUE_DRAW; + } + Square winnerKSq = pos.king_square(strongerSide); Square loserKSq = pos.king_square(weakerSide); @@ -144,9 +148,9 @@ Value Endgame::operator()(const Position& pos) const { if ( pos.piece_count(strongerSide, QUEEN) || pos.piece_count(strongerSide, ROOK) - || pos.piece_count(strongerSide, BISHOP) > 1) - // TODO: check for two equal-colored bishops! - result += VALUE_KNOWN_WIN; + || pos.bishop_pair(strongerSide)) { + result += VALUE_KNOWN_WIN; + } return strongerSide == pos.side_to_move() ? result : -result; } @@ -402,11 +406,11 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // No assertions about the material of weakerSide, because we want draws to // be detected even when the weaker side has some pawns. - Bitboard pawns = pos.pieces(PAWN, strongerSide); + Bitboard pawns = pos.pieces(strongerSide, PAWN); File pawnFile = file_of(pos.piece_list(strongerSide, PAWN)[0]); // All pawns are on a single rook file ? - if ( (pawnFile == FILE_A || pawnFile == FILE_H) + if ( (pawnFile == FILE_A || pawnFile == FILE_H) && !(pawns & ~file_bb(pawnFile))) { Square bishopSq = pos.piece_list(strongerSide, BISHOP)[0]; @@ -417,7 +421,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { && abs(file_of(kingSq) - pawnFile) <= 1) { // The bishop has the wrong color, and the defending king is on the - // file of the pawn(s) or the neighboring file. Find the rank of the + // file of the pawn(s) or the adjacent file. Find the rank of the // frontmost pawn. Rank rank; if (strongerSide == WHITE) @@ -456,12 +460,12 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square kingSq = pos.king_square(weakerSide); if ( relative_rank(weakerSide, kingSq) <= RANK_2 && relative_rank(weakerSide, pos.king_square(strongerSide)) >= RANK_4 - && (pos.pieces(ROOK, weakerSide) & rank_bb(relative_rank(weakerSide, RANK_3))) - && (pos.pieces(PAWN, weakerSide) & rank_bb(relative_rank(weakerSide, RANK_2))) - && (pos.attacks_from(kingSq) & pos.pieces(PAWN, weakerSide))) + && (pos.pieces(weakerSide, ROOK) & rank_bb(relative_rank(weakerSide, RANK_3))) + && (pos.pieces(weakerSide, PAWN) & rank_bb(relative_rank(weakerSide, RANK_2))) + && (pos.attacks_from(kingSq) & pos.pieces(weakerSide, PAWN))) { Square rsq = pos.piece_list(weakerSide, ROOK)[0]; - if (pos.attacks_from(rsq, strongerSide) & pos.pieces(PAWN, weakerSide)) + if (pos.attacks_from(rsq, strongerSide) & pos.pieces(weakerSide, PAWN)) return SCALE_FACTOR_DRAW; } return SCALE_FACTOR_NONE; @@ -639,14 +643,14 @@ ScaleFactor Endgame::operator()(const Position& pos) const { assert(pos.piece_count(weakerSide, PAWN) == 0); Square ksq = pos.king_square(weakerSide); - Bitboard pawns = pos.pieces(PAWN, strongerSide); + Bitboard pawns = pos.pieces(strongerSide, PAWN); // Are all pawns on the 'a' file? if (!(pawns & ~FileABB)) { // Does the defending king block the pawns? if ( square_distance(ksq, relative_square(strongerSide, SQ_A8)) <= 1 - || ( file_of(ksq) == FILE_A + || ( file_of(ksq) == FILE_A && !in_front_bb(strongerSide, ksq) & pawns)) return SCALE_FACTOR_DRAW; } @@ -655,7 +659,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { { // Does the defending king block the pawns? if ( square_distance(ksq, relative_square(strongerSide, SQ_H8)) <= 1 - || ( file_of(ksq) == FILE_H + || ( file_of(ksq) == FILE_H && !in_front_bb(strongerSide, ksq) & pawns)) return SCALE_FACTOR_DRAW; } @@ -706,9 +710,9 @@ ScaleFactor Endgame::operator()(const Position& pos) const { return SCALE_FACTOR_DRAW; else { - Bitboard path = squares_in_front_of(strongerSide, pawnSq); + Bitboard path = forward_bb(strongerSide, pawnSq); - if (path & pos.pieces(KING, weakerSide)) + if (path & pos.pieces(weakerSide, KING)) return SCALE_FACTOR_DRAW; if ( (pos.attacks_from(weakerBishopSq) & path) @@ -769,20 +773,20 @@ ScaleFactor Endgame::operator()(const Position& pos) const { return SCALE_FACTOR_NONE; case 1: - // Pawns on neighboring files. Draw if defender firmly controls the square + // Pawns on adjacent files. Draw if defender firmly controls the square // in front of the frontmost pawn's path, and the square diagonally behind // this square on the file of the other pawn. if ( ksq == blockSq1 && opposite_colors(ksq, wbsq) && ( bbsq == blockSq2 - || (pos.attacks_from(blockSq2) & pos.pieces(BISHOP, weakerSide)) + || (pos.attacks_from(blockSq2) & pos.pieces(weakerSide, BISHOP)) || abs(r1 - r2) >= 2)) return SCALE_FACTOR_DRAW; else if ( ksq == blockSq2 && opposite_colors(ksq, wbsq) && ( bbsq == blockSq1 - || (pos.attacks_from(blockSq1) & pos.pieces(BISHOP, weakerSide)))) + || (pos.attacks_from(blockSq1) & pos.pieces(weakerSide, BISHOP)))) return SCALE_FACTOR_DRAW; else return SCALE_FACTOR_NONE; diff --git a/DroidFish/jni/stockfish/endgame.h b/DroidFish/jni/stockfish/endgame.h index 70f795c..0d7c063 100644 --- a/DroidFish/jni/stockfish/endgame.h +++ b/DroidFish/jni/stockfish/endgame.h @@ -61,11 +61,12 @@ enum EndgameType { }; -/// Some magic to detect family type of endgame from its enum value +/// Endgame functions can be of two types according if return a Value or a +/// ScaleFactor. Type eg_fun::type equals to either ScaleFactor or Value +/// depending if the template parameter is 0 or 1. -template struct bool_to_type { typedef Value type; }; -template<> struct bool_to_type { typedef ScaleFactor type; }; -template struct eg_family : public bool_to_type<(E > SCALE_FUNS)> {}; +template struct eg_fun { typedef Value type; }; +template<> struct eg_fun<1> { typedef ScaleFactor type; }; /// Base and derived templates for endgame evaluation and scaling functions @@ -79,7 +80,7 @@ struct EndgameBase { }; -template::type> +template SCALE_FUNS)>::type> struct Endgame : public EndgameBase { explicit Endgame(Color c) : strongerSide(c), weakerSide(~c) {} @@ -93,18 +94,18 @@ private: /// Endgames class stores in two std::map the pointers to endgame evaluation /// and scaling base objects. Then we use polymorphism to invoke the actual -/// endgame function calling its operator() method that is virtual. +/// endgame function calling its operator() that is virtual. class Endgames { - typedef std::map*> M1; - typedef std::map*> M2; + typedef std::map::type>*> M1; + typedef std::map::type>*> M2; M1 m1; M2 m2; - M1& map(Value*) { return m1; } - M2& map(ScaleFactor*) { return m2; } + M1& map(M1::mapped_type) { return m1; } + M2& map(M2::mapped_type) { return m2; } template void add(const std::string& code); @@ -112,9 +113,8 @@ public: Endgames(); ~Endgames(); - template EndgameBase* get(Key key) { - return map((T*)0).count(key) ? map((T*)0)[key] : NULL; - } + template T probe(Key key, T& eg) + { return eg = map(eg).count(key) ? map(eg)[key] : NULL; } }; #endif // !defined(ENDGAME_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/evaluate.cpp b/DroidFish/jni/stockfish/evaluate.cpp index 6f652c0..8a5b661 100644 --- a/DroidFish/jni/stockfish/evaluate.cpp +++ b/DroidFish/jni/stockfish/evaluate.cpp @@ -18,7 +18,6 @@ */ #include -#include #include #include #include @@ -37,8 +36,8 @@ namespace { struct EvalInfo { // Pointers to material and pawn hash table entries - MaterialInfo* mi; - PawnInfo* pi; + MaterialEntry* mi; + PawnEntry* pi; // attackedBy[color][piece type] is a bitboard representing all squares // attacked by a given color and piece type, attackedBy[color][0] contains @@ -151,13 +150,16 @@ namespace { #undef S + // Bonus for having the side to move (modified by Joona Kiiski) + const Score Tempo = make_score(24, 11); + // Rooks and queens on the 7th rank (modified by Joona Kiiski) const Score RookOn7thBonus = make_score(47, 98); const Score QueenOn7thBonus = make_score(27, 54); // Rooks on open files (modified by Joona Kiiski) - const Score RookOpenFileBonus = make_score(43, 43); - const Score RookHalfOpenFileBonus = make_score(19, 19); + const Score RookOpenFileBonus = make_score(43, 21); + const Score RookHalfOpenFileBonus = make_score(19, 10); // Penalty for rooks trapped inside a friendly king which has lost the // right to castle. @@ -168,6 +170,9 @@ namespace { // happen in Chess960 games. const Score TrappedBishopA1H1Penalty = make_score(100, 100); + // Penalty for an undefended bishop or knight + const Score UndefendedMinorPenalty = make_score(25, 10); + // The SpaceMask[Color] contains the area of the board which is considered // by the space evaluation. In the middle game, each side is given a bonus // based on how many squares inside this area are safe and available for @@ -220,8 +225,8 @@ namespace { std::stringstream TraceStream; enum TracedType { - PST = 8, IMBALANCE = 9, MOBILITY = 10, THREAT = 11, - PASSED = 12, UNSTOPPABLE = 13, SPACE = 14, TOTAL = 15 + PST = 8, IMBALANCE = 9, MOBILITY = 10, THREAT = 11, + PASSED = 12, UNSTOPPABLE = 13, SPACE = 14, TOTAL = 15 }; // Function prototypes @@ -248,42 +253,129 @@ namespace { Score evaluate_unstoppable_pawns(const Position& pos, EvalInfo& ei); - inline Score apply_weight(Score v, Score weight); - Value scale_by_game_phase(const Score& v, Phase ph, ScaleFactor sf); + Value interpolate(const Score& v, Phase ph, ScaleFactor sf); Score weight_option(const std::string& mgOpt, const std::string& egOpt, Score internalWeight); - void init_safety(); double to_cp(Value v); void trace_add(int idx, Score term_w, Score term_b = SCORE_ZERO); + void trace_row(const char* name, int idx); } -/// evaluate() is the main evaluation function. It always computes two -/// values, an endgame score and a middle game score, and interpolates -/// between them based on the remaining material. -Value evaluate(const Position& pos, Value& margin) { return do_evaluate(pos, margin); } +namespace Eval { + + Color RootColor; + + /// evaluate() is the main evaluation function. It always computes two + /// values, an endgame score and a middle game score, and interpolates + /// between them based on the remaining material. + + Value evaluate(const Position& pos, Value& margin) { + return do_evaluate(pos, margin); + } + + + /// init() computes evaluation weights from the corresponding UCI parameters + /// and setup king tables. + + void init() { + + Weights[Mobility] = weight_option("Mobility (Middle Game)", "Mobility (Endgame)", WeightsInternal[Mobility]); + Weights[PassedPawns] = weight_option("Passed Pawns (Middle Game)", "Passed Pawns (Endgame)", WeightsInternal[PassedPawns]); + Weights[Space] = weight_option("Space", "Space", WeightsInternal[Space]); + Weights[KingDangerUs] = weight_option("Cowardice", "Cowardice", WeightsInternal[KingDangerUs]); + Weights[KingDangerThem] = weight_option("Aggressiveness", "Aggressiveness", WeightsInternal[KingDangerThem]); + + // King safety is asymmetrical. Our king danger level is weighted by + // "Cowardice" UCI parameter, instead the opponent one by "Aggressiveness". + // If running in analysis mode, make sure we use symmetrical king safety. We + // do this by replacing both Weights[kingDangerUs] and Weights[kingDangerThem] + // by their average. + if (Options["UCI_AnalyseMode"]) + Weights[KingDangerUs] = Weights[KingDangerThem] = (Weights[KingDangerUs] + Weights[KingDangerThem]) / 2; + + const int MaxSlope = 30; + const int Peak = 1280; + + for (int t = 0, i = 1; i < 100; i++) + { + t = std::min(Peak, std::min(int(0.4 * i * i), t + MaxSlope)); + + KingDangerTable[1][i] = apply_weight(make_score(t, 0), Weights[KingDangerUs]); + KingDangerTable[0][i] = apply_weight(make_score(t, 0), Weights[KingDangerThem]); + } + } + + + /// trace() is like evaluate() but instead of a value returns a string suitable + /// to be print on stdout with the detailed descriptions and values of each + /// evaluation term. Used mainly for debugging. + + std::string trace(const Position& pos) { + + Value margin; + std::string totals; + + RootColor = pos.side_to_move(); + + TraceStream.str(""); + TraceStream << std::showpoint << std::showpos << std::fixed << std::setprecision(2); + memset(TracedScores, 0, 2 * 16 * sizeof(Score)); + + do_evaluate(pos, margin); + + totals = TraceStream.str(); + TraceStream.str(""); + + TraceStream << std::setw(21) << "Eval term " << "| White | Black | Total \n" + << " | MG EG | MG EG | MG EG \n" + << "---------------------+-------------+-------------+---------------\n"; + + trace_row("Material, PST, Tempo", PST); + trace_row("Material imbalance", IMBALANCE); + trace_row("Pawns", PAWN); + trace_row("Knights", KNIGHT); + trace_row("Bishops", BISHOP); + trace_row("Rooks", ROOK); + trace_row("Queens", QUEEN); + trace_row("Mobility", MOBILITY); + trace_row("King safety", KING); + trace_row("Threats", THREAT); + trace_row("Passed pawns", PASSED); + trace_row("Unstoppable pawns", UNSTOPPABLE); + trace_row("Space", SPACE); + + TraceStream << "---------------------+-------------+-------------+---------------\n"; + trace_row("Total", TOTAL); + TraceStream << totals; + + return TraceStream.str(); + } + +} // namespace Eval + namespace { template Value do_evaluate(const Position& pos, Value& margin) { + assert(!pos.in_check()); + EvalInfo ei; Value margins[2]; Score score, mobilityWhite, mobilityBlack; - assert(pos.thread() >= 0 && pos.thread() < MAX_THREADS); - assert(!pos.in_check()); - - // Initialize score by reading the incrementally updated scores included - // in the position object (material + piece square tables). - score = pos.value(); - // margins[] store the uncertainty estimation of position's evaluation // that typically is used by the search for pruning decisions. margins[WHITE] = margins[BLACK] = VALUE_ZERO; + // Initialize score by reading the incrementally updated scores included + // in the position object (material + piece square tables) and adding + // Tempo bonus. Score is computed from the point of view of white. + score = pos.psq_score() + (pos.side_to_move() == WHITE ? Tempo : -Tempo); + // Probe the material hash table - ei.mi = Threads[pos.thread()].materialTable.material_info(pos); + ei.mi = pos.this_thread()->materialTable.probe(pos); score += ei.mi->material_value(); // If we have a specialized evaluation function for the current material @@ -295,7 +387,7 @@ Value do_evaluate(const Position& pos, Value& margin) { } // Probe the pawn hash table - ei.pi = Threads[pos.thread()].pawnTable.pawn_info(pos); + ei.pi = pos.this_thread()->pawnTable.probe(pos); score += ei.pi->pawns_value(); // Initialize attack and king safety bitboards @@ -339,7 +431,7 @@ Value do_evaluate(const Position& pos, Value& margin) { // If we don't already have an unusual scale factor, check for opposite // colored bishop endgames, and use a lower scale for those. if ( ei.mi->game_phase() < PHASE_MIDGAME - && pos.opposite_colored_bishops() + && pos.opposite_bishops() && sf == SCALE_FACTOR_NORMAL) { // Only the two bishops ? @@ -357,14 +449,13 @@ Value do_evaluate(const Position& pos, Value& margin) { sf = ScaleFactor(50); } - // Interpolate between the middle game and the endgame score margin = margins[pos.side_to_move()]; - Value v = scale_by_game_phase(score, ei.mi->game_phase(), sf); + Value v = interpolate(score, ei.mi->game_phase(), sf); // In case of tracing add all single evaluation contributions for both white and black if (Trace) { - trace_add(PST, pos.value()); + trace_add(PST, pos.psq_score()); trace_add(IMBALANCE, ei.mi->material_value()); trace_add(PAWN, ei.pi->pawns_value()); trace_add(MOBILITY, apply_weight(mobilityWhite, Weights[Mobility]), apply_weight(mobilityBlack, Weights[Mobility])); @@ -387,34 +478,6 @@ Value do_evaluate(const Position& pos, Value& margin) { return pos.side_to_move() == WHITE ? v : -v; } -} // namespace - - -/// read_weights() reads evaluation weights from the corresponding UCI parameters - -void read_evaluation_uci_options(Color us) { - - // King safety is asymmetrical. Our king danger level is weighted by - // "Cowardice" UCI parameter, instead the opponent one by "Aggressiveness". - const int kingDangerUs = (us == WHITE ? KingDangerUs : KingDangerThem); - const int kingDangerThem = (us == WHITE ? KingDangerThem : KingDangerUs); - - Weights[Mobility] = weight_option("Mobility (Middle Game)", "Mobility (Endgame)", WeightsInternal[Mobility]); - Weights[PassedPawns] = weight_option("Passed Pawns (Middle Game)", "Passed Pawns (Endgame)", WeightsInternal[PassedPawns]); - Weights[Space] = weight_option("Space", "Space", WeightsInternal[Space]); - Weights[kingDangerUs] = weight_option("Cowardice", "Cowardice", WeightsInternal[KingDangerUs]); - Weights[kingDangerThem] = weight_option("Aggressiveness", "Aggressiveness", WeightsInternal[KingDangerThem]); - - // If running in analysis mode, make sure we use symmetrical king safety. We do this - // by replacing both Weights[kingDangerUs] and Weights[kingDangerThem] by their average. - if (Options["UCI_AnalyseMode"]) - Weights[kingDangerUs] = Weights[kingDangerThem] = (Weights[kingDangerUs] + Weights[kingDangerThem]) / 2; - - init_safety(); -} - - -namespace { // init_eval_info() initializes king bitboards for given color adding // pawn attacks. To be done at the beginning of the evaluation. @@ -454,10 +517,10 @@ namespace { // Increase bonus if supported by pawn, especially if the opponent has // no minor piece which can exchange the outpost piece. - if (bonus && bit_is_set(ei.attackedBy[Us][PAWN], s)) + if (bonus && (ei.attackedBy[Us][PAWN] & s)) { - if ( !pos.pieces(KNIGHT, Them) - && !(same_color_squares(s) & pos.pieces(BISHOP, Them))) + if ( !pos.pieces(Them, KNIGHT) + && !(same_color_squares(s) & pos.pieces(Them, BISHOP))) bonus += bonus + bonus / 2; else bonus += bonus / 2; @@ -488,16 +551,14 @@ namespace { if (Piece == KNIGHT || Piece == QUEEN) b = pos.attacks_from(s); else if (Piece == BISHOP) - b = bishop_attacks_bb(s, pos.occupied_squares() & ~pos.pieces(QUEEN, Us)); + b = attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)); else if (Piece == ROOK) - b = rook_attacks_bb(s, pos.occupied_squares() & ~pos.pieces(ROOK, QUEEN, Us)); + b = attacks_bb(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)); else assert(false); - // Update attack info ei.attackedBy[Us][Piece] |= b; - // King attacks if (b & ei.kingRing[Them]) { ei.kingAttackersCount[Us]++; @@ -507,20 +568,31 @@ namespace { ei.kingAdjacentZoneAttacksCount[Us] += popcount(bb); } - // Mobility mob = (Piece != QUEEN ? popcount(b & mobilityArea) : popcount(b & mobilityArea)); mobility += MobilityBonus[Piece][mob]; + // Add a bonus if a slider is pinning an enemy piece + if ( (Piece == BISHOP || Piece == ROOK || Piece == QUEEN) + && (PseudoAttacks[Piece][pos.king_square(Them)] & s)) + { + b = BetweenBB[s][pos.king_square(Them)] & pos.pieces(); + + assert(b); + + if (!more_than_one(b) && (b & pos.pieces(Them))) + score += ThreatBonus[Piece][type_of(pos.piece_on(first_1(b)))]; + } + // Decrease score if we are attacked by an enemy pawn. Remaining part // of threat evaluation must be done later when we have full attack info. - if (bit_is_set(ei.attackedBy[Them][PAWN], s)) + if (ei.attackedBy[Them][PAWN] & s) score -= ThreatenedByPawnPenalty[Piece]; // Bishop and knight outposts squares if ( (Piece == BISHOP || Piece == KNIGHT) - && !(pos.pieces(PAWN, Them) & attack_span_mask(Us, s))) + && !(pos.pieces(Them, PAWN) & attack_span_mask(Us, s))) score += evaluate_outposts(pos, ei, s); // Queen or rook on 7th rank @@ -542,7 +614,7 @@ namespace { Square d = pawn_push(Us) + (file_of(s) == FILE_A ? DELTA_E : DELTA_W); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) { - if (!pos.square_is_empty(s + d + pawn_push(Us))) + if (!pos.is_empty(s + d + pawn_push(Us))) score -= 2*TrappedBishopA1H1Penalty; else if (pos.piece_on(s + 2*d) == make_piece(Us, PAWN)) score -= TrappedBishopA1H1Penalty; @@ -608,15 +680,25 @@ namespace { const Color Them = (Us == WHITE ? BLACK : WHITE); - Bitboard b; + Bitboard b, undefendedMinors, weakEnemies; Score score = SCORE_ZERO; + // Undefended minors get penalized even if not under attack + undefendedMinors = pos.pieces(Them) + & (pos.pieces(BISHOP) | pos.pieces(KNIGHT)) + & ~ei.attackedBy[Them][0]; + + if (undefendedMinors) + score += more_than_one(undefendedMinors) ? UndefendedMinorPenalty * 2 + : UndefendedMinorPenalty; + // Enemy pieces not defended by a pawn and under our attack - Bitboard weakEnemies = pos.pieces(Them) - & ~ei.attackedBy[Them][PAWN] - & ei.attackedBy[Us][0]; + weakEnemies = pos.pieces(Them) + & ~ei.attackedBy[Them][PAWN] + & ei.attackedBy[Us][0]; + if (!weakEnemies) - return SCORE_ZERO; + return score; // Add bonus according to type of attacked enemy piece and to the // type of attacking piece, from knights to queens. Kings are not @@ -670,8 +752,8 @@ namespace { int attackUnits; const Square ksq = pos.king_square(Us); - // King shelter - Score score = ei.pi->king_shelter(pos, ksq); + // King shelter and enemy pawns storm + Score score = ei.pi->king_safety(pos, ksq); // King safety. This is quite complicated, and is almost certainly far // from optimally tuned. @@ -693,7 +775,7 @@ namespace { attackUnits = std::min(25, (ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) / 2) + 3 * (ei.kingAdjacentZoneAttacksCount[Them] + popcount(undefended)) + InitKingDanger[relative_square(Us, ksq)] - - mg_value(ei.pi->king_shelter(pos, ksq)) / 32; + - mg_value(score) / 32; // Analyse enemy's safe queen contact checks. First find undefended // squares around the king attacked by enemy queen... @@ -761,8 +843,8 @@ namespace { // value that will be used for pruning because this value can sometimes // be very big, and so capturing a single attacking piece can therefore // result in a score change far bigger than the value of the captured piece. - score -= KingDangerTable[Us][attackUnits]; - margins[Us] += mg_value(KingDangerTable[Us][attackUnits]); + score -= KingDangerTable[Us == Eval::RootColor][attackUnits]; + margins[Us] += mg_value(KingDangerTable[Us == Eval::RootColor][attackUnits]); } if (Trace) @@ -812,16 +894,16 @@ namespace { ebonus -= Value(square_distance(pos.king_square(Us), blockSq + pawn_push(Us)) * rr); // If the pawn is free to advance, increase bonus - if (pos.square_is_empty(blockSq)) + if (pos.is_empty(blockSq)) { - squaresToQueen = squares_in_front_of(Us, s); + squaresToQueen = forward_bb(Us, s); defendedSquares = squaresToQueen & ei.attackedBy[Us][0]; // If there is an enemy rook or queen attacking the pawn from behind, // add all X-ray attacks by the rook or queen. Otherwise consider only // the squares in the pawn's path attacked or occupied by the enemy. - if ( (squares_in_front_of(Them, s) & pos.pieces(ROOK, QUEEN, Them)) - && (squares_in_front_of(Them, s) & pos.pieces(ROOK, QUEEN, Them) & pos.attacks_from(s))) + if ( (forward_bb(Them, s) & pos.pieces(Them, ROOK, QUEEN)) + && (forward_bb(Them, s) & pos.pieces(Them, ROOK, QUEEN) & pos.attacks_from(s))) unsafeSquares = squaresToQueen; else unsafeSquares = squaresToQueen & (ei.attackedBy[Them][0] | pos.pieces(Them)); @@ -841,7 +923,7 @@ namespace { // Increase the bonus if the passed pawn is supported by a friendly pawn // on the same rank and a bit smaller if it's on the previous rank. - supportingPawns = pos.pieces(PAWN, Us) & neighboring_files_bb(file_of(s)); + supportingPawns = pos.pieces(Us, PAWN) & adjacent_files_bb(file_of(s)); if (supportingPawns & rank_bb(s)) ebonus += Value(r * 20); @@ -858,7 +940,7 @@ namespace { { if (pos.non_pawn_material(Them) <= KnightValueMidgame) ebonus += ebonus / 4; - else if (pos.pieces(ROOK, QUEEN, Them)) + else if (pos.pieces(Them, ROOK, QUEEN)) ebonus -= ebonus / 4; } score += make_score(mbonus, ebonus); @@ -896,7 +978,7 @@ namespace { { s = pop_1st_bit(&b); queeningSquare = relative_square(c, make_square(file_of(s), RANK_8)); - queeningPath = squares_in_front_of(c, s); + queeningPath = forward_bb(c, s); // Compute plies to queening and check direct advancement movesToGo = rank_distance(s, queeningSquare) - int(relative_rank(c, s) == RANK_2); @@ -909,7 +991,7 @@ namespace { // Opponent king cannot block because path is defended and position // is not in check. So only friendly pieces can be blockers. assert(!pos.in_check()); - assert((queeningPath & pos.occupied_squares()) == (queeningPath & pos.pieces(c))); + assert((queeningPath & pos.pieces()) == (queeningPath & pos.pieces(c))); // Add moves needed to free the path from friendly pieces and retest condition movesToGo += popcount(queeningPath & pos.pieces(c)); @@ -931,7 +1013,7 @@ namespace { loserSide = ~winnerSide; // Step 3. Can the losing side possibly create a new passed pawn and thus prevent the loss? - b = candidates = pos.pieces(PAWN, loserSide); + b = candidates = pos.pieces(loserSide, PAWN); while (b) { @@ -944,8 +1026,8 @@ namespace { // Check if (without even considering any obstacles) we're too far away or doubled if ( pliesToQueen[winnerSide] + 3 <= pliesToGo - || (squares_in_front_of(loserSide, s) & pos.pieces(PAWN, loserSide))) - clear_bit(&candidates, s); + || (forward_bb(loserSide, s) & pos.pieces(loserSide, PAWN))) + candidates ^= s; } // If any candidate is already a passed pawn it _may_ promote in time. We give up. @@ -967,9 +1049,9 @@ namespace { pliesToGo = 2 * movesToGo - int(loserSide == pos.side_to_move()); // Generate list of blocking pawns and supporters - supporters = neighboring_files_bb(file_of(s)) & candidates; - opposed = squares_in_front_of(loserSide, s) & pos.pieces(PAWN, winnerSide); - blockers = passed_pawn_mask(loserSide, s) & pos.pieces(PAWN, winnerSide); + supporters = adjacent_files_bb(file_of(s)) & candidates; + opposed = forward_bb(loserSide, s) & pos.pieces(winnerSide, PAWN); + blockers = passed_pawn_mask(loserSide, s) & pos.pieces(winnerSide, PAWN); assert(blockers); @@ -1026,7 +1108,7 @@ namespace { } // Winning pawn is unstoppable and will promote as first, return big score - Score score = make_score(0, (Value) 0x500 - 0x20 * pliesToQueen[winnerSide]); + Score score = make_score(0, (Value) 1280 - 32 * pliesToQueen[winnerSide]); return winnerSide == WHITE ? score : -score; } @@ -1046,12 +1128,12 @@ namespace { // SpaceMask[]. A square is unsafe if it is attacked by an enemy // pawn, or if it is undefended and attacked by an enemy piece. Bitboard safe = SpaceMask[Us] - & ~pos.pieces(PAWN, Us) + & ~pos.pieces(Us, PAWN) & ~ei.attackedBy[Them][PAWN] & (ei.attackedBy[Us][0] | ~ei.attackedBy[Them][0]); // Find all squares which are at most three squares behind some friendly pawn - Bitboard behind = pos.pieces(PAWN, Us); + Bitboard behind = pos.pieces(Us, PAWN); behind |= (Us == WHITE ? behind >> 8 : behind << 8); behind |= (Us == WHITE ? behind >> 16 : behind << 16); @@ -1059,18 +1141,10 @@ namespace { } - // apply_weight() applies an evaluation weight to a value trying to prevent overflow - - inline Score apply_weight(Score v, Score w) { - return make_score((int(mg_value(v)) * mg_value(w)) / 0x100, - (int(eg_value(v)) * eg_value(w)) / 0x100); - } - - - // scale_by_game_phase() interpolates between a middle game and an endgame score, + // interpolate() interpolates between a middle game and an endgame score, // based on game phase. It also scales the return value by a ScaleFactor array. - Value scale_by_game_phase(const Score& v, Phase ph, ScaleFactor sf) { + Value interpolate(const Score& v, Phase ph, ScaleFactor sf) { assert(mg_value(v) > -VALUE_INFINITE && mg_value(v) < VALUE_INFINITE); assert(eg_value(v) > -VALUE_INFINITE && eg_value(v) < VALUE_INFINITE); @@ -1095,33 +1169,6 @@ namespace { } - // init_safety() initizes the king safety evaluation, based on UCI - // parameters. It is called from read_weights(). - - void init_safety() { - - const Value MaxSlope = Value(30); - const Value Peak = Value(1280); - Value t[100]; - - // First setup the base table - for (int i = 0; i < 100; i++) - { - t[i] = Value(int(0.4 * i * i)); - - if (i > 0) - t[i] = std::min(t[i], t[i - 1] + MaxSlope); - - t[i] = std::min(t[i], Peak); - } - - // Then apply the weights and get the final KingDangerTable[] array - for (Color c = WHITE; c <= BLACK; c++) - for (int i = 0; i < 100; i++) - KingDangerTable[c][i] = apply_weight(make_score(t[i], 0), Weights[KingDangerUs + c]); - } - - // A couple of little helpers used by tracing code, to_cp() converts a value to // a double in centipawns scale, trace_add() stores white and black scores. @@ -1129,14 +1176,15 @@ namespace { void trace_add(int idx, Score wScore, Score bScore) { - TracedScores[WHITE][idx] = wScore; - TracedScores[BLACK][idx] = bScore; + TracedScores[WHITE][idx] = wScore; + TracedScores[BLACK][idx] = bScore; } + // trace_row() is an helper function used by tracing code to register the // values of a single evaluation term. - void trace_row(const char *name, int idx) { + void trace_row(const char* name, int idx) { Score wScore = TracedScores[WHITE][idx]; Score bScore = TracedScores[BLACK][idx]; @@ -1159,47 +1207,3 @@ namespace { } } } - - -/// trace_evaluate() is like evaluate() but instead of a value returns a string -/// suitable to be print on stdout with the detailed descriptions and values of -/// each evaluation term. Used mainly for debugging. - -std::string trace_evaluate(const Position& pos) { - - Value margin; - std::string totals; - - TraceStream.str(""); - TraceStream << std::showpoint << std::showpos << std::fixed << std::setprecision(2); - memset(TracedScores, 0, 2 * 16 * sizeof(Score)); - - do_evaluate(pos, margin); - - totals = TraceStream.str(); - TraceStream.str(""); - - TraceStream << std::setw(21) << "Eval term " << "| White | Black | Total \n" - << " | MG EG | MG EG | MG EG \n" - << "---------------------+-------------+-------------+---------------\n"; - - trace_row("Material, PST, Tempo", PST); - trace_row("Material imbalance", IMBALANCE); - trace_row("Pawns", PAWN); - trace_row("Knights", KNIGHT); - trace_row("Bishops", BISHOP); - trace_row("Rooks", ROOK); - trace_row("Queens", QUEEN); - trace_row("Mobility", MOBILITY); - trace_row("King safety", KING); - trace_row("Threats", THREAT); - trace_row("Passed pawns", PASSED); - trace_row("Unstoppable pawns", UNSTOPPABLE); - trace_row("Space", SPACE); - - TraceStream << "---------------------+-------------+-------------+---------------\n"; - trace_row("Total", TOTAL); - TraceStream << totals; - - return TraceStream.str(); -} diff --git a/DroidFish/jni/stockfish/evaluate.h b/DroidFish/jni/stockfish/evaluate.h index 82ca105..a0418b7 100644 --- a/DroidFish/jni/stockfish/evaluate.h +++ b/DroidFish/jni/stockfish/evaluate.h @@ -24,8 +24,14 @@ class Position; +namespace Eval { + +extern Color RootColor; + +extern void init(); extern Value evaluate(const Position& pos, Value& margin); -extern std::string trace_evaluate(const Position& pos); -extern void read_evaluation_uci_options(Color sideToMove); +extern std::string trace(const Position& pos); + +} #endif // !defined(EVALUATE_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/history.h b/DroidFish/jni/stockfish/history.h index 2eb93d8..7899c84 100644 --- a/DroidFish/jni/stockfish/history.h +++ b/DroidFish/jni/stockfish/history.h @@ -20,9 +20,10 @@ #if !defined(HISTORY_H_INCLUDED) #define HISTORY_H_INCLUDED -#include "types.h" -#include #include +#include + +#include "types.h" /// The History class stores statistics about how often different moves /// have been successful or unsuccessful during the current search. These diff --git a/DroidFish/jni/stockfish/main.cpp b/DroidFish/jni/stockfish/main.cpp index 4e397e9..6e88988 100644 --- a/DroidFish/jni/stockfish/main.cpp +++ b/DroidFish/jni/stockfish/main.cpp @@ -21,37 +21,32 @@ #include #include "bitboard.h" -#include "misc.h" +#include "evaluate.h" #include "position.h" #include "search.h" #include "thread.h" +#include "tt.h" +#include "ucioption.h" -using namespace std; - -extern void uci_loop(); -extern void benchmark(int argc, char* argv[]); +extern void uci_loop(const std::string&); extern void kpk_bitbase_init(); int main(int argc, char* argv[]) { - bitboards_init(); + std::cout << engine_info() << std::endl; + + Bitboards::init(); Position::init(); kpk_bitbase_init(); Search::init(); Threads.init(); + Eval::init(); + TT.set_size(Options["Hash"]); - cout << engine_info() << endl; + std::string args; - if (argc == 1) - uci_loop(); + for (int i = 1; i < argc; i++) + args += std::string(argv[i]) + " "; - else if (string(argv[1]) == "bench") - benchmark(argc, argv); - - else - cerr << "\nUsage: stockfish bench [hash size = 128] [threads = 1] " - << "[limit = 12] [fen positions file = default] " - << "[limited by depth, time, nodes or perft = depth]" << endl; - - Threads.exit(); + uci_loop(args); } diff --git a/DroidFish/jni/stockfish/material.cpp b/DroidFish/jni/stockfish/material.cpp index 7369bca..b106892 100644 --- a/DroidFish/jni/stockfish/material.cpp +++ b/DroidFish/jni/stockfish/material.cpp @@ -17,9 +17,9 @@ along with this program. If not, see . */ +#include #include #include -#include #include "material.h" @@ -84,67 +84,57 @@ namespace { } // namespace -/// MaterialInfoTable c'tor and d'tor allocate and free the space for Endgames +/// MaterialTable::probe() takes a position object as input, looks up a MaterialEntry +/// object, and returns a pointer to it. If the material configuration is not +/// already present in the table, it is computed and stored there, so we don't +/// have to recompute everything when the same material configuration occurs again. -void MaterialInfoTable::init() { Base::init(); if (!funcs) funcs = new Endgames(); } -MaterialInfoTable::~MaterialInfoTable() { delete funcs; } - - -/// MaterialInfoTable::material_info() takes a position object as input, -/// computes or looks up a MaterialInfo object, and returns a pointer to it. -/// If the material configuration is not already present in the table, it -/// is stored there, so we don't have to recompute everything when the -/// same material configuration occurs again. - -MaterialInfo* MaterialInfoTable::material_info(const Position& pos) const { +MaterialEntry* MaterialTable::probe(const Position& pos) { Key key = pos.material_key(); - MaterialInfo* mi = probe(key); + MaterialEntry* e = entries[key]; - // If mi->key matches the position's material hash key, it means that we + // If e->key matches the position's material hash key, it means that we // have analysed this material configuration before, and we can simply // return the information we found the last time instead of recomputing it. - if (mi->key == key) - return mi; + if (e->key == key) + return e; - // Initialize MaterialInfo entry - memset(mi, 0, sizeof(MaterialInfo)); - mi->key = key; - mi->factor[WHITE] = mi->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; - - // Store game phase - mi->gamePhase = MaterialInfoTable::game_phase(pos); + memset(e, 0, sizeof(MaterialEntry)); + e->key = key; + e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; + e->gamePhase = MaterialTable::game_phase(pos); // Let's look if we have a specialized evaluation function for this // particular material configuration. First we look for a fixed // configuration one, then a generic one if previous search failed. - if ((mi->evaluationFunction = funcs->get(key)) != NULL) - return mi; + if (endgames.probe(key, e->evaluationFunction)) + return e; if (is_KXK(pos)) { - mi->evaluationFunction = &EvaluateKXK[WHITE]; - return mi; + e->evaluationFunction = &EvaluateKXK[WHITE]; + return e; } if (is_KXK(pos)) { - mi->evaluationFunction = &EvaluateKXK[BLACK]; - return mi; + e->evaluationFunction = &EvaluateKXK[BLACK]; + return e; } if (!pos.pieces(PAWN) && !pos.pieces(ROOK) && !pos.pieces(QUEEN)) { // Minor piece endgame with at least one minor piece per side and // no pawns. Note that the case KmmK is already handled by KXK. - assert((pos.pieces(KNIGHT, WHITE) | pos.pieces(BISHOP, WHITE))); - assert((pos.pieces(KNIGHT, BLACK) | pos.pieces(BISHOP, BLACK))); + assert((pos.pieces(WHITE, KNIGHT) | pos.pieces(WHITE, BISHOP))); + assert((pos.pieces(BLACK, KNIGHT) | pos.pieces(BLACK, BISHOP))); if ( pos.piece_count(WHITE, BISHOP) + pos.piece_count(WHITE, KNIGHT) <= 2 && pos.piece_count(BLACK, BISHOP) + pos.piece_count(BLACK, KNIGHT) <= 2) { - mi->evaluationFunction = &EvaluateKmmKm[pos.side_to_move()]; - return mi; + e->evaluationFunction = &EvaluateKmmKm[pos.side_to_move()]; + return e; } } @@ -155,26 +145,26 @@ MaterialInfo* MaterialInfoTable::material_info(const Position& pos) const { // scaling functions and we need to decide which one to use. EndgameBase* sf; - if ((sf = funcs->get(key)) != NULL) + if (endgames.probe(key, sf)) { - mi->scalingFunction[sf->color()] = sf; - return mi; + e->scalingFunction[sf->color()] = sf; + return e; } // Generic scaling functions that refer to more then one material // distribution. Should be probed after the specialized ones. // Note that these ones don't return after setting the function. if (is_KBPsKs(pos)) - mi->scalingFunction[WHITE] = &ScaleKBPsK[WHITE]; + e->scalingFunction[WHITE] = &ScaleKBPsK[WHITE]; if (is_KBPsKs(pos)) - mi->scalingFunction[BLACK] = &ScaleKBPsK[BLACK]; + e->scalingFunction[BLACK] = &ScaleKBPsK[BLACK]; if (is_KQKRPs(pos)) - mi->scalingFunction[WHITE] = &ScaleKQKRPs[WHITE]; + e->scalingFunction[WHITE] = &ScaleKQKRPs[WHITE]; else if (is_KQKRPs(pos)) - mi->scalingFunction[BLACK] = &ScaleKQKRPs[BLACK]; + e->scalingFunction[BLACK] = &ScaleKQKRPs[BLACK]; Value npm_w = pos.non_pawn_material(WHITE); Value npm_b = pos.non_pawn_material(BLACK); @@ -184,32 +174,32 @@ MaterialInfo* MaterialInfoTable::material_info(const Position& pos) const { if (pos.piece_count(BLACK, PAWN) == 0) { assert(pos.piece_count(WHITE, PAWN) >= 2); - mi->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; } else if (pos.piece_count(WHITE, PAWN) == 0) { assert(pos.piece_count(BLACK, PAWN) >= 2); - mi->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; + e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; } else if (pos.piece_count(WHITE, PAWN) == 1 && pos.piece_count(BLACK, PAWN) == 1) { // This is a special case because we set scaling functions // for both colors instead of only one. - mi->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; - mi->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; + e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; } } // No pawns makes it difficult to win, even with a material advantage if (pos.piece_count(WHITE, PAWN) == 0 && npm_w - npm_b <= BishopValueMidgame) { - mi->factor[WHITE] = (uint8_t) + e->factor[WHITE] = (uint8_t) (npm_w == npm_b || npm_w < RookValueMidgame ? 0 : NoPawnsSF[std::min(pos.piece_count(WHITE, BISHOP), 2)]); } if (pos.piece_count(BLACK, PAWN) == 0 && npm_b - npm_w <= BishopValueMidgame) { - mi->factor[BLACK] = (uint8_t) + e->factor[BLACK] = (uint8_t) (npm_w == npm_b || npm_b < RookValueMidgame ? 0 : NoPawnsSF[std::min(pos.piece_count(BLACK, BISHOP), 2)]); } @@ -219,7 +209,7 @@ MaterialInfo* MaterialInfoTable::material_info(const Position& pos) const { int minorPieceCount = pos.piece_count(WHITE, KNIGHT) + pos.piece_count(WHITE, BISHOP) + pos.piece_count(BLACK, KNIGHT) + pos.piece_count(BLACK, BISHOP); - mi->spaceWeight = minorPieceCount * minorPieceCount; + e->spaceWeight = minorPieceCount * minorPieceCount; } // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder @@ -231,16 +221,16 @@ MaterialInfo* MaterialInfoTable::material_info(const Position& pos) const { { pos.piece_count(BLACK, BISHOP) > 1, pos.piece_count(BLACK, PAWN), pos.piece_count(BLACK, KNIGHT), pos.piece_count(BLACK, BISHOP) , pos.piece_count(BLACK, ROOK), pos.piece_count(BLACK, QUEEN) } }; - mi->value = (int16_t)((imbalance(pieceCount) - imbalance(pieceCount)) / 16); - return mi; + e->value = (int16_t)((imbalance(pieceCount) - imbalance(pieceCount)) / 16); + return e; } -/// MaterialInfoTable::imbalance() calculates imbalance comparing piece count of each +/// MaterialTable::imbalance() calculates imbalance comparing piece count of each /// piece type for both colors. template -int MaterialInfoTable::imbalance(const int pieceCount[][8]) { +int MaterialTable::imbalance(const int pieceCount[][8]) { const Color Them = (Us == WHITE ? BLACK : WHITE); @@ -272,11 +262,11 @@ int MaterialInfoTable::imbalance(const int pieceCount[][8]) { } -/// MaterialInfoTable::game_phase() calculates the phase given the current +/// MaterialTable::game_phase() calculates the phase given the current /// position. Because the phase is strictly a function of the material, it -/// is stored in MaterialInfo. +/// is stored in MaterialEntry. -Phase MaterialInfoTable::game_phase(const Position& pos) { +Phase MaterialTable::game_phase(const Position& pos) { Value npm = pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK); diff --git a/DroidFish/jni/stockfish/material.h b/DroidFish/jni/stockfish/material.h index 45bfe8b..75074f9 100644 --- a/DroidFish/jni/stockfish/material.h +++ b/DroidFish/jni/stockfish/material.h @@ -21,8 +21,8 @@ #define MATERIAL_H_INCLUDED #include "endgame.h" +#include "misc.h" #include "position.h" -#include "tt.h" #include "types.h" const int MaterialTableSize = 8192; @@ -34,7 +34,7 @@ enum Phase { }; -/// MaterialInfo is a class which contains various information about a +/// MaterialEntry is a class which contains various information about a /// material configuration. It contains a material balance evaluation, /// a function pointer to a special endgame evaluation function (which in /// most cases is NULL, meaning that the standard evaluation function will @@ -44,9 +44,9 @@ enum Phase { /// For instance, in KRB vs KR endgames, the score is scaled down by a factor /// of 4, which will result in scores of absolute value less than one pawn. -class MaterialInfo { +class MaterialEntry { - friend class MaterialInfoTable; + friend struct MaterialTable; public: Score material_value() const; @@ -67,32 +67,28 @@ private: }; -/// The MaterialInfoTable class represents a pawn hash table. The most important -/// method is material_info(), which returns a pointer to a MaterialInfo object. +/// The MaterialTable class represents a material hash table. The most important +/// method is probe(), which returns a pointer to a MaterialEntry object. -class MaterialInfoTable : public SimpleHash { -public: - ~MaterialInfoTable(); - void init(); - MaterialInfo* material_info(const Position& pos) const; +struct MaterialTable { + + MaterialEntry* probe(const Position& pos); static Phase game_phase(const Position& pos); + template static int imbalance(const int pieceCount[][8]); -private: - template - static int imbalance(const int pieceCount[][8]); - - Endgames* funcs; + HashTable entries; + Endgames endgames; }; -/// MaterialInfo::scale_factor takes a position and a color as input, and +/// MaterialEntry::scale_factor takes a position and a color as input, and /// returns a scale factor for the given color. We have to provide the /// position in addition to the color, because the scale factor need not /// to be a constant: It can also be a function which should be applied to /// the position. For instance, in KBP vs K endgames, a scaling function /// which checks for draws with rook pawns and wrong-colored bishops. -inline ScaleFactor MaterialInfo::scale_factor(const Position& pos, Color c) const { +inline ScaleFactor MaterialEntry::scale_factor(const Position& pos, Color c) const { if (!scalingFunction[c]) return ScaleFactor(factor[c]); @@ -101,23 +97,23 @@ inline ScaleFactor MaterialInfo::scale_factor(const Position& pos, Color c) cons return sf == SCALE_FACTOR_NONE ? ScaleFactor(factor[c]) : sf; } -inline Value MaterialInfo::evaluate(const Position& pos) const { +inline Value MaterialEntry::evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } -inline Score MaterialInfo::material_value() const { +inline Score MaterialEntry::material_value() const { return make_score(value, value); } -inline int MaterialInfo::space_weight() const { +inline int MaterialEntry::space_weight() const { return spaceWeight; } -inline Phase MaterialInfo::game_phase() const { +inline Phase MaterialEntry::game_phase() const { return gamePhase; } -inline bool MaterialInfo::specialized_eval_exists() const { +inline bool MaterialEntry::specialized_eval_exists() const { return evaluationFunction != NULL; } diff --git a/DroidFish/jni/stockfish/misc.cpp b/DroidFish/jni/stockfish/misc.cpp index 8b8976e..0f0fca4 100644 --- a/DroidFish/jni/stockfish/misc.cpp +++ b/DroidFish/jni/stockfish/misc.cpp @@ -17,45 +17,23 @@ along with this program. If not, see . */ -#if defined(_MSC_VER) - -#define _CRT_SECURE_NO_DEPRECATE -#define NOMINMAX // disable macros min() and max() -#include -#include - -#else - -# include -# include -# include -# if defined(__hpux) -# include -# endif - -#endif - -#if !defined(NO_PREFETCH) -# include -#endif - -#include -#include -#include #include #include #include -#include "bitcount.h" #include "misc.h" #include "thread.h" +#if defined(__hpux) +# include +#endif + using namespace std; /// Version number. If Version is left empty, then Tag plus current /// date (in the format YYMMDD) is used as a version number. -static const string Version = "2.2.2"; +static const string Version = ""; static const string Tag = ""; @@ -73,19 +51,17 @@ const string engine_info(bool to_uci) { string month, day, year; stringstream s, date(__DATE__); // From compiler, format is "Sep 21 2008" + s << "Stockfish " << Version; + if (Version.empty()) { date >> month >> day >> year; - s << "Stockfish " << Tag - << setfill('0') << " " << year.substr(2) - << setw(2) << (1 + months.find(month) / 4) - << setw(2) << day << cpu64 << popcnt; + s << Tag << setfill('0') << " " << year.substr(2) + << setw(2) << (1 + months.find(month) / 4) << setw(2) << day; } - else - s << "Stockfish " << Version << cpu64 << popcnt; - s << (to_uci ? "\nid author ": " by ") + s << cpu64 << popcnt << (to_uci ? "\nid author ": " by ") << "Tord Romstad, Marco Costalba and Joona Kiiski"; return s.str(); @@ -112,39 +88,85 @@ void dbg_print() { } -/// system_time() returns the current system time, measured in milliseconds +/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +/// can toggle the logging of std::cout and std:cin at runtime while preserving +/// usual i/o functionality and without changing a single line of code! +/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 -int system_time() { +class Logger { -#if defined(_MSC_VER) - struct _timeb t; - _ftime(&t); - return int(t.time * 1000 + t.millitm); -#else - struct timeval t; - gettimeofday(&t, NULL); - return t.tv_sec * 1000 + t.tv_usec / 1000; -#endif -} + Logger() : in(cin.rdbuf(), file), out(cout.rdbuf(), file) {} + ~Logger() { start(false); } + + struct Tie: public streambuf { // MSVC requires splitted streambuf for cin and cout + + Tie(streambuf* b, ofstream& f) : buf(b), file(f) {} + + int sync() { return file.rdbuf()->pubsync(), buf->pubsync(); } + int overflow(int c) { return log(buf->sputc((char)c), "<< "); } + int underflow() { return buf->sgetc(); } + int uflow() { return log(buf->sbumpc(), ">> "); } + + int log(int c, const char* prefix) { + + static int last = '\n'; + + if (last == '\n') + file.rdbuf()->sputn(prefix, 3); + + return last = file.rdbuf()->sputc((char)c); + } + + streambuf* buf; + ofstream& file; + }; + + ofstream file; + Tie in, out; + +public: + static void start(bool b) { + + static Logger l; + + if (b && !l.file.is_open()) + { + l.file.open("io_log.txt", ifstream::out | ifstream::app); + cin.rdbuf(&l.in); + cout.rdbuf(&l.out); + } + else if (!b && l.file.is_open()) + { + cout.rdbuf(l.out.buf); + cin.rdbuf(l.in.buf); + l.file.close(); + } + } +}; + + +/// Trampoline helper to avoid moving Logger to misc.h +void start_logger(bool b) { Logger::start(b); } /// cpu_count() tries to detect the number of CPU cores int cpu_count() { -#if defined(_MSC_VER) +#if defined(_WIN32) || defined(_WIN64) SYSTEM_INFO s; GetSystemInfo(&s); - return std::min(int(s.dwNumberOfProcessors), MAX_THREADS); + return s.dwNumberOfProcessors; #else # if defined(_SC_NPROCESSORS_ONLN) - return std::min((int)sysconf(_SC_NPROCESSORS_ONLN), MAX_THREADS); + return sysconf(_SC_NPROCESSORS_ONLN); # elif defined(__hpux) struct pst_dynamic psd; if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) == -1) return 1; - return std::min((int)psd.psd_proc_cnt, MAX_THREADS); + return psd.psd_proc_cnt; # else return 1; # endif @@ -156,24 +178,16 @@ int cpu_count() { /// timed_wait() waits for msec milliseconds. It is mainly an helper to wrap /// conversion from milliseconds to struct timespec, as used by pthreads. -void timed_wait(WaitCondition* sleepCond, Lock* sleepLock, int msec) { +void timed_wait(WaitCondition& sleepCond, Lock& sleepLock, int msec) { -#if defined(_MSC_VER) +#if defined(_WIN32) || defined(_WIN64) int tm = msec; #else - struct timeval t; - struct timespec abstime, *tm = &abstime; + timespec ts, *tm = &ts; + uint64_t ms = Time::current_time().msec() + msec; - gettimeofday(&t, NULL); - - abstime.tv_sec = t.tv_sec + (msec / 1000); - abstime.tv_nsec = (t.tv_usec + (msec % 1000) * 1000) * 1000; - - if (abstime.tv_nsec > 1000000000LL) - { - abstime.tv_sec += 1; - abstime.tv_nsec -= 1000000000LL; - } + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000LL; #endif cond_timedwait(sleepCond, sleepLock, tm); @@ -189,6 +203,8 @@ void prefetch(char*) {} #else +# include + void prefetch(char* addr) { # if defined(__INTEL_COMPILER) || defined(__ICL) diff --git a/DroidFish/jni/stockfish/misc.h b/DroidFish/jni/stockfish/misc.h index d6fb062..29d35d5 100644 --- a/DroidFish/jni/stockfish/misc.h +++ b/DroidFish/jni/stockfish/misc.h @@ -22,15 +22,15 @@ #include #include +#include -#include "lock.h" #include "types.h" extern const std::string engine_info(bool to_uci = false); -extern int system_time(); extern int cpu_count(); -extern void timed_wait(WaitCondition*, Lock*, int); +extern void timed_wait(WaitCondition&, Lock&, int); extern void prefetch(char* addr); +extern void start_logger(bool b); extern void dbg_hit_on(bool b); extern void dbg_hit_on_c(bool c, bool b); @@ -38,13 +38,36 @@ extern void dbg_mean_of(int v); extern void dbg_print(); class Position; -extern Move move_from_uci(const Position& pos, const std::string& str); +extern Move move_from_uci(const Position& pos, std::string& str); extern const std::string move_to_uci(Move m, bool chess960); extern const std::string move_to_san(Position& pos, Move m); + struct Log : public std::ofstream { Log(const std::string& f = "log.txt") : std::ofstream(f.c_str(), std::ios::out | std::ios::app) {} ~Log() { if (is_open()) close(); } }; + +struct Time { + void restart() { system_time(&t); } + uint64_t msec() const { return time_to_msec(t); } + int elapsed() const { return int(current_time().msec() - time_to_msec(t)); } + + static Time current_time() { Time t; t.restart(); return t; } + +private: + sys_time_t t; +}; + + +template +struct HashTable { + HashTable() : e(Size, Entry()) { memset(&e[0], 0, sizeof(Entry) * Size); } + Entry* operator[](Key k) { return &e[(uint32_t)k & (Size - 1)]; } + +private: + std::vector e; +}; + #endif // !defined(MISC_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/move.cpp b/DroidFish/jni/stockfish/move.cpp index 1bf9b6a..74271b3 100644 --- a/DroidFish/jni/stockfish/move.cpp +++ b/DroidFish/jni/stockfish/move.cpp @@ -18,7 +18,6 @@ */ #include -#include #include #include "movegen.h" @@ -47,7 +46,7 @@ const string move_to_uci(Move m, bool chess960) { to = from + (file_of(to) == FILE_H ? Square(2) : -Square(2)); if (is_promotion(m)) - promotion = char(tolower(piece_type_to_char(promotion_piece_type(m)))); + promotion = char(tolower(piece_type_to_char(promotion_type(m)))); return square_to_string(from) + square_to_string(to) + promotion; } @@ -57,7 +56,10 @@ const string move_to_uci(Move m, bool chess960) { /// simple coordinate notation and returns an equivalent Move if any. /// Moves are guaranteed to be legal. -Move move_from_uci(const Position& pos, const string& str) { +Move move_from_uci(const Position& pos, string& str) { + + if (str.length() == 5) // Junior could send promotion in uppercase + str[4] = char(tolower(str[4])); for (MoveList ml(pos); !ml.end(); ++ml) if (str == move_to_uci(ml.move(), pos.is_chess960())) @@ -85,7 +87,7 @@ const string move_to_san(Position& pos, Move m) { bool ambiguousMove, ambiguousFile, ambiguousRank; Square sq, from = from_sq(m); Square to = to_sq(m); - PieceType pt = type_of(pos.piece_on(from)); + PieceType pt = type_of(pos.piece_moved(m)); string san; if (is_castle(m)) @@ -98,8 +100,8 @@ const string move_to_san(Position& pos, Move m) { // Disambiguation if we have more then one piece with destination 'to' // note that for pawns is not needed because starting file is explicit. - attackers = pos.attackers_to(to) & pos.pieces(pt, pos.side_to_move()); - clear_bit(&attackers, from); + attackers = pos.attackers_to(to) & pos.pieces(pos.side_to_move(), pt); + attackers ^= from; ambiguousMove = ambiguousFile = ambiguousRank = false; while (attackers) @@ -143,7 +145,7 @@ const string move_to_san(Position& pos, Move m) { if (is_promotion(m)) { san += '='; - san += piece_type_to_char(promotion_piece_type(m)); + san += piece_type_to_char(promotion_type(m)); } } diff --git a/DroidFish/jni/stockfish/movegen.cpp b/DroidFish/jni/stockfish/movegen.cpp index 0ec1fce..98e411b 100644 --- a/DroidFish/jni/stockfish/movegen.cpp +++ b/DroidFish/jni/stockfish/movegen.cpp @@ -29,55 +29,35 @@ /// Version used for pawns, where the 'from' square is given as a delta from the 'to' square #define SERIALIZE_PAWNS(b, d) while (b) { Square to = pop_1st_bit(&b); \ - (*mlist++).move = make_move(to + (d), to); } + (*mlist++).move = make_move(to - (d), to); } namespace { - enum CastlingSide { KING_SIDE, QUEEN_SIDE }; - template - MoveStack* generate_castle_moves(const Position& pos, MoveStack* mlist, Color us) { + MoveStack* generate_castle(const Position& pos, MoveStack* mlist, Color us) { - const CastleRight CR[] = { Side ? WHITE_OOO : WHITE_OO, - Side ? BLACK_OOO : BLACK_OO }; - - if (!pos.can_castle(CR[us])) + if (pos.castle_impeded(us, Side) || !pos.can_castle(make_castle_right(us, Side))) return mlist; // After castling, the rook and king final positions are the same in Chess960 // as they would be in standard chess. Square kfrom = pos.king_square(us); - Square rfrom = pos.castle_rook_square(CR[us]); + Square rfrom = pos.castle_rook_square(us, Side); Square kto = relative_square(us, Side == KING_SIDE ? SQ_G1 : SQ_C1); - Square rto = relative_square(us, Side == KING_SIDE ? SQ_F1 : SQ_D1); Bitboard enemies = pos.pieces(~us); assert(!pos.in_check()); - assert(pos.piece_on(kfrom) == make_piece(us, KING)); - assert(pos.piece_on(rfrom) == make_piece(us, ROOK)); - - // Unimpeded rule: All the squares between the king's initial and final squares - // (including the final square), and all the squares between the rook's initial - // and final squares (including the final square), must be vacant except for - // the king and castling rook. - for (Square s = std::min(rfrom, rto), e = std::max(rfrom, rto); s <= e; s++) - if (s != kfrom && s != rfrom && !pos.square_is_empty(s)) - return mlist; for (Square s = std::min(kfrom, kto), e = std::max(kfrom, kto); s <= e; s++) - if ( (s != kfrom && s != rfrom && !pos.square_is_empty(s)) - ||(pos.attackers_to(s) & enemies)) + if ( s != kfrom // We are not in check + && (pos.attackers_to(s) & enemies)) return mlist; // Because we generate only legal castling moves we need to verify that // when moving the castling rook we do not discover some hidden checker. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - if (pos.is_chess960()) - { - Bitboard occ = pos.occupied_squares(); - clear_bit(&occ, rfrom); - if (pos.attackers_to(kto, occ) & enemies) + if ( pos.is_chess960() + && (pos.attackers_to(kto, pos.pieces() ^ rfrom) & enemies)) return mlist; - } (*mlist++).move = make_castle(kfrom, rfrom); @@ -91,35 +71,20 @@ namespace { template inline Bitboard move_pawns(Bitboard p) { - return Delta == DELTA_N ? p << 8 : Delta == DELTA_S ? p >> 8 : - Delta == DELTA_NE ? p << 9 : Delta == DELTA_SE ? p >> 7 : - Delta == DELTA_NW ? p << 7 : Delta == DELTA_SW ? p >> 9 : p; - } - - - template - inline MoveStack* generate_pawn_captures(MoveStack* mlist, Bitboard pawns, Bitboard target) { - - const Bitboard TFileABB = ( Delta == DELTA_NE - || Delta == DELTA_SE ? FileABB : FileHBB); - - Bitboard b = move_pawns(pawns) & target & ~TFileABB; - SERIALIZE_PAWNS(b, -Delta); - return mlist; + return Delta == DELTA_N ? p << 8 + : Delta == DELTA_S ? p >> 8 + : Delta == DELTA_NE ? (p & ~FileHBB) << 9 + : Delta == DELTA_SE ? (p & ~FileHBB) >> 7 + : Delta == DELTA_NW ? (p & ~FileABB) << 7 + : Delta == DELTA_SW ? (p & ~FileABB) >> 9 : 0; } template inline MoveStack* generate_promotions(MoveStack* mlist, Bitboard pawnsOn7, Bitboard target, Square ksq) { - const Bitboard TFileABB = ( Delta == DELTA_NE - || Delta == DELTA_SE ? FileABB : FileHBB); - Bitboard b = move_pawns(pawnsOn7) & target; - if (Delta != DELTA_N && Delta != DELTA_S) - b &= ~TFileABB; - while (b) { Square to = pop_1st_bit(&b); @@ -127,30 +92,32 @@ namespace { if (Type == MV_CAPTURE || Type == MV_EVASION || Type == MV_NON_EVASION) (*mlist++).move = make_promotion(to - Delta, to, QUEEN); - if (Type == MV_NON_CAPTURE || Type == MV_EVASION || Type == MV_NON_EVASION) + if (Type == MV_QUIET || Type == MV_EVASION || Type == MV_NON_EVASION) { (*mlist++).move = make_promotion(to - Delta, to, ROOK); (*mlist++).move = make_promotion(to - Delta, to, BISHOP); (*mlist++).move = make_promotion(to - Delta, to, KNIGHT); } - // Knight-promotion is the only one that can give a check (direct or - // discovered) not already included in the queen-promotion. - if (Type == MV_NON_CAPTURE_CHECK && bit_is_set(StepAttacksBB[W_KNIGHT][to], ksq)) + // Knight-promotion is the only one that can give a direct check not + // already included in the queen-promotion. + if (Type == MV_QUIET_CHECK && (StepAttacksBB[W_KNIGHT][to] & ksq)) (*mlist++).move = make_promotion(to - Delta, to, KNIGHT); else (void)ksq; // Silence a warning under MSVC } + return mlist; } template - MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist, Bitboard target, Square ksq) { + MoveStack* generate_pawn_moves(const Position& pos, MoveStack* mlist, Bitboard target, Square ksq = SQ_NONE) { - // Calculate our parametrized parameters at compile time, named according to + // Compute our parametrized parameters at compile time, named according to // the point of view of white side. const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); const Square UP = (Us == WHITE ? DELTA_N : DELTA_S); @@ -159,8 +126,8 @@ namespace { Bitboard b1, b2, dc1, dc2, emptySquares; - Bitboard pawnsOn7 = pos.pieces(PAWN, Us) & TRank7BB; - Bitboard pawnsNotOn7 = pos.pieces(PAWN, Us) & ~TRank7BB; + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; Bitboard enemies = (Type == MV_EVASION ? pos.pieces(Them) & target: Type == MV_CAPTURE ? target : pos.pieces(Them)); @@ -168,7 +135,7 @@ namespace { // Single and double pawn pushes, no promotions if (Type != MV_CAPTURE) { - emptySquares = (Type == MV_NON_CAPTURE ? target : pos.empty_squares()); + emptySquares = (Type == MV_QUIET ? target : ~pos.pieces()); b1 = move_pawns(pawnsNotOn7) & emptySquares; b2 = move_pawns(b1 & TRank3BB) & emptySquares; @@ -179,9 +146,8 @@ namespace { b2 &= target; } - if (Type == MV_NON_CAPTURE_CHECK) + if (Type == MV_QUIET_CHECK) { - // Consider only direct checks b1 &= pos.attacks_from(ksq, Them); b2 &= pos.attacks_from(ksq, Them); @@ -199,15 +165,15 @@ namespace { } } - SERIALIZE_PAWNS(b1, -UP); - SERIALIZE_PAWNS(b2, -UP -UP); + SERIALIZE_PAWNS(b1, UP); + SERIALIZE_PAWNS(b2, UP + UP); } // Promotions and underpromotions - if (pawnsOn7) + if (pawnsOn7 && (Type != MV_EVASION || (target & TRank8BB))) { if (Type == MV_CAPTURE) - emptySquares = pos.empty_squares(); + emptySquares = ~pos.pieces(); if (Type == MV_EVASION) emptySquares &= target; @@ -220,17 +186,20 @@ namespace { // Standard and en-passant captures if (Type == MV_CAPTURE || Type == MV_EVASION || Type == MV_NON_EVASION) { - mlist = generate_pawn_captures(mlist, pawnsNotOn7, enemies); - mlist = generate_pawn_captures(mlist, pawnsNotOn7, enemies); + b1 = move_pawns(pawnsNotOn7) & enemies; + b2 = move_pawns(pawnsNotOn7) & enemies; + + SERIALIZE_PAWNS(b1, RIGHT); + SERIALIZE_PAWNS(b2, LEFT); if (pos.ep_square() != SQ_NONE) { - assert(rank_of(pos.ep_square()) == (Us == WHITE ? RANK_6 : RANK_3)); + assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); // 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 // is a discovery check and we are forced to do otherwise. - if (Type == MV_EVASION && !bit_is_set(target, pos.ep_square() - UP)) + if (Type == MV_EVASION && !(target & (pos.ep_square() - UP))) return mlist; b1 = pawnsNotOn7 & pos.attacks_from(pos.ep_square(), Them); @@ -251,73 +220,56 @@ namespace { Color us, const CheckInfo& ci) { assert(Pt != KING && Pt != PAWN); - Bitboard checkSqs, b; + Bitboard b, target; Square from; const Square* pl = pos.piece_list(us, Pt); - if ((from = *pl++) == SQ_NONE) - return mlist; - - checkSqs = ci.checkSq[Pt] & pos.empty_squares(); - - do + if (*pl != SQ_NONE) { - if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) - && !(PseudoAttacks[Pt][from] & checkSqs)) - continue; + target = ci.checkSq[Pt] & ~pos.pieces(); // Non capture checks only - if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from)) - continue; + do { + from = *pl; - b = pos.attacks_from(from) & checkSqs; - SERIALIZE(b); + if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) + && !(PseudoAttacks[Pt][from] & target)) + continue; - } while ((from = *pl++) != SQ_NONE); + if (ci.dcCandidates && (ci.dcCandidates & from)) + continue; + + b = pos.attacks_from(from) & target; + SERIALIZE(b); + } while (*++pl != SQ_NONE); + } return mlist; } - template<> - FORCE_INLINE MoveStack* generate_direct_checks(const Position& p, MoveStack* m, - Color us, const CheckInfo& ci) { - - return us == WHITE ? generate_pawn_moves(p, m, ci.dcCandidates, ci.ksq) - : generate_pawn_moves(p, m, ci.dcCandidates, ci.ksq); - } - - - template - FORCE_INLINE MoveStack* generate_piece_moves(const Position& p, MoveStack* m, Color us, Bitboard t) { - - assert(Pt == PAWN); - return us == WHITE ? generate_pawn_moves(p, m, t, SQ_NONE) - : generate_pawn_moves(p, m, t, SQ_NONE); - } - - template - FORCE_INLINE MoveStack* generate_piece_moves(const Position& pos, MoveStack* mlist, Color us, Bitboard target) { + FORCE_INLINE MoveStack* generate_moves(const Position& pos, MoveStack* mlist, + Color us, Bitboard target) { + assert(Pt != KING && Pt != PAWN); Bitboard b; Square from; const Square* pl = pos.piece_list(us, Pt); if (*pl != SQ_NONE) - { do { from = *pl; b = pos.attacks_from(from) & target; SERIALIZE(b); } while (*++pl != SQ_NONE); - } + return mlist; } template<> - FORCE_INLINE MoveStack* generate_piece_moves(const Position& pos, MoveStack* mlist, Color us, Bitboard target) { - + FORCE_INLINE MoveStack* generate_moves(const Position& pos, MoveStack* mlist, + Color us, Bitboard target) { Square from = pos.king_square(us); Bitboard b = pos.attacks_from(from) & target; SERIALIZE(b); @@ -330,7 +282,7 @@ namespace { /// generate generates all pseudo-legal captures and queen /// promotions. Returns a pointer to the end of the move list. /// -/// generate generates all pseudo-legal non-captures and +/// generate generates all pseudo-legal non-captures and /// underpromotions. Returns a pointer to the end of the move list. /// /// generate generates all pseudo-legal captures and @@ -339,7 +291,7 @@ namespace { template MoveStack* generate(const Position& pos, MoveStack* mlist) { - assert(Type == MV_CAPTURE || Type == MV_NON_CAPTURE || Type == MV_NON_EVASION); + assert(Type == MV_CAPTURE || Type == MV_QUIET || Type == MV_NON_EVASION); assert(!pos.in_check()); Color us = pos.side_to_move(); @@ -348,23 +300,25 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) { if (Type == MV_CAPTURE) target = pos.pieces(~us); - else if (Type == MV_NON_CAPTURE) - target = pos.empty_squares(); + else if (Type == MV_QUIET) + target = ~pos.pieces(); else if (Type == MV_NON_EVASION) - target = pos.pieces(~us) | pos.empty_squares(); + target = ~pos.pieces(us); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); + mlist = (us == WHITE ? generate_pawn_moves(pos, mlist, target) + : generate_pawn_moves(pos, mlist, target)); + + mlist = generate_moves(pos, mlist, us, target); + mlist = generate_moves(pos, mlist, us, target); + mlist = generate_moves(pos, mlist, us, target); + mlist = generate_moves(pos, mlist, us, target); + mlist = generate_moves(pos, mlist, us, target); if (Type != MV_CAPTURE && pos.can_castle(us)) { - mlist = generate_castle_moves(pos, mlist, us); - mlist = generate_castle_moves(pos, mlist, us); + mlist = generate_castle(pos, mlist, us); + mlist = generate_castle(pos, mlist, us); } return mlist; @@ -372,14 +326,14 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) { // Explicit template instantiations template MoveStack* generate(const Position& pos, MoveStack* mlist); -template MoveStack* generate(const Position& pos, MoveStack* mlist); +template MoveStack* generate(const Position& pos, MoveStack* mlist); template MoveStack* generate(const Position& pos, MoveStack* mlist); -/// generate generates all pseudo-legal non-captures and knight +/// generate generates all pseudo-legal non-captures and knight /// underpromotions that give check. Returns a pointer to the end of the move list. template<> -MoveStack* generate(const Position& pos, MoveStack* mlist) { +MoveStack* generate(const Position& pos, MoveStack* mlist) { assert(!pos.in_check()); @@ -395,7 +349,7 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) if (pt == PAWN) continue; // Will be generated togheter with direct checks - Bitboard b = pos.attacks_from(Piece(pt), from) & pos.empty_squares(); + Bitboard b = pos.attacks_from(Piece(pt), from) & ~pos.pieces(); if (pt == KING) b &= ~PseudoAttacks[QUEEN][ci.ksq]; @@ -403,7 +357,9 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) SERIALIZE(b); } - mlist = generate_direct_checks(pos, mlist, us, ci); + mlist = (us == WHITE ? generate_pawn_moves(pos, mlist, ci.dcCandidates, ci.ksq) + : generate_pawn_moves(pos, mlist, ci.dcCandidates, ci.ksq)); + mlist = generate_direct_checks(pos, mlist, us, ci); mlist = generate_direct_checks(pos, mlist, us, ci); mlist = generate_direct_checks(pos, mlist, us, ci); @@ -411,8 +367,8 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) if (pos.can_castle(us)) { - mlist = generate_castle_moves(pos, mlist, us); - mlist = generate_castle_moves(pos, mlist, us); + mlist = generate_castle(pos, mlist, us); + mlist = generate_castle(pos, mlist, us); } return mlist; @@ -454,7 +410,7 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) { // If queen and king are far or not on a diagonal line we can safely // remove all the squares attacked in the other direction becuase are // not reachable by the king anyway. - if (squares_between(ksq, checksq) || !bit_is_set(PseudoAttacks[BISHOP][checksq], ksq)) + if (between_bb(ksq, checksq) || !(PseudoAttacks[BISHOP][checksq] & ksq)) sliderAttacks |= PseudoAttacks[QUEEN][checksq]; // Otherwise we need to use real rook attacks to check if king is safe @@ -478,13 +434,15 @@ MoveStack* generate(const Position& pos, MoveStack* mlist) { return mlist; // Blocking evasions or captures of the checking piece - target = squares_between(checksq, ksq) | checkers; + target = between_bb(checksq, ksq) | checkers; - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - mlist = generate_piece_moves(pos, mlist, us, target); - return generate_piece_moves(pos, mlist, us, target); + mlist = (us == WHITE ? generate_pawn_moves(pos, mlist, target) + : generate_pawn_moves(pos, mlist, target)); + + mlist = generate_moves(pos, mlist, us, target); + mlist = generate_moves(pos, mlist, us, target); + mlist = generate_moves(pos, mlist, us, target); + return generate_moves(pos, mlist, us, target); } diff --git a/DroidFish/jni/stockfish/movegen.h b/DroidFish/jni/stockfish/movegen.h index c5c6e3e..68ae705 100644 --- a/DroidFish/jni/stockfish/movegen.h +++ b/DroidFish/jni/stockfish/movegen.h @@ -24,8 +24,8 @@ enum MoveType { MV_CAPTURE, - MV_NON_CAPTURE, - MV_NON_CAPTURE_CHECK, + MV_QUIET, + MV_QUIET_CHECK, MV_EVASION, MV_NON_EVASION, MV_LEGAL diff --git a/DroidFish/jni/stockfish/movepick.cpp b/DroidFish/jni/stockfish/movepick.cpp index 8a7e9ae..a86a170 100644 --- a/DroidFish/jni/stockfish/movepick.cpp +++ b/DroidFish/jni/stockfish/movepick.cpp @@ -23,39 +23,24 @@ #include "movegen.h" #include "movepick.h" -#include "search.h" -#include "types.h" namespace { - enum MovegenPhase { - PH_TT_MOVE, // Transposition table move - PH_GOOD_CAPTURES, // Queen promotions and captures with SEE values >= captureThreshold (captureThreshold <= 0) - PH_GOOD_PROBCUT, // Queen promotions and captures with SEE values > captureThreshold (captureThreshold >= 0) - PH_KILLERS, // Killer moves from the current ply - PH_NONCAPTURES_1, // Non-captures and underpromotions with positive score - PH_NONCAPTURES_2, // Non-captures and underpromotions with non-positive score - PH_BAD_CAPTURES, // Queen promotions and captures with SEE values < captureThreshold (captureThreshold <= 0) - PH_EVASIONS, // Check evasions - PH_QCAPTURES, // Captures in quiescence search - PH_QRECAPTURES, // Recaptures in quiescence search - PH_QCHECKS, // Non-capture checks in quiescence search - PH_STOP + enum Sequencer { + MAIN_SEARCH, CAPTURES_S1, KILLERS_S1, QUIETS_1_S1, QUIETS_2_S1, BAD_CAPTURES_S1, + EVASION, EVASIONS_S2, + QSEARCH_0, CAPTURES_S3, QUIET_CHECKS_S3, + QSEARCH_1, CAPTURES_S4, + PROBCUT, CAPTURES_S5, + RECAPTURE, CAPTURES_S6, + STOP }; - CACHE_LINE_ALIGNMENT - const uint8_t MainSearchTable[] = { PH_TT_MOVE, PH_GOOD_CAPTURES, PH_KILLERS, PH_NONCAPTURES_1, PH_NONCAPTURES_2, PH_BAD_CAPTURES, PH_STOP }; - const uint8_t EvasionTable[] = { PH_TT_MOVE, PH_EVASIONS, PH_STOP }; - const uint8_t QsearchWithChecksTable[] = { PH_TT_MOVE, PH_QCAPTURES, PH_QCHECKS, PH_STOP }; - const uint8_t QsearchWithoutChecksTable[] = { PH_TT_MOVE, PH_QCAPTURES, PH_STOP }; - const uint8_t QsearchRecapturesTable[] = { PH_TT_MOVE, PH_QRECAPTURES, PH_STOP }; - const uint8_t ProbCutTable[] = { PH_TT_MOVE, PH_GOOD_PROBCUT, PH_STOP }; - // Unary predicate used by std::partition to split positive scores from remaining // ones so to sort separately the two sets, and with the second sort delayed. inline bool has_positive_score(const MoveStack& move) { return move.score > 0; } - // Picks and pushes to the front the best move in range [firstMove, lastMove), + // Picks and moves to the front the best move in the range [firstMove, lastMove), // it is faster than sorting all the moves in advance when moves are few, as // normally are the possible captures. inline MoveStack* pick_best(MoveStack* firstMove, MoveStack* lastMove) @@ -65,7 +50,8 @@ namespace { } } -/// Constructors for the MovePicker class. As arguments we pass information + +/// Constructors of the MovePicker class. As arguments we pass information /// to help it to return the presumably good moves first, to decide which /// moves to return (in the quiescence search, for instance, we only want to /// search captures, promotions and some checks) and about how important good @@ -73,18 +59,20 @@ namespace { MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h, Search::Stack* ss, Value beta) : pos(p), H(h), depth(d) { - captureThreshold = 0; - badCaptures = moves + MAX_MOVES; assert(d > DEPTH_ZERO); + captureThreshold = 0; + curMove = lastMove = moves; + lastBadCapture = moves + MAX_MOVES - 1; + if (p.in_check()) - { - killers[0].move = killers[1].move = MOVE_NONE; - phasePtr = EvasionTable; - } + phase = EVASION; + else { + phase = MAIN_SEARCH; + killers[0].move = ss->killers[0]; killers[1].move = ss->killers[1]; @@ -95,137 +83,59 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h, // Consider negative captures as good if still enough to reach beta else if (ss && ss->eval > beta) captureThreshold = beta - ss->eval; - - phasePtr = MainSearchTable; } ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE); - phasePtr += int(ttMove == MOVE_NONE) - 1; - go_next_phase(); + lastMove += (ttMove != MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h, Square recaptureSq) - : pos(p), H(h) { +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const History& h, + Square sq) : pos(p), H(h), curMove(moves), lastMove(moves) { assert(d <= DEPTH_ZERO); if (p.in_check()) - phasePtr = EvasionTable; - else if (d >= DEPTH_QS_CHECKS) - phasePtr = QsearchWithChecksTable; - else if (d >= DEPTH_QS_RECAPTURES) - { - phasePtr = QsearchWithoutChecksTable; + phase = EVASION; - // Skip TT move if is not a capture or a promotion, this avoids - // qsearch tree explosion due to a possible perpetual check or - // similar rare cases when TT table is full. - if (ttm != MOVE_NONE && !pos.is_capture_or_promotion(ttm)) + else if (d > DEPTH_QS_NO_CHECKS) + phase = QSEARCH_0; + + else if (d > DEPTH_QS_RECAPTURES) + { + phase = QSEARCH_1; + + // Skip TT move if is not a capture or a promotion, this avoids qsearch + // tree explosion due to a possible perpetual check or similar rare cases + // when TT table is full. + if (ttm && !pos.is_capture_or_promotion(ttm)) ttm = MOVE_NONE; } else { - phasePtr = QsearchRecapturesTable; - recaptureSquare = recaptureSq; + phase = RECAPTURE; + recaptureSquare = sq; ttm = MOVE_NONE; } ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE); - phasePtr += int(ttMove == MOVE_NONE) - 1; - go_next_phase(); + lastMove += (ttMove != MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, const History& h, PieceType parentCapture) - : pos(p), H(h) { +MovePicker::MovePicker(const Position& p, Move ttm, const History& h, PieceType pt) + : pos(p), H(h), curMove(moves), lastMove(moves) { - assert (!pos.in_check()); + assert(!pos.in_check()); - // In ProbCut we consider only captures better than parent's move - captureThreshold = PieceValueMidgame[Piece(parentCapture)]; - phasePtr = ProbCutTable; - - if ( ttm != MOVE_NONE - && (!pos.is_capture(ttm) || pos.see(ttm) <= captureThreshold)) - ttm = MOVE_NONE; + phase = PROBCUT; + // In ProbCut we generate only captures better than parent's captured piece + captureThreshold = PieceValueMidgame[pt]; ttMove = (ttm && pos.is_pseudo_legal(ttm) ? ttm : MOVE_NONE); - phasePtr += int(ttMove == MOVE_NONE) - 1; - go_next_phase(); -} + if (ttMove && (!pos.is_capture(ttMove) || pos.see(ttMove) <= captureThreshold)) + ttMove = MOVE_NONE; -/// MovePicker::go_next_phase() generates, scores and sorts the next bunch -/// of moves when there are no more moves to try for the current phase. - -void MovePicker::go_next_phase() { - - curMove = moves; - phase = *(++phasePtr); - switch (phase) { - - case PH_TT_MOVE: - lastMove = curMove + 1; - return; - - case PH_GOOD_CAPTURES: - case PH_GOOD_PROBCUT: - lastMove = generate(pos, moves); - score_captures(); - return; - - case PH_KILLERS: - curMove = killers; - lastMove = curMove + 2; - return; - - case PH_NONCAPTURES_1: - lastNonCapture = lastMove = generate(pos, moves); - score_noncaptures(); - lastMove = std::partition(curMove, lastMove, has_positive_score); - sort(curMove, lastMove); - return; - - case PH_NONCAPTURES_2: - curMove = lastMove; - lastMove = lastNonCapture; - if (depth >= 3 * ONE_PLY) - sort(curMove, lastMove); - return; - - case PH_BAD_CAPTURES: - // Bad captures SEE value is already calculated so just pick - // them in order to get SEE move ordering. - curMove = badCaptures; - lastMove = moves + MAX_MOVES; - return; - - case PH_EVASIONS: - assert(pos.in_check()); - lastMove = generate(pos, moves); - score_evasions(); - return; - - case PH_QCAPTURES: - lastMove = generate(pos, moves); - score_captures(); - return; - - case PH_QRECAPTURES: - lastMove = generate(pos, moves); - return; - - case PH_QCHECKS: - lastMove = generate(pos, moves); - return; - - case PH_STOP: - lastMove = curMove + 1; // Avoid another go_next_phase() call - return; - - default: - assert(false); - return; - } + lastMove += (ttMove != MOVE_NONE); } @@ -250,7 +160,6 @@ void MovePicker::score_captures() { // some SEE calls in case we get a cutoff (idea from Pablo Vazquez). Move m; - // Use MVV/LVA ordering for (MoveStack* cur = moves; cur != lastMove; cur++) { m = cur->move; @@ -258,32 +167,28 @@ void MovePicker::score_captures() { - type_of(pos.piece_moved(m)); if (is_promotion(m)) - cur->score += PieceValueMidgame[Piece(promotion_piece_type(m))]; + cur->score += PieceValueMidgame[promotion_type(m)]; } } void MovePicker::score_noncaptures() { Move m; - Square from; for (MoveStack* cur = moves; cur != lastMove; cur++) { m = cur->move; - from = from_sq(m); - cur->score = H.value(pos.piece_on(from), to_sq(m)); + cur->score = H.value(pos.piece_moved(m), to_sq(m)); } } void MovePicker::score_evasions() { - // Try good captures ordered by MVV/LVA, then non-captures if - // destination square is not under attack, ordered by history - // value, and at the end bad-captures and non-captures with a - // negative SEE. This last group is ordered by the SEE score. + // Try good captures ordered by MVV/LVA, then non-captures if destination square + // is not under attack, ordered by history value, then bad-captures and quiet + // moves with a negative SEE. This last group is ordered by the SEE score. Move m; int seeScore; - // Skip if we don't have at least two moves to order if (lastMove < moves + 2) return; @@ -300,6 +205,67 @@ void MovePicker::score_evasions() { } } + +/// MovePicker::generate_next() generates, scores and sorts the next bunch of moves, +/// when there are no more moves to try for the current phase. + +void MovePicker::generate_next() { + + curMove = moves; + + switch (++phase) { + + case CAPTURES_S1: case CAPTURES_S3: case CAPTURES_S4: case CAPTURES_S5: case CAPTURES_S6: + lastMove = generate(pos, moves); + score_captures(); + return; + + case KILLERS_S1: + curMove = killers; + lastMove = curMove + 2; + return; + + case QUIETS_1_S1: + lastQuiet = lastMove = generate(pos, moves); + score_noncaptures(); + lastMove = std::partition(curMove, lastMove, has_positive_score); + sort(curMove, lastMove); + return; + + case QUIETS_2_S1: + curMove = lastMove; + lastMove = lastQuiet; + if (depth >= 3 * ONE_PLY) + sort(curMove, lastMove); + return; + + case BAD_CAPTURES_S1: + // Just pick them in reverse order to get MVV/LVA ordering + curMove = moves + MAX_MOVES - 1; + lastMove = lastBadCapture; + return; + + case EVASIONS_S2: + lastMove = generate(pos, moves); + score_evasions(); + return; + + case QUIET_CHECKS_S3: + lastMove = generate(pos, moves); + return; + + case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT: case RECAPTURE: + phase = STOP; + case STOP: + lastMove = curMove + 1; // Avoid another next_phase() call + return; + + default: + assert(false); + } +} + + /// MovePicker::next_move() is the most important method of the MovePicker class. /// It returns a new pseudo legal move every time it is called, until there /// are no more moves left. It picks the move with the biggest score from a list @@ -314,50 +280,38 @@ Move MovePicker::next_move() { while (true) { while (curMove == lastMove) - go_next_phase(); + generate_next(); switch (phase) { - case PH_TT_MOVE: + case MAIN_SEARCH: case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT: curMove++; return ttMove; - break; - case PH_GOOD_CAPTURES: + case CAPTURES_S1: move = pick_best(curMove++, lastMove)->move; if (move != ttMove) { - assert(captureThreshold <= 0); // Otherwise we must use see instead of see_sign + assert(captureThreshold <= 0); // Otherwise we cannot use see_sign() - // Check for a non negative SEE now - int seeValue = pos.see_sign(move); - if (seeValue >= captureThreshold) + if (pos.see_sign(move) >= captureThreshold) return move; // Losing capture, move it to the tail of the array - (--badCaptures)->move = move; - badCaptures->score = seeValue; + (lastBadCapture--)->move = move; } break; - case PH_GOOD_PROBCUT: - move = pick_best(curMove++, lastMove)->move; - if ( move != ttMove - && pos.see(move) > captureThreshold) - return move; - break; - - case PH_KILLERS: + case KILLERS_S1: move = (curMove++)->move; - if ( move != MOVE_NONE - && pos.is_pseudo_legal(move) - && move != ttMove + if ( move != MOVE_NONE + && pos.is_pseudo_legal(move) + && move != ttMove && !pos.is_capture(move)) return move; break; - case PH_NONCAPTURES_1: - case PH_NONCAPTURES_2: + case QUIETS_1_S1: case QUIETS_2_S1: move = (curMove++)->move; if ( move != ttMove && move != killers[0].move @@ -365,35 +319,38 @@ Move MovePicker::next_move() { return move; break; - case PH_BAD_CAPTURES: - move = pick_best(curMove++, lastMove)->move; - return move; + case BAD_CAPTURES_S1: + return (curMove--)->move; - case PH_EVASIONS: - case PH_QCAPTURES: + case EVASIONS_S2: case CAPTURES_S3: case CAPTURES_S4: move = pick_best(curMove++, lastMove)->move; if (move != ttMove) return move; break; - case PH_QRECAPTURES: - move = (curMove++)->move; + case CAPTURES_S5: + move = pick_best(curMove++, lastMove)->move; + if (move != ttMove && pos.see(move) > captureThreshold) + return move; + break; + + case CAPTURES_S6: + move = pick_best(curMove++, lastMove)->move; if (to_sq(move) == recaptureSquare) return move; break; - case PH_QCHECKS: + case QUIET_CHECKS_S3: move = (curMove++)->move; if (move != ttMove) return move; break; - case PH_STOP: + case STOP: return MOVE_NONE; default: assert(false); - break; } } } diff --git a/DroidFish/jni/stockfish/movepick.h b/DroidFish/jni/stockfish/movepick.h index 72f5245..03305eb 100644 --- a/DroidFish/jni/stockfish/movepick.h +++ b/DroidFish/jni/stockfish/movepick.h @@ -25,13 +25,13 @@ #include "search.h" #include "types.h" -/// MovePicker is a class which is used to pick one pseudo legal move at a time -/// from the current position. It is initialized with a Position object and a few -/// moves we have reason to believe are good. The most important method is -/// MovePicker::next_move(), which returns a new pseudo legal move each time -/// it is called, until there are no moves left, when MOVE_NONE is returned. -/// In order to improve the efficiency of the alpha beta algorithm, MovePicker -/// attempts to return the moves which are most likely to get a cut-off first. + +/// MovePicker class is used to pick one pseudo legal move at a time from the +/// current position. The most important method is next_move(), which returns a +/// new pseudo legal move each time it is called, until there are no moves left, +/// when MOVE_NONE is returned. In order to improve the efficiency of the alpha +/// beta algorithm, MovePicker attempts to return the moves which are most likely +/// to get a cut-off first. class MovePicker { @@ -39,15 +39,15 @@ class MovePicker { public: MovePicker(const Position&, Move, Depth, const History&, Search::Stack*, Value); - MovePicker(const Position&, Move, Depth, const History&, Square recaptureSq); - MovePicker(const Position&, Move, const History&, PieceType parentCapture); + MovePicker(const Position&, Move, Depth, const History&, Square); + MovePicker(const Position&, Move, const History&, PieceType); Move next_move(); private: void score_captures(); void score_noncaptures(); void score_evasions(); - void go_next_phase(); + void generate_next(); const Position& pos; const History& H; @@ -56,8 +56,7 @@ private: MoveStack killers[2]; Square recaptureSquare; int captureThreshold, phase; - const uint8_t* phasePtr; - MoveStack *curMove, *lastMove, *lastNonCapture, *badCaptures; + MoveStack *curMove, *lastMove, *lastQuiet, *lastBadCapture; MoveStack moves[MAX_MOVES]; }; diff --git a/DroidFish/jni/stockfish/pawns.cpp b/DroidFish/jni/stockfish/pawns.cpp index 35bc45a..049e73b 100644 --- a/DroidFish/jni/stockfish/pawns.cpp +++ b/DroidFish/jni/stockfish/pawns.cpp @@ -26,6 +26,7 @@ namespace { + #define V Value #define S(mg, eg) make_score(mg, eg) // Doubled pawn penalty by opposed flag and file @@ -63,58 +64,65 @@ namespace { const Score PawnStructureWeight = S(233, 201); - #undef S + // Weakness of our pawn shelter in front of the king indexed by [king pawn][rank] + const Value ShelterWeakness[2][8] = + { { V(141), V(0), V(38), V(102), V(128), V(141), V(141) }, + { V( 61), V(0), V(16), V( 44), V( 56), V( 61), V( 61) } }; - inline Score apply_weight(Score v, Score w) { - return make_score((int(mg_value(v)) * mg_value(w)) / 0x100, - (int(eg_value(v)) * eg_value(w)) / 0x100); - } + // Danger of enemy pawns moving toward our king indexed by [pawn blocked][rank] + const Value StormDanger[2][8] = + { { V(26), V(0), V(128), V(51), V(26) }, + { V(13), V(0), V( 64), V(25), V(13) } }; + + // Max bonus for king safety. Corresponds to start position with all the pawns + // in front of the king and no enemy pawn on the horizont. + const Value MaxSafetyBonus = V(263); + + #undef S + #undef V } -/// PawnInfoTable::pawn_info() takes a position object as input, computes -/// a PawnInfo object, and returns a pointer to it. The result is also stored -/// in an hash table, so we don't have to recompute everything when the same -/// pawn structure occurs again. +/// PawnTable::probe() takes a position object as input, computes a PawnEntry +/// object, and returns a pointer to it. The result is also stored in a hash +/// table, so we don't have to recompute everything when the same pawn structure +/// occurs again. -PawnInfo* PawnInfoTable::pawn_info(const Position& pos) const { +PawnEntry* PawnTable::probe(const Position& pos) { Key key = pos.pawn_key(); - PawnInfo* pi = probe(key); + PawnEntry* e = entries[key]; - // If pi->key matches the position's pawn hash key, it means that we + // If e->key matches the position's pawn hash key, it means that we // have analysed this pawn structure before, and we can simply return // the information we found the last time instead of recomputing it. - if (pi->key == key) - return pi; + if (e->key == key) + return e; - // Initialize PawnInfo entry - pi->key = key; - pi->passedPawns[WHITE] = pi->passedPawns[BLACK] = 0; - pi->kingSquares[WHITE] = pi->kingSquares[BLACK] = SQ_NONE; - pi->halfOpenFiles[WHITE] = pi->halfOpenFiles[BLACK] = 0xFF; + e->key = key; + e->passedPawns[WHITE] = e->passedPawns[BLACK] = 0; + e->kingSquares[WHITE] = e->kingSquares[BLACK] = SQ_NONE; + e->halfOpenFiles[WHITE] = e->halfOpenFiles[BLACK] = 0xFF; - // Calculate pawn attacks - Bitboard wPawns = pos.pieces(PAWN, WHITE); - Bitboard bPawns = pos.pieces(PAWN, BLACK); - pi->pawnAttacks[WHITE] = ((wPawns << 9) & ~FileABB) | ((wPawns << 7) & ~FileHBB); - pi->pawnAttacks[BLACK] = ((bPawns >> 7) & ~FileABB) | ((bPawns >> 9) & ~FileHBB); + Bitboard wPawns = pos.pieces(WHITE, PAWN); + Bitboard bPawns = pos.pieces(BLACK, PAWN); + e->pawnAttacks[WHITE] = ((wPawns & ~FileHBB) << 9) | ((wPawns & ~FileABB) << 7); + e->pawnAttacks[BLACK] = ((bPawns & ~FileHBB) >> 7) | ((bPawns & ~FileABB) >> 9); - // Evaluate pawns for both colors and weight the result - pi->value = evaluate_pawns(pos, wPawns, bPawns, pi) - - evaluate_pawns(pos, bPawns, wPawns, pi); + e->value = evaluate_pawns(pos, wPawns, bPawns, e) + - evaluate_pawns(pos, bPawns, wPawns, e); - pi->value = apply_weight(pi->value, PawnStructureWeight); + e->value = apply_weight(e->value, PawnStructureWeight); - return pi; + return e; } -/// PawnInfoTable::evaluate_pawns() evaluates each pawn of the given color +/// PawnTable::evaluate_pawns() evaluates each pawn of the given color template -Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, - Bitboard theirPawns, PawnInfo* pi) { +Score PawnTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, + Bitboard theirPawns, PawnEntry* e) { const Color Them = (Us == WHITE ? BLACK : WHITE); @@ -135,32 +143,32 @@ Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, r = rank_of(s); // This file cannot be half open - pi->halfOpenFiles[Us] &= ~(1 << f); + e->halfOpenFiles[Us] &= ~(1 << f); // Our rank plus previous one. Used for chain detection b = rank_bb(r) | rank_bb(Us == WHITE ? r - Rank(1) : r + Rank(1)); // Flag the pawn as passed, isolated, doubled or member of a pawn // chain (but not the backward one). + chain = ourPawns & adjacent_files_bb(f) & b; + isolated = !(ourPawns & adjacent_files_bb(f)); + doubled = ourPawns & forward_bb(Us, s); + opposed = theirPawns & forward_bb(Us, s); passed = !(theirPawns & passed_pawn_mask(Us, s)); - doubled = ourPawns & squares_in_front_of(Us, s); - opposed = theirPawns & squares_in_front_of(Us, s); - isolated = !(ourPawns & neighboring_files_bb(f)); - chain = ourPawns & neighboring_files_bb(f) & b; // Test for backward pawn backward = false; // If the pawn is passed, isolated, or member of a pawn chain it cannot - // be backward. If there are friendly pawns behind on neighboring files + // be backward. If there are friendly pawns behind on adjacent files // or if can capture an enemy pawn it cannot be backward either. if ( !(passed | isolated | chain) && !(ourPawns & attack_span_mask(Them, s)) && !(pos.attacks_from(s, Us) & theirPawns)) { // We now know that there are no friendly pawns beside or behind this - // pawn on neighboring files. We now check whether the pawn is - // backward by looking in the forward direction on the neighboring + // pawn on adjacent files. We now check whether the pawn is + // backward by looking in the forward direction on the adjacent // files, and seeing whether we meet a friendly or an enemy pawn first. b = pos.attacks_from(s, Us); @@ -178,8 +186,8 @@ Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, // A not passed pawn is a candidate to become passed if it is free to // advance and if the number of friendly pawns beside or behind this - // pawn on neighboring files is higher or equal than the number of - // enemy pawns in the forward direction on the neighboring files. + // pawn on adjacent files is higher or equal than the number of + // enemy pawns in the forward direction on the adjacent files. candidate = !(opposed | passed | backward | isolated) && (b = attack_span_mask(Them, s + pawn_push(Us)) & ourPawns) != 0 && popcount(b) >= popcount(attack_span_mask(Us, s) & theirPawns); @@ -188,7 +196,7 @@ Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, // full attack info to evaluate passed pawns. Only the frontmost passed // pawn on each file is considered a true passed pawn. if (passed && !doubled) - set_bit(&(pi->passedPawns[Us]), s); + e->passedPawns[Us] |= s; // Score this pawn if (isolated) @@ -206,35 +214,69 @@ Score PawnInfoTable::evaluate_pawns(const Position& pos, Bitboard ourPawns, if (candidate) value += CandidateBonus[relative_rank(Us, s)]; } + return value; } -/// PawnInfo::updateShelter() calculates and caches king shelter. It is called -/// only when king square changes, about 20% of total king_shelter() calls. +/// PawnEntry::shelter_storm() calculates shelter and storm penalties for the file +/// the king is on, as well as the two adjacent files. + template -Score PawnInfo::updateShelter(const Position& pos, Square ksq) { +Value PawnEntry::shelter_storm(const Position& pos, Square ksq) { - const int Shift = (Us == WHITE ? 8 : -8); + const Color Them = (Us == WHITE ? BLACK : WHITE); - Bitboard pawns; - int r, shelter = 0; + Value safety = MaxSafetyBonus; + Bitboard b = pos.pieces(PAWN) & (in_front_bb(Us, ksq) | rank_bb(ksq)); + Bitboard ourPawns = b & pos.pieces(Us) & ~rank_bb(ksq); + Bitboard theirPawns = b & pos.pieces(Them); + Rank rkUs, rkThem; + File kf = file_of(ksq); - if (relative_rank(Us, ksq) <= RANK_4) + kf = (kf == FILE_A) ? kf++ : (kf == FILE_H) ? kf-- : kf; + + for (int f = kf - 1; f <= kf + 1; f++) { - pawns = pos.pieces(PAWN, Us) & this_and_neighboring_files_bb(file_of(ksq)); - r = ksq & (7 << 3); - for (int i = 0; i < 3; i++) - { - r += Shift; - shelter += BitCount8Bit[(pawns >> r) & 0xFF] << (6 - i); - } + // Shelter penalty is higher for the pawn in front of the king + b = ourPawns & FileBB[f]; + rkUs = b ? rank_of(Us == WHITE ? first_1(b) : ~last_1(b)) : RANK_1; + safety -= ShelterWeakness[f != kf][rkUs]; + + // Storm danger is smaller if enemy pawn is blocked + b = theirPawns & FileBB[f]; + rkThem = b ? rank_of(Us == WHITE ? first_1(b) : ~last_1(b)) : RANK_1; + safety -= StormDanger[rkThem == rkUs + 1][rkThem]; } + + return safety; +} + + +/// PawnEntry::update_safety() calculates and caches a bonus for king safety. It is +/// called only when king square changes, about 20% of total king_safety() calls. + +template +Score PawnEntry::update_safety(const Position& pos, Square ksq) { + kingSquares[Us] = ksq; - kingShelters[Us] = make_score(shelter, 0); - return kingShelters[Us]; + castleRights[Us] = pos.can_castle(Us); + + if (relative_rank(Us, ksq) > RANK_4) + return kingSafety[Us] = SCORE_ZERO; + + Value bonus = shelter_storm(pos, ksq); + + // If we can castle use the bonus after the castle if is bigger + if (pos.can_castle(make_castle_right(Us, KING_SIDE))) + bonus = std::max(bonus, shelter_storm(pos, relative_square(Us, SQ_G1))); + + if (pos.can_castle(make_castle_right(Us, QUEEN_SIDE))) + bonus = std::max(bonus, shelter_storm(pos, relative_square(Us, SQ_C1))); + + return kingSafety[Us] = make_score(bonus, 0); } // Explicit template instantiation -template Score PawnInfo::updateShelter(const Position& pos, Square ksq); -template Score PawnInfo::updateShelter(const Position& pos, Square ksq); +template Score PawnEntry::update_safety(const Position& pos, Square ksq); +template Score PawnEntry::update_safety(const Position& pos, Square ksq); diff --git a/DroidFish/jni/stockfish/pawns.h b/DroidFish/jni/stockfish/pawns.h index f2b4441..4fee931 100644 --- a/DroidFish/jni/stockfish/pawns.h +++ b/DroidFish/jni/stockfish/pawns.h @@ -20,22 +20,22 @@ #if !defined(PAWNS_H_INCLUDED) #define PAWNS_H_INCLUDED +#include "misc.h" #include "position.h" -#include "tt.h" #include "types.h" const int PawnTableSize = 16384; -/// PawnInfo is a class which contains various information about a pawn +/// PawnEntry is a class which contains various information about a pawn /// structure. Currently, it only includes a middle game and an end game /// pawn structure evaluation, and a bitboard of passed pawns. We may want /// to add further information in the future. A lookup to the pawn hash -/// table (performed by calling the pawn_info method in a PawnInfoTable -/// object) returns a pointer to a PawnInfo object. +/// table (performed by calling the probe method in a PawnTable object) +/// returns a pointer to a PawnEntry object. -class PawnInfo { +class PawnEntry { - friend class PawnInfoTable; + friend struct PawnTable; public: Score pawns_value() const; @@ -46,62 +46,69 @@ public: int has_open_file_to_right(Color c, File f) const; template - Score king_shelter(const Position& pos, Square ksq); + Score king_safety(const Position& pos, Square ksq); private: template - Score updateShelter(const Position& pos, Square ksq); + Score update_safety(const Position& pos, Square ksq); + + template + Value shelter_storm(const Position& pos, Square ksq); Key key; Bitboard passedPawns[2]; Bitboard pawnAttacks[2]; Square kingSquares[2]; + int castleRights[2]; Score value; int halfOpenFiles[2]; - Score kingShelters[2]; + Score kingSafety[2]; }; -/// The PawnInfoTable class represents a pawn hash table. The most important -/// method is pawn_info, which returns a pointer to a PawnInfo object. +/// The PawnTable class represents a pawn hash table. The most important +/// method is probe, which returns a pointer to a PawnEntry object. -class PawnInfoTable : public SimpleHash { -public: - PawnInfo* pawn_info(const Position& pos) const; +struct PawnTable { + + PawnEntry* probe(const Position& pos); -private: template - static Score evaluate_pawns(const Position& pos, Bitboard ourPawns, Bitboard theirPawns, PawnInfo* pi); + static Score evaluate_pawns(const Position& pos, Bitboard ourPawns, + Bitboard theirPawns, PawnEntry* e); + + HashTable entries; }; -inline Score PawnInfo::pawns_value() const { +inline Score PawnEntry::pawns_value() const { return value; } -inline Bitboard PawnInfo::pawn_attacks(Color c) const { +inline Bitboard PawnEntry::pawn_attacks(Color c) const { return pawnAttacks[c]; } -inline Bitboard PawnInfo::passed_pawns(Color c) const { +inline Bitboard PawnEntry::passed_pawns(Color c) const { return passedPawns[c]; } -inline int PawnInfo::file_is_half_open(Color c, File f) const { +inline int PawnEntry::file_is_half_open(Color c, File f) const { return halfOpenFiles[c] & (1 << int(f)); } -inline int PawnInfo::has_open_file_to_left(Color c, File f) const { +inline int PawnEntry::has_open_file_to_left(Color c, File f) const { return halfOpenFiles[c] & ((1 << int(f)) - 1); } -inline int PawnInfo::has_open_file_to_right(Color c, File f) const { +inline int PawnEntry::has_open_file_to_right(Color c, File f) const { return halfOpenFiles[c] & ~((1 << int(f+1)) - 1); } template -inline Score PawnInfo::king_shelter(const Position& pos, Square ksq) { - return kingSquares[Us] == ksq ? kingShelters[Us] : updateShelter(pos, ksq); +inline Score PawnEntry::king_safety(const Position& pos, Square ksq) { + return kingSquares[Us] == ksq && castleRights[Us] == pos.can_castle(Us) + ? kingSafety[Us] : update_safety(pos, ksq); } #endif // !defined(PAWNS_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/platform.h b/DroidFish/jni/stockfish/platform.h new file mode 100644 index 0000000..e002d21 --- /dev/null +++ b/DroidFish/jni/stockfish/platform.h @@ -0,0 +1,109 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2012 Marco Costalba, Joona Kiiski, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#if !defined(PLATFORM_H_INCLUDED) +#define PLATFORM_H_INCLUDED + +#if defined(_MSC_VER) + +// Disable some silly and noisy warning from MSVC compiler +#pragma warning(disable: 4127) // Conditional expression is constant +#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type +#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#pragma warning(disable: 4996) // Function _ftime() may be unsafe + +// MSVC does not support +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else +# include +#endif + +#if !defined(_WIN32) && !defined(_WIN64) // Linux - Unix + +# include +typedef timeval sys_time_t; + +inline void system_time(sys_time_t* t) { gettimeofday(t, NULL); } +inline uint64_t time_to_msec(const sys_time_t& t) { return t.tv_sec * 1000LL + t.tv_usec / 1000; } + +# include +typedef pthread_mutex_t Lock; +typedef pthread_cond_t WaitCondition; +typedef pthread_t NativeHandle; +typedef void*(*pt_start_fn)(void*); + +# define lock_init(x) pthread_mutex_init(&(x), NULL) +# define lock_grab(x) pthread_mutex_lock(&(x)) +# define lock_release(x) pthread_mutex_unlock(&(x)) +# define lock_destroy(x) pthread_mutex_destroy(&(x)) +# define cond_destroy(x) pthread_cond_destroy(&(x)) +# define cond_init(x) pthread_cond_init(&(x), NULL) +# define cond_signal(x) pthread_cond_signal(&(x)) +# define cond_wait(x,y) pthread_cond_wait(&(x),&(y)) +# define cond_timedwait(x,y,z) pthread_cond_timedwait(&(x),&(y),z) +# define thread_create(x,f,t) !pthread_create(&(x),NULL,(pt_start_fn)f,t) +# define thread_join(x) pthread_join(x, NULL) + +#else // Windows and MinGW + +# include +typedef _timeb sys_time_t; + +inline void system_time(sys_time_t* t) { _ftime(t); } +inline uint64_t time_to_msec(const sys_time_t& t) { return t.time * 1000LL + t.millitm; } + +#if !defined(NOMINMAX) +# define NOMINMAX // disable macros min() and max() +#endif + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#undef NOMINMAX + +// We use critical sections on Windows to support Windows XP and older versions, +// unfortunatly cond_wait() is racy between lock_release() and WaitForSingleObject() +// but apart from this they have the same speed performance of SRW locks. +typedef CRITICAL_SECTION Lock; +typedef HANDLE WaitCondition; +typedef HANDLE NativeHandle; + +# define lock_init(x) InitializeCriticalSection(&(x)) +# define lock_grab(x) EnterCriticalSection(&(x)) +# define lock_release(x) LeaveCriticalSection(&(x)) +# define lock_destroy(x) DeleteCriticalSection(&(x)) +# define cond_init(x) { x = CreateEvent(0, FALSE, FALSE, 0); } +# define cond_destroy(x) CloseHandle(x) +# define cond_signal(x) SetEvent(x) +# define cond_wait(x,y) { lock_release(y); WaitForSingleObject(x, INFINITE); lock_grab(y); } +# define cond_timedwait(x,y,z) { lock_release(y); WaitForSingleObject(x,z); lock_grab(y); } +# define thread_create(x,f,t) (x = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)f,t,0,NULL), x != NULL) +# define thread_join(x) { WaitForSingleObject(x, INFINITE); CloseHandle(x); } + +#endif + +#endif // !defined(PLATFORM_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/position.cpp b/DroidFish/jni/stockfish/position.cpp index ccc29ac..c26ab1b 100644 --- a/DroidFish/jni/stockfish/position.cpp +++ b/DroidFish/jni/stockfish/position.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -37,7 +36,7 @@ using std::cout; using std::endl; Key Position::zobrist[2][8][64]; -Key Position::zobEp[64]; +Key Position::zobEp[8]; Key Position::zobCastle[16]; Key Position::zobSideToMove; Key Position::zobExclusion; @@ -63,15 +62,8 @@ const Value PieceValueEndgame[17] = { RookValueEndgame, QueenValueEndgame }; - -namespace { - - // Bonus for having the side to move (modified by Joona Kiiski) - const Score TempoValue = make_score(48, 22); - - // To convert a Piece to and from a FEN char - const string PieceToChar(" PNBRQK pnbrqk ."); -} +// To convert a Piece to and from a FEN char +static const string PieceToChar(" PNBRQK pnbrqk"); /// CheckInfo c'tor @@ -93,33 +85,26 @@ CheckInfo::CheckInfo(const Position& pos) { } -/// Position c'tors. Here we always create a copy of the original position -/// or the FEN string, we want the new born Position object do not depend -/// on any external data so we detach state pointer from the source one. +/// Position::operator=() creates a copy of 'pos'. We want the new born Position +/// object do not depend on any external data so we detach state pointer from +/// the source one. -void Position::copy(const Position& pos, int th) { +void Position::operator=(const Position& pos) { memcpy(this, &pos, sizeof(Position)); startState = *st; st = &startState; - threadID = th; nodes = 0; assert(pos_is_ok()); } -Position::Position(const string& fen, bool isChess960, int th) { - - from_fen(fen, isChess960); - threadID = th; -} - /// Position::from_fen() initializes the position object with the given FEN /// string. 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::from_fen(const string& fenStr, bool isChess960) { +void Position::from_fen(const string& fenStr, bool isChess960, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -169,7 +154,7 @@ void Position::from_fen(const string& fenStr, bool isChess960) { sq += Square(token - '0'); // Advance the given number of files else if (token == '/') - sq = make_square(FILE_A, rank_of(sq) - Rank(2)); + sq -= Square(16); else if ((p = PieceToChar.find(token)) != string::npos) { @@ -216,7 +201,7 @@ void Position::from_fen(const string& fenStr, bool isChess960) { { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - if (!(attackers_to(st->epSquare) & pieces(PAWN, sideToMove))) + if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) st->epSquare = SQ_NONE; } @@ -230,11 +215,12 @@ void Position::from_fen(const string& fenStr, bool isChess960) { st->key = compute_key(); st->pawnKey = compute_pawn_key(); st->materialKey = compute_material_key(); - st->value = compute_value(); + st->psqScore = compute_psq_score(); st->npMaterial[WHITE] = compute_non_pawn_material(WHITE); st->npMaterial[BLACK] = compute_non_pawn_material(BLACK); st->checkersBB = attackers_to(king_square(sideToMove)) & pieces(~sideToMove); chess960 = isChess960; + thisThread = th; assert(pos_is_ok()); } @@ -243,14 +229,27 @@ void Position::from_fen(const string& fenStr, bool isChess960) { /// Position::set_castle_right() is an helper function used to set castling /// rights given the corresponding color and the rook starting square. -void Position::set_castle_right(Color c, Square rsq) { +void Position::set_castle_right(Color c, Square rfrom) { - int f = (rsq < king_square(c) ? WHITE_OOO : WHITE_OO) << c; + Square kfrom = king_square(c); + CastlingSide cs = kfrom < rfrom ? KING_SIDE : QUEEN_SIDE; + CastleRight cr = make_castle_right(c, cs); - st->castleRights |= f; - castleRightsMask[king_square(c)] ^= f; - castleRightsMask[rsq] ^= f; - castleRookSquare[f] = rsq; + st->castleRights |= cr; + castleRightsMask[kfrom] |= cr; + castleRightsMask[rfrom] |= cr; + castleRookSquare[c][cs] = 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) + castlePath[c][cs] |= s; + + for (Square s = std::min(kfrom, kto); s <= std::max(kfrom, kto); s++) + if (s != kfrom && s != rfrom) + castlePath[c][cs] |= s; } @@ -271,7 +270,7 @@ const string Position::to_fen() const { { sq = make_square(file, rank); - if (square_is_empty(sq)) + if (is_empty(sq)) emptyCnt++; else { @@ -294,16 +293,16 @@ const string Position::to_fen() const { fen << (sideToMove == WHITE ? " w " : " b "); if (can_castle(WHITE_OO)) - fen << (chess960 ? char(toupper(file_to_char(file_of(castle_rook_square(WHITE_OO))))) : 'K'); + fen << (chess960 ? char(toupper(file_to_char(file_of(castle_rook_square(WHITE, KING_SIDE))))) : 'K'); if (can_castle(WHITE_OOO)) - fen << (chess960 ? char(toupper(file_to_char(file_of(castle_rook_square(WHITE_OOO))))) : 'Q'); + fen << (chess960 ? char(toupper(file_to_char(file_of(castle_rook_square(WHITE, QUEEN_SIDE))))) : 'Q'); if (can_castle(BLACK_OO)) - fen << (chess960 ? file_to_char(file_of(castle_rook_square(BLACK_OO))) : 'k'); + fen << (chess960 ? file_to_char(file_of(castle_rook_square(BLACK, KING_SIDE))) : 'k'); if (can_castle(BLACK_OOO)) - fen << (chess960 ? file_to_char(file_of(castle_rook_square(BLACK_OOO))) : 'q'); + fen << (chess960 ? file_to_char(file_of(castle_rook_square(BLACK, QUEEN_SIDE))) : 'q'); if (st->castleRights == CASTLES_NONE) fen << '-'; @@ -320,30 +319,23 @@ const string Position::to_fen() const { void Position::print(Move move) const { - const char* dottedLine = "\n+---+---+---+---+---+---+---+---+\n"; + const string dottedLine = "\n+---+---+---+---+---+---+---+---+"; + const string twoRows = dottedLine + "\n| | . | | . | | . | | . |" + + dottedLine + "\n| . | | . | | . | | . | |"; + + string brd = twoRows + twoRows + twoRows + twoRows + dottedLine; if (move) { - Position p(*this, thread()); + Position p(*this); cout << "\nMove is: " << (sideToMove == BLACK ? ".." : "") << move_to_san(p, move); } - for (Rank rank = RANK_8; rank >= RANK_1; rank--) - { - cout << dottedLine << '|'; - for (File file = FILE_A; file <= FILE_H; file++) - { - Square sq = make_square(file, rank); - Piece piece = piece_on(sq); - char c = (color_of(piece) == BLACK ? '=' : ' '); + for (Square sq = SQ_A1; sq <= SQ_H8; sq++) + if (piece_on(sq) != NO_PIECE) + brd[513 - 68*rank_of(sq) + 4*file_of(sq)] = PieceToChar[piece_on(sq)]; - if (piece == NO_PIECE && !opposite_colors(sq, SQ_A1)) - piece++; // Index the dot - - cout << c << PieceToChar[piece] << c << '|'; - } - } - cout << dottedLine << "Fen is: " << to_fen() << "\nKey is: " << st->key << endl; + cout << brd << "\nFen is: " << to_fen() << "\nKey is: " << st->key << endl; } @@ -365,10 +357,9 @@ Bitboard Position::hidden_checkers() const { while (pinners) { - b = squares_between(ksq, pop_1st_bit(&pinners)) & occupied_squares(); + b = between_bb(ksq, pop_1st_bit(&pinners)) & pieces(); - // Only one bit set and is an our piece? - if (b && !(b & (b - 1)) && (b & pieces(sideToMove))) + if (b && !more_than_one(b) && (b & pieces(sideToMove))) result |= b; } return result; @@ -384,11 +375,11 @@ template Bitboard Position::hidden_checkers() const; Bitboard Position::attackers_to(Square s, Bitboard occ) const { - return (attacks_from(s, BLACK) & pieces(PAWN, WHITE)) - | (attacks_from(s, WHITE) & pieces(PAWN, BLACK)) + return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_from(s) & pieces(KNIGHT)) - | (rook_attacks_bb(s, occ) & pieces(ROOK, QUEEN)) - | (bishop_attacks_bb(s, occ) & pieces(BISHOP, QUEEN)) + | (attacks_bb(s, occ) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occ) & pieces(BISHOP, QUEEN)) | (attacks_from(s) & pieces(KING)); } @@ -398,13 +389,13 @@ Bitboard Position::attackers_to(Square s, Bitboard occ) const { Bitboard Position::attacks_from(Piece p, Square s, Bitboard occ) { - assert(square_is_ok(s)); + assert(is_ok(s)); switch (type_of(p)) { - case BISHOP: return bishop_attacks_bb(s, occ); - case ROOK : return rook_attacks_bb(s, occ); - case QUEEN : return bishop_attacks_bb(s, occ) | rook_attacks_bb(s, occ); + case BISHOP: return attacks_bb(s, occ); + case ROOK : return attacks_bb(s, occ); + case QUEEN : return attacks_bb(s, occ) | attacks_bb(s, occ); default : return StepAttacksBB[p][s]; } } @@ -416,26 +407,25 @@ Bitboard Position::attacks_from(Piece p, Square s, Bitboard occ) { bool Position::move_attacks_square(Move m, Square s) const { assert(is_ok(m)); - assert(square_is_ok(s)); + assert(is_ok(s)); Bitboard occ, xray; Square from = from_sq(m); Square to = to_sq(m); - Piece piece = piece_on(from); + Piece piece = piece_moved(m); - assert(!square_is_empty(from)); + assert(!is_empty(from)); // Update occupancy as if the piece is moving - occ = occupied_squares(); - do_move_bb(&occ, make_move_bb(from, to)); + occ = pieces() ^ from ^ to; // The piece moved in 'to' attacks the square 's' ? - if (bit_is_set(attacks_from(piece, to, occ), s)) + if (attacks_from(piece, to, occ) & s) return true; // Scan for possible X-ray attackers behind the moved piece - xray = (rook_attacks_bb(s, occ) & pieces(ROOK, QUEEN, color_of(piece))) - |(bishop_attacks_bb(s, occ) & pieces(BISHOP, QUEEN, color_of(piece))); + xray = (attacks_bb< ROOK>(s, occ) & pieces(color_of(piece), QUEEN, ROOK)) + | (attacks_bb(s, occ) & pieces(color_of(piece), QUEEN, BISHOP)); // Verify attackers are triggered by our move and not already existing return xray && (xray ^ (xray & attacks_from(s))); @@ -452,7 +442,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { Color us = sideToMove; Square from = from_sq(m); - assert(color_of(piece_on(from)) == us); + assert(color_of(piece_moved(m)) == us); assert(piece_on(king_square(us)) == make_piece(us, KING)); // En passant captures are a tricky special case. Because they are rather @@ -464,19 +454,15 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { Square to = to_sq(m); Square capsq = to + pawn_push(them); Square ksq = king_square(us); - Bitboard b = occupied_squares(); + Bitboard b = (pieces() ^ from ^ capsq) | to; assert(to == ep_square()); - assert(piece_on(from) == make_piece(us, PAWN)); + assert(piece_moved(m) == make_piece(us, PAWN)); assert(piece_on(capsq) == make_piece(them, PAWN)); assert(piece_on(to) == NO_PIECE); - clear_bit(&b, from); - clear_bit(&b, capsq); - set_bit(&b, to); - - return !(rook_attacks_bb(ksq, b) & pieces(ROOK, QUEEN, them)) - && !(bishop_attacks_bb(ksq, b) & pieces(BISHOP, QUEEN, them)); + return !(attacks_bb< ROOK>(ksq, b) & pieces(them, QUEEN, ROOK)) + && !(attacks_bb(ksq, b) & pieces(them, QUEEN, BISHOP)); } // If the moving piece is a king, check whether the destination @@ -488,7 +474,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. return !pinned - || !bit_is_set(pinned, from) + || !(pinned & from) || squares_aligned(from, to_sq(m), king_square(us)); } @@ -517,14 +503,14 @@ bool Position::is_pseudo_legal(const Move m) const { Color them = ~sideToMove; Square from = from_sq(m); Square to = to_sq(m); - Piece pc = piece_on(from); + Piece pc = piece_moved(m); // Use a slower but simpler function for uncommon cases if (is_special(m)) return move_is_legal(m); // Is not a promotion, so promotion piece must be empty - if (promotion_piece_type(m) - 2 != NO_PIECE_TYPE) + if (promotion_type(m) - 2 != NO_PIECE_TYPE) return false; // If the from square is not occupied by a piece belonging to the side to @@ -570,7 +556,7 @@ bool Position::is_pseudo_legal(const Move m) const { case DELTA_N: case DELTA_S: // Pawn push. The destination square must be empty. - if (!square_is_empty(to)) + if (!is_empty(to)) return false; break; @@ -578,9 +564,9 @@ bool Position::is_pseudo_legal(const Move m) const { // Double white pawn push. The destination square must be on the fourth // rank, and both the destination square and the square between the // source and destination squares must be empty. - if ( rank_of(to) != RANK_4 - || !square_is_empty(to) - || !square_is_empty(from + DELTA_N)) + if ( rank_of(to) != RANK_4 + || !is_empty(to) + || !is_empty(from + DELTA_N)) return false; break; @@ -588,9 +574,9 @@ bool Position::is_pseudo_legal(const Move m) const { // Double black pawn push. The destination square must be on the fifth // rank, and both the destination square and the square between the // source and destination squares must be empty. - if ( rank_of(to) != RANK_5 - || !square_is_empty(to) - || !square_is_empty(from + DELTA_S)) + if ( rank_of(to) != RANK_5 + || !is_empty(to) + || !is_empty(from + DELTA_S)) return false; break; @@ -598,7 +584,7 @@ bool Position::is_pseudo_legal(const Move m) const { return false; } } - else if (!bit_is_set(attacks_from(pc, from), to)) + else if (!(attacks_from(pc, from) & to)) return false; // Evasions generator already takes care to avoid some kind of illegal moves @@ -606,28 +592,22 @@ bool Position::is_pseudo_legal(const Move m) const { // same kind of moves are filtered out here. if (in_check()) { - // In case of king moves under check we have to remove king so to catch - // as invalid moves like b1a1 when opposite queen is on c1. - if (type_of(piece_on(from)) == KING) + if (type_of(pc) != KING) { - Bitboard b = occupied_squares(); - clear_bit(&b, from); - if (attackers_to(to_sq(m), b) & pieces(~us)) - return false; - } - else - { - Bitboard target = checkers(); - Square checksq = pop_1st_bit(&target); + Bitboard b = checkers(); + Square checksq = pop_1st_bit(&b); - if (target) // double check ? In this case a king move is required + if (b) // double check ? In this case a king move is required return false; // Our move must be a blocking evasion or a capture of the checking piece - target = squares_between(checksq, king_square(us)) | checkers(); - if (!bit_is_set(target, to_sq(m))) + if (!((between_bb(checksq, king_square(us)) | checkers()) & to)) return false; } + // In case of king moves under check we have to remove king so to catch + // as invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + return false; } return true; @@ -647,14 +627,14 @@ bool Position::move_gives_check(Move m, const CheckInfo& ci) const { PieceType pt = type_of(piece_on(from)); // Direct check ? - if (bit_is_set(ci.checkSq[pt], to)) + if (ci.checkSq[pt] & to) return true; // Discovery check ? - if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from)) + if (ci.dcCandidates && (ci.dcCandidates & from)) { // For pawn and king moves we need to verify also direction - if ( (pt != PAWN && pt != KING) + if ( (pt != PAWN && pt != KING) || !squares_aligned(from, to, king_square(~sideToMove))) return true; } @@ -664,15 +644,11 @@ bool Position::move_gives_check(Move m, const CheckInfo& ci) const { return false; Color us = sideToMove; - Bitboard b = occupied_squares(); Square ksq = king_square(~us); // Promotion with check ? if (is_promotion(m)) - { - clear_bit(&b, from); - return bit_is_set(attacks_from(Piece(promotion_piece_type(m)), to, b), ksq); - } + return attacks_from(Piece(promotion_type(m)), to, pieces() ^ from) & ksq; // En passant capture with check ? We have already handled the case // of direct checks and ordinary discovered check, the only case we @@ -681,33 +657,22 @@ bool Position::move_gives_check(Move m, const CheckInfo& ci) const { if (is_enpassant(m)) { Square capsq = make_square(file_of(to), rank_of(from)); - clear_bit(&b, from); - clear_bit(&b, capsq); - set_bit(&b, to); - return (rook_attacks_bb(ksq, b) & pieces(ROOK, QUEEN, us)) - ||(bishop_attacks_bb(ksq, b) & pieces(BISHOP, QUEEN, us)); + Bitboard b = (pieces() ^ from ^ capsq) | to; + + return (attacks_bb< ROOK>(ksq, b) & pieces(us, QUEEN, ROOK)) + | (attacks_bb(ksq, b) & pieces(us, QUEEN, BISHOP)); } // Castling with check ? if (is_castle(m)) { - Square kfrom, kto, rfrom, rto; - kfrom = from; - rfrom = to; + Square kfrom = from; + Square rfrom = to; // 'King captures the rook' notation + Square kto = relative_square(us, rfrom > kfrom ? SQ_G1 : SQ_C1); + Square rto = relative_square(us, rfrom > kfrom ? SQ_F1 : SQ_D1); + Bitboard b = (pieces() ^ kfrom ^ rfrom) | rto | kto; - if (rfrom > kfrom) - { - kto = relative_square(us, SQ_G1); - rto = relative_square(us, SQ_F1); - } else { - kto = relative_square(us, SQ_C1); - rto = relative_square(us, SQ_D1); - } - clear_bit(&b, kfrom); - clear_bit(&b, rfrom); - set_bit(&b, rto); - set_bit(&b, kto); - return bit_is_set(rook_attacks_bb(rto, b), ksq); + return attacks_bb(rto, b) & ksq; } return false; @@ -735,14 +700,6 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI // Copy some fields of old state to our new StateInfo object except the ones // which are recalculated from scratch anyway, then switch our state pointer // to point to the new, ready to be updated, state. - struct ReducedStateInfo { - Key pawnKey, materialKey; - Value npMaterial[2]; - int castleRights, rule50, pliesFromNull; - Score value; - Square epSquare; - }; - memcpy(&newSt, st, sizeof(ReducedStateInfo)); newSt.previous = st; @@ -752,7 +709,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI k ^= zobSideToMove; // Increment the 50 moves rule draw counter. Resetting it to zero in the - // case of non-reversible moves is taken care of later. + // case of a capture or a pawn move is taken care of later. st->rule50++; st->pliesFromNull++; @@ -802,9 +759,9 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI st->npMaterial[them] -= PieceValueMidgame[capture]; // Remove the captured piece - clear_bit(&byColorBB[them], capsq); - clear_bit(&byTypeBB[capture], capsq); - clear_bit(&occupied, capsq); + byTypeBB[ALL_PIECES] ^= capsq; + byTypeBB[capture] ^= capsq; + byColorBB[them] ^= capsq; // Update piece list, move the last piece at index[capsq] position and // shrink the list. @@ -823,7 +780,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI st->materialKey ^= zobrist[them][capture][pieceCount[them][capture]]; // Update incremental scores - st->value -= pst(make_piece(them, capture), capsq); + st->psqScore -= pieceSquareTable[make_piece(them, capture)][capsq]; // Reset rule 50 counter st->rule50 = 0; @@ -835,27 +792,26 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI // Reset en passant square if (st->epSquare != SQ_NONE) { - k ^= zobEp[st->epSquare]; + k ^= zobEp[file_of(st->epSquare)]; st->epSquare = SQ_NONE; } // Update castle rights if needed - if ( st->castleRights != CASTLES_NONE - && (castleRightsMask[from] & castleRightsMask[to]) != ALL_CASTLES) + if (st->castleRights && (castleRightsMask[from] | castleRightsMask[to])) { - k ^= zobCastle[st->castleRights]; - st->castleRights &= castleRightsMask[from] & castleRightsMask[to]; - k ^= zobCastle[st->castleRights]; + int cr = castleRightsMask[from] | castleRightsMask[to]; + k ^= zobCastle[st->castleRights & cr]; + st->castleRights &= ~cr; } // Prefetch TT access as soon as we know key is updated prefetch((char*)TT.first_entry(k)); // Move the piece - Bitboard move_bb = make_move_bb(from, to); - do_move_bb(&byColorBB[us], move_bb); - do_move_bb(&byTypeBB[pt], move_bb); - do_move_bb(&occupied, move_bb); + Bitboard from_to_bb = SquareBB[from] ^ SquareBB[to]; + byTypeBB[ALL_PIECES] ^= from_to_bb; + byTypeBB[pt] ^= from_to_bb; + byColorBB[us] ^= from_to_bb; board[to] = board[from]; board[from] = NO_PIECE; @@ -869,23 +825,23 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI if (pt == PAWN) { // Set en-passant square, only if moved pawn can be captured - if ( (to ^ from) == 16 - && (attacks_from(from + pawn_push(us), us) & pieces(PAWN, them))) + if ( (int(to) ^ int(from)) == 16 + && (attacks_from(from + pawn_push(us), us) & pieces(them, PAWN))) { st->epSquare = Square((from + to) / 2); - k ^= zobEp[st->epSquare]; + k ^= zobEp[file_of(st->epSquare)]; } if (is_promotion(m)) { - PieceType promotion = promotion_piece_type(m); + PieceType promotion = promotion_type(m); assert(relative_rank(us, to) == RANK_8); assert(promotion >= KNIGHT && promotion <= QUEEN); // Replace the pawn with the promoted piece - clear_bit(&byTypeBB[PAWN], to); - set_bit(&byTypeBB[promotion], to); + byTypeBB[PAWN] ^= to; + byTypeBB[promotion] |= to; board[to] = make_piece(us, promotion); // Update piece lists, move the last pawn at index[to] position @@ -904,8 +860,8 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI ^ zobrist[us][PAWN][pieceCount[us][PAWN]]; // Update incremental score - st->value += pst(make_piece(us, promotion), to) - - pst(make_piece(us, PAWN), to); + st->psqScore += pieceSquareTable[make_piece(us, promotion)][to] + - pieceSquareTable[make_piece(us, PAWN)][to]; // Update material st->npMaterial[us] += PieceValueMidgame[promotion]; @@ -919,11 +875,11 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI } // Prefetch pawn and material hash tables - Threads[threadID].pawnTable.prefetch(st->pawnKey); - Threads[threadID].materialTable.prefetch(st->materialKey); + prefetch((char*)thisThread->pawnTable.entries[st->pawnKey]); + prefetch((char*)thisThread->materialTable.entries[st->materialKey]); // Update incremental scores - st->value += pst_delta(piece, from, to); + st->psqScore += psq_delta(piece, from, to); // Set capture piece st->capturedType = capture; @@ -941,24 +897,22 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI else { // Direct checks - if (bit_is_set(ci.checkSq[pt], to)) - st->checkersBB = SetMaskBB[to]; + if (ci.checkSq[pt] & to) + st->checkersBB |= to; // Discovery checks - if (ci.dcCandidates && bit_is_set(ci.dcCandidates, from)) + if (ci.dcCandidates && (ci.dcCandidates & from)) { if (pt != ROOK) - st->checkersBB |= attacks_from(king_square(them)) & pieces(ROOK, QUEEN, us); + st->checkersBB |= attacks_from(king_square(them)) & pieces(us, QUEEN, ROOK); if (pt != BISHOP) - st->checkersBB |= attacks_from(king_square(them)) & pieces(BISHOP, QUEEN, us); + st->checkersBB |= attacks_from(king_square(them)) & pieces(us, QUEEN, BISHOP); } } } - // Finish sideToMove = ~sideToMove; - st->value += (sideToMove == WHITE ? TempoValue : -TempoValue); assert(pos_is_ok()); } @@ -987,21 +941,21 @@ void Position::undo_move(Move m) { PieceType pt = type_of(piece); PieceType capture = st->capturedType; - assert(square_is_empty(from)); + assert(is_empty(from)); assert(color_of(piece) == us); assert(capture != KING); if (is_promotion(m)) { - PieceType promotion = promotion_piece_type(m); + PieceType promotion = promotion_type(m); assert(promotion == pt); assert(relative_rank(us, to) == RANK_8); assert(promotion >= KNIGHT && promotion <= QUEEN); // Replace the promoted piece with the pawn - clear_bit(&byTypeBB[promotion], to); - set_bit(&byTypeBB[PAWN], to); + byTypeBB[promotion] ^= to; + byTypeBB[PAWN] |= to; board[to] = make_piece(us, PAWN); // Update piece lists, move the last promoted piece at index[to] position @@ -1017,10 +971,10 @@ void Position::undo_move(Move m) { } // Put the piece back at the source square - Bitboard move_bb = make_move_bb(to, from); - do_move_bb(&byColorBB[us], move_bb); - do_move_bb(&byTypeBB[pt], move_bb); - do_move_bb(&occupied, move_bb); + Bitboard from_to_bb = SquareBB[from] ^ SquareBB[to]; + byTypeBB[ALL_PIECES] ^= from_to_bb; + byTypeBB[pt] ^= from_to_bb; + byColorBB[us] ^= from_to_bb; board[from] = board[to]; board[to] = NO_PIECE; @@ -1045,9 +999,9 @@ void Position::undo_move(Move m) { } // Restore the captured piece - set_bit(&byColorBB[them], capsq); - set_bit(&byTypeBB[capture], capsq); - set_bit(&occupied, capsq); + byTypeBB[ALL_PIECES] |= capsq; + byTypeBB[capture] |= capsq; + byColorBB[them] |= capsq; board[capsq] = make_piece(them, capture); @@ -1100,21 +1054,13 @@ void Position::do_castle_move(Move m) { assert(piece_on(kfrom) == make_piece(us, KING)); assert(piece_on(rfrom) == make_piece(us, ROOK)); - // Remove pieces from source squares - clear_bit(&byColorBB[us], kfrom); - clear_bit(&byTypeBB[KING], kfrom); - clear_bit(&occupied, kfrom); - clear_bit(&byColorBB[us], rfrom); - clear_bit(&byTypeBB[ROOK], rfrom); - clear_bit(&occupied, rfrom); - - // Put pieces on destination squares - set_bit(&byColorBB[us], kto); - set_bit(&byTypeBB[KING], kto); - set_bit(&occupied, kto); - set_bit(&byColorBB[us], rto); - set_bit(&byTypeBB[ROOK], rto); - set_bit(&occupied, rto); + // Move the pieces, with some care; in chess960 could be kto == rfrom + Bitboard k_from_to_bb = SquareBB[kfrom] ^ SquareBB[kto]; + Bitboard r_from_to_bb = SquareBB[rfrom] ^ SquareBB[rto]; + byTypeBB[KING] ^= k_from_to_bb; + byTypeBB[ROOK] ^= r_from_to_bb; + byTypeBB[ALL_PIECES] ^= k_from_to_bb ^ r_from_to_bb; + byColorBB[us] ^= k_from_to_bb ^ r_from_to_bb; // Update board Piece king = make_piece(us, KING); @@ -1136,8 +1082,8 @@ void Position::do_castle_move(Move m) { st->capturedType = NO_PIECE_TYPE; // Update incremental scores - st->value += pst_delta(king, kfrom, kto); - st->value += pst_delta(rook, rfrom, rto); + st->psqScore += psq_delta(king, kfrom, kto); + st->psqScore += psq_delta(rook, rfrom, rto); // Update hash key st->key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto]; @@ -1146,24 +1092,18 @@ void Position::do_castle_move(Move m) { // Clear en passant square if (st->epSquare != SQ_NONE) { - st->key ^= zobEp[st->epSquare]; + st->key ^= zobEp[file_of(st->epSquare)]; st->epSquare = SQ_NONE; } // Update castling rights - st->key ^= zobCastle[st->castleRights]; - st->castleRights &= castleRightsMask[kfrom]; - st->key ^= zobCastle[st->castleRights]; - - // Reset rule 50 counter - st->rule50 = 0; + st->key ^= zobCastle[st->castleRights & castleRightsMask[kfrom]]; + st->castleRights &= ~castleRightsMask[kfrom]; // Update checkers BB st->checkersBB = attackers_to(king_square(~us)) & pieces(us); - // Finish sideToMove = ~sideToMove; - st->value += (sideToMove == WHITE ? TempoValue : -TempoValue); } else // Undo: point our state pointer back to the previous state @@ -1189,7 +1129,7 @@ void Position::do_null_move(StateInfo& backupSt) { dst->key = src->key; dst->epSquare = src->epSquare; - dst->value = src->value; + dst->psqScore = src->psqScore; dst->rule50 = src->rule50; dst->pliesFromNull = src->pliesFromNull; @@ -1198,7 +1138,7 @@ void Position::do_null_move(StateInfo& backupSt) { if (Do) { if (st->epSquare != SQ_NONE) - st->key ^= zobEp[st->epSquare]; + st->key ^= zobEp[file_of(st->epSquare)]; st->key ^= zobSideToMove; prefetch((char*)TT.first_entry(st->key)); @@ -1206,7 +1146,6 @@ void Position::do_null_move(StateInfo& backupSt) { st->epSquare = SQ_NONE; st->rule50++; st->pliesFromNull = 0; - st->value += (sideToMove == WHITE) ? TempoValue : -TempoValue; } assert(pos_is_ok()); @@ -1227,13 +1166,10 @@ int Position::see_sign(Move m) const { assert(is_ok(m)); - Square from = from_sq(m); - Square to = to_sq(m); - // Early return if SEE cannot be negative because captured piece value // is not less then capturing one. Note that king moves always return // here because king midgame value is set to 0. - if (PieceValueMidgame[piece_on(to)] >= PieceValueMidgame[piece_on(from)]) + if (PieceValueMidgame[piece_on(to_sq(m))] >= PieceValueMidgame[piece_moved(m)]) return 1; return see(m); @@ -1258,7 +1194,7 @@ int Position::see(Move m) const { from = from_sq(m); to = to_sq(m); capturedType = type_of(piece_on(to)); - occ = occupied_squares(); + occ = pieces(); // Handle en passant moves if (is_enpassant(m)) @@ -1269,13 +1205,13 @@ int Position::see(Move m) const { assert(type_of(piece_on(capQq)) == PAWN); // Remove the captured pawn - clear_bit(&occ, capQq); + occ ^= capQq; capturedType = PAWN; } // Find all attackers to the destination square, with the moving piece // removed, but possibly an X-ray attacker added behind it. - clear_bit(&occ, from); + occ ^= from; attackers = attackers_to(to, occ); // If the opponent has no attackers we are finished @@ -1304,8 +1240,8 @@ int Position::see(Move m) const { // and scan for new X-ray attacks behind the attacker. b = stmAttackers & pieces(pt); occ ^= (b & (~b + 1)); - attackers |= (rook_attacks_bb(to, occ) & pieces(ROOK, QUEEN)) - | (bishop_attacks_bb(to, occ) & pieces(BISHOP, QUEEN)); + attackers |= (attacks_bb(to, occ) & pieces(ROOK, QUEEN)) + | (attacks_bb(to, occ) & pieces(BISHOP, QUEEN)); attackers &= occ; // Cut out pieces we've already done @@ -1343,27 +1279,16 @@ int Position::see(Move m) const { void Position::clear() { + memset(this, 0, sizeof(Position)); + startState.epSquare = SQ_NONE; st = &startState; - memset(st, 0, sizeof(StateInfo)); - st->epSquare = SQ_NONE; - - memset(byColorBB, 0, sizeof(Bitboard) * 2); - memset(byTypeBB, 0, sizeof(Bitboard) * 8); - memset(pieceCount, 0, sizeof(int) * 2 * 8); - memset(index, 0, sizeof(int) * 64); for (int i = 0; i < 8; i++) for (int j = 0; j < 16; j++) pieceList[0][i][j] = pieceList[1][i][j] = SQ_NONE; for (Square sq = SQ_A1; sq <= SQ_H8; sq++) - { board[sq] = NO_PIECE; - castleRightsMask[sq] = ALL_CASTLES; - } - sideToMove = WHITE; - nodes = 0; - occupied = 0; } @@ -1379,9 +1304,9 @@ void Position::put_piece(Piece p, Square s) { index[s] = pieceCount[c][pt]++; pieceList[c][pt][index[s]] = s; - set_bit(&byTypeBB[pt], s); - set_bit(&byColorBB[c], s); - set_bit(&occupied, s); + byTypeBB[ALL_PIECES] |= s; + byTypeBB[pt] |= s; + byColorBB[c] |= s; } @@ -1392,19 +1317,21 @@ void Position::put_piece(Piece p, Square s) { Key Position::compute_key() const { - Key result = zobCastle[st->castleRights]; + Key k = zobCastle[st->castleRights]; - for (Square s = SQ_A1; s <= SQ_H8; s++) - if (!square_is_empty(s)) - result ^= zobrist[color_of(piece_on(s))][type_of(piece_on(s))][s]; + for (Bitboard b = pieces(); b; ) + { + Square s = pop_1st_bit(&b); + k ^= zobrist[color_of(piece_on(s))][type_of(piece_on(s))][s]; + } if (ep_square() != SQ_NONE) - result ^= zobEp[ep_square()]; + k ^= zobEp[file_of(ep_square())]; if (sideToMove == BLACK) - result ^= zobSideToMove; + k ^= zobSideToMove; - return result; + return k; } @@ -1416,16 +1343,15 @@ Key Position::compute_key() const { Key Position::compute_pawn_key() const { - Bitboard b; - Key result = 0; + Key k = 0; - for (Color c = WHITE; c <= BLACK; c++) + for (Bitboard b = pieces(PAWN); b; ) { - b = pieces(PAWN, c); - while (b) - result ^= zobrist[c][PAWN][pop_1st_bit(&b)]; + Square s = pop_1st_bit(&b); + k ^= zobrist[color_of(piece_on(s))][PAWN][s]; } - return result; + + return k; } @@ -1437,36 +1363,32 @@ Key Position::compute_pawn_key() const { Key Position::compute_material_key() const { - Key result = 0; + Key k = 0; for (Color c = WHITE; c <= BLACK; c++) for (PieceType pt = PAWN; pt <= QUEEN; pt++) - for (int i = 0; i < piece_count(c, pt); i++) - result ^= zobrist[c][pt][i]; + for (int cnt = 0; cnt < piece_count(c, pt); cnt++) + k ^= zobrist[c][pt][cnt]; - return result; + return k; } -/// Position::compute_value() compute the incremental scores for the middle +/// Position::compute_psq_score() computes the incremental scores for the middle /// game and the endgame. These functions are used to initialize the incremental /// scores when a new position is set up, and to verify that the scores are correctly /// updated by do_move and undo_move when the program is running in debug mode. -Score Position::compute_value() const { +Score Position::compute_psq_score() const { - Bitboard b; - Score result = SCORE_ZERO; + Score score = SCORE_ZERO; - for (Color c = WHITE; c <= BLACK; c++) - for (PieceType pt = PAWN; pt <= KING; pt++) - { - b = pieces(pt, c); - while (b) - result += pst(make_piece(c, pt), pop_1st_bit(&b)); - } + for (Bitboard b = pieces(); b; ) + { + Square s = pop_1st_bit(&b); + score += pieceSquareTable[piece_on(s)][s]; + } - result += (sideToMove == WHITE ? TempoValue / 2 : -TempoValue / 2); - return result; + return score; } @@ -1477,12 +1399,12 @@ Score Position::compute_value() const { Value Position::compute_non_pawn_material(Color c) const { - Value result = VALUE_ZERO; + Value value = VALUE_ZERO; for (PieceType pt = KNIGHT; pt <= QUEEN; pt++) - result += piece_count(c, pt) * PieceValueMidgame[pt]; + value += piece_count(c, pt) * PieceValueMidgame[pt]; - return result; + return value; } @@ -1545,73 +1467,72 @@ void Position::init() { for (Square s = SQ_A1; s <= SQ_H8; s++) zobrist[c][pt][s] = rk.rand(); - for (Square s = SQ_A1; s <= SQ_H8; s++) - zobEp[s] = rk.rand(); + for (File f = FILE_A; f <= FILE_H; f++) + zobEp[f] = rk.rand(); - for (int i = 0; i < 16; i++) - zobCastle[i] = rk.rand(); + for (int cr = CASTLES_NONE; cr <= ALL_CASTLES; cr++) + { + Bitboard b = cr; + while (b) + { + Key k = zobCastle[1ULL << pop_1st_bit(&b)]; + zobCastle[cr] ^= k ? k : rk.rand(); + } + } zobSideToMove = rk.rand(); zobExclusion = rk.rand(); - for (Piece p = W_PAWN; p <= W_KING; p++) + for (PieceType pt = PAWN; pt <= KING; pt++) { - Score ps = make_score(PieceValueMidgame[p], PieceValueEndgame[p]); + Score v = make_score(PieceValueMidgame[pt], PieceValueEndgame[pt]); for (Square s = SQ_A1; s <= SQ_H8; s++) { - pieceSquareTable[p][s] = ps + PSQT[p][s]; - pieceSquareTable[p+8][~s] = -pieceSquareTable[p][s]; + pieceSquareTable[make_piece(WHITE, pt)][ s] = (v + PSQT[pt][s]); + pieceSquareTable[make_piece(BLACK, pt)][~s] = -(v + PSQT[pt][s]); } } } -/// Position::flip_me() flips position with the white and black sides reversed. This +/// Position::flip() flips position with the white and black sides reversed. This /// is only useful for debugging especially for finding evaluation symmetry bugs. -void Position::flip_me() { +void Position::flip() { - // Make a copy of current position before to start changing - const Position pos(*this, threadID); + const Position pos(*this); clear(); - threadID = pos.thread(); - // Board + sideToMove = ~pos.side_to_move(); + thisThread = pos.this_thread(); + nodes = pos.nodes_searched(); + chess960 = pos.is_chess960(); + startPosPly = pos.startpos_ply_counter(); + for (Square s = SQ_A1; s <= SQ_H8; s++) - if (!pos.square_is_empty(s)) + if (!pos.is_empty(s)) put_piece(Piece(pos.piece_on(s) ^ 8), ~s); - // Side to move - sideToMove = ~pos.side_to_move(); - - // Castling rights if (pos.can_castle(WHITE_OO)) - set_castle_right(BLACK, ~pos.castle_rook_square(WHITE_OO)); + set_castle_right(BLACK, ~pos.castle_rook_square(WHITE, KING_SIDE)); if (pos.can_castle(WHITE_OOO)) - set_castle_right(BLACK, ~pos.castle_rook_square(WHITE_OOO)); + set_castle_right(BLACK, ~pos.castle_rook_square(WHITE, QUEEN_SIDE)); if (pos.can_castle(BLACK_OO)) - set_castle_right(WHITE, ~pos.castle_rook_square(BLACK_OO)); + set_castle_right(WHITE, ~pos.castle_rook_square(BLACK, KING_SIDE)); if (pos.can_castle(BLACK_OOO)) - set_castle_right(WHITE, ~pos.castle_rook_square(BLACK_OOO)); + set_castle_right(WHITE, ~pos.castle_rook_square(BLACK, QUEEN_SIDE)); - // En passant square if (pos.st->epSquare != SQ_NONE) st->epSquare = ~pos.st->epSquare; - // Checkers st->checkersBB = attackers_to(king_square(sideToMove)) & pieces(~sideToMove); - // Hash keys st->key = compute_key(); st->pawnKey = compute_pawn_key(); st->materialKey = compute_material_key(); - - // Incremental scores - st->value = compute_value(); - - // Material + st->psqScore = compute_psq_score(); st->npMaterial[WHITE] = compute_non_pawn_material(WHITE); st->npMaterial[BLACK] = compute_non_pawn_material(BLACK); @@ -1624,42 +1545,39 @@ void Position::flip_me() { bool Position::pos_is_ok(int* failedStep) const { + int dummy, *step = failedStep ? failedStep : &dummy; + // What features of the position should be verified? - const bool debugAll = false; + const bool all = false; - const bool debugBitboards = debugAll || false; - const bool debugKingCount = debugAll || false; - const bool debugKingCapture = debugAll || false; - const bool debugCheckerCount = debugAll || false; - const bool debugKey = debugAll || false; - const bool debugMaterialKey = debugAll || false; - const bool debugPawnKey = debugAll || false; - const bool debugIncrementalEval = debugAll || false; - const bool debugNonPawnMaterial = debugAll || false; - const bool debugPieceCounts = debugAll || false; - const bool debugPieceList = debugAll || false; - const bool debugCastleSquares = debugAll || false; + const bool debugBitboards = all || false; + const bool debugKingCount = all || false; + const bool debugKingCapture = all || false; + const bool debugCheckerCount = all || false; + const bool debugKey = all || false; + const bool debugMaterialKey = all || false; + const bool debugPawnKey = all || false; + const bool debugIncrementalEval = all || false; + const bool debugNonPawnMaterial = all || false; + const bool debugPieceCounts = all || false; + const bool debugPieceList = all || false; + const bool debugCastleSquares = all || false; - if (failedStep) *failedStep = 1; + *step = 1; - // Side to move OK? if (sideToMove != WHITE && sideToMove != BLACK) return false; - // Are the king squares in the position correct? - if (failedStep) (*failedStep)++; - if (piece_on(king_square(WHITE)) != W_KING) + if ((*step)++, piece_on(king_square(WHITE)) != W_KING) return false; - if (failedStep) (*failedStep)++; - if (piece_on(king_square(BLACK)) != B_KING) + if ((*step)++, piece_on(king_square(BLACK)) != B_KING) return false; - // Do both sides have exactly one king? - if (failedStep) (*failedStep)++; - if (debugKingCount) + if ((*step)++, debugKingCount) { - int kingCount[2] = {0, 0}; + int kingCount[2] = {}; + for (Square s = SQ_A1; s <= SQ_H8; s++) if (type_of(piece_on(s)) == KING) kingCount[color_of(piece_on(s))]++; @@ -1668,33 +1586,22 @@ bool Position::pos_is_ok(int* failedStep) const { return false; } - // Can the side to move capture the opponent's king? - if (failedStep) (*failedStep)++; - if (debugKingCapture) - { - Color us = sideToMove; - Color them = ~us; - Square ksq = king_square(them); - if (attackers_to(ksq) & pieces(us)) + if ((*step)++, debugKingCapture) + if (attackers_to(king_square(~sideToMove)) & pieces(sideToMove)) return false; - } - // Is there more than 2 checkers? - if (failedStep) (*failedStep)++; - if (debugCheckerCount && popcount(st->checkersBB) > 2) + if ((*step)++, debugCheckerCount && popcount(st->checkersBB) > 2) return false; - // Bitboards OK? - if (failedStep) (*failedStep)++; - if (debugBitboards) + if ((*step)++, debugBitboards) { // The intersection of the white and black pieces must be empty - if (!(pieces(WHITE) & pieces(BLACK))) + if (pieces(WHITE) & pieces(BLACK)) return false; // The union of the white and black pieces must be equal to all // occupied squares - if ((pieces(WHITE) | pieces(BLACK)) != occupied_squares()) + if ((pieces(WHITE) | pieces(BLACK)) != pieces()) return false; // Separate piece type bitboards must have empty intersections @@ -1704,57 +1611,35 @@ bool Position::pos_is_ok(int* failedStep) const { return false; } - // En passant square OK? - if (failedStep) (*failedStep)++; - if (ep_square() != SQ_NONE) + if ((*step)++, ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6) + return false; + + if ((*step)++, debugKey && st->key != compute_key()) + return false; + + if ((*step)++, debugPawnKey && st->pawnKey != compute_pawn_key()) + return false; + + if ((*step)++, debugMaterialKey && st->materialKey != compute_material_key()) + return false; + + if ((*step)++, debugIncrementalEval && st->psqScore != compute_psq_score()) + return false; + + if ((*step)++, debugNonPawnMaterial) { - // The en passant square must be on rank 6, from the point of view of the - // side to move. - if (relative_rank(sideToMove, ep_square()) != RANK_6) + if ( st->npMaterial[WHITE] != compute_non_pawn_material(WHITE) + || st->npMaterial[BLACK] != compute_non_pawn_material(BLACK)) return false; } - // Hash key OK? - if (failedStep) (*failedStep)++; - if (debugKey && st->key != compute_key()) - return false; - - // Pawn hash key OK? - if (failedStep) (*failedStep)++; - if (debugPawnKey && st->pawnKey != compute_pawn_key()) - return false; - - // Material hash key OK? - if (failedStep) (*failedStep)++; - if (debugMaterialKey && st->materialKey != compute_material_key()) - return false; - - // Incremental eval OK? - if (failedStep) (*failedStep)++; - if (debugIncrementalEval && st->value != compute_value()) - return false; - - // Non-pawn material OK? - if (failedStep) (*failedStep)++; - if (debugNonPawnMaterial) - { - if (st->npMaterial[WHITE] != compute_non_pawn_material(WHITE)) - return false; - - if (st->npMaterial[BLACK] != compute_non_pawn_material(BLACK)) - return false; - } - - // Piece counts OK? - if (failedStep) (*failedStep)++; - if (debugPieceCounts) + if ((*step)++, debugPieceCounts) for (Color c = WHITE; c <= BLACK; c++) for (PieceType pt = PAWN; pt <= KING; pt++) - if (pieceCount[c][pt] != popcount(pieces(pt, c))) + if (pieceCount[c][pt] != popcount(pieces(c, pt))) return false; - if (failedStep) (*failedStep)++; - if (debugPieceList) + if ((*step)++, debugPieceList) for (Color c = WHITE; c <= BLACK; c++) for (PieceType pt = PAWN; pt <= KING; pt++) for (int i = 0; i < pieceCount[c][pt]; i++) @@ -1766,20 +1651,23 @@ bool Position::pos_is_ok(int* failedStep) const { return false; } - if (failedStep) (*failedStep)++; - if (debugCastleSquares) - for (CastleRight f = WHITE_OO; f <= BLACK_OOO; f = CastleRight(f << 1)) - { - if (!can_castle(f)) - continue; + if ((*step)++, debugCastleSquares) + for (Color c = WHITE; c <= BLACK; c++) + for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) + { + CastleRight cr = make_castle_right(c, s); - Piece rook = (f & (WHITE_OO | WHITE_OOO) ? W_ROOK : B_ROOK); + if (!can_castle(cr)) + continue; - if ( castleRightsMask[castleRookSquare[f]] != (ALL_CASTLES ^ f) - || piece_on(castleRookSquare[f]) != rook) - return false; - } + if ((castleRightsMask[king_square(c)] & cr) != cr) + return false; - if (failedStep) *failedStep = 0; + if ( piece_on(castleRookSquare[c][s]) != make_piece(c, ROOK) + || castleRightsMask[castleRookSquare[c][s]] != cr) + return false; + } + + *step = 0; return true; } diff --git a/DroidFish/jni/stockfish/position.h b/DroidFish/jni/stockfish/position.h index c391d82..55dc33f 100644 --- a/DroidFish/jni/stockfish/position.h +++ b/DroidFish/jni/stockfish/position.h @@ -29,6 +29,7 @@ /// The checkInfo struct is initialized at c'tor time and keeps info used /// to detect if a move gives check. class Position; +class Thread; struct CheckInfo { @@ -50,7 +51,7 @@ struct StateInfo { Key pawnKey, materialKey; Value npMaterial[2]; int castleRights, rule50, pliesFromNull; - Score value; + Score psqScore; Square epSquare; Key key; @@ -59,6 +60,14 @@ struct StateInfo { StateInfo* previous; }; +struct ReducedStateInfo { + Key pawnKey, materialKey; + Value npMaterial[2]; + int castleRights, rule50, pliesFromNull; + Score psqScore; + Square epSquare; +}; + /// The position data structure. A position consists of the following data: /// @@ -83,65 +92,45 @@ struct StateInfo { /// * A counter for detecting 50 move rule draws. class Position { - - // No copy c'tor or assignment operator allowed - Position(const Position&); - Position& operator=(const Position&); - public: Position() {} - Position(const Position& pos, int th) { copy(pos, th); } - Position(const std::string& fen, bool isChess960, int th); + Position(const Position& p) { *this = p; } + Position(const Position& p, Thread* t) { *this = p; thisThread = t; } + Position(const std::string& f, bool c960, Thread* t) { from_fen(f, c960, t); } + void operator=(const Position&); // Text input/output - void copy(const Position& pos, int th); - void from_fen(const std::string& fen, bool isChess960); + void from_fen(const std::string& fen, bool isChess960, Thread* th); const std::string to_fen() const; void print(Move m = MOVE_NONE) const; - // The piece on a given square - Piece piece_on(Square s) const; - Piece piece_moved(Move m) const; - bool square_is_empty(Square s) const; - - // Side to move - Color side_to_move() const; - - // Bitboard representation of the position - Bitboard empty_squares() const; - Bitboard occupied_squares() const; - Bitboard pieces(Color c) const; + // Position representation + Bitboard pieces() const; Bitboard pieces(PieceType pt) const; - Bitboard pieces(PieceType pt, Color c) const; Bitboard pieces(PieceType pt1, PieceType pt2) const; - Bitboard pieces(PieceType pt1, PieceType pt2, Color c) const; - - // Number of pieces of each color and type + Bitboard pieces(Color c) const; + Bitboard pieces(Color c, PieceType pt) const; + Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; + Piece piece_on(Square s) const; + Square king_square(Color c) const; + Square ep_square() const; + bool is_empty(Square s) const; + const Square* piece_list(Color c, PieceType pt) const; int piece_count(Color c, PieceType pt) const; - // The en passant square - Square ep_square() const; + // Castling + int can_castle(CastleRight f) const; + int can_castle(Color c) const; + bool castle_impeded(Color c, CastlingSide s) const; + Square castle_rook_square(Color c, CastlingSide s) const; - // Current king position for each color - Square king_square(Color c) const; - - // Castling rights - bool can_castle(CastleRight f) const; - bool can_castle(Color c) const; - Square castle_rook_square(CastleRight f) const; - - // Bitboards for pinned pieces and discovered check candidates + // Checking + bool in_check() const; + Bitboard checkers() const; Bitboard discovered_check_candidates() const; Bitboard pinned_pieces() const; - // Checking pieces and under check information - Bitboard checkers() const; - bool in_check() const; - - // Piece lists - const Square* piece_list(Color c, PieceType pt) const; - - // Information about attacks to or from a given square + // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occ) const; Bitboard attacks_from(Piece p, Square s) const; @@ -157,12 +146,14 @@ public: bool is_capture(Move m) const; bool is_capture_or_promotion(Move m) const; bool is_passed_pawn_push(Move m) const; - - // Piece captured with previous moves + Piece piece_moved(Move m) const; PieceType captured_piece_type() const; - // Information about pawns + // Piece specific bool pawn_is_passed(Color c, Square s) const; + bool pawn_on_7th(Color c) const; + bool opposite_bishops() const; + bool bishop_pair(Color c) const; // Doing and undoing moves void do_move(Move m, StateInfo& st); @@ -180,37 +171,32 @@ public: Key pawn_key() const; Key material_key() const; - // Incremental evaluation - Score value() const; + // Incremental piece-square evaluation + Score psq_score() const; + Score psq_delta(Piece p, Square from, Square to) const; Value non_pawn_material(Color c) const; - Score pst_delta(Piece piece, Square from, Square to) const; // Other properties of the position - template bool is_draw() const; + Color side_to_move() const; int startpos_ply_counter() const; - bool opposite_colored_bishops() const; - bool has_pawn_on_7th(Color c) const; bool is_chess960() const; - - // Current thread ID searching on the position - int thread() const; - + Thread* this_thread() const; int64_t nodes_searched() const; void set_nodes_searched(int64_t n); + template bool is_draw() const; // Position consistency check, for debugging bool pos_is_ok(int* failedStep = NULL) const; - void flip_me(); + void flip(); // Global initialization static void init(); private: - - // Initialization helper functions (used while setting up a position) + // Initialization helpers (used while setting up a position) void clear(); void put_piece(Piece p, Square s); - void set_castle_right(Color c, Square rsq); + void set_castle_right(Color c, Square rfrom); bool move_is_legal(const Move m) const; // Helper template functions @@ -223,40 +209,33 @@ private: Key compute_material_key() const; // Computing incremental evaluation scores and material counts - Score pst(Piece p, Square s) const; - Score compute_value() const; + Score compute_psq_score() const; Value compute_non_pawn_material(Color c) const; - // Board + // Board and pieces Piece board[64]; // [square] - - // Bitboards Bitboard byTypeBB[8]; // [pieceType] Bitboard byColorBB[2]; // [color] - Bitboard occupied; - - // Piece counts int pieceCount[2][8]; // [color][pieceType] - - // Piece lists Square pieceList[2][8][16]; // [color][pieceType][index] int index[64]; // [square] // Other info - int castleRightsMask[64]; // [square] - Square castleRookSquare[16]; // [castleRight] + int castleRightsMask[64]; // [square] + Square castleRookSquare[2][2]; // [color][side] + Bitboard castlePath[2][2]; // [color][side] StateInfo startState; int64_t nodes; int startPosPly; Color sideToMove; - int threadID; + Thread* thisThread; StateInfo* st; int chess960; // Static variables static Score pieceSquareTable[16][64]; // [piece][square] static Key zobrist[2][8][64]; // [color][pieceType][square]/[piece count] - static Key zobEp[64]; // [square] + static Key zobEp[8]; // [file] static Key zobCastle[16]; // [castleRight] static Key zobSideToMove; static Key zobExclusion; @@ -278,7 +257,7 @@ inline Piece Position::piece_moved(Move m) const { return board[from_sq(m)]; } -inline bool Position::square_is_empty(Square s) const { +inline bool Position::is_empty(Square s) const { return board[s] == NO_PIECE; } @@ -286,32 +265,28 @@ inline Color Position::side_to_move() const { return sideToMove; } -inline Bitboard Position::occupied_squares() const { - return occupied; -} - -inline Bitboard Position::empty_squares() const { - return ~occupied; -} - -inline Bitboard Position::pieces(Color c) const { - return byColorBB[c]; +inline Bitboard Position::pieces() const { + return byTypeBB[ALL_PIECES]; } inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } -inline Bitboard Position::pieces(PieceType pt, Color c) const { - return byTypeBB[pt] & byColorBB[c]; -} - inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { return byTypeBB[pt1] | byTypeBB[pt2]; } -inline Bitboard Position::pieces(PieceType pt1, PieceType pt2, Color c) const { - return (byTypeBB[pt1] | byTypeBB[pt2]) & byColorBB[c]; +inline Bitboard Position::pieces(Color c) const { + return byColorBB[c]; +} + +inline Bitboard Position::pieces(Color c, PieceType pt) const { + return byColorBB[c] & byTypeBB[pt]; +} + +inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const { + return byColorBB[c] & (byTypeBB[pt1] | byTypeBB[pt2]); } inline int Position::piece_count(Color c, PieceType pt) const { @@ -330,16 +305,28 @@ inline Square Position::king_square(Color c) const { return pieceList[c][KING][0]; } -inline bool Position::can_castle(CastleRight f) const { +inline int Position::can_castle(CastleRight f) const { return st->castleRights & f; } -inline bool Position::can_castle(Color c) const { - return st->castleRights & ((WHITE_OO | WHITE_OOO) << c); +inline int Position::can_castle(Color c) const { + return st->castleRights & ((WHITE_OO | WHITE_OOO) << (2 * c)); } -inline Square Position::castle_rook_square(CastleRight f) const { - return castleRookSquare[f]; +inline bool Position::castle_impeded(Color c, CastlingSide s) const { + return byTypeBB[ALL_PIECES] & castlePath[c][s]; +} + +inline Square Position::castle_rook_square(Color c, CastlingSide s) const { + return castleRookSquare[c][s]; +} + +template +inline Bitboard Position::attacks_from(Square s) const { + + return Pt == BISHOP || Pt == ROOK ? attacks_bb(s, pieces()) + : Pt == QUEEN ? attacks_from(s) | attacks_from(s) + : StepAttacksBB[Pt][s]; } template<> @@ -347,32 +334,12 @@ inline Bitboard Position::attacks_from(Square s, Color c) const { return StepAttacksBB[make_piece(c, PAWN)][s]; } -template // Knight and King and white pawns -inline Bitboard Position::attacks_from(Square s) const { - return StepAttacksBB[Piece][s]; -} - -template<> -inline Bitboard Position::attacks_from(Square s) const { - return bishop_attacks_bb(s, occupied_squares()); -} - -template<> -inline Bitboard Position::attacks_from(Square s) const { - return rook_attacks_bb(s, occupied_squares()); -} - -template<> -inline Bitboard Position::attacks_from(Square s) const { - return attacks_from(s) | attacks_from(s); -} - inline Bitboard Position::attacks_from(Piece p, Square s) const { - return attacks_from(p, s, occupied_squares()); + return attacks_from(p, s, byTypeBB[ALL_PIECES]); } inline Bitboard Position::attackers_to(Square s) const { - return attackers_to(s, occupied_squares()); + return attackers_to(s, byTypeBB[ALL_PIECES]); } inline Bitboard Position::checkers() const { @@ -392,7 +359,7 @@ inline Bitboard Position::pinned_pieces() const { } inline bool Position::pawn_is_passed(Color c, Square s) const { - return !(pieces(PAWN, ~c) & passed_pawn_mask(c, s)); + return !(pieces(~c, PAWN) & passed_pawn_mask(c, s)); } inline Key Position::key() const { @@ -411,16 +378,12 @@ inline Key Position::material_key() const { return st->materialKey; } -inline Score Position::pst(Piece p, Square s) const { - return pieceSquareTable[p][s]; +inline Score Position::psq_delta(Piece p, Square from, Square to) const { + return pieceSquareTable[p][to] - pieceSquareTable[p][from]; } -inline Score Position::pst_delta(Piece piece, Square from, Square to) const { - return pieceSquareTable[piece][to] - pieceSquareTable[piece][from]; -} - -inline Score Position::value() const { - return st->value; +inline Score Position::psq_score() const { + return st->psqScore; } inline Value Position::non_pawn_material(Color c) const { @@ -429,7 +392,7 @@ inline Value Position::non_pawn_material(Color c) const { inline bool Position::is_passed_pawn_push(Move m) const { - return board[from_sq(m)] == make_piece(sideToMove, PAWN) + return type_of(piece_moved(m)) == PAWN && pawn_is_passed(sideToMove, to_sq(m)); } @@ -437,15 +400,21 @@ inline int Position::startpos_ply_counter() const { return startPosPly + st->pliesFromNull; // HACK } -inline bool Position::opposite_colored_bishops() const { +inline bool Position::opposite_bishops() const { return pieceCount[WHITE][BISHOP] == 1 && pieceCount[BLACK][BISHOP] == 1 && opposite_colors(pieceList[WHITE][BISHOP][0], pieceList[BLACK][BISHOP][0]); } -inline bool Position::has_pawn_on_7th(Color c) const { - return pieces(PAWN, c) & rank_bb(relative_rank(c, RANK_7)); +inline bool Position::bishop_pair(Color c) const { + + return pieceCount[c][BISHOP] >= 2 + && opposite_colors(pieceList[c][BISHOP][0], pieceList[c][BISHOP][1]); +} + +inline bool Position::pawn_on_7th(Color c) const { + return pieces(c, PAWN) & rank_bb(relative_rank(c, RANK_7)); } inline bool Position::is_chess960() const { @@ -455,22 +424,22 @@ inline bool Position::is_chess960() const { inline bool Position::is_capture_or_promotion(Move m) const { assert(is_ok(m)); - return is_special(m) ? !is_castle(m) : !square_is_empty(to_sq(m)); + return is_special(m) ? !is_castle(m) : !is_empty(to_sq(m)); } inline bool Position::is_capture(Move m) const { // Note that castle is coded as "king captures the rook" assert(is_ok(m)); - return (!square_is_empty(to_sq(m)) && !is_castle(m)) || is_enpassant(m); + return (!is_empty(to_sq(m)) && !is_castle(m)) || is_enpassant(m); } inline PieceType Position::captured_piece_type() const { return st->capturedType; } -inline int Position::thread() const { - return threadID; +inline Thread* Position::this_thread() const { + return thisThread; } #endif // !defined(POSITION_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/search.cpp b/DroidFish/jni/stockfish/search.cpp index 1006541..93014bd 100644 --- a/DroidFish/jni/stockfish/search.cpp +++ b/DroidFish/jni/stockfish/search.cpp @@ -28,7 +28,6 @@ #include "book.h" #include "evaluate.h" #include "history.h" -#include "misc.h" #include "movegen.h" #include "movepick.h" #include "search.h" @@ -43,14 +42,20 @@ namespace Search { LimitsType Limits; std::vector RootMoves; Position RootPosition; + Time SearchTime; } using std::string; using std::cout; using std::endl; -using namespace std; +using Eval::evaluate; using namespace Search; +// For some reason argument-dependent lookup (ADL) doesn't work for Android's +// STLPort, so explicitly qualify following functions. +using std::count; +using std::find; + namespace { // Set to true to force running with one thread. Used for debugging @@ -67,7 +72,7 @@ namespace { const Depth RazorDepth = 4 * ONE_PLY; // Dynamic razoring margin based on depth - inline Value razor_margin(Depth d) { return Value(0x200 + 0x10 * int(d)); } + inline Value razor_margin(Depth d) { return Value(512 + 16 * int(d)); } // Maximum depth for use of dynamic threat detection when null move fails low const Depth ThreatDepth = 5 * ONE_PLY; @@ -77,13 +82,13 @@ namespace { // At Non-PV nodes we do an internal iterative deepening search // when the static evaluation is bigger then beta - IIDMargin. - const Value IIDMargin = Value(0x100); + const Value IIDMargin = Value(256); // Minimum depth for use of singular extension const Depth SingularExtensionDepth[] = { 8 * ONE_PLY, 6 * ONE_PLY }; // Futility margin for quiescence search - const Value FutilityMarginQS = Value(0x80); + const Value FutilityMarginQS = Value(128); // Futility lookup tables (initialized at startup) and their access functions Value FutilityMargins[16][64]; // [depth][moveNumber] @@ -131,15 +136,14 @@ namespace { Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth); void id_loop(Position& pos); - bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta, Value *bValue); + bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta); bool connected_moves(const Position& pos, Move m1, Move m2); Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); - bool can_return_tt(const TTEntry* tte, Depth depth, Value beta, int ply); + bool can_return_tt(const TTEntry* tte, Depth depth, Value ttValue, Value beta); bool connected_threat(const Position& pos, Move m, Move threat); - Value refine_eval(const TTEntry* tte, Value defaultEval, int ply); + Value refine_eval(const TTEntry* tte, Value ttValue, Value defaultEval); Move do_skill_level(); - int elapsed_time(bool reset = false); string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE); void pv_info_to_log(Position& pos, int depth, Value score, int time, Move pv[]); void pv_info_to_uci(const Position& pos, int depth, Value alpha, Value beta); @@ -177,11 +181,11 @@ namespace { } // Test for a capture that triggers a pawn endgame - if ( captureOrPromotion - && type_of(pos.piece_on(to_sq(m))) != PAWN + if ( captureOrPromotion + && type_of(pos.piece_on(to_sq(m))) != PAWN + && !is_special(m) && ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) - - PieceValueMidgame[pos.piece_on(to_sq(m))] == VALUE_ZERO) - && !is_special(m)) + - PieceValueMidgame[pos.piece_on(to_sq(m))] == VALUE_ZERO)) return true; return false; @@ -250,11 +254,10 @@ void Search::think() { static Book book; // Defined static to initialize the PRNG only once - Move bm; Position& pos = RootPosition; Chess960 = pos.is_chess960(); - elapsed_time(true); - TimeMgr.init(Limits, pos.startpos_ply_counter()); + Eval::RootColor = pos.side_to_move(); + TimeMgr.init(Limits, pos.startpos_ply_counter(), pos.side_to_move()); TT.new_search(); H.clear(); @@ -267,23 +270,15 @@ void Search::think() { goto finalize; } - if ( Options["OwnBook"] - && (bm = book.probe(pos, Options["Book File"], Options["Best Book Move"])) != MOVE_NONE - && count(RootMoves.begin(), RootMoves.end(), bm)) + if (Options["OwnBook"] && !Limits.infinite) { - std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), bm)); - goto finalize; - } + Move bookMove = book.probe(pos, Options["Book File"], Options["Best Book Move"]); - // Read UCI options: GUI could change UCI parameters during the game - read_evaluation_uci_options(pos.side_to_move()); - Threads.read_uci_options(); - - TT.set_size(Options["Hash"]); - if (Options["Clear Hash"]) - { - Options["Clear Hash"] = false; - TT.clear(); + if (bookMove && count(RootMoves.begin(), RootMoves.end(), bookMove)) + { + std::swap(RootMoves[0], *find(RootMoves.begin(), RootMoves.end(), bookMove)); + goto finalize; + } } UCIMultiPV = Options["MultiPV"]; @@ -300,17 +295,13 @@ void Search::think() { log << "\nSearching: " << pos.to_fen() << "\ninfinite: " << Limits.infinite << " ponder: " << Limits.ponder - << " time: " << Limits.time - << " increment: " << Limits.increment - << " moves to go: " << Limits.movesToGo + << " time: " << Limits.time[pos.side_to_move()] + << " increment: " << Limits.inc[pos.side_to_move()] + << " moves to go: " << Limits.movestogo << endl; } - for (int i = 0; i < Threads.size(); i++) - { - Threads[i].maxPly = 0; - Threads[i].wake_up(); - } + Threads.wake_up(); // Set best timer interval to avoid lagging under time pressure. Timer is // used to check for remaining available thinking time. @@ -322,13 +313,12 @@ void Search::think() { // We're ready to start searching. Call the iterative deepening loop function id_loop(pos); - // Stop timer and send all the slaves to sleep, if not already sleeping - Threads.set_timer(0); - Threads.set_size(1); + Threads.set_timer(0); // Stop timer + Threads.sleep(); if (Options["Use Search Log"]) { - int e = elapsed_time(); + int e = SearchTime.elapsed(); Log log(Options["Search Log Filename"]); log << "Nodes: " << pos.nodes_searched() @@ -347,7 +337,7 @@ finalize: // but if we are pondering or in infinite search, we shouldn't print the best // move before we are told to do so. if (!Signals.stop && (Limits.ponder || Limits.infinite)) - Threads.wait_for_stop_or_ponderhit(); + pos.this_thread()->wait_for_stop_or_ponderhit(); // Best move could be MOVE_NONE when searching on a stalemate position cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], Chess960) @@ -375,7 +365,7 @@ namespace { ss->currentMove = MOVE_NULL; // Hack to skip update gains // Iterative deepening loop until requested to stop or target depth reached - while (!Signals.stop && ++depth <= MAX_PLY && (!Limits.maxDepth || depth <= Limits.maxDepth)) + while (!Signals.stop && ++depth <= MAX_PLY && (!Limits.depth || depth <= Limits.depth)) { // Save last iteration's scores before first PV line is searched and all // the move scores but the (new) PV are set to -VALUE_INFINITE. @@ -436,7 +426,7 @@ namespace { // Send full PV info to GUI if we are going to leave the loop or // if we have a fail high/low and we are deep in the search. - if ((bestValue > alpha && bestValue < beta) || elapsed_time() > 2000) + if ((bestValue > alpha && bestValue < beta) || SearchTime.elapsed() > 2000) pv_info_to_uci(pos, depth, alpha, beta); // In case of failing high/low increase aspiration window and @@ -466,8 +456,8 @@ namespace { if (SkillLevelEnabled && depth == 1 + SkillLevel) skillBest = do_skill_level(); - if (Options["Use Search Log"]) - pv_info_to_log(pos, depth, bestValue, elapsed_time(), &RootMoves[0].pv[0]); + if (!Signals.stop && Options["Use Search Log"]) + pv_info_to_log(pos, depth, bestValue, SearchTime.elapsed(), &RootMoves[0].pv[0]); // Filter out startup noise when monitoring best move stability if (depth > 2 && BestMoveChanges) @@ -485,14 +475,14 @@ namespace { // Stop search if most of available time is already consumed. We // probably don't have enough time to search the first move at the // next iteration anyway. - if (elapsed_time() > (TimeMgr.available_time() * 62) / 100) + if (SearchTime.elapsed() > (TimeMgr.available_time() * 62) / 100) stop = true; // Stop search early if one move seems to be much better than others if ( depth >= 12 && !stop && ( (bestMoveNeverChanged && pos.captured_piece_type()) - || elapsed_time() > (TimeMgr.available_time() * 40) / 100)) + || SearchTime.elapsed() > (TimeMgr.available_time() * 40) / 100)) { Value rBeta = bestValue - EasyMoveMargin; (ss+1)->excludedMove = RootMoves[0].pv[0]; @@ -543,23 +533,22 @@ namespace { const bool RootNode = (NT == Root || NT == SplitPointRoot); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); - assert(PvNode == (alpha != beta - 1)); + assert((alpha == beta - 1) || PvNode); assert(depth > DEPTH_ZERO); - assert(pos.thread() >= 0 && pos.thread() < Threads.size()); - Move movesSearched[MAX_MOVES]; + Move movesSearched[64]; StateInfo st; const TTEntry *tte; Key posKey; - Move ttMove, move, excludedMove, threatMove; + Move ttMove, move, excludedMove, bestMove, threatMove; Depth ext, newDepth; - ValueType vt; - Value bestValue, value, oldAlpha; + Bound bt; + Value bestValue, value, oldAlpha, ttValue; Value refinedValue, nullValue, futilityBase, futilityValue; bool isPvMove, inCheck, singularExtensionNode, givesCheck; bool captureOrPromotion, dangerous, doFullDepthSearch; int moveCount = 0, playedMoveCount = 0; - Thread& thread = Threads[pos.thread()]; + Thread* thisThread = pos.this_thread(); SplitPoint* sp = NULL; refinedValue = bestValue = value = -VALUE_INFINITE; @@ -568,26 +557,38 @@ namespace { ss->ply = (ss-1)->ply + 1; // Used to send selDepth info to GUI - if (PvNode && thread.maxPly < ss->ply) - thread.maxPly = ss->ply; + if (PvNode && thisThread->maxPly < ss->ply) + thisThread->maxPly = ss->ply; // Step 1. Initialize node - if (!SpNode) + if (SpNode) { - ss->currentMove = ss->bestMove = threatMove = (ss+1)->excludedMove = MOVE_NONE; - (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; - (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + tte = NULL; + ttMove = excludedMove = MOVE_NONE; + ttValue = VALUE_ZERO; + sp = ss->sp; + bestMove = sp->bestMove; + threatMove = sp->threatMove; + bestValue = sp->bestValue; + moveCount = sp->moveCount; // Lock must be held here + + assert(bestValue > -VALUE_INFINITE && moveCount > 0); + + goto split_point_start; } else { - sp = ss->sp; - tte = NULL; - ttMove = excludedMove = MOVE_NONE; - threatMove = sp->threatMove; - goto split_point_start; + ss->currentMove = threatMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; + (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; + (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + } // Step 2. Check for aborted search and immediate draw + // Enforce node limit here. FIXME: This only works with 1 search thread. + if (Limits.nodes && pos.nodes_searched() >= Limits.nodes) + Signals.stop = true; + if (( Signals.stop || pos.is_draw() || ss->ply > MAX_PLY) && !RootNode) @@ -614,27 +615,27 @@ namespace { posKey = excludedMove ? pos.exclusion_key() : pos.key(); tte = TT.probe(posKey); ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tte ? tte->move() : MOVE_NONE; + ttValue = tte ? value_from_tt(tte->value(), ss->ply) : VALUE_ZERO; // At PV nodes we check for exact scores, while at non-PV nodes we check for // a fail high/low. Biggest advantage at probing at PV nodes is to have a // smooth experience in analysis mode. We don't probe at Root nodes otherwise // we should also update RootMoveList to avoid bogus output. - if (!RootNode && tte && (PvNode ? tte->depth() >= depth && tte->type() == VALUE_TYPE_EXACT - : can_return_tt(tte, depth, beta, ss->ply))) + if (!RootNode && tte && (PvNode ? tte->depth() >= depth && tte->type() == BOUND_EXACT + : can_return_tt(tte, depth, ttValue, beta))) { TT.refresh(tte); - ss->bestMove = move = ttMove; // Can be MOVE_NONE - value = value_from_tt(tte->value(), ss->ply); + ss->currentMove = ttMove; // Can be MOVE_NONE - if ( value >= beta - && move - && !pos.is_capture_or_promotion(move) - && move != ss->killers[0]) + if ( ttValue >= beta + && ttMove + && !pos.is_capture_or_promotion(ttMove) + && ttMove != ss->killers[0]) { ss->killers[1] = ss->killers[0]; - ss->killers[0] = move; + ss->killers[0] = ttMove; } - return value; + return ttValue; } // Step 5. Evaluate the position statically and update parent's gain statistics @@ -646,19 +647,19 @@ namespace { ss->eval = tte->static_value(); ss->evalMargin = tte->static_value_margin(); - refinedValue = refine_eval(tte, ss->eval, ss->ply); + refinedValue = refine_eval(tte, ttValue, ss->eval); } else { refinedValue = ss->eval = evaluate(pos, ss->evalMargin); - TT.store(posKey, VALUE_NONE, VALUE_TYPE_NONE, DEPTH_NONE, MOVE_NONE, ss->eval, ss->evalMargin); + TT.store(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, ss->eval, ss->evalMargin); } // Update gain for the parent non-capture move given the static position // evaluation before and after the move. - if ( (move = (ss-1)->currentMove) != MOVE_NULL - && (ss-1)->eval != VALUE_NONE - && ss->eval != VALUE_NONE + if ( (move = (ss-1)->currentMove) != MOVE_NULL + && (ss-1)->eval != VALUE_NONE + && ss->eval != VALUE_NONE && !pos.captured_piece_type() && !is_special(move)) { @@ -673,7 +674,7 @@ namespace { && refinedValue + razor_margin(depth) < beta && ttMove == MOVE_NONE && abs(beta) < VALUE_MATE_IN_MAX_PLY - && !pos.has_pawn_on_7th(pos.side_to_move())) + && !pos.pawn_on_7th(pos.side_to_move())) { Value rbeta = beta - razor_margin(depth); Value v = qsearch(pos, ss, rbeta-1, rbeta, DEPTH_ZERO); @@ -745,7 +746,7 @@ namespace { // move which was reduced. If a connection is found, return a fail // low score (which will cause the reduced move to fail high in the // parent node, which will trigger a re-search with full depth). - threatMove = (ss+1)->bestMove; + threatMove = (ss+1)->currentMove; if ( depth < ThreatDepth && (ss-1)->reduction @@ -770,6 +771,8 @@ namespace { Depth rdepth = depth - ONE_PLY - 3 * ONE_PLY; assert(rdepth >= ONE_PLY); + assert((ss-1)->currentMove != MOVE_NONE); + assert((ss-1)->currentMove != MOVE_NULL); MovePicker mp(pos, ttMove, H, pos.captured_piece_type()); CheckInfo ci(pos); @@ -777,6 +780,7 @@ namespace { while ((move = mp.next_move()) != MOVE_NONE) if (pos.pl_move_is_legal(move, ci.pinned)) { + ss->currentMove = move; pos.do_move(move, st, ci, pos.move_gives_check(move, ci)); value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth); pos.undo_move(move); @@ -804,29 +808,20 @@ split_point_start: // At split points actual search starts from here MovePickerExt mp(pos, ttMove, depth, H, ss, PvNode ? -VALUE_INFINITE : beta); CheckInfo ci(pos); - ss->bestMove = MOVE_NONE; futilityBase = ss->eval + ss->evalMargin; singularExtensionNode = !RootNode && !SpNode - && depth >= SingularExtensionDepth[PvNode] - && ttMove != MOVE_NONE + && depth >= SingularExtensionDepth[PvNode] + && ttMove != MOVE_NONE && !excludedMove // Recursive singular search is not allowed - && (tte->type() & VALUE_TYPE_LOWER) - && tte->depth() >= depth - 3 * ONE_PLY; - if (SpNode) - { - lock_grab(&(sp->lock)); - bestValue = sp->bestValue; - moveCount = sp->moveCount; - - assert(bestValue > -VALUE_INFINITE && moveCount > 0); - } + && (tte->type() & BOUND_LOWER) + && tte->depth() >= depth - 3 * ONE_PLY; // Step 11. Loop through moves // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs - while ( bestValue < beta + while ( bestValue < beta && (move = mp.next_move()) != MOVE_NONE - && !thread.cutoff_occurred() + && !thisThread->cutoff_occurred() && !Signals.stop) { assert(is_ok(move)); @@ -847,7 +842,7 @@ split_point_start: // At split points actual search starts from here if (SpNode) { moveCount = ++sp->moveCount; - lock_release(&(sp->lock)); + lock_release(sp->lock); } else moveCount++; @@ -856,7 +851,7 @@ split_point_start: // At split points actual search starts from here { Signals.firstRootMove = (moveCount == 1); - if (pos.thread() == 0 && elapsed_time() > 2000) + if (thisThread == Threads.main_thread() && SearchTime.elapsed() > 2000) cout << "info depth " << depth / ONE_PLY << " currmove " << move_to_uci(move, Chess960) << " currmovenumber " << moveCount + PVIdx << endl; @@ -873,20 +868,18 @@ split_point_start: // At split points actual search starts from here ext = ONE_PLY; else if (givesCheck && pos.see_sign(move) >= 0) - ext = PvNode ? ONE_PLY : ONE_PLY / 2; + ext = ONE_PLY / 2; // Singular extension search. If all moves but one fail low on a search of // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move // is singular and should be extended. To verify this we do a reduced search // on all the other moves but the ttMove, if result is lower than ttValue minus // a margin then we extend ttMove. - if ( singularExtensionNode + if ( singularExtensionNode && !ext - && move == ttMove - && pos.pl_move_is_legal(move, ci.pinned)) + && move == ttMove + && pos.pl_move_is_legal(move, ci.pinned)) { - Value ttValue = value_from_tt(tte->value(), ss->ply); - if (abs(ttValue) < VALUE_KNOWN_WIN) { Value rBeta = ttValue - int(depth); @@ -895,7 +888,6 @@ split_point_start: // At split points actual search starts from here value = search(pos, ss, rBeta - 1, rBeta, depth / 2); ss->skipNullMove = false; ss->excludedMove = MOVE_NONE; - ss->bestMove = MOVE_NONE; if (value < rBeta) ext = ONE_PLY; } @@ -918,7 +910,7 @@ split_point_start: // At split points actual search starts from here && (!threatMove || !connected_threat(pos, move, threatMove))) { if (SpNode) - lock_grab(&(sp->lock)); + lock_grab(sp->lock); continue; } @@ -933,7 +925,7 @@ split_point_start: // At split points actual search starts from here if (futilityValue < beta) { if (SpNode) - lock_grab(&(sp->lock)); + lock_grab(sp->lock); continue; } @@ -943,7 +935,7 @@ split_point_start: // At split points actual search starts from here && pos.see_sign(move) < 0) { if (SpNode) - lock_grab(&(sp->lock)); + lock_grab(sp->lock); continue; } @@ -957,7 +949,7 @@ split_point_start: // At split points actual search starts from here } ss->currentMove = move; - if (!SpNode && !captureOrPromotion) + if (!SpNode && !captureOrPromotion && playedMoveCount < 64) movesSearched[playedMoveCount++] = move; // Step 14. Make the move @@ -965,7 +957,7 @@ split_point_start: // At split points actual search starts from here // Step 15. Reduced depth search (LMR). If the move fails high will be // re-searched at full depth. - if ( depth > 3 * ONE_PLY + if ( depth > 3 * ONE_PLY && !isPvMove && !captureOrPromotion && !dangerous @@ -974,11 +966,10 @@ split_point_start: // At split points actual search starts from here && ss->killers[1] != move) { ss->reduction = reduction(depth, moveCount); - Depth d = newDepth - ss->reduction; + Depth d = std::max(newDepth - ss->reduction, ONE_PLY); alpha = SpNode ? sp->alpha : alpha; - value = d < ONE_PLY ? -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -(alpha+1), -alpha, d); + value = -search(pos, ss+1, -(alpha+1), -alpha, d); doFullDepthSearch = (value > alpha && ss->reduction != DEPTH_ZERO); ss->reduction = DEPTH_ZERO; @@ -1009,12 +1000,12 @@ split_point_start: // At split points actual search starts from here // Step 18. Check for new best move if (SpNode) { - lock_grab(&(sp->lock)); + lock_grab(sp->lock); bestValue = sp->bestValue; alpha = sp->alpha; } - // Finished searching the move. If StopRequest is true, the search + // Finished searching the move. If Signals.stop is true, the search // was aborted because the user interrupted the search or because we // ran out of time. In this case, the return value of the search cannot // be trusted, and we don't update the best move and/or PV. @@ -1045,37 +1036,39 @@ split_point_start: // At split points actual search starts from here if (value > bestValue) { bestValue = value; - ss->bestMove = move; + bestMove = move; if ( PvNode && value > alpha && value < beta) // We want always alpha < beta alpha = value; - if (SpNode && !thread.cutoff_occurred()) + if (SpNode && !thisThread->cutoff_occurred()) { sp->bestValue = value; - sp->ss->bestMove = move; + sp->bestMove = move; sp->alpha = alpha; - sp->is_betaCutoff = (value >= beta); + + if (value >= beta) + sp->cutoff = true; } } // Step 19. Check for split if ( !SpNode - && depth >= Threads.min_split_depth() - && bestValue < beta - && Threads.available_slave_exists(pos.thread()) + && depth >= Threads.min_split_depth() + && bestValue < beta + && Threads.available_slave_exists(thisThread) && !Signals.stop - && !thread.cutoff_occurred()) - bestValue = Threads.split(pos, ss, alpha, beta, bestValue, depth, - threatMove, moveCount, &mp, NT); + && !thisThread->cutoff_occurred()) + bestValue = Threads.split(pos, ss, alpha, beta, bestValue, &bestMove, + depth, threatMove, moveCount, &mp, NT); } // Step 20. Check for mate and stalemate // All legal moves have been searched and if there are no legal moves, it // must be mate or stalemate. Note that we can have a false positive in - // case of StopRequest or thread.cutoff_occurred() are set, but this is + // case of Signals.stop or thread.cutoff_occurred() are set, but this is // harmless because return value is discarded anyhow in the parent nodes. // If we are in a singular extension search then return a fail low score. if (!moveCount) @@ -1086,18 +1079,18 @@ split_point_start: // At split points actual search starts from here { assert(!playedMoveCount); - bestValue = alpha; + bestValue = oldAlpha; } // Step 21. Update tables // Update transposition table entry, killers and history - if (!SpNode && !Signals.stop && !thread.cutoff_occurred()) + if (!SpNode && !Signals.stop && !thisThread->cutoff_occurred()) { - move = bestValue <= oldAlpha ? MOVE_NONE : ss->bestMove; - vt = bestValue <= oldAlpha ? VALUE_TYPE_UPPER - : bestValue >= beta ? VALUE_TYPE_LOWER : VALUE_TYPE_EXACT; + move = bestValue <= oldAlpha ? MOVE_NONE : bestMove; + bt = bestValue <= oldAlpha ? BOUND_UPPER + : bestValue >= beta ? BOUND_LOWER : BOUND_EXACT; - TT.store(posKey, value_to_tt(bestValue, ss->ply), vt, depth, move, ss->eval, ss->evalMargin); + TT.store(posKey, value_to_tt(bestValue, ss->ply), bt, depth, move, ss->eval, ss->evalMargin); // Update killers and history for non capture cut-off moves if ( bestValue >= beta @@ -1123,14 +1116,6 @@ split_point_start: // At split points actual search starts from here } } - if (SpNode) - { - // Here we have the lock still grabbed - sp->is_slave[pos.thread()] = false; - sp->nodes += pos.nodes_searched(); - lock_release(&(sp->lock)); - } - assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; @@ -1148,20 +1133,19 @@ split_point_start: // At split points actual search starts from here assert(NT == PV || NT == NonPV); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); - assert(PvNode == (alpha != beta - 1)); + assert((alpha == beta - 1) || PvNode); assert(depth <= DEPTH_ZERO); - assert(pos.thread() >= 0 && pos.thread() < Threads.size()); StateInfo st; - Move ttMove, move; - Value bestValue, value, evalMargin, futilityValue, futilityBase; + Move ttMove, move, bestMove; + Value ttValue, bestValue, value, evalMargin, futilityValue, futilityBase; bool inCheck, enoughMaterial, givesCheck, evasionPrunable; const TTEntry* tte; Depth ttDepth; - ValueType vt; + Bound bt; Value oldAlpha = alpha; - ss->bestMove = ss->currentMove = MOVE_NONE; + ss->currentMove = bestMove = MOVE_NONE; ss->ply = (ss-1)->ply + 1; // Check for an instant draw or maximum ply reached @@ -1178,11 +1162,12 @@ split_point_start: // At split points actual search starts from here // pruning, but only for move ordering. tte = TT.probe(pos.key()); ttMove = (tte ? tte->move() : MOVE_NONE); + ttValue = tte ? value_from_tt(tte->value(),ss->ply) : VALUE_ZERO; - if (!PvNode && tte && can_return_tt(tte, ttDepth, beta, ss->ply)) + if (!PvNode && tte && can_return_tt(tte, ttDepth, ttValue, beta)) { - ss->bestMove = ttMove; // Can be MOVE_NONE - return value_from_tt(tte->value(), ss->ply); + ss->currentMove = ttMove; // Can be MOVE_NONE + return ttValue; } // Evaluate the position statically @@ -1208,7 +1193,7 @@ split_point_start: // At split points actual search starts from here if (bestValue >= beta) { if (!tte) - TT.store(pos.key(), value_to_tt(bestValue, ss->ply), VALUE_TYPE_LOWER, DEPTH_NONE, MOVE_NONE, ss->eval, evalMargin); + TT.store(pos.key(), value_to_tt(bestValue, ss->ply), BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->eval, evalMargin); return bestValue; } @@ -1265,8 +1250,8 @@ split_point_start: // At split points actual search starts from here // Detect non-capture evasions that are candidate to be pruned evasionPrunable = !PvNode - && inCheck - && bestValue > VALUE_MATED_IN_MAX_PLY + && inCheck + && bestValue > VALUE_MATED_IN_MAX_PLY && !pos.is_capture(move) && !pos.can_castle(pos.side_to_move()); @@ -1285,13 +1270,8 @@ split_point_start: // At split points actual search starts from here && move != ttMove && !pos.is_capture_or_promotion(move) && ss->eval + PawnValueMidgame / 4 < beta - && !check_is_dangerous(pos, move, futilityBase, beta, &bestValue)) - { - if (ss->eval + PawnValueMidgame / 4 > bestValue) - bestValue = ss->eval + PawnValueMidgame / 4; - + && !check_is_dangerous(pos, move, futilityBase, beta)) continue; - } // Check for legality only before to do the move if (!pos.pl_move_is_legal(move, ci.pinned)) @@ -1310,7 +1290,7 @@ split_point_start: // At split points actual search starts from here if (value > bestValue) { bestValue = value; - ss->bestMove = move; + bestMove = move; if ( PvNode && value > alpha @@ -1325,11 +1305,11 @@ split_point_start: // At split points actual search starts from here return mated_in(ss->ply); // Plies to mate from the root // Update transposition table - move = bestValue <= oldAlpha ? MOVE_NONE : ss->bestMove; - vt = bestValue <= oldAlpha ? VALUE_TYPE_UPPER - : bestValue >= beta ? VALUE_TYPE_LOWER : VALUE_TYPE_EXACT; + move = bestValue <= oldAlpha ? MOVE_NONE : bestMove; + bt = bestValue <= oldAlpha ? BOUND_UPPER + : bestValue >= beta ? BOUND_LOWER : BOUND_EXACT; - TT.store(pos.key(), value_to_tt(bestValue, ss->ply), vt, ttDepth, move, ss->eval, evalMargin); + TT.store(pos.key(), value_to_tt(bestValue, ss->ply), bt, ttDepth, move, ss->eval, evalMargin); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1341,55 +1321,43 @@ split_point_start: // At split points actual search starts from here // bestValue is updated only when returning false because in that case move // will be pruned. - bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta, Value *bestValue) + bool check_is_dangerous(Position &pos, Move move, Value futilityBase, Value beta) { Bitboard b, occ, oldAtt, newAtt, kingAtt; - Square from, to, ksq, victimSq; + Square from, to, ksq; Piece pc; Color them; - Value futilityValue, bv = *bestValue; from = from_sq(move); to = to_sq(move); them = ~pos.side_to_move(); ksq = pos.king_square(them); kingAtt = pos.attacks_from(ksq); - pc = pos.piece_on(from); + pc = pos.piece_moved(move); - occ = pos.occupied_squares() & ~(1ULL << from) & ~(1ULL << ksq); + occ = pos.pieces() ^ from ^ ksq; oldAtt = pos.attacks_from(pc, from, occ); newAtt = pos.attacks_from(pc, to, occ); // Rule 1. Checks which give opponent's king at most one escape square are dangerous b = kingAtt & ~pos.pieces(them) & ~newAtt & ~(1ULL << to); - if (!(b && (b & (b - 1)))) + if (!more_than_one(b)) return true; // Rule 2. Queen contact check is very dangerous - if ( type_of(pc) == QUEEN - && bit_is_set(kingAtt, to)) + if (type_of(pc) == QUEEN && (kingAtt & to)) return true; // Rule 3. Creating new double threats with checks b = pos.pieces(them) & newAtt & ~oldAtt & ~(1ULL << ksq); - while (b) { - victimSq = pop_1st_bit(&b); - futilityValue = futilityBase + PieceValueEndgame[pos.piece_on(victimSq)]; - // Note that here we generate illegal "double move"! - if ( futilityValue >= beta - && pos.see_sign(make_move(from, victimSq)) >= 0) + if (futilityBase + PieceValueEndgame[pos.piece_on(pop_1st_bit(&b))] >= beta) return true; - - if (futilityValue > bv) - bv = futilityValue; } - // Update bestValue only if check is not dangerous (because we will prune the move) - *bestValue = bv; return false; } @@ -1423,25 +1391,21 @@ split_point_start: // At split points actual search starts from here // Case 3: Moving through the vacated square p2 = pos.piece_on(f2); - if ( piece_is_slider(p2) - && bit_is_set(squares_between(f2, t2), f1)) + if (piece_is_slider(p2) && (between_bb(f2, t2) & f1)) return true; // Case 4: The destination square for m2 is defended by the moving piece in m1 p1 = pos.piece_on(t1); - if (bit_is_set(pos.attacks_from(p1, t1), t2)) + if (pos.attacks_from(p1, t1) & t2) return true; // Case 5: Discovered check, checking piece is the piece moved in m1 ksq = pos.king_square(pos.side_to_move()); if ( piece_is_slider(p1) - && bit_is_set(squares_between(t1, ksq), f2)) - { - Bitboard occ = pos.occupied_squares(); - clear_bit(&occ, f2); - if (bit_is_set(pos.attacks_from(p1, t1, occ), ksq)) - return true; - } + && (between_bb(t1, ksq) & f2) + && (pos.attacks_from(p1, t1, pos.pieces() ^ f2) & ksq)) + return true; + return false; } @@ -1509,9 +1473,9 @@ split_point_start: // At split points actual search starts from here // Case 3: If the moving piece in the threatened move is a slider, don't // prune safe moves which block its ray. - if ( piece_is_slider(pos.piece_on(tfrom)) - && bit_is_set(squares_between(tfrom, tto), mto) - && pos.see_sign(m) >= 0) + if ( piece_is_slider(pos.piece_on(tfrom)) + && (between_bb(tfrom, tto) & mto) + && pos.see_sign(m) >= 0) return true; return false; @@ -1521,50 +1485,32 @@ split_point_start: // At split points actual search starts from here // can_return_tt() returns true if a transposition table score can be used to // cut-off at a given point in search. - bool can_return_tt(const TTEntry* tte, Depth depth, Value beta, int ply) { - - Value v = value_from_tt(tte->value(), ply); + bool can_return_tt(const TTEntry* tte, Depth depth, Value v, Value beta) { return ( tte->depth() >= depth || v >= std::max(VALUE_MATE_IN_MAX_PLY, beta) || v < std::min(VALUE_MATED_IN_MAX_PLY, beta)) - && ( ((tte->type() & VALUE_TYPE_LOWER) && v >= beta) - || ((tte->type() & VALUE_TYPE_UPPER) && v < beta)); + && ( ((tte->type() & BOUND_LOWER) && v >= beta) + || ((tte->type() & BOUND_UPPER) && v < beta)); } // refine_eval() returns the transposition table score if possible, otherwise // falls back on static position evaluation. - Value refine_eval(const TTEntry* tte, Value defaultEval, int ply) { + Value refine_eval(const TTEntry* tte, Value v, Value defaultEval) { assert(tte); - Value v = value_from_tt(tte->value(), ply); - - if ( ((tte->type() & VALUE_TYPE_LOWER) && v >= defaultEval) - || ((tte->type() & VALUE_TYPE_UPPER) && v < defaultEval)) + if ( ((tte->type() & BOUND_LOWER) && v >= defaultEval) + || ((tte->type() & BOUND_UPPER) && v < defaultEval)) return v; return defaultEval; } - // current_search_time() returns the number of milliseconds which have passed - // since the beginning of the current search. - - int elapsed_time(bool reset) { - - static int searchStartTime; - - if (reset) - searchStartTime = system_time(); - - return system_time() - searchStartTime; - } - - // score_to_uci() converts a value to a string suitable for use with the UCI // protocol specifications: // @@ -1593,7 +1539,7 @@ split_point_start: // At split points actual search starts from here void pv_info_to_uci(const Position& pos, int depth, Value alpha, Value beta) { - int t = elapsed_time(); + int t = SearchTime.elapsed(); int selDepth = 0; for (int i = 0; i < Threads.size(); i++) @@ -1725,7 +1671,7 @@ split_point_start: // At split points actual search starts from here static RKISS rk; // PRNG sequence should be not deterministic - for (int i = abs(system_time() % 50); i > 0; i--) + for (int i = Time::current_time().msec() % 50; i > 0; i--) rk.rand(); // RootMoves are already sorted by score in descending order @@ -1763,9 +1709,9 @@ split_point_start: // At split points actual search starts from here /// RootMove::extract_pv_from_tt() builds a PV by adding moves from the TT table. -/// We consider also failing high nodes and not only VALUE_TYPE_EXACT nodes so -/// to allow to always have a ponder move even when we fail high at root, and -/// a long PV to print that is important for position analysis. +/// We consider also failing high nodes and not only BOUND_EXACT nodes so to +/// allow to always have a ponder move even when we fail high at root, and a +/// long PV to print that is important for position analysis. void RootMove::extract_pv_from_tt(Position& pos) { @@ -1781,14 +1727,14 @@ void RootMove::extract_pv_from_tt(Position& pos) { pos.do_move(m, *st++); while ( (tte = TT.probe(pos.key())) != NULL - && tte->move() != MOVE_NONE - && pos.is_pseudo_legal(tte->move()) - && pos.pl_move_is_legal(tte->move(), pos.pinned_pieces()) + && (m = tte->move()) != MOVE_NONE // Local copy, TT entry could change + && pos.is_pseudo_legal(m) + && pos.pl_move_is_legal(m, pos.pinned_pieces()) && ply < MAX_PLY && (!pos.is_draw() || ply < 2)) { - pv.push_back(tte->move()); - pos.do_move(tte->move(), *st++); + pv.push_back(m); + pos.do_move(m, *st++); ply++; } pv.push_back(MOVE_NONE); @@ -1819,7 +1765,7 @@ void RootMove::insert_pv_in_tt(Position& pos) { if (!tte || tte->move() != pv[ply]) { v = (pos.in_check() ? VALUE_NONE : evaluate(pos, m)); - TT.store(k, VALUE_NONE, VALUE_TYPE_NONE, DEPTH_NONE, pv[ply], v, m); + TT.store(k, VALUE_NONE, BOUND_NONE, DEPTH_NONE, pv[ply], v, m); } pos.do_move(pv[ply], *st++); @@ -1830,34 +1776,34 @@ void RootMove::insert_pv_in_tt(Position& pos) { /// Thread::idle_loop() is where the thread is parked when it has no work to do. -/// The parameter 'sp', if non-NULL, is a pointer to an active SplitPoint object -/// for which the thread is the master. +/// The parameter 'master_sp', if non-NULL, is a pointer to an active SplitPoint +/// object for which the thread is the master. -void Thread::idle_loop(SplitPoint* sp) { +void Thread::idle_loop(SplitPoint* sp_master) { - while (true) + // If this thread is the master of a split point and all slaves have + // finished their work at this split point, return from the idle loop. + while (!sp_master || sp_master->slavesMask) { // If we are not searching, wait for a condition to be signaled // instead of wasting CPU time polling for work. while ( do_sleep - || do_terminate - || (Threads.use_sleeping_threads() && !is_searching)) + || do_exit + || (!is_searching && Threads.use_sleeping_threads())) { - assert((!sp && threadID) || Threads.use_sleeping_threads()); - - if (do_terminate) + if (do_exit) { - assert(!sp); + assert(!sp_master); return; } // Grab the lock to avoid races with Thread::wake_up() - lock_grab(&sleepLock); + lock_grab(sleepLock); // If we are master and all slaves have finished don't go to sleep - if (sp && Threads.split_point_finished(sp)) + if (sp_master && !sp_master->slavesMask) { - lock_release(&sleepLock); + lock_release(sleepLock); break; } @@ -1866,54 +1812,58 @@ void Thread::idle_loop(SplitPoint* sp) { // in the meanwhile, allocated us and sent the wake_up() call before we // had the chance to grab the lock. if (do_sleep || !is_searching) - cond_wait(&sleepCond, &sleepLock); + cond_wait(sleepCond, sleepLock); - lock_release(&sleepLock); + lock_release(sleepLock); } // If this thread has been assigned work, launch a search if (is_searching) { - assert(!do_terminate); + assert(!do_sleep && !do_exit); + + lock_grab(Threads.splitLock); + + assert(is_searching); + SplitPoint* sp = curSplitPoint; + + lock_release(Threads.splitLock); - // Copy split point position and search stack and call search() Stack ss[MAX_PLY_PLUS_2]; - SplitPoint* tsp = splitPoint; - Position pos(*tsp->pos, threadID); + Position pos(*sp->pos, this); - memcpy(ss, tsp->ss - 1, 4 * sizeof(Stack)); - (ss+1)->sp = tsp; + memcpy(ss, sp->ss - 1, 4 * sizeof(Stack)); + (ss+1)->sp = sp; - if (tsp->nodeType == Root) - search(pos, ss+1, tsp->alpha, tsp->beta, tsp->depth); - else if (tsp->nodeType == PV) - search(pos, ss+1, tsp->alpha, tsp->beta, tsp->depth); - else if (tsp->nodeType == NonPV) - search(pos, ss+1, tsp->alpha, tsp->beta, tsp->depth); + lock_grab(sp->lock); + + if (sp->nodeType == Root) + search(pos, ss+1, sp->alpha, sp->beta, sp->depth); + else if (sp->nodeType == PV) + search(pos, ss+1, sp->alpha, sp->beta, sp->depth); + else if (sp->nodeType == NonPV) + search(pos, ss+1, sp->alpha, sp->beta, sp->depth); else assert(false); assert(is_searching); is_searching = false; + sp->slavesMask &= ~(1ULL << idx); + sp->nodes += pos.nodes_searched(); // Wake up master thread so to allow it to return from the idle loop in // case we are the last slave of the split point. - if ( Threads.use_sleeping_threads() - && threadID != tsp->master - && !Threads[tsp->master].is_searching) - Threads[tsp->master].wake_up(); - } + if ( Threads.use_sleeping_threads() + && this != sp->master + && !sp->master->is_searching) + sp->master->wake_up(); - // If this thread is the master of a split point and all slaves have - // finished their work at this split point, return from the idle loop. - if (sp && Threads.split_point_finished(sp)) - { - // Because sp->is_slave[] is reset under lock protection, - // be sure sp->lock has been released before to return. - lock_grab(&(sp->lock)); - lock_release(&(sp->lock)); - return; + // After releasing the lock we cannot access anymore any SplitPoint + // related data in a safe way becuase it could have been released under + // our feet by the sp master. Also accessing other Thread objects is + // unsafe because if we are exiting there is a chance are already freed. + lock_release(sp->lock); } } } @@ -1925,18 +1875,18 @@ void Thread::idle_loop(SplitPoint* sp) { void check_time() { - static int lastInfoTime; - int e = elapsed_time(); + static Time lastInfoTime = Time::current_time(); - if (system_time() - lastInfoTime >= 1000 || !lastInfoTime) + if (lastInfoTime.elapsed() >= 1000) { - lastInfoTime = system_time(); + lastInfoTime.restart(); dbg_print(); } if (Limits.ponder) return; + int e = SearchTime.elapsed(); bool stillAtFirstMove = Signals.firstRootMove && !Signals.failedLowAtRoot && e > TimeMgr.available_time(); @@ -1945,7 +1895,6 @@ void check_time() { || stillAtFirstMove; if ( (Limits.use_time_management() && noMoreTime) - || (Limits.maxTime && e >= Limits.maxTime) - /* missing nodes limit */ ) // FIXME + || (Limits.movetime && e >= Limits.movetime)) Signals.stop = true; } diff --git a/DroidFish/jni/stockfish/search.h b/DroidFish/jni/stockfish/search.h index a4cba6b..c573807 100644 --- a/DroidFish/jni/stockfish/search.h +++ b/DroidFish/jni/stockfish/search.h @@ -23,6 +23,7 @@ #include #include +#include "misc.h" #include "types.h" class Position; @@ -39,7 +40,6 @@ struct Stack { int ply; Move currentMove; Move excludedMove; - Move bestMove; Move killers[2]; Depth reduction; Value eval; @@ -78,9 +78,9 @@ struct RootMove { struct LimitsType { LimitsType() { memset(this, 0, sizeof(LimitsType)); } - bool use_time_management() const { return !(maxTime | maxDepth | maxNodes | infinite); } + bool use_time_management() const { return !(movetime | depth | nodes | infinite); } - int time, increment, movesToGo, maxTime, maxDepth, maxNodes, infinite, ponder; + int time[2], inc[2], movestogo, depth, nodes, movetime, infinite, ponder; }; @@ -95,6 +95,7 @@ extern volatile SignalsType Signals; extern LimitsType Limits; extern std::vector RootMoves; extern Position RootPosition; +extern Time SearchTime; extern void init(); extern int64_t perft(Position& pos, Depth depth); diff --git a/DroidFish/jni/stockfish/thread.cpp b/DroidFish/jni/stockfish/thread.cpp index 1db61a2..a25ae07 100644 --- a/DroidFish/jni/stockfish/thread.cpp +++ b/DroidFish/jni/stockfish/thread.cpp @@ -17,6 +17,7 @@ along with this program. If not, see . */ +#include #include #include "movegen.h" @@ -31,225 +32,261 @@ ThreadsManager Threads; // Global object namespace { extern "C" { // start_routine() is the C function which is called when a new thread - // is launched. It simply calls idle_loop() of the supplied thread. The first - // and last thread are special. First one is the main search thread while the - // last one mimics a timer, they run in main_loop() and timer_loop(). + // is launched. It is a wrapper to member function pointed by start_fn. -#if defined(_MSC_VER) - DWORD WINAPI start_routine(LPVOID thread) { -#else - void* start_routine(void* thread) { -#endif - - Thread* th = (Thread*)thread; - - if (th->threadID == 0) - th->main_loop(); - - else if (th->threadID == MAX_THREADS) - th->timer_loop(); - - else - th->idle_loop(NULL); - - return 0; - } + long start_routine(Thread* th) { (th->*(th->start_fn))(); return 0; } } } -// wake_up() wakes up the thread, normally at the beginning of the search or, -// if "sleeping threads" is used, when there is some work to do. +// Thread c'tor starts a newly-created thread of execution that will call +// the idle loop function pointed by start_fn going immediately to sleep. -void Thread::wake_up() { +Thread::Thread(Fn fn) { - lock_grab(&sleepLock); - cond_signal(&sleepCond); - lock_release(&sleepLock); + is_searching = do_exit = false; + maxPly = splitPointsCnt = 0; + curSplitPoint = NULL; + start_fn = fn; + idx = Threads.size(); + + do_sleep = (fn != &Thread::main_loop); // Avoid a race with start_searching() + + lock_init(sleepLock); + cond_init(sleepCond); + + for (int j = 0; j < MAX_SPLITPOINTS_PER_THREAD; j++) + lock_init(splitPoints[j].lock); + + if (!thread_create(handle, start_routine, this)) + { + std::cerr << "Failed to create thread number " << idx << std::endl; + ::exit(EXIT_FAILURE); + } } -// cutoff_occurred() checks whether a beta cutoff has occurred in the current -// active split point, or in some ancestor of the split point. +// Thread d'tor waits for thread termination before to return. + +Thread::~Thread() { + + assert(do_sleep); + + do_exit = true; // Search must be already finished + wake_up(); + + thread_join(handle); // Wait for thread termination + + lock_destroy(sleepLock); + cond_destroy(sleepCond); + + for (int j = 0; j < MAX_SPLITPOINTS_PER_THREAD; j++) + lock_destroy(splitPoints[j].lock); +} + + +// Thread::timer_loop() is where the timer thread waits maxPly milliseconds and +// then calls check_time(). If maxPly is 0 thread sleeps until is woken up. +extern void check_time(); + +void Thread::timer_loop() { + + while (!do_exit) + { + lock_grab(sleepLock); + timed_wait(sleepCond, sleepLock, maxPly ? maxPly : INT_MAX); + lock_release(sleepLock); + check_time(); + } +} + + +// Thread::main_loop() is where the main thread is parked waiting to be started +// when there is a new search. Main thread will launch all the slave threads. + +void Thread::main_loop() { + + while (true) + { + lock_grab(sleepLock); + + do_sleep = true; // Always return to sleep after a search + is_searching = false; + + while (do_sleep && !do_exit) + { + cond_signal(Threads.sleepCond); // Wake up UI thread if needed + cond_wait(sleepCond, sleepLock); + } + + lock_release(sleepLock); + + if (do_exit) + return; + + is_searching = true; + + Search::think(); + } +} + + +// Thread::wake_up() wakes up the thread, normally at the beginning of the search +// or, if "sleeping threads" is used at split time. + +void Thread::wake_up() { + + lock_grab(sleepLock); + cond_signal(sleepCond); + lock_release(sleepLock); +} + + +// Thread::wait_for_stop_or_ponderhit() is called when the maximum depth is +// reached while the program is pondering. The point is to work around a wrinkle +// in the UCI protocol: When pondering, the engine is not allowed to give a +// "bestmove" before the GUI sends it a "stop" or "ponderhit" command. We simply +// wait here until one of these commands (that raise StopRequest) is sent and +// then return, after which the bestmove and pondermove will be printed. + +void Thread::wait_for_stop_or_ponderhit() { + + Signals.stopOnPonderhit = true; + + lock_grab(sleepLock); + while (!Signals.stop) cond_wait(sleepCond, sleepLock); + lock_release(sleepLock); +} + + +// Thread::cutoff_occurred() checks whether a beta cutoff has occurred in the +// current active split point, or in some ancestor of the split point. bool Thread::cutoff_occurred() const { - for (SplitPoint* sp = splitPoint; sp; sp = sp->parent) - if (sp->is_betaCutoff) + for (SplitPoint* sp = curSplitPoint; sp; sp = sp->parent) + if (sp->cutoff) return true; return false; } -// is_available_to() checks whether the thread is available to help the thread with -// threadID "master" at a split point. An obvious requirement is that thread must be -// idle. With more than two threads, this is not by itself sufficient: If the thread -// is the master of some active split point, it is only available as a slave to the -// threads which are busy searching the split point at the top of "slave"'s split +// Thread::is_available_to() checks whether the thread is available to help the +// thread 'master' at a split point. An obvious requirement is that thread must +// be idle. With more than two threads, this is not sufficient: If the thread is +// the master of some active split point, it is only available as a slave to the +// slaves which are busy searching the split point at the top of slaves split // point stack (the "helpful master concept" in YBWC terminology). -bool Thread::is_available_to(int master) const { +bool Thread::is_available_to(Thread* master) const { if (is_searching) return false; // Make a local copy to be sure doesn't become zero under our feet while // testing next condition and so leading to an out of bound access. - int localActiveSplitPoints = activeSplitPoints; + int spCnt = splitPointsCnt; // No active split points means that the thread is available as a slave for any // other thread otherwise apply the "helpful master" concept if possible. - if ( !localActiveSplitPoints - || splitPoints[localActiveSplitPoints - 1].is_slave[master]) - return true; - - return false; + return !spCnt || (splitPoints[spCnt - 1].slavesMask & (1ULL << master->idx)); } -// read_uci_options() updates number of active threads and other parameters -// according to the UCI options values. It is called before to start a new search. +// init() is called at startup. Initializes lock and condition variable and +// launches requested threads sending them immediately to sleep. We cannot use +// a c'tor becuase Threads is a static object and we need a fully initialized +// engine at this point due to allocation of endgames in Thread c'tor. + +void ThreadsManager::init() { + + cond_init(sleepCond); + lock_init(splitLock); + timer = new Thread(&Thread::timer_loop); + threads.push_back(new Thread(&Thread::main_loop)); + read_uci_options(); +} + + +// d'tor cleanly terminates the threads when the program exits. + +ThreadsManager::~ThreadsManager() { + + for (int i = 0; i < size(); i++) + delete threads[i]; + + delete timer; + lock_destroy(splitLock); + cond_destroy(sleepCond); +} + + +// read_uci_options() updates internal threads parameters from the corresponding +// UCI options and creates/destroys threads to match the requested number. Thread +// objects are dynamically allocated to avoid creating in advance all possible +// threads, with included pawns and material tables, if only few are used. void ThreadsManager::read_uci_options() { maxThreadsPerSplitPoint = Options["Max Threads per Split Point"]; minimumSplitDepth = Options["Min Split Depth"] * ONE_PLY; useSleepingThreads = Options["Use Sleeping Threads"]; + int requested = Options["Threads"]; - set_size(Options["Threads"]); -} + assert(requested > 0); + while (size() < requested) + threads.push_back(new Thread(&Thread::idle_loop)); -// set_size() changes the number of active threads and raises do_sleep flag for -// all the unused threads that will go immediately to sleep. - -void ThreadsManager::set_size(int cnt) { - - assert(cnt > 0 && cnt <= MAX_THREADS); - - activeThreads = cnt; - - for (int i = 1; i < MAX_THREADS; i++) // Ignore main thread - if (i < activeThreads) - { - // Dynamically allocate pawn and material hash tables according to the - // number of active threads. This avoids preallocating memory for all - // possible threads if only few are used. - threads[i].pawnTable.init(); - threads[i].materialTable.init(); - - threads[i].do_sleep = false; - } - else - threads[i].do_sleep = true; -} - - -// init() is called during startup. Initializes locks and condition variables -// and launches all threads sending them immediately to sleep. - -void ThreadsManager::init() { - - // Initialize sleep condition and lock used by thread manager - cond_init(&sleepCond); - lock_init(&threadsLock); - - // Initialize thread's sleep conditions and split point locks - for (int i = 0; i <= MAX_THREADS; i++) + while (size() > requested) { - lock_init(&threads[i].sleepLock); - cond_init(&threads[i].sleepCond); - - for (int j = 0; j < MAX_ACTIVE_SPLIT_POINTS; j++) - lock_init(&(threads[i].splitPoints[j].lock)); - } - - // Allocate main thread tables to call evaluate() also when not searching - threads[0].pawnTable.init(); - threads[0].materialTable.init(); - - // Create and launch all the threads, threads will go immediately to sleep - for (int i = 0; i <= MAX_THREADS; i++) - { - threads[i].is_searching = false; - threads[i].do_sleep = (i != 0); // Avoid a race with start_thinking() - threads[i].threadID = i; - -#if defined(_MSC_VER) - threads[i].handle = CreateThread(NULL, 0, start_routine, &threads[i], 0, NULL); - bool ok = (threads[i].handle != NULL); -#else - bool ok = !pthread_create(&threads[i].handle, NULL, start_routine, &threads[i]); -#endif - - if (!ok) - { - std::cerr << "Failed to create thread number " << i << std::endl; - ::exit(EXIT_FAILURE); - } + delete threads.back(); + threads.pop_back(); } } -// exit() is called to cleanly terminate the threads when the program finishes +// wake_up() is called before a new search to start the threads that are waiting +// on the sleep condition and to reset maxPly. When useSleepingThreads is set +// threads will be woken up at split time. -void ThreadsManager::exit() { +void ThreadsManager::wake_up() const { - for (int i = 0; i <= MAX_THREADS; i++) + for (int i = 0; i < size(); i++) { - threads[i].do_terminate = true; // Search must be already finished - threads[i].wake_up(); + threads[i]->maxPly = 0; + threads[i]->do_sleep = false; - // Wait for thread termination -#if defined(_MSC_VER) - WaitForSingleObject(threads[i].handle, INFINITE); - CloseHandle(threads[i].handle); -#else - pthread_join(threads[i].handle, NULL); -#endif - - // Now we can safely destroy associated locks and wait conditions - lock_destroy(&threads[i].sleepLock); - cond_destroy(&threads[i].sleepCond); - - for (int j = 0; j < MAX_ACTIVE_SPLIT_POINTS; j++) - lock_destroy(&(threads[i].splitPoints[j].lock)); + if (!useSleepingThreads) + threads[i]->wake_up(); } +} - lock_destroy(&threadsLock); - cond_destroy(&sleepCond); + +// sleep() is called after the search finishes to ask all the threads but the +// main one to go waiting on a sleep condition. + +void ThreadsManager::sleep() const { + + for (int i = 1; i < size(); i++) // Main thread will go to sleep by itself + threads[i]->do_sleep = true; // to avoid a race with start_searching() } // available_slave_exists() tries to find an idle thread which is available as -// a slave for the thread with threadID 'master'. +// a slave for the thread 'master'. -bool ThreadsManager::available_slave_exists(int master) const { +bool ThreadsManager::available_slave_exists(Thread* master) const { - assert(master >= 0 && master < activeThreads); - - for (int i = 0; i < activeThreads; i++) - if (threads[i].is_available_to(master)) + for (int i = 0; i < size(); i++) + if (threads[i]->is_available_to(master)) return true; return false; } -// split_point_finished() checks if all the slave threads of a given split -// point have finished searching. - -bool ThreadsManager::split_point_finished(SplitPoint* sp) const { - - for (int i = 0; i < activeThreads; i++) - if (sp->is_slave[i]) - return false; - - return true; -} - - // split() does the actual work of distributing the work at a node between // several available threads. If it does not succeed in splitting the node // (because no idle threads are available, or because we have no unused split @@ -261,32 +298,29 @@ bool ThreadsManager::split_point_finished(SplitPoint* sp) const { template Value ThreadsManager::split(Position& pos, Stack* ss, Value alpha, Value beta, - Value bestValue, Depth depth, Move threatMove, - int moveCount, MovePicker* mp, int nodeType) { + Value bestValue, Move* bestMove, Depth depth, + Move threatMove, int moveCount, MovePicker* mp, int nodeType) { assert(pos.pos_is_ok()); assert(bestValue > -VALUE_INFINITE); assert(bestValue <= alpha); assert(alpha < beta); assert(beta <= VALUE_INFINITE); assert(depth > DEPTH_ZERO); - assert(pos.thread() >= 0 && pos.thread() < activeThreads); - assert(activeThreads > 1); - int i, master = pos.thread(); - Thread& masterThread = threads[master]; + Thread* master = pos.this_thread(); - // If we already have too many active split points, don't split - if (masterThread.activeSplitPoints >= MAX_ACTIVE_SPLIT_POINTS) + if (master->splitPointsCnt >= MAX_SPLITPOINTS_PER_THREAD) return bestValue; // Pick the next available split point from the split point stack - SplitPoint* sp = &masterThread.splitPoints[masterThread.activeSplitPoints]; + SplitPoint* sp = &master->splitPoints[master->splitPointsCnt]; - // Initialize the split point - sp->parent = masterThread.splitPoint; + sp->parent = master->curSplitPoint; sp->master = master; - sp->is_betaCutoff = false; + sp->cutoff = false; + sp->slavesMask = 1ULL << master->idx; sp->depth = depth; + sp->bestMove = *bestMove; sp->threatMove = threatMove; sp->alpha = alpha; sp->beta = beta; @@ -298,88 +332,71 @@ Value ThreadsManager::split(Position& pos, Stack* ss, Value alpha, Value beta, sp->nodes = 0; sp->ss = ss; - for (i = 0; i < activeThreads; i++) - sp->is_slave[i] = false; + assert(master->is_searching); - // If we are here it means we are not available - assert(masterThread.is_searching); - - int workersCnt = 1; // At least the master is included + master->curSplitPoint = sp; + int slavesCnt = 0; // Try to allocate available threads and ask them to start searching setting // is_searching flag. This must be done under lock protection to avoid concurrent // allocation of the same slave by another master. - lock_grab(&threadsLock); + lock_grab(sp->lock); + lock_grab(splitLock); - for (i = 0; !Fake && i < activeThreads && workersCnt < maxThreadsPerSplitPoint; i++) - if (threads[i].is_available_to(master)) + for (int i = 0; i < size() && !Fake; ++i) + if (threads[i]->is_available_to(master)) { - workersCnt++; - sp->is_slave[i] = true; - threads[i].splitPoint = sp; - - // This makes the slave to exit from idle_loop() - threads[i].is_searching = true; + sp->slavesMask |= 1ULL << i; + threads[i]->curSplitPoint = sp; + threads[i]->is_searching = true; // Slave leaves idle_loop() if (useSleepingThreads) - threads[i].wake_up(); + threads[i]->wake_up(); + + if (++slavesCnt + 1 >= maxThreadsPerSplitPoint) // Master is always included + break; } - lock_release(&threadsLock); + master->splitPointsCnt++; - // We failed to allocate even one slave, return - if (!Fake && workersCnt == 1) - return bestValue; - - masterThread.splitPoint = sp; - masterThread.activeSplitPoints++; + lock_release(splitLock); + lock_release(sp->lock); // Everything is set up. The master thread enters the idle loop, from which // it will instantly launch a search, because its is_searching flag is set. // We pass the split point as a parameter to the idle loop, which means that // the thread will return from the idle loop when all slaves have finished // their work at this split point. - masterThread.idle_loop(sp); + if (slavesCnt || Fake) + { + master->idle_loop(sp); - // In helpful master concept a master can help only a sub-tree of its split - // point, and because here is all finished is not possible master is booked. - assert(!masterThread.is_searching); + // In helpful master concept a master can help only a sub-tree of its split + // point, and because here is all finished is not possible master is booked. + assert(!master->is_searching); + } // We have returned from the idle loop, which means that all threads are - // finished. Note that changing state and decreasing activeSplitPoints is done - // under lock protection to avoid a race with Thread::is_available_to(). - lock_grab(&threadsLock); + // finished. Note that setting is_searching and decreasing splitPointsCnt is + // done under lock protection to avoid a race with Thread::is_available_to(). + lock_grab(sp->lock); // To protect sp->nodes + lock_grab(splitLock); - masterThread.is_searching = true; - masterThread.activeSplitPoints--; - - lock_release(&threadsLock); - - masterThread.splitPoint = sp->parent; + master->is_searching = true; + master->splitPointsCnt--; + master->curSplitPoint = sp->parent; pos.set_nodes_searched(pos.nodes_searched() + sp->nodes); + *bestMove = sp->bestMove; + + lock_release(splitLock); + lock_release(sp->lock); return sp->bestValue; } // Explicit template instantiations -template Value ThreadsManager::split(Position&, Stack*, Value, Value, Value, Depth, Move, int, MovePicker*, int); -template Value ThreadsManager::split(Position&, Stack*, Value, Value, Value, Depth, Move, int, MovePicker*, int); - - -// Thread::timer_loop() is where the timer thread waits maxPly milliseconds and -// then calls do_timer_event(). If maxPly is 0 thread sleeps until is woken up. -extern void check_time(); - -void Thread::timer_loop() { - - while (!do_terminate) - { - lock_grab(&sleepLock); - timed_wait(&sleepCond, &sleepLock, maxPly ? maxPly : INT_MAX); - lock_release(&sleepLock); - check_time(); - } -} +template Value ThreadsManager::split(Position&, Stack*, Value, Value, Value, Move*, Depth, Move, int, MovePicker*, int); +template Value ThreadsManager::split(Position&, Stack*, Value, Value, Value, Move*, Depth, Move, int, MovePicker*, int); // ThreadsManager::set_timer() is used to set the timer to trigger after msec @@ -387,124 +404,46 @@ void Thread::timer_loop() { void ThreadsManager::set_timer(int msec) { - Thread& timer = threads[MAX_THREADS]; - - lock_grab(&timer.sleepLock); - timer.maxPly = msec; - cond_signal(&timer.sleepCond); // Wake up and restart the timer - lock_release(&timer.sleepLock); + lock_grab(timer->sleepLock); + timer->maxPly = msec; + cond_signal(timer->sleepCond); // Wake up and restart the timer + lock_release(timer->sleepLock); } -// Thread::main_loop() is where the main thread is parked waiting to be started -// when there is a new search. Main thread will launch all the slave threads. +// ThreadsManager::wait_for_search_finished() waits for main thread to go to +// sleep, this means search is finished. Then returns. -void Thread::main_loop() { +void ThreadsManager::wait_for_search_finished() { - while (true) - { - lock_grab(&sleepLock); - - do_sleep = true; // Always return to sleep after a search - is_searching = false; - - while (do_sleep && !do_terminate) - { - cond_signal(&Threads.sleepCond); // Wake up UI thread if needed - cond_wait(&sleepCond, &sleepLock); - } - - is_searching = true; - - lock_release(&sleepLock); - - if (do_terminate) - return; - - Search::think(); - } + Thread* t = main_thread(); + lock_grab(t->sleepLock); + cond_signal(t->sleepCond); // In case is waiting for stop or ponderhit + while (!t->do_sleep) cond_wait(sleepCond, t->sleepLock); + lock_release(t->sleepLock); } -// ThreadsManager::start_thinking() is used by UI thread to wake up the main -// thread parked in main_loop() and starting a new search. If asyncMode is true -// then function returns immediately, otherwise caller is blocked waiting for -// the search to finish. +// ThreadsManager::start_searching() wakes up the main thread sleeping in +// main_loop() so to start a new search, then returns immediately. -void ThreadsManager::start_thinking(const Position& pos, const LimitsType& limits, - const std::set& searchMoves, bool async) { - Thread& main = threads[0]; +void ThreadsManager::start_searching(const Position& pos, const LimitsType& limits, + const std::vector& searchMoves) { + wait_for_search_finished(); - lock_grab(&main.sleepLock); + SearchTime.restart(); // As early as possible - // Wait main thread has finished before to launch a new search - while (!main.do_sleep) - cond_wait(&sleepCond, &main.sleepLock); - - // Copy input arguments to initialize the search - RootPosition.copy(pos, 0); - Limits = limits; - RootMoves.clear(); - - // Populate RootMoves with all the legal moves (default) or, if a searchMoves - // set is given, with the subset of legal moves to search. - for (MoveList ml(pos); !ml.end(); ++ml) - if (searchMoves.empty() || searchMoves.count(ml.move())) - RootMoves.push_back(RootMove(ml.move())); - - // Reset signals before to start the new search Signals.stopOnPonderhit = Signals.firstRootMove = false; Signals.stop = Signals.failedLowAtRoot = false; - main.do_sleep = false; - cond_signal(&main.sleepCond); // Wake up main thread and start searching + RootPosition = pos; + Limits = limits; + RootMoves.clear(); - if (!async) - while (!main.do_sleep) - cond_wait(&sleepCond, &main.sleepLock); + for (MoveList ml(pos); !ml.end(); ++ml) + if (searchMoves.empty() || std::count(searchMoves.begin(), searchMoves.end(), ml.move())) + RootMoves.push_back(RootMove(ml.move())); - lock_release(&main.sleepLock); -} - - -// ThreadsManager::stop_thinking() is used by UI thread to raise a stop request -// and to wait for the main thread finishing the search. Needed to wait exiting -// and terminate the threads after a 'quit' command. - -void ThreadsManager::stop_thinking() { - - Thread& main = threads[0]; - - Search::Signals.stop = true; - - lock_grab(&main.sleepLock); - - cond_signal(&main.sleepCond); // In case is waiting for stop or ponderhit - - while (!main.do_sleep) - cond_wait(&sleepCond, &main.sleepLock); - - lock_release(&main.sleepLock); -} - - -// ThreadsManager::wait_for_stop_or_ponderhit() is called when the maximum depth -// is reached while the program is pondering. The point is to work around a wrinkle -// in the UCI protocol: When pondering, the engine is not allowed to give a -// "bestmove" before the GUI sends it a "stop" or "ponderhit" command. We simply -// wait here until one of these commands (that raise StopRequest) is sent and -// then return, after which the bestmove and pondermove will be printed. - -void ThreadsManager::wait_for_stop_or_ponderhit() { - - Signals.stopOnPonderhit = true; - - Thread& main = threads[0]; - - lock_grab(&main.sleepLock); - - while (!Signals.stop) - cond_wait(&main.sleepCond, &main.sleepLock); - - lock_release(&main.sleepLock); + main_thread()->do_sleep = false; + main_thread()->wake_up(); } diff --git a/DroidFish/jni/stockfish/thread.h b/DroidFish/jni/stockfish/thread.h index cc49063..3eb8fc9 100644 --- a/DroidFish/jni/stockfish/thread.h +++ b/DroidFish/jni/stockfish/thread.h @@ -20,10 +20,8 @@ #if !defined(THREAD_H_INCLUDED) #define THREAD_H_INCLUDED -#include -#include +#include -#include "lock.h" #include "material.h" #include "movepick.h" #include "pawns.h" @@ -31,32 +29,34 @@ #include "search.h" const int MAX_THREADS = 32; -const int MAX_ACTIVE_SPLIT_POINTS = 8; +const int MAX_SPLITPOINTS_PER_THREAD = 8; + +class Thread; struct SplitPoint { - // Const data after splitPoint has been setup - SplitPoint* parent; + // Const data after split point has been setup const Position* pos; + const Search::Stack* ss; Depth depth; Value beta; int nodeType; - int ply; - int master; + Thread* master; Move threatMove; // Const pointers to shared data MovePicker* mp; - Search::Stack* ss; + SplitPoint* parent; // Shared data Lock lock; + volatile uint64_t slavesMask; volatile int64_t nodes; volatile Value alpha; volatile Value bestValue; + volatile Move bestMove; volatile int moveCount; - volatile bool is_betaCutoff; - volatile bool is_slave[MAX_THREADS]; + volatile bool cutoff; }; @@ -65,33 +65,40 @@ struct SplitPoint { /// tables so that once we get a pointer to an entry its life time is unlimited /// and we don't have to care about someone changing the entry under our feet. -struct Thread { +class Thread { + + Thread(const Thread&); // Only declared to disable the default ones + Thread& operator=(const Thread&); // that are not suitable in this case. + + typedef void (Thread::* Fn) (); + +public: + Thread(Fn fn); + ~Thread(); void wake_up(); bool cutoff_occurred() const; - bool is_available_to(int master) const; - void idle_loop(SplitPoint* sp); + bool is_available_to(Thread* master) const; + void idle_loop(SplitPoint* sp_master); + void idle_loop() { idle_loop(NULL); } // Hack to allow storing in start_fn void main_loop(); void timer_loop(); + void wait_for_stop_or_ponderhit(); - SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS]; - MaterialInfoTable materialTable; - PawnInfoTable pawnTable; - int threadID; + SplitPoint splitPoints[MAX_SPLITPOINTS_PER_THREAD]; + MaterialTable materialTable; + PawnTable pawnTable; + int idx; int maxPly; Lock sleepLock; WaitCondition sleepCond; - SplitPoint* volatile splitPoint; - volatile int activeSplitPoints; + NativeHandle handle; + Fn start_fn; + SplitPoint* volatile curSplitPoint; + volatile int splitPointsCnt; volatile bool is_searching; volatile bool do_sleep; - volatile bool do_terminate; - -#if defined(_MSC_VER) - HANDLE handle; -#else - pthread_t handle; -#endif + volatile bool do_exit; }; @@ -105,37 +112,37 @@ class ThreadsManager { static storage duration are automatically set to zero before enter main() */ public: - Thread& operator[](int threadID) { return threads[threadID]; } - void init(); - void exit(); + void init(); // No c'tor becuase Threads is static and we need engine initialized + ~ThreadsManager(); + Thread& operator[](int id) { return *threads[id]; } bool use_sleeping_threads() const { return useSleepingThreads; } int min_split_depth() const { return minimumSplitDepth; } - int size() const { return activeThreads; } + int size() const { return (int)threads.size(); } + Thread* main_thread() { return threads[0]; } - void set_size(int cnt); + void wake_up() const; + void sleep() const; void read_uci_options(); - bool available_slave_exists(int master) const; - bool split_point_finished(SplitPoint* sp) const; + bool available_slave_exists(Thread* master) const; void set_timer(int msec); - void wait_for_stop_or_ponderhit(); - void stop_thinking(); - void start_thinking(const Position& pos, const Search::LimitsType& limits, - const std::set& = std::set(), bool async = false); + void wait_for_search_finished(); + void start_searching(const Position& pos, const Search::LimitsType& limits, + const std::vector& searchMoves); template - Value split(Position& pos, Search::Stack* ss, Value alpha, Value beta, Value bestValue, + Value split(Position& pos, Search::Stack* ss, Value alpha, Value beta, Value bestValue, Move* bestMove, Depth depth, Move threatMove, int moveCount, MovePicker* mp, int nodeType); private: - friend struct Thread; + friend class Thread; - Thread threads[MAX_THREADS + 1]; // Last one is used as a timer - Lock threadsLock; + std::vector threads; + Thread* timer; + Lock splitLock; + WaitCondition sleepCond; Depth minimumSplitDepth; int maxThreadsPerSplitPoint; - int activeThreads; bool useSleepingThreads; - WaitCondition sleepCond; }; extern ThreadsManager Threads; diff --git a/DroidFish/jni/stockfish/timeman.cpp b/DroidFish/jni/stockfish/timeman.cpp index 28f9f3e..b8f8396 100644 --- a/DroidFish/jni/stockfish/timeman.cpp +++ b/DroidFish/jni/stockfish/timeman.cpp @@ -20,7 +20,6 @@ #include #include -#include "misc.h" #include "search.h" #include "timeman.h" #include "ucioption.h" @@ -73,7 +72,7 @@ namespace { enum TimeType { OptimumTime, MaxTime }; template - int remaining(int myTime, int movesToGo, int fullMoveNumber); + int remaining(int myTime, int movesToGo, int fullMoveNumber, int slowMover); } @@ -84,7 +83,7 @@ void TimeManager::pv_instability(int curChanges, int prevChanges) { } -void TimeManager::init(const Search::LimitsType& limits, int currentPly) +void TimeManager::init(const Search::LimitsType& limits, int currentPly, Color us) { /* We support four different kind of time controls: @@ -108,25 +107,26 @@ void TimeManager::init(const Search::LimitsType& limits, int currentPly) int emergencyBaseTime = Options["Emergency Base Time"]; int emergencyMoveTime = Options["Emergency Move Time"]; int minThinkingTime = Options["Minimum Thinking Time"]; + int slowMover = Options["Slow Mover"]; // Initialize to maximum values but unstablePVExtraTime that is reset unstablePVExtraTime = 0; - optimumSearchTime = maximumSearchTime = limits.time; + optimumSearchTime = maximumSearchTime = limits.time[us]; // We calculate optimum time usage for different hypothetic "moves to go"-values and choose the // minimum of calculated search time values. Usually the greatest hypMTG gives the minimum values. - for (hypMTG = 1; hypMTG <= (limits.movesToGo ? std::min(limits.movesToGo, MoveHorizon) : MoveHorizon); hypMTG++) + for (hypMTG = 1; hypMTG <= (limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon); hypMTG++) { // Calculate thinking time for hypothetic "moves to go"-value - hypMyTime = limits.time - + limits.increment * (hypMTG - 1) + hypMyTime = limits.time[us] + + limits.inc[us] * (hypMTG - 1) - emergencyBaseTime - emergencyMoveTime * std::min(hypMTG, emergencyMoveHorizon); hypMyTime = std::max(hypMyTime, 0); - t1 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly); - t2 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly); + t1 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly, slowMover); + t2 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly, slowMover); optimumSearchTime = std::min(optimumSearchTime, t1); maximumSearchTime = std::min(maximumSearchTime, t2); @@ -143,12 +143,12 @@ void TimeManager::init(const Search::LimitsType& limits, int currentPly) namespace { template - int remaining(int myTime, int movesToGo, int currentPly) + int remaining(int myTime, int movesToGo, int currentPly, int slowMover) { const float TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); const float TStealRatio = (T == OptimumTime ? 0 : StealRatio); - int thisMoveImportance = move_importance(currentPly); + int thisMoveImportance = move_importance(currentPly) * slowMover / 100; int otherMovesImportance = 0; for (int i = 1; i < movesToGo; i++) diff --git a/DroidFish/jni/stockfish/timeman.h b/DroidFish/jni/stockfish/timeman.h index 2030cc2..ddf96c6 100644 --- a/DroidFish/jni/stockfish/timeman.h +++ b/DroidFish/jni/stockfish/timeman.h @@ -25,7 +25,7 @@ class TimeManager { public: - void init(const Search::LimitsType& limits, int currentPly); + void init(const Search::LimitsType& limits, int currentPly, Color us); void pv_instability(int curChanges, int prevChanges); int available_time() const { return optimumSearchTime + unstablePVExtraTime; } int maximum_time() const { return maximumSearchTime; } diff --git a/DroidFish/jni/stockfish/tt.cpp b/DroidFish/jni/stockfish/tt.cpp index fdded2a..09a74d2 100644 --- a/DroidFish/jni/stockfish/tt.cpp +++ b/DroidFish/jni/stockfish/tt.cpp @@ -84,7 +84,7 @@ void TranspositionTable::clear() { /// more valuable than a TTEntry t2 if t1 is from the current search and t2 is from /// a previous search, or if the depth of t1 is bigger than the depth of t2. -void TranspositionTable::store(const Key posKey, Value v, ValueType t, Depth d, Move m, Value statV, Value kingD) { +void TranspositionTable::store(const Key posKey, Value v, Bound t, Depth d, Move m, Value statV, Value kingD) { int c1, c2, c3; TTEntry *tte, *replace; @@ -106,7 +106,7 @@ void TranspositionTable::store(const Key posKey, Value v, ValueType t, Depth d, // Implement replace strategy c1 = (replace->generation() == generation ? 2 : 0); - c2 = (tte->generation() == generation || tte->type() == VALUE_TYPE_EXACT ? -2 : 0); + c2 = (tte->generation() == generation || tte->type() == BOUND_EXACT ? -2 : 0); c3 = (tte->depth() < replace->depth() ? 1 : 0); if (c1 + c2 + c3 > 0) diff --git a/DroidFish/jni/stockfish/tt.h b/DroidFish/jni/stockfish/tt.h index 707bb0f..3edd5e8 100644 --- a/DroidFish/jni/stockfish/tt.h +++ b/DroidFish/jni/stockfish/tt.h @@ -20,12 +20,9 @@ #if !defined(TT_H_INCLUDED) #define TT_H_INCLUDED -#include - #include "misc.h" #include "types.h" - /// The TTEntry is the class of transposition table entries /// /// A TTEntry needs 128 bits to be stored @@ -47,11 +44,11 @@ class TTEntry { public: - void save(uint32_t k, Value v, ValueType t, Depth d, Move m, int g, Value statV, Value statM) { + void save(uint32_t k, Value v, Bound b, Depth d, Move m, int g, Value statV, Value statM) { key32 = (uint32_t)k; move16 = (uint16_t)m; - valueType = (uint8_t)t; + bound = (uint8_t)b; generation8 = (uint8_t)g; value16 = (int16_t)v; depth16 = (int16_t)d; @@ -64,7 +61,7 @@ public: Depth depth() const { return (Depth)depth16; } Move move() const { return (Move)move16; } Value value() const { return (Value)value16; } - ValueType type() const { return (ValueType)valueType; } + Bound type() const { return (Bound)bound; } int generation() const { return (int)generation8; } Value static_value() const { return (Value)staticValue; } Value static_value_margin() const { return (Value)staticMargin; } @@ -72,7 +69,7 @@ public: private: uint32_t key32; uint16_t move16; - uint8_t valueType, generation8; + uint8_t bound, generation8; int16_t value16, depth16, staticValue, staticMargin; }; @@ -103,7 +100,7 @@ public: ~TranspositionTable(); void set_size(size_t mbSize); void clear(); - void store(const Key posKey, Value v, ValueType type, Depth d, Move m, Value statV, Value kingD); + void store(const Key posKey, Value v, Bound type, Depth d, Move m, Value statV, Value kingD); TTEntry* probe(const Key posKey) const; void new_search(); TTEntry* first_entry(const Key posKey) const; @@ -136,38 +133,4 @@ inline void TranspositionTable::refresh(const TTEntry* tte) const { const_cast(tte)->set_generation(generation); } - -/// A simple fixed size hash table used to store pawns and material -/// configurations. It is basically just an array of Entry objects. -/// Without cluster concept or overwrite policy. - -template -struct SimpleHash { - - typedef SimpleHash Base; - - void init() { - - if (entries) - return; - - entries = new (std::nothrow) Entry[HashSize]; - if (!entries) - { - std::cerr << "Failed to allocate " << HashSize * sizeof(Entry) - << " bytes for hash table." << std::endl; - exit(EXIT_FAILURE); - } - memset(entries, 0, HashSize * sizeof(Entry)); - } - - virtual ~SimpleHash() { delete [] entries; } - - Entry* probe(Key key) const { return entries + ((uint32_t)key & (HashSize - 1)); } - void prefetch(Key key) const { ::prefetch((char*)probe(key)); } - -protected: - Entry* entries; -}; - #endif // !defined(TT_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/types.h b/DroidFish/jni/stockfish/types.h index 487343a..40a2baa 100644 --- a/DroidFish/jni/stockfish/types.h +++ b/DroidFish/jni/stockfish/types.h @@ -35,30 +35,11 @@ /// | only in 64-bit mode. For compiling requires hardware with /// | popcnt support. +#include #include #include -#include -#if defined(_MSC_VER) - -// Disable some silly and noisy warning from MSVC compiler -#pragma warning(disable: 4127) // Conditional expression is constant -#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type -#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' - -// MSVC does not support -typedef signed __int8 int8_t; -typedef unsigned __int8 uint8_t; -typedef signed __int16 int16_t; -typedef unsigned __int16 uint16_t; -typedef signed __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef signed __int64 int64_t; -typedef unsigned __int64 uint64_t; - -#else -# include -#endif +#include "platform.h" #if defined(_WIN64) # include // MSVC popcnt and bsfq instrinsics @@ -99,7 +80,7 @@ const bool Is64Bit = false; typedef uint64_t Key; typedef uint64_t Bitboard; -const int MAX_MOVES = 256; +const int MAX_MOVES = 192; const int MAX_PLY = 100; const int MAX_PLY_PLUS_2 = MAX_PLY + 2; @@ -147,15 +128,20 @@ inline bool operator<(const MoveStack& f, const MoveStack& s) { return f.score < s.score; } -enum CastleRight { +enum CastleRight { // Defined as in PolyGlot book hash key CASTLES_NONE = 0, WHITE_OO = 1, - BLACK_OO = 2, - WHITE_OOO = 4, + WHITE_OOO = 2, + BLACK_OO = 4, BLACK_OOO = 8, ALL_CASTLES = 15 }; +enum CastlingSide { + KING_SIDE, + QUEEN_SIDE +}; + enum ScaleFactor { SCALE_FACTOR_DRAW = 0, SCALE_FACTOR_NORMAL = 64, @@ -163,11 +149,11 @@ enum ScaleFactor { SCALE_FACTOR_NONE = 255 }; -enum ValueType { - VALUE_TYPE_NONE = 0, - VALUE_TYPE_UPPER = 1, - VALUE_TYPE_LOWER = 2, - VALUE_TYPE_EXACT = VALUE_TYPE_UPPER | VALUE_TYPE_LOWER +enum Bound { + BOUND_NONE = 0, + BOUND_UPPER = 1, + BOUND_LOWER = 2, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; enum Value { @@ -186,7 +172,7 @@ enum Value { }; enum PieceType { - NO_PIECE_TYPE = 0, + NO_PIECE_TYPE = 0, ALL_PIECES = 0, PAWN = 1, KNIGHT = 2, BISHOP = 3, ROOK = 4, QUEEN = 5, KING = 6 }; @@ -207,7 +193,7 @@ enum Depth { DEPTH_ZERO = 0 * ONE_PLY, DEPTH_QS_CHECKS = -1 * ONE_PLY, DEPTH_QS_NO_CHECKS = -2 * ONE_PLY, - DEPTH_QS_RECAPTURES = -4 * ONE_PLY, + DEPTH_QS_RECAPTURES = -5 * ONE_PLY, DEPTH_NONE = -127 * ONE_PLY }; @@ -317,21 +303,27 @@ inline Score operator/(Score s, int i) { return make_score(mg_value(s) / i, eg_value(s) / i); } +/// Weight score v by score w trying to prevent overflow +inline Score apply_weight(Score v, Score w) { + return make_score((int(mg_value(v)) * mg_value(w)) / 0x100, + (int(eg_value(v)) * eg_value(w)) / 0x100); +} + #undef ENABLE_OPERATORS_ON #undef ENABLE_SAFE_OPERATORS_ON -const Value PawnValueMidgame = Value(0x0C6); -const Value PawnValueEndgame = Value(0x102); -const Value KnightValueMidgame = Value(0x331); -const Value KnightValueEndgame = Value(0x34E); -const Value BishopValueMidgame = Value(0x344); -const Value BishopValueEndgame = Value(0x359); -const Value RookValueMidgame = Value(0x4F6); -const Value RookValueEndgame = Value(0x4FE); -const Value QueenValueMidgame = Value(0x9D9); -const Value QueenValueEndgame = Value(0x9FE); +const Value PawnValueMidgame = Value(198); +const Value PawnValueEndgame = Value(258); +const Value KnightValueMidgame = Value(817); +const Value KnightValueEndgame = Value(846); +const Value BishopValueMidgame = Value(836); +const Value BishopValueEndgame = Value(857); +const Value RookValueMidgame = Value(1270); +const Value RookValueEndgame = Value(1278); +const Value QueenValueMidgame = Value(2521); +const Value QueenValueEndgame = Value(2558); -extern const Value PieceValueMidgame[17]; +extern const Value PieceValueMidgame[17]; // Indexed by Piece or PieceType extern const Value PieceValueEndgame[17]; extern int SquareDistance[64][64]; @@ -340,7 +332,7 @@ inline Color operator~(Color c) { } inline Square operator~(Square s) { - return Square(s ^ 56); + return Square(s ^ 56); // Vertical flip SQ_A1 -> SQ_A8 } inline Value mate_in(int ply) { @@ -355,6 +347,10 @@ inline Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) | pt); } +inline CastleRight make_castle_right(Color c, CastlingSide s) { + return CastleRight(WHITE_OO << ((s == QUEEN_SIDE) + 2 * c)); +} + inline PieceType type_of(Piece p) { return PieceType(p & 7); } @@ -367,7 +363,7 @@ inline Square make_square(File f, Rank r) { return Square((r << 3) | f); } -inline bool square_is_ok(Square s) { +inline bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } @@ -380,7 +376,7 @@ inline Rank rank_of(Square s) { } inline Square mirror(Square s) { - return Square(s ^ 7); + return Square(s ^ 7); // Horizontal flip SQ_A1 -> SQ_H1 } inline Square relative_square(Color c, Square s) { @@ -396,7 +392,7 @@ inline Rank relative_rank(Color c, Square s) { } inline bool opposite_colors(Square s1, Square s2) { - int s = s1 ^ s2; + int s = int(s1) ^ int(s2); return ((s >> 3) ^ s) & 1; } @@ -452,7 +448,7 @@ inline int is_castle(Move m) { return (m & (3 << 14)) == (3 << 14); } -inline PieceType promotion_piece_type(Move m) { +inline PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + 2); } @@ -486,23 +482,18 @@ inline const std::string square_to_string(Square s) { /// Our insertion sort implementation, works with pointers and iterators and is /// guaranteed to be stable, as is needed. template -void sort(K firstMove, K lastMove) +void sort(K first, K last) { - T value; - K cur, p, d; + T tmp; + K p, q; - if (firstMove != lastMove) - for (cur = firstMove + 1; cur != lastMove; cur++) - { - p = d = cur; - value = *p--; - if (*p < value) - { - do *d = *p; - while (--d != firstMove && *--p < value); - *d = value; - } - } + for (p = first + 1; p < last; p++) + { + tmp = *p; + for (q = p; q != first && *(q-1) < tmp; --q) + *q = *(q-1); + *q = tmp; + } } #endif // !defined(TYPES_H_INCLUDED) diff --git a/DroidFish/jni/stockfish/uci.cpp b/DroidFish/jni/stockfish/uci.cpp index ceb04dc..0ba4de3 100644 --- a/DroidFish/jni/stockfish/uci.cpp +++ b/DroidFish/jni/stockfish/uci.cpp @@ -20,10 +20,8 @@ #include #include #include -#include #include "evaluate.h" -#include "misc.h" #include "position.h" #include "search.h" #include "thread.h" @@ -31,6 +29,8 @@ using namespace std; +extern void benchmark(const Position& pos, istream& is); + namespace { // FEN string of the initial position, normal chess @@ -44,7 +44,6 @@ namespace { void set_option(istringstream& up); void set_position(Position& pos, istringstream& up); void go(Position& pos, istringstream& up); - void perft(Position& pos, istringstream& up); } @@ -53,14 +52,17 @@ namespace { /// that we exit gracefully if the GUI dies unexpectedly. In addition to the UCI /// commands, the function also supports a few debug commands. -void uci_loop() { +void uci_loop(const string& args) { - Position pos(StartFEN, false, 0); // The root position + Position pos(StartFEN, false, Threads.main_thread()); // The root position string cmd, token; while (token != "quit") { - if (!getline(cin, cmd)) // Block here waiting for input + if (!args.empty()) + cmd = args; + + else if (!getline(cin, cmd)) // Block here waiting for input cmd = "quit"; istringstream is(cmd); @@ -68,7 +70,10 @@ void uci_loop() { is >> skipws >> token; if (token == "quit" || token == "stop") - Threads.stop_thinking(); + { + Search::Signals.stop = true; + Threads.wait_for_search_finished(); // Cannot quit while threads are running + } else if (token == "ponderhit") { @@ -78,14 +83,17 @@ void uci_loop() { Search::Limits.ponder = false; if (Search::Signals.stopOnPonderhit) - Threads.stop_thinking(); + { + Search::Signals.stop = true; + Threads.main_thread()->wake_up(); // Could be sleeping + } } else if (token == "go") go(pos, is); else if (token == "ucinewgame") - pos.from_fen(StartFEN, false); + { /* Avoid returning "Unknown command" */ } else if (token == "isready") cout << "readyok" << endl; @@ -96,20 +104,17 @@ void uci_loop() { else if (token == "setoption") set_option(is); - else if (token == "perft") - perft(pos, is); - else if (token == "d") pos.print(); else if (token == "flip") - pos.flip_me(); + pos.flip(); else if (token == "eval") - { - read_evaluation_uci_options(pos.side_to_move()); - cout << trace_evaluate(pos) << endl; - } + cout << Eval::trace(pos) << endl; + + else if (token == "bench") + benchmark(pos, is); else if (token == "key") cout << "key: " << hex << pos.key() @@ -120,8 +125,25 @@ void uci_loop() { cout << "id name " << engine_info(true) << "\n" << Options << "\nuciok" << endl; + + else if (token == "perft" && (is >> token)) // Read depth + { + stringstream ss; + + ss << Options["Hash"] << " " + << Options["Threads"] << " " << token << " current perft"; + + benchmark(pos, ss); + } + else cout << "Unknown command: " << cmd << endl; + + if (!args.empty()) // Command line arguments have one-shot behaviour + { + Threads.wait_for_search_finished(); + break; + } } } @@ -151,7 +173,7 @@ namespace { else return; - pos.from_fen(fen, Options["UCI_Chess960"]); + pos.from_fen(fen, Options["UCI_Chess960"], Threads.main_thread()); // Parse move list (if any) while (is >> token && (m = move_from_uci(pos, token)) != MOVE_NONE) @@ -182,81 +204,50 @@ namespace { while (is >> token) value += string(" ", !value.empty()) + token; - if (!Options.count(name)) - cout << "No such option: " << name << endl; - - else if (value.empty()) // UCI buttons don't have a value - Options[name] = true; - - else + if (Options.count(name)) Options[name] = value; + else + cout << "No such option: " << name << endl; } // go() is called when engine receives the "go" UCI command. The function sets // the thinking time and other parameters from the input string, and then starts - // the main searching thread. + // the search. void go(Position& pos, istringstream& is) { - string token; Search::LimitsType limits; - std::set searchMoves; - int time[] = { 0, 0 }, inc[] = { 0, 0 }; + vector searchMoves; + string token; while (is >> token) { - if (token == "infinite") + if (token == "wtime") + is >> limits.time[WHITE]; + else if (token == "btime") + is >> limits.time[BLACK]; + else if (token == "winc") + is >> limits.inc[WHITE]; + else if (token == "binc") + is >> limits.inc[BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "infinite") limits.infinite = true; else if (token == "ponder") limits.ponder = true; - else if (token == "wtime") - is >> time[WHITE]; - else if (token == "btime") - is >> time[BLACK]; - else if (token == "winc") - is >> inc[WHITE]; - else if (token == "binc") - is >> inc[BLACK]; - else if (token == "movestogo") - is >> limits.movesToGo; - else if (token == "depth") - is >> limits.maxDepth; - else if (token == "nodes") - is >> limits.maxNodes; - else if (token == "movetime") - is >> limits.maxTime; else if (token == "searchmoves") while (is >> token) - searchMoves.insert(move_from_uci(pos, token)); + searchMoves.push_back(move_from_uci(pos, token)); } - limits.time = time[pos.side_to_move()]; - limits.increment = inc[pos.side_to_move()]; - - Threads.start_thinking(pos, limits, searchMoves, true); - } - - - // perft() is called when engine receives the "perft" command. The function - // calls perft() with the required search depth then prints counted leaf nodes - // and elapsed time. - - void perft(Position& pos, istringstream& is) { - - int depth, time; - - if (!(is >> depth)) - return; - - time = system_time(); - - int64_t n = Search::perft(pos, depth * ONE_PLY); - - time = system_time() - time; - - std::cout << "\nNodes " << n - << "\nTime (ms) " << time - << "\nNodes/second " << int(n / (time / 1000.0)) << std::endl; + Threads.start_searching(pos, limits, searchMoves); } } diff --git a/DroidFish/jni/stockfish/ucioption.cpp b/DroidFish/jni/stockfish/ucioption.cpp index 897326e..4761004 100644 --- a/DroidFish/jni/stockfish/ucioption.cpp +++ b/DroidFish/jni/stockfish/ucioption.cpp @@ -20,21 +20,32 @@ #include #include +#include "evaluate.h" #include "misc.h" #include "thread.h" +#include "tt.h" #include "ucioption.h" using std::string; -using namespace std; OptionsMap Options; // Global object +namespace { + +/// 'On change' actions, triggered by an option's value change +void on_logger(const UCIOption& opt) { start_logger(opt); } +void on_eval(const UCIOption&) { Eval::init(); } +void on_threads(const UCIOption&) { Threads.read_uci_options(); } +void on_hash_size(const UCIOption& opt) { TT.set_size(opt); } +void on_clear_hash(const UCIOption&) { TT.clear(); } /// Our case insensitive less() function as required by UCI protocol -static bool ci_less(char c1, char c2) { return tolower(c1) < tolower(c2); } +bool ci_less(char c1, char c2) { return tolower(c1) < tolower(c2); } + +} bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { - return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ci_less); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ci_less); } @@ -48,38 +59,41 @@ OptionsMap::OptionsMap() { int msd = cpus < 8 ? 4 : 7; OptionsMap& o = *this; + o["Use Debug Log"] = UCIOption(false, on_logger); o["Use Search Log"] = UCIOption(false); o["Search Log Filename"] = UCIOption("SearchLog.txt"); o["Book File"] = UCIOption("book.bin"); o["Best Book Move"] = UCIOption(false); - o["Mobility (Middle Game)"] = UCIOption(100, 0, 200); - o["Mobility (Endgame)"] = UCIOption(100, 0, 200); - o["Passed Pawns (Middle Game)"] = UCIOption(100, 0, 200); - o["Passed Pawns (Endgame)"] = UCIOption(100, 0, 200); - o["Space"] = UCIOption(100, 0, 200); - o["Aggressiveness"] = UCIOption(100, 0, 200); - o["Cowardice"] = UCIOption(100, 0, 200); - o["Min Split Depth"] = UCIOption(msd, 4, 7); - o["Max Threads per Split Point"] = UCIOption(5, 4, 8); - o["Threads"] = UCIOption(cpus, 1, MAX_THREADS); - o["Use Sleeping Threads"] = UCIOption(false); - o["Hash"] = UCIOption(32, 4, 8192); - o["Clear Hash"] = UCIOption(false, "button"); + o["Mobility (Middle Game)"] = UCIOption(100, 0, 200, on_eval); + o["Mobility (Endgame)"] = UCIOption(100, 0, 200, on_eval); + o["Passed Pawns (Middle Game)"] = UCIOption(100, 0, 200, on_eval); + o["Passed Pawns (Endgame)"] = UCIOption(100, 0, 200, on_eval); + o["Space"] = UCIOption(100, 0, 200, on_eval); + o["Aggressiveness"] = UCIOption(100, 0, 200, on_eval); + o["Cowardice"] = UCIOption(100, 0, 200, on_eval); + o["Min Split Depth"] = UCIOption(msd, 4, 7, on_threads); + o["Max Threads per Split Point"] = UCIOption(5, 4, 8, on_threads); + o["Threads"] = UCIOption(cpus, 1, MAX_THREADS, on_threads); + o["Use Sleeping Threads"] = UCIOption(true, on_threads); + o["Hash"] = UCIOption(32, 4, 8192, on_hash_size); + o["Clear Hash"] = UCIOption(on_clear_hash); o["Ponder"] = UCIOption(true); - o["OwnBook"] = UCIOption(true); + o["OwnBook"] = UCIOption(false); o["MultiPV"] = UCIOption(1, 1, 500); o["Skill Level"] = UCIOption(20, 0, 20); o["Emergency Move Horizon"] = UCIOption(40, 0, 50); o["Emergency Base Time"] = UCIOption(200, 0, 30000); o["Emergency Move Time"] = UCIOption(70, 0, 5000); o["Minimum Thinking Time"] = UCIOption(20, 0, 5000); + o["Slow Mover"] = UCIOption(100, 10, 1000); o["UCI_Chess960"] = UCIOption(false); - o["UCI_AnalyseMode"] = UCIOption(false); + o["UCI_AnalyseMode"] = UCIOption(false, on_eval); } /// operator<<() is used to output all the UCI options in chronological insertion /// order (the idx field) and in the format defined by the UCI protocol. + std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { for (size_t idx = 0; idx < om.size(); idx++) @@ -103,26 +117,35 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { /// UCIOption class c'tors -UCIOption::UCIOption(const char* v) : type("string"), min(0), max(0), idx(Options.size()) +UCIOption::UCIOption(const char* v, Fn* f) : type("string"), min(0), max(0), idx(Options.size()), on_change(f) { defaultValue = currentValue = v; } -UCIOption::UCIOption(bool v, string t) : type(t), min(0), max(0), idx(Options.size()) +UCIOption::UCIOption(bool v, Fn* f) : type("check"), min(0), max(0), idx(Options.size()), on_change(f) { defaultValue = currentValue = (v ? "true" : "false"); } -UCIOption::UCIOption(int v, int minv, int maxv) : type("spin"), min(minv), max(maxv), idx(Options.size()) +UCIOption::UCIOption(Fn* f) : type("button"), min(0), max(0), idx(Options.size()), on_change(f) +{} + +UCIOption::UCIOption(int v, int minv, int maxv, Fn* f) : type("spin"), min(minv), max(maxv), idx(Options.size()), on_change(f) { std::ostringstream ss; ss << v; defaultValue = currentValue = ss.str(); } /// UCIOption::operator=() updates currentValue. Normally it's up to the GUI to /// check for option's limits, but we could receive the new value directly from -/// the user by teminal window, so let's check the bounds anyway. +/// the user by console window, so let's check the bounds anyway. void UCIOption::operator=(const string& v) { assert(!type.empty()); - if ( !v.empty() - && (type == "check" || type == "button") == (v == "true" || v == "false") - && (type != "spin" || (atoi(v.c_str()) >= min && atoi(v.c_str()) <= max))) - currentValue = v; + if ( (type == "button" || !v.empty()) + && (type != "check" || (v == "true" || v == "false")) + && (type != "spin" || (atoi(v.c_str()) >= min && atoi(v.c_str()) <= max))) + { + if (type != "button") + currentValue = v; + + if (on_change) + (*on_change)(*this); + } } diff --git a/DroidFish/jni/stockfish/ucioption.h b/DroidFish/jni/stockfish/ucioption.h index f80ff86..c57cf02 100644 --- a/DroidFish/jni/stockfish/ucioption.h +++ b/DroidFish/jni/stockfish/ucioption.h @@ -29,17 +29,19 @@ struct OptionsMap; /// UCIOption class implements an option as defined by UCI protocol class UCIOption { + + typedef void (Fn)(const UCIOption&); + public: - UCIOption() {} // Required by std::map::operator[] - UCIOption(const char* v); - UCIOption(bool v, std::string type = "check"); - UCIOption(int v, int min, int max); + UCIOption(Fn* = NULL); + UCIOption(bool v, Fn* = NULL); + UCIOption(const char* v, Fn* = NULL); + UCIOption(int v, int min, int max, Fn* = NULL); void operator=(const std::string& v); - void operator=(bool v) { *this = std::string(v ? "true" : "false"); } operator int() const { - assert(type == "check" || type == "button" || type == "spin"); + assert(type == "check" || type == "spin"); return (type == "spin" ? atoi(currentValue.c_str()) : currentValue == "true"); } @@ -54,6 +56,7 @@ private: std::string defaultValue, currentValue, type; int min, max; size_t idx; + Fn* on_change; };