mirror of
https://github.com/sockspls/badfish
synced 2025-04-30 16:53:09 +00:00
commit
2f8c692caa
34 changed files with 999 additions and 536 deletions
2
AUTHORS
2
AUTHORS
|
@ -115,6 +115,7 @@ Nick Pelling (nickpelling)
|
|||
Nicklas Persson (NicklasPersson)
|
||||
Niklas Fiekas (niklasf)
|
||||
Nikolay Kostov (NikolayIT)
|
||||
Nguyen Pham
|
||||
Ondrej Mosnáček (WOnder93)
|
||||
Oskar Werkelin Ahlin
|
||||
Pablo Vazquez
|
||||
|
@ -154,6 +155,7 @@ Tom Vijlbrief (tomtor)
|
|||
Tomasz Sobczyk (Sopel97)
|
||||
Torsten Franz (torfranz, tfranzer)
|
||||
Tracey Emery (basepr1me)
|
||||
Unai Corzo (unaiic)
|
||||
Uri Blass (uriblass)
|
||||
Vince Negri (cuddlestmonkey)
|
||||
|
||||
|
|
24
Readme.md
24
Readme.md
|
@ -165,17 +165,23 @@ are in use, see the engine log.
|
|||
|
||||
## Compiling Stockfish yourself from the sources
|
||||
|
||||
On Unix-like systems, it should be possible to compile Stockfish
|
||||
directly from the source code with the included Makefile.
|
||||
Stockfish has support for 32 or 64-bit CPUs, certain hardware
|
||||
instructions, big-endian machines such as Power PC, and other platforms.
|
||||
|
||||
Stockfish has support for 32 or 64-bit CPUs, the hardware POPCNT
|
||||
instruction, big-endian machines such as Power PC, and other platforms.
|
||||
On Unix-like systems, it should be easy to compile Stockfish
|
||||
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
|
||||
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.
|
||||
```
|
||||
cd src
|
||||
make help
|
||||
make build ARCH=x86-64-modern
|
||||
```
|
||||
|
||||
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
|
||||
compiler you used to create your executable. These informations can
|
||||
|
|
52
script/README.md
Normal file
52
script/README.md
Normal 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
68
script/pgn_to_plain.py
Normal 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()
|
50
src/Makefile
50
src/Makefile
|
@ -69,7 +69,7 @@ endif
|
|||
### Section 2. High-level Configuration
|
||||
### ==========================================================================
|
||||
#
|
||||
# flag --- Comp switch --- Description
|
||||
# flag --- Comp switch --- Description
|
||||
# ----------------------------------------------------------------------------
|
||||
#
|
||||
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
|
||||
|
@ -92,7 +92,7 @@ endif
|
|||
optimize = yes
|
||||
debug = no
|
||||
sanitize = no
|
||||
bits = 32
|
||||
bits = 64
|
||||
prefetch = no
|
||||
popcnt = no
|
||||
sse = no
|
||||
|
@ -100,36 +100,35 @@ avx2 = no
|
|||
pext = no
|
||||
|
||||
### 2.2 Architecture specific
|
||||
|
||||
ifeq ($(ARCH),general-32)
|
||||
arch = any
|
||||
bits = 32
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),x86-32-old)
|
||||
arch = i386
|
||||
bits = 32
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),x86-32)
|
||||
arch = i386
|
||||
bits = 32
|
||||
prefetch = yes
|
||||
sse = yes
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),general-64)
|
||||
arch = any
|
||||
bits = 64
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),x86-64)
|
||||
arch = x86_64
|
||||
bits = 64
|
||||
prefetch = yes
|
||||
sse = yes
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),x86-64-modern)
|
||||
arch = x86_64
|
||||
bits = 64
|
||||
prefetch = yes
|
||||
popcnt = yes
|
||||
sse = yes
|
||||
|
@ -146,7 +145,6 @@ endif
|
|||
|
||||
ifeq ($(ARCH),x86-64-bmi2)
|
||||
arch = x86_64
|
||||
bits = 64
|
||||
prefetch = yes
|
||||
popcnt = yes
|
||||
sse = yes
|
||||
|
@ -157,26 +155,31 @@ endif
|
|||
ifeq ($(ARCH),armv7)
|
||||
arch = armv7
|
||||
prefetch = yes
|
||||
bits = 32
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),armv8)
|
||||
arch = armv8-a
|
||||
bits = 64
|
||||
prefetch = yes
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),ppc-32)
|
||||
arch = ppc
|
||||
bits = 32
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),ppc-64)
|
||||
arch = ppc64
|
||||
bits = 64
|
||||
popcnt = yes
|
||||
prefetch = yes
|
||||
endif
|
||||
|
||||
|
||||
### ==========================================================================
|
||||
### Section 3. Low-level configuration
|
||||
### Section 3. Low-level Configuration
|
||||
### ==========================================================================
|
||||
|
||||
### 3.1 Selecting compiler (default = gcc)
|
||||
|
||||
CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
|
||||
DEPENDFLAGS += -std=c++17
|
||||
LDFLAGS += $(EXTRALDFLAGS)
|
||||
|
@ -190,7 +193,7 @@ ifeq ($(COMP),gcc)
|
|||
CXX=g++
|
||||
CXXFLAGS += -pedantic -Wextra -Wshadow
|
||||
|
||||
ifeq ($(ARCH),armv7)
|
||||
ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
|
||||
ifeq ($(OS),Android)
|
||||
CXXFLAGS += -m$(bits)
|
||||
LDFLAGS += -m$(bits)
|
||||
|
@ -247,7 +250,7 @@ ifeq ($(COMP),clang)
|
|||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),armv7)
|
||||
ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
|
||||
ifeq ($(OS),Android)
|
||||
CXXFLAGS += -m$(bits)
|
||||
LDFLAGS += -m$(bits)
|
||||
|
@ -391,22 +394,15 @@ ifeq ($(pext),yes)
|
|||
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
|
||||
### needs access to the optimization flags.
|
||||
ifeq ($(optimize),yes)
|
||||
ifeq ($(debug), no)
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang msys2))
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||
CXXFLAGS += -flto
|
||||
LDFLAGS += $(CXXFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(comp),mingw)
|
||||
ifeq ($(KERNEL),Linux)
|
||||
CXXFLAGS += -flto
|
||||
LDFLAGS += $(CXXFLAGS)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
@ -417,9 +413,8 @@ ifeq ($(OS), Android)
|
|||
LDFLAGS += -fPIE -pie
|
||||
endif
|
||||
|
||||
|
||||
### ==========================================================================
|
||||
### Section 4. Public targets
|
||||
### Section 4. Public Targets
|
||||
### ==========================================================================
|
||||
|
||||
help:
|
||||
|
@ -447,6 +442,7 @@ help:
|
|||
@echo "ppc-64 > PPC 64-bit"
|
||||
@echo "ppc-32 > PPC 32-bit"
|
||||
@echo "armv7 > ARMv7 32-bit"
|
||||
@echo "armv8 > ARMv8 64-bit"
|
||||
@echo "general-64 > unspecified 64-bit"
|
||||
@echo "general-32 > unspecified 32-bit"
|
||||
@echo ""
|
||||
|
@ -518,7 +514,7 @@ default:
|
|||
help
|
||||
|
||||
### ==========================================================================
|
||||
### Section 5. Private targets
|
||||
### Section 5. Private Targets
|
||||
### ==========================================================================
|
||||
|
||||
all: $(EXE) .depend
|
||||
|
@ -550,7 +546,8 @@ config-sanity:
|
|||
@test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
|
||||
@test "$(optimize)" = "yes" || test "$(optimize)" = "no"
|
||||
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
||||
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7"
|
||||
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \
|
||||
test "$(arch)" = "armv7" || test "$(arch)" = "armv8-a"
|
||||
@test "$(bits)" = "32" || test "$(bits)" = "64"
|
||||
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "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
|
||||
|
||||
-include .depend
|
||||
|
||||
|
|
|
@ -108,25 +108,25 @@ namespace {
|
|||
stm = Color ((idx >> 12) & 0x01);
|
||||
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
|
||||
|| ksq[WHITE] == psq
|
||||
|| ksq[BLACK] == psq
|
||||
|| (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
|
||||
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
|
||||
&& rank_of(psq) == RANK_7
|
||||
&& ksq[stm] != psq + NORTH
|
||||
&& ( distance(ksq[~stm], psq + NORTH) > 1
|
||||
|| (attacks_bb<KING>(ksq[stm]) & (psq + NORTH))))
|
||||
&& ksq[WHITE] != psq + NORTH
|
||||
&& ( distance(ksq[BLACK], psq + NORTH) > 1
|
||||
|| (distance(ksq[WHITE], psq + NORTH) == 1)))
|
||||
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
|
||||
&& ( !(attacks_bb<KING>(ksq[stm]) & ~(attacks_bb<KING>(ksq[~stm]) | pawn_attacks_bb(~stm, psq)))
|
||||
|| (attacks_bb<KING>(ksq[stm]) & psq & ~attacks_bb<KING>(ksq[~stm]))))
|
||||
&& ( !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
|
||||
|| (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
|
||||
result = DRAW;
|
||||
|
||||
// Position will be classified later
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace {
|
|||
Bitboard RookTable[0x19000]; // To store rook 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)
|
||||
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;
|
||||
}
|
||||
|
@ -78,11 +79,8 @@ void Bitboards::init() {
|
|||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
||||
|
||||
Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST };
|
||||
Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };
|
||||
|
||||
init_magics(RookTable, RookMagics, RookDirections);
|
||||
init_magics(BishopTable, BishopMagics, BishopDirections);
|
||||
init_magics(ROOK, RookTable, RookMagics);
|
||||
init_magics(BISHOP, BishopTable, BishopMagics);
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
{
|
||||
|
@ -108,15 +106,17 @@ void Bitboards::init() {
|
|||
|
||||
namespace {
|
||||
|
||||
Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) {
|
||||
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
||||
|
||||
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;
|
||||
while(safe_destination(s, directions[i]) && !(occupied & s))
|
||||
attacks |= (s += directions[i]);
|
||||
while(safe_destination(s, d) && !(occupied & s))
|
||||
attacks |= (s += d);
|
||||
}
|
||||
|
||||
return attacks;
|
||||
|
@ -128,7 +128,7 @@ namespace {
|
|||
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
|
||||
// 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
|
||||
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
|
||||
// apply to the 64 or 32 bits word to get the index.
|
||||
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);
|
||||
|
||||
// Set the offset for the attacks table of the square. We have individual
|
||||
|
@ -160,7 +160,7 @@ namespace {
|
|||
b = size = 0;
|
||||
do {
|
||||
occupancy[size] = b;
|
||||
reference[size] = sliding_attack(directions, s, b);
|
||||
reference[size] = sliding_attack(pt, s, b);
|
||||
|
||||
if (HasPext)
|
||||
m.attacks[pext(b, m.mask)] = reference[size];
|
||||
|
|
|
@ -110,6 +110,7 @@ inline Bitboard square_bb(Square s) {
|
|||
return SquareBB[s];
|
||||
}
|
||||
|
||||
|
||||
/// Overloads of bitwise operators between a Bitboard and a Square for testing
|
||||
/// whether a given bit is set in a bitboard, and for setting and clearing bits.
|
||||
|
||||
|
@ -201,11 +202,25 @@ inline Bitboard adjacent_files_bb(Square s) {
|
|||
}
|
||||
|
||||
|
||||
/// between_bb() returns squares that are linearly between the given squares
|
||||
/// If the given squares are not on a same file/rank/diagonal, return 0.
|
||||
/// line_bb(Square, Square) returns a bitboard representing an entire line,
|
||||
/// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
/// be attacked by a pawn of the given color when it moves along its file,
|
||||
/// starting from the given square.
|
||||
/// be attacked by a pawn of the given color when it moves along its file, starting
|
||||
/// from the given square.
|
||||
|
||||
inline Bitboard pawn_attack_span(Color c, Square 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.
|
||||
|
||||
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.
|
||||
|
||||
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(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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
/// attacks_bb(Square) returns the pseudo attacks of the give piece type
|
||||
/// assuming an empty board.
|
||||
|
||||
|
@ -283,6 +301,7 @@ inline Bitboard attacks_bb(Square s) {
|
|||
return PseudoAttacks[Pt][s];
|
||||
}
|
||||
|
||||
|
||||
/// attacks_bb(Square, Bitboard) returns the attacks by the given piece
|
||||
/// assuming the board is occupied according to the passed Bitboard.
|
||||
/// Sliding piece attacks do not continue passed an occupied square.
|
||||
|
|
398
src/endgame.cpp
398
src/endgame.cpp
|
@ -28,12 +28,14 @@ namespace {
|
|||
|
||||
// Used to drive the king towards the edge of the board
|
||||
// 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) {
|
||||
int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
|
||||
return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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())
|
||||
return VALUE_DRAW;
|
||||
|
||||
Square winnerKSq = pos.square<KING>(strongSide);
|
||||
Square loserKSq = pos.square<KING>(weakSide);
|
||||
Square strongKing = pos.square<KING>(strongSide);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
|
||||
Value result = pos.non_pawn_material(strongSide)
|
||||
+ pos.count<PAWN>(strongSide) * PawnValueEg
|
||||
+ push_to_edge(loserKSq)
|
||||
+ push_close(winnerKSq, loserKSq);
|
||||
+ push_to_edge(weakKing)
|
||||
+ push_close(strongKing, weakKing);
|
||||
|
||||
if ( pos.count<QUEEN>(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, weakSide, VALUE_ZERO, 0));
|
||||
|
||||
Square winnerKSq = pos.square<KING>(strongSide);
|
||||
Square loserKSq = pos.square<KING>(weakSide);
|
||||
Square bishopSq = pos.square<BISHOP>(strongSide);
|
||||
Square strongKing = pos.square<KING>(strongSide);
|
||||
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
|
||||
// If our bishop does not attack A1/H8, we flip the enemy king square
|
||||
// to drive to opposite corners (A8/H1).
|
||||
|
||||
Value result = (VALUE_KNOWN_WIN + 3520)
|
||||
+ push_close(winnerKSq, loserKSq)
|
||||
+ 420 * push_to_corner(opposite_colors(bishopSq, SQ_A1) ? flip_file(loserKSq) : loserKSq);
|
||||
+ push_close(strongKing, weakKing)
|
||||
+ 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
|
||||
|
||||
assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
|
||||
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));
|
||||
|
||||
// Assume strongSide is white and the pawn is on files A-D
|
||||
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||
Square psq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||
Square strongPawn = 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;
|
||||
|
||||
if (!Bitbases::probe(wksq, psq, bksq, us))
|
||||
if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
|
||||
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;
|
||||
}
|
||||
|
@ -179,36 +181,35 @@ Value Endgame<KRKP>::operator()(const Position& pos) const {
|
|||
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||
|
||||
Square wksq = relative_square(strongSide, pos.square<KING>(strongSide));
|
||||
Square bksq = relative_square(strongSide, pos.square<KING>(weakSide));
|
||||
Square rsq = relative_square(strongSide, pos.square<ROOK>(strongSide));
|
||||
Square psq = relative_square(strongSide, pos.square<PAWN>(weakSide));
|
||||
|
||||
Square queeningSq = make_square(file_of(psq), RANK_1);
|
||||
Square strongKing = relative_square(strongSide, pos.square<KING>(strongSide));
|
||||
Square weakKing = relative_square(strongSide, pos.square<KING>(weakSide));
|
||||
Square strongRook = relative_square(strongSide, pos.square<ROOK>(strongSide));
|
||||
Square weakPawn = relative_square(strongSide, pos.square<PAWN>(weakSide));
|
||||
Square queeningSquare = make_square(file_of(weakPawn), RANK_1);
|
||||
Value result;
|
||||
|
||||
// If the stronger side's king is in front of the pawn, it's a win
|
||||
if (forward_file_bb(WHITE, wksq) & psq)
|
||||
result = RookValueEg - distance(wksq, psq);
|
||||
if (forward_file_bb(WHITE, strongKing) & weakPawn)
|
||||
result = RookValueEg - distance(strongKing, weakPawn);
|
||||
|
||||
// If the weaker side's king is too far from the pawn and the rook,
|
||||
// it's a win.
|
||||
else if ( distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide)
|
||||
&& distance(bksq, rsq) >= 3)
|
||||
result = RookValueEg - distance(wksq, psq);
|
||||
else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
|
||||
&& distance(weakKing, strongRook) >= 3)
|
||||
result = RookValueEg - distance(strongKing, weakPawn);
|
||||
|
||||
// If the pawn is far advanced and supported by the defending king,
|
||||
// the position is drawish
|
||||
else if ( rank_of(bksq) <= RANK_3
|
||||
&& distance(bksq, psq) == 1
|
||||
&& rank_of(wksq) >= RANK_4
|
||||
&& distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide))
|
||||
result = Value(80) - 8 * distance(wksq, psq);
|
||||
else if ( rank_of(weakKing) <= RANK_3
|
||||
&& distance(weakKing, weakPawn) == 1
|
||||
&& rank_of(strongKing) >= RANK_4
|
||||
&& distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
|
||||
result = Value(80) - 8 * distance(strongKing, weakPawn);
|
||||
|
||||
else
|
||||
result = Value(200) - 8 * ( distance(wksq, psq + SOUTH)
|
||||
- distance(bksq, psq + SOUTH)
|
||||
- distance(psq, queeningSq));
|
||||
result = Value(200) - 8 * ( distance(strongKing, weakPawn + SOUTH)
|
||||
- distance(weakKing, weakPawn + SOUTH)
|
||||
- distance(weakPawn, queeningSquare));
|
||||
|
||||
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, weakSide, KnightValueMg, 0));
|
||||
|
||||
Square bksq = pos.square<KING>(weakSide);
|
||||
Square bnsq = pos.square<KNIGHT>(weakSide);
|
||||
Value result = Value(push_to_edge(bksq) + push_away(bksq, bnsq));
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
Square weakKnight = pos.square<KNIGHT>(weakSide);
|
||||
Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
|
||||
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, weakSide, VALUE_ZERO, 1));
|
||||
|
||||
Square winnerKSq = pos.square<KING>(strongSide);
|
||||
Square loserKSq = pos.square<KING>(weakSide);
|
||||
Square pawnSq = pos.square<PAWN>(weakSide);
|
||||
Square strongKing = pos.square<KING>(strongSide);
|
||||
Square weakKing = pos.square<KING>(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
|
||||
|| distance(loserKSq, pawnSq) != 1
|
||||
|| ((FileBBB | FileDBB | FileEBB | FileGBB) & pawnSq))
|
||||
if ( relative_rank(weakSide, weakPawn) != RANK_7
|
||||
|| distance(weakKing, weakPawn) != 1
|
||||
|| ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
|
||||
result += QueenValueEg - PawnValueEg;
|
||||
|
||||
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
|
||||
/// 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.
|
||||
|
@ -277,29 +278,32 @@ Value Endgame<KQKR>::operator()(const Position& pos) const {
|
|||
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
||||
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
||||
|
||||
Square winnerKSq = pos.square<KING>(strongSide);
|
||||
Square loserKSq = pos.square<KING>(weakSide);
|
||||
Square strongKing = pos.square<KING>(strongSide);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
|
||||
Value result = QueenValueEg
|
||||
- RookValueEg
|
||||
+ push_to_edge(loserKSq)
|
||||
+ push_close(winnerKSq, loserKSq);
|
||||
+ push_to_edge(weakKing)
|
||||
+ push_close(strongKing, weakKing);
|
||||
|
||||
return strongSide == pos.side_to_move() ? result : -result;
|
||||
}
|
||||
|
||||
|
||||
/// 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<>
|
||||
Value Endgame<KNNKP>::operator()(const Position& pos) const {
|
||||
|
||||
assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
|
||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
Square weakPawn = pos.square<PAWN>(weakSide);
|
||||
|
||||
Value result = PawnValueEg
|
||||
+ 2 * push_to_edge(pos.square<KING>(weakSide))
|
||||
- 10 * relative_rank(weakSide, pos.square<PAWN>(weakSide));
|
||||
+ 2 * push_to_edge(weakKing)
|
||||
- 10 * relative_rank(weakSide, weakPawn);
|
||||
|
||||
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 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?
|
||||
if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
|
||||
{
|
||||
Square bishopSq = pos.square<BISHOP>(strongSide);
|
||||
Square queeningSq = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
|
||||
Square weakKingSq = pos.square<KING>(weakSide);
|
||||
Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
|
||||
|
||||
if ( opposite_colors(queeningSq, bishopSq)
|
||||
&& distance(queeningSq, weakKingSq) <= 1)
|
||||
if ( opposite_colors(queeningSquare, strongBishop)
|
||||
&& distance(queeningSquare, weakKing) <= 1)
|
||||
return SCALE_FACTOR_DRAW;
|
||||
}
|
||||
|
||||
|
@ -343,28 +349,24 @@ ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
|
|||
&& pos.count<PAWN>(weakSide) >= 1)
|
||||
{
|
||||
// Get the least advanced weakSide pawn
|
||||
Square weakPawnSq = 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);
|
||||
Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
|
||||
|
||||
// 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
|
||||
if ( relative_rank(strongSide, weakPawnSq) == RANK_7
|
||||
&& (strongPawns & (weakPawnSq + pawn_push(weakSide)))
|
||||
&& (opposite_colors(bishopSq, weakPawnSq) || !more_than_one(strongPawns)))
|
||||
// the bishop cannot attack it or they only have one pawn left.
|
||||
if ( relative_rank(strongSide, weakPawn) == RANK_7
|
||||
&& (strongPawns & (weakPawn + pawn_push(weakSide)))
|
||||
&& (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
|
||||
{
|
||||
int strongKingDist = distance(weakPawnSq, strongKingSq);
|
||||
int weakKingDist = distance(weakPawnSq, weakKingSq);
|
||||
int strongKingDist = distance(weakPawn, strongKing);
|
||||
int weakKingDist = distance(weakPawn, weakKing);
|
||||
|
||||
// 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
|
||||
// closer. (I think this rule only fails in practically
|
||||
// unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
|
||||
// and positions where qsearch will immediately correct the
|
||||
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w)
|
||||
if ( relative_rank(strongSide, weakKingSq) >= RANK_7
|
||||
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
|
||||
if ( relative_rank(strongSide, weakKing) >= RANK_7
|
||||
&& weakKingDist <= 2
|
||||
&& weakKingDist <= strongKingDist)
|
||||
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<PAWN>(weakSide) >= 1);
|
||||
|
||||
Square kingSq = pos.square<KING>(weakSide);
|
||||
Square rsq = pos.square<ROOK>(weakSide);
|
||||
Square strongKing = pos.square<KING>(strongSide);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
Square weakRook = pos.square<ROOK>(weakSide);
|
||||
|
||||
if ( relative_rank(weakSide, kingSq) <= RANK_2
|
||||
&& relative_rank(weakSide, pos.square<KING>(strongSide)) >= RANK_4
|
||||
&& relative_rank(weakSide, rsq) == RANK_3
|
||||
if ( relative_rank(weakSide, weakKing) <= RANK_2
|
||||
&& relative_rank(weakSide, strongKing) >= RANK_4
|
||||
&& relative_rank(weakSide, weakRook) == RANK_3
|
||||
&& ( pos.pieces(weakSide, PAWN)
|
||||
& attacks_bb<KING>(kingSq)
|
||||
& pawn_attacks_bb(strongSide, rsq)))
|
||||
& attacks_bb<KING>(weakKing)
|
||||
& pawn_attacks_bb(strongSide, weakRook)))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
return SCALE_FACTOR_NONE;
|
||||
|
@ -412,89 +415,89 @@ ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
|
|||
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
||||
|
||||
// Assume strongSide is white and the pawn is on files A-D
|
||||
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||
Square wrsq = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
|
||||
Square wpsq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||
Square brsq = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
|
||||
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||
Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
|
||||
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||
Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
|
||||
|
||||
File f = file_of(wpsq);
|
||||
Rank r = rank_of(wpsq);
|
||||
Square queeningSq = make_square(f, RANK_8);
|
||||
File pawnFile = file_of(strongPawn);
|
||||
Rank pawnRank = rank_of(strongPawn);
|
||||
Square queeningSquare = make_square(pawnFile, RANK_8);
|
||||
int tempo = (pos.side_to_move() == strongSide);
|
||||
|
||||
// If the pawn is not too far advanced and the defending king defends the
|
||||
// queening square, use the third-rank defence.
|
||||
if ( r <= RANK_5
|
||||
&& distance(bksq, queeningSq) <= 1
|
||||
&& wksq <= SQ_H5
|
||||
&& (rank_of(brsq) == RANK_6 || (r <= RANK_3 && rank_of(wrsq) != RANK_6)))
|
||||
if ( pawnRank <= RANK_5
|
||||
&& distance(weakKing, queeningSquare) <= 1
|
||||
&& strongKing <= SQ_H5
|
||||
&& (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
// The defending side saves a draw by checking from behind in case the pawn
|
||||
// has advanced to the 6th rank with the king behind.
|
||||
if ( r == RANK_6
|
||||
&& distance(bksq, queeningSq) <= 1
|
||||
&& rank_of(wksq) + tempo <= RANK_6
|
||||
&& (rank_of(brsq) == RANK_1 || (!tempo && distance<File>(brsq, wpsq) >= 3)))
|
||||
if ( pawnRank == RANK_6
|
||||
&& distance(weakKing, queeningSquare) <= 1
|
||||
&& rank_of(strongKing) + tempo <= RANK_6
|
||||
&& (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
if ( r >= RANK_6
|
||||
&& bksq == queeningSq
|
||||
&& rank_of(brsq) == RANK_1
|
||||
&& (!tempo || distance(wksq, wpsq) >= 2))
|
||||
if ( pawnRank >= RANK_6
|
||||
&& weakKing == queeningSquare
|
||||
&& rank_of(weakRook) == RANK_1
|
||||
&& (!tempo || distance(strongKing, strongPawn) >= 2))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
// 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.
|
||||
if ( wpsq == SQ_A7
|
||||
&& wrsq == SQ_A8
|
||||
&& (bksq == SQ_H7 || bksq == SQ_G7)
|
||||
&& file_of(brsq) == FILE_A
|
||||
&& (rank_of(brsq) <= RANK_3 || file_of(wksq) >= FILE_D || rank_of(wksq) <= RANK_5))
|
||||
if ( strongPawn == SQ_A7
|
||||
&& strongRook == SQ_A8
|
||||
&& (weakKing == SQ_H7 || weakKing == SQ_G7)
|
||||
&& file_of(weakRook) == FILE_A
|
||||
&& (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
// If the defending king blocks the pawn and the attacking king is too far
|
||||
// away, it's a draw.
|
||||
if ( r <= RANK_5
|
||||
&& bksq == wpsq + NORTH
|
||||
&& distance(wksq, wpsq) - tempo >= 2
|
||||
&& distance(wksq, brsq) - tempo >= 2)
|
||||
if ( pawnRank <= RANK_5
|
||||
&& weakKing == strongPawn + NORTH
|
||||
&& distance(strongKing, strongPawn) - tempo >= 2
|
||||
&& distance(strongKing, weakRook) - tempo >= 2)
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
// 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,
|
||||
// and the defending king cannot gain tempi by threatening the attacking rook.
|
||||
if ( r == RANK_7
|
||||
&& f != FILE_A
|
||||
&& file_of(wrsq) == f
|
||||
&& wrsq != queeningSq
|
||||
&& (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
|
||||
&& (distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo))
|
||||
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(wksq, queeningSq));
|
||||
if ( pawnRank == RANK_7
|
||||
&& pawnFile != FILE_A
|
||||
&& file_of(strongRook) == pawnFile
|
||||
&& strongRook != queeningSquare
|
||||
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
|
||||
&& (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
|
||||
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
|
||||
|
||||
// Similar to the above, but with the pawn further back
|
||||
if ( f != FILE_A
|
||||
&& file_of(wrsq) == f
|
||||
&& wrsq < wpsq
|
||||
&& (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
|
||||
&& (distance(wksq, wpsq + NORTH) < distance(bksq, wpsq + NORTH) - 2 + tempo)
|
||||
&& ( distance(bksq, wrsq) + tempo >= 3
|
||||
|| ( distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo
|
||||
&& (distance(wksq, wpsq + NORTH) < distance(bksq, wrsq) + tempo))))
|
||||
if ( pawnFile != FILE_A
|
||||
&& file_of(strongRook) == pawnFile
|
||||
&& strongRook < strongPawn
|
||||
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
|
||||
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
|
||||
&& ( distance(weakKing, strongRook) + tempo >= 3
|
||||
|| ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
|
||||
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
|
||||
return ScaleFactor( SCALE_FACTOR_MAX
|
||||
- 8 * distance(wpsq, queeningSq)
|
||||
- 2 * distance(wksq, queeningSq));
|
||||
- 8 * distance(strongPawn, queeningSquare)
|
||||
- 2 * distance(strongKing, queeningSquare));
|
||||
|
||||
// If the pawn is not far advanced and the defending king is somewhere in
|
||||
// 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);
|
||||
if ( distance<File>(bksq, wpsq) == 1
|
||||
&& distance(wksq, bksq) > 2)
|
||||
return ScaleFactor(24 - 2 * distance(wksq, bksq));
|
||||
if ( distance<File>(weakKing, strongPawn) == 1
|
||||
&& distance(strongKing, weakKing) > 2)
|
||||
return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
|
||||
}
|
||||
return SCALE_FACTOR_NONE;
|
||||
}
|
||||
|
@ -508,10 +511,11 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
|
|||
// Test for a rook pawn
|
||||
if (pos.pieces(PAWN) & (FileABB | FileHBB))
|
||||
{
|
||||
Square ksq = pos.square<KING>(weakSide);
|
||||
Square bsq = pos.square<BISHOP>(weakSide);
|
||||
Square psq = pos.square<PAWN>(strongSide);
|
||||
Rank rk = relative_rank(strongSide, psq);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
Square weakBishop = pos.square<BISHOP>(weakSide);
|
||||
Square strongKing = pos.square<KING>(strongSide);
|
||||
Square strongPawn = pos.square<PAWN>(strongSide);
|
||||
Rank pawnRank = relative_rank(strongSide, strongPawn);
|
||||
Direction push = pawn_push(strongSide);
|
||||
|
||||
// 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
|
||||
// reduction or a stronger one if the defending king is near the
|
||||
// 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);
|
||||
else
|
||||
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
|
||||
// pawn from a reasonable distance and the defending king is near
|
||||
// the corner
|
||||
if ( rk == RANK_6
|
||||
&& distance(psq + 2 * push, ksq) <= 1
|
||||
&& (attacks_bb<BISHOP>(bsq) & (psq + push))
|
||||
&& distance<File>(bsq, psq) >= 2)
|
||||
if ( pawnRank == RANK_6
|
||||
&& distance(strongPawn + 2 * push, weakKing) <= 1
|
||||
&& (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
|
||||
&& distance<File>(weakBishop, strongPawn) >= 2)
|
||||
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, weakSide, RookValueMg, 1));
|
||||
|
||||
Square wpsq1 = pos.squares<PAWN>(strongSide)[0];
|
||||
Square wpsq2 = pos.squares<PAWN>(strongSide)[1];
|
||||
Square bksq = pos.square<KING>(weakSide);
|
||||
Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
|
||||
Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
&& distance<File>(bksq, wpsq2) <= 1
|
||||
&& relative_rank(strongSide, bksq) > r)
|
||||
if ( distance<File>(weakKing, strongPawn1) <= 1
|
||||
&& distance<File>(weakKing, strongPawn2) <= 1
|
||||
&& relative_rank(strongSide, weakKing) > pawnRank)
|
||||
{
|
||||
assert(r > RANK_1 && r < RANK_7);
|
||||
return ScaleFactor(7 * r);
|
||||
assert(pawnRank > RANK_1 && pawnRank < RANK_7);
|
||||
return ScaleFactor(7 * pawnRank);
|
||||
}
|
||||
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.
|
||||
template<>
|
||||
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(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
||||
|
||||
Square ksq = pos.square<KING>(weakSide);
|
||||
Bitboard pawns = pos.pieces(strongSide, PAWN);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
|
||||
|
||||
// If all pawns are ahead of the king on a single rook file, it's a draw.
|
||||
if (!((pawns & ~FileABB) || (pawns & ~FileHBB)) &&
|
||||
!(pawns & ~passed_pawn_span(weakSide, ksq)))
|
||||
if (!((strongPawns & ~FileABB) || (strongPawns & ~FileHBB)) &&
|
||||
!(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
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, weakSide, BishopValueMg, 0));
|
||||
|
||||
Square pawnSq = pos.square<PAWN>(strongSide);
|
||||
Square strongBishopSq = pos.square<BISHOP>(strongSide);
|
||||
Square weakBishopSq = pos.square<BISHOP>(weakSide);
|
||||
Square weakKingSq = pos.square<KING>(weakSide);
|
||||
Square strongPawn = pos.square<PAWN>(strongSide);
|
||||
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||
Square weakBishop = pos.square<BISHOP>(weakSide);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
|
||||
// Case 1: Defending king blocks the pawn, and cannot be driven away
|
||||
if ( (forward_file_bb(strongSide, pawnSq) & weakKingSq)
|
||||
&& ( opposite_colors(weakKingSq, strongBishopSq)
|
||||
|| relative_rank(strongSide, weakKingSq) <= RANK_6))
|
||||
if ( (forward_file_bb(strongSide, strongPawn) & weakKing)
|
||||
&& ( opposite_colors(weakKing, strongBishop)
|
||||
|| relative_rank(strongSide, weakKing) <= RANK_6))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
// Case 2: Opposite colored bishops
|
||||
if (opposite_colors(strongBishopSq, weakBishopSq))
|
||||
if (opposite_colors(strongBishop, weakBishop))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
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, weakSide, BishopValueMg, 0));
|
||||
|
||||
Square wbsq = pos.square<BISHOP>(strongSide);
|
||||
Square bbsq = pos.square<BISHOP>(weakSide);
|
||||
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||
Square weakBishop = pos.square<BISHOP>(weakSide);
|
||||
|
||||
if (!opposite_colors(wbsq, bbsq))
|
||||
if (!opposite_colors(strongBishop, weakBishop))
|
||||
return SCALE_FACTOR_NONE;
|
||||
|
||||
Square ksq = pos.square<KING>(weakSide);
|
||||
Square psq1 = pos.squares<PAWN>(strongSide)[0];
|
||||
Square psq2 = pos.squares<PAWN>(strongSide)[1];
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
|
||||
Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
|
||||
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);
|
||||
blockSq2 = make_square(file_of(psq2), rank_of(psq1));
|
||||
blockSq1 = strongPawn1 + pawn_push(strongSide);
|
||||
blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
|
||||
}
|
||||
else
|
||||
{
|
||||
blockSq1 = psq2 + pawn_push(strongSide);
|
||||
blockSq2 = make_square(file_of(psq1), rank_of(psq2));
|
||||
blockSq1 = strongPawn2 + pawn_push(strongSide);
|
||||
blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
|
||||
}
|
||||
|
||||
switch (distance<File>(psq1, psq2))
|
||||
switch (distance<File>(strongPawn1, strongPawn2))
|
||||
{
|
||||
case 0:
|
||||
// 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.
|
||||
if ( file_of(ksq) == file_of(blockSq1)
|
||||
&& relative_rank(strongSide, ksq) >= relative_rank(strongSide, blockSq1)
|
||||
&& opposite_colors(ksq, wbsq))
|
||||
if ( file_of(weakKing) == file_of(blockSq1)
|
||||
&& relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
|
||||
&& opposite_colors(weakKing, strongBishop))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
else
|
||||
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
|
||||
// square in front of the frontmost pawn's path, and the square diagonally
|
||||
// behind this square on the file of the other pawn.
|
||||
if ( ksq == blockSq1
|
||||
&& opposite_colors(ksq, wbsq)
|
||||
&& ( bbsq == blockSq2
|
||||
if ( weakKing == blockSq1
|
||||
&& opposite_colors(weakKing, strongBishop)
|
||||
&& ( weakBishop == blockSq2
|
||||
|| (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
|
||||
|| distance<Rank>(psq1, psq2) >= 2))
|
||||
|| distance<Rank>(strongPawn1, strongPawn2) >= 2))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
else if ( ksq == blockSq2
|
||||
&& opposite_colors(ksq, wbsq)
|
||||
&& ( bbsq == blockSq1
|
||||
else if ( weakKing == blockSq2
|
||||
&& opposite_colors(weakKing, strongBishop)
|
||||
&& ( weakBishop == blockSq1
|
||||
|| (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
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 stronger side's bishop, it's a draw.
|
||||
template<>
|
||||
|
@ -698,14 +702,14 @@ ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
|
|||
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
||||
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
||||
|
||||
Square pawnSq = pos.square<PAWN>(strongSide);
|
||||
Square strongBishopSq = pos.square<BISHOP>(strongSide);
|
||||
Square weakKingSq = pos.square<KING>(weakSide);
|
||||
Square strongPawn = pos.square<PAWN>(strongSide);
|
||||
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||
Square weakKing = pos.square<KING>(weakSide);
|
||||
|
||||
if ( file_of(weakKingSq) == file_of(pawnSq)
|
||||
&& relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
|
||||
&& ( opposite_colors(weakKingSq, strongBishopSq)
|
||||
|| relative_rank(strongSide, weakKingSq) <= RANK_6))
|
||||
if ( file_of(weakKing) == file_of(strongPawn)
|
||||
&& relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
|
||||
&& ( opposite_colors(weakKing, strongBishop)
|
||||
|| relative_rank(strongSide, weakKing) <= RANK_6))
|
||||
return SCALE_FACTOR_DRAW;
|
||||
|
||||
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 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
|
||||
/// 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).
|
||||
|
@ -724,18 +728,18 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
|
|||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||
|
||||
// Assume strongSide is white and the pawn is on files A-D
|
||||
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||
Square psq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||
|
||||
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
|
||||
|
||||
// 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.
|
||||
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;
|
||||
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
|
|
113
src/evaluate.cpp
113
src/evaluate.cpp
|
@ -37,7 +37,7 @@ namespace Trace {
|
|||
enum Tracing { NO_TRACE, TRACE };
|
||||
|
||||
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];
|
||||
|
@ -61,7 +61,7 @@ namespace Trace {
|
|||
|
||||
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 << " ---- ----" << " | " << " ---- ----";
|
||||
else
|
||||
os << scores[t][WHITE] << " | " << scores[t][BLACK];
|
||||
|
@ -129,32 +129,34 @@ namespace {
|
|||
};
|
||||
|
||||
// Assorted bonuses and penalties
|
||||
constexpr Score BishopPawns = S( 3, 7);
|
||||
constexpr Score BishopKingProtector = S( 6, 9);
|
||||
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 CorneredBishop = S( 50, 50);
|
||||
constexpr Score FlankAttacks = S( 8, 0);
|
||||
constexpr Score Hanging = S( 69, 36);
|
||||
constexpr Score BishopKingProtector = S( 6, 9);
|
||||
constexpr Score KnightKingProtector = S( 8, 9);
|
||||
constexpr Score KnightOnQueen = S( 16, 11);
|
||||
constexpr Score KnightOutpost = S( 56, 36);
|
||||
constexpr Score LongDiagonalBishop = S( 45, 0);
|
||||
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 PawnlessFlank = S( 17, 95);
|
||||
constexpr Score QueenInfiltration = S( -2, 14);
|
||||
constexpr Score ReachableOutpost = S( 31, 22);
|
||||
constexpr Score RestrictedPiece = S( 7, 7);
|
||||
constexpr Score RookOnKingRing = S( 16, 0);
|
||||
constexpr Score RookOnQueenFile = S( 5, 9);
|
||||
constexpr Score SliderOnQueen = S( 59, 18);
|
||||
constexpr Score RookOnQueenFile = S( 6, 11);
|
||||
constexpr Score SliderOnQueen = S( 60, 18);
|
||||
constexpr Score ThreatByKing = S( 24, 89);
|
||||
constexpr Score ThreatByPawnPush = S( 48, 39);
|
||||
constexpr Score ThreatBySafePawn = S(173, 94);
|
||||
constexpr Score TrappedRook = S( 55, 13);
|
||||
constexpr Score WeakQueen = S( 51, 14);
|
||||
constexpr Score WeakQueenProtection = S( 15, 0);
|
||||
constexpr Score WeakQueenProtection = S( 14, 0);
|
||||
constexpr Score WeakQueen = S( 56, 15);
|
||||
|
||||
|
||||
#undef S
|
||||
|
||||
|
@ -175,8 +177,7 @@ namespace {
|
|||
template<Color Us> Score threats() const;
|
||||
template<Color Us> Score passed() const;
|
||||
template<Color Us> Score space() const;
|
||||
ScaleFactor scale_factor(Value eg) const;
|
||||
Score initiative(Score score) const;
|
||||
Value winnable(Score score) const;
|
||||
|
||||
const Position& pos;
|
||||
Material::Entry* me;
|
||||
|
@ -218,6 +219,7 @@ namespace {
|
|||
|
||||
// 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.
|
||||
|
||||
template<Tracing T> template<Color Us>
|
||||
void Evaluation<T>::initialize() {
|
||||
|
||||
|
@ -257,6 +259,7 @@ namespace {
|
|||
|
||||
|
||||
// Evaluation::pieces() scores pieces of a given color and type
|
||||
|
||||
template<Tracing T> template<Color Us, PieceType Pt>
|
||||
Score Evaluation<T>::pieces() {
|
||||
|
||||
|
@ -279,7 +282,7 @@ namespace {
|
|||
: attacks_bb<Pt>(s, pos.pieces());
|
||||
|
||||
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;
|
||||
attackedBy[Us][Pt] |= b;
|
||||
|
@ -376,6 +379,10 @@ namespace {
|
|||
Bitboard queenPinners;
|
||||
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
|
||||
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)
|
||||
|
@ -386,6 +393,7 @@ namespace {
|
|||
|
||||
|
||||
// Evaluation::king() assigns bonuses and penalties to a king of a given color
|
||||
|
||||
template<Tracing T> template<Color Us>
|
||||
Score Evaluation<T>::king() const {
|
||||
|
||||
|
@ -494,6 +502,7 @@ namespace {
|
|||
|
||||
// Evaluation::threats() assigns bonuses according to the types of the
|
||||
// attacking and the attacked pieces.
|
||||
|
||||
template<Tracing T> template<Color Us>
|
||||
Score Evaluation<T>::threats() const {
|
||||
|
||||
|
@ -679,16 +688,15 @@ namespace {
|
|||
}
|
||||
|
||||
|
||||
// Evaluation::space() computes the space evaluation for a given side. The
|
||||
// space evaluation is a simple bonus based on the number of safe squares
|
||||
// available for minor pieces on the central four files on ranks 2--4. Safe
|
||||
// squares one, two or three squares behind a friendly pawn are counted
|
||||
// twice. Finally, the space bonus is multiplied by a weight. The aim is to
|
||||
// improve play on game opening.
|
||||
// Evaluation::space() computes a space evaluation for a given side, aiming to improve game
|
||||
// play in the opening. It is based on the number of safe squares on the 4 central files
|
||||
// on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
|
||||
// Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
|
||||
|
||||
template<Tracing T> template<Color Us>
|
||||
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)
|
||||
return SCORE_ZERO;
|
||||
|
||||
|
@ -719,12 +727,12 @@ namespace {
|
|||
}
|
||||
|
||||
|
||||
// Evaluation::initiative() computes the initiative correction value
|
||||
// for the position. It is a second order bonus/malus based on the
|
||||
// known attacking/defending status of the players.
|
||||
// Evaluation::winnable() adjusts the mg and eg score components based on the
|
||||
// known attacking/defending status of the players. A single value is derived
|
||||
// by interpolation from the mg and eg values and returned.
|
||||
|
||||
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))
|
||||
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
||||
|
@ -746,7 +754,6 @@ namespace {
|
|||
+ 24 * infiltration
|
||||
+ 51 * !pos.non_pawn_material()
|
||||
- 43 * almostUnwinnable
|
||||
- 2 * pos.rule50_count()
|
||||
-110 ;
|
||||
|
||||
Value mg = mg_value(score);
|
||||
|
@ -758,17 +765,10 @@ namespace {
|
|||
int u = ((mg > 0) - (mg < 0)) * Utility::clamp(complexity + 50, -abs(mg), 0);
|
||||
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
|
||||
|
||||
if (T)
|
||||
Trace::add(INITIATIVE, make_score(u, v));
|
||||
mg += u;
|
||||
eg += v;
|
||||
|
||||
return make_score(u, v);
|
||||
}
|
||||
|
||||
|
||||
// Evaluation::scale_factor() computes the scale factor for the winning side
|
||||
|
||||
template<Tracing T>
|
||||
ScaleFactor Evaluation<T>::scale_factor(Value eg) const {
|
||||
// Compute the scale factor for the winning side
|
||||
|
||||
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
||||
int sf = me->scale_factor(pos, strongSide);
|
||||
|
@ -788,7 +788,18 @@ namespace {
|
|||
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;
|
||||
|
||||
// Main evaluation begins here
|
||||
|
||||
initialize<WHITE>();
|
||||
initialize<BLACK>();
|
||||
|
||||
// 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>()
|
||||
+ pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
|
||||
+ pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >()
|
||||
|
@ -843,14 +853,8 @@ namespace {
|
|||
+ passed< WHITE>() - passed< BLACK>()
|
||||
+ space< WHITE>() - space< BLACK>();
|
||||
|
||||
score += initiative(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;
|
||||
// Derive single value from mg and eg parts of score
|
||||
v = winnable(score);
|
||||
|
||||
// In case of tracing add all remaining individual evaluation terms
|
||||
if (T)
|
||||
|
@ -859,11 +863,18 @@ namespace {
|
|||
Trace::add(IMBALANCE, me->imbalance());
|
||||
Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
|
||||
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
|
||||
Trace::add(TOTAL, score);
|
||||
}
|
||||
|
||||
// Evaluation grain
|
||||
v = (v / 16) * 16;
|
||||
|
||||
// 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
|
||||
|
@ -913,11 +924,11 @@ std::string Eval::trace(const Position& pos) {
|
|||
<< " Threats | " << Term(THREAT)
|
||||
<< " Passed | " << Term(PASSED)
|
||||
<< " Space | " << Term(SPACE)
|
||||
<< " Initiative | " << Term(INITIATIVE)
|
||||
<< " Winnable | " << Term(WINNABLE)
|
||||
<< " ------------+-------------+-------------+------------\n"
|
||||
<< " Total | " << Term(TOTAL);
|
||||
|
||||
ss << "\nTotal evaluation: " << to_cp(v) << " (white side)\n";
|
||||
ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
@ -976,7 +987,7 @@ bool EvalList::is_valid(const Position& pos)
|
|||
for (Piece pc = NO_PIECE; pc < PIECE_NB; ++pc)
|
||||
{
|
||||
auto pt = type_of(pc);
|
||||
if (pt == NO_PIECE || pt == 7) // ‘¶<E28098>Ý‚µ‚È‚¢‹î
|
||||
if (pt == NO_PIECE_TYPE || pt == 7) // ‘¶<E28098>Ý‚µ‚È‚¢‹î
|
||||
continue;
|
||||
|
||||
// ‹îpc‚ÌBonaPiece‚ÌŠJŽn”Ô<E2809D>†
|
||||
|
|
|
@ -234,4 +234,4 @@ namespace Learner
|
|||
|
||||
#endif
|
||||
|
||||
#endif // ifndef _LEARN_H_
|
||||
#endif // ifndef _LEARN_H_
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <filesystem>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
|
||||
#include "learn.h"
|
||||
#include "multi_think.h"
|
||||
|
@ -111,6 +112,11 @@ namespace Learner
|
|||
// 局面の配列 : PSVector は packed sfen vector の略。
|
||||
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 defined (LEARN_GENSFEN_USE_DRAW_RESULT)
|
||||
// 勝敗 = 引き分けとして書き出す。
|
||||
// こうしたほうが自分が入玉したときに、相手の入玉を許しにくい(かも)
|
||||
flush_psv(0);
|
||||
#endif
|
||||
if (use_draw_in_training_data_generation) {
|
||||
// 勝敗 = 引き分けとして書き出す。
|
||||
// こうしたほうが自分が入玉したときに、相手の入玉を許しにくい(かも)
|
||||
flush_psv(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (pos.is_draw(ply)) {
|
||||
// Do not write if draw.
|
||||
break;
|
||||
if (use_draw_in_training_data_generation) {
|
||||
// Write if draw.
|
||||
flush_psv(0);
|
||||
}
|
||||
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.
|
||||
flush_psv(-1);
|
||||
if (pos.checkers()) // Mate
|
||||
flush_psv(-1);
|
||||
else if (use_draw_in_training_data_generation) {
|
||||
flush_psv(0); // Stalemate
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -576,10 +589,10 @@ void MultiThinkGenSfen::thread_worker(size_t thread_id)
|
|||
// 各千日手に応じた処理。
|
||||
|
||||
if (pos.is_draw(0)) {
|
||||
#if defined (LEARN_GENSFEN_USE_DRAW_RESULT)
|
||||
// 引き分けを書き出すとき
|
||||
flush_psv(is_win);
|
||||
#endif
|
||||
if (use_draw_in_training_data_generation) {
|
||||
// Write if draw.
|
||||
flush_psv(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1015,9 +1028,24 @@ double sigmoid(double x)
|
|||
// 評価値を勝率[0,1]に変換する関数
|
||||
double winning_percentage(double value)
|
||||
{
|
||||
// この600.0という定数は、ponanza定数。(ponanzaがそうしているらしいという意味で)
|
||||
// ゲームの進行度に合わせたものにしたほうがいいかも知れないけども、その効果のほどは不明。
|
||||
return sigmoid(value / 600.0);
|
||||
// In Maxima,
|
||||
// load("C:/maxima-5.44.0/cform.lisp");
|
||||
// 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)方式
|
||||
// 実際のゲームの勝敗で補正する。
|
||||
|
||||
const double eval_winrate = winning_percentage(shallow);
|
||||
const double teacher_winrate = winning_percentage(deep);
|
||||
const double q = winning_percentage(shallow);
|
||||
const double p = winning_percentage(deep);
|
||||
const double dq = delta_winning_percentage(shallow);
|
||||
|
||||
// 期待勝率を勝っていれば1、負けていれば 0、引き分けなら0.5として補正項として用いる。
|
||||
// game_result = 1,0,-1なので1足して2で割る。
|
||||
|
@ -1127,7 +1156,9 @@ double calc_grad(Value deep, Value shallow , const PackedSfenValue& psv)
|
|||
|
||||
// 実際の勝率を補正項として使っている。
|
||||
// これが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;
|
||||
}
|
||||
|
@ -1240,11 +1271,8 @@ struct SfenReader
|
|||
{
|
||||
if (eval_limit < abs(p.score))
|
||||
continue;
|
||||
#if !defined (LEARN_GENSFEN_USE_DRAW_RESULT)
|
||||
if (p.game_result == 0)
|
||||
if (!use_draw_in_validation && p.game_result == 0)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
sfen_for_mse.push_back(p);
|
||||
} else {
|
||||
break;
|
||||
|
@ -1926,10 +1954,10 @@ void LearnerThink::thread_worker(size_t thread_id)
|
|||
if (eval_limit < abs(ps.score))
|
||||
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;
|
||||
#endif
|
||||
|
||||
|
||||
// 序盤局面に関する読み飛ばし
|
||||
if (ps.gamePly < prng.rand(reduction_gameply))
|
||||
|
@ -1953,13 +1981,13 @@ void LearnerThink::thread_worker(size_t thread_id)
|
|||
{
|
||||
auto key = pos.key();
|
||||
// rmseの計算用に使っている局面なら除外する。
|
||||
if (sr.is_for_rmse(key))
|
||||
if (sr.is_for_rmse(key) && use_hash_in_training)
|
||||
goto RetryRead;
|
||||
|
||||
// 直近で用いた局面も除外する。
|
||||
auto hash_index = size_t(key & (sr.READ_SFEN_HASH_SIZE - 1));
|
||||
auto key2 = sr.hash[hash_index];
|
||||
if (key == key2)
|
||||
if (key == key2 && use_hash_in_training)
|
||||
goto RetryRead;
|
||||
sr.hash[hash_index] = key; // 今回のkeyに入れ替えておく。
|
||||
}
|
||||
|
@ -2408,30 +2436,36 @@ void shuffle_files_on_memory(const vector<string>& filenames,const string output
|
|||
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;
|
||||
uint64_t data_size=0;
|
||||
uint64_t filtered_size = 0;
|
||||
auto th = Threads.main();
|
||||
auto &tpos = th->rootPos;
|
||||
// plain形式の雑巾をやねうら王用のpackedsfenvalueに変換する
|
||||
fs.open(output_file_name, ios::app | ios::binary);
|
||||
|
||||
StateListPtr states;
|
||||
for (auto filename : filenames) {
|
||||
std::cout << "convert " << filename << " ... ";
|
||||
std::string line;
|
||||
ifstream ifs;
|
||||
ifs.open(filename);
|
||||
PackedSfenValue p;
|
||||
data_size = 0;
|
||||
filtered_size = 0;
|
||||
p.gamePly = 1; // apery形式では含まれない。一応初期化するべし
|
||||
bool ignore_flag = false;
|
||||
|
||||
while (std::getline(ifs, line)) {
|
||||
std::stringstream ss(line);
|
||||
std::string token;
|
||||
std::string value;
|
||||
ss >> token;
|
||||
if (token == "sfen") {
|
||||
StateInfo si;
|
||||
tpos.set(line.substr(5), false, &si, Threads.main());
|
||||
tpos.sfen_pack(p.sfen);
|
||||
if (token == "fen") {
|
||||
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
|
||||
tpos.set(line.substr(4), false, &states->back(), Threads.main());
|
||||
tpos.sfen_pack(p.sfen);
|
||||
}
|
||||
else if (token == "move") {
|
||||
ss >> value;
|
||||
|
@ -2443,29 +2477,232 @@ void convert_bin(const vector<string>& filenames , const string& output_file_nam
|
|||
else if (token == "ply") {
|
||||
int temp;
|
||||
ss >> temp;
|
||||
if(temp < ply_minimum || temp > ply_maximum){
|
||||
ignore_flag = true;
|
||||
}
|
||||
p.gamePly = uint16_t(temp); // 此処のキャストいらない?
|
||||
if (interpolate_eval != 0){
|
||||
p.score = min(3000, interpolate_eval * temp);
|
||||
}
|
||||
}
|
||||
else if (token == "result") {
|
||||
int temp;
|
||||
ss >> temp;
|
||||
p.game_result = int8_t(temp); // 此処のキャストいらない?
|
||||
if (interpolate_eval){
|
||||
p.score = p.score * p.game_result;
|
||||
}
|
||||
}
|
||||
else if (token == "e") {
|
||||
if(!ignore_flag){
|
||||
fs.write((char*)&p, sizeof(PackedSfenValue));
|
||||
data_size+=1;
|
||||
// debug
|
||||
/*
|
||||
std::cout<<tpos<<std::endl;
|
||||
std::cout<<to_usi_string(Move(p.move))<<","<<p.score<<","<<int(p.gamePly)<<","<<int(p.game_result)<<std::endl;
|
||||
*/
|
||||
// std::cout<<tpos<<std::endl;
|
||||
// std::cout<<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();
|
||||
}
|
||||
std::cout << "all done" << std::endl;
|
||||
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)
|
||||
//{
|
||||
// Position tpos;
|
||||
|
@ -2549,6 +2786,11 @@ void learn(Position&, istringstream& is)
|
|||
bool use_convert_plain = false;
|
||||
// plain形式の教師をやねうら王のbinに変換する
|
||||
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")
|
||||
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 == "eta1_epoch") is >> eta1_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;
|
||||
|
||||
|
@ -2664,7 +2909,7 @@ void learn(Position&, istringstream& is)
|
|||
else if (option == "eval_limit") is >> eval_limit;
|
||||
else if (option == "save_only_once") save_only_once = true;
|
||||
else if (option == "no_shuffle") no_shuffle = true;
|
||||
|
||||
|
||||
#if defined(EVAL_NNUE)
|
||||
else if (option == "nn_batch_size") is >> nn_batch_size;
|
||||
else if (option == "newbob_decay") is >> newbob_decay;
|
||||
|
@ -2679,6 +2924,8 @@ void learn(Position&, istringstream& is)
|
|||
// 雑巾のconvert関連
|
||||
else if (option == "convert_plain") use_convert_plain = 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
|
||||
filenames.push_back(option);
|
||||
|
@ -2788,10 +3035,17 @@ void learn(Position&, istringstream& is)
|
|||
{
|
||||
is_ready(true);
|
||||
cout << "convert_bin.." << endl;
|
||||
convert_bin(filenames,output_file_name);
|
||||
convert_bin(filenames,output_file_name, ply_minimum, ply_maximum, interpolate_eval);
|
||||
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 << "eval_limit : " << eval_limit << endl;
|
||||
|
|
|
@ -44,12 +44,12 @@ namespace {
|
|||
constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
|
||||
// THEIR PIECES
|
||||
// pair pawn knight bishop rook queen
|
||||
{ 0 }, // Bishop pair
|
||||
{ 36, 0 }, // Pawn
|
||||
{ 9, 63, 0 }, // Knight OUR PIECES
|
||||
{ 59, 65, 42, 0 }, // Bishop
|
||||
{ 46, 39, 24, -24, 0 }, // Rook
|
||||
{ 97, 100, -42, 137, 268, 0 } // Queen
|
||||
{ }, // Bishop pair
|
||||
{ 36, }, // Pawn
|
||||
{ 9, 63, }, // Knight OUR PIECES
|
||||
{ 59, 65, 42, }, // Bishop
|
||||
{ 46, 39, 24, -24, }, // Rook
|
||||
{ 97, 100, -42, 137, 268, } // Queen
|
||||
};
|
||||
|
||||
// Endgame evaluation and scaling functions are accessed directly and not through
|
||||
|
@ -79,8 +79,10 @@ namespace {
|
|||
&& pos.count<PAWN>(~us) >= 1;
|
||||
}
|
||||
|
||||
|
||||
/// imbalance() calculates the imbalance by comparing the piece count of each
|
||||
/// piece type for both colors.
|
||||
|
||||
template<Color Us>
|
||||
int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
|
||||
|
||||
|
@ -94,9 +96,9 @@ namespace {
|
|||
if (!pieceCount[Us][pt1])
|
||||
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]
|
||||
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
|
||||
|
||||
|
@ -110,6 +112,7 @@ namespace {
|
|||
|
||||
namespace Material {
|
||||
|
||||
|
||||
/// 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
|
||||
/// is found. Otherwise a new Entry is computed and stored there, so we don't
|
||||
|
|
|
@ -44,7 +44,7 @@ struct Entry {
|
|||
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
|
||||
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
|
||||
// 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
|
||||
|
|
21
src/misc.cpp
21
src/misc.cpp
|
@ -295,9 +295,10 @@ void prefetch(void* addr) {
|
|||
#endif
|
||||
|
||||
|
||||
/// 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.
|
||||
/// With c++17 some of this functionality can be simplified.
|
||||
/// 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. With c++17 some of this functionality could be simplified.
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
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;
|
||||
|
||||
// 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(
|
||||
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
|
||||
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);
|
||||
mem = VirtualAlloc(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -361,7 +362,7 @@ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
|||
|
||||
static bool firstCall = true;
|
||||
|
||||
// try to allocate large pages
|
||||
// Try to allocate large pages
|
||||
mem = aligned_ttmem_alloc_large_pages(allocSize);
|
||||
|
||||
// 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;
|
||||
|
||||
// fall back to regular, page aligned, allocation if necessary
|
||||
// Fall back to regular, page aligned, allocation if necessary
|
||||
if (!mem)
|
||||
mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
|
||||
|
@ -395,7 +396,9 @@ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
|||
|
||||
#endif
|
||||
|
||||
/// aligned_ttmem_free will free the previously allocated ttmem
|
||||
|
||||
/// aligned_ttmem_free() will free the previously allocated ttmem
|
||||
|
||||
#if defined(_WIN64)
|
||||
|
||||
void aligned_ttmem_free(void* mem) {
|
||||
|
|
14
src/misc.h
14
src/misc.h
|
@ -130,6 +130,20 @@ inline std::ostream& operator<<(std::ostream& os, PRNG& prng)
|
|||
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
|
||||
/// logical processor group. This usually means to be limited to use max 64
|
||||
/// cores. To overcome this, some special platform specific API should be
|
||||
|
|
|
@ -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
|
||||
// useless legality checks later on.
|
||||
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
|
||||
Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace {
|
|||
|
||||
/// MovePicker constructor for the main search
|
||||
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),
|
||||
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {
|
||||
|
||||
|
|
|
@ -88,9 +88,9 @@ enum StatsType { NoCaptures, Captures };
|
|||
/// 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;
|
||||
|
||||
/// LowPlyHistory at higher depths records successful quiet moves on plies 0 to 3
|
||||
/// and quiet moves which are/were in the PV (ttPv)
|
||||
/// It get cleared with each new search and get filled during iterative deepening
|
||||
/// At higher depths LowPlyHistory records successful quiet moves near the root and quiet
|
||||
/// moves which are/were in the PV (ttPv)
|
||||
/// It is cleared with each new search and filled during iterative deepening
|
||||
constexpr int MAX_LPH = 4;
|
||||
typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
|
||||
|
||||
|
@ -133,7 +133,7 @@ public:
|
|||
const CapturePieceToHistory*,
|
||||
const PieceToHistory**,
|
||||
Move,
|
||||
Move*,
|
||||
const Move*,
|
||||
int);
|
||||
Move next_move(bool skipQuiets = false);
|
||||
|
||||
|
|
|
@ -66,6 +66,12 @@ namespace {
|
|||
#undef S
|
||||
#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>
|
||||
Score evaluate(const Position& pos, Pawns::Entry* e) {
|
||||
|
||||
|
@ -150,17 +156,17 @@ namespace {
|
|||
&& !(theirPawns & adjacent_files_bb(s)))
|
||||
score -= Doubled;
|
||||
else
|
||||
score -= Isolated
|
||||
+ WeakUnopposed * !opposed;
|
||||
score -= Isolated
|
||||
+ WeakUnopposed * !opposed;
|
||||
}
|
||||
|
||||
else if (backward)
|
||||
score -= Backward
|
||||
+ WeakUnopposed * !opposed;
|
||||
score -= Backward
|
||||
+ WeakUnopposed * !opposed;
|
||||
|
||||
if (!support)
|
||||
score -= Doubled * doubled
|
||||
+ WeakLever * more_than_one(lever);
|
||||
score -= Doubled * doubled
|
||||
+ WeakLever * more_than_one(lever);
|
||||
}
|
||||
|
||||
return score;
|
||||
|
@ -170,6 +176,7 @@ namespace {
|
|||
|
||||
namespace Pawns {
|
||||
|
||||
|
||||
/// Pawns::probe() looks up the current position's pawns configuration in
|
||||
/// the pawns hash table. It returns a pointer to the Entry if the position
|
||||
/// is found. Otherwise a new Entry is computed and stored there, so we don't
|
||||
|
@ -196,7 +203,7 @@ Entry* probe(const Position& pos) {
|
|||
/// penalty for a king, looking at the king file and the two closest files.
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ struct Entry {
|
|||
Score do_king_safety(const Position& pos);
|
||||
|
||||
template<Color Us>
|
||||
Score evaluate_shelter(const Position& pos, Square ksq);
|
||||
Score evaluate_shelter(const Position& pos, Square ksq) const;
|
||||
|
||||
Key key;
|
||||
Score scores[COLOR_NB];
|
||||
|
|
|
@ -64,10 +64,11 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|||
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||
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(' ') << std::dec << "\nCheckers: ";
|
||||
|
||||
|
@ -104,8 +105,7 @@ Key cuckoo[8192];
|
|||
Move cuckooMove[8192];
|
||||
|
||||
|
||||
/// Position::init() initializes at startup the various arrays used to compute
|
||||
/// hash keys.
|
||||
/// Position::init() initializes at startup the various arrays used to compute hash keys
|
||||
|
||||
void Position::init() {
|
||||
|
||||
|
@ -1277,6 +1277,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||
return bool(res);
|
||||
}
|
||||
|
||||
|
||||
/// Position::is_draw() tests whether the position is drawn by 50-move rule
|
||||
/// or by repetition. It does not detect stalemates.
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ struct StateInfo {
|
|||
#endif // defined(EVAL_NNUE)
|
||||
};
|
||||
|
||||
|
||||
/// 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
|
||||
/// 'draw by repetition' detection. Use a std::deque because pointers to
|
||||
|
|
17
src/psqt.cpp
17
src/psqt.cpp
|
@ -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( -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( -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) }
|
||||
};
|
||||
|
||||
|
@ -101,20 +101,21 @@ constexpr Score PBonus[RANK_NB][FILE_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
|
||||
// tables are initialized by flipping and changing the sign of the white scores.
|
||||
|
||||
// PSQT::init() initializes piece-square tables: the white halves of the tables are
|
||||
// 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() {
|
||||
|
||||
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]);
|
||||
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
{
|
||||
File f = File(edge_distance(file_of(s)));
|
||||
psq[ pc][ s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
||||
: Bonus[pc][rank_of(s)][f]);
|
||||
psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
||||
: Bonus[pc][rank_of(s)][f]);
|
||||
psq[~pc][flip_rank(s)] = -psq[pc][s];
|
||||
}
|
||||
}
|
||||
|
|
157
src/search.cpp
157
src/search.cpp
|
@ -65,9 +65,9 @@ namespace {
|
|||
constexpr uint64_t TtHitAverageResolution = 1024;
|
||||
|
||||
// Razor and futility margins
|
||||
constexpr int RazorMargin = 531;
|
||||
constexpr int RazorMargin = 527;
|
||||
Value futility_margin(Depth d, bool improving) {
|
||||
return Value(217 * (d - improving));
|
||||
return Value(227 * (d - improving));
|
||||
}
|
||||
|
||||
// Reductions lookup table, initialized at startup
|
||||
|
@ -75,16 +75,16 @@ namespace {
|
|||
|
||||
Depth reduction(bool i, Depth d, int 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) {
|
||||
return (4 + depth * depth) / (2 - improving);
|
||||
return (3 + depth * depth) / (2 - improving);
|
||||
}
|
||||
|
||||
// History and stats update bonus, based on depth
|
||||
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
|
||||
|
@ -236,14 +236,8 @@ void MainThread::search() {
|
|||
}
|
||||
else
|
||||
{
|
||||
for (Thread* th : Threads)
|
||||
{
|
||||
th->bestMoveChanges = 0;
|
||||
if (th != this)
|
||||
th->start_searching();
|
||||
}
|
||||
|
||||
Thread::search(); // Let's start searching!
|
||||
Threads.start_searching(); // start non-main threads
|
||||
Thread::search(); // main thread start searching
|
||||
}
|
||||
|
||||
// When we reach the maximum depth, we can arrive here without a raise of
|
||||
|
@ -260,9 +254,7 @@ void MainThread::search() {
|
|||
Threads.stop = true;
|
||||
|
||||
// Wait until all threads have finished
|
||||
for (Thread* th : Threads)
|
||||
if (th != this)
|
||||
th->wait_for_search_finished();
|
||||
Threads.wait_for_search_finished();
|
||||
|
||||
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
||||
// the available ones before exiting.
|
||||
|
@ -271,37 +263,11 @@ void MainThread::search() {
|
|||
|
||||
Thread* bestThread = this;
|
||||
|
||||
// Check if there are threads with a better score than main thread
|
||||
if ( int(Options["MultiPV"]) == 1
|
||||
&& !Limits.depth
|
||||
&& !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"]))
|
||||
&& rootMoves[0].pv[0] != MOVE_NONE)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (int(Options["MultiPV"]) == 1 &&
|
||||
!Limits.depth &&
|
||||
!(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) &&
|
||||
rootMoves[0].pv[0] != MOVE_NONE)
|
||||
bestThread = Threads.get_best_thread();
|
||||
|
||||
bestPreviousScore = bestThread->rootMoves[0].score;
|
||||
|
||||
|
@ -437,12 +403,12 @@ void Thread::search() {
|
|||
if (rootDepth >= 4)
|
||||
{
|
||||
Value prev = rootMoves[pvIdx].previousScore;
|
||||
delta = Value(21);
|
||||
delta = Value(19);
|
||||
alpha = std::max(prev - delta,-VALUE_INFINITE);
|
||||
beta = std::min(prev + delta, VALUE_INFINITE);
|
||||
|
||||
// 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)
|
||||
: -make_score(dct, dct / 2));
|
||||
|
@ -540,13 +506,13 @@ void Thread::search() {
|
|||
&& !Threads.stop
|
||||
&& !mainThread->stopOnPonderhit)
|
||||
{
|
||||
double fallingEval = (332 + 6 * (mainThread->bestPreviousScore - bestValue)
|
||||
+ 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 704.0;
|
||||
double fallingEval = (296 + 6 * (mainThread->bestPreviousScore - bestValue)
|
||||
+ 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 725.0;
|
||||
fallingEval = Utility::clamp(fallingEval, 0.5, 1.5);
|
||||
|
||||
// If the bestMove is stable over several iterations, reduce time accordingly
|
||||
timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.94 : 0.91;
|
||||
double reduction = (1.41 + mainThread->previousTimeReduction) / (2.27 * timeReduction);
|
||||
timeReduction = lastBestMoveDepth + 10 < completedDepth ? 1.92 : 0.95;
|
||||
double reduction = (1.47 + mainThread->previousTimeReduction) / (2.22 * timeReduction);
|
||||
|
||||
// Use part of the gained time from a previous stable move for the current move
|
||||
for (Thread* th : Threads)
|
||||
|
@ -559,7 +525,7 @@ void Thread::search() {
|
|||
double totalTime = rootMoves.size() == 1 ? 0 :
|
||||
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 we are allowed to ponder do not stop the search now but
|
||||
|
@ -571,7 +537,7 @@ void Thread::search() {
|
|||
}
|
||||
else if ( Threads.increaseDepth
|
||||
&& !mainThread->ponder
|
||||
&& Time.elapsed() > totalTime * 0.6)
|
||||
&& Time.elapsed() > totalTime * 0.56)
|
||||
Threads.increaseDepth = false;
|
||||
else
|
||||
Threads.increaseDepth = true;
|
||||
|
@ -661,7 +627,7 @@ namespace {
|
|||
|| pos.is_draw(ss->ply)
|
||||
|| ss->ply >= MAX_PLY)
|
||||
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos)
|
||||
: value_draw(pos.this_thread());
|
||||
: value_draw(pos.this_thread());
|
||||
|
||||
// Step 3. Mate distance pruning. Even if we mate at the next move our score
|
||||
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because
|
||||
|
@ -696,7 +662,7 @@ namespace {
|
|||
// search to overwrite a previous full search TT value, so we use a different
|
||||
// position key in case of an excluded move.
|
||||
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);
|
||||
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
|
||||
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
|
||||
|
@ -704,7 +670,7 @@ namespace {
|
|||
ttPv = PvNode || (ttHit && tte->is_pv());
|
||||
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->ttHitAverage can be used to approximate the running average of ttHit
|
||||
|
@ -801,9 +767,10 @@ namespace {
|
|||
// Step 6. Static evaluation of the position
|
||||
if (ss->inCheck)
|
||||
{
|
||||
// Skip early pruning when in check
|
||||
ss->staticEval = eval = VALUE_NONE;
|
||||
improving = false;
|
||||
goto moves_loop; // Skip early pruning when in check
|
||||
goto moves_loop;
|
||||
}
|
||||
else if (ttHit)
|
||||
{
|
||||
|
@ -853,10 +820,10 @@ namespace {
|
|||
// Step 9. Null move search with verification search (~40 Elo)
|
||||
if ( !PvNode
|
||||
&& (ss-1)->currentMove != MOVE_NULL
|
||||
&& (ss-1)->statScore < 23397
|
||||
&& (ss-1)->statScore < 23824
|
||||
&& eval >= beta
|
||||
&& eval >= ss->staticEval
|
||||
&& ss->staticEval >= beta - 32 * depth - 30 * improving + 120 * ttPv + 292
|
||||
&& ss->staticEval >= beta - 33 * depth - 33 * improving + 112 * ttPv + 311
|
||||
&& !excludedMove
|
||||
&& pos.non_pawn_material(us)
|
||||
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
|
||||
|
@ -864,7 +831,7 @@ namespace {
|
|||
assert(eval - beta >= 0);
|
||||
|
||||
// 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->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
|
||||
// much above beta, we can (almost) safely prune the previous move.
|
||||
if ( !PvNode
|
||||
&& depth >= 5
|
||||
&& depth > 4
|
||||
&& abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
{
|
||||
Value raisedBeta = beta + 189 - 45 * improving;
|
||||
Value raisedBeta = beta + 176 - 49 * improving;
|
||||
assert(raisedBeta < VALUE_INFINITE);
|
||||
MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &captureHistory);
|
||||
int probCutCount = 0;
|
||||
|
@ -1037,14 +1004,15 @@ moves_loop: // When in check, search starts from here
|
|||
// Futility pruning: parent node (~5 Elo)
|
||||
if ( lmrDepth < 6
|
||||
&& !ss->inCheck
|
||||
&& ss->staticEval + 235 + 172 * lmrDepth <= alpha
|
||||
&& ss->staticEval + 284 + 188 * lmrDepth <= alpha
|
||||
&& (*contHist[0])[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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
else
|
||||
|
@ -1059,12 +1027,14 @@ moves_loop: // When in check, search starts from here
|
|||
if ( !givesCheck
|
||||
&& lmrDepth < 6
|
||||
&& !(PvNode && abs(bestValue) < 2)
|
||||
&& PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))]
|
||||
&& !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;
|
||||
|
||||
// See based pruning
|
||||
if (!pos.see_ge(move, Value(-194) * depth)) // (~25 Elo)
|
||||
if (!pos.see_ge(move, Value(-202) * depth)) // (~25 Elo)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -1106,8 +1076,8 @@ moves_loop: // When in check, search starts from here
|
|||
else if (singularBeta >= beta)
|
||||
return singularBeta;
|
||||
|
||||
// If the eval of ttMove is greater than beta we try also if there is an other move that
|
||||
// pushes it over beta, if so also produce a cutoff
|
||||
// If the eval of ttMove is greater than beta we try also if there is another
|
||||
// move that pushes it over beta, if so also produce a cutoff.
|
||||
else if (ttValue >= beta)
|
||||
{
|
||||
ss->excludedMove = move;
|
||||
|
@ -1177,15 +1147,15 @@ moves_loop: // When in check, search starts from here
|
|||
|| moveCountPruning
|
||||
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|
||||
|| cutNode
|
||||
|| thisThread->ttHitAverage < 375 * TtHitAverageResolution * TtHitAverageWindow / 1024))
|
||||
|| thisThread->ttHitAverage < 415 * TtHitAverageResolution * TtHitAverageWindow / 1024))
|
||||
{
|
||||
Depth r = reduction(improving, depth, moveCount);
|
||||
|
||||
// Decrease reduction if the ttHit running average is large
|
||||
if (thisThread->ttHitAverage > 500 * TtHitAverageResolution * TtHitAverageWindow / 1024)
|
||||
if (thisThread->ttHitAverage > 473 * TtHitAverageResolution * TtHitAverageWindow / 1024)
|
||||
r--;
|
||||
|
||||
// Reduction if other threads are searching this position.
|
||||
// Reduction if other threads are searching this position
|
||||
if (th.marked())
|
||||
r++;
|
||||
|
||||
|
@ -1197,7 +1167,7 @@ moves_loop: // When in check, search starts from here
|
|||
r++;
|
||||
|
||||
// Decrease reduction if opponent's move count is high (~5 Elo)
|
||||
if ((ss-1)->moveCount > 14)
|
||||
if ((ss-1)->moveCount > 13)
|
||||
r--;
|
||||
|
||||
// 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)
|
||||
else if ( type_of(move) == NORMAL
|
||||
&& !pos.see_ge(reverse_move(move)))
|
||||
r -= 2 + ttPv;
|
||||
r -= 2 + ttPv - (type_of(movedPiece) == PAWN);
|
||||
|
||||
ss->statScore = thisThread->mainHistory[us][from_to(move)]
|
||||
+ (*contHist[0])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||
+ (*contHist[3])[movedPiece][to_sq(move)]
|
||||
- 4926;
|
||||
- 4826;
|
||||
|
||||
// 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--;
|
||||
|
||||
else if ((ss-1)->statScore >= -116 && ss->statScore < -154)
|
||||
else if ((ss-1)->statScore >= -125 && ss->statScore < -138)
|
||||
r++;
|
||||
|
||||
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
|
||||
r -= ss->statScore / 16434;
|
||||
r -= ss->statScore / 14615;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1245,7 +1215,7 @@ moves_loop: // When in check, search starts from here
|
|||
|
||||
// Unless giving check, this capture is likely bad
|
||||
if ( !givesCheck
|
||||
&& ss->staticEval + PieceValue[EG][pos.captured_piece()] + 200 * depth <= alpha)
|
||||
&& ss->staticEval + PieceValue[EG][pos.captured_piece()] + 211 * depth <= alpha)
|
||||
r++;
|
||||
}
|
||||
|
||||
|
@ -1322,7 +1292,7 @@ moves_loop: // When in check, search starts from here
|
|||
rm.pv.push_back(*m);
|
||||
|
||||
// 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.
|
||||
if (moveCount > 1)
|
||||
++thisThread->bestMoveChanges;
|
||||
|
@ -1507,7 +1477,7 @@ moves_loop: // When in check, search starts from here
|
|||
if (PvNode && bestValue > alpha)
|
||||
alpha = bestValue;
|
||||
|
||||
futilityBase = bestValue + 154;
|
||||
futilityBase = bestValue + 141;
|
||||
}
|
||||
|
||||
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))
|
||||
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.
|
||||
if (ss->inCheck && bestValue == -VALUE_INFINITE)
|
||||
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
|
||||
// "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.
|
||||
|
||||
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
|
||||
// from the transposition table (which refers to the plies to mate/be mated
|
||||
// from current position) to "plies to mate/be mated (TB win/loss) from the root".
|
||||
// However, 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.
|
||||
// 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
|
||||
// current position) to "plies to mate/be mated (TB win/loss) from the root". However,
|
||||
// for mate scores, to avoid potentially false mate scores related to the 50 moves rule
|
||||
// and the graph history interaction, we return an optimal TB score instead.
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (depth > 12 && ss->ply < MAX_LPH)
|
||||
thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7);
|
||||
if (depth > 11 && ss->ply < MAX_LPH)
|
||||
thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 6);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
/// 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.
|
||||
|
||||
|
|
|
@ -1200,7 +1200,7 @@ WDLScore search(Position& pos, ProbeState* result) {
|
|||
auto moveList = MoveList<LEGAL>(pos);
|
||||
size_t totalCount = moveList.size(), moveCount = 0;
|
||||
|
||||
for (const Move& move : moveList)
|
||||
for (const Move move : moveList)
|
||||
{
|
||||
if ( !pos.capture(move)
|
||||
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
|
||||
|
@ -1362,7 +1362,7 @@ void Tablebases::init(const std::string& paths) {
|
|||
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) {
|
||||
TBTables.add({KING, p1, KING});
|
||||
|
||||
|
@ -1469,7 +1469,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
|||
StateInfo st;
|
||||
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;
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ Thread::~Thread() {
|
|||
stdThread.join();
|
||||
}
|
||||
|
||||
|
||||
/// Thread::bestMoveCount(Move move) return best move counter for the given root move
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/// Thread::clear() reset histories, usually before a new game
|
||||
|
||||
void Thread::clear() {
|
||||
|
@ -81,6 +83,7 @@ void Thread::clear() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Thread::start_searching() wakes up the thread that will start the search
|
||||
|
||||
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() {
|
||||
|
||||
|
@ -170,6 +174,7 @@ void ThreadPool::clear() {
|
|||
main()->previousTimeReduction = 1.0;
|
||||
}
|
||||
|
||||
|
||||
/// 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.
|
||||
|
||||
|
@ -208,7 +213,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||
|
||||
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->rootMoves = rootMoves;
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -109,6 +109,9 @@ struct ThreadPool : public std::vector<Thread*> {
|
|||
MainThread* main() const { return static_cast<MainThread*>(front()); }
|
||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
||||
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;
|
||||
|
||||
|
|
|
@ -28,14 +28,14 @@
|
|||
|
||||
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:
|
||||
// 1) x basetime (+z increment)
|
||||
// 2) x moves in y seconds (+z increment)
|
||||
|
||||
/// 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)
|
||||
// 2) x moves in y seconds (+ z increment)
|
||||
|
||||
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
||||
|
||||
TimePoint minThinkingTime = TimePoint(Options["Minimum Thinking Time"]);
|
||||
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
|
||||
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
|
||||
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
||||
|
@ -61,7 +61,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||
|
||||
startTime = limits.startTime;
|
||||
|
||||
//Maximum move horizon of 50 moves
|
||||
// Maximum move horizon of 50 moves
|
||||
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
|
||||
|
||||
// Make sure timeLeft is > 0 since we may use it as a divisor
|
||||
|
@ -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,
|
||||
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)
|
||||
|
@ -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
|
||||
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));
|
||||
|
||||
if (Options["Ponder"])
|
||||
|
|
11
src/tt.cpp
11
src/tt.cpp
|
@ -30,23 +30,23 @@
|
|||
|
||||
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.
|
||||
|
||||
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
|
||||
if (m || (k >> 48) != key16)
|
||||
if (m || (uint16_t)k != key16)
|
||||
move16 = (uint16_t)m;
|
||||
|
||||
// Overwrite less valuable entries
|
||||
if ( (k >> 48) != key16
|
||||
if ((uint16_t)k != key16
|
||||
|| d - DEPTH_OFFSET > depth8 - 4
|
||||
|| b == BOUND_EXACT)
|
||||
{
|
||||
assert(d >= DEPTH_OFFSET);
|
||||
|
||||
key16 = (uint16_t)(k >> 48);
|
||||
key16 = (uint16_t)k;
|
||||
value16 = (int16_t)v;
|
||||
eval16 = (int16_t)ev;
|
||||
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
||||
|
@ -107,6 +107,7 @@ void TranspositionTable::clear() {
|
|||
th.join();
|
||||
}
|
||||
|
||||
|
||||
/// 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.
|
||||
/// 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
|
||||
|
||||
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)
|
||||
if (!tte[i].key16 || tte[i].key16 == key16)
|
||||
|
|
7
src/tt.h
7
src/tt.h
|
@ -60,8 +60,8 @@ private:
|
|||
/// A TranspositionTable is an array of Cluster, of size clusterCount. Each
|
||||
/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
|
||||
/// contains information on exactly one position. The size of a Cluster should
|
||||
/// divide the size of a cache line for best performance,
|
||||
/// as the cacheline is prefetched when possible.
|
||||
/// divide the size of a cache line for best performance, as the cacheline is
|
||||
/// prefetched when possible.
|
||||
|
||||
class TranspositionTable {
|
||||
|
||||
|
@ -82,9 +82,8 @@ public:
|
|||
void resize(size_t mbSize);
|
||||
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 {
|
||||
return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0];
|
||||
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -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);
|
||||
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 << ","
|
||||
<< v << ","
|
||||
<< r(v).first << "," << r(v).second << ","
|
||||
|
|
21
src/types.h
21
src/types.h
|
@ -219,7 +219,6 @@ constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
|
|||
typedef int Depth;
|
||||
|
||||
enum : int {
|
||||
|
||||
DEPTH_QS_CHECKS = 0,
|
||||
DEPTH_QS_NO_CHECKS = -1,
|
||||
DEPTH_QS_RECAPTURES = -5,
|
||||
|
@ -288,11 +287,11 @@ inline Value mg_value(Score s) {
|
|||
}
|
||||
|
||||
#define ENABLE_BASE_OPERATORS_ON(T) \
|
||||
constexpr T operator+(T d1, T d2) { return T(int(d1) + int(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 d1, int d2) { return T(int(d1) - d2); } \
|
||||
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, T d2) { return d1 = d1 - d2; }
|
||||
inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
|
||||
inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
|
||||
|
||||
#define ENABLE_INCR_OPERATORS_ON(T) \
|
||||
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_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
|
||||
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
|
||||
}
|
||||
|
||||
constexpr Square flip_rank(Square s) {
|
||||
constexpr Square flip_rank(Square s) { // Swap A1 <-> 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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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) {
|
||||
|
||||
// at most 2^32 clusters.
|
||||
constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
|
||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
|
||||
o["Debug Log File"] << Option("", on_logger);
|
||||
o["Contempt"] << Option(24, -100, 100);
|
||||
|
@ -70,7 +69,6 @@ void init(OptionsMap& o) {
|
|||
o["MultiPV"] << Option(1, 1, 500);
|
||||
o["Skill Level"] << Option(20, 0, 20);
|
||||
o["Move Overhead"] << Option(10, 0, 5000);
|
||||
o["Minimum Thinking Time"] << Option( 0, 0, 5000);
|
||||
o["Slow Mover"] << Option(100, 10, 1000);
|
||||
o["nodestime"] << Option(0, 0, 10000);
|
||||
o["UCI_Chess960"] << Option(false);
|
||||
|
|
Loading…
Add table
Reference in a new issue