diff --git a/src/learn/gensfen.cpp b/src/learn/gensfen.cpp index 4a6f26dc..1a9187ae 100644 --- a/src/learn/gensfen.cpp +++ b/src/learn/gensfen.cpp @@ -3,7 +3,6 @@ #include "packed_sfen.h" #include "multi_think.h" #include "sfen_stream.h" -#include "../syzygy/tbprobe.h" #include "misc.h" #include "position.h" @@ -73,7 +72,7 @@ namespace Learner file_worker_thread.join(); output_file_stream.reset(); -#if defined(_DEBUG) +#if !defined(NDEBUG) { // All buffers should be empty since file_worker_thread // should have written everything before exiting. diff --git a/src/learn/half_float.h b/src/learn/half_float.h index ebe77526..5808a786 100644 --- a/src/learn/half_float.h +++ b/src/learn/half_float.h @@ -11,122 +11,122 @@ namespace HalfFloat { - // IEEE 754 float 32 format is : - // sign(1bit) + exponent(8bits) + fraction(23bits) = 32bits - // - // Our float16 format is : - // sign(1bit) + exponent(5bits) + fraction(10bits) = 16bits - union float32_converter - { - int32_t n; - float f; - }; + // IEEE 754 float 32 format is : + // sign(1bit) + exponent(8bits) + fraction(23bits) = 32bits + // + // Our float16 format is : + // sign(1bit) + exponent(5bits) + fraction(10bits) = 16bits + union float32_converter + { + int32_t n; + float f; + }; - // 16-bit float - struct float16 - { - // --- constructors + // 16-bit float + struct float16 + { + // --- constructors - float16() {} - float16(int16_t n) { from_float((float)n); } - float16(int32_t n) { from_float((float)n); } - float16(float n) { from_float(n); } - float16(double n) { from_float((float)n); } + float16() {} + float16(int16_t n) { from_float((float)n); } + float16(int32_t n) { from_float((float)n); } + float16(float n) { from_float(n); } + float16(double n) { from_float((float)n); } - // build from a float - void from_float(float f) { *this = to_float16(f); } + // build from a float + void from_float(float f) { *this = to_float16(f); } - // --- implicit converters + // --- implicit converters - operator int32_t() const { return (int32_t)to_float(*this); } - operator float() const { return to_float(*this); } - operator double() const { return double(to_float(*this)); } + operator int32_t() const { return (int32_t)to_float(*this); } + operator float() const { return to_float(*this); } + operator double() const { return double(to_float(*this)); } - // --- operators + // --- operators - float16 operator += (float16 rhs) { from_float(to_float(*this) + to_float(rhs)); return *this; } - float16 operator -= (float16 rhs) { from_float(to_float(*this) - to_float(rhs)); return *this; } - float16 operator *= (float16 rhs) { from_float(to_float(*this) * to_float(rhs)); return *this; } - float16 operator /= (float16 rhs) { from_float(to_float(*this) / to_float(rhs)); return *this; } - float16 operator + (float16 rhs) const { return float16(*this) += rhs; } - float16 operator - (float16 rhs) const { return float16(*this) -= rhs; } - float16 operator * (float16 rhs) const { return float16(*this) *= rhs; } - float16 operator / (float16 rhs) const { return float16(*this) /= rhs; } - float16 operator - () const { return float16(-to_float(*this)); } - bool operator == (float16 rhs) const { return this->v_ == rhs.v_; } - bool operator != (float16 rhs) const { return !(*this == rhs); } + float16 operator += (float16 rhs) { from_float(to_float(*this) + to_float(rhs)); return *this; } + float16 operator -= (float16 rhs) { from_float(to_float(*this) - to_float(rhs)); return *this; } + float16 operator *= (float16 rhs) { from_float(to_float(*this) * to_float(rhs)); return *this; } + float16 operator /= (float16 rhs) { from_float(to_float(*this) / to_float(rhs)); return *this; } + float16 operator + (float16 rhs) const { return float16(*this) += rhs; } + float16 operator - (float16 rhs) const { return float16(*this) -= rhs; } + float16 operator * (float16 rhs) const { return float16(*this) *= rhs; } + float16 operator / (float16 rhs) const { return float16(*this) /= rhs; } + float16 operator - () const { return float16(-to_float(*this)); } + bool operator == (float16 rhs) const { return this->v_ == rhs.v_; } + bool operator != (float16 rhs) const { return !(*this == rhs); } - static void UnitTest() { unit_test(); } + static void UnitTest() { unit_test(); } - private: + private: - // --- entity + // --- entity - uint16_t v_; + uint16_t v_; - // --- conversion between float and float16 + // --- conversion between float and float16 - static float16 to_float16(float f) - { - float32_converter c; - c.f = f; - u32 n = c.n; + static float16 to_float16(float f) + { + float32_converter c; + c.f = f; + u32 n = c.n; - // The sign bit is MSB in common. - uint16_t sign_bit = (n >> 16) & 0x8000; + // The sign bit is MSB in common. + uint16_t sign_bit = (n >> 16) & 0x8000; - // The exponent of IEEE 754's float 32 is biased +127 , so we change this bias into +15 and limited to 5-bit. - uint16_t exponent = (((n >> 23) - 127 + 15) & 0x1f) << 10; + // The exponent of IEEE 754's float 32 is biased +127 , so we change this bias into +15 and limited to 5-bit. + uint16_t exponent = (((n >> 23) - 127 + 15) & 0x1f) << 10; - // The fraction is limited to 10-bit. - uint16_t fraction = (n >> (23-10)) & 0x3ff; + // The fraction is limited to 10-bit. + uint16_t fraction = (n >> (23-10)) & 0x3ff; - float16 f_; - f_.v_ = sign_bit | exponent | fraction; + float16 f_; + f_.v_ = sign_bit | exponent | fraction; - return f_; - } + return f_; + } - static float to_float(float16 v) - { - u32 sign_bit = (v.v_ & 0x8000) << 16; - u32 exponent = ((((v.v_ >> 10) & 0x1f) - 15 + 127) & 0xff) << 23; - u32 fraction = (v.v_ & 0x3ff) << (23 - 10); + static float to_float(float16 v) + { + u32 sign_bit = (v.v_ & 0x8000) << 16; + u32 exponent = ((((v.v_ >> 10) & 0x1f) - 15 + 127) & 0xff) << 23; + u32 fraction = (v.v_ & 0x3ff) << (23 - 10); - float32_converter c; - c.n = sign_bit | exponent | fraction; - return c.f; - } + float32_converter c; + c.n = sign_bit | exponent | fraction; + return c.f; + } - // It is not a unit test, but I confirmed that it can be calculated. I'll fix the code later (maybe). - static void unit_test() - { - float16 a, b, c, d; - a = 1; - std::cout << (float)a << std::endl; - b = -118.625; - std::cout << (float)b << std::endl; - c = 2.5; - std::cout << (float)c << std::endl; - d = a + c; - std::cout << (float)d << std::endl; + // It is not a unit test, but I confirmed that it can be calculated. I'll fix the code later (maybe). + static void unit_test() + { + float16 a, b, c, d; + a = 1; + std::cout << (float)a << std::endl; + b = -118.625; + std::cout << (float)b << std::endl; + c = 2.5; + std::cout << (float)c << std::endl; + d = a + c; + std::cout << (float)d << std::endl; - c *= 1.5; - std::cout << (float)c << std::endl; + c *= 1.5; + std::cout << (float)c << std::endl; - b /= 3; - std::cout << (float)b << std::endl; + b /= 3; + std::cout << (float)b << std::endl; - float f1 = 1.5; - a += f1; - std::cout << (float)a << std::endl; + float f1 = 1.5; + a += f1; + std::cout << (float)a << std::endl; - a += f1 * (float)a; - std::cout << (float)a << std::endl; - } + a += f1 * (float)a; + std::cout << (float)a << std::endl; + } - }; + }; } diff --git a/src/learn/learn.cpp b/src/learn/learn.cpp index 6c865d98..b09700e9 100644 --- a/src/learn/learn.cpp +++ b/src/learn/learn.cpp @@ -1066,14 +1066,14 @@ namespace Learner pos.do_move((Move)ps.move, state[ply++]); - // There is a possibility that all the pieces are blocked and stuck. - // Also, the declaration win phase is excluded from - // learning because you cannot go to leaf with PV moves. - // (shouldn't write out such teacher aspect itself, - // but may have written it out with an old generation routine) - // Skip the position if there are no legal moves (=checkmated or stalemate). - if (MoveList(pos).size() == 0) - goto RETRY_READ; + // There is a possibility that all the pieces are blocked and stuck. + // Also, the declaration win phase is excluded from + // learning because you cannot go to leaf with PV moves. + // (shouldn't write out such teacher aspect itself, + // but may have written it out with an old generation routine) + // Skip the position if there are no legal moves (=checkmated or stalemate). + if (MoveList(pos).size() == 0) + goto RETRY_READ; // Evaluation value of shallow search (qsearch) const auto [_, pv] = Search::qsearch(pos); diff --git a/src/learn/multi_think.cpp b/src/learn/multi_think.cpp index daed3e96..d2ae65eb 100644 --- a/src/learn/multi_think.cpp +++ b/src/learn/multi_think.cpp @@ -1,103 +1,103 @@ #include "multi_think.h" -#include "nnue/evaluate_nnue.h" - #include "tt.h" #include "uci.h" #include "types.h" #include "search.h" +#include "nnue/evaluate_nnue.h" + #include void MultiThink::go_think() { - // Read evaluation function, etc. - // In the case of the learn command, the value of the evaluation function may be corrected after reading the evaluation function, so - // Skip memory corruption check. - Eval::NNUE::init(); + // Read evaluation function, etc. + // In the case of the learn command, the value of the evaluation function may be corrected after reading the evaluation function, so + // Skip memory corruption check. + Eval::NNUE::init(); - // Call the derived class's init(). - init(); + // Call the derived class's init(). + init(); - // The loop upper limit is set with set_loop_max(). - loop_count = 0; - done_count = 0; + // The loop upper limit is set with set_loop_max(). + loop_count = 0; + done_count = 0; - // Create threads as many as Options["Threads"] and start thinking. - std::vector threads; - auto thread_num = (size_t)Options["Threads"]; + // Create threads as many as Options["Threads"] and start thinking. + std::vector threads; + auto thread_num = (size_t)Options["Threads"]; - // Secure end flag of worker thread + // Secure end flag of worker thread threads_finished=0; - // start worker thread - for (size_t i = 0; i < thread_num; ++i) - { - threads.push_back(std::thread([i, this] - { - // exhaust all processor threads. - WinProcGroup::bindThisThread(i); + // start worker thread + for (size_t i = 0; i < thread_num; ++i) + { + threads.push_back(std::thread([i, this] + { + // exhaust all processor threads. + WinProcGroup::bindThisThread(i); - // execute the overridden process - this->thread_worker(i); + // execute the overridden process + this->thread_worker(i); - // Set the end flag because the thread has ended - this->threads_finished++; - })); - } + // Set the end flag because the thread has ended + this->threads_finished++; + })); + } - // wait for all threads to finish - // for (auto& th :threads) - // th.join(); - // If you write like, the thread will rush here while it is still working, - // During that time, callback_func() cannot be called and you cannot save. - // Therefore, you need to check the end flag yourself. + // wait for all threads to finish + // for (auto& th :threads) + // th.join(); + // If you write like, the thread will rush here while it is still working, + // During that time, callback_func() cannot be called and you cannot save. + // Therefore, you need to check the end flag yourself. - // function to determine if all threads have finished - auto threads_done = [&]() - { - return threads_finished == thread_num; - }; + // function to determine if all threads have finished + auto threads_done = [&]() + { + return threads_finished == thread_num; + }; - // Call back if the callback function is set. - auto do_a_callback = [&]() - { - if (callback_func) - callback_func(); - }; + // Call back if the callback function is set. + auto do_a_callback = [&]() + { + if (callback_func) + callback_func(); + }; - for (uint64_t i = 0 ; ; ) - { - // If all threads have finished, exit the loop. - if (threads_done()) - break; + for (uint64_t i = 0 ; ; ) + { + // If all threads have finished, exit the loop. + if (threads_done()) + break; - sleep(1000); + sleep(1000); - // callback_func() is called every callback_seconds. - if (++i == callback_seconds) - { - do_a_callback(); - // Since I am returning from ↑, I reset the counter, so - // no matter how long it takes to save() etc. in do_a_callback() - // The next call will take a certain amount of time. - i = 0; - } - } + // callback_func() is called every callback_seconds. + if (++i == callback_seconds) + { + do_a_callback(); + // Since I am returning from ↑, I reset the counter, so + // no matter how long it takes to save() etc. in do_a_callback() + // The next call will take a certain amount of time. + i = 0; + } + } - // Last save. - std::cout << std::endl << "finalize.."; + // Last save. + std::cout << std::endl << "finalize.."; - // do_a_callback(); - // → It should be saved by the caller, so I feel that it is not necessary here. + // do_a_callback(); + // → It should be saved by the caller, so I feel that it is not necessary here. - // It is possible that the exit code of the thread is running but the exit code of the thread is running, so - // We need to wait for the end with join(). - for (auto& th : threads) - th.join(); + // It is possible that the exit code of the thread is running but the exit code of the thread is running, so + // We need to wait for the end with join(). + for (auto& th : threads) + th.join(); - // The file writing thread etc. are still running only when all threads are finished - // Since the work itself may not have completed, output only that all threads have finished. - std::cout << "all threads are joined." << std::endl; + // The file writing thread etc. are still running only when all threads are finished + // Since the work itself may not have completed, output only that all threads have finished. + std::cout << "all threads are joined." << std::endl; } diff --git a/src/learn/multi_think.h b/src/learn/multi_think.h index e6c436f8..7e541909 100644 --- a/src/learn/multi_think.h +++ b/src/learn/multi_think.h @@ -19,84 +19,84 @@ // Derive and use this class. struct MultiThink { - static constexpr std::uint64_t LOOP_COUNT_FINISHED = std::numeric_limits::max(); + static constexpr std::uint64_t LOOP_COUNT_FINISHED = std::numeric_limits::max(); - MultiThink() : prng{}, loop_count(0) { } + MultiThink() : prng{}, loop_count(0) { } - MultiThink(std::uint64_t seed) : prng(seed), loop_count(0) { } + MultiThink(std::uint64_t seed) : prng(seed), loop_count(0) { } - MultiThink(const std::string& seed) : prng(seed), loop_count(0) { } + MultiThink(const std::string& seed) : prng(seed), loop_count(0) { } - // Call this function from the master thread, each thread will think, - // Return control when the thought ending condition is satisfied. - // Do something else. - // ・It is safe for each thread to call Learner::search(),qsearch() - // Separates the substitution table for each thread. (It will be restored after the end.) - // ・Book is not thread safe when in on the fly mode, so temporarily change this mode. - // Turn it off. - // [Requirements] - // 1) Override thread_worker() - // 2) Set the loop count with set_loop_max() - // 3) set a function to be called back periodically (if necessary) - // callback_func and callback_interval - void go_think(); + // Call this function from the master thread, each thread will think, + // Return control when the thought ending condition is satisfied. + // Do something else. + // ・It is safe for each thread to call Learner::search(),qsearch() + // Separates the substitution table for each thread. (It will be restored after the end.) + // ・Book is not thread safe when in on the fly mode, so temporarily change this mode. + // Turn it off. + // [Requirements] + // 1) Override thread_worker() + // 2) Set the loop count with set_loop_max() + // 3) set a function to be called back periodically (if necessary) + // callback_func and callback_interval + void go_think(); - // If there is something you want to initialize on the derived class side, override this, - // Called when initialization is completed with go_think(). - // It is better to read the fixed trace at that timing. - virtual void init() {} + // If there is something you want to initialize on the derived class side, override this, + // Called when initialization is completed with go_think(). + // It is better to read the fixed trace at that timing. + virtual void init() {} - // A thread worker that is called by creating a thread when you go_think() - // Override and use this. - virtual void thread_worker(size_t thread_id) = 0; + // A thread worker that is called by creating a thread when you go_think() + // Override and use this. + virtual void thread_worker(size_t thread_id) = 0; - // Called back every callback_seconds [seconds] when go_think(). - std::function callback_func; - uint64_t callback_seconds = 600; + // Called back every callback_seconds [seconds] when go_think(). + std::function callback_func; + uint64_t callback_seconds = 600; - // Set the number of times worker processes (calls Search::think()). - void set_loop_max(uint64_t loop_max_) { loop_max = loop_max_; } + // Set the number of times worker processes (calls Search::think()). + void set_loop_max(uint64_t loop_max_) { loop_max = loop_max_; } - // Get the value set by set_loop_max(). - uint64_t get_loop_max() const { return loop_max; } + // Get the value set by set_loop_max(). + uint64_t get_loop_max() const { return loop_max; } - // [ASYNC] Take the value of the loop counter and add the loop counter after taking it out. - // If the loop counter has reached loop_max, return UINT64_MAX. - // If you want to generate a phase, you must call this function at the time of generating the phase, - // Please note that the number of generated phases and the value of the counter will not match. - uint64_t get_next_loop_count() { - std::unique_lock lk(loop_mutex); - if (loop_count >= loop_max) - return LOOP_COUNT_FINISHED; - return loop_count++; - } + // [ASYNC] Take the value of the loop counter and add the loop counter after taking it out. + // If the loop counter has reached loop_max, return UINT64_MAX. + // If you want to generate a phase, you must call this function at the time of generating the phase, + // Please note that the number of generated phases and the value of the counter will not match. + uint64_t get_next_loop_count() { + std::unique_lock lk(loop_mutex); + if (loop_count >= loop_max) + return LOOP_COUNT_FINISHED; + return loop_count++; + } - // [ASYNC] For returning the processed number. Each time it is called, it returns a counter that is incremented. - uint64_t get_done_count() { - std::unique_lock lk(loop_mutex); - return ++done_count; - } + // [ASYNC] For returning the processed number. Each time it is called, it returns a counter that is incremented. + uint64_t get_done_count() { + std::unique_lock lk(loop_mutex); + return ++done_count; + } - // Mutex when worker thread accesses I/O - std::mutex io_mutex; + // Mutex when worker thread accesses I/O + std::mutex io_mutex; protected: - // Random number generator body - AsyncPRNG prng; + // Random number generator body + AsyncPRNG prng; private: - // number of times worker processes (calls Search::think()) - std::atomic loop_max; - // number of times the worker has processed (calls Search::think()) - std::atomic loop_count; - // To return the number of times it has been processed. - std::atomic done_count; + // number of times worker processes (calls Search::think()) + std::atomic loop_max; + // number of times the worker has processed (calls Search::think()) + std::atomic loop_count; + // To return the number of times it has been processed. + std::atomic done_count; - // Mutex when changing the variables in ↑ - std::mutex loop_mutex; + // Mutex when changing the variables in ↑ + std::mutex loop_mutex; - // Thread end flag. - std::atomic threads_finished; + // Thread end flag. + std::atomic threads_finished; }; // Mechanism to process task during idle time. @@ -105,48 +105,48 @@ private: // Convenient to use when you want to write MultiThink thread worker in master-slave method. struct TaskDispatcher { - typedef std::function Task; + typedef std::function Task; - // slave calls this function during idle. - void on_idle(size_t thread_id) - { - Task task; - while ((task = get_task_async()) != nullptr) - task(thread_id); + // slave calls this function during idle. + void on_idle(size_t thread_id) + { + Task task; + while ((task = get_task_async()) != nullptr) + task(thread_id); - sleep(1); - } + sleep(1); + } - // Stack [ASYNC] task. - void push_task_async(Task task) - { - std::unique_lock lk(task_mutex); - tasks.push_back(task); - } + // Stack [ASYNC] task. + void push_task_async(Task task) + { + std::unique_lock lk(task_mutex); + tasks.push_back(task); + } - // Allocate size array elements for task in advance. - void task_reserve(size_t size) - { - tasks.reserve(size); - } + // Allocate size array elements for task in advance. + void task_reserve(size_t size) + { + tasks.reserve(size); + } protected: - // set of tasks - std::vector tasks; + // set of tasks + std::vector tasks; - // Take out one [ASYNC] task. Called from on_idle(). - Task get_task_async() - { - std::unique_lock lk(task_mutex); - if (tasks.size() == 0) - return nullptr; - Task task = *tasks.rbegin(); - tasks.pop_back(); - return task; - } + // Take out one [ASYNC] task. Called from on_idle(). + Task get_task_async() + { + std::unique_lock lk(task_mutex); + if (tasks.size() == 0) + return nullptr; + Task task = *tasks.rbegin(); + tasks.pop_back(); + return task; + } - // a mutex for accessing tasks - std::mutex task_mutex; + // a mutex for accessing tasks + std::mutex task_mutex; }; #endif diff --git a/src/learn/sfen_packer.cpp b/src/learn/sfen_packer.cpp index 2de7efa4..777b5943 100644 --- a/src/learn/sfen_packer.cpp +++ b/src/learn/sfen_packer.cpp @@ -13,378 +13,374 @@ using namespace std; namespace Learner { - // Class that handles bitstream - // useful when doing aspect encoding - struct BitStream - { - // Set the memory to store the data in advance. - // Assume that memory is cleared to 0. - void set_data(std::uint8_t* data_) { data = data_; reset(); } - - // Get the pointer passed in set_data(). - uint8_t* get_data() const { return data; } - - // Get the cursor. - int get_cursor() const { return bit_cursor; } - - // reset the cursor - void reset() { bit_cursor = 0; } - - // Write 1bit to the stream. - // If b is non-zero, write out 1. If 0, write 0. - void write_one_bit(int b) + // Class that handles bitstream + // useful when doing aspect encoding + struct BitStream { - if (b) - data[bit_cursor / 8] |= 1 << (bit_cursor & 7); + // Set the memory to store the data in advance. + // Assume that memory is cleared to 0. + void set_data(std::uint8_t* data_) { data = data_; reset(); } - ++bit_cursor; - } + // Get the pointer passed in set_data(). + uint8_t* get_data() const { return data; } - // Get 1 bit from the stream. - int read_one_bit() + // Get the cursor. + int get_cursor() const { return bit_cursor; } + + // reset the cursor + void reset() { bit_cursor = 0; } + + // Write 1bit to the stream. + // If b is non-zero, write out 1. If 0, write 0. + void write_one_bit(int b) + { + if (b) + data[bit_cursor / 8] |= 1 << (bit_cursor & 7); + + ++bit_cursor; + } + + // Get 1 bit from the stream. + int read_one_bit() + { + int b = (data[bit_cursor / 8] >> (bit_cursor & 7)) & 1; + ++bit_cursor; + + return b; + } + + // write n bits of data + // Data shall be written out from the lower order of d. + void write_n_bit(int d, int n) + { + for (int i = 0; i > (bit_cursor & 7)) & 1; - ++bit_cursor; + void pack(const Position& pos); - return b; - } + // sfen packed by pack() (256bit = 32bytes) + // Or sfen to decode with unpack() + uint8_t *data; // uint8_t[32]; - // write n bits of data - // Data shall be written out from the lower order of d. - void write_n_bit(int d, int n) + BitStream stream; + + // Output the board pieces to stream. + void write_board_piece_to_stream(Piece pc); + + // Read one board piece from stream + Piece read_board_piece_from_stream(); + }; + + + // Huffman coding + // * is simplified from mini encoding to make conversion easier. + // + // Huffman Encoding + // + // Empty xxxxxxx0 + // Pawn xxxxx001 + 1 bit (Color) + // Knight xxxxx011 + 1 bit (Color) + // Bishop xxxxx101 + 1 bit (Color) + // Rook xxxxx111 + 1 bit (Color) + // Queen xxxx1001 + 1 bit (Color) + // + // Worst case: + // - 32 empty squares 32 bits + // - 30 pieces 150 bits + // - 2 kings 12 bits + // - castling rights 4 bits + // - ep square 7 bits + // - rule50 7 bits + // - game ply 16 bits + // - TOTAL 228 bits < 256 bits + + struct HuffmanedPiece { - for (int i = 0; i = RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + { + Piece pc = pos.piece_on(make_square(f, r)); + if (type_of(pc) == KING) + continue; + write_board_piece_to_stream(pc); + } + } + + // TODO(someone): Support chess960. + stream.write_one_bit(pos.can_castle(WHITE_OO)); + stream.write_one_bit(pos.can_castle(WHITE_OOO)); + stream.write_one_bit(pos.can_castle(BLACK_OO)); + stream.write_one_bit(pos.can_castle(BLACK_OOO)); + + if (pos.ep_square() == SQ_NONE) { + stream.write_one_bit(0); + } + else { + stream.write_one_bit(1); + stream.write_n_bit(static_cast(pos.ep_square()), 6); + } + + stream.write_n_bit(pos.state()->rule50, 6); + + const int fm = 1 + (pos.game_ply()-(pos.side_to_move() == BLACK)) / 2; + stream.write_n_bit(fm, 8); + + // Write high bits of half move. This is a fix for the + // limited range of half move counter. + // This is backwards compatibile. + stream.write_n_bit(fm >> 8, 8); + + // Write the highest bit of rule50 at the end. This is a backwards + // compatibile fix for rule50 having only 6 bits stored. + // This bit is just ignored by the old parsers. + stream.write_n_bit(pos.state()->rule50 >> 6, 1); + + assert(stream.get_cursor() <= 256); } - private: - // Next bit position to read/write. - int bit_cursor; - - // data entity - std::uint8_t* data; - }; - - // Class for compressing/decompressing sfen - // sfen can be packed to 256bit (32bytes) by Huffman coding. - // This is proven by mini. The above is Huffman coding. - // - // Internal format = 1-bit turn + 7-bit king position *2 + piece on board (Huffman coding) + hand piece (Huffman coding) - // Side to move (White = 0, Black = 1) (1bit) - // White King Position (6 bits) - // Black King Position (6 bits) - // Huffman Encoding of the board - // Castling availability (1 bit x 4) - // En passant square (1 or 1 + 6 bits) - // Rule 50 (6 bits) - // Game play (8 bits) - // - // TODO(someone): Rename SFEN to FEN. - // - struct SfenPacker - { - void pack(const Position& pos); - - // sfen packed by pack() (256bit = 32bytes) - // Or sfen to decode with unpack() - uint8_t *data; // uint8_t[32]; - - BitStream stream; - // Output the board pieces to stream. - void write_board_piece_to_stream(Piece pc); + void SfenPacker::write_board_piece_to_stream(Piece pc) + { + // piece type + PieceType pr = type_of(pc); + auto c = huffman_table[pr]; + stream.write_n_bit(c.code, c.bits); + + if (pc == NO_PIECE) + return; + + // first and second flag + stream.write_one_bit(color_of(pc)); + } // Read one board piece from stream - Piece read_board_piece_from_stream(); - }; - - - // Huffman coding - // * is simplified from mini encoding to make conversion easier. - // - // Huffman Encoding - // - // Empty xxxxxxx0 - // Pawn xxxxx001 + 1 bit (Color) - // Knight xxxxx011 + 1 bit (Color) - // Bishop xxxxx101 + 1 bit (Color) - // Rook xxxxx111 + 1 bit (Color) - // Queen xxxx1001 + 1 bit (Color) - // - // Worst case: - // - 32 empty squares 32 bits - // - 30 pieces 150 bits - // - 2 kings 12 bits - // - castling rights 4 bits - // - ep square 7 bits - // - rule50 7 bits - // - game ply 16 bits - // - TOTAL 228 bits < 256 bits - - struct HuffmanedPiece - { - int code; // how it will be coded - int bits; // How many bits do you have - }; - - constexpr HuffmanedPiece huffman_table[] = - { - {0b0000,1}, // NO_PIECE - {0b0001,4}, // PAWN - {0b0011,4}, // KNIGHT - {0b0101,4}, // BISHOP - {0b0111,4}, // ROOK - {0b1001,4}, // QUEEN - }; - - // Pack sfen and store in data[32]. - void SfenPacker::pack(const Position& pos) - { - // cout << pos; - - memset(data, 0, 32 /* 256bit */); - stream.set_data(data); - - // turn - // Side to move. - stream.write_one_bit((int)(pos.side_to_move())); - - // 7-bit positions for leading and trailing balls - // White king and black king, 6 bits for each. - for(auto c: Colors) - stream.write_n_bit(pos.king_square(c), 6); - - // Write the pieces on the board other than the kings. - for (Rank r = RANK_8; r >= RANK_1; --r) + Piece SfenPacker::read_board_piece_from_stream() { - for (File f = FILE_A; f <= FILE_H; ++f) - { - Piece pc = pos.piece_on(make_square(f, r)); - if (type_of(pc) == KING) - continue; - write_board_piece_to_stream(pc); - } - } - - // TODO(someone): Support chess960. - stream.write_one_bit(pos.can_castle(WHITE_OO)); - stream.write_one_bit(pos.can_castle(WHITE_OOO)); - stream.write_one_bit(pos.can_castle(BLACK_OO)); - stream.write_one_bit(pos.can_castle(BLACK_OOO)); - - if (pos.ep_square() == SQ_NONE) { - stream.write_one_bit(0); - } - else { - stream.write_one_bit(1); - stream.write_n_bit(static_cast(pos.ep_square()), 6); - } - - stream.write_n_bit(pos.state()->rule50, 6); - - const int fm = 1 + (pos.game_ply()-(pos.side_to_move() == BLACK)) / 2; - stream.write_n_bit(fm, 8); - - // Write high bits of half move. This is a fix for the - // limited range of half move counter. - // This is backwards compatibile. - stream.write_n_bit(fm >> 8, 8); - - // Write the highest bit of rule50 at the end. This is a backwards - // compatibile fix for rule50 having only 6 bits stored. - // This bit is just ignored by the old parsers. - stream.write_n_bit(pos.state()->rule50 >> 6, 1); - - assert(stream.get_cursor() <= 256); - } - - // Output the board pieces to stream. - void SfenPacker::write_board_piece_to_stream(Piece pc) - { - // piece type - PieceType pr = type_of(pc); - auto c = huffman_table[pr]; - stream.write_n_bit(c.code, c.bits); - - if (pc == NO_PIECE) - return; - - // first and second flag - stream.write_one_bit(color_of(pc)); - } - - // Read one board piece from stream - Piece SfenPacker::read_board_piece_from_stream() - { - PieceType pr = NO_PIECE_TYPE; - int code = 0, bits = 0; - while (true) - { - code |= stream.read_one_bit() << bits; - ++bits; - - assert(bits <= 6); - - for (pr = NO_PIECE_TYPE; pr (reinterpret_cast(&sfen))); - - pos.clear(); - std::memset(si, 0, sizeof(StateInfo)); - std::fill_n(&pos.pieceList[0][0], sizeof(pos.pieceList) / sizeof(Square), SQ_NONE); - pos.st = si; - - // Active color - pos.sideToMove = (Color)stream.read_one_bit(); - - pos.pieceList[W_KING][0] = SQUARE_NB; - pos.pieceList[B_KING][0] = SQUARE_NB; - - // First the position of the ball - for (auto c : Colors) - pos.board[stream.read_n_bit(6)] = make_piece(c, KING); - - // Piece placement - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - { - auto sq = make_square(f, r); - - // it seems there are already balls - Piece pc; - if (type_of(pos.board[sq]) != KING) + PieceType pr = NO_PIECE_TYPE; + int code = 0, bits = 0; + while (true) { - assert(pos.board[sq] == NO_PIECE); - pc = packer.read_board_piece_from_stream(); + code |= stream.read_one_bit() << bits; + ++bits; + + assert(bits <= 6); + + for (pr = NO_PIECE_TYPE; pr (reinterpret_cast(&sfen))); + + pos.clear(); + std::memset(si, 0, sizeof(StateInfo)); + std::fill_n(&pos.pieceList[0][0], sizeof(pos.pieceList) / sizeof(Square), SQ_NONE); + pos.st = si; + + // Active color + pos.sideToMove = (Color)stream.read_one_bit(); + + pos.pieceList[W_KING][0] = SQUARE_NB; + pos.pieceList[B_KING][0] = SQUARE_NB; + + // First the position of the ball + for (auto c : Colors) + pos.board[stream.read_n_bit(6)] = make_piece(c, KING); + + // Piece placement + for (Rank r = RANK_8; r >= RANK_1; --r) { - pc = pos.board[sq]; - // put_piece() will catch ASSERT unless you remove it all. - pos.board[sq] = NO_PIECE; + for (File f = FILE_A; f <= FILE_H; ++f) + { + auto sq = make_square(f, r); + + // it seems there are already balls + Piece pc; + if (type_of(pos.board[sq]) != KING) + { + assert(pos.board[sq] == NO_PIECE); + pc = packer.read_board_piece_from_stream(); + } + else + { + pc = pos.board[sq]; + // put_piece() will catch ASSERT unless you remove it all. + pos.board[sq] = NO_PIECE; + } + + // There may be no pieces, so skip in that case. + if (pc == NO_PIECE) + continue; + + pos.put_piece(Piece(pc), sq); + + if (stream.get_cursor()> 256) + return 1; + } } - // There may be no pieces, so skip in that case. - if (pc == NO_PIECE) - continue; + // Castling availability. + // TODO(someone): Support chess960. + pos.st->castlingRights = 0; + if (stream.read_one_bit()) { + Square rsq; + for (rsq = relative_square(WHITE, SQ_H1); pos.piece_on(rsq) != W_ROOK; --rsq) {} + pos.set_castling_right(WHITE, rsq); + } + if (stream.read_one_bit()) { + Square rsq; + for (rsq = relative_square(WHITE, SQ_A1); pos.piece_on(rsq) != W_ROOK; ++rsq) {} + pos.set_castling_right(WHITE, rsq); + } + if (stream.read_one_bit()) { + Square rsq; + for (rsq = relative_square(BLACK, SQ_H1); pos.piece_on(rsq) != B_ROOK; --rsq) {} + pos.set_castling_right(BLACK, rsq); + } + if (stream.read_one_bit()) { + Square rsq; + for (rsq = relative_square(BLACK, SQ_A1); pos.piece_on(rsq) != B_ROOK; ++rsq) {} + pos.set_castling_right(BLACK, rsq); + } - pos.put_piece(Piece(pc), sq); + // En passant square. Ignore if no pawn capture is possible + if (stream.read_one_bit()) { + Square ep_square = static_cast(stream.read_n_bit(6)); + pos.st->epSquare = ep_square; - if (stream.get_cursor()> 256) - return 1; + if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN)) + || !(pos.pieces(~pos.sideToMove, PAWN) & (pos.st->epSquare + pawn_push(~pos.sideToMove)))) + pos.st->epSquare = SQ_NONE; + } + else { + pos.st->epSquare = SQ_NONE; + } - //assert(stream.get_cursor() <= 256); - } + // Halfmove clock + pos.st->rule50 = stream.read_n_bit(6); + + // Fullmove number + pos.gamePly = stream.read_n_bit(8); + + // Read the highest bit of rule50. This was added as a fix for rule50 + // counter having only 6 bits stored. + // In older entries this will just be a zero bit. + pos.gamePly |= stream.read_n_bit(8) << 8; + + // Read the highest bit of rule50. This was added as a fix for rule50 + // counter having only 6 bits stored. + // In older entries this will just be a zero bit. + pos.st->rule50 |= stream.read_n_bit(1) << 6; + + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + pos.gamePly = std::max(2 * (pos.gamePly - 1), 0) + (pos.sideToMove == BLACK); + + assert(stream.get_cursor() <= 256); + + pos.chess960 = false; + pos.thisThread = th; + pos.set_state(pos.st); + + assert(pos.pos_is_ok()); + + return 0; } - // Castling availability. - // TODO(someone): Support chess960. - pos.st->castlingRights = 0; - if (stream.read_one_bit()) { - Square rsq; - for (rsq = relative_square(WHITE, SQ_H1); pos.piece_on(rsq) != W_ROOK; --rsq) {} - pos.set_castling_right(WHITE, rsq); + PackedSfen sfen_pack(Position& pos) + { + PackedSfen sfen; + + SfenPacker sp; + sp.data = (uint8_t*)&sfen; + sp.pack(pos); + + return sfen; } - if (stream.read_one_bit()) { - Square rsq; - for (rsq = relative_square(WHITE, SQ_A1); pos.piece_on(rsq) != W_ROOK; ++rsq) {} - pos.set_castling_right(WHITE, rsq); - } - if (stream.read_one_bit()) { - Square rsq; - for (rsq = relative_square(BLACK, SQ_H1); pos.piece_on(rsq) != B_ROOK; --rsq) {} - pos.set_castling_right(BLACK, rsq); - } - if (stream.read_one_bit()) { - Square rsq; - for (rsq = relative_square(BLACK, SQ_A1); pos.piece_on(rsq) != B_ROOK; ++rsq) {} - pos.set_castling_right(BLACK, rsq); - } - - // En passant square. Ignore if no pawn capture is possible - if (stream.read_one_bit()) { - Square ep_square = static_cast(stream.read_n_bit(6)); - pos.st->epSquare = ep_square; - - if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN)) - || !(pos.pieces(~pos.sideToMove, PAWN) & (pos.st->epSquare + pawn_push(~pos.sideToMove)))) - pos.st->epSquare = SQ_NONE; - } - else { - pos.st->epSquare = SQ_NONE; - } - - // Halfmove clock - pos.st->rule50 = stream.read_n_bit(6); - - // Fullmove number - pos.gamePly = stream.read_n_bit(8); - - // Read the highest bit of rule50. This was added as a fix for rule50 - // counter having only 6 bits stored. - // In older entries this will just be a zero bit. - pos.gamePly |= stream.read_n_bit(8) << 8; - - // Read the highest bit of rule50. This was added as a fix for rule50 - // counter having only 6 bits stored. - // In older entries this will just be a zero bit. - pos.st->rule50 |= stream.read_n_bit(1) << 6; - - // Convert from fullmove starting from 1 to gamePly starting from 0, - // handle also common incorrect FEN with fullmove = 0. - pos.gamePly = std::max(2 * (pos.gamePly - 1), 0) + (pos.sideToMove == BLACK); - - assert(stream.get_cursor() <= 256); - - pos.chess960 = false; - pos.thisThread = th; - pos.set_state(pos.st); - - assert(pos.pos_is_ok()); - - return 0; - } - - PackedSfen sfen_pack(Position& pos) - { - PackedSfen sfen; - - SfenPacker sp; - sp.data = (uint8_t*)&sfen; - sp.pack(pos); - - return sfen; - } }