1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-05-01 09:13:08 +00:00

Merge pull request #2 from nodchip/master

merge
This commit is contained in:
FireFather 2020-06-28 01:37:01 +02:00 committed by GitHub
commit 2f8c692caa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 999 additions and 536 deletions

View file

@ -115,6 +115,7 @@ Nick Pelling (nickpelling)
Nicklas Persson (NicklasPersson) Nicklas Persson (NicklasPersson)
Niklas Fiekas (niklasf) Niklas Fiekas (niklasf)
Nikolay Kostov (NikolayIT) Nikolay Kostov (NikolayIT)
Nguyen Pham
Ondrej Mosnáček (WOnder93) Ondrej Mosnáček (WOnder93)
Oskar Werkelin Ahlin Oskar Werkelin Ahlin
Pablo Vazquez Pablo Vazquez
@ -154,6 +155,7 @@ Tom Vijlbrief (tomtor)
Tomasz Sobczyk (Sopel97) Tomasz Sobczyk (Sopel97)
Torsten Franz (torfranz, tfranzer) Torsten Franz (torfranz, tfranzer)
Tracey Emery (basepr1me) Tracey Emery (basepr1me)
Unai Corzo (unaiic)
Uri Blass (uriblass) Uri Blass (uriblass)
Vince Negri (cuddlestmonkey) Vince Negri (cuddlestmonkey)

View file

@ -165,17 +165,23 @@ are in use, see the engine log.
## Compiling Stockfish yourself from the sources ## Compiling Stockfish yourself from the sources
On Unix-like systems, it should be possible to compile Stockfish Stockfish has support for 32 or 64-bit CPUs, certain hardware
directly from the source code with the included Makefile. instructions, big-endian machines such as Power PC, and other platforms.
Stockfish has support for 32 or 64-bit CPUs, the hardware POPCNT On Unix-like systems, it should be easy to compile Stockfish
instruction, big-endian machines such as Power PC, and other platforms. directly from the source code with the included Makefile in the folder
`src`. In general it is recommended to run `make help` to see a list of make
targets with corresponding descriptions.
In general it is recommended to run `make help` to see a list of make ```
targets with corresponding descriptions. When not using the Makefile to cd src
compile (for instance with Microsoft MSVC) you need to manually make help
set/unset some switches in the compiler command line; see file *types.h* make build ARCH=x86-64-modern
for a quick reference. ```
When not using the Makefile to compile (for instance with Microsoft MSVC) you
need to manually set/unset some switches in the compiler command line; see
file *types.h* for a quick reference.
When reporting an issue or a bug, please tell us which version and When reporting an issue or a bug, please tell us which version and
compiler you used to create your executable. These informations can compiler you used to create your executable. These informations can

52
script/README.md Normal file
View file

@ -0,0 +1,52 @@
# `pgn_to_plain`
This script converts pgn files into text file to apply `learn convert_bin` command. You need to import [python-chess](https://pypi.org/project/python-chess/) to use this script.
pip install python-chess
# Example of Qhapaq's finetune using `pgn_to_plain`
## Download data
You can download data from [here](http://rebel13.nl/index.html)
## Convert pgn files
**Important : convert text will be superheavy (approx 200 byte / position)**
python pgn_to_plain.py --pgn "pgn/*.pgn" --start_ply 1 --output converted_pgn.txt
`--pgn` option supports wildcard. When you use pgn files with elo >= 3300, You will get 1.7 GB text file.
## Convert into training data
### Example build command
make nnue-learn ARCH=x86-64
See `src/Makefile` for detail.
### Convert
./stockfish
learn convert_bin converted_pgn.txt output_file_name pgn_bin.bin
learn shuffle pgn_bin.bin
You also need to prepare validation data for training like following.
python pgn_to_plain.py --pgn "pgn/ccrl-40-15-3400.pgn" --start_ply 1 --output ccrl-40-15-3400.txt
./stockfish
learn convert_bin ccrl-40-15-3400.txt ccrl-40-15-3400_plain.bin
### Learn
./stockfish
setoption name Threads value 8
learn shuffled_sfen.bin newbob_decay 0.5 validation_set_file_name ccrl-40-15-3400_plain.bin nn_batch_size 50000 batchsize 1000000 eval_save_interval 8000000 eta 0.05 lambda 0.0 eval_limit 3000 mirror_percentage 0 use_draw_in_training 1

68
script/pgn_to_plain.py Normal file
View file

@ -0,0 +1,68 @@
import chess.pgn
import argparse
import glob
from typing import List
# todo close in c++ tools using pgn-extract
# https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/help.html#-w
def parse_result(result_str:str, board:chess.Board) -> int:
if result_str == "1/2-1/2":
return 0
if result_str == "0-1":
if board.turn == chess.WHITE:
return -1
else:
return 1
elif result_str == "1-0":
if board.turn == chess.WHITE:
return 1
else:
return 0
else:
print("illeagal result", result_str)
raise ValueError
def game_sanity_check(game: chess.pgn.Game) -> bool:
if not game.headers["Result"] in ["1/2-1/2", "0-1", "1-0"]:
print("invalid result", game.headers["Result"])
return False
return True
def parse_game(game: chess.pgn.Game, writer, start_play: int=1)->None:
board: chess.Board = game.board()
if not game_sanity_check(game):
return
result: str = game.headers["Result"]
for ply, move in enumerate(game.mainline_moves()):
if ply >= start_play:
writer.write("fen " + board.fen() + "\n")
writer.write("move " + str(move) + "\n")
writer.write("score 0\n")
writer.write("ply " + str(ply)+"\n")
writer.write("result " + str(parse_result(result, board)) +"\n")
writer.write("e\n")
board.push(move)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--pgn", type=str, required=True)
parser.add_argument("--start_ply", type=int, default=1)
parser.add_argument("--output", type=str, default="plain.txt")
args = parser.parse_args()
pgn_files: List[str] = glob.glob(args.pgn)
f = open(args.output, 'w')
for pgn_file in pgn_files:
print("parse", pgn_file)
pgn_loader = open(pgn_file)
while True:
game = chess.pgn.read_game(pgn_loader)
if game is None:
break
parse_game(game, f, args.start_ply)
f.close()
if __name__=="__main__":
main()

View file

@ -92,7 +92,7 @@ endif
optimize = yes optimize = yes
debug = no debug = no
sanitize = no sanitize = no
bits = 32 bits = 64
prefetch = no prefetch = no
popcnt = no popcnt = no
sse = no sse = no
@ -100,36 +100,35 @@ avx2 = no
pext = no pext = no
### 2.2 Architecture specific ### 2.2 Architecture specific
ifeq ($(ARCH),general-32) ifeq ($(ARCH),general-32)
arch = any arch = any
bits = 32
endif endif
ifeq ($(ARCH),x86-32-old) ifeq ($(ARCH),x86-32-old)
arch = i386 arch = i386
bits = 32
endif endif
ifeq ($(ARCH),x86-32) ifeq ($(ARCH),x86-32)
arch = i386 arch = i386
bits = 32
prefetch = yes prefetch = yes
sse = yes sse = yes
endif endif
ifeq ($(ARCH),general-64) ifeq ($(ARCH),general-64)
arch = any arch = any
bits = 64
endif endif
ifeq ($(ARCH),x86-64) ifeq ($(ARCH),x86-64)
arch = x86_64 arch = x86_64
bits = 64
prefetch = yes prefetch = yes
sse = yes sse = yes
endif endif
ifeq ($(ARCH),x86-64-modern) ifeq ($(ARCH),x86-64-modern)
arch = x86_64 arch = x86_64
bits = 64
prefetch = yes prefetch = yes
popcnt = yes popcnt = yes
sse = yes sse = yes
@ -146,7 +145,6 @@ endif
ifeq ($(ARCH),x86-64-bmi2) ifeq ($(ARCH),x86-64-bmi2)
arch = x86_64 arch = x86_64
bits = 64
prefetch = yes prefetch = yes
popcnt = yes popcnt = yes
sse = yes sse = yes
@ -157,26 +155,31 @@ endif
ifeq ($(ARCH),armv7) ifeq ($(ARCH),armv7)
arch = armv7 arch = armv7
prefetch = yes prefetch = yes
bits = 32
endif
ifeq ($(ARCH),armv8)
arch = armv8-a
bits = 64
prefetch = yes
endif endif
ifeq ($(ARCH),ppc-32) ifeq ($(ARCH),ppc-32)
arch = ppc arch = ppc
bits = 32
endif endif
ifeq ($(ARCH),ppc-64) ifeq ($(ARCH),ppc-64)
arch = ppc64 arch = ppc64
bits = 64
popcnt = yes popcnt = yes
prefetch = yes prefetch = yes
endif endif
### ========================================================================== ### ==========================================================================
### Section 3. Low-level configuration ### Section 3. Low-level Configuration
### ========================================================================== ### ==========================================================================
### 3.1 Selecting compiler (default = gcc) ### 3.1 Selecting compiler (default = gcc)
CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
DEPENDFLAGS += -std=c++17 DEPENDFLAGS += -std=c++17
LDFLAGS += $(EXTRALDFLAGS) LDFLAGS += $(EXTRALDFLAGS)
@ -190,7 +193,7 @@ ifeq ($(COMP),gcc)
CXX=g++ CXX=g++
CXXFLAGS += -pedantic -Wextra -Wshadow CXXFLAGS += -pedantic -Wextra -Wshadow
ifeq ($(ARCH),armv7) ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
ifeq ($(OS),Android) ifeq ($(OS),Android)
CXXFLAGS += -m$(bits) CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits) LDFLAGS += -m$(bits)
@ -247,7 +250,7 @@ ifeq ($(COMP),clang)
endif endif
endif endif
ifeq ($(ARCH),armv7) ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
ifeq ($(OS),Android) ifeq ($(OS),Android)
CXXFLAGS += -m$(bits) CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits) LDFLAGS += -m$(bits)
@ -391,22 +394,15 @@ ifeq ($(pext),yes)
endif endif
endif endif
### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows. ### 3.8 Link Time Optimization
### This is a mix of compile and link time options because the lto link phase ### This is a mix of compile and link time options because the lto link phase
### needs access to the optimization flags. ### needs access to the optimization flags.
ifeq ($(optimize),yes) ifeq ($(optimize),yes)
ifeq ($(debug), no) ifeq ($(debug), no)
ifeq ($(comp),$(filter $(comp),gcc clang msys2)) ifeq ($(comp),$(filter $(comp),gcc clang mingw))
CXXFLAGS += -flto CXXFLAGS += -flto
LDFLAGS += $(CXXFLAGS) LDFLAGS += $(CXXFLAGS)
endif endif
ifeq ($(comp),mingw)
ifeq ($(KERNEL),Linux)
CXXFLAGS += -flto
LDFLAGS += $(CXXFLAGS)
endif
endif
endif endif
endif endif
@ -417,9 +413,8 @@ ifeq ($(OS), Android)
LDFLAGS += -fPIE -pie LDFLAGS += -fPIE -pie
endif endif
### ========================================================================== ### ==========================================================================
### Section 4. Public targets ### Section 4. Public Targets
### ========================================================================== ### ==========================================================================
help: help:
@ -447,6 +442,7 @@ help:
@echo "ppc-64 > PPC 64-bit" @echo "ppc-64 > PPC 64-bit"
@echo "ppc-32 > PPC 32-bit" @echo "ppc-32 > PPC 32-bit"
@echo "armv7 > ARMv7 32-bit" @echo "armv7 > ARMv7 32-bit"
@echo "armv8 > ARMv8 64-bit"
@echo "general-64 > unspecified 64-bit" @echo "general-64 > unspecified 64-bit"
@echo "general-32 > unspecified 32-bit" @echo "general-32 > unspecified 32-bit"
@echo "" @echo ""
@ -518,7 +514,7 @@ default:
help help
### ========================================================================== ### ==========================================================================
### Section 5. Private targets ### Section 5. Private Targets
### ========================================================================== ### ==========================================================================
all: $(EXE) .depend all: $(EXE) .depend
@ -550,7 +546,8 @@ config-sanity:
@test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no" @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
@test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no"
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7" test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \
test "$(arch)" = "armv7" || test "$(arch)" = "armv8-a"
@test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(bits)" = "32" || test "$(bits)" = "64"
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
@ -613,4 +610,3 @@ nnue-learn-use-blas: config-sanity
-@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null
-include .depend -include .depend

View file

@ -108,25 +108,25 @@ namespace {
stm = Color ((idx >> 12) & 0x01); stm = Color ((idx >> 12) & 0x01);
psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
// Check if two pieces are on the same square or if a king can be captured // Invalid if two pieces are on the same square or if a king can be captured
if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
|| ksq[WHITE] == psq || ksq[WHITE] == psq
|| ksq[BLACK] == psq || ksq[BLACK] == psq
|| (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK]))) || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
result = INVALID; result = INVALID;
// Immediate win if a pawn can be promoted without getting captured // Win if the pawn can be promoted without getting captured
else if ( stm == WHITE else if ( stm == WHITE
&& rank_of(psq) == RANK_7 && rank_of(psq) == RANK_7
&& ksq[stm] != psq + NORTH && ksq[WHITE] != psq + NORTH
&& ( distance(ksq[~stm], psq + NORTH) > 1 && ( distance(ksq[BLACK], psq + NORTH) > 1
|| (attacks_bb<KING>(ksq[stm]) & (psq + NORTH)))) || (distance(ksq[WHITE], psq + NORTH) == 1)))
result = WIN; result = WIN;
// Immediate draw if it is a stalemate or a king captures undefended pawn // Draw if it is stalemate or the black king can capture the pawn
else if ( stm == BLACK else if ( stm == BLACK
&& ( !(attacks_bb<KING>(ksq[stm]) & ~(attacks_bb<KING>(ksq[~stm]) | pawn_attacks_bb(~stm, psq))) && ( !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
|| (attacks_bb<KING>(ksq[stm]) & psq & ~attacks_bb<KING>(ksq[~stm])))) || (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
result = DRAW; result = DRAW;
// Position will be classified later // Position will be classified later

View file

@ -40,7 +40,7 @@ namespace {
Bitboard RookTable[0x19000]; // To store rook attacks Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks Bitboard BishopTable[0x1480]; // To store bishop attacks
void init_magics(Bitboard table[], Magic magics[], Direction directions[]); void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
} }
@ -56,8 +56,9 @@ const std::string Bitboards::pretty(Bitboard b) {
for (File f = FILE_A; f <= FILE_H; ++f) for (File f = FILE_A; f <= FILE_H; ++f)
s += b & make_square(f, r) ? "| X " : "| "; s += b & make_square(f, r) ? "| X " : "| ";
s += "|\n+---+---+---+---+---+---+---+---+\n"; s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
} }
s += " a b c d e f g h\n";
return s; return s;
} }
@ -78,11 +79,8 @@ void Bitboards::init() {
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2)); SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST }; init_magics(ROOK, RookTable, RookMagics);
Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; init_magics(BISHOP, BishopTable, BishopMagics);
init_magics(RookTable, RookMagics, RookDirections);
init_magics(BishopTable, BishopMagics, BishopDirections);
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{ {
@ -108,15 +106,17 @@ void Bitboards::init() {
namespace { namespace {
Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) { Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard attacks = 0; Bitboard attacks = 0;
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
for (int i = 0; i < 4; ++i) for(Direction d : (pt == ROOK ? RookDirections : BishopDirections))
{ {
Square s = sq; Square s = sq;
while(safe_destination(s, directions[i]) && !(occupied & s)) while(safe_destination(s, d) && !(occupied & s))
attacks |= (s += directions[i]); attacks |= (s += d);
} }
return attacks; return attacks;
@ -128,7 +128,7 @@ namespace {
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
// called "fancy" approach. // called "fancy" approach.
void init_magics(Bitboard table[], Magic magics[], Direction directions[]) { void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
// Optimal PRNG seeds to pick the correct magics in the shortest time // Optimal PRNG seeds to pick the correct magics in the shortest time
int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
@ -148,7 +148,7 @@ namespace {
// the number of 1s of the mask. Hence we deduce the size of the shift to // 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. // apply to the 64 or 32 bits word to get the index.
Magic& m = magics[s]; Magic& m = magics[s];
m.mask = sliding_attack(directions, s, 0) & ~edges; m.mask = sliding_attack(pt, s, 0) & ~edges;
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
// Set the offset for the attacks table of the square. We have individual // Set the offset for the attacks table of the square. We have individual
@ -160,7 +160,7 @@ namespace {
b = size = 0; b = size = 0;
do { do {
occupancy[size] = b; occupancy[size] = b;
reference[size] = sliding_attack(directions, s, b); reference[size] = sliding_attack(pt, s, b);
if (HasPext) if (HasPext)
m.attacks[pext(b, m.mask)] = reference[size]; m.attacks[pext(b, m.mask)] = reference[size];

View file

@ -110,6 +110,7 @@ inline Bitboard square_bb(Square s) {
return SquareBB[s]; return SquareBB[s];
} }
/// Overloads of bitwise operators between a Bitboard and a Square for testing /// 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. /// whether a given bit is set in a bitboard, and for setting and clearing bits.
@ -201,11 +202,25 @@ inline Bitboard adjacent_files_bb(Square s) {
} }
/// between_bb() returns squares that are linearly between the given squares /// line_bb(Square, Square) returns a bitboard representing an entire line,
/// If the given squares are not on a same file/rank/diagonal, return 0. /// from board edge to board edge, that intersects the given squares. If the
/// given squares are not on a same file/rank/diagonal, returns 0. For instance,
/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.
inline Bitboard line_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return LineBB[s1][s2];
}
/// between_bb() returns a bitboard representing squares that are linearly
/// between the given squares (excluding the given squares). If the given
/// squares are not on a same file/rank/diagonal, return 0. For instance,
/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6.
inline Bitboard between_bb(Square s1, Square s2) { inline Bitboard between_bb(Square s1, Square s2) {
Bitboard b = LineBB[s1][s2] & ((AllSquares << s1) ^ (AllSquares << s2)); Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2));
return b & (b - 1); //exclude lsb return b & (b - 1); //exclude lsb
} }
@ -229,8 +244,8 @@ inline Bitboard forward_file_bb(Color c, Square s) {
/// pawn_attack_span() returns a bitboard representing all the squares that can /// pawn_attack_span() returns a bitboard representing all the squares that can
/// be attacked by a pawn of the given color when it moves along its file, /// be attacked by a pawn of the given color when it moves along its file, starting
/// starting from the given square. /// from the given square.
inline Bitboard pawn_attack_span(Color c, Square s) { inline Bitboard pawn_attack_span(Color c, Square s) {
return forward_ranks_bb(c, s) & adjacent_files_bb(s); return forward_ranks_bb(c, s) & adjacent_files_bb(s);
@ -241,7 +256,7 @@ inline Bitboard pawn_attack_span(Color c, Square s) {
/// the given color and on the given square is a passed pawn. /// the given color and on the given square is a passed pawn.
inline Bitboard passed_pawn_span(Color c, Square s) { inline Bitboard passed_pawn_span(Color c, Square s) {
return forward_ranks_bb(c, s) & (adjacent_files_bb(s) | file_bb(s)); return pawn_attack_span(c, s) | forward_file_bb(c, s);
} }
@ -249,7 +264,7 @@ inline Bitboard passed_pawn_span(Color c, Square s) {
/// straight or on a diagonal line. /// straight or on a diagonal line.
inline bool aligned(Square s1, Square s2, Square s3) { inline bool aligned(Square s1, Square s2, Square s3) {
return LineBB[s1][s2] & s3; return line_bb(s1, s2) & s3;
} }
@ -264,7 +279,9 @@ template<> inline int distance<Square>(Square x, Square y) { return SquareDistan
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); } inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
/// Return the target square bitboard if we do not step off the board, empty otherwise
/// safe_destination() returns the bitboard of target square for the given step
/// from the given square. If the step is off the board, returns empty bitboard.
inline Bitboard safe_destination(Square s, int step) inline Bitboard safe_destination(Square s, int step)
{ {
@ -272,6 +289,7 @@ inline Bitboard safe_destination(Square s, int step)
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
} }
/// attacks_bb(Square) returns the pseudo attacks of the give piece type /// attacks_bb(Square) returns the pseudo attacks of the give piece type
/// assuming an empty board. /// assuming an empty board.
@ -283,6 +301,7 @@ inline Bitboard attacks_bb(Square s) {
return PseudoAttacks[Pt][s]; return PseudoAttacks[Pt][s];
} }
/// attacks_bb(Square, Bitboard) returns the attacks by the given piece /// attacks_bb(Square, Bitboard) returns the attacks by the given piece
/// assuming the board is occupied according to the passed Bitboard. /// assuming the board is occupied according to the passed Bitboard.
/// Sliding piece attacks do not continue passed an occupied square. /// Sliding piece attacks do not continue passed an occupied square.

View file

@ -28,12 +28,14 @@ namespace {
// Used to drive the king towards the edge of the board // Used to drive the king towards the edge of the board
// in KX vs K and KQ vs KR endgames. // in KX vs K and KQ vs KR endgames.
// Values range from 27 (center squares) to 90 (in the corners)
inline int push_to_edge(Square s) { inline int push_to_edge(Square s) {
int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s)); int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2); return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
} }
// Used to drive the king towards A1H8 corners in KBN vs K endgames. // Used to drive the king towards A1H8 corners in KBN vs K endgames.
// Values range from 0 on A8H1 diagonal to 7 in A1H8 corners
inline int push_to_corner(Square s) { inline int push_to_corner(Square s) {
return abs(7 - rank_of(s) - file_of(s)); return abs(7 - rank_of(s) - file_of(s));
} }
@ -103,13 +105,13 @@ Value Endgame<KXK>::operator()(const Position& pos) const {
if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size()) if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
return VALUE_DRAW; return VALUE_DRAW;
Square winnerKSq = pos.square<KING>(strongSide); Square strongKing = pos.square<KING>(strongSide);
Square loserKSq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Value result = pos.non_pawn_material(strongSide) Value result = pos.non_pawn_material(strongSide)
+ pos.count<PAWN>(strongSide) * PawnValueEg + pos.count<PAWN>(strongSide) * PawnValueEg
+ push_to_edge(loserKSq) + push_to_edge(weakKing)
+ push_close(winnerKSq, loserKSq); + push_close(strongKing, weakKing);
if ( pos.count<QUEEN>(strongSide) if ( pos.count<QUEEN>(strongSide)
|| pos.count<ROOK>(strongSide) || pos.count<ROOK>(strongSide)
@ -130,16 +132,16 @@ Value Endgame<KBNK>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0)); assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
Square winnerKSq = pos.square<KING>(strongSide); Square strongKing = pos.square<KING>(strongSide);
Square loserKSq = pos.square<KING>(weakSide); Square strongBishop = pos.square<BISHOP>(strongSide);
Square bishopSq = pos.square<BISHOP>(strongSide); Square weakKing = pos.square<KING>(weakSide);
// If our bishop does not attack A1/H8, we flip the enemy king square // If our bishop does not attack A1/H8, we flip the enemy king square
// to drive to opposite corners (A8/H1). // to drive to opposite corners (A8/H1).
Value result = (VALUE_KNOWN_WIN + 3520) Value result = (VALUE_KNOWN_WIN + 3520)
+ push_close(winnerKSq, loserKSq) + push_close(strongKing, weakKing)
+ 420 * push_to_corner(opposite_colors(bishopSq, SQ_A1) ? flip_file(loserKSq) : loserKSq); + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
@ -154,16 +156,16 @@ Value Endgame<KPK>::operator()(const Position& pos) const {
assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
// Assume strongSide is white and the pawn is on files A-D // Assume strongSide is white and the pawn is on files A-D
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide)); Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide)); Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
Square psq = normalize(pos, strongSide, pos.square<PAWN>(strongSide)); Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
if (!Bitbases::probe(wksq, psq, bksq, us)) if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
return VALUE_DRAW; return VALUE_DRAW;
Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq)); Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn));
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
} }
@ -179,36 +181,35 @@ Value Endgame<KRKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 0)); assert(verify_material(pos, strongSide, RookValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Square wksq = relative_square(strongSide, pos.square<KING>(strongSide)); Square strongKing = relative_square(strongSide, pos.square<KING>(strongSide));
Square bksq = relative_square(strongSide, pos.square<KING>(weakSide)); Square weakKing = relative_square(strongSide, pos.square<KING>(weakSide));
Square rsq = relative_square(strongSide, pos.square<ROOK>(strongSide)); Square strongRook = relative_square(strongSide, pos.square<ROOK>(strongSide));
Square psq = relative_square(strongSide, pos.square<PAWN>(weakSide)); Square weakPawn = relative_square(strongSide, pos.square<PAWN>(weakSide));
Square queeningSquare = make_square(file_of(weakPawn), RANK_1);
Square queeningSq = make_square(file_of(psq), RANK_1);
Value result; Value result;
// If the stronger side's king is in front of the pawn, it's a win // If the stronger side's king is in front of the pawn, it's a win
if (forward_file_bb(WHITE, wksq) & psq) if (forward_file_bb(WHITE, strongKing) & weakPawn)
result = RookValueEg - distance(wksq, psq); result = RookValueEg - distance(strongKing, weakPawn);
// If the weaker side's king is too far from the pawn and the rook, // If the weaker side's king is too far from the pawn and the rook,
// it's a win. // it's a win.
else if ( distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide) else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
&& distance(bksq, rsq) >= 3) && distance(weakKing, strongRook) >= 3)
result = RookValueEg - distance(wksq, psq); result = RookValueEg - distance(strongKing, weakPawn);
// If the pawn is far advanced and supported by the defending king, // If the pawn is far advanced and supported by the defending king,
// the position is drawish // the position is drawish
else if ( rank_of(bksq) <= RANK_3 else if ( rank_of(weakKing) <= RANK_3
&& distance(bksq, psq) == 1 && distance(weakKing, weakPawn) == 1
&& rank_of(wksq) >= RANK_4 && rank_of(strongKing) >= RANK_4
&& distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide)) && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
result = Value(80) - 8 * distance(wksq, psq); result = Value(80) - 8 * distance(strongKing, weakPawn);
else else
result = Value(200) - 8 * ( distance(wksq, psq + SOUTH) result = Value(200) - 8 * ( distance(strongKing, weakPawn + SOUTH)
- distance(bksq, psq + SOUTH) - distance(weakKing, weakPawn + SOUTH)
- distance(psq, queeningSq)); - distance(weakPawn, queeningSquare));
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
} }
@ -235,9 +236,9 @@ Value Endgame<KRKN>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 0)); assert(verify_material(pos, strongSide, RookValueMg, 0));
assert(verify_material(pos, weakSide, KnightValueMg, 0)); assert(verify_material(pos, weakSide, KnightValueMg, 0));
Square bksq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Square bnsq = pos.square<KNIGHT>(weakSide); Square weakKnight = pos.square<KNIGHT>(weakSide);
Value result = Value(push_to_edge(bksq) + push_away(bksq, bnsq)); Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
} }
@ -252,22 +253,22 @@ Value Endgame<KQKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, QueenValueMg, 0)); assert(verify_material(pos, strongSide, QueenValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Square winnerKSq = pos.square<KING>(strongSide); Square strongKing = pos.square<KING>(strongSide);
Square loserKSq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Square pawnSq = pos.square<PAWN>(weakSide); Square weakPawn = pos.square<PAWN>(weakSide);
Value result = Value(push_close(winnerKSq, loserKSq)); Value result = Value(push_close(strongKing, weakKing));
if ( relative_rank(weakSide, pawnSq) != RANK_7 if ( relative_rank(weakSide, weakPawn) != RANK_7
|| distance(loserKSq, pawnSq) != 1 || distance(weakKing, weakPawn) != 1
|| ((FileBBB | FileDBB | FileEBB | FileGBB) & pawnSq)) || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
result += QueenValueEg - PawnValueEg; result += QueenValueEg - PawnValueEg;
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
} }
/// KQ vs KR. This is almost identical to KX vs K: We give the attacking /// KQ vs KR. This is almost identical to KX vs K: we give the attacking
/// king a bonus for having the kings close together, and for forcing the /// king a bonus for having the kings close together, and for forcing the
/// defending king towards the edge. If we also take care to avoid null move for /// defending king towards the edge. If we also take care to avoid null move for
/// the defending side in the search, this is usually sufficient to win KQ vs KR. /// the defending side in the search, this is usually sufficient to win KQ vs KR.
@ -277,29 +278,32 @@ Value Endgame<KQKR>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, QueenValueMg, 0)); assert(verify_material(pos, strongSide, QueenValueMg, 0));
assert(verify_material(pos, weakSide, RookValueMg, 0)); assert(verify_material(pos, weakSide, RookValueMg, 0));
Square winnerKSq = pos.square<KING>(strongSide); Square strongKing = pos.square<KING>(strongSide);
Square loserKSq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Value result = QueenValueEg Value result = QueenValueEg
- RookValueEg - RookValueEg
+ push_to_edge(loserKSq) + push_to_edge(weakKing)
+ push_close(winnerKSq, loserKSq); + push_close(strongKing, weakKing);
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
} }
/// KNN vs KP. Very drawish, but there are some mate opportunities if we can /// KNN vs KP. Very drawish, but there are some mate opportunities if we can
// press the weakSide King to a corner before the pawn advances too much. /// press the weakSide King to a corner before the pawn advances too much.
template<> template<>
Value Endgame<KNNKP>::operator()(const Position& pos) const { Value Endgame<KNNKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Square weakKing = pos.square<KING>(weakSide);
Square weakPawn = pos.square<PAWN>(weakSide);
Value result = PawnValueEg Value result = PawnValueEg
+ 2 * push_to_edge(pos.square<KING>(weakSide)) + 2 * push_to_edge(weakKing)
- 10 * relative_rank(weakSide, pos.square<PAWN>(weakSide)); - 10 * relative_rank(weakSide, weakPawn);
return strongSide == pos.side_to_move() ? result : -result; return strongSide == pos.side_to_move() ? result : -result;
} }
@ -325,15 +329,17 @@ ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
Bitboard strongPawns = pos.pieces(strongSide, PAWN); Bitboard strongPawns = pos.pieces(strongSide, PAWN);
Bitboard allPawns = pos.pieces(PAWN); Bitboard allPawns = pos.pieces(PAWN);
Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Square strongKing = pos.square<KING>(strongSide);
// All strongSide pawns are on a single rook file? // All strongSide pawns are on a single rook file?
if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB)) if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
{ {
Square bishopSq = pos.square<BISHOP>(strongSide); Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
Square queeningSq = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
Square weakKingSq = pos.square<KING>(weakSide);
if ( opposite_colors(queeningSq, bishopSq) if ( opposite_colors(queeningSquare, strongBishop)
&& distance(queeningSq, weakKingSq) <= 1) && distance(queeningSquare, weakKing) <= 1)
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
} }
@ -343,28 +349,24 @@ ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
&& pos.count<PAWN>(weakSide) >= 1) && pos.count<PAWN>(weakSide) >= 1)
{ {
// Get the least advanced weakSide pawn // Get the least advanced weakSide pawn
Square weakPawnSq = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
Square strongKingSq = pos.square<KING>(strongSide);
Square weakKingSq = pos.square<KING>(weakSide);
Square bishopSq = pos.square<BISHOP>(strongSide);
// There's potential for a draw if our pawn is blocked on the 7th rank, // There's potential for a draw if our pawn is blocked on the 7th rank,
// the bishop cannot attack it or they only have one pawn left // the bishop cannot attack it or they only have one pawn left.
if ( relative_rank(strongSide, weakPawnSq) == RANK_7 if ( relative_rank(strongSide, weakPawn) == RANK_7
&& (strongPawns & (weakPawnSq + pawn_push(weakSide))) && (strongPawns & (weakPawn + pawn_push(weakSide)))
&& (opposite_colors(bishopSq, weakPawnSq) || !more_than_one(strongPawns))) && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
{ {
int strongKingDist = distance(weakPawnSq, strongKingSq); int strongKingDist = distance(weakPawn, strongKing);
int weakKingDist = distance(weakPawnSq, weakKingSq); int weakKingDist = distance(weakPawn, weakKing);
// It's a draw if the weak king is on its back two ranks, within 2 // It's a draw if the weak king is on its back two ranks, within 2
// squares of the blocking pawn and the strong king is not // squares of the blocking pawn and the strong king is not
// closer. (I think this rule only fails in practically // closer. (I think this rule only fails in practically
// unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
// and positions where qsearch will immediately correct the // and positions where qsearch will immediately correct the
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w) // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
if ( relative_rank(strongSide, weakKingSq) >= RANK_7 if ( relative_rank(strongSide, weakKing) >= RANK_7
&& weakKingDist <= 2 && weakKingDist <= 2
&& weakKingDist <= strongKingDist) && weakKingDist <= strongKingDist)
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
@ -384,15 +386,16 @@ ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
assert(pos.count<ROOK>(weakSide) == 1); assert(pos.count<ROOK>(weakSide) == 1);
assert(pos.count<PAWN>(weakSide) >= 1); assert(pos.count<PAWN>(weakSide) >= 1);
Square kingSq = pos.square<KING>(weakSide); Square strongKing = pos.square<KING>(strongSide);
Square rsq = pos.square<ROOK>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Square weakRook = pos.square<ROOK>(weakSide);
if ( relative_rank(weakSide, kingSq) <= RANK_2 if ( relative_rank(weakSide, weakKing) <= RANK_2
&& relative_rank(weakSide, pos.square<KING>(strongSide)) >= RANK_4 && relative_rank(weakSide, strongKing) >= RANK_4
&& relative_rank(weakSide, rsq) == RANK_3 && relative_rank(weakSide, weakRook) == RANK_3
&& ( pos.pieces(weakSide, PAWN) && ( pos.pieces(weakSide, PAWN)
& attacks_bb<KING>(kingSq) & attacks_bb<KING>(weakKing)
& pawn_attacks_bb(strongSide, rsq))) & pawn_attacks_bb(strongSide, weakRook)))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
@ -412,89 +415,89 @@ ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
assert(verify_material(pos, weakSide, RookValueMg, 0)); assert(verify_material(pos, weakSide, RookValueMg, 0));
// Assume strongSide is white and the pawn is on files A-D // Assume strongSide is white and the pawn is on files A-D
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide)); Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide)); Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
Square wrsq = normalize(pos, strongSide, pos.square<ROOK>(strongSide)); Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
Square wpsq = normalize(pos, strongSide, pos.square<PAWN>(strongSide)); Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
Square brsq = normalize(pos, strongSide, pos.square<ROOK>(weakSide)); Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
File f = file_of(wpsq); File pawnFile = file_of(strongPawn);
Rank r = rank_of(wpsq); Rank pawnRank = rank_of(strongPawn);
Square queeningSq = make_square(f, RANK_8); Square queeningSquare = make_square(pawnFile, RANK_8);
int tempo = (pos.side_to_move() == strongSide); int tempo = (pos.side_to_move() == strongSide);
// If the pawn is not too far advanced and the defending king defends the // If the pawn is not too far advanced and the defending king defends the
// queening square, use the third-rank defence. // queening square, use the third-rank defence.
if ( r <= RANK_5 if ( pawnRank <= RANK_5
&& distance(bksq, queeningSq) <= 1 && distance(weakKing, queeningSquare) <= 1
&& wksq <= SQ_H5 && strongKing <= SQ_H5
&& (rank_of(brsq) == RANK_6 || (r <= RANK_3 && rank_of(wrsq) != RANK_6))) && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
// The defending side saves a draw by checking from behind in case the pawn // The defending side saves a draw by checking from behind in case the pawn
// has advanced to the 6th rank with the king behind. // has advanced to the 6th rank with the king behind.
if ( r == RANK_6 if ( pawnRank == RANK_6
&& distance(bksq, queeningSq) <= 1 && distance(weakKing, queeningSquare) <= 1
&& rank_of(wksq) + tempo <= RANK_6 && rank_of(strongKing) + tempo <= RANK_6
&& (rank_of(brsq) == RANK_1 || (!tempo && distance<File>(brsq, wpsq) >= 3))) && (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
if ( r >= RANK_6 if ( pawnRank >= RANK_6
&& bksq == queeningSq && weakKing == queeningSquare
&& rank_of(brsq) == RANK_1 && rank_of(weakRook) == RANK_1
&& (!tempo || distance(wksq, wpsq) >= 2)) && (!tempo || distance(strongKing, strongPawn) >= 2))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
// White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7
// and the black rook is behind the pawn. // and the black rook is behind the pawn.
if ( wpsq == SQ_A7 if ( strongPawn == SQ_A7
&& wrsq == SQ_A8 && strongRook == SQ_A8
&& (bksq == SQ_H7 || bksq == SQ_G7) && (weakKing == SQ_H7 || weakKing == SQ_G7)
&& file_of(brsq) == FILE_A && file_of(weakRook) == FILE_A
&& (rank_of(brsq) <= RANK_3 || file_of(wksq) >= FILE_D || rank_of(wksq) <= RANK_5)) && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
// If the defending king blocks the pawn and the attacking king is too far // If the defending king blocks the pawn and the attacking king is too far
// away, it's a draw. // away, it's a draw.
if ( r <= RANK_5 if ( pawnRank <= RANK_5
&& bksq == wpsq + NORTH && weakKing == strongPawn + NORTH
&& distance(wksq, wpsq) - tempo >= 2 && distance(strongKing, strongPawn) - tempo >= 2
&& distance(wksq, brsq) - tempo >= 2) && distance(strongKing, weakRook) - tempo >= 2)
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
// Pawn on the 7th rank supported by the rook from behind usually wins if the // Pawn on the 7th rank supported by the rook from behind usually wins if the
// attacking king is closer to the queening square than the defending king, // attacking king is closer to the queening square than the defending king,
// and the defending king cannot gain tempi by threatening the attacking rook. // and the defending king cannot gain tempi by threatening the attacking rook.
if ( r == RANK_7 if ( pawnRank == RANK_7
&& f != FILE_A && pawnFile != FILE_A
&& file_of(wrsq) == f && file_of(strongRook) == pawnFile
&& wrsq != queeningSq && strongRook != queeningSquare
&& (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo) && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
&& (distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo)) && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(wksq, queeningSq)); return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
// Similar to the above, but with the pawn further back // Similar to the above, but with the pawn further back
if ( f != FILE_A if ( pawnFile != FILE_A
&& file_of(wrsq) == f && file_of(strongRook) == pawnFile
&& wrsq < wpsq && strongRook < strongPawn
&& (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo) && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
&& (distance(wksq, wpsq + NORTH) < distance(bksq, wpsq + NORTH) - 2 + tempo) && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
&& ( distance(bksq, wrsq) + tempo >= 3 && ( distance(weakKing, strongRook) + tempo >= 3
|| ( distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
&& (distance(wksq, wpsq + NORTH) < distance(bksq, wrsq) + tempo)))) && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
return ScaleFactor( SCALE_FACTOR_MAX return ScaleFactor( SCALE_FACTOR_MAX
- 8 * distance(wpsq, queeningSq) - 8 * distance(strongPawn, queeningSquare)
- 2 * distance(wksq, queeningSq)); - 2 * distance(strongKing, queeningSquare));
// If the pawn is not far advanced and the defending king is somewhere in // If the pawn is not far advanced and the defending king is somewhere in
// the pawn's path, it's probably a draw. // the pawn's path, it's probably a draw.
if (r <= RANK_4 && bksq > wpsq) if (pawnRank <= RANK_4 && weakKing > strongPawn)
{ {
if (file_of(bksq) == file_of(wpsq)) if (file_of(weakKing) == file_of(strongPawn))
return ScaleFactor(10); return ScaleFactor(10);
if ( distance<File>(bksq, wpsq) == 1 if ( distance<File>(weakKing, strongPawn) == 1
&& distance(wksq, bksq) > 2) && distance(strongKing, weakKing) > 2)
return ScaleFactor(24 - 2 * distance(wksq, bksq)); return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
} }
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
} }
@ -508,10 +511,11 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
// Test for a rook pawn // Test for a rook pawn
if (pos.pieces(PAWN) & (FileABB | FileHBB)) if (pos.pieces(PAWN) & (FileABB | FileHBB))
{ {
Square ksq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Square bsq = pos.square<BISHOP>(weakSide); Square weakBishop = pos.square<BISHOP>(weakSide);
Square psq = pos.square<PAWN>(strongSide); Square strongKing = pos.square<KING>(strongSide);
Rank rk = relative_rank(strongSide, psq); Square strongPawn = pos.square<PAWN>(strongSide);
Rank pawnRank = relative_rank(strongSide, strongPawn);
Direction push = pawn_push(strongSide); Direction push = pawn_push(strongSide);
// If the pawn is on the 5th rank and the pawn (currently) is on // If the pawn is on the 5th rank and the pawn (currently) is on
@ -519,11 +523,11 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
// a fortress. Depending on the king position give a moderate // a fortress. Depending on the king position give a moderate
// reduction or a stronger one if the defending king is near the // reduction or a stronger one if the defending king is near the
// corner but not trapped there. // corner but not trapped there.
if (rk == RANK_5 && !opposite_colors(bsq, psq)) if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn))
{ {
int d = distance(psq + 3 * push, ksq); int d = distance(strongPawn + 3 * push, weakKing);
if (d <= 2 && !(d == 0 && ksq == pos.square<KING>(strongSide) + 2 * push)) if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push))
return ScaleFactor(24); return ScaleFactor(24);
else else
return ScaleFactor(48); return ScaleFactor(48);
@ -533,10 +537,10 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
// it's drawn if the bishop attacks the square in front of the // it's drawn if the bishop attacks the square in front of the
// pawn from a reasonable distance and the defending king is near // pawn from a reasonable distance and the defending king is near
// the corner // the corner
if ( rk == RANK_6 if ( pawnRank == RANK_6
&& distance(psq + 2 * push, ksq) <= 1 && distance(strongPawn + 2 * push, weakKing) <= 1
&& (attacks_bb<BISHOP>(bsq) & (psq + push)) && (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
&& distance<File>(bsq, psq) >= 2) && distance<File>(weakBishop, strongPawn) >= 2)
return ScaleFactor(8); return ScaleFactor(8);
} }
@ -551,28 +555,28 @@ ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 2)); assert(verify_material(pos, strongSide, RookValueMg, 2));
assert(verify_material(pos, weakSide, RookValueMg, 1)); assert(verify_material(pos, weakSide, RookValueMg, 1));
Square wpsq1 = pos.squares<PAWN>(strongSide)[0]; Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
Square wpsq2 = pos.squares<PAWN>(strongSide)[1]; Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
Square bksq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
// Does the stronger side have a passed pawn? // Does the stronger side have a passed pawn?
if (pos.pawn_passed(strongSide, wpsq1) || pos.pawn_passed(strongSide, wpsq2)) if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2))
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
Rank r = std::max(relative_rank(strongSide, wpsq1), relative_rank(strongSide, wpsq2)); Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2));
if ( distance<File>(bksq, wpsq1) <= 1 if ( distance<File>(weakKing, strongPawn1) <= 1
&& distance<File>(bksq, wpsq2) <= 1 && distance<File>(weakKing, strongPawn2) <= 1
&& relative_rank(strongSide, bksq) > r) && relative_rank(strongSide, weakKing) > pawnRank)
{ {
assert(r > RANK_1 && r < RANK_7); assert(pawnRank > RANK_1 && pawnRank < RANK_7);
return ScaleFactor(7 * r); return ScaleFactor(7 * pawnRank);
} }
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
} }
/// K and two or more pawns vs K. There is just a single rule here: If all pawns /// K and two or more pawns vs K. There is just a single rule here: if all pawns
/// are on the same rook file and are blocked by the defending king, it's a draw. /// are on the same rook file and are blocked by the defending king, it's a draw.
template<> template<>
ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const { ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
@ -581,12 +585,12 @@ ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
assert(pos.count<PAWN>(strongSide) >= 2); assert(pos.count<PAWN>(strongSide) >= 2);
assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
Square ksq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Bitboard pawns = pos.pieces(strongSide, PAWN); Bitboard strongPawns = pos.pieces(strongSide, PAWN);
// If all pawns are ahead of the king on a single rook file, it's a draw. // If all pawns are ahead of the king on a single rook file, it's a draw.
if (!((pawns & ~FileABB) || (pawns & ~FileHBB)) && if (!((strongPawns & ~FileABB) || (strongPawns & ~FileHBB)) &&
!(pawns & ~passed_pawn_span(weakSide, ksq))) !(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
@ -603,19 +607,19 @@ ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, BishopValueMg, 1)); assert(verify_material(pos, strongSide, BishopValueMg, 1));
assert(verify_material(pos, weakSide, BishopValueMg, 0)); assert(verify_material(pos, weakSide, BishopValueMg, 0));
Square pawnSq = pos.square<PAWN>(strongSide); Square strongPawn = pos.square<PAWN>(strongSide);
Square strongBishopSq = pos.square<BISHOP>(strongSide); Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakBishopSq = pos.square<BISHOP>(weakSide); Square weakBishop = pos.square<BISHOP>(weakSide);
Square weakKingSq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
// Case 1: Defending king blocks the pawn, and cannot be driven away // Case 1: Defending king blocks the pawn, and cannot be driven away
if ( (forward_file_bb(strongSide, pawnSq) & weakKingSq) if ( (forward_file_bb(strongSide, strongPawn) & weakKing)
&& ( opposite_colors(weakKingSq, strongBishopSq) && ( opposite_colors(weakKing, strongBishop)
|| relative_rank(strongSide, weakKingSq) <= RANK_6)) || relative_rank(strongSide, weakKing) <= RANK_6))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
// Case 2: Opposite colored bishops // Case 2: Opposite colored bishops
if (opposite_colors(strongBishopSq, weakBishopSq)) if (opposite_colors(strongBishop, weakBishop))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
@ -629,36 +633,36 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, BishopValueMg, 2)); assert(verify_material(pos, strongSide, BishopValueMg, 2));
assert(verify_material(pos, weakSide, BishopValueMg, 0)); assert(verify_material(pos, weakSide, BishopValueMg, 0));
Square wbsq = pos.square<BISHOP>(strongSide); Square strongBishop = pos.square<BISHOP>(strongSide);
Square bbsq = pos.square<BISHOP>(weakSide); Square weakBishop = pos.square<BISHOP>(weakSide);
if (!opposite_colors(wbsq, bbsq)) if (!opposite_colors(strongBishop, weakBishop))
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
Square ksq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
Square psq1 = pos.squares<PAWN>(strongSide)[0]; Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
Square psq2 = pos.squares<PAWN>(strongSide)[1]; Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
Square blockSq1, blockSq2; Square blockSq1, blockSq2;
if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2)) if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
{ {
blockSq1 = psq1 + pawn_push(strongSide); blockSq1 = strongPawn1 + pawn_push(strongSide);
blockSq2 = make_square(file_of(psq2), rank_of(psq1)); blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
} }
else else
{ {
blockSq1 = psq2 + pawn_push(strongSide); blockSq1 = strongPawn2 + pawn_push(strongSide);
blockSq2 = make_square(file_of(psq1), rank_of(psq2)); blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
} }
switch (distance<File>(psq1, psq2)) switch (distance<File>(strongPawn1, strongPawn2))
{ {
case 0: case 0:
// Both pawns are on the same file. It's an easy draw if the defender firmly // Both pawns are on the same file. It's an easy draw if the defender firmly
// controls some square in the frontmost pawn's path. // controls some square in the frontmost pawn's path.
if ( file_of(ksq) == file_of(blockSq1) if ( file_of(weakKing) == file_of(blockSq1)
&& relative_rank(strongSide, ksq) >= relative_rank(strongSide, blockSq1) && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
&& opposite_colors(ksq, wbsq)) && opposite_colors(weakKing, strongBishop))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
else else
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
@ -667,16 +671,16 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
// Pawns on adjacent files. It's a draw if the defender firmly controls the // Pawns on adjacent files. It's a draw if the defender firmly controls the
// square in front of the frontmost pawn's path, and the square diagonally // square in front of the frontmost pawn's path, and the square diagonally
// behind this square on the file of the other pawn. // behind this square on the file of the other pawn.
if ( ksq == blockSq1 if ( weakKing == blockSq1
&& opposite_colors(ksq, wbsq) && opposite_colors(weakKing, strongBishop)
&& ( bbsq == blockSq2 && ( weakBishop == blockSq2
|| (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) || (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
|| distance<Rank>(psq1, psq2) >= 2)) || distance<Rank>(strongPawn1, strongPawn2) >= 2))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
else if ( ksq == blockSq2 else if ( weakKing == blockSq2
&& opposite_colors(ksq, wbsq) && opposite_colors(weakKing, strongBishop)
&& ( bbsq == blockSq1 && ( weakBishop == blockSq1
|| (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP)))) || (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
else else
@ -689,7 +693,7 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
} }
/// KBP vs KN. There is a single rule: If the defending king is somewhere along /// KBP vs KN. There is a single rule: if the defending king is somewhere along
/// the path of the pawn, and the square of the king is not of the same color as /// the path of the pawn, and the square of the king is not of the same color as
/// the stronger side's bishop, it's a draw. /// the stronger side's bishop, it's a draw.
template<> template<>
@ -698,14 +702,14 @@ ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, BishopValueMg, 1)); assert(verify_material(pos, strongSide, BishopValueMg, 1));
assert(verify_material(pos, weakSide, KnightValueMg, 0)); assert(verify_material(pos, weakSide, KnightValueMg, 0));
Square pawnSq = pos.square<PAWN>(strongSide); Square strongPawn = pos.square<PAWN>(strongSide);
Square strongBishopSq = pos.square<BISHOP>(strongSide); Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakKingSq = pos.square<KING>(weakSide); Square weakKing = pos.square<KING>(weakSide);
if ( file_of(weakKingSq) == file_of(pawnSq) if ( file_of(weakKing) == file_of(strongPawn)
&& relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq) && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
&& ( opposite_colors(weakKingSq, strongBishopSq) && ( opposite_colors(weakKing, strongBishop)
|| relative_rank(strongSide, weakKingSq) <= RANK_6)) || relative_rank(strongSide, weakKing) <= RANK_6))
return SCALE_FACTOR_DRAW; return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
@ -713,7 +717,7 @@ ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
/// KP vs KP. This is done by removing the weakest side's pawn and probing the /// KP vs KP. This is done by removing the weakest side's pawn and probing the
/// KP vs K bitbase: If the weakest side has a draw without the pawn, it probably /// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably
/// has at least a draw with the pawn as well. The exception is when the stronger /// has at least a draw with the pawn as well. The exception is when the stronger
/// side's pawn is far advanced and not on a rook file; in this case it is often /// side's pawn is far advanced and not on a rook file; in this case it is often
/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). /// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1).
@ -724,18 +728,18 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
// Assume strongSide is white and the pawn is on files A-D // Assume strongSide is white and the pawn is on files A-D
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide)); Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide)); Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
Square psq = normalize(pos, strongSide, pos.square<PAWN>(strongSide)); Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
// If the pawn has advanced to the fifth rank or further, and is not a // If the pawn has advanced to the fifth rank or further, and is not a
// rook pawn, it's too dangerous to assume that it's at least a draw. // rook pawn, it's too dangerous to assume that it's at least a draw.
if (rank_of(psq) >= RANK_5 && file_of(psq) != FILE_A) if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A)
return SCALE_FACTOR_NONE; return SCALE_FACTOR_NONE;
// Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
// it's probably at least a draw even with the pawn. // it's probably at least a draw even with the pawn.
return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
} }

View file

@ -37,7 +37,7 @@ namespace Trace {
enum Tracing { NO_TRACE, TRACE }; enum Tracing { NO_TRACE, TRACE };
enum Term { // The first 8 entries are reserved for PieceType enum Term { // The first 8 entries are reserved for PieceType
MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB
}; };
Score scores[TERM_NB][COLOR_NB]; Score scores[TERM_NB][COLOR_NB];
@ -61,7 +61,7 @@ namespace Trace {
std::ostream& operator<<(std::ostream& os, Term t) { std::ostream& operator<<(std::ostream& os, Term t) {
if (t == MATERIAL || t == IMBALANCE || t == INITIATIVE || t == TOTAL) if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
os << " ---- ----" << " | " << " ---- ----"; os << " ---- ----" << " | " << " ---- ----";
else else
os << scores[t][WHITE] << " | " << scores[t][BLACK]; os << scores[t][WHITE] << " | " << scores[t][BLACK];
@ -129,32 +129,34 @@ namespace {
}; };
// Assorted bonuses and penalties // Assorted bonuses and penalties
constexpr Score BishopPawns = S( 3, 7); constexpr Score BishopKingProtector = S( 6, 9);
constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopOnKingRing = S( 24, 0);
constexpr Score BishopOutpost = S( 30, 23);
constexpr Score BishopPawns = S( 3, 7);
constexpr Score BishopXRayPawns = S( 4, 5); constexpr Score BishopXRayPawns = S( 4, 5);
constexpr Score CorneredBishop = S( 50, 50); constexpr Score CorneredBishop = S( 50, 50);
constexpr Score FlankAttacks = S( 8, 0); constexpr Score FlankAttacks = S( 8, 0);
constexpr Score Hanging = S( 69, 36); constexpr Score Hanging = S( 69, 36);
constexpr Score BishopKingProtector = S( 6, 9);
constexpr Score KnightKingProtector = S( 8, 9); constexpr Score KnightKingProtector = S( 8, 9);
constexpr Score KnightOnQueen = S( 16, 11); constexpr Score KnightOnQueen = S( 16, 11);
constexpr Score KnightOutpost = S( 56, 36);
constexpr Score LongDiagonalBishop = S( 45, 0); constexpr Score LongDiagonalBishop = S( 45, 0);
constexpr Score MinorBehindPawn = S( 18, 3); constexpr Score MinorBehindPawn = S( 18, 3);
constexpr Score KnightOutpost = S( 56, 36);
constexpr Score BishopOutpost = S( 30, 23);
constexpr Score ReachableOutpost = S( 31, 22);
constexpr Score PassedFile = S( 11, 8); constexpr Score PassedFile = S( 11, 8);
constexpr Score PawnlessFlank = S( 17, 95); constexpr Score PawnlessFlank = S( 17, 95);
constexpr Score QueenInfiltration = S( -2, 14);
constexpr Score ReachableOutpost = S( 31, 22);
constexpr Score RestrictedPiece = S( 7, 7); constexpr Score RestrictedPiece = S( 7, 7);
constexpr Score RookOnKingRing = S( 16, 0); constexpr Score RookOnKingRing = S( 16, 0);
constexpr Score RookOnQueenFile = S( 5, 9); constexpr Score RookOnQueenFile = S( 6, 11);
constexpr Score SliderOnQueen = S( 59, 18); constexpr Score SliderOnQueen = S( 60, 18);
constexpr Score ThreatByKing = S( 24, 89); constexpr Score ThreatByKing = S( 24, 89);
constexpr Score ThreatByPawnPush = S( 48, 39); constexpr Score ThreatByPawnPush = S( 48, 39);
constexpr Score ThreatBySafePawn = S(173, 94); constexpr Score ThreatBySafePawn = S(173, 94);
constexpr Score TrappedRook = S( 55, 13); constexpr Score TrappedRook = S( 55, 13);
constexpr Score WeakQueen = S( 51, 14); constexpr Score WeakQueenProtection = S( 14, 0);
constexpr Score WeakQueenProtection = S( 15, 0); constexpr Score WeakQueen = S( 56, 15);
#undef S #undef S
@ -175,8 +177,7 @@ namespace {
template<Color Us> Score threats() const; template<Color Us> Score threats() const;
template<Color Us> Score passed() const; template<Color Us> Score passed() const;
template<Color Us> Score space() const; template<Color Us> Score space() const;
ScaleFactor scale_factor(Value eg) const; Value winnable(Score score) const;
Score initiative(Score score) const;
const Position& pos; const Position& pos;
Material::Entry* me; Material::Entry* me;
@ -218,6 +219,7 @@ namespace {
// Evaluation::initialize() computes king and pawn attacks, and the king ring // Evaluation::initialize() computes king and pawn attacks, and the king ring
// bitboard for a given color. This is done at the beginning of the evaluation. // bitboard for a given color. This is done at the beginning of the evaluation.
template<Tracing T> template<Color Us> template<Tracing T> template<Color Us>
void Evaluation<T>::initialize() { void Evaluation<T>::initialize() {
@ -257,6 +259,7 @@ namespace {
// Evaluation::pieces() scores pieces of a given color and type // Evaluation::pieces() scores pieces of a given color and type
template<Tracing T> template<Color Us, PieceType Pt> template<Tracing T> template<Color Us, PieceType Pt>
Score Evaluation<T>::pieces() { Score Evaluation<T>::pieces() {
@ -279,7 +282,7 @@ namespace {
: attacks_bb<Pt>(s, pos.pieces()); : attacks_bb<Pt>(s, pos.pieces());
if (pos.blockers_for_king(Us) & s) if (pos.blockers_for_king(Us) & s)
b &= LineBB[pos.square<KING>(Us)][s]; b &= line_bb(pos.square<KING>(Us), s);
attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
attackedBy[Us][Pt] |= b; attackedBy[Us][Pt] |= b;
@ -376,6 +379,10 @@ namespace {
Bitboard queenPinners; Bitboard queenPinners;
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners)) if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
score -= WeakQueen; score -= WeakQueen;
// Bonus for queen on weak square in enemy camp
if (relative_rank(Us, s) > RANK_4 && (~pe->pawn_attacks_span(Them) & s))
score += QueenInfiltration;
} }
} }
if (T) if (T)
@ -386,6 +393,7 @@ namespace {
// Evaluation::king() assigns bonuses and penalties to a king of a given color // Evaluation::king() assigns bonuses and penalties to a king of a given color
template<Tracing T> template<Color Us> template<Tracing T> template<Color Us>
Score Evaluation<T>::king() const { Score Evaluation<T>::king() const {
@ -494,6 +502,7 @@ namespace {
// Evaluation::threats() assigns bonuses according to the types of the // Evaluation::threats() assigns bonuses according to the types of the
// attacking and the attacked pieces. // attacking and the attacked pieces.
template<Tracing T> template<Color Us> template<Tracing T> template<Color Us>
Score Evaluation<T>::threats() const { Score Evaluation<T>::threats() const {
@ -679,16 +688,15 @@ namespace {
} }
// Evaluation::space() computes the space evaluation for a given side. The // Evaluation::space() computes a space evaluation for a given side, aiming to improve game
// space evaluation is a simple bonus based on the number of safe squares // play in the opening. It is based on the number of safe squares on the 4 central files
// available for minor pieces on the central four files on ranks 2--4. Safe // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
// squares one, two or three squares behind a friendly pawn are counted // Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
// twice. Finally, the space bonus is multiplied by a weight. The aim is to
// improve play on game opening.
template<Tracing T> template<Color Us> template<Tracing T> template<Color Us>
Score Evaluation<T>::space() const { Score Evaluation<T>::space() const {
// Early exit if, for example, both queens or 6 minor pieces have been exchanged
if (pos.non_pawn_material() < SpaceThreshold) if (pos.non_pawn_material() < SpaceThreshold)
return SCORE_ZERO; return SCORE_ZERO;
@ -719,12 +727,12 @@ namespace {
} }
// Evaluation::initiative() computes the initiative correction value // Evaluation::winnable() adjusts the mg and eg score components based on the
// for the position. It is a second order bonus/malus based on the // known attacking/defending status of the players. A single value is derived
// known attacking/defending status of the players. // by interpolation from the mg and eg values and returned.
template<Tracing T> template<Tracing T>
Score Evaluation<T>::initiative(Score score) const { Value Evaluation<T>::winnable(Score score) const {
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK)) int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK)); - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
@ -746,7 +754,6 @@ namespace {
+ 24 * infiltration + 24 * infiltration
+ 51 * !pos.non_pawn_material() + 51 * !pos.non_pawn_material()
- 43 * almostUnwinnable - 43 * almostUnwinnable
- 2 * pos.rule50_count()
-110 ; -110 ;
Value mg = mg_value(score); Value mg = mg_value(score);
@ -758,17 +765,10 @@ namespace {
int u = ((mg > 0) - (mg < 0)) * Utility::clamp(complexity + 50, -abs(mg), 0); int u = ((mg > 0) - (mg < 0)) * Utility::clamp(complexity + 50, -abs(mg), 0);
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
if (T) mg += u;
Trace::add(INITIATIVE, make_score(u, v)); eg += v;
return make_score(u, v); // Compute the scale factor for the winning side
}
// Evaluation::scale_factor() computes the scale factor for the winning side
template<Tracing T>
ScaleFactor Evaluation<T>::scale_factor(Value eg) const {
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
int sf = me->scale_factor(pos, strongSide); int sf = me->scale_factor(pos, strongSide);
@ -788,7 +788,18 @@ namespace {
sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)); sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide));
} }
return ScaleFactor(sf); // Interpolate between the middlegame and (scaled by 'sf') endgame score
v = mg * int(me->game_phase())
+ eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
v /= PHASE_MIDGAME;
if (T)
{
Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score)));
Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL));
}
return Value(v);
} }
@ -824,12 +835,11 @@ namespace {
return pos.side_to_move() == WHITE ? v : -v; return pos.side_to_move() == WHITE ? v : -v;
// Main evaluation begins here // Main evaluation begins here
initialize<WHITE>(); initialize<WHITE>();
initialize<BLACK>(); initialize<BLACK>();
// Pieces evaluated first (also populates attackedBy, attackedBy2). // Pieces evaluated first (also populates attackedBy, attackedBy2).
// Note that the order of evaluation of the terms is left unspecified // Note that the order of evaluation of the terms is left unspecified.
score += pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>() score += pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>()
+ pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>() + pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
+ pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >() + pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >()
@ -843,14 +853,8 @@ namespace {
+ passed< WHITE>() - passed< BLACK>() + passed< WHITE>() - passed< BLACK>()
+ space< WHITE>() - space< BLACK>(); + space< WHITE>() - space< BLACK>();
score += initiative(score); // Derive single value from mg and eg parts of score
v = winnable(score);
// Interpolate between a middlegame and a (scaled by 'sf') endgame score
ScaleFactor sf = scale_factor(eg_value(score));
v = mg_value(score) * int(me->game_phase())
+ eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
v /= PHASE_MIDGAME;
// In case of tracing add all remaining individual evaluation terms // In case of tracing add all remaining individual evaluation terms
if (T) if (T)
@ -859,11 +863,18 @@ namespace {
Trace::add(IMBALANCE, me->imbalance()); Trace::add(IMBALANCE, me->imbalance());
Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
Trace::add(TOTAL, score);
} }
// Evaluation grain
v = (v / 16) * 16;
// Side to move point of view // Side to move point of view
return (pos.side_to_move() == WHITE ? v : -v) + Tempo; v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
// Damp down the evaluation linearly when shuffling
v = v * (100 - pos.rule50_count()) / 100;
return v;
} }
} // namespace } // namespace
@ -913,11 +924,11 @@ std::string Eval::trace(const Position& pos) {
<< " Threats | " << Term(THREAT) << " Threats | " << Term(THREAT)
<< " Passed | " << Term(PASSED) << " Passed | " << Term(PASSED)
<< " Space | " << Term(SPACE) << " Space | " << Term(SPACE)
<< " Initiative | " << Term(INITIATIVE) << " Winnable | " << Term(WINNABLE)
<< " ------------+-------------+-------------+------------\n" << " ------------+-------------+-------------+------------\n"
<< " Total | " << Term(TOTAL); << " Total | " << Term(TOTAL);
ss << "\nTotal evaluation: " << to_cp(v) << " (white side)\n"; ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";
return ss.str(); return ss.str();
} }
@ -976,7 +987,7 @@ bool EvalList::is_valid(const Position& pos)
for (Piece pc = NO_PIECE; pc < PIECE_NB; ++pc) for (Piece pc = NO_PIECE; pc < PIECE_NB; ++pc)
{ {
auto pt = type_of(pc); auto pt = type_of(pc);
if (pt == NO_PIECE || pt == 7) // <E28098>ݵȢî if (pt == NO_PIECE_TYPE || pt == 7) // <E28098>ݵȢî
continue; continue;
// îpcÌBonaPieceÌŠJŽn”Ô<E2809D> // îpcÌBonaPieceÌŠJŽn”Ô<E2809D>

View file

@ -17,6 +17,7 @@
#include <filesystem> #include <filesystem>
#include <random> #include <random>
#include <regex>
#include "learn.h" #include "learn.h"
#include "multi_think.h" #include "multi_think.h"
@ -111,6 +112,11 @@ namespace Learner
// 局面の配列 : PSVector は packed sfen vector の略。 // 局面の配列 : PSVector は packed sfen vector の略。
typedef std::vector<PackedSfenValue> PSVector; typedef std::vector<PackedSfenValue> PSVector;
bool use_draw_in_training_data_generation = false;
bool use_draw_in_training = false;
bool use_draw_in_validation = false;
bool use_hash_in_training = true;
// ----------------------------------- // -----------------------------------
// 局面のファイルへの書き出し // 局面のファイルへの書き出し
// ----------------------------------- // -----------------------------------
@ -495,25 +501,32 @@ void MultiThinkGenSfen::thread_worker(size_t thread_id)
// 長手数に達したのか // 長手数に達したのか
if (ply >= MAX_PLY2) if (ply >= MAX_PLY2)
{ {
#if defined (LEARN_GENSFEN_USE_DRAW_RESULT) if (use_draw_in_training_data_generation) {
// 勝敗 = 引き分けとして書き出す。 // 勝敗 = 引き分けとして書き出す。
// こうしたほうが自分が入玉したときに、相手の入玉を許しにくい(かも) // こうしたほうが自分が入玉したときに、相手の入玉を許しにくい(かも)
flush_psv(0); flush_psv(0);
#endif }
break; break;
} }
if (pos.is_draw(ply)) { if (pos.is_draw(ply)) {
// Do not write if draw. if (use_draw_in_training_data_generation) {
// Write if draw.
flush_psv(0);
}
break; break;
} }
// 全駒されて詰んでいたりしないか? // 全駒されて詰んでいたりしないか?
if (MoveList<LEGAL>(pos).size() == 0) if (MoveList<LEGAL>(pos).size() == 0) // Can be mate or stalemate
{ {
// (この局面の一つ前の局面までは書き出す) // (この局面の一つ前の局面までは書き出す)
// Write the positions other than this position if checkmated. // Write the positions other than this position if checkmated.
if (pos.checkers()) // Mate
flush_psv(-1); flush_psv(-1);
else if (use_draw_in_training_data_generation) {
flush_psv(0); // Stalemate
}
break; break;
} }
@ -576,10 +589,10 @@ void MultiThinkGenSfen::thread_worker(size_t thread_id)
// 各千日手に応じた処理。 // 各千日手に応じた処理。
if (pos.is_draw(0)) { if (pos.is_draw(0)) {
#if defined (LEARN_GENSFEN_USE_DRAW_RESULT) if (use_draw_in_training_data_generation) {
// 引き分けを書き出すとき // Write if draw.
flush_psv(is_win); flush_psv(0);
#endif }
break; break;
} }
@ -1015,9 +1028,24 @@ double sigmoid(double x)
// 評価値を勝率[0,1]に変換する関数 // 評価値を勝率[0,1]に変換する関数
double winning_percentage(double value) double winning_percentage(double value)
{ {
// この600.0という定数は、ponanza定数。(ponanzaがそうしているらしいという意味で) // In Maxima,
// ゲームの進行度に合わせたものにしたほうがいいかも知れないけども、その効果のほどは不明。 // load("C:/maxima-5.44.0/cform.lisp");
return sigmoid(value / 600.0); // PawnValueEg = 206;
// cform(1.0 / (1.0 + 10.0 ^ (-value / PawnValueEg / 4.0)));
constexpr double PawnValue = PawnValueEg;
return 1.0 * pow(pow(10.0, -0.25 * pow(PawnValue, -1) * value) + 1.0, -1);
}
double delta_winning_percentage(double value)
{
// In Maxima,
// load("C:/maxima-5.44.0/cform.lisp");
// PawnValueEg = 206;
// cform(diff(1.0/(1.0+10.0^(-value/PawnValue/4.0)),value));
constexpr double PawnValue = PawnValueEg;
return
0.5756462732485115 * pow(PawnValue, -1) * pow(10.0, -0.25 * pow(PawnValue, -1) * value) *
pow(pow(10.0, -0.25 * pow(PawnValue, -1) * value) + 1.0, -2);
} }
// 普通のシグモイド関数の導関数。 // 普通のシグモイド関数の導関数。
@ -1115,8 +1143,9 @@ double calc_grad(Value deep, Value shallow , const PackedSfenValue& psv)
// elmo(WCSC27)方式 // elmo(WCSC27)方式
// 実際のゲームの勝敗で補正する。 // 実際のゲームの勝敗で補正する。
const double eval_winrate = winning_percentage(shallow); const double q = winning_percentage(shallow);
const double teacher_winrate = winning_percentage(deep); const double p = winning_percentage(deep);
const double dq = delta_winning_percentage(shallow);
// 期待勝率を勝っていれば1、負けていれば 0、引き分けなら0.5として補正項として用いる。 // 期待勝率を勝っていれば1、負けていれば 0、引き分けなら0.5として補正項として用いる。
// game_result = 1,0,-1なので1足して2で割る。 // game_result = 1,0,-1なので1足して2で割る。
@ -1127,7 +1156,9 @@ double calc_grad(Value deep, Value shallow , const PackedSfenValue& psv)
// 実際の勝率を補正項として使っている。 // 実際の勝率を補正項として使っている。
// これがelmo(WCSC27)のアイデアで、現代のオーパーツ。 // これがelmo(WCSC27)のアイデアで、現代のオーパーツ。
const double grad = (1 - lambda) * (eval_winrate - t) + lambda * (eval_winrate - teacher_winrate); const double pp = (q - p) * dq / q / (1.0 - q);
const double tt = (q - t) * dq / q / (1.0 - q);
const double grad = lambda * pp + (1.0 - lambda) * tt;
return grad; return grad;
} }
@ -1240,11 +1271,8 @@ struct SfenReader
{ {
if (eval_limit < abs(p.score)) if (eval_limit < abs(p.score))
continue; continue;
#if !defined (LEARN_GENSFEN_USE_DRAW_RESULT) if (!use_draw_in_validation && p.game_result == 0)
if (p.game_result == 0)
continue; continue;
#endif
sfen_for_mse.push_back(p); sfen_for_mse.push_back(p);
} else { } else {
break; break;
@ -1926,10 +1954,10 @@ void LearnerThink::thread_worker(size_t thread_id)
if (eval_limit < abs(ps.score)) if (eval_limit < abs(ps.score))
goto RetryRead; goto RetryRead;
#if !defined (LEARN_GENSFEN_USE_DRAW_RESULT)
if (ps.game_result == 0) if (!use_draw_in_training && ps.game_result == 0)
goto RetryRead; goto RetryRead;
#endif
// 序盤局面に関する読み飛ばし // 序盤局面に関する読み飛ばし
if (ps.gamePly < prng.rand(reduction_gameply)) if (ps.gamePly < prng.rand(reduction_gameply))
@ -1953,13 +1981,13 @@ void LearnerThink::thread_worker(size_t thread_id)
{ {
auto key = pos.key(); auto key = pos.key();
// rmseの計算用に使っている局面なら除外する。 // rmseの計算用に使っている局面なら除外する。
if (sr.is_for_rmse(key)) if (sr.is_for_rmse(key) && use_hash_in_training)
goto RetryRead; goto RetryRead;
// 直近で用いた局面も除外する。 // 直近で用いた局面も除外する。
auto hash_index = size_t(key & (sr.READ_SFEN_HASH_SIZE - 1)); auto hash_index = size_t(key & (sr.READ_SFEN_HASH_SIZE - 1));
auto key2 = sr.hash[hash_index]; auto key2 = sr.hash[hash_index];
if (key == key2) if (key == key2 && use_hash_in_training)
goto RetryRead; goto RetryRead;
sr.hash[hash_index] = key; // 今回のkeyに入れ替えておく。 sr.hash[hash_index] = key; // 今回のkeyに入れ替えておく。
} }
@ -2408,29 +2436,35 @@ void shuffle_files_on_memory(const vector<string>& filenames,const string output
std::cout << "..shuffle_on_memory done." << std::endl; std::cout << "..shuffle_on_memory done." << std::endl;
} }
void convert_bin(const vector<string>& filenames , const string& output_file_name) void convert_bin(const vector<string>& filenames, const string& output_file_name, const int ply_minimum, const int ply_maximum, const int interpolate_eval)
{ {
std::fstream fs; std::fstream fs;
uint64_t data_size=0;
uint64_t filtered_size = 0;
auto th = Threads.main(); auto th = Threads.main();
auto &tpos = th->rootPos; auto &tpos = th->rootPos;
// plain形式の雑巾をやねうら王用のpackedsfenvalueに変換する // plain形式の雑巾をやねうら王用のpackedsfenvalueに変換する
fs.open(output_file_name, ios::app | ios::binary); fs.open(output_file_name, ios::app | ios::binary);
StateListPtr states;
for (auto filename : filenames) { for (auto filename : filenames) {
std::cout << "convert " << filename << " ... "; std::cout << "convert " << filename << " ... ";
std::string line; std::string line;
ifstream ifs; ifstream ifs;
ifs.open(filename); ifs.open(filename);
PackedSfenValue p; PackedSfenValue p;
data_size = 0;
filtered_size = 0;
p.gamePly = 1; // apery形式では含まれない。一応初期化するべし p.gamePly = 1; // apery形式では含まれない。一応初期化するべし
bool ignore_flag = false;
while (std::getline(ifs, line)) { while (std::getline(ifs, line)) {
std::stringstream ss(line); std::stringstream ss(line);
std::string token; std::string token;
std::string value; std::string value;
ss >> token; ss >> token;
if (token == "sfen") { if (token == "fen") {
StateInfo si; states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
tpos.set(line.substr(5), false, &si, Threads.main()); tpos.set(line.substr(4), false, &states->back(), Threads.main());
tpos.sfen_pack(p.sfen); tpos.sfen_pack(p.sfen);
} }
else if (token == "move") { else if (token == "move") {
@ -2443,29 +2477,232 @@ void convert_bin(const vector<string>& filenames , const string& output_file_nam
else if (token == "ply") { else if (token == "ply") {
int temp; int temp;
ss >> temp; ss >> temp;
if(temp < ply_minimum || temp > ply_maximum){
ignore_flag = true;
}
p.gamePly = uint16_t(temp); // 此処のキャストいらない? p.gamePly = uint16_t(temp); // 此処のキャストいらない?
if (interpolate_eval != 0){
p.score = min(3000, interpolate_eval * temp);
}
} }
else if (token == "result") { else if (token == "result") {
int temp; int temp;
ss >> temp; ss >> temp;
p.game_result = int8_t(temp); // 此処のキャストいらない? p.game_result = int8_t(temp); // 此処のキャストいらない?
if (interpolate_eval){
p.score = p.score * p.game_result;
}
} }
else if (token == "e") { else if (token == "e") {
if(!ignore_flag){
fs.write((char*)&p, sizeof(PackedSfenValue)); fs.write((char*)&p, sizeof(PackedSfenValue));
data_size+=1;
// debug // debug
/* // std::cout<<tpos<<std::endl;
std::cout<<tpos<<std::endl; // std::cout<<p.score<<","<<int(p.gamePly)<<","<<int(p.game_result)<<std::endl;
std::cout<<to_usi_string(Move(p.move))<<","<<p.score<<","<<int(p.gamePly)<<","<<int(p.game_result)<<std::endl; }else{
*/ ignore_flag = false;
filtered_size += 1;
}
} }
} }
std::cout << "done" << std::endl; std::cout << "done" << data_size <<" parsed " << filtered_size<<" is filtered"<< std::endl;
ifs.close(); ifs.close();
} }
std::cout << "all done" << std::endl; std::cout << "all done" << std::endl;
fs.close(); fs.close();
} }
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}
int parse_game_result_from_pgn_extract(std::string result) {
// White Win
if (result == "\"1-0\"") {
return 1;
}
// Black Win
else if (result == "\"0-1\"") {
return -1;
}
// Draw
else {
return 0;
}
}
// 0.25 --> 25
// #-4 --> -mate_in(4)
// #3 --> mate_in(3)
Value parse_score_from_pgn_extract(std::string eval) {
if (eval.substr(0, 1) == "#") {
if (eval.substr(1, 1) == "-") {
return -mate_in(stoi(eval.substr(2, eval.length() - 2)));
}
else {
return mate_in(stoi(eval.substr(1, eval.length() - 1)));
}
}
else {
return Value(stod(eval) * 100.0f);
}
}
// pgn-extract形式の教師をやねうら王用のPackedSfenValueに変換する
void convert_bin_from_pgn_extract(const vector<string>& filenames, const string& output_file_name)
{
auto th = Threads.main();
auto &pos = th->rootPos;
std::fstream ofs;
ofs.open(output_file_name, ios::out | ios::binary);
int game_count = 0;
int fen_count = 0;
for (auto filename : filenames) {
std::cout << now_string() << " convert " << filename << std::endl;
ifstream ifs;
ifs.open(filename);
int game_result = 0;
std::string line;
while (std::getline(ifs, line)) {
if (line.empty()) {
continue;
}
else if (line.substr(0, 1) == "[") {
std::regex pattern_result(R"(\[Result (.+?)\])");
std::smatch match;
// example: [Result "1-0"]
if (std::regex_search(line, match, pattern_result)) {
game_result = parse_game_result_from_pgn_extract(match.str(1));
//std::cout << "game_result=" << game_result << std::endl;
game_count++;
if (game_count % 10000 == 0) {
std::cout << now_string() << " game_count=" << game_count << ", fen_count=" << fen_count << std::endl;
}
}
continue;
}
else {
int gamePly = 0;
PackedSfenValue psv;
memset((char*)&psv, 0, sizeof(PackedSfenValue));
auto itr = line.cbegin();
while (true) {
gamePly++;
std::regex pattern_bracket(R"(\{(.+?)\})");
std::regex pattern_eval(R"(\[\%eval (.+?)\])");
std::regex pattern_move(R"((.+?)\{)");
std::smatch match;
// example: { [%eval 0.25] [%clk 0:10:00] }
if (!std::regex_search(itr, line.cend(), match, pattern_bracket)) {
break;
}
itr += match.position(0) + match.length(0);
std::string str_eval_clk = match.str(1);
trim(str_eval_clk);
//std::cout << "str_eval_clk="<< str_eval_clk << std::endl;
// example: [%eval 0.25]
// example: [%eval #-4]
// example: [%eval #3]
if (!std::regex_search(str_eval_clk, match, pattern_eval)) {
continue;
}
else {
std::string str_eval = match.str(1);
trim(str_eval);
psv.score = parse_score_from_pgn_extract(str_eval);
//std::cout << "psv.score=" << psv.score << std::endl;
}
// example: { rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1 }
if (!std::regex_search(itr, line.cend(), match, pattern_bracket)) {
break;
}
itr += match.position(0) + match.length(0);
std::string str_fen = match.str(1);
trim(str_fen);
//std::cout << "str_fen=" << str_fen << std::endl;
StateInfo si;
pos.set(str_fen, false, &si, th);
pos.sfen_pack(psv.sfen);
// example: d7d5 {
if (!std::regex_search(itr, line.cend(), match, pattern_move)) {
break;
}
itr += match.position(0) + match.length(0) - 1;
std::string str_move = match.str(1);
trim(str_move);
//std::cout << "str_move=" << str_move << std::endl;
psv.move = UCI::to_move(pos, str_move);
//
psv.gamePly = gamePly;
psv.game_result = game_result;
if (pos.side_to_move() == BLACK) {
psv.score *= -1;
psv.game_result *= -1;
}
//std::cout << "write: "
// << "score=" << psv.score
// << ", move=" << psv.move
// << ", gamePly=" << psv.gamePly
// << ", game_result=" << (int)psv.game_result
// << std::endl;
ofs.write((char*)&psv, sizeof(PackedSfenValue));
memset((char*)&psv, 0, sizeof(PackedSfenValue));
fen_count++;
}
game_result = 0;
}
}
}
std::cout << now_string() << " game_count=" << game_count << ", fen_count=" << fen_count << std::endl;
std::cout << now_string() << " all done" << std::endl;
ofs.close();
}
//void convert_plain(const vector<string>& filenames , const string& output_file_name) //void convert_plain(const vector<string>& filenames , const string& output_file_name)
//{ //{
// Position tpos; // Position tpos;
@ -2549,6 +2786,11 @@ void learn(Position&, istringstream& is)
bool use_convert_plain = false; bool use_convert_plain = false;
// plain形式の教師をやねうら王のbinに変換する // plain形式の教師をやねうら王のbinに変換する
bool use_convert_bin = false; bool use_convert_bin = false;
int ply_minimum = 0;
int ply_maximum = 114514;
bool interpolate_eval = 0;
// pgn-extract形式の教師をやねうら王のbinに変換する
bool use_convert_bin_from_pgn_extract = false;
// それらのときに書き出すファイル名(デフォルトでは"shuffled_sfen.bin") // それらのときに書き出すファイル名(デフォルトでは"shuffled_sfen.bin")
string output_file_name = "shuffled_sfen.bin"; string output_file_name = "shuffled_sfen.bin";
@ -2628,7 +2870,10 @@ void learn(Position&, istringstream& is)
else if (option == "eta3") is >> eta3; else if (option == "eta3") is >> eta3;
else if (option == "eta1_epoch") is >> eta1_epoch; else if (option == "eta1_epoch") is >> eta1_epoch;
else if (option == "eta2_epoch") is >> eta2_epoch; else if (option == "eta2_epoch") is >> eta2_epoch;
else if (option == "use_draw_in_training_data_generation") is >> use_draw_in_training_data_generation;
else if (option == "use_draw_in_training") is >> use_draw_in_training;
else if (option == "use_draw_in_validation") is >> use_draw_in_validation;
else if (option == "use_hash_in_training") is >> use_hash_in_training;
// 割引率 // 割引率
else if (option == "discount_rate") is >> discount_rate; else if (option == "discount_rate") is >> discount_rate;
@ -2679,6 +2924,8 @@ void learn(Position&, istringstream& is)
// 雑巾のconvert関連 // 雑巾のconvert関連
else if (option == "convert_plain") use_convert_plain = true; else if (option == "convert_plain") use_convert_plain = true;
else if (option == "convert_bin") use_convert_bin = true; else if (option == "convert_bin") use_convert_bin = true;
else if (option == "interpolate_eval") is >> interpolate_eval;
else if (option == "convert_bin_from_pgn-extract") use_convert_bin_from_pgn_extract = true;
// さもなくば、それはファイル名である。 // さもなくば、それはファイル名である。
else else
filenames.push_back(option); filenames.push_back(option);
@ -2788,10 +3035,17 @@ void learn(Position&, istringstream& is)
{ {
is_ready(true); is_ready(true);
cout << "convert_bin.." << endl; cout << "convert_bin.." << endl;
convert_bin(filenames,output_file_name); convert_bin(filenames,output_file_name, ply_minimum, ply_maximum, interpolate_eval);
return; return;
} }
if (use_convert_bin_from_pgn_extract)
{
is_ready(true);
cout << "convert_bin_from_pgn-extract.." << endl;
convert_bin_from_pgn_extract(filenames, output_file_name);
return;
}
cout << "loop : " << loop << endl; cout << "loop : " << loop << endl;
cout << "eval_limit : " << eval_limit << endl; cout << "eval_limit : " << eval_limit << endl;

View file

@ -44,12 +44,12 @@ namespace {
constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = { constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
// THEIR PIECES // THEIR PIECES
// pair pawn knight bishop rook queen // pair pawn knight bishop rook queen
{ 0 }, // Bishop pair { }, // Bishop pair
{ 36, 0 }, // Pawn { 36, }, // Pawn
{ 9, 63, 0 }, // Knight OUR PIECES { 9, 63, }, // Knight OUR PIECES
{ 59, 65, 42, 0 }, // Bishop { 59, 65, 42, }, // Bishop
{ 46, 39, 24, -24, 0 }, // Rook { 46, 39, 24, -24, }, // Rook
{ 97, 100, -42, 137, 268, 0 } // Queen { 97, 100, -42, 137, 268, } // Queen
}; };
// Endgame evaluation and scaling functions are accessed directly and not through // Endgame evaluation and scaling functions are accessed directly and not through
@ -79,8 +79,10 @@ namespace {
&& pos.count<PAWN>(~us) >= 1; && pos.count<PAWN>(~us) >= 1;
} }
/// imbalance() calculates the imbalance by comparing the piece count of each /// imbalance() calculates the imbalance by comparing the piece count of each
/// piece type for both colors. /// piece type for both colors.
template<Color Us> template<Color Us>
int imbalance(const int pieceCount[][PIECE_TYPE_NB]) { int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
@ -94,9 +96,9 @@ namespace {
if (!pieceCount[Us][pt1]) if (!pieceCount[Us][pt1])
continue; continue;
int v = 0; int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
for (int pt2 = NO_PIECE_TYPE; pt2 <= pt1; ++pt2) for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
@ -110,6 +112,7 @@ namespace {
namespace Material { namespace Material {
/// Material::probe() looks up the current position's material configuration in /// Material::probe() looks up the current position's material configuration in
/// the material hash table. It returns a pointer to the Entry if the position /// the material hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't /// is found. Otherwise a new Entry is computed and stored there, so we don't

View file

@ -44,7 +44,7 @@ struct Entry {
bool specialized_eval_exists() const { return evaluationFunction != nullptr; } bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
// scale_factor takes a position and a color as input and returns a scale factor // 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 // for the given color. We have to provide the position in addition to the color
// because the scale factor may also be a function which should be applied to // because the scale factor may also be a function which should be applied to
// the position. For instance, in KBP vs K endgames, the scaling function looks // the position. For instance, in KBP vs K endgames, the scaling function looks

View file

@ -295,9 +295,10 @@ void prefetch(void* addr) {
#endif #endif
/// aligned_ttmem_alloc will return suitably aligned memory, and if possible use large pages. /// aligned_ttmem_alloc() will return suitably aligned memory, and if possible use large pages.
/// The returned pointer is the aligned one, while the mem argument is the one that needs to be passed to free. /// The returned pointer is the aligned one, while the mem argument is the one that needs
/// With c++17 some of this functionality can be simplified. /// to be passed to free. With c++17 some of this functionality could be simplified.
#if defined(__linux__) && !defined(__ANDROID__) #if defined(__linux__) && !defined(__ANDROID__)
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) { void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
@ -337,17 +338,17 @@ static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
// we still need to query GetLastError() to ensure that the privileges were actually obtained... // we still need to query GetLastError() to ensure that the privileges were actually obtained.
if (AdjustTokenPrivileges( if (AdjustTokenPrivileges(
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
GetLastError() == ERROR_SUCCESS) GetLastError() == ERROR_SUCCESS)
{ {
// round up size to full pages and allocate // Round up size to full pages and allocate
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
mem = VirtualAlloc( mem = VirtualAlloc(
NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
// privilege no longer needed, restore previous state // Privilege no longer needed, restore previous state
AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL); AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL);
} }
} }
@ -361,7 +362,7 @@ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
static bool firstCall = true; static bool firstCall = true;
// try to allocate large pages // Try to allocate large pages
mem = aligned_ttmem_alloc_large_pages(allocSize); mem = aligned_ttmem_alloc_large_pages(allocSize);
// Suppress info strings on the first call. The first call occurs before 'uci' // Suppress info strings on the first call. The first call occurs before 'uci'
@ -375,7 +376,7 @@ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
} }
firstCall = false; firstCall = false;
// fall back to regular, page aligned, allocation if necessary // Fall back to regular, page aligned, allocation if necessary
if (!mem) if (!mem)
mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
@ -395,7 +396,9 @@ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
#endif #endif
/// aligned_ttmem_free will free the previously allocated ttmem
/// aligned_ttmem_free() will free the previously allocated ttmem
#if defined(_WIN64) #if defined(_WIN64)
void aligned_ttmem_free(void* mem) { void aligned_ttmem_free(void* mem) {

View file

@ -130,6 +130,20 @@ inline std::ostream& operator<<(std::ostream& os, PRNG& prng)
return os; return os;
} }
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ typedef unsigned __int128 uint128;
return ((uint128)a * (uint128)b) >> 64;
#else
uint64_t aL = (uint32_t)a, aH = a >> 32;
uint64_t bL = (uint32_t)b, bH = b >> 32;
uint64_t c1 = (aL * bL) >> 32;
uint64_t c2 = aH * bL + c1;
uint64_t c3 = aL * bH + (uint32_t)c2;
return aH * bH + (c2 >> 32) + (c3 >> 32);
#endif
}
/// Under Windows it is not possible for a process to run on more than one /// Under Windows it is not possible for a process to run on more than one
/// logical processor group. This usually means to be limited to use max 64 /// logical processor group. This usually means to be limited to use max 64
/// cores. To overcome this, some special platform specific API should be /// cores. To overcome this, some special platform specific API should be

View file

@ -332,7 +332,7 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
// the king evasions in order to skip known illegal moves, which avoids any // the king evasions in order to skip known illegal moves, which avoids any
// useless legality checks later on. // useless legality checks later on.
while (sliders) while (sliders)
sliderAttacks |= LineBB[ksq][pop_lsb(&sliders)] & ~pos.checkers(); sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers();
// Generate evasions for king, capture and non capture moves // Generate evasions for king, capture and non capture moves
Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks; Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;

View file

@ -57,7 +57,7 @@ namespace {
/// MovePicker constructor for the main search /// MovePicker constructor for the main search
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp, MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp,
const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers, int pl) const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl)
: pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch), : pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) { ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {

View file

@ -88,9 +88,9 @@ enum StatsType { NoCaptures, Captures };
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory; typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
/// LowPlyHistory at higher depths records successful quiet moves on plies 0 to 3 /// At higher depths LowPlyHistory records successful quiet moves near the root and quiet
/// and quiet moves which are/were in the PV (ttPv) /// moves which are/were in the PV (ttPv)
/// It get cleared with each new search and get filled during iterative deepening /// It is cleared with each new search and filled during iterative deepening
constexpr int MAX_LPH = 4; constexpr int MAX_LPH = 4;
typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory; typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
@ -133,7 +133,7 @@ public:
const CapturePieceToHistory*, const CapturePieceToHistory*,
const PieceToHistory**, const PieceToHistory**,
Move, Move,
Move*, const Move*,
int); int);
Move next_move(bool skipQuiets = false); Move next_move(bool skipQuiets = false);

View file

@ -66,6 +66,12 @@ namespace {
#undef S #undef S
#undef V #undef V
/// evaluate() calculates a score for the static pawn structure of the given position.
/// We cannot use the location of pieces or king in this function, as the evaluation
/// of the pawn structure will be stored in a small cache for speed reasons, and will
/// be re-used even when the pieces have moved.
template<Color Us> template<Color Us>
Score evaluate(const Position& pos, Pawns::Entry* e) { Score evaluate(const Position& pos, Pawns::Entry* e) {
@ -170,6 +176,7 @@ namespace {
namespace Pawns { namespace Pawns {
/// Pawns::probe() looks up the current position's pawns configuration in /// Pawns::probe() looks up the current position's pawns configuration in
/// the pawns hash table. It returns a pointer to the Entry if the position /// the pawns hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't /// is found. Otherwise a new Entry is computed and stored there, so we don't
@ -196,7 +203,7 @@ Entry* probe(const Position& pos) {
/// penalty for a king, looking at the king file and the two closest files. /// penalty for a king, looking at the king file and the two closest files.
template<Color Us> template<Color Us>
Score Entry::evaluate_shelter(const Position& pos, Square ksq) { Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
constexpr Color Them = ~Us; constexpr Color Them = ~Us;

View file

@ -50,7 +50,7 @@ struct Entry {
Score do_king_safety(const Position& pos); Score do_king_safety(const Position& pos);
template<Color Us> template<Color Us>
Score evaluate_shelter(const Position& pos, Square ksq); Score evaluate_shelter(const Position& pos, Square ksq) const;
Key key; Key key;
Score scores[COLOR_NB]; Score scores[COLOR_NB];

View file

@ -64,10 +64,11 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
for (File f = FILE_A; f <= FILE_H; ++f) for (File f = FILE_A; f <= FILE_H; ++f)
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
os << " |\n +---+---+---+---+---+---+---+---+\n"; os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
} }
os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase os << " a b c d e f g h\n"
<< "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
<< std::setfill('0') << std::setw(16) << pos.key() << std::setfill('0') << std::setw(16) << pos.key()
<< std::setfill(' ') << std::dec << "\nCheckers: "; << std::setfill(' ') << std::dec << "\nCheckers: ";
@ -104,8 +105,7 @@ Key cuckoo[8192];
Move cuckooMove[8192]; Move cuckooMove[8192];
/// Position::init() initializes at startup the various arrays used to compute /// Position::init() initializes at startup the various arrays used to compute hash keys
/// hash keys.
void Position::init() { void Position::init() {
@ -1277,6 +1277,7 @@ bool Position::see_ge(Move m, Value threshold) const {
return bool(res); return bool(res);
} }
/// Position::is_draw() tests whether the position is drawn by 50-move rule /// Position::is_draw() tests whether the position is drawn by 50-move rule
/// or by repetition. It does not detect stalemates. /// or by repetition. It does not detect stalemates.

View file

@ -68,6 +68,7 @@ struct StateInfo {
#endif // defined(EVAL_NNUE) #endif // defined(EVAL_NNUE)
}; };
/// A list to keep track of the position states along the setup moves (from the /// A list to keep track of the position states along the setup moves (from the
/// start position to the position just before the search starts). Needed by /// start position to the position just before the search starts). Needed by
/// 'draw by repetition' detection. Use a std::deque because pointers to /// 'draw by repetition' detection. Use a std::deque because pointers to

View file

@ -91,9 +91,9 @@ constexpr Score PBonus[RANK_NB][FILE_NB] =
{ }, { },
{ S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) }, { S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) },
{ S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) }, { S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) },
{ S( -8, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S(-12, -9) }, { S( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) },
{ S( 13, 9), S( 0, 4), S(-13, 3), S( 1,-12), S( 11,-12), S( -2, -6), S(-13, 13), S( 5, 8) }, { S( 13, 9), S( 0, 4), S(-13, 3), S( 1,-12), S( 11,-12), S( -2, -6), S(-13, 13), S( 5, 8) },
{ S( -5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S(-18, 13) }, { S( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) },
{ S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) } { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) }
}; };
@ -101,12 +101,13 @@ constexpr Score PBonus[RANK_NB][FILE_NB] =
Score psq[PIECE_NB][SQUARE_NB]; Score psq[PIECE_NB][SQUARE_NB];
// init() initializes piece-square tables: the white halves of the tables are
// copied from Bonus[] adding the piece value, then the black halves of the // PSQT::init() initializes piece-square tables: the white halves of the tables are
// tables are initialized by flipping and changing the sign of the white scores. // copied from Bonus[] and PBonus[], adding the piece value, then the black halves of
// the tables are initialized by flipping and changing the sign of the white scores.
void init() { void init() {
for (Piece pc = W_PAWN; pc <= W_KING; ++pc) for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING})
{ {
Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);

View file

@ -65,9 +65,9 @@ namespace {
constexpr uint64_t TtHitAverageResolution = 1024; constexpr uint64_t TtHitAverageResolution = 1024;
// Razor and futility margins // Razor and futility margins
constexpr int RazorMargin = 531; constexpr int RazorMargin = 527;
Value futility_margin(Depth d, bool improving) { Value futility_margin(Depth d, bool improving) {
return Value(217 * (d - improving)); return Value(227 * (d - improving));
} }
// Reductions lookup table, initialized at startup // Reductions lookup table, initialized at startup
@ -75,16 +75,16 @@ namespace {
Depth reduction(bool i, Depth d, int mn) { Depth reduction(bool i, Depth d, int mn) {
int r = Reductions[d] * Reductions[mn]; int r = Reductions[d] * Reductions[mn];
return (r + 511) / 1024 + (!i && r > 1007); return (r + 570) / 1024 + (!i && r > 1018);
} }
constexpr int futility_move_count(bool improving, Depth depth) { constexpr int futility_move_count(bool improving, Depth depth) {
return (4 + depth * depth) / (2 - improving); return (3 + depth * depth) / (2 - improving);
} }
// History and stats update bonus, based on depth // History and stats update bonus, based on depth
int stat_bonus(Depth d) { int stat_bonus(Depth d) {
return d > 15 ? -8 : 19 * d * d + 155 * d - 132; return d > 15 ? 27 : 17 * d * d + 133 * d - 134;
} }
// Add a small random component to draw evaluations to avoid 3fold-blindness // Add a small random component to draw evaluations to avoid 3fold-blindness
@ -236,14 +236,8 @@ void MainThread::search() {
} }
else else
{ {
for (Thread* th : Threads) Threads.start_searching(); // start non-main threads
{ Thread::search(); // main thread start searching
th->bestMoveChanges = 0;
if (th != this)
th->start_searching();
}
Thread::search(); // Let's start searching!
} }
// When we reach the maximum depth, we can arrive here without a raise of // When we reach the maximum depth, we can arrive here without a raise of
@ -260,9 +254,7 @@ void MainThread::search() {
Threads.stop = true; Threads.stop = true;
// Wait until all threads have finished // Wait until all threads have finished
for (Thread* th : Threads) Threads.wait_for_search_finished();
if (th != this)
th->wait_for_search_finished();
// When playing in 'nodes as time' mode, subtract the searched nodes from // When playing in 'nodes as time' mode, subtract the searched nodes from
// the available ones before exiting. // the available ones before exiting.
@ -271,37 +263,11 @@ void MainThread::search() {
Thread* bestThread = this; Thread* bestThread = this;
// Check if there are threads with a better score than main thread if (int(Options["MultiPV"]) == 1 &&
if ( int(Options["MultiPV"]) == 1 !Limits.depth &&
&& !Limits.depth !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) &&
&& !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) rootMoves[0].pv[0] != MOVE_NONE)
&& rootMoves[0].pv[0] != MOVE_NONE) bestThread = Threads.get_best_thread();
{
std::map<Move, int64_t> votes;
Value minScore = this->rootMoves[0].score;
// Find minimum score
for (Thread* th: Threads)
minScore = std::min(minScore, th->rootMoves[0].score);
// Vote according to score and depth, and select the best thread
for (Thread* th : Threads)
{
votes[th->rootMoves[0].pv[0]] +=
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
{
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
bestThread = th;
}
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
bestThread = th;
}
}
bestPreviousScore = bestThread->rootMoves[0].score; bestPreviousScore = bestThread->rootMoves[0].score;
@ -437,12 +403,12 @@ void Thread::search() {
if (rootDepth >= 4) if (rootDepth >= 4)
{ {
Value prev = rootMoves[pvIdx].previousScore; Value prev = rootMoves[pvIdx].previousScore;
delta = Value(21); delta = Value(19);
alpha = std::max(prev - delta,-VALUE_INFINITE); alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE);
// Adjust contempt based on root move's previousScore (dynamic contempt) // Adjust contempt based on root move's previousScore (dynamic contempt)
int dct = ct + (102 - ct / 2) * prev / (abs(prev) + 157); int dct = ct + (110 - ct / 2) * prev / (abs(prev) + 140);
contempt = (us == WHITE ? make_score(dct, dct / 2) contempt = (us == WHITE ? make_score(dct, dct / 2)
: -make_score(dct, dct / 2)); : -make_score(dct, dct / 2));
@ -540,13 +506,13 @@ void Thread::search() {
&& !Threads.stop && !Threads.stop
&& !mainThread->stopOnPonderhit) && !mainThread->stopOnPonderhit)
{ {
double fallingEval = (332 + 6 * (mainThread->bestPreviousScore - bestValue) double fallingEval = (296 + 6 * (mainThread->bestPreviousScore - bestValue)
+ 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 704.0; + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 725.0;
fallingEval = Utility::clamp(fallingEval, 0.5, 1.5); fallingEval = Utility::clamp(fallingEval, 0.5, 1.5);
// If the bestMove is stable over several iterations, reduce time accordingly // If the bestMove is stable over several iterations, reduce time accordingly
timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.94 : 0.91; timeReduction = lastBestMoveDepth + 10 < completedDepth ? 1.92 : 0.95;
double reduction = (1.41 + mainThread->previousTimeReduction) / (2.27 * timeReduction); double reduction = (1.47 + mainThread->previousTimeReduction) / (2.22 * timeReduction);
// Use part of the gained time from a previous stable move for the current move // Use part of the gained time from a previous stable move for the current move
for (Thread* th : Threads) for (Thread* th : Threads)
@ -559,7 +525,7 @@ void Thread::search() {
double totalTime = rootMoves.size() == 1 ? 0 : double totalTime = rootMoves.size() == 1 ? 0 :
Time.optimum() * fallingEval * reduction * bestMoveInstability; Time.optimum() * fallingEval * reduction * bestMoveInstability;
// Stop the search if we have exceeded the totalTime, at least 1ms search. // Stop the search if we have exceeded the totalTime, at least 1ms search
if (Time.elapsed() > totalTime) if (Time.elapsed() > totalTime)
{ {
// If we are allowed to ponder do not stop the search now but // If we are allowed to ponder do not stop the search now but
@ -571,7 +537,7 @@ void Thread::search() {
} }
else if ( Threads.increaseDepth else if ( Threads.increaseDepth
&& !mainThread->ponder && !mainThread->ponder
&& Time.elapsed() > totalTime * 0.6) && Time.elapsed() > totalTime * 0.56)
Threads.increaseDepth = false; Threads.increaseDepth = false;
else else
Threads.increaseDepth = true; Threads.increaseDepth = true;
@ -696,7 +662,7 @@ namespace {
// search to overwrite a previous full search TT value, so we use a different // search to overwrite a previous full search TT value, so we use a different
// position key in case of an excluded move. // position key in case of an excluded move.
excludedMove = ss->excludedMove; excludedMove = ss->excludedMove;
posKey = pos.key() ^ Key(excludedMove << 16); // Isn't a very good hash posKey = pos.key() ^ (Key(excludedMove) << 48); // Isn't a very good hash
tte = TT.probe(posKey, ttHit); tte = TT.probe(posKey, ttHit);
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
@ -704,7 +670,7 @@ namespace {
ttPv = PvNode || (ttHit && tte->is_pv()); ttPv = PvNode || (ttHit && tte->is_pv());
formerPv = ttPv && !PvNode; formerPv = ttPv && !PvNode;
if (ttPv && depth > 12 && ss->ply - 1 < MAX_LPH && !pos.captured_piece() && is_ok((ss-1)->currentMove)) if (ttPv && depth > 12 && ss->ply - 1 < MAX_LPH && !priorCapture && is_ok((ss-1)->currentMove))
thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5); thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5);
// thisThread->ttHitAverage can be used to approximate the running average of ttHit // thisThread->ttHitAverage can be used to approximate the running average of ttHit
@ -801,9 +767,10 @@ namespace {
// Step 6. Static evaluation of the position // Step 6. Static evaluation of the position
if (ss->inCheck) if (ss->inCheck)
{ {
// Skip early pruning when in check
ss->staticEval = eval = VALUE_NONE; ss->staticEval = eval = VALUE_NONE;
improving = false; improving = false;
goto moves_loop; // Skip early pruning when in check goto moves_loop;
} }
else if (ttHit) else if (ttHit)
{ {
@ -853,10 +820,10 @@ namespace {
// Step 9. Null move search with verification search (~40 Elo) // Step 9. Null move search with verification search (~40 Elo)
if ( !PvNode if ( !PvNode
&& (ss-1)->currentMove != MOVE_NULL && (ss-1)->currentMove != MOVE_NULL
&& (ss-1)->statScore < 23397 && (ss-1)->statScore < 23824
&& eval >= beta && eval >= beta
&& eval >= ss->staticEval && eval >= ss->staticEval
&& ss->staticEval >= beta - 32 * depth - 30 * improving + 120 * ttPv + 292 && ss->staticEval >= beta - 33 * depth - 33 * improving + 112 * ttPv + 311
&& !excludedMove && !excludedMove
&& pos.non_pawn_material(us) && pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
@ -864,7 +831,7 @@ namespace {
assert(eval - beta >= 0); assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and value // Null move dynamic reduction based on depth and value
Depth R = (854 + 68 * depth) / 258 + std::min(int(eval - beta) / 192, 3); Depth R = (737 + 77 * depth) / 246 + std::min(int(eval - beta) / 192, 3);
ss->currentMove = MOVE_NULL; ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
@ -904,10 +871,10 @@ namespace {
// If we have a good enough capture and a reduced search returns a value // If we have a good enough capture and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move. // much above beta, we can (almost) safely prune the previous move.
if ( !PvNode if ( !PvNode
&& depth >= 5 && depth > 4
&& abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
{ {
Value raisedBeta = beta + 189 - 45 * improving; Value raisedBeta = beta + 176 - 49 * improving;
assert(raisedBeta < VALUE_INFINITE); assert(raisedBeta < VALUE_INFINITE);
MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &captureHistory); MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &captureHistory);
int probCutCount = 0; int probCutCount = 0;
@ -1037,14 +1004,15 @@ moves_loop: // When in check, search starts from here
// Futility pruning: parent node (~5 Elo) // Futility pruning: parent node (~5 Elo)
if ( lmrDepth < 6 if ( lmrDepth < 6
&& !ss->inCheck && !ss->inCheck
&& ss->staticEval + 235 + 172 * lmrDepth <= alpha && ss->staticEval + 284 + 188 * lmrDepth <= alpha
&& (*contHist[0])[movedPiece][to_sq(move)] && (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)] < 27400) + (*contHist[3])[movedPiece][to_sq(move)]
+ (*contHist[5])[movedPiece][to_sq(move)] / 2 < 28388)
continue; continue;
// Prune moves with negative SEE (~20 Elo) // Prune moves with negative SEE (~20 Elo)
if (!pos.see_ge(move, Value(-(32 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 17)) * lmrDepth * lmrDepth)))
continue; continue;
} }
else else
@ -1059,12 +1027,14 @@ moves_loop: // When in check, search starts from here
if ( !givesCheck if ( !givesCheck
&& lmrDepth < 6 && lmrDepth < 6
&& !(PvNode && abs(bestValue) < 2) && !(PvNode && abs(bestValue) < 2)
&& PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))]
&& !ss->inCheck && !ss->inCheck
&& ss->staticEval + 270 + 384 * lmrDepth + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha) && ss->staticEval + 267 + 391 * lmrDepth
+ PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha)
continue; continue;
// See based pruning // See based pruning
if (!pos.see_ge(move, Value(-194) * depth)) // (~25 Elo) if (!pos.see_ge(move, Value(-202) * depth)) // (~25 Elo)
continue; continue;
} }
} }
@ -1106,8 +1076,8 @@ moves_loop: // When in check, search starts from here
else if (singularBeta >= beta) else if (singularBeta >= beta)
return singularBeta; return singularBeta;
// If the eval of ttMove is greater than beta we try also if there is an other move that // If the eval of ttMove is greater than beta we try also if there is another
// pushes it over beta, if so also produce a cutoff // move that pushes it over beta, if so also produce a cutoff.
else if (ttValue >= beta) else if (ttValue >= beta)
{ {
ss->excludedMove = move; ss->excludedMove = move;
@ -1177,15 +1147,15 @@ moves_loop: // When in check, search starts from here
|| moveCountPruning || moveCountPruning
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|| cutNode || cutNode
|| thisThread->ttHitAverage < 375 * TtHitAverageResolution * TtHitAverageWindow / 1024)) || thisThread->ttHitAverage < 415 * TtHitAverageResolution * TtHitAverageWindow / 1024))
{ {
Depth r = reduction(improving, depth, moveCount); Depth r = reduction(improving, depth, moveCount);
// Decrease reduction if the ttHit running average is large // Decrease reduction if the ttHit running average is large
if (thisThread->ttHitAverage > 500 * TtHitAverageResolution * TtHitAverageWindow / 1024) if (thisThread->ttHitAverage > 473 * TtHitAverageResolution * TtHitAverageWindow / 1024)
r--; r--;
// Reduction if other threads are searching this position. // Reduction if other threads are searching this position
if (th.marked()) if (th.marked())
r++; r++;
@ -1197,7 +1167,7 @@ moves_loop: // When in check, search starts from here
r++; r++;
// Decrease reduction if opponent's move count is high (~5 Elo) // Decrease reduction if opponent's move count is high (~5 Elo)
if ((ss-1)->moveCount > 14) if ((ss-1)->moveCount > 13)
r--; r--;
// Decrease reduction if ttMove has been singularly extended (~3 Elo) // Decrease reduction if ttMove has been singularly extended (~3 Elo)
@ -1219,23 +1189,23 @@ moves_loop: // When in check, search starts from here
// hence break make_move(). (~2 Elo) // hence break make_move(). (~2 Elo)
else if ( type_of(move) == NORMAL else if ( type_of(move) == NORMAL
&& !pos.see_ge(reverse_move(move))) && !pos.see_ge(reverse_move(move)))
r -= 2 + ttPv; r -= 2 + ttPv - (type_of(movedPiece) == PAWN);
ss->statScore = thisThread->mainHistory[us][from_to(move)] ss->statScore = thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)]
- 4926; - 4826;
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo) // Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
if (ss->statScore >= -102 && (ss-1)->statScore < -114) if (ss->statScore >= -100 && (ss-1)->statScore < -112)
r--; r--;
else if ((ss-1)->statScore >= -116 && ss->statScore < -154) else if ((ss-1)->statScore >= -125 && ss->statScore < -138)
r++; r++;
// Decrease/increase reduction for moves with a good/bad history (~30 Elo) // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
r -= ss->statScore / 16434; r -= ss->statScore / 14615;
} }
else else
{ {
@ -1245,7 +1215,7 @@ moves_loop: // When in check, search starts from here
// Unless giving check, this capture is likely bad // Unless giving check, this capture is likely bad
if ( !givesCheck if ( !givesCheck
&& ss->staticEval + PieceValue[EG][pos.captured_piece()] + 200 * depth <= alpha) && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 211 * depth <= alpha)
r++; r++;
} }
@ -1322,7 +1292,7 @@ moves_loop: // When in check, search starts from here
rm.pv.push_back(*m); rm.pv.push_back(*m);
// We record how often the best move has been changed in each // We record how often the best move has been changed in each
// iteration. This information is used for time management: When // iteration. This information is used for time management: when
// the best move changes frequently, we allocate some more time. // the best move changes frequently, we allocate some more time.
if (moveCount > 1) if (moveCount > 1)
++thisThread->bestMoveChanges; ++thisThread->bestMoveChanges;
@ -1507,7 +1477,7 @@ moves_loop: // When in check, search starts from here
if (PvNode && bestValue > alpha) if (PvNode && bestValue > alpha)
alpha = bestValue; alpha = bestValue;
futilityBase = bestValue + 154; futilityBase = bestValue + 141;
} }
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
@ -1556,7 +1526,7 @@ moves_loop: // When in check, search starts from here
} }
} }
// Don't search moves with negative SEE values // Do not search moves with negative SEE values
if ( !ss->inCheck && !pos.see_ge(move)) if ( !ss->inCheck && !pos.see_ge(move))
continue; continue;
@ -1609,7 +1579,7 @@ moves_loop: // When in check, search starts from here
} }
} }
// All legal moves have been searched. A special case: If we're in check // All legal moves have been searched. A special case: if we're in check
// and no legal moves were found, it is checkmate. // and no legal moves were found, it is checkmate.
if (ss->inCheck && bestValue == -VALUE_INFINITE) if (ss->inCheck && bestValue == -VALUE_INFINITE)
return mated_in(ss->ply); // Plies to mate from the root return mated_in(ss->ply); // Plies to mate from the root
@ -1626,7 +1596,7 @@ moves_loop: // When in check, search starts from here
// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to
// "plies to mate from the current position". standard scores are unchanged. // "plies to mate from the current position". Standard scores are unchanged.
// The function is called before storing a value in the transposition table. // The function is called before storing a value in the transposition table.
Value value_to_tt(Value v, int ply) { Value value_to_tt(Value v, int ply) {
@ -1638,11 +1608,11 @@ moves_loop: // When in check, search starts from here
} }
// value_from_tt() is the inverse of value_to_tt(): It adjusts a mate or TB score // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score
// from the transposition table (which refers to the plies to mate/be mated // from the transposition table (which refers to the plies to mate/be mated from
// from current position) to "plies to mate/be mated (TB win/loss) from the root". // current position) to "plies to mate/be mated (TB win/loss) from the root". However,
// However, for mate scores, to avoid potentially false mate scores related to the 50 moves rule, // for mate scores, to avoid potentially false mate scores related to the 50 moves rule
// and the graph history interaction, return an optimal TB score instead. // and the graph history interaction, we return an optimal TB score instead.
Value value_from_tt(Value v, int ply, int r50c) { Value value_from_tt(Value v, int ply, int r50c) {
@ -1763,8 +1733,8 @@ moves_loop: // When in check, search starts from here
thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move;
} }
if (depth > 12 && ss->ply < MAX_LPH) if (depth > 11 && ss->ply < MAX_LPH)
thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7); thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 6);
} }
// When playing with strength handicap, choose best move among a set of RootMoves // When playing with strength handicap, choose best move among a set of RootMoves
@ -1802,6 +1772,7 @@ moves_loop: // When in check, search starts from here
} // namespace } // namespace
/// MainThread::check_time() is used to print debug info and, more importantly, /// MainThread::check_time() is used to print debug info and, more importantly,
/// to detect when we are out of available time and thus stop the search. /// to detect when we are out of available time and thus stop the search.

View file

@ -1200,7 +1200,7 @@ WDLScore search(Position& pos, ProbeState* result) {
auto moveList = MoveList<LEGAL>(pos); auto moveList = MoveList<LEGAL>(pos);
size_t totalCount = moveList.size(), moveCount = 0; size_t totalCount = moveList.size(), moveCount = 0;
for (const Move& move : moveList) for (const Move move : moveList)
{ {
if ( !pos.capture(move) if ( !pos.capture(move)
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
@ -1362,7 +1362,7 @@ void Tablebases::init(const std::string& paths) {
LeadPawnsSize[leadPawnsCnt][f] = idx; LeadPawnsSize[leadPawnsCnt][f] = idx;
} }
// Add entries in TB tables if the corresponding ".rtbw" file exsists // Add entries in TB tables if the corresponding ".rtbw" file exists
for (PieceType p1 = PAWN; p1 < KING; ++p1) { for (PieceType p1 = PAWN; p1 < KING; ++p1) {
TBTables.add({KING, p1, KING}); TBTables.add({KING, p1, KING});
@ -1469,7 +1469,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
StateInfo st; StateInfo st;
int minDTZ = 0xFFFF; int minDTZ = 0xFFFF;
for (const Move& move : MoveList<LEGAL>(pos)) for (const Move move : MoveList<LEGAL>(pos))
{ {
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;

View file

@ -52,6 +52,7 @@ Thread::~Thread() {
stdThread.join(); stdThread.join();
} }
/// Thread::bestMoveCount(Move move) return best move counter for the given root move /// Thread::bestMoveCount(Move move) return best move counter for the given root move
int Thread::best_move_count(Move move) const { int Thread::best_move_count(Move move) const {
@ -62,6 +63,7 @@ int Thread::best_move_count(Move move) const {
return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0; return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
} }
/// Thread::clear() reset histories, usually before a new game /// Thread::clear() reset histories, usually before a new game
void Thread::clear() { void Thread::clear() {
@ -81,6 +83,7 @@ void Thread::clear() {
} }
} }
/// Thread::start_searching() wakes up the thread that will start the search /// Thread::start_searching() wakes up the thread that will start the search
void Thread::start_searching() { void Thread::start_searching() {
@ -158,7 +161,8 @@ void ThreadPool::set(size_t requested) {
} }
} }
/// ThreadPool::clear() sets threadPool data to initial values.
/// ThreadPool::clear() sets threadPool data to initial values
void ThreadPool::clear() { void ThreadPool::clear() {
@ -170,6 +174,7 @@ void ThreadPool::clear() {
main()->previousTimeReduction = 1.0; main()->previousTimeReduction = 1.0;
} }
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and /// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
/// returns immediately. Main thread will wake up other threads and start the search. /// returns immediately. Main thread will wake up other threads and start the search.
@ -208,7 +213,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
for (Thread* th : *this) for (Thread* th : *this)
{ {
th->nodes = th->tbHits = th->nmpMinPly = 0; th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
th->rootDepth = th->completedDepth = 0; th->rootDepth = th->completedDepth = 0;
th->rootMoves = rootMoves; th->rootMoves = rootMoves;
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
@ -218,3 +223,54 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
main()->start_searching(); main()->start_searching();
} }
Thread* ThreadPool::get_best_thread() const {
Thread* bestThread = front();
std::map<Move, int64_t> votes;
Value minScore = VALUE_NONE;
// Find minimum score of all threads
for (Thread* th: *this)
minScore = std::min(minScore, th->rootMoves[0].score);
// Vote according to score and depth, and select the best thread
for (Thread* th : *this)
{
votes[th->rootMoves[0].pv[0]] +=
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
{
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
bestThread = th;
}
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
bestThread = th;
}
return bestThread;
}
/// Start non-main threads
void ThreadPool::start_searching() {
for (Thread* th : *this)
if (th != front())
th->start_searching();
}
/// Wait for non-main threads
void ThreadPool::wait_for_search_finished() const {
for (Thread* th : *this)
if (th != front())
th->wait_for_search_finished();
}

View file

@ -109,6 +109,9 @@ struct ThreadPool : public std::vector<Thread*> {
MainThread* main() const { return static_cast<MainThread*>(front()); } MainThread* main() const { return static_cast<MainThread*>(front()); }
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
std::atomic_bool stop, increaseDepth; std::atomic_bool stop, increaseDepth;

View file

@ -28,14 +28,14 @@
TimeManagement Time; // Our global time management object TimeManagement Time; // Our global time management object
/// init() is called at the beginning of the search and calculates the bounds
/// of time allowed for the current game ply. We currently support: /// TimeManagement::init() is called at the beginning of the search and calculates
/// the bounds of time allowed for the current game ply. We currently support:
// 1) x basetime (+ z increment) // 1) x basetime (+ z increment)
// 2) x moves in y seconds (+ z increment) // 2) x moves in y seconds (+ z increment)
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
TimePoint minThinkingTime = TimePoint(Options["Minimum Thinking Time"]);
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
TimePoint slowMover = TimePoint(Options["Slow Mover"]); TimePoint slowMover = TimePoint(Options["Slow Mover"]);
TimePoint npmsec = TimePoint(Options["nodestime"]); TimePoint npmsec = TimePoint(Options["nodestime"]);
@ -79,7 +79,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
{ {
opt_scale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0, opt_scale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0,
0.2 * limits.time[us] / double(timeLeft)); 0.2 * limits.time[us] / double(timeLeft));
max_scale = 4 + std::min(36, ply) / 12.0; max_scale = std::min(7.0, 4.0 + ply / 12.0);
} }
// x moves in y seconds (+ z increment) // x moves in y seconds (+ z increment)
@ -91,7 +91,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
} }
// Never use more than 80% of the available time for this move // Never use more than 80% of the available time for this move
optimumTime = std::max(minThinkingTime, TimePoint(opt_scale * timeLeft)); optimumTime = TimePoint(opt_scale * timeLeft);
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, max_scale * optimumTime)); maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, max_scale * optimumTime));
if (Options["Ponder"]) if (Options["Ponder"])

View file

@ -30,23 +30,23 @@
TranspositionTable TT; // Our global transposition table TranspositionTable TT; // Our global transposition table
/// TTEntry::save populates the TTEntry with a new node's data, possibly /// TTEntry::save() populates the TTEntry with a new node's data, possibly
/// overwriting an old position. Update is not atomic and can be racy. /// overwriting an old position. Update is not atomic and can be racy.
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
// Preserve any existing move for the same position // Preserve any existing move for the same position
if (m || (k >> 48) != key16) if (m || (uint16_t)k != key16)
move16 = (uint16_t)m; move16 = (uint16_t)m;
// Overwrite less valuable entries // Overwrite less valuable entries
if ( (k >> 48) != key16 if ((uint16_t)k != key16
|| d - DEPTH_OFFSET > depth8 - 4 || d - DEPTH_OFFSET > depth8 - 4
|| b == BOUND_EXACT) || b == BOUND_EXACT)
{ {
assert(d >= DEPTH_OFFSET); assert(d >= DEPTH_OFFSET);
key16 = (uint16_t)(k >> 48); key16 = (uint16_t)k;
value16 = (int16_t)v; value16 = (int16_t)v;
eval16 = (int16_t)ev; eval16 = (int16_t)ev;
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
@ -107,6 +107,7 @@ void TranspositionTable::clear() {
th.join(); th.join();
} }
/// TranspositionTable::probe() looks up the current position in the transposition /// TranspositionTable::probe() looks up the current position in the transposition
/// table. It returns true and a pointer to the TTEntry if the position is found. /// table. It returns true and a pointer to the TTEntry if the position is found.
/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry /// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
@ -120,7 +121,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
#else #else
TTEntry* const tte = first_entry(key); TTEntry* const tte = first_entry(key);
const uint16_t key16 = key >> 48; // Use the high 16 bits as key inside the cluster const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
for (int i = 0; i < ClusterSize; ++i) for (int i = 0; i < ClusterSize; ++i)
if (!tte[i].key16 || tte[i].key16 == key16) if (!tte[i].key16 || tte[i].key16 == key16)

View file

@ -60,8 +60,8 @@ private:
/// A TranspositionTable is an array of Cluster, of size clusterCount. Each /// A TranspositionTable is an array of Cluster, of size clusterCount. Each
/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry /// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
/// contains information on exactly one position. The size of a Cluster should /// contains information on exactly one position. The size of a Cluster should
/// divide the size of a cache line for best performance, /// divide the size of a cache line for best performance, as the cacheline is
/// as the cacheline is prefetched when possible. /// prefetched when possible.
class TranspositionTable { class TranspositionTable {
@ -82,9 +82,8 @@ public:
void resize(size_t mbSize); void resize(size_t mbSize);
void clear(); void clear();
// The 32 lowest order bits of the key are used to get the index of the cluster
TTEntry* first_entry(const Key key) const { TTEntry* first_entry(const Key key) const {
return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0]; return &table[mul_hi64(key, clusterCount)].entry[0];
} }
private: private:

View file

@ -70,7 +70,7 @@ static void make_option(const string& n, int v, const SetRange& r) {
Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
LastOption = &Options[n]; LastOption = &Options[n];
// Print formatted parameters, ready to be copy-pasted in fishtest // Print formatted parameters, ready to be copy-pasted in Fishtest
std::cout << n << "," std::cout << n << ","
<< v << "," << v << ","
<< r(v).first << "," << r(v).second << "," << r(v).first << "," << r(v).second << ","

View file

@ -219,7 +219,6 @@ constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
typedef int Depth; typedef int Depth;
enum : int { enum : int {
DEPTH_QS_CHECKS = 0, DEPTH_QS_CHECKS = 0,
DEPTH_QS_NO_CHECKS = -1, DEPTH_QS_NO_CHECKS = -1,
DEPTH_QS_RECAPTURES = -5, DEPTH_QS_RECAPTURES = -5,
@ -288,11 +287,11 @@ inline Value mg_value(Score s) {
} }
#define ENABLE_BASE_OPERATORS_ON(T) \ #define ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
constexpr T operator-(T d) { return T(-int(d)); } \ constexpr T operator-(T d) { return T(-int(d)); } \
inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
#define ENABLE_INCR_OPERATORS_ON(T) \ #define ENABLE_INCR_OPERATORS_ON(T) \
inline T& operator++(T& d) { return d = T(int(d) + 1); } \ inline T& operator++(T& d) { return d = T(int(d) + 1); } \
@ -322,12 +321,6 @@ ENABLE_BASE_OPERATORS_ON(Score)
#undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_INCR_OPERATORS_ON
#undef ENABLE_BASE_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON
/// Additional operators to add integers to a Value
constexpr Value operator+(Value v, int i) { return Value(int(v) + i); }
constexpr Value operator-(Value v, int i) { return Value(int(v) - i); }
inline Value& operator+=(Value& v, int i) { return v = v + i; }
inline Value& operator-=(Value& v, int i) { return v = v - i; }
/// Additional operators to add a Direction to a Square /// Additional operators to add a Direction to a Square
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
@ -364,16 +357,16 @@ constexpr Color operator~(Color c) {
return Color(c ^ BLACK); // Toggle color return Color(c ^ BLACK); // Toggle color
} }
constexpr Square flip_rank(Square s) { constexpr Square flip_rank(Square s) { // Swap A1 <-> A8
return Square(s ^ SQ_A8); return Square(s ^ SQ_A8);
} }
constexpr Square flip_file(Square s) { constexpr Square flip_file(Square s) { // Swap A1 <-> H1
return Square(s ^ SQ_H1); return Square(s ^ SQ_H1);
} }
constexpr Piece operator~(Piece pc) { constexpr Piece operator~(Piece pc) {
return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT
} }
constexpr CastlingRights operator&(Color c, CastlingRights cr) { constexpr CastlingRights operator&(Color c, CastlingRights cr) {

View file

@ -53,12 +53,11 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const
} }
/// init() initializes the UCI options to their hard-coded default values /// UCI::init() initializes the UCI options to their hard-coded default values
void init(OptionsMap& o) { void init(OptionsMap& o) {
// at most 2^32 clusters. constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
o["Debug Log File"] << Option("", on_logger); o["Debug Log File"] << Option("", on_logger);
o["Contempt"] << Option(24, -100, 100); o["Contempt"] << Option(24, -100, 100);
@ -70,7 +69,6 @@ void init(OptionsMap& o) {
o["MultiPV"] << Option(1, 1, 500); o["MultiPV"] << Option(1, 1, 500);
o["Skill Level"] << Option(20, 0, 20); o["Skill Level"] << Option(20, 0, 20);
o["Move Overhead"] << Option(10, 0, 5000); o["Move Overhead"] << Option(10, 0, 5000);
o["Minimum Thinking Time"] << Option( 0, 0, 5000);
o["Slow Mover"] << Option(100, 10, 1000); o["Slow Mover"] << Option(100, 10, 1000);
o["nodestime"] << Option(0, 0, 10000); o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false); o["UCI_Chess960"] << Option(false);