stellar

Stellar - UCI Chess engine written in C++20
git clone git://git.dimitrijedobrota.com/stellar.git
Log | Files | Refs | README | LICENSE

commit 0218a1559240963bdc53c7157b1cb16cfa4878f0
parent 00388d4c00764e6a1f68f488b2c3a20dec31baf0
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Tue, 19 Mar 2024 20:29:33 +0000

Pawn hash table

Diffstat:
MCMakeLists.txt | 2+-
Msrc/board/board.cpp | 2++
Msrc/board/board.hpp | 32+++++++++++++++++++++++---------
Msrc/board/zobrist.hpp | 11+++++++++++
Msrc/engine/evaluate.cpp | 91++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/move/move.cpp | 4++++
Msrc/move/move.hpp | 24++++++++++++------------
7 files changed, 120 insertions(+), 46 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project( Stellar - VERSION 1.3.7 + VERSION 1.3.8 DESCRIPTION "Chess engine written in C++" HOMEPAGE_URL https://git.dimitrijedobrota.com/stellar.git LANGUAGES CXX diff --git a/src/board/board.cpp b/src/board/board.cpp @@ -12,6 +12,7 @@ namespace zobrist { /* Init arrays for Zobris hashing */ +U32 keys_pawn[2][64] = {0}; U64 keys_piece[2][12][64] = {0}; U64 keys_enpassant[64] = {0}; U64 keys_castle[16] = {0}; @@ -57,6 +58,7 @@ Board::Board(const std::string &fen) { enpassant = fen[++i] != '-' ? from_coordinates(fen.substr(i, 2)) : Square::no_sq; + hash_pawn = zobrist::hash_pawn(*this); hash = zobrist::hash(*this); } diff --git a/src/board/board.hpp b/src/board/board.hpp @@ -28,10 +28,11 @@ class Board { /* Getters */ - [[nodiscard]] inline constexpr U64 get_hash() const; - [[nodiscard]] inline constexpr Color get_side() const; - [[nodiscard]] inline constexpr uint8_t get_castle() const; - [[nodiscard]] inline constexpr Square get_enpassant() const; + [[nodiscard]] inline constexpr U64 get_hash() const { return hash; } + [[nodiscard]] inline constexpr Color get_side() const { return side; } + [[nodiscard]] inline constexpr uint8_t get_castle() const { return castle; } + [[nodiscard]] inline constexpr Square get_enpassant() const { return enpassant; } + [[nodiscard]] inline constexpr U64 get_hash_pawn() const { return hash_pawn; } [[nodiscard]] inline constexpr U64 get_bitboard_color(Color side) const; [[nodiscard]] inline constexpr U64 get_bitboard_occupancy() const; @@ -49,6 +50,8 @@ class Board { /* Setters */ inline constexpr void xor_hash(U64 op); + inline constexpr void xor_hash_pawn(U32 op); + inline constexpr void switch_side(); inline constexpr void and_castle(uint8_t right); inline constexpr void set_enpassant(Square target); @@ -74,16 +77,12 @@ class Board { U64 colors[2] = {0}; U64 pieces[6] = {0}; U64 hash = 0; + U32 hash_pawn = 0; Color side = WHITE; Square enpassant = Square::no_sq; uint8_t castle = 0; }; -constexpr Color Board::get_side() const { return side; } -constexpr U64 Board::get_hash() const { return hash; } -constexpr uint8_t Board::get_castle() const { return castle; } -constexpr Square Board::get_enpassant() const { return enpassant; } - constexpr U64 Board::get_bitboard_color(Color side) const { return colors[side]; } constexpr U64 Board::get_bitboard_occupancy() const { return colors[WHITE] | colors[BLACK]; } constexpr U64 Board::get_bitboard_piece(Type piece) const { return pieces[piece]; } @@ -122,6 +121,8 @@ constexpr Type Board::get_square_piece_type(Square square) const { /* Setters */ constexpr void Board::xor_hash(U64 op) { hash ^= op; } +constexpr void Board::xor_hash_pawn(U32 op) { hash_pawn ^= op; } + constexpr void Board::and_castle(uint8_t right) { hash ^= zobrist::key_castle(castle); castle &= right; @@ -203,4 +204,17 @@ U64 zobrist::hash(const Board &board) { return key_final; } +U32 zobrist::hash_pawn(const Board &board) { + U32 key_final = C32(0); + uint8_t square = 0; + + U64 bitboard_white = board.get_bitboard_piece(PAWN, WHITE); + bitboard_for_each_bit(square, bitboard_white) { key_final ^= keys_pawn[WHITE][square]; } + + U64 bitboard_black = board.get_bitboard_piece(PAWN, BLACK); + bitboard_for_each_bit(square, bitboard_black) { key_final ^= keys_pawn[BLACK][square]; } + + return key_final; +} + #endif diff --git a/src/board/zobrist.hpp b/src/board/zobrist.hpp @@ -11,6 +11,7 @@ class Board; namespace zobrist { +extern U32 keys_pawn[2][64]; extern U64 keys_piece[2][12][64]; extern U64 keys_enpassant[64]; extern U64 keys_castle[16]; @@ -35,12 +36,22 @@ inline void init() { for (int castle = 0; castle < 16; castle++) { keys_castle[castle] = gen3(); } + + Random gen4(C32(3642040919)); + for (int c = 0; c < 2; c++) { + for (int square = 0; square < 64; square++) { + keys_pawn[c][square] = gen4.get_U32(); + } + } }; inline U64 hash(const Board &board); +inline U32 hash_pawn(const Board &board); + inline constexpr U64 key_side() { return keys_side; } inline constexpr U64 key_castle(int exp) { return keys_castle[exp]; } inline constexpr U64 key_enpassant(Square square) { return keys_enpassant[square]; } +inline constexpr U64 key_pawn(Color color, Square square) { return keys_pawn[color][square]; } inline constexpr U64 key_piece(Type type, Color color, Square square) { return keys_piece[color][type][square]; } diff --git a/src/engine/evaluate.cpp b/src/engine/evaluate.cpp @@ -6,6 +6,7 @@ #include "utils.hpp" #include <array> +#include <vector> namespace evaluate { @@ -63,8 +64,38 @@ inline constexpr const mask_passed_array mask_passed = []() constexpr -> mask_pa return mask_passed; }(); -using score::Phase::ENDGAME; -using score::Phase::OPENING; +struct Hashe { + U32 key; + int16_t opening; + int16_t endgame; + int16_t total; +}; + +template <U32 SIZE> struct PTable { + + static void clear() { memset(table, 0x00, SIZE * sizeof(Hashe)); } + + [[nodiscard]] static inline U32 read(U32 hash, Color side) { + const U32 key = side * SIZE + hash % SIZE; + const Hashe &phashe = table[key]; + return phashe.key == hash ? key : __UINT32_MAX__; + } + + [[nodiscard]] static inline int16_t read_opening(U32 key) { return table[key].opening; } + [[nodiscard]] static inline int16_t read_endgame(U32 key) { return table[key].endgame; } + [[nodiscard]] static inline int16_t read_total(U32 key) { return table[key].total; } + + static inline void write(U32 hash, Color side, int16_t opening, int16_t endgame, int16_t total) { + table[side * SIZE + hash % SIZE] = {hash, opening, endgame, total}; + } + + private: + static std::array<Hashe, 2 * SIZE> table; +}; + +template <U32 SIZE> std::array<Hashe, 2 * SIZE> PTable<SIZE>::table = {{{0}}}; + +PTable<65537> ptable; uint16_t score_game_phase(const Board &board) { int16_t total = 0; @@ -76,34 +107,47 @@ uint16_t score_game_phase(const Board &board) { } int16_t score_position_side(const Board &board, const Color side, const uint16_t phase_score) { - U64 bitboard; + using score::Phase::ENDGAME; + using score::Phase::OPENING; - int16_t total = 0, opening = 0, endgame = 0; + U64 bitboard; int8_t square_i; const U64 pawns = board.get_bitboard_piece(PAWN); const U64 pawnsS = board.get_bitboard_piece(PAWN, side); const U64 pawnsO = pawns & ~pawnsS; - bitboard = board.get_bitboard_piece(PAWN, side); - bitboard_for_each_bit(square_i, bitboard) { - const auto square = static_cast<Square>(square_i); - opening += score::get(PAWN, side, square, OPENING) + score::get(PAWN, OPENING); - endgame += score::get(PAWN, side, square, ENDGAME) + score::get(PAWN, ENDGAME); - - // check isolated, doubled and passed pawns - const uint8_t file = get_file(square), rank = get_rank(square); - if (!(mask_isolated[file] & pawnsS)) { - opening -= score::pawn_isolated_opening; - endgame -= score::pawn_isolated_endgame; - } + int16_t total = 0, opening = 0, endgame = 0; - if (bit::count(pawnsS & mask_file[file]) > 1) { - opening -= score::pawn_double_opening; - endgame -= score::pawn_double_endgame; + const U32 hash = board.get_hash_pawn(); + const U32 key = ptable.read(hash, side); + if (key == __UINT32_MAX__) { + bitboard = board.get_bitboard_piece(PAWN, side); + bitboard_for_each_bit(square_i, bitboard) { + const auto square = static_cast<Square>(square_i); + opening += score::get(PAWN, side, square, OPENING) + score::get(PAWN, OPENING); + endgame += score::get(PAWN, side, square, ENDGAME) + score::get(PAWN, ENDGAME); + + // check isolated, doubled and passed pawns + const uint8_t file = get_file(square), rank = get_rank(square); + if (!(mask_isolated[file] & pawnsS)) { + opening -= score::pawn_isolated_opening; + endgame -= score::pawn_isolated_endgame; + } + + if (bit::count(pawnsS & mask_file[file]) > 1) { + opening -= score::pawn_double_opening; + endgame -= score::pawn_double_endgame; + } + + if (!(pawnsO & mask_passed[side][square_i])) total += score::pawn_passed[side][rank]; } - if (!(pawnsO & mask_passed[side][square_i])) total += score::pawn_passed[side][rank]; + ptable.write(hash, side, opening, endgame, total); + } else { + opening = ptable.read_opening(key); + endgame = ptable.read_endgame(key); + total = ptable.read_total(key); } bitboard = board.get_bitboard_piece(KNIGHT, side); @@ -151,10 +195,9 @@ int16_t score_position_side(const Board &board, const Color side, const uint16_t if (!(pawnsS & mask_file[file])) total -= score::file_open_semi; } - opening += total, endgame += total; - if (phase_score > score::phase_opening) return opening; - if (phase_score < score::phase_endgame) return endgame; - return score::interpolate(phase_score, opening, endgame); + if (phase_score > score::phase_opening) return opening + total; + if (phase_score < score::phase_endgame) return endgame + total; + return score::interpolate(phase_score, opening, endgame) + total; } int16_t score_position(const Board &board) { diff --git a/src/move/move.cpp b/src/move/move.cpp @@ -7,12 +7,16 @@ void Move::piece_remove(Board &board, Type type, Color color, Square square) const { board.pop_piece(type, color, square); + board.xor_hash(zobrist::key_piece(type, color, square)); + if (type == PAWN) board.xor_hash_pawn(zobrist::key_pawn(color, square)); } void Move::piece_set(Board &board, Type type, Color color, Square square) const { board.set_piece(type, color, square); + board.xor_hash(zobrist::key_piece(type, color, square)); + if (type == PAWN) board.xor_hash_pawn(zobrist::key_pawn(color, square)); } void Move::piece_move(Board &board, Type type, Color color, Square source, Square target) const { diff --git a/src/move/move.hpp b/src/move/move.hpp @@ -34,23 +34,23 @@ struct Move { return a.source_i == b.source_i && a.target_i == b.target_i && a.flags_i == b.flags_i; } - [[nodiscard]] Square source() const { return static_cast<Square>(source_i); } - [[nodiscard]] Square target() const { return static_cast<Square>(target_i); } + [[nodiscard]] constexpr Square source() const { return static_cast<Square>(source_i); } + [[nodiscard]] constexpr Square target() const { return static_cast<Square>(target_i); } - [[nodiscard]] bool is_capture() const { return flags_i != PQUIET && (flags_i & CAPTURE); } - [[nodiscard]] bool is_promote() const { return flags_i & 0x8; } + [[nodiscard]] constexpr bool is_capture() const { return flags_i != PQUIET && (flags_i & CAPTURE); } + [[nodiscard]] constexpr bool is_promote() const { return flags_i & 0x8; } - [[nodiscard]] bool is_double() const { return flags_i == DOUBLE; } - [[nodiscard]] bool is_repeatable() const { return flags_i == QUIET; } - [[nodiscard]] bool is_quiet() const { return flags_i == QUIET || flags_i == PQUIET; } + [[nodiscard]] constexpr bool is_double() const { return flags_i == DOUBLE; } + [[nodiscard]] constexpr bool is_repeatable() const { return flags_i == QUIET; } + [[nodiscard]] constexpr bool is_quiet() const { return flags_i == QUIET || flags_i == PQUIET; } - [[nodiscard]] bool is_castle() const { return flags_i == CASTLEK || flags_i == CASTLEQ; } - [[nodiscard]] bool is_castle_king() const { return flags_i == CASTLEK; } - [[nodiscard]] bool is_castle_queen() const { return flags_i == CASTLEQ; } + [[nodiscard]] constexpr bool is_castle() const { return flags_i == CASTLEK || flags_i == CASTLEQ; } + [[nodiscard]] constexpr bool is_castle_king() const { return flags_i == CASTLEK; } + [[nodiscard]] constexpr bool is_castle_queen() const { return flags_i == CASTLEQ; } - [[nodiscard]] bool is_enpassant() const { return flags_i == ENPASSANT; } + [[nodiscard]] constexpr bool is_enpassant() const { return flags_i == ENPASSANT; } - [[nodiscard]] const Type promoted() const { return static_cast<Type>((flags_i & 0x3) + 1); } + [[nodiscard]] constexpr const Type promoted() const { return static_cast<Type>((flags_i & 0x3) + 1); } bool make(Board &board) const;