mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-22 10:06:26 +08:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
235df6a887 | ||
|
|
8d86c87e1e | ||
|
|
c172af1b61 | ||
|
|
b7c36d078b | ||
|
|
db1b0bfa1d | ||
|
|
6f946f823c | ||
|
|
2de78ebc7b | ||
|
|
32934c0c8d | ||
|
|
b4fcfed55b | ||
|
|
dc2302b701 | ||
|
|
268c12bf31 | ||
|
|
9f943a132a | ||
|
|
20390fcb3c | ||
|
|
d95b9189d8 | ||
|
|
9d5d69e435 | ||
|
|
08265aef81 | ||
|
|
9d1e4d041d | ||
|
|
cff3a6d33e | ||
|
|
20a2ca366f | ||
|
|
c3ba5fb9d3 | ||
|
|
eba8925d81 | ||
|
|
ee9f650242 | ||
|
|
8a0dd93c56 | ||
|
|
5a72ff128e | ||
|
|
3f63dd1023 | ||
|
|
62ab7e4612 | ||
|
|
1867785231 | ||
|
|
bac4da70c9 | ||
|
|
8189ae9e1c | ||
|
|
1d525bb45f | ||
|
|
da7d872eda | ||
|
|
49d52b8266 | ||
|
|
75d001addd | ||
|
|
7daaf03b39 | ||
|
|
2e778445d5 | ||
|
|
93bc05cf69 | ||
|
|
a7227ac26f | ||
|
|
bb0da595a7 | ||
|
|
20d7197a9b | ||
|
|
f4758ced90 | ||
|
|
3c05bd70eb | ||
|
|
7000e100bd | ||
|
|
2ed22e4fc8 | ||
|
|
940c53c366 | ||
|
|
4df8651c82 | ||
|
|
2d4e2bc62a | ||
|
|
d89a03cc35 | ||
|
|
34b1d0538b | ||
|
|
cdf1f23bc5 | ||
|
|
e86468cc63 | ||
|
|
e7bad2687a | ||
|
|
3f610e2b13 | ||
|
|
6cc11272e2 | ||
|
|
a28c17ecb6 | ||
|
|
ec23692433 | ||
|
|
787d358554 | ||
|
|
ff0d9dad2b | ||
|
|
046fd4926f | ||
|
|
c595185b3c |
@@ -71,8 +71,8 @@ void benchmark(const std::string& commandLine) {
|
||||
|
||||
std::istringstream csVal(commandLine);
|
||||
std::istringstream csStr(commandLine);
|
||||
std::string ttSize, threads, fileName;
|
||||
int val, secsPerPos;
|
||||
std::string ttSize, threads, fileName, limitType;
|
||||
int val, secsPerPos, maxDepth, maxNodes;
|
||||
|
||||
csStr >> ttSize;
|
||||
csVal >> val;
|
||||
@@ -95,8 +95,18 @@ void benchmark(const std::string& commandLine) {
|
||||
set_option_value("Use Search Log", "true");
|
||||
set_option_value("Search Log Filename", "bench.txt");
|
||||
|
||||
csVal >> secsPerPos;
|
||||
csVal >> val;
|
||||
csVal >> fileName;
|
||||
csVal >> limitType;
|
||||
|
||||
secsPerPos = maxDepth = maxNodes = 0;
|
||||
|
||||
if (limitType == "time")
|
||||
secsPerPos = val * 1000;
|
||||
else if (limitType == "depth")
|
||||
maxDepth = val;
|
||||
else
|
||||
maxNodes = val;
|
||||
|
||||
std::vector<std::string> positions;
|
||||
|
||||
@@ -121,12 +131,21 @@ void benchmark(const std::string& commandLine) {
|
||||
for (int i = 0; i < 16; i++)
|
||||
positions.push_back(std::string(BenchmarkPositions[i]));
|
||||
|
||||
int startTime = get_system_time();
|
||||
std::vector<std::string>::iterator it;
|
||||
for (it = positions.begin(); it != positions.end(); ++it)
|
||||
int cnt = 1;
|
||||
int64_t totalNodes = 0;
|
||||
for (it = positions.begin(); it != positions.end(); ++it, ++cnt)
|
||||
{
|
||||
Move moves[1] = {MOVE_NONE};
|
||||
int dummy[2] = {0, 0};
|
||||
Move moves[1] = {MOVE_NONE};
|
||||
int dummy[2] = {0, 0};
|
||||
Position pos(*it);
|
||||
think(pos, true, false, 0, dummy, dummy, 0, 0, 0, secsPerPos * 1000, moves);
|
||||
std::cout << "\nProcessing position " << cnt << '/' << positions.size() << std::endl << std::endl;
|
||||
think(pos, true, false, 0, dummy, dummy, 0, maxDepth, maxNodes, secsPerPos, moves);
|
||||
totalNodes += nodes_searched();
|
||||
}
|
||||
std::cout << "\nProcessing time (ms) " << get_system_time() - startTime << std::endl
|
||||
<< "Nodes searched " << totalNodes << std::endl
|
||||
<< "Press any key to exit" << std::endl;
|
||||
std::cin >> fileName;
|
||||
}
|
||||
|
||||
@@ -1120,13 +1120,7 @@ namespace {
|
||||
|
||||
ev = apply_scale_factor(ev, sf[(ev > Value(0) ? WHITE : BLACK)]);
|
||||
|
||||
// Linearized sigmoid interpolator
|
||||
int sph = int(ph);
|
||||
sph -= (64 - sph) / 4;
|
||||
sph = Min(PHASE_MIDGAME, Max(PHASE_ENDGAME, sph));
|
||||
|
||||
Value result = Value(int((mv * sph + ev * (128 - sph)) / 128));
|
||||
|
||||
Value result = Value(int((mv * ph + ev * (128 - ph)) / 128));
|
||||
return Value(int(result) & ~(GrainSize - 1));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
////
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "history.h"
|
||||
|
||||
|
||||
@@ -71,16 +71,18 @@ int main(int argc, char *argv[]) {
|
||||
// Process command line arguments
|
||||
if (argc >= 2 && string(argv[1]) == "bench")
|
||||
{
|
||||
if (argc < 4 || argc > 6)
|
||||
if (argc < 4 || argc > 7)
|
||||
{
|
||||
std::cout << "Usage: glaurung bench <hash size> <threads> "
|
||||
<< "[time = 60s] [fen positions file = default]"
|
||||
<< "[time = 60s] [fen positions file = default] "
|
||||
<< "[time, depth or node limited = time]"
|
||||
<< std::endl;
|
||||
exit(0);
|
||||
}
|
||||
string time = argc > 4 ? argv[4] : "60";
|
||||
string fen = argc > 5 ? argv[5] : "default";
|
||||
benchmark(string(argv[2]) + " " + string(argv[3]) + " " + time + " " + fen);
|
||||
string lim = argc > 6 ? argv[6] : "time";
|
||||
benchmark(string(argv[2]) + " " + string(argv[3]) + " " + time + " " + fen + " " + lim);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
////
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
|
||||
#include "material.h"
|
||||
|
||||
14
src/misc.h
14
src/misc.h
@@ -37,7 +37,7 @@
|
||||
/// Version number. If this is left empty, the current date (in the format
|
||||
/// YYMMDD) is used as a version number.
|
||||
|
||||
const std::string EngineVersion = "1.0";
|
||||
const std::string EngineVersion = "1.1a";
|
||||
|
||||
|
||||
////
|
||||
@@ -60,21 +60,21 @@ extern int Bioskey();
|
||||
////
|
||||
//// Debug
|
||||
////
|
||||
extern bool dbg_show_mean;
|
||||
extern bool dbg_show_hit_rate;
|
||||
|
||||
extern long dbg_cnt0;
|
||||
extern long dbg_cnt1;
|
||||
|
||||
inline void dbg_hit_on(bool b) { dbg_cnt0++; if (b) dbg_cnt1++; }
|
||||
inline void dbg_hit_on(bool b) { dbg_show_hit_rate = true; dbg_cnt0++; if (b) dbg_cnt1++; }
|
||||
inline void dbg_hit_on_c(bool c, bool b) { if (c) dbg_hit_on(b); }
|
||||
|
||||
inline void dbg_before() { dbg_cnt0++; }
|
||||
inline void dbg_after() { dbg_cnt1++; }
|
||||
inline void dbg_before() { dbg_show_hit_rate = true; dbg_cnt0++; }
|
||||
inline void dbg_after() { dbg_show_hit_rate = true; dbg_cnt1++; }
|
||||
|
||||
inline void dbg_mean_of(int v) { dbg_cnt0++; dbg_cnt1 += v; }
|
||||
|
||||
extern void dbg_print_hit_rate();
|
||||
extern void dbg_print_mean();
|
||||
|
||||
extern bool dbg_show_mean;
|
||||
extern bool dbg_show_hit_rate;
|
||||
|
||||
#endif // !defined(MISC_H_INCLUDED)
|
||||
|
||||
@@ -122,15 +122,16 @@ int generate_captures(const Position& pos, MoveStack* mlist) {
|
||||
Bitboard target = pos.pieces_of_color(opposite_color(us));
|
||||
MoveStack* mlist_start = mlist;
|
||||
|
||||
mlist = generate_piece_moves<QUEEN>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<ROOK>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target);
|
||||
|
||||
if (us == WHITE)
|
||||
mlist = generate_pawn_captures<WHITE>(pos, mlist);
|
||||
else
|
||||
mlist = generate_pawn_captures<BLACK>(pos, mlist);
|
||||
|
||||
mlist = generate_piece_moves<KNIGHT>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<BISHOP>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<ROOK>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<QUEEN>(pos, mlist, us, target);
|
||||
mlist = generate_piece_moves<KING>(pos, mlist, us, target);
|
||||
return int(mlist - mlist_start);
|
||||
}
|
||||
|
||||
130
src/movepick.cpp
130
src/movepick.cpp
@@ -26,6 +26,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "history.h"
|
||||
#include "evaluate.h"
|
||||
#include "movegen.h"
|
||||
#include "movepick.h"
|
||||
#include "search.h"
|
||||
@@ -44,7 +45,9 @@ namespace {
|
||||
int MainSearchPhaseIndex;
|
||||
int EvasionsPhaseIndex;
|
||||
int QsearchWithChecksPhaseIndex;
|
||||
int QsearchNoCapturesPhaseIndex;
|
||||
int QsearchWithoutChecksPhaseIndex;
|
||||
int NoMovesPhaseIndex;
|
||||
|
||||
}
|
||||
|
||||
@@ -62,28 +65,38 @@ namespace {
|
||||
/// search captures, promotions and some checks) and about how important good
|
||||
/// move ordering is at the current node.
|
||||
|
||||
MovePicker::MovePicker(const Position& p, bool pvnode, Move ttm, Move mk,
|
||||
Move k1, Move k2, Depth d) : pos(p) {
|
||||
pvNode = pvnode;
|
||||
MovePicker::MovePicker(const Position& p, bool pv, Move ttm,
|
||||
const SearchStack& ss, Depth d, EvalInfo* ei) : pos(p) {
|
||||
pvNode = pv;
|
||||
ttMove = ttm;
|
||||
mateKiller = (mk == ttm)? MOVE_NONE : mk;
|
||||
killer1 = k1;
|
||||
killer2 = k2;
|
||||
mateKiller = (ss.mateKiller == ttm)? MOVE_NONE : ss.mateKiller;
|
||||
killer1 = ss.killers[0];
|
||||
killer2 = ss.killers[1];
|
||||
depth = d;
|
||||
movesPicked = 0;
|
||||
numOfMoves = 0;
|
||||
numOfBadCaptures = 0;
|
||||
dc = p.discovered_check_candidates(p.side_to_move());
|
||||
|
||||
// With EvalInfo we are able to know how many captures are possible before
|
||||
// generating them. So avoid generating in case we know are zero.
|
||||
Color us = pos.side_to_move();
|
||||
Color them = opposite_color(us);
|
||||
bool noCaptures = ei
|
||||
&& (ei->attackedBy[us][0] & pos.pieces_of_color(them)) == 0
|
||||
&& !ei->mi->specialized_eval_exists()
|
||||
&& (pos.ep_square() == SQ_NONE)
|
||||
&& !pos.has_pawn_on_7th(us);
|
||||
|
||||
if (p.is_check())
|
||||
phaseIndex = EvasionsPhaseIndex;
|
||||
phaseIndex = EvasionsPhaseIndex;
|
||||
else if (depth > Depth(0))
|
||||
phaseIndex = MainSearchPhaseIndex;
|
||||
phaseIndex = MainSearchPhaseIndex;
|
||||
else if (depth == Depth(0))
|
||||
phaseIndex = QsearchWithChecksPhaseIndex;
|
||||
phaseIndex = (noCaptures ? QsearchNoCapturesPhaseIndex : QsearchWithChecksPhaseIndex);
|
||||
else
|
||||
phaseIndex = QsearchWithoutChecksPhaseIndex;
|
||||
phaseIndex = (noCaptures ? NoMovesPhaseIndex : QsearchWithoutChecksPhaseIndex);
|
||||
|
||||
dc = p.discovered_check_candidates(us);
|
||||
pinned = p.pinned_pieces(p.side_to_move());
|
||||
|
||||
finished = false;
|
||||
@@ -211,6 +224,8 @@ void MovePicker::score_captures() {
|
||||
// where it is possible to recapture with the hanging piece). Exchanging
|
||||
// big pieces before capturing a hanging piece probably helps to reduce
|
||||
// the subtree size.
|
||||
// While scoring captures it moves all captures with negative SEE values
|
||||
// to the badCaptures[] array.
|
||||
Move m;
|
||||
int seeValue;
|
||||
|
||||
@@ -225,8 +240,15 @@ void MovePicker::score_captures() {
|
||||
else
|
||||
moves[i].score = int(pos.midgame_value_of_piece_on(move_to(m)))
|
||||
-int(pos.type_of_piece_on(move_from(m)));
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
// Losing capture, move it to the badCaptures[] array
|
||||
assert(numOfBadCaptures < 63);
|
||||
moves[i].score = seeValue;
|
||||
badCaptures[numOfBadCaptures++] = moves[i];
|
||||
moves[i--] = moves[--numOfMoves];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,10 +270,11 @@ void MovePicker::score_noncaptures() {
|
||||
else
|
||||
hs = H.move_ordering_score(pos.piece_on(move_from(m)), m);
|
||||
|
||||
// Ensure moves in history are always sorted as first
|
||||
// Ensure history is always preferred to pst
|
||||
if (hs > 0)
|
||||
hs += 1000;
|
||||
|
||||
// pst based scoring
|
||||
moves[i].score = hs + pos.mg_pst_delta(m);
|
||||
}
|
||||
}
|
||||
@@ -270,7 +293,6 @@ void MovePicker::score_evasions() {
|
||||
} else
|
||||
moves[i].score = H.move_ordering_score(pos.piece_on(move_from(m)), m);
|
||||
}
|
||||
// FIXME try psqt also here
|
||||
}
|
||||
|
||||
void MovePicker::score_qcaptures() {
|
||||
@@ -288,8 +310,11 @@ void MovePicker::score_qcaptures() {
|
||||
}
|
||||
|
||||
|
||||
/// find_best_index() loops across the moves and returns index of
|
||||
/// the highest scored one.
|
||||
/// find_best_index() loops across the moves and returns index of
|
||||
/// the highest scored one. There is also a second version that
|
||||
/// lowers the priority of moves that attack the same square,
|
||||
/// so that if the best move that attack a square fails the next
|
||||
/// move picked attacks a different square if any, not the same one.
|
||||
|
||||
int MovePicker::find_best_index() {
|
||||
|
||||
@@ -304,14 +329,48 @@ int MovePicker::find_best_index() {
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
int MovePicker::find_best_index(Bitboard* squares, int values[]) {
|
||||
|
||||
int hs;
|
||||
Move m;
|
||||
Square to;
|
||||
int bestScore = -10000000, bestIndex = -1;
|
||||
|
||||
for (int i = movesPicked; i < numOfMoves; i++)
|
||||
{
|
||||
m = moves[i].move;
|
||||
to = move_to(m);
|
||||
|
||||
if (!bit_is_set(*squares, to))
|
||||
{
|
||||
// Init at first use
|
||||
set_bit(squares, to);
|
||||
values[to] = 0;
|
||||
}
|
||||
|
||||
hs = moves[i].score - values[to];
|
||||
if (hs > bestScore)
|
||||
{
|
||||
bestIndex = i;
|
||||
bestScore = hs;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex != -1)
|
||||
{
|
||||
// Raise value of the picked square, so next attack
|
||||
// to the same square will get low priority.
|
||||
to = move_to(moves[bestIndex].move);
|
||||
values[to] += 0xB00;
|
||||
}
|
||||
return bestIndex;
|
||||
}
|
||||
|
||||
|
||||
/// MovePicker::pick_move_from_list() picks the move with the biggest score
|
||||
/// from a list of generated moves (moves[] or badCaptures[], depending on
|
||||
/// the current move generation phase). It takes care not to return the
|
||||
/// transposition table move if that has already been serched previously.
|
||||
/// While picking captures in the PH_GOOD_CAPTURES phase (i.e. while picking
|
||||
/// non-losing captures in the main search), it moves all captures with
|
||||
/// negative SEE values to the badCaptures[] array.
|
||||
|
||||
Move MovePicker::pick_move_from_list() {
|
||||
|
||||
@@ -325,23 +384,8 @@ Move MovePicker::pick_move_from_list() {
|
||||
|
||||
while (movesPicked < numOfMoves)
|
||||
{
|
||||
int bestScore = -10000000;
|
||||
bestIndex = -1;
|
||||
for (int i = movesPicked; i < numOfMoves; i++)
|
||||
{
|
||||
if (moves[i].score < 0)
|
||||
{
|
||||
// Losing capture, move it to the badCaptures[] array
|
||||
assert(numOfBadCaptures < 63);
|
||||
badCaptures[numOfBadCaptures++] = moves[i];
|
||||
moves[i--] = moves[--numOfMoves];
|
||||
}
|
||||
else if (moves[i].score > bestScore)
|
||||
{
|
||||
bestIndex = i;
|
||||
bestScore = moves[i].score;
|
||||
}
|
||||
}
|
||||
bestIndex = find_best_index();
|
||||
|
||||
if (bestIndex != -1) // Found a good capture
|
||||
{
|
||||
move = moves[bestIndex].move;
|
||||
@@ -461,8 +505,9 @@ MovePicker::MovegenPhase MovePicker::current_move_type() const {
|
||||
|
||||
/// MovePicker::init_phase_table() initializes the PhaseTable[],
|
||||
/// MainSearchPhaseIndex, EvasionPhaseIndex, QsearchWithChecksPhaseIndex
|
||||
/// and QsearchWithoutChecksPhaseIndex variables. It is only called once
|
||||
/// during program startup, and never again while the program is running.
|
||||
/// QsearchNoCapturesPhaseIndex, QsearchWithoutChecksPhaseIndex and
|
||||
/// NoMovesPhaseIndex variables. It is only called once during program
|
||||
/// startup, and never again while the program is running.
|
||||
|
||||
void MovePicker::init_phase_table() {
|
||||
|
||||
@@ -491,8 +536,17 @@ void MovePicker::init_phase_table() {
|
||||
PhaseTable[i++] = PH_QCHECKS;
|
||||
PhaseTable[i++] = PH_STOP;
|
||||
|
||||
// Quiescence search with checks only and no captures
|
||||
QsearchNoCapturesPhaseIndex = i - 1;
|
||||
PhaseTable[i++] = PH_QCHECKS;
|
||||
PhaseTable[i++] = PH_STOP;
|
||||
|
||||
// Quiescence search without checks
|
||||
QsearchWithoutChecksPhaseIndex = i - 1;
|
||||
PhaseTable[i++] = PH_QCAPTURES;
|
||||
PhaseTable[i++] = PH_STOP;
|
||||
|
||||
// Do not generate any move
|
||||
NoMovesPhaseIndex = i - 1;
|
||||
PhaseTable[i++] = PH_STOP;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
//// Types
|
||||
////
|
||||
|
||||
struct EvalInfo;
|
||||
struct SearchStack;
|
||||
|
||||
/// MovePicker is a class which is used to pick one 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
|
||||
@@ -60,7 +63,7 @@ public:
|
||||
PH_STOP
|
||||
};
|
||||
|
||||
MovePicker(const Position& p, bool pvnode, Move ttm, Move mk, Move k1, Move k2, Depth d);
|
||||
MovePicker(const Position& p, bool pvnode, Move ttm, const SearchStack& ss, Depth d, EvalInfo* ei = NULL);
|
||||
Move get_next_move();
|
||||
Move get_next_move(Lock &lock);
|
||||
int number_of_moves() const;
|
||||
@@ -77,6 +80,7 @@ private:
|
||||
void score_qcaptures();
|
||||
Move pick_move_from_list();
|
||||
int find_best_index();
|
||||
int find_best_index(Bitboard* squares, int values[]);
|
||||
|
||||
const Position& pos;
|
||||
Move ttMove, mateKiller, killer1, killer2;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
////
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "pawns.h"
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ const SquareDelta PawnPush[2] = {
|
||||
|
||||
static const char PieceChars[] = " pnbrqk";
|
||||
|
||||
char piece_type_to_char(PieceType pt, bool upcase = false) {
|
||||
char piece_type_to_char(PieceType pt, bool upcase) {
|
||||
return upcase? toupper(PieceChars[pt]) : PieceChars[pt];
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ inline SquareDelta pawn_push(Color c) {
|
||||
//// Prototypes
|
||||
////
|
||||
|
||||
extern char piece_type_to_char(PieceType pt, bool upcase);
|
||||
extern char piece_type_to_char(PieceType pt, bool upcase = false);
|
||||
extern PieceType piece_type_from_char(char c);
|
||||
extern bool piece_is_ok(Piece pc);
|
||||
extern bool piece_type_is_ok(PieceType pt);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "movepick.h"
|
||||
#include "position.h"
|
||||
#include "psqtab.h"
|
||||
#include "san.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
|
||||
@@ -38,6 +39,8 @@
|
||||
//// Variables
|
||||
////
|
||||
|
||||
extern SearchStack EmptySearchStack;
|
||||
|
||||
int Position::castleRightsMask[64];
|
||||
|
||||
Key Position::zobrist[2][8][64];
|
||||
@@ -49,6 +52,7 @@ Key Position::zobSideToMove;
|
||||
Value Position::MgPieceSquareTable[16][64];
|
||||
Value Position::EgPieceSquareTable[16][64];
|
||||
|
||||
static bool RequestPending = false;
|
||||
|
||||
////
|
||||
//// Functions
|
||||
@@ -242,7 +246,7 @@ const std::string Position::to_fen() const {
|
||||
|
||||
fen += (rank > RANK_1 ? '/' : ' ');
|
||||
}
|
||||
fen += (sideToMove == WHITE ? 'w' : 'b') + ' ';
|
||||
fen += (sideToMove == WHITE ? "w " : "b ");
|
||||
if (castleRights != NO_CASTLES)
|
||||
{
|
||||
if (can_castle_kingside(WHITE)) fen += 'K';
|
||||
@@ -263,29 +267,45 @@ const std::string Position::to_fen() const {
|
||||
|
||||
|
||||
/// Position::print() prints an ASCII representation of the position to
|
||||
/// the standard output.
|
||||
/// the standard output. If a move is given then also the san is print.
|
||||
|
||||
void Position::print() const {
|
||||
char pieceStrings[][8] =
|
||||
{"| ? ", "| P ", "| N ", "| B ", "| R ", "| Q ", "| K ", "| ? ",
|
||||
"| ? ", "|=P=", "|=N=", "|=B=", "|=R=", "|=Q=", "|=K="
|
||||
};
|
||||
void Position::print(Move m) const {
|
||||
|
||||
for(Rank rank = RANK_8; rank >= RANK_1; rank--) {
|
||||
std::cout << "+---+---+---+---+---+---+---+---+\n";
|
||||
for(File file = FILE_A; file <= FILE_H; file++) {
|
||||
Square sq = make_square(file, rank);
|
||||
Piece piece = piece_on(sq);
|
||||
if(piece == EMPTY)
|
||||
std::cout << ((square_color(sq) == WHITE)? "| " : "| . ");
|
||||
else
|
||||
std::cout << pieceStrings[piece];
|
||||
}
|
||||
std::cout << "|\n";
|
||||
static const std::string pieceLetters = " PNBRQK PNBRQK .";
|
||||
|
||||
// Check for reentrancy, as example when called from inside
|
||||
// MovePicker that is used also here in move_to_san()
|
||||
if (RequestPending)
|
||||
return;
|
||||
|
||||
RequestPending = true;
|
||||
|
||||
std::cout << std::endl;
|
||||
if (m != MOVE_NONE)
|
||||
{
|
||||
std::string col = (color_of_piece_on(move_from(m)) == BLACK ? ".." : "");
|
||||
std::cout << "Move is: " << col << move_to_san(*this, m) << std::endl;
|
||||
}
|
||||
std::cout << "+---+---+---+---+---+---+---+---+\n";
|
||||
std::cout << to_fen() << std::endl;
|
||||
std::cout << key << std::endl;
|
||||
for (Rank rank = RANK_8; rank >= RANK_1; rank--)
|
||||
{
|
||||
std::cout << "+---+---+---+---+---+---+---+---+" << std::endl;
|
||||
for (File file = FILE_A; file <= FILE_H; file++)
|
||||
{
|
||||
Square sq = make_square(file, rank);
|
||||
Piece piece = piece_on(sq);
|
||||
if (piece == EMPTY && square_color(sq) == WHITE)
|
||||
piece = NO_PIECE;
|
||||
|
||||
char col = (color_of_piece_on(sq) == BLACK ? '=' : ' ');
|
||||
std::cout << '|' << col << pieceLetters[piece] << col;
|
||||
}
|
||||
std::cout << '|' << std::endl;
|
||||
}
|
||||
std::cout << "+---+---+---+---+---+---+---+---+" << std::endl
|
||||
<< "Fen is: " << to_fen() << std::endl
|
||||
<< "Key is: " << key << std::endl;
|
||||
|
||||
RequestPending = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -655,10 +675,12 @@ bool Position::move_is_check(Move m, Bitboard dcCandidates) const {
|
||||
|
||||
|
||||
/// Position::move_is_capture() tests whether a move from the current
|
||||
/// position is a capture.
|
||||
/// position is a capture. Move must not be MOVE_NONE.
|
||||
|
||||
bool Position::move_is_capture(Move m) const {
|
||||
|
||||
assert(m != MOVE_NONE);
|
||||
|
||||
return ( !square_is_empty(move_to(m))
|
||||
&& (color_of_piece_on(move_to(m)) == opposite_color(side_to_move()))
|
||||
)
|
||||
@@ -1905,8 +1927,7 @@ bool Position::is_mate() {
|
||||
|
||||
if (is_check())
|
||||
{
|
||||
MovePicker mp = MovePicker(*this, false, MOVE_NONE, MOVE_NONE,
|
||||
MOVE_NONE, MOVE_NONE, Depth(0));
|
||||
MovePicker mp = MovePicker(*this, false, MOVE_NONE, EmptySearchStack, Depth(0));
|
||||
return mp.get_next_move() == MOVE_NONE;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -128,7 +128,7 @@ public:
|
||||
// Text input/output
|
||||
void from_fen(const std::string &fen);
|
||||
const std::string to_fen() const;
|
||||
void print() const;
|
||||
void print(Move m = MOVE_NONE) const;
|
||||
|
||||
// Copying
|
||||
void copy(const Position &pos);
|
||||
@@ -174,13 +174,13 @@ public:
|
||||
// Number of pieces of each color and type
|
||||
int piece_count(Color c, PieceType pt) const;
|
||||
|
||||
// The en passant square:
|
||||
// The en passant square
|
||||
Square ep_square() const;
|
||||
|
||||
// Current king position for each color
|
||||
Square king_square(Color c) const;
|
||||
|
||||
// Castling rights.
|
||||
// Castling rights
|
||||
bool can_castle_kingside(Color c) const;
|
||||
bool can_castle_queenside(Color c) const;
|
||||
bool can_castle(Color c) const;
|
||||
|
||||
502
src/san.cpp
502
src/san.cpp
@@ -31,6 +31,7 @@
|
||||
#include "movepick.h"
|
||||
#include "san.h"
|
||||
|
||||
extern SearchStack EmptySearchStack;
|
||||
|
||||
////
|
||||
//// Local definitions
|
||||
@@ -50,7 +51,7 @@ namespace {
|
||||
|
||||
/// Functions
|
||||
|
||||
Ambiguity move_ambiguity(Position &pos, Move m);
|
||||
Ambiguity move_ambiguity(const Position& pos, Move m);
|
||||
const std::string time_string(int milliseconds);
|
||||
const std::string score_string(Value v);
|
||||
}
|
||||
@@ -61,206 +62,222 @@ namespace {
|
||||
////
|
||||
|
||||
/// move_to_san() takes a position and a move as input, where it is assumed
|
||||
/// that the move is a legal move from the position. The return value is
|
||||
/// that the move is a legal move from the position. The return value is
|
||||
/// a string containing the move in short algebraic notation.
|
||||
|
||||
const std::string move_to_san(Position &pos, Move m) {
|
||||
std::string str;
|
||||
const std::string move_to_san(const Position& pos, Move m) {
|
||||
|
||||
assert(pos.is_ok());
|
||||
assert(move_is_ok(m));
|
||||
|
||||
if(m == MOVE_NONE) {
|
||||
str = "(none)";
|
||||
return str;
|
||||
}
|
||||
else if(m == MOVE_NULL) {
|
||||
str = "(null)";
|
||||
return str;
|
||||
}
|
||||
else if(move_is_long_castle(m))
|
||||
str = "O-O-O";
|
||||
else if(move_is_short_castle(m))
|
||||
str = "O-O";
|
||||
else {
|
||||
Square from, to;
|
||||
Piece pc;
|
||||
std::string san = "";
|
||||
|
||||
from = move_from(m);
|
||||
to = move_to(m);
|
||||
pc = pos.piece_on(move_from(m));
|
||||
|
||||
str = "";
|
||||
|
||||
if(type_of_piece(pc) == PAWN) {
|
||||
if(pos.move_is_capture(m))
|
||||
str += file_to_char(square_file(move_from(m)));
|
||||
}
|
||||
else {
|
||||
str += piece_type_to_char(type_of_piece(pc), true);
|
||||
|
||||
Ambiguity amb = move_ambiguity(pos, m);
|
||||
switch(amb) {
|
||||
|
||||
case AMBIGUITY_NONE:
|
||||
break;
|
||||
|
||||
case AMBIGUITY_FILE:
|
||||
str += file_to_char(square_file(from));
|
||||
break;
|
||||
|
||||
case AMBIGUITY_RANK:
|
||||
str += rank_to_char(square_rank(from));
|
||||
break;
|
||||
|
||||
case AMBIGUITY_BOTH:
|
||||
str += square_to_string(from);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
if (m == MOVE_NONE)
|
||||
return "(none)";
|
||||
else if (m == MOVE_NULL)
|
||||
return "(null)";
|
||||
else if (move_is_long_castle(m))
|
||||
san = "O-O-O";
|
||||
else if (move_is_short_castle(m))
|
||||
san = "O-O";
|
||||
else
|
||||
{
|
||||
Piece pc = pos.piece_on(move_from(m));
|
||||
if (type_of_piece(pc) != PAWN)
|
||||
{
|
||||
san += piece_type_to_char(type_of_piece(pc), true);
|
||||
Square from = move_from(m);
|
||||
switch (move_ambiguity(pos, m)) {
|
||||
case AMBIGUITY_NONE:
|
||||
break;
|
||||
case AMBIGUITY_FILE:
|
||||
san += file_to_char(square_file(from));
|
||||
break;
|
||||
case AMBIGUITY_RANK:
|
||||
san += rank_to_char(square_rank(from));
|
||||
break;
|
||||
case AMBIGUITY_BOTH:
|
||||
san += square_to_string(from);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
if (pos.move_is_capture(m))
|
||||
{
|
||||
if (type_of_piece(pc) == PAWN)
|
||||
san += file_to_char(square_file(move_from(m)));
|
||||
san += "x";
|
||||
}
|
||||
san += square_to_string(move_to(m));
|
||||
if (move_promotion(m))
|
||||
{
|
||||
san += '=';
|
||||
san += piece_type_to_char(move_promotion(m), true);
|
||||
}
|
||||
}
|
||||
|
||||
if(pos.move_is_capture(m))
|
||||
str += "x";
|
||||
|
||||
str += square_to_string(move_to(m));
|
||||
|
||||
if(move_promotion(m)) {
|
||||
str += "=";
|
||||
str += piece_type_to_char(move_promotion(m), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Is the move check? We don't use pos.move_is_check(m) here, because
|
||||
// Position::move_is_check doesn't detect all checks (not castling moves,
|
||||
// promotions and en passant captures).
|
||||
UndoInfo u;
|
||||
pos.do_move(m, u);
|
||||
if(pos.is_check())
|
||||
str += pos.is_mate()? "#" : "+";
|
||||
pos.undo_move(m, u);
|
||||
Position p(pos);
|
||||
p.do_move(m, u);
|
||||
if (p.is_check())
|
||||
san += p.is_mate()? "#" : "+";
|
||||
|
||||
return str;
|
||||
return san;
|
||||
}
|
||||
|
||||
|
||||
/// move_from_san() takes a position and a string as input, and tries to
|
||||
/// interpret the string as a move in short algebraic notation. On success,
|
||||
/// interpret the string as a move in short algebraic notation. On success,
|
||||
/// the move is returned. On failure (i.e. if the string is unparsable, or
|
||||
/// if the move is illegal or ambiguous), MOVE_NONE is returned.
|
||||
|
||||
Move move_from_san(Position &pos, const std::string &movestr) {
|
||||
Move move_from_san(const Position& pos, const std::string& movestr) {
|
||||
|
||||
assert(pos.is_ok());
|
||||
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE,
|
||||
MOVE_NONE, OnePly);
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, EmptySearchStack, OnePly);
|
||||
|
||||
// Castling moves
|
||||
if(movestr == "O-O-O") {
|
||||
Move m;
|
||||
while((m = mp.get_next_move()) != MOVE_NONE)
|
||||
if(move_is_long_castle(m) && pos.pl_move_is_legal(m))
|
||||
return m;
|
||||
return MOVE_NONE;
|
||||
}
|
||||
else if(movestr == "O-O") {
|
||||
Move m;
|
||||
while((m = mp.get_next_move()) != MOVE_NONE)
|
||||
if(move_is_short_castle(m) && pos.pl_move_is_legal(m))
|
||||
return m;
|
||||
return MOVE_NONE;
|
||||
}
|
||||
if (movestr == "O-O-O" || movestr == "O-O-O+")
|
||||
{
|
||||
Move m;
|
||||
while ((m = mp.get_next_move()) != MOVE_NONE)
|
||||
if (move_is_long_castle(m) && pos.pl_move_is_legal(m))
|
||||
return m;
|
||||
|
||||
// Normal moves
|
||||
const char *cstr = movestr.c_str();
|
||||
const char *c;
|
||||
char *cc;
|
||||
char str[10];
|
||||
int i;
|
||||
|
||||
// Initialize str[] by making a copy of movestr with the characters
|
||||
// 'x', '=', '+' and '#' removed.
|
||||
cc = str;
|
||||
for(i=0, c=cstr; i<10 && *c!='\0' && *c!='\n' && *c!=' '; i++, c++)
|
||||
if(!strchr("x=+#", *c)) {
|
||||
*cc = strchr("nrq", *c)? toupper(*c) : *c;
|
||||
cc++;
|
||||
}
|
||||
*cc = '\0';
|
||||
|
||||
size_t left = 0, right = strlen(str) - 1;
|
||||
PieceType pt = NO_PIECE_TYPE, promotion;
|
||||
Square to;
|
||||
File fromFile = FILE_NONE;
|
||||
Rank fromRank = RANK_NONE;
|
||||
|
||||
// Promotion?
|
||||
if(strchr("BNRQ", str[right])) {
|
||||
promotion = piece_type_from_char(str[right]);
|
||||
right--;
|
||||
}
|
||||
else
|
||||
promotion = NO_PIECE_TYPE;
|
||||
|
||||
// Find the moving piece:
|
||||
if(left < right) {
|
||||
if(strchr("BNRQK", str[left])) {
|
||||
pt = piece_type_from_char(str[left]);
|
||||
left++;
|
||||
}
|
||||
else
|
||||
pt = PAWN;
|
||||
}
|
||||
|
||||
// Find the to square:
|
||||
if(left < right) {
|
||||
if(str[right] < '1' || str[right] > '8' ||
|
||||
str[right-1] < 'a' || str[right-1] > 'h')
|
||||
return MOVE_NONE;
|
||||
to = make_square(file_from_char(str[right-1]), rank_from_char(str[right]));
|
||||
right -= 2;
|
||||
}
|
||||
else
|
||||
else if (movestr == "O-O" || movestr == "O-O+")
|
||||
{
|
||||
Move m;
|
||||
while ((m = mp.get_next_move()) != MOVE_NONE)
|
||||
if (move_is_short_castle(m) && pos.pl_move_is_legal(m))
|
||||
return m;
|
||||
|
||||
return MOVE_NONE;
|
||||
|
||||
// Find the file and/or rank of the from square:
|
||||
if(left <= right) {
|
||||
if(strchr("abcdefgh", str[left])) {
|
||||
fromFile = file_from_char(str[left]);
|
||||
left++;
|
||||
}
|
||||
if(strchr("12345678", str[left]))
|
||||
fromRank = rank_from_char(str[left]);
|
||||
}
|
||||
|
||||
// Look for a matching move:
|
||||
// Normal moves. We use a simple FSM to parse the san string.
|
||||
enum { START, TO_FILE, TO_RANK, PROMOTION_OR_CHECK, PROMOTION, CHECK, END };
|
||||
static const std::string pieceLetters = "KQRBN";
|
||||
PieceType pt = NO_PIECE_TYPE, promotion = NO_PIECE_TYPE;
|
||||
File fromFile = FILE_NONE, toFile = FILE_NONE;
|
||||
Rank fromRank = RANK_NONE, toRank = RANK_NONE;
|
||||
Square to;
|
||||
int state = START;
|
||||
|
||||
for (size_t i = 0; i < movestr.length(); i++)
|
||||
{
|
||||
char type, c = movestr[i];
|
||||
if (pieceLetters.find(c) != std::string::npos)
|
||||
type = 'P';
|
||||
else if (c >= 'a' && c <= 'h')
|
||||
type = 'F';
|
||||
else if (c >= '1' && c <= '8')
|
||||
type = 'R';
|
||||
else
|
||||
type = c;
|
||||
|
||||
switch (type) {
|
||||
case 'P':
|
||||
if (state == START)
|
||||
{
|
||||
pt = piece_type_from_char(c);
|
||||
state = TO_FILE;
|
||||
}
|
||||
else if (state == PROMOTION)
|
||||
{
|
||||
promotion = piece_type_from_char(c);
|
||||
state = (i < movestr.length() - 1) ? CHECK : END;
|
||||
}
|
||||
else
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
case 'F':
|
||||
if (state == START)
|
||||
{
|
||||
pt = PAWN;
|
||||
fromFile = toFile = file_from_char(c);
|
||||
state = TO_RANK;
|
||||
}
|
||||
else if (state == TO_FILE)
|
||||
{
|
||||
toFile = file_from_char(c);
|
||||
state = TO_RANK;
|
||||
}
|
||||
else if (state == TO_RANK && toFile != FILE_NONE)
|
||||
{
|
||||
// Previous file was for disambiguation
|
||||
fromFile = toFile;
|
||||
toFile = file_from_char(c);
|
||||
}
|
||||
else
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
case 'R':
|
||||
if (state == TO_RANK)
|
||||
{
|
||||
toRank = rank_from_char(c);
|
||||
state = (i < movestr.length() - 1) ? PROMOTION_OR_CHECK : END;
|
||||
}
|
||||
else if (state == TO_FILE && fromRank == RANK_NONE)
|
||||
{
|
||||
// It's a disambiguation rank instead of a file
|
||||
fromRank = rank_from_char(c);
|
||||
}
|
||||
else
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
case 'x': case 'X':
|
||||
if (state == TO_RANK)
|
||||
{
|
||||
// Previous file was for disambiguation, or it's a pawn capture
|
||||
fromFile = toFile;
|
||||
state = TO_FILE;
|
||||
}
|
||||
else if (state != TO_FILE)
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
case '=':
|
||||
if (state == PROMOTION_OR_CHECK)
|
||||
state = PROMOTION;
|
||||
else
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
case '+': case '#':
|
||||
if (state == PROMOTION_OR_CHECK || state == CHECK)
|
||||
state = END;
|
||||
else
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
default:
|
||||
return MOVE_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state != END)
|
||||
return MOVE_NONE;
|
||||
|
||||
// Look for a matching move
|
||||
Move m, move = MOVE_NONE;
|
||||
to = make_square(toFile, toRank);
|
||||
int matches = 0;
|
||||
|
||||
while((m = mp.get_next_move()) != MOVE_NONE) {
|
||||
bool match = true;
|
||||
if(pos.type_of_piece_on(move_from(m)) != pt)
|
||||
match = false;
|
||||
else if(move_to(m) != to)
|
||||
match = false;
|
||||
else if(move_promotion(m) != promotion)
|
||||
match = false;
|
||||
else if(fromFile != FILE_NONE && fromFile != square_file(move_from(m)))
|
||||
match = false;
|
||||
else if(fromRank != RANK_NONE && fromRank != square_rank(move_from(m)))
|
||||
match = false;
|
||||
if(match) {
|
||||
move = m;
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
|
||||
if(matches == 1)
|
||||
return move;
|
||||
else
|
||||
return MOVE_NONE;
|
||||
while ((m = mp.get_next_move()) != MOVE_NONE)
|
||||
if ( pos.type_of_piece_on(move_from(m)) == pt
|
||||
&& move_to(m) == to
|
||||
&& move_promotion(m) == promotion
|
||||
&& (fromFile == FILE_NONE || fromFile == square_file(move_from(m)))
|
||||
&& (fromRank == RANK_NONE || fromRank == square_rank(move_from(m))))
|
||||
{
|
||||
move = m;
|
||||
matches++;
|
||||
}
|
||||
return (matches == 1 ? move : MOVE_NONE);
|
||||
}
|
||||
|
||||
|
||||
@@ -271,34 +288,31 @@ Move move_from_san(Position &pos, const std::string &movestr) {
|
||||
/// length of 80 characters. After a line break, 'startColumn' spaces are
|
||||
/// inserted at the beginning of the new line.
|
||||
|
||||
const std::string line_to_san(const Position &pos, Move line[], int startColumn,
|
||||
bool breakLines) {
|
||||
Position p = Position(pos);
|
||||
const std::string line_to_san(const Position& pos, Move line[], int startColumn, bool breakLines) {
|
||||
|
||||
UndoInfo u;
|
||||
std::stringstream s;
|
||||
std::string moveStr;
|
||||
size_t length, maxLength;
|
||||
size_t length = 0;
|
||||
size_t maxLength = 80 - startColumn;
|
||||
Position p(pos);
|
||||
|
||||
length = 0;
|
||||
maxLength = 80 - startColumn;
|
||||
for (int i = 0; line[i] != MOVE_NONE; i++)
|
||||
{
|
||||
moveStr = move_to_san(p, line[i]);
|
||||
length += moveStr.length() + 1;
|
||||
if (breakLines && length > maxLength)
|
||||
{
|
||||
s << '\n' << std::setw(startColumn) << ' ';
|
||||
length = moveStr.length() + 1;
|
||||
}
|
||||
s << moveStr << ' ';
|
||||
|
||||
for(int i = 0; line[i] != MOVE_NONE; i++) {
|
||||
moveStr = move_to_san(p, line[i]);
|
||||
length += moveStr.length() + 1;
|
||||
if(breakLines && length > maxLength) {
|
||||
s << "\n";
|
||||
for(int j = 0; j < startColumn; j++)
|
||||
s << " ";
|
||||
length = moveStr.length() + 1;
|
||||
}
|
||||
s << moveStr << " ";
|
||||
|
||||
if(line[i] == MOVE_NULL)
|
||||
p.do_null_move(u);
|
||||
else
|
||||
p.do_move(line[i], u);
|
||||
if (line[i] == MOVE_NULL)
|
||||
p.do_null_move(u);
|
||||
else
|
||||
p.do_move(line[i], u);
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
@@ -307,26 +321,26 @@ const std::string line_to_san(const Position &pos, Move line[], int startColumn,
|
||||
/// It is used to write search information to the log file (which is created
|
||||
/// when the UCI parameter "Use Search Log" is "true").
|
||||
|
||||
const std::string pretty_pv(const Position &pos, int time, int depth,
|
||||
const std::string pretty_pv(const Position& pos, int time, int depth,
|
||||
uint64_t nodes, Value score, Move pv[]) {
|
||||
std::stringstream s;
|
||||
|
||||
// Depth
|
||||
s << std::setw(2) << std::setfill(' ') << depth << " ";
|
||||
s << std::setw(2) << depth << " ";
|
||||
|
||||
// Score
|
||||
s << std::setw(8) << score_string(score);
|
||||
|
||||
// Time
|
||||
s << std::setw(8) << std::setfill(' ') << time_string(time) << " ";
|
||||
s << std::setw(8) << time_string(time) << " ";
|
||||
|
||||
// Nodes
|
||||
if(nodes < 1000000ULL)
|
||||
s << std::setw(8) << std::setfill(' ') << nodes << " ";
|
||||
else if(nodes < 1000000000ULL)
|
||||
s << std::setw(7) << std::setfill(' ') << nodes/1000ULL << 'k' << " ";
|
||||
if (nodes < 1000000ULL)
|
||||
s << std::setw(8) << nodes << " ";
|
||||
else if (nodes < 1000000000ULL)
|
||||
s << std::setw(7) << nodes/1000ULL << 'k' << " ";
|
||||
else
|
||||
s << std::setw(7) << std::setfill(' ') << nodes/1000000ULL << 'M' << " ";
|
||||
s << std::setw(7) << nodes/1000000ULL << 'M' << " ";
|
||||
|
||||
// PV
|
||||
s << line_to_san(pos, pv, 30, true);
|
||||
@@ -337,82 +351,80 @@ const std::string pretty_pv(const Position &pos, int time, int depth,
|
||||
|
||||
namespace {
|
||||
|
||||
Ambiguity move_ambiguity(Position &pos, Move m) {
|
||||
Square from, to;
|
||||
Piece pc;
|
||||
Ambiguity move_ambiguity(const Position& pos, Move m) {
|
||||
|
||||
from = move_from(m);
|
||||
to = move_to(m);
|
||||
pc = pos.piece_on(from);
|
||||
Square from = move_from(m);
|
||||
Square to = move_to(m);
|
||||
Piece pc = pos.piece_on(from);
|
||||
|
||||
// King moves are never ambiguous, because there is never two kings of
|
||||
// the same color.
|
||||
if(type_of_piece(pc) == KING)
|
||||
return AMBIGUITY_NONE;
|
||||
if (type_of_piece(pc) == KING)
|
||||
return AMBIGUITY_NONE;
|
||||
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE,
|
||||
MOVE_NONE, OnePly);
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, EmptySearchStack, OnePly);
|
||||
Move mv, moveList[8];
|
||||
int i, j, n;
|
||||
|
||||
n = 0;
|
||||
while((mv = mp.get_next_move()) != MOVE_NONE)
|
||||
if(move_to(mv) == to && pos.piece_on(move_from(mv)) == pc
|
||||
&& pos.pl_move_is_legal(mv))
|
||||
moveList[n++] = mv;
|
||||
if(n == 1)
|
||||
return AMBIGUITY_NONE;
|
||||
int n = 0;
|
||||
while ((mv = mp.get_next_move()) != MOVE_NONE)
|
||||
if (move_to(mv) == to && pos.piece_on(move_from(mv)) == pc && pos.pl_move_is_legal(mv))
|
||||
moveList[n++] = mv;
|
||||
|
||||
j = 0;
|
||||
for(i = 0; i < n; i++)
|
||||
if(square_file(move_from(moveList[i])) == square_file(from))
|
||||
j++;
|
||||
if(j == 1)
|
||||
return AMBIGUITY_FILE;
|
||||
if (n == 1)
|
||||
return AMBIGUITY_NONE;
|
||||
|
||||
j = 0;
|
||||
for(i = 0; i < n; i++)
|
||||
if(square_rank(move_from(moveList[i])) == square_rank(from))
|
||||
j++;
|
||||
if(j == 1)
|
||||
return AMBIGUITY_RANK;
|
||||
int f = 0, r = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (square_file(move_from(moveList[i])) == square_file(from))
|
||||
f++;
|
||||
|
||||
if (square_rank(move_from(moveList[i])) == square_rank(from))
|
||||
r++;
|
||||
}
|
||||
if (f == 1)
|
||||
return AMBIGUITY_FILE;
|
||||
|
||||
if (r == 1)
|
||||
return AMBIGUITY_RANK;
|
||||
|
||||
return AMBIGUITY_BOTH;
|
||||
}
|
||||
|
||||
|
||||
const std::string time_string(int milliseconds) {
|
||||
|
||||
std::stringstream s;
|
||||
s << std::setfill('0');
|
||||
|
||||
int hours = milliseconds / (1000 * 60 * 60);
|
||||
int minutes = (milliseconds - hours*1000*60*60) / (60*1000);
|
||||
int seconds = (milliseconds - hours*1000*60*60 - minutes*60*1000) / 1000;
|
||||
int hours = milliseconds / (1000*60*60);
|
||||
int minutes = (milliseconds - hours*1000*60*60) / (1000*60);
|
||||
int seconds = (milliseconds - hours*1000*60*60 - minutes*1000*60) / 1000;
|
||||
|
||||
if(hours)
|
||||
s << hours << ':';
|
||||
s << std::setw(2) << std::setfill('0') << minutes << ':';
|
||||
s << std::setw(2) << std::setfill('0') << seconds;
|
||||
if (hours)
|
||||
s << hours << ':';
|
||||
|
||||
s << std::setw(2) << minutes << ':' << std::setw(2) << seconds;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
|
||||
const std::string score_string(Value v) {
|
||||
|
||||
std::stringstream s;
|
||||
|
||||
if(abs(v) >= VALUE_MATE - 200) {
|
||||
if(v < 0)
|
||||
s << "-#" << (VALUE_MATE + v) / 2;
|
||||
else
|
||||
if (v >= VALUE_MATE - 200)
|
||||
s << "#" << (VALUE_MATE - v + 1) / 2;
|
||||
}
|
||||
else {
|
||||
float floatScore = float(v) / float(PawnValueMidgame);
|
||||
if(v >= 0)
|
||||
s << '+';
|
||||
s << std::setprecision(2) << std::fixed << floatScore;
|
||||
else if(v <= -VALUE_MATE + 200)
|
||||
s << "-#" << (VALUE_MATE + v) / 2;
|
||||
else
|
||||
{
|
||||
float floatScore = float(v) / float(PawnValueMidgame);
|
||||
if (v >= 0)
|
||||
s << '+';
|
||||
|
||||
s << std::setprecision(2) << std::fixed << floatScore;
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
11
src/san.h
11
src/san.h
@@ -36,12 +36,9 @@
|
||||
//// Prototypes
|
||||
////
|
||||
|
||||
extern const std::string move_to_san(Position &pos, Move m);
|
||||
extern Move move_from_san(Position &pos, const std::string &str);
|
||||
extern const std::string line_to_san(const Position &pos, Move line[],
|
||||
int startColumn, bool breakLines);
|
||||
extern const std::string pretty_pv(const Position &pos, int time, int depth,
|
||||
uint64_t nodes, Value score, Move pv[]);
|
||||
|
||||
extern const std::string move_to_san(const Position& pos, Move m);
|
||||
extern Move move_from_san(const Position& pos, const std::string& str);
|
||||
extern const std::string line_to_san(const Position& pos, Move line[], int startColumn, bool breakLines);
|
||||
extern const std::string pretty_pv(const Position& pos, int time, int depth, uint64_t nodes, Value score, Move pv[]);
|
||||
|
||||
#endif // !defined(SAN_H_INCLUDED)
|
||||
|
||||
296
src/search.cpp
296
src/search.cpp
@@ -106,6 +106,9 @@ namespace {
|
||||
const bool UseIIDAtPVNodes = true;
|
||||
const bool UseIIDAtNonPVNodes = false;
|
||||
|
||||
// Use null move driven internal iterative deepening?
|
||||
bool UseNullDrivenIID = true;
|
||||
|
||||
// Internal iterative deepening margin. At Non-PV moves, when
|
||||
// UseIIDAtNonPVNodes is true, we do an internal iterative deepening search
|
||||
// when the static evaluation is at most IIDMargin below beta.
|
||||
@@ -144,7 +147,7 @@ namespace {
|
||||
bool UseFutilityPruning = true;
|
||||
|
||||
// Margins for futility pruning in the quiescence search, at frontier
|
||||
// nodes, and at pre-frontier nodes:
|
||||
// nodes, and at pre-frontier nodes
|
||||
Value FutilityMargin0 = Value(0x80);
|
||||
Value FutilityMargin1 = Value(0x100);
|
||||
Value FutilityMargin2 = Value(0x300);
|
||||
@@ -167,27 +170,28 @@ namespace {
|
||||
Depth PawnEndgameExtension[2] = {OnePly, OnePly};
|
||||
Depth MateThreatExtension[2] = {Depth(0), Depth(0)};
|
||||
|
||||
// Search depth at iteration 1:
|
||||
// Search depth at iteration 1
|
||||
const Depth InitialDepth = OnePly /*+ OnePly/2*/;
|
||||
|
||||
// Node counters
|
||||
int NodesSincePoll;
|
||||
int NodesBetweenPolls = 30000;
|
||||
|
||||
// Iteration counter:
|
||||
// Iteration counter
|
||||
int Iteration;
|
||||
bool LastIterations;
|
||||
|
||||
// Scores and number of times the best move changed for each iteration:
|
||||
Value ValueByIteration[PLY_MAX_PLUS_2];
|
||||
int BestMoveChangesByIteration[PLY_MAX_PLUS_2];
|
||||
|
||||
// MultiPV mode:
|
||||
// MultiPV mode
|
||||
int MultiPV = 1;
|
||||
|
||||
// Time managment variables
|
||||
int SearchStartTime;
|
||||
int MaxNodes, MaxDepth;
|
||||
int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime, TimeAdvantage;
|
||||
int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime;
|
||||
Move BestRootMove, PonderMove, EasyMove;
|
||||
int RootMoveNumber;
|
||||
bool InfiniteSearch;
|
||||
@@ -237,19 +241,20 @@ namespace {
|
||||
Depth depth, int ply, int threadID);
|
||||
void sp_search(SplitPoint *sp, int threadID);
|
||||
void sp_search_pv(SplitPoint *sp, int threadID);
|
||||
void init_search_stack(SearchStack& ss);
|
||||
void init_search_stack(SearchStack ss[]);
|
||||
void init_node(const Position &pos, SearchStack ss[], int ply, int threadID);
|
||||
void update_pv(SearchStack ss[], int ply);
|
||||
void sp_update_pv(SearchStack *pss, SearchStack ss[], int ply);
|
||||
bool connected_moves(const Position &pos, Move m1, Move m2);
|
||||
Depth extension(const Position &pos, Move m, bool pvNode, bool check,
|
||||
bool singleReply, bool mateThreat);
|
||||
bool move_is_killer(Move m, const SearchStack& ss);
|
||||
Depth extension(const Position &pos, Move m, bool pvNode, bool check, bool singleReply, bool mateThreat, bool* dangerous);
|
||||
bool ok_to_do_nullmove(const Position &pos);
|
||||
bool ok_to_prune(const Position &pos, Move m, Move threat, Depth d);
|
||||
bool ok_to_use_TT(const TTEntry* tte, Depth depth, Value beta, int ply);
|
||||
bool ok_to_history(const Position &pos, Move m);
|
||||
void update_history(const Position& pos, Move m, Depth depth,
|
||||
Move movesSearched[], int moveCount);
|
||||
void update_history(const Position& pos, Move m, Depth depth, Move movesSearched[], int moveCount);
|
||||
void update_killers(Move m, SearchStack& ss);
|
||||
|
||||
bool fail_high_ply_1();
|
||||
int current_search_time();
|
||||
@@ -297,6 +302,9 @@ Lock IOLock;
|
||||
|
||||
History H; // Should be made local?
|
||||
|
||||
// The empty search stack
|
||||
SearchStack EmptySearchStack;
|
||||
|
||||
|
||||
////
|
||||
//// Functions
|
||||
@@ -385,6 +393,7 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
|
||||
if (UseLogFile)
|
||||
LogFile.open(get_option_value_string("Search Log Filename").c_str(), std::ios::out | std::ios::app);
|
||||
|
||||
UseNullDrivenIID = get_option_value_bool("Null driven IID");
|
||||
UseQSearchFutilityPruning = get_option_value_bool("Futility Pruning (Quiescence Search)");
|
||||
UseFutilityPruning = get_option_value_bool("Futility Pruning (Main Search)");
|
||||
|
||||
@@ -422,16 +431,14 @@ void think(const Position &pos, bool infinite, bool ponder, int side_to_move,
|
||||
int myIncrement = increment[side_to_move];
|
||||
int oppTime = time[1 - side_to_move];
|
||||
|
||||
TimeAdvantage = myTime - oppTime;
|
||||
|
||||
if (!movesToGo) // Sudden death time control
|
||||
{
|
||||
if (increment)
|
||||
{
|
||||
if (myIncrement)
|
||||
{
|
||||
MaxSearchTime = myTime / 30 + myIncrement;
|
||||
AbsoluteMaxSearchTime = Max(myTime / 4, myIncrement - 100);
|
||||
} else { // Blitz game without increment
|
||||
MaxSearchTime = myTime / 40;
|
||||
MaxSearchTime = myTime / 30;
|
||||
AbsoluteMaxSearchTime = myTime / 8;
|
||||
}
|
||||
}
|
||||
@@ -561,6 +568,9 @@ void init_threads() {
|
||||
// Wait until the thread has finished launching:
|
||||
while (!Threads[i].running);
|
||||
}
|
||||
|
||||
// Init also the empty search stack
|
||||
init_search_stack(EmptySearchStack);
|
||||
}
|
||||
|
||||
|
||||
@@ -617,6 +627,7 @@ namespace {
|
||||
ValueByIteration[0] = Value(0);
|
||||
ValueByIteration[1] = rml.get_move_score(0);
|
||||
Iteration = 1;
|
||||
LastIterations = false;
|
||||
|
||||
EasyMove = rml.scan_for_easy_move();
|
||||
|
||||
@@ -671,9 +682,8 @@ namespace {
|
||||
ExtraSearchTime = BestMoveChangesByIteration[Iteration] * (MaxSearchTime / 2)
|
||||
+ BestMoveChangesByIteration[Iteration-1] * (MaxSearchTime / 3);
|
||||
|
||||
// If we need some more and we are in time advantage take it
|
||||
if (ExtraSearchTime > 0 && TimeAdvantage > 2 * MaxSearchTime)
|
||||
ExtraSearchTime += MaxSearchTime / 2;
|
||||
// Try to guess if the current iteration is the last one or the last two
|
||||
LastIterations = (current_search_time() > ((MaxSearchTime + ExtraSearchTime)*58) / 128);
|
||||
|
||||
// Stop search if most of MaxSearchTime is consumed at the end of the
|
||||
// iteration. We probably don't have enough time to search the first
|
||||
@@ -766,7 +776,8 @@ namespace {
|
||||
<< " currmovenumber " << i + 1 << std::endl;
|
||||
|
||||
// Decide search depth for this move
|
||||
ext = extension(pos, move, true, pos.move_is_check(move), false, false);
|
||||
bool dangerous;
|
||||
ext = extension(pos, move, true, pos.move_is_check(move), false, false, &dangerous);
|
||||
newDepth = (Iteration - 2) * OnePly + ext + InitialDepth;
|
||||
|
||||
// Make the move, and search it
|
||||
@@ -783,7 +794,7 @@ namespace {
|
||||
|
||||
if (Problem && StopOnPonderhit)
|
||||
StopOnPonderhit = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = -search(pos, ss, -alpha, newDepth, 1, true, 0);
|
||||
@@ -933,16 +944,14 @@ namespace {
|
||||
|
||||
// Initialize a MovePicker object for the current position, and prepare
|
||||
// to search all moves
|
||||
MovePicker mp = MovePicker(pos, true, ttMove, ss[ply].mateKiller,
|
||||
ss[ply].killer1, ss[ply].killer2, depth);
|
||||
MovePicker mp = MovePicker(pos, true, ttMove, ss[ply], depth);
|
||||
|
||||
Move move, movesSearched[256];
|
||||
int moveCount = 0;
|
||||
Value value, bestValue = -VALUE_INFINITE;
|
||||
Bitboard dcCandidates = mp.discovered_check_candidates();
|
||||
bool isCheck = pos.is_check();
|
||||
bool mateThreat = MateThreatExtension[1] > Depth(0)
|
||||
&& pos.has_mate_threat(opposite_color(pos.side_to_move()));
|
||||
bool mateThreat = pos.has_mate_threat(opposite_color(pos.side_to_move()));
|
||||
|
||||
// Loop through all legal moves until no moves remain or a beta cutoff
|
||||
// occurs.
|
||||
@@ -955,7 +964,6 @@ namespace {
|
||||
bool singleReply = (isCheck && mp.number_of_moves() == 1);
|
||||
bool moveIsCheck = pos.move_is_check(move, dcCandidates);
|
||||
bool moveIsCapture = pos.move_is_capture(move);
|
||||
bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move);
|
||||
|
||||
movesSearched[moveCount++] = ss[ply].currentMove = move;
|
||||
|
||||
@@ -967,7 +975,8 @@ namespace {
|
||||
ss[ply].currentMoveCaptureValue = Value(0);
|
||||
|
||||
// Decide the new search depth
|
||||
Depth ext = extension(pos, move, true, moveIsCheck, singleReply, mateThreat);
|
||||
bool dangerous;
|
||||
Depth ext = extension(pos, move, true, moveIsCheck, singleReply, mateThreat, &dangerous);
|
||||
Depth newDepth = depth - OnePly + ext;
|
||||
|
||||
// Make and search the move
|
||||
@@ -981,14 +990,12 @@ namespace {
|
||||
// Try to reduce non-pv search depth by one ply if move seems not problematic,
|
||||
// if the move fails high will be re-searched at full depth.
|
||||
if ( depth >= 2*OnePly
|
||||
&& ext == Depth(0)
|
||||
&& moveCount >= LMRPVMoves
|
||||
&& !dangerous
|
||||
&& !moveIsCapture
|
||||
&& !move_promotion(move)
|
||||
&& !moveIsPassedPawnPush
|
||||
&& !move_is_castle(move)
|
||||
&& move != ss[ply].killer1
|
||||
&& move != ss[ply].killer2)
|
||||
&& !move_is_killer(move, ss[ply]))
|
||||
{
|
||||
ss[ply].reduction = OnePly;
|
||||
value = -search(pos, ss, -alpha, newDepth-OnePly, ply+1, true, threadID);
|
||||
@@ -1070,11 +1077,7 @@ namespace {
|
||||
if (ok_to_history(pos, m)) // Only non capture moves are considered
|
||||
{
|
||||
update_history(pos, m, depth, movesSearched, moveCount);
|
||||
if (m != ss[ply].killer1)
|
||||
{
|
||||
ss[ply].killer2 = ss[ply].killer1;
|
||||
ss[ply].killer1 = m;
|
||||
}
|
||||
update_killers(m, ss[ply]);
|
||||
}
|
||||
TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER);
|
||||
}
|
||||
@@ -1125,12 +1128,13 @@ namespace {
|
||||
|
||||
if (tte && ok_to_use_TT(tte, depth, beta, ply))
|
||||
{
|
||||
ss[ply].currentMove = ttMove; // can be MOVE_NONE ?
|
||||
ss[ply].currentMove = ttMove; // can be MOVE_NONE
|
||||
return value_from_tt(tte->value(), ply);
|
||||
}
|
||||
|
||||
Value approximateEval = quick_evaluate(pos);
|
||||
bool mateThreat = false;
|
||||
bool nullDrivenIID = false;
|
||||
bool isCheck = pos.is_check();
|
||||
|
||||
// Null move search
|
||||
@@ -1143,7 +1147,22 @@ namespace {
|
||||
|
||||
UndoInfo u;
|
||||
pos.do_null_move(u);
|
||||
Value nullValue = -search(pos, ss, -(beta-1), depth-4*OnePly, ply+1, false, threadID);
|
||||
int R = (depth > 7 ? 4 : 3);
|
||||
|
||||
Value nullValue = -search(pos, ss, -(beta-1), depth-R*OnePly, ply+1, false, threadID);
|
||||
|
||||
// Check for a null capture artifact, if the value without the null capture
|
||||
// is above beta then there is a good possibility that this is a cut-node.
|
||||
// We will do an IID later to find a ttMove.
|
||||
if ( UseNullDrivenIID
|
||||
&& nullValue < beta
|
||||
&& depth > 6 * OnePly
|
||||
&& ttMove == MOVE_NONE
|
||||
&& ss[ply + 1].currentMove != MOVE_NONE
|
||||
&& pos.move_is_capture(ss[ply + 1].currentMove)
|
||||
&& pos.see(ss[ply + 1].currentMove) * PawnValueMidgame + nullValue > beta - IIDMargin)
|
||||
nullDrivenIID = true;
|
||||
|
||||
pos.undo_null_move(u);
|
||||
|
||||
if (nullValue >= beta)
|
||||
@@ -1163,8 +1182,10 @@ namespace {
|
||||
// low score (which will cause the reduced move to fail high in the
|
||||
// parent node, which will trigger a re-search with full depth).
|
||||
if (nullValue == value_mated_in(ply + 2))
|
||||
{
|
||||
mateThreat = true;
|
||||
|
||||
nullDrivenIID = false;
|
||||
}
|
||||
ss[ply].threatMove = ss[ply + 1].currentMove;
|
||||
if ( depth < ThreatDepth
|
||||
&& ss[ply - 1].reduction
|
||||
@@ -1188,11 +1209,23 @@ namespace {
|
||||
search(pos, ss, beta, Min(depth/2, depth-2*OnePly), ply, false, threadID);
|
||||
ttMove = ss[ply].pv[ply];
|
||||
}
|
||||
else if (nullDrivenIID)
|
||||
{
|
||||
// The null move failed low due to a suspicious capture. Perhaps we
|
||||
// are facing a null capture artifact due to the side to move change
|
||||
// and this is a cut-node. So it's a good time to search for a ttMove.
|
||||
Move tm = ss[ply].threatMove;
|
||||
|
||||
assert(tm != MOVE_NONE);
|
||||
|
||||
search(pos, ss, beta, Min(depth/2, depth-3*OnePly), ply, false, threadID);
|
||||
ttMove = ss[ply].pv[ply];
|
||||
ss[ply].threatMove = tm;
|
||||
}
|
||||
|
||||
// Initialize a MovePicker object for the current position, and prepare
|
||||
// to search all moves:
|
||||
MovePicker mp = MovePicker(pos, false, ttMove, ss[ply].mateKiller,
|
||||
ss[ply].killer1, ss[ply].killer2, depth);
|
||||
MovePicker mp = MovePicker(pos, false, ttMove, ss[ply], depth);
|
||||
|
||||
Move move, movesSearched[256];
|
||||
int moveCount = 0;
|
||||
@@ -1214,19 +1247,18 @@ namespace {
|
||||
bool singleReply = (isCheck && mp.number_of_moves() == 1);
|
||||
bool moveIsCheck = pos.move_is_check(move, dcCandidates);
|
||||
bool moveIsCapture = pos.move_is_capture(move);
|
||||
bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move);
|
||||
|
||||
movesSearched[moveCount++] = ss[ply].currentMove = move;
|
||||
|
||||
// Decide the new search depth
|
||||
Depth ext = extension(pos, move, false, moveIsCheck, singleReply, mateThreat);
|
||||
bool dangerous;
|
||||
Depth ext = extension(pos, move, false, moveIsCheck, singleReply, mateThreat, &dangerous);
|
||||
Depth newDepth = depth - OnePly + ext;
|
||||
|
||||
// Futility pruning
|
||||
if ( useFutilityPruning
|
||||
&& ext == Depth(0)
|
||||
&& !dangerous
|
||||
&& !moveIsCapture
|
||||
&& !moveIsPassedPawnPush
|
||||
&& !move_promotion(move))
|
||||
{
|
||||
if ( moveCount >= 2 + int(depth)
|
||||
@@ -1254,15 +1286,13 @@ namespace {
|
||||
|
||||
// Try to reduce non-pv search depth by one ply if move seems not problematic,
|
||||
// if the move fails high will be re-searched at full depth.
|
||||
if ( depth >= 2*OnePly
|
||||
&& ext == Depth(0)
|
||||
&& moveCount >= LMRNonPVMoves
|
||||
if ( depth >= 2*OnePly
|
||||
&& moveCount >= LMRNonPVMoves
|
||||
&& !dangerous
|
||||
&& !moveIsCapture
|
||||
&& !move_promotion(move)
|
||||
&& !moveIsPassedPawnPush
|
||||
&& !move_is_castle(move)
|
||||
&& move != ss[ply].killer1
|
||||
&& move != ss[ply].killer2)
|
||||
&& !move_is_killer(move, ss[ply]))
|
||||
{
|
||||
ss[ply].reduction = OnePly;
|
||||
value = -search(pos, ss, -(beta-1), newDepth-OnePly, ply+1, true, threadID);
|
||||
@@ -1321,11 +1351,7 @@ namespace {
|
||||
if (ok_to_history(pos, m)) // Only non capture moves are considered
|
||||
{
|
||||
update_history(pos, m, depth, movesSearched, moveCount);
|
||||
if (m != ss[ply].killer1)
|
||||
{
|
||||
ss[ply].killer2 = ss[ply].killer1;
|
||||
ss[ply].killer1 = m;
|
||||
}
|
||||
update_killers(m, ss[ply]);
|
||||
}
|
||||
TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER);
|
||||
}
|
||||
@@ -1382,12 +1408,13 @@ namespace {
|
||||
// Initialize a MovePicker object for the current position, and prepare
|
||||
// to search the moves. Because the depth is <= 0 here, only captures,
|
||||
// queen promotions and checks (only if depth == 0) will be generated.
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE,
|
||||
MOVE_NONE, depth);
|
||||
MovePicker mp = MovePicker(pos, false, MOVE_NONE, EmptySearchStack, depth, &ei);
|
||||
Move move;
|
||||
int moveCount = 0;
|
||||
Bitboard dcCandidates = mp.discovered_check_candidates();
|
||||
bool isCheck = pos.is_check();
|
||||
bool pvNode = (beta - alpha != 1);
|
||||
bool enoughMaterial = pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame;
|
||||
|
||||
// Loop through the moves until no moves remain or a beta cutoff
|
||||
// occurs.
|
||||
@@ -1396,20 +1423,17 @@ namespace {
|
||||
{
|
||||
assert(move_is_ok(move));
|
||||
|
||||
bool moveIsCheck = pos.move_is_check(move, dcCandidates);
|
||||
bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move);
|
||||
|
||||
moveCount++;
|
||||
ss[ply].currentMove = move;
|
||||
|
||||
// Futility pruning
|
||||
if ( UseQSearchFutilityPruning
|
||||
&& enoughMaterial
|
||||
&& !isCheck
|
||||
&& !moveIsCheck
|
||||
&& !pvNode
|
||||
&& !move_promotion(move)
|
||||
&& !moveIsPassedPawnPush
|
||||
&& beta - alpha == 1
|
||||
&& pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame)
|
||||
&& !pos.move_is_check(move, dcCandidates)
|
||||
&& !pos.move_is_passed_pawn_push(move))
|
||||
{
|
||||
Value futilityValue = staticValue
|
||||
+ Max(pos.midgame_value_of_piece_on(move_to(move)),
|
||||
@@ -1425,7 +1449,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
// Don't search captures and checks with negative SEE values.
|
||||
// Don't search captures and checks with negative SEE values
|
||||
if ( !isCheck
|
||||
&& !move_promotion(move)
|
||||
&& (pos.midgame_value_of_piece_on(move_from(move)) >
|
||||
@@ -1463,6 +1487,13 @@ namespace {
|
||||
// Update transposition table
|
||||
TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, VALUE_TYPE_EXACT);
|
||||
|
||||
// Update killers only for good check moves
|
||||
Move m = ss[ply].currentMove;
|
||||
if (alpha >= beta && ok_to_history(pos, m)) // Only non capture moves are considered
|
||||
{
|
||||
// Wrong to update history when depth is <= 0
|
||||
update_killers(m, ss[ply]);
|
||||
}
|
||||
return bestValue;
|
||||
}
|
||||
|
||||
@@ -1497,7 +1528,6 @@ namespace {
|
||||
|
||||
bool moveIsCheck = pos.move_is_check(move, sp->dcCandidates);
|
||||
bool moveIsCapture = pos.move_is_capture(move);
|
||||
bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move);
|
||||
|
||||
lock_grab(&(sp->lock));
|
||||
int moveCount = ++sp->moves;
|
||||
@@ -1506,14 +1536,14 @@ namespace {
|
||||
ss[sp->ply].currentMove = move;
|
||||
|
||||
// Decide the new search depth.
|
||||
Depth ext = extension(pos, move, false, moveIsCheck, false, false);
|
||||
bool dangerous;
|
||||
Depth ext = extension(pos, move, false, moveIsCheck, false, false, &dangerous);
|
||||
Depth newDepth = sp->depth - OnePly + ext;
|
||||
|
||||
// Prune?
|
||||
if ( useFutilityPruning
|
||||
&& ext == Depth(0)
|
||||
&& !dangerous
|
||||
&& !moveIsCapture
|
||||
&& !moveIsPassedPawnPush
|
||||
&& !move_promotion(move)
|
||||
&& moveCount >= 2 + int(sp->depth)
|
||||
&& ok_to_prune(pos, move, ss[sp->ply].threatMove, sp->depth))
|
||||
@@ -1525,14 +1555,12 @@ namespace {
|
||||
|
||||
// Try to reduce non-pv search depth by one ply if move seems not problematic,
|
||||
// if the move fails high will be re-searched at full depth.
|
||||
if ( ext == Depth(0)
|
||||
if ( !dangerous
|
||||
&& moveCount >= LMRNonPVMoves
|
||||
&& !moveIsCapture
|
||||
&& !moveIsPassedPawnPush
|
||||
&& !move_promotion(move)
|
||||
&& !move_is_castle(move)
|
||||
&& move != ss[sp->ply].killer1
|
||||
&& move != ss[sp->ply].killer2)
|
||||
&& !move_is_killer(move, ss[sp->ply]))
|
||||
{
|
||||
ss[sp->ply].reduction = OnePly;
|
||||
value = -search(pos, ss, -(sp->beta-1), newDepth - OnePly, sp->ply+1, true, threadID);
|
||||
@@ -1610,7 +1638,6 @@ namespace {
|
||||
{
|
||||
bool moveIsCheck = pos.move_is_check(move, sp->dcCandidates);
|
||||
bool moveIsCapture = pos.move_is_capture(move);
|
||||
bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move);
|
||||
|
||||
assert(move_is_ok(move));
|
||||
|
||||
@@ -1624,7 +1651,8 @@ namespace {
|
||||
ss[sp->ply].currentMove = move;
|
||||
|
||||
// Decide the new search depth.
|
||||
Depth ext = extension(pos, move, true, moveIsCheck, false, false);
|
||||
bool dangerous;
|
||||
Depth ext = extension(pos, move, true, moveIsCheck, false, false, &dangerous);
|
||||
Depth newDepth = sp->depth - OnePly + ext;
|
||||
|
||||
// Make and search the move.
|
||||
@@ -1633,14 +1661,12 @@ namespace {
|
||||
|
||||
// Try to reduce non-pv search depth by one ply if move seems not problematic,
|
||||
// if the move fails high will be re-searched at full depth.
|
||||
if ( ext == Depth(0)
|
||||
if ( !dangerous
|
||||
&& moveCount >= LMRPVMoves
|
||||
&& !moveIsCapture
|
||||
&& !moveIsPassedPawnPush
|
||||
&& !move_promotion(move)
|
||||
&& !move_is_castle(move)
|
||||
&& move != ss[sp->ply].killer1
|
||||
&& move != ss[sp->ply].killer2)
|
||||
&& !move_is_killer(move, ss[sp->ply]))
|
||||
{
|
||||
ss[sp->ply].reduction = OnePly;
|
||||
value = -search(pos, ss, -sp->alpha, newDepth - OnePly, sp->ply+1, true, threadID);
|
||||
@@ -1871,17 +1897,28 @@ namespace {
|
||||
|
||||
// init_search_stack() initializes a search stack at the beginning of a
|
||||
// new search from the root.
|
||||
void init_search_stack(SearchStack& ss) {
|
||||
|
||||
ss.pv[0] = MOVE_NONE;
|
||||
ss.pv[1] = MOVE_NONE;
|
||||
ss.currentMove = MOVE_NONE;
|
||||
ss.threatMove = MOVE_NONE;
|
||||
ss.reduction = Depth(0);
|
||||
for (int j = 0; j < KILLER_MAX; j++)
|
||||
ss.killers[j] = MOVE_NONE;
|
||||
}
|
||||
|
||||
void init_search_stack(SearchStack ss[]) {
|
||||
for(int i = 0; i < 3; i++) {
|
||||
ss[i].pv[i] = MOVE_NONE;
|
||||
ss[i].pv[i+1] = MOVE_NONE;
|
||||
ss[i].currentMove = MOVE_NONE;
|
||||
ss[i].mateKiller = MOVE_NONE;
|
||||
ss[i].killer1 = MOVE_NONE;
|
||||
ss[i].killer2 = MOVE_NONE;
|
||||
ss[i].threatMove = MOVE_NONE;
|
||||
ss[i].reduction = Depth(0);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
ss[i].pv[i] = MOVE_NONE;
|
||||
ss[i].pv[i+1] = MOVE_NONE;
|
||||
ss[i].currentMove = MOVE_NONE;
|
||||
ss[i].threatMove = MOVE_NONE;
|
||||
ss[i].reduction = Depth(0);
|
||||
for (int j = 0; j < KILLER_MAX; j++)
|
||||
ss[i].killers[j] = MOVE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1905,13 +1942,13 @@ namespace {
|
||||
NodesSincePoll = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ss[ply].pv[ply] = ss[ply].pv[ply+1] = ss[ply].currentMove = MOVE_NONE;
|
||||
ss[ply+2].mateKiller = MOVE_NONE;
|
||||
ss[ply+2].killer1 = ss[ply+2].killer2 = MOVE_NONE;
|
||||
ss[ply].threatMove = MOVE_NONE;
|
||||
ss[ply].reduction = Depth(0);
|
||||
ss[ply].currentMoveCaptureValue = Value(0);
|
||||
for (int j = 0; j < KILLER_MAX; j++)
|
||||
ss[ply+2].killers[j] = MOVE_NONE;
|
||||
|
||||
if(Threads[threadID].printCurrentLine)
|
||||
print_current_line(ss, ply, threadID);
|
||||
@@ -2014,14 +2051,32 @@ namespace {
|
||||
}
|
||||
|
||||
|
||||
// move_is_killer() checks if the given move is among the
|
||||
// killer moves of that ply.
|
||||
|
||||
bool move_is_killer(Move m, const SearchStack& ss) {
|
||||
|
||||
const Move* k = ss.killers;
|
||||
for (int i = 0; i < KILLER_MAX; i++, k++)
|
||||
if (*k == m)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// extension() decides whether a move should be searched with normal depth,
|
||||
// or with extended depth. Certain classes of moves (checking moves, in
|
||||
// particular) are searched with bigger depth than ordinary moves.
|
||||
// particular) are searched with bigger depth than ordinary moves and in
|
||||
// any case are marked as 'dangerous'. Note that also if a move is not
|
||||
// extended, as example because the corresponding UCI option is set to zero,
|
||||
// the move is marked as 'dangerous' so, at least, we avoid to prune it.
|
||||
|
||||
Depth extension(const Position &pos, Move m, bool pvNode,
|
||||
bool check, bool singleReply, bool mateThreat) {
|
||||
Depth extension(const Position &pos, Move m, bool pvNode, bool check,
|
||||
bool singleReply, bool mateThreat, bool* dangerous) {
|
||||
|
||||
Depth result = Depth(0);
|
||||
*dangerous = check || singleReply || mateThreat;
|
||||
|
||||
if (check)
|
||||
result += CheckExtension[pvNode];
|
||||
@@ -2029,26 +2084,37 @@ namespace {
|
||||
if (singleReply)
|
||||
result += SingleReplyExtension[pvNode];
|
||||
|
||||
if (pos.move_is_pawn_push_to_7th(m))
|
||||
result += PawnPushTo7thExtension[pvNode];
|
||||
|
||||
if (pos.move_is_passed_pawn_push(m))
|
||||
result += PassedPawnExtension[pvNode];
|
||||
|
||||
if (mateThreat)
|
||||
result += MateThreatExtension[pvNode];
|
||||
|
||||
if ( pos.midgame_value_of_piece_on(move_to(m)) >= RookValueMidgame
|
||||
&& ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK)
|
||||
- pos.midgame_value_of_piece_on(move_to(m)) == Value(0))
|
||||
if (pos.move_is_pawn_push_to_7th(m))
|
||||
{
|
||||
result += PawnPushTo7thExtension[pvNode];
|
||||
*dangerous = true;
|
||||
}
|
||||
if (pos.move_is_passed_pawn_push(m))
|
||||
{
|
||||
result += PassedPawnExtension[pvNode];
|
||||
*dangerous = true;
|
||||
}
|
||||
|
||||
if ( pos.midgame_value_of_piece_on(move_to(m)) >= RookValueMidgame
|
||||
&& ( pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK)
|
||||
- pos.midgame_value_of_piece_on(move_to(m)) == Value(0))
|
||||
&& !move_promotion(m))
|
||||
{
|
||||
result += PawnEndgameExtension[pvNode];
|
||||
|
||||
*dangerous = true;
|
||||
}
|
||||
|
||||
if ( pvNode
|
||||
&& pos.move_is_capture(m)
|
||||
&& pos.type_of_piece_on(move_to(m)) != PAWN
|
||||
&& pos.see(m) >= 0)
|
||||
{
|
||||
result += OnePly/2;
|
||||
*dangerous = true;
|
||||
}
|
||||
|
||||
return Min(result, OnePly);
|
||||
}
|
||||
@@ -2137,13 +2203,11 @@ namespace {
|
||||
|
||||
|
||||
// ok_to_history() returns true if a move m can be stored
|
||||
// in history. Should be a non capturing move.
|
||||
// in history. Should be a non capturing move nor a promotion.
|
||||
|
||||
bool ok_to_history(const Position& pos, Move m) {
|
||||
|
||||
return pos.square_is_empty(move_to(m))
|
||||
&& !move_promotion(m)
|
||||
&& !move_is_ep(m);
|
||||
return !pos.move_is_capture(m) && !move_promotion(m);
|
||||
}
|
||||
|
||||
|
||||
@@ -2156,8 +2220,26 @@ namespace {
|
||||
H.success(pos.piece_on(move_from(m)), m, depth);
|
||||
|
||||
for (int i = 0; i < moveCount - 1; i++)
|
||||
if (ok_to_history(pos, movesSearched[i]) && m != movesSearched[i])
|
||||
{
|
||||
assert(m != movesSearched[i]);
|
||||
if (ok_to_history(pos, movesSearched[i]))
|
||||
H.failure(pos.piece_on(move_from(movesSearched[i])), movesSearched[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// update_killers() add a good move that produced a beta-cutoff
|
||||
// among the killer moves of that ply.
|
||||
|
||||
void update_killers(Move m, SearchStack& ss) {
|
||||
|
||||
if (m == ss.killers[0])
|
||||
return;
|
||||
|
||||
for (int i = KILLER_MAX - 1; i > 0; i--)
|
||||
ss.killers[i] = ss.killers[i - 1];
|
||||
|
||||
ss.killers[0] = m;
|
||||
}
|
||||
|
||||
// fail_high_ply_1() checks if some thread is currently resolving a fail
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
const int PLY_MAX = 100;
|
||||
const int PLY_MAX_PLUS_2 = 102;
|
||||
const int KILLER_MAX = 2;
|
||||
|
||||
|
||||
////
|
||||
@@ -56,8 +57,9 @@ struct SearchStack {
|
||||
Move pv[PLY_MAX];
|
||||
Move currentMove;
|
||||
Value currentMoveCaptureValue;
|
||||
Move mateKiller, killer1, killer2;
|
||||
Move mateKiller;
|
||||
Move threatMove;
|
||||
Move killers[KILLER_MAX];
|
||||
Depth reduction;
|
||||
};
|
||||
|
||||
@@ -66,10 +68,9 @@ struct SearchStack {
|
||||
//// Global variables
|
||||
////
|
||||
|
||||
extern SearchStack EmptySearchStack;
|
||||
extern TranspositionTable TT;
|
||||
|
||||
extern int ActiveThreads;
|
||||
|
||||
extern Lock SMPLock;
|
||||
|
||||
// Perhaps better to make H local, and pass as parameter to MovePicker?
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
//// Includes
|
||||
////
|
||||
|
||||
#include <cstdlib> // for abs()
|
||||
#include <string>
|
||||
|
||||
#include "color.h"
|
||||
|
||||
30
src/tt.cpp
30
src/tt.cpp
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "tt.h"
|
||||
|
||||
@@ -107,29 +108,25 @@ void TranspositionTable::store(const Position &pos, Value v, Depth d,
|
||||
TTEntry *tte, *replace;
|
||||
|
||||
tte = replace = first_entry(pos);
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (int i = 0; i < 4; i++, tte++)
|
||||
{
|
||||
if (!(tte+i)->key()) // still empty
|
||||
{
|
||||
*(tte+i) = TTEntry(pos.get_key(), v, type, d, m, generation);
|
||||
writes++;
|
||||
return;
|
||||
}
|
||||
if ((tte+i)->key() == pos.get_key()) // overwrite old
|
||||
if (!tte->key() || tte->key() == pos.get_key()) // empty or overwrite old
|
||||
{
|
||||
if (m == MOVE_NONE)
|
||||
m = (tte+i)->move();
|
||||
m = tte->move();
|
||||
|
||||
*(tte+i) = TTEntry(pos.get_key(), v, type, d, m, generation);
|
||||
*tte = TTEntry(pos.get_key(), v, type, d, m, generation);
|
||||
return;
|
||||
}
|
||||
if ( i == 0 // already is (replace == tte+i), common case
|
||||
|| replace->generation() < (tte+i)->generation())
|
||||
else if (i == 0) // replace would be a no-op in this common case
|
||||
continue;
|
||||
|
||||
if ( replace->generation() > (tte+i)->generation()
|
||||
|| (tte+i)->depth() < replace->depth())
|
||||
replace = tte+i;
|
||||
int c1 = (replace->generation() == generation ? 2 : 0);
|
||||
int c2 = (tte->generation() == generation ? -2 : 0);
|
||||
int c3 = (tte->depth() < replace->depth() ? 1 : 0);
|
||||
|
||||
if (c1 + c2 + c3 > 0)
|
||||
replace = tte;
|
||||
}
|
||||
*replace = TTEntry(pos.get_key(), v, type, d, m, generation);
|
||||
writes++;
|
||||
@@ -144,9 +141,8 @@ const TTEntry* TranspositionTable::retrieve(const Position &pos) const {
|
||||
|
||||
TTEntry *tte = first_entry(pos);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (int i = 0; i < 4; i++, tte++)
|
||||
{
|
||||
tte += i;
|
||||
if (tte->key() == pos.get_key())
|
||||
return tte;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ namespace {
|
||||
o.push_back(Option("Full Depth Moves (non-PV nodes)", 3, 1, 100));
|
||||
o.push_back(Option("Threat Depth", 5, 0, 100));
|
||||
o.push_back(Option("Selective Plies", 7, 0, 10));
|
||||
o.push_back(Option("Null driven IID", true));
|
||||
o.push_back(Option("Futility Pruning (Main Search)", true));
|
||||
o.push_back(Option("Futility Pruning (Quiescence Search)", true));
|
||||
o.push_back(Option("Futility Margin 0", 50, 0, 1000));
|
||||
@@ -176,7 +177,7 @@ namespace {
|
||||
template<typename T>
|
||||
T get_option_value(const std::string& optionName) {
|
||||
|
||||
T ret;
|
||||
T ret = T();
|
||||
Options::iterator it = option_with_name(optionName);
|
||||
|
||||
if (it != options.end())
|
||||
|
||||
Reference in New Issue
Block a user