stellar

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

commit 8f954a0404ce9be9d8d5cb9bb59257b6cb5b83f3
parent 227d847754d734b8d663443e463161e4857fac6d
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Sat, 29 Jul 2023 21:13:58 +0200

Restructure project for better modularity

Diffstat:
MCMakeLists.txt | 15+--------------
Dinclude/attack.h | 17-----------------
Dinclude/utils.h | 86-------------------------------------------------------------------------------
Asrc/CMakeLists.txt | 9+++++++++
Dsrc/attack.c | 366-------------------------------------------------------------------------------
Asrc/attack/CMakeLists.txt | 7+++++++
Asrc/attack/attack.c | 371+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/board/CMakeLists.txt | 7+++++++
Rsrc/board.c -> src/board/board.c | 0
Dsrc/engine.c | 442-------------------------------------------------------------------------------
Asrc/engine/CMakeLists.txt | 17+++++++++++++++++
Asrc/engine/engine.c | 425+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/include/attack.h | 18++++++++++++++++++
Rinclude/board.h -> src/include/board.h | 0
Rinclude/moves.h -> src/include/moves.h | 0
Rinclude/perft.h -> src/include/perft.h | 0
Rinclude/score.h -> src/include/score.h | 0
Asrc/include/utils.h | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/moves/CMakeLists.txt | 7+++++++
Rsrc/moves.c -> src/moves/moves.c | 0
Dsrc/perft.c | 127-------------------------------------------------------------------------------
Asrc/perft/CMakeLists.txt | 17+++++++++++++++++
Asrc/perft/perft.c | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/score/CMakeLists.txt | 7+++++++
Rsrc/score.c -> src/score/score.c | 0
Asrc/utils/CMakeLists.txt | 7+++++++
Rsrc/utils.c -> src/utils/utils.c | 0
27 files changed, 1128 insertions(+), 1052 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -13,17 +13,4 @@ set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) - -file(GLOB sources "src/*.c") -add_executable(engine ${sources}) - -target_link_libraries(engine "cul") - -set_target_properties(engine PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} -) - -target_include_directories(engine - PRIVATE "${PROJECT_SOURCE_DIR}/include" -) +add_subdirectory(src) diff --git a/include/attack.h b/include/attack.h @@ -1,17 +0,0 @@ -#ifndef ATTACK_H -#define ATTACK_H - -#include "utils.h" - -void init_leapers_attacks(void); -void init_sliders_attacks(void); - -U64 get_wpawn_attacks(Square square, U64 occupancy); -U64 get_bpawn_attacks(Square square, U64 occupancy); -U64 get_knight_attacks(Square square, U64 occupancy); -U64 get_king_attacks(Square square, U64 occupancy); -U64 get_bishop_attacks(Square square, U64 occupancy); -U64 get_rook_attacks(Square square, U64 occupancy); -U64 get_queen_attacks(Square square, U64 occupancy); - -#endif diff --git a/include/utils.h b/include/utils.h @@ -1,86 +0,0 @@ -#ifndef __UTILS_H -#define __UTILS_H - -// useful macros -#define MAX(a, b) ((a > b) ? a : b) -#define MIN(a, b) ((a < b) ? a : b) - -// define number types -typedef unsigned long long U64; // define bitboard data type -#define C64(constantU64) constantU64##ULL // define shorthand for constants - -typedef unsigned int U32; -#define C32(constantU32) constantU32##U - -// useful bit patterns -extern const U64 universe; -extern const U64 notAFile; -extern const U64 notHFile; - -// useful bit functions -#define bit_get(bitboard, square) (((bitboard) >> (square)) & C64(1)) -#define bit_set(bitboard, square) ((bitboard) |= C64(1) << (square)) -#define bit_pop(bitboard, square) ((bitboard) &= ~(C64(1) << (square))) -int bit_count(U64 bitboard); -int bit_lsb_index(U64 bitboard); - -#define bitboard_for_each_bit(var, bb) \ - for (var = bit_lsb_index(bb); bb; bit_pop(bb, var), var = bit_lsb_index(bb)) - -void bitboard_print(U64 bitboard); - -// squares -// clang-format off -enum enumSquare { - a1, b1, c1, d1, e1, f1, g1, h1, - a2, b2, c2, d2, e2, f2, g2, h2, - a3, b3, c3, d3, e3, f3, g3, h3, - a4, b4, c4, d4, e4, f4, g4, h4, - a5, b5, c5, d5, e5, f5, g5, h5, - a6, b6, c6, d6, e6, f6, g6, h6, - a7, b7, c7, d7, e7, f7, g7, h7, - a8, b8, c8, d8, e8, f8, g8, h8, no_sq -}; -// clang-format on -typedef enum enumSquare Square; - -extern const char *square_to_coordinates[]; -Square coordinates_to_square(char *cord); - -// board moving -typedef U64 (*direction_f)(U64); -U64 soutOne(U64 b); -U64 nortOne(U64 b); -U64 eastOne(U64 b); -U64 westOne(U64 b); -U64 soEaOne(U64 b); -U64 soWeOne(U64 b); -U64 noEaOne(U64 b); -U64 noWeOne(U64 b); - -// board rotation -U64 rotateLeft(U64 x, int s); -U64 rotateRight(U64 x, int s); - -// enum types for color and piece type -enum enumColor { - WHITE = 0, - BLACK -}; -enum enumPiece { - PAWN = 0, - KNIGHT, - BISHOP, - ROOK, - QUEEN, - KING -}; - -typedef enum enumColor eColor; -typedef enum enumPiece ePiece; - -int get_time_ms(void); - -typedef U64 (*attack_f)(Square square, U64 occupancy); - -#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB files "*") +foreach(file in ${files}) + if(IS_DIRECTORY "${file}" AND EXISTS "${file}/CMakeLists.txt") + get_filename_component(mod ${file} NAME) + message(STATUS "Found target ${mod}") + add_subdirectory(${mod}) + endif() +endforeach() + diff --git a/src/attack.c b/src/attack.c @@ -1,366 +0,0 @@ -#include "string.h" - -#include "attack.h" -#include "utils.h" - -#define UNUSED(x) (void)(x) - -// Magic constants - -// clang-format off -const int bishop_relevant_bits[64] = { - 6, 5, 5, 5, 5, 5, 5, 6, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 7, 7, 7, 7, 5, 5, - 5, 5, 7, 9, 9, 7, 5, 5, - 5, 5, 7, 9, 9, 7, 5, 5, - 5, 5, 7, 7, 7, 7, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 6, 5, 5, 5, 5, 5, 5, 6, -}; - -const U64 bishop_magic_numbers[64] = { - C64(0x40040844404084), C64(0x2004208a004208), C64(0x10190041080202), - C64(0x108060845042010), C64(0x581104180800210), C64(0x2112080446200010), - C64(0x1080820820060210), C64(0x3c0808410220200), C64(0x4050404440404), - C64(0x21001420088), C64(0x24d0080801082102), C64(0x1020a0a020400), - C64(0x40308200402), C64(0x4011002100800), C64(0x401484104104005), - C64(0x801010402020200), C64(0x400210c3880100), C64(0x404022024108200), - C64(0x810018200204102), C64(0x4002801a02003), C64(0x85040820080400), - C64(0x810102c808880400), C64(0xe900410884800), C64(0x8002020480840102), - C64(0x220200865090201), C64(0x2010100a02021202), C64(0x152048408022401), - C64(0x20080002081110), C64(0x4001001021004000), C64(0x800040400a011002), - C64(0xe4004081011002), C64(0x1c004001012080), C64(0x8004200962a00220), - C64(0x8422100208500202), C64(0x2000402200300c08), C64(0x8646020080080080), - C64(0x80020a0200100808), C64(0x2010004880111000), C64(0x623000a080011400), - C64(0x42008c0340209202), C64(0x209188240001000), C64(0x400408a884001800), - C64(0x110400a6080400), C64(0x1840060a44020800), C64(0x90080104000041), - C64(0x201011000808101), C64(0x1a2208080504f080), C64(0x8012020600211212), - C64(0x500861011240000), C64(0x180806108200800), C64(0x4000020e01040044), - C64(0x300000261044000a), C64(0x802241102020002), C64(0x20906061210001), - C64(0x5a84841004010310), C64(0x4010801011c04), C64(0xa010109502200), - C64(0x4a02012000), C64(0x500201010098b028), C64(0x8040002811040900), - C64(0x28000010020204), C64(0x6000020202d0240), C64(0x8918844842082200), - C64(0x4010011029020020), -}; - -const int rook_relevant_bits[64] = { - 12, 11, 11, 11, 11, 11, 11, 12, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, - 12, 11, 11, 11, 11, 11, 11, 12, -}; - -const U64 rook_magic_numbers[64] = { - C64(0x8a80104000800020), C64(0x140002000100040), C64(0x2801880a0017001), - C64(0x100081001000420), C64(0x200020010080420), C64(0x3001c0002010008), - C64(0x8480008002000100), C64(0x2080088004402900), C64(0x800098204000), - C64(0x2024401000200040), C64(0x100802000801000), C64(0x120800800801000), - C64(0x208808088000400), C64(0x2802200800400), C64(0x2200800100020080), - C64(0x801000060821100), C64(0x80044006422000), C64(0x100808020004000), - C64(0x12108a0010204200), C64(0x140848010000802), C64(0x481828014002800), - C64(0x8094004002004100), C64(0x4010040010010802), C64(0x20008806104), - C64(0x100400080208000), C64(0x2040002120081000), C64(0x21200680100081), - C64(0x20100080080080), C64(0x2000a00200410), C64(0x20080800400), - C64(0x80088400100102), C64(0x80004600042881), C64(0x4040008040800020), - C64(0x440003000200801), C64(0x4200011004500), C64(0x188020010100100), - C64(0x14800401802800), C64(0x2080040080800200), C64(0x124080204001001), - C64(0x200046502000484), C64(0x480400080088020), C64(0x1000422010034000), - C64(0x30200100110040), C64(0x100021010009), C64(0x2002080100110004), - C64(0x202008004008002), C64(0x20020004010100), C64(0x2048440040820001), - C64(0x101002200408200), C64(0x40802000401080), C64(0x4008142004410100), - C64(0x2060820c0120200), C64(0x1001004080100), C64(0x20c020080040080), - C64(0x2935610830022400), C64(0x44440041009200), C64(0x280001040802101), - C64(0x2100190040002085), C64(0x80c0084100102001), C64(0x4024081001000421), - C64(0x20030a0244872), C64(0x12001008414402), C64(0x2006104900a0804), - C64(0x1004081002402), -}; - - -// Utility functions - -int hash(U64 key, U64 magic, int relevant_bits) { - return (key * magic) >> (64 - relevant_bits); -} - -// pseudo random numbers - -U32 state = C32(1804289383); - -U32 get_random_U32_number() { - U32 number = state; - - number ^= number << 13; - number ^= number >> 17; - number ^= number << 5; - - return state = number; -} - -U64 get_random_U64_number() { - U64 n1, n2, n3, n4; - - n1 = (U64)(get_random_U32_number()) & C64(0xFFFF); - n2 = (U64)(get_random_U32_number()) & C64(0xFFFF); - n3 = (U64)(get_random_U32_number()) & C64(0xFFFF); - n4 = (U64)(get_random_U32_number()) & C64(0xFFFF); - - return n1 | (n2 << 16) | (n3 << 32) | (n4 << 48); -} - -// pawn attack table [side][square] -U64 pawn_attacks[2][64]; - -// knight attack table [square] -U64 knight_attacks[64]; - -// king attack table [square] -U64 king_attacks[64]; - -// bishop attack mask -U64 bishop_masks[64]; - -// rook attack mask -U64 rook_masks[64]; - -// bishop attack table [square][occupancies] -U64 bishop_attacks[64][512]; // 256 K - -// rook attack table [square][occupancies] -U64 rook_attacks[64][4096]; // 2048K - -U64 get_wpawn_attacks(Square square, U64 occupancy){ - UNUSED(occupancy); - return pawn_attacks[WHITE][square]; -} - -U64 get_bpawn_attacks(Square square, U64 occupancy){ - UNUSED(occupancy); - return pawn_attacks[BLACK][square]; -} - -U64 get_knight_attacks(Square square, U64 occupancy){ - UNUSED(occupancy); - return knight_attacks[square]; -} - -U64 get_king_attacks(Square square, U64 occupancy){ - UNUSED(occupancy); - return king_attacks[square]; -} - -U64 get_bishop_attacks(Square square, U64 occupancy){ - occupancy &= bishop_masks[square]; - occupancy = hash(occupancy, bishop_magic_numbers[square], - bishop_relevant_bits[square]); - return bishop_attacks[square][occupancy]; -} - -U64 get_rook_attacks(Square square, U64 occupancy){ - occupancy &= rook_masks[square]; - occupancy = - hash(occupancy, rook_magic_numbers[square], rook_relevant_bits[square]); - return rook_attacks[square][occupancy]; -} - -U64 get_queen_attacks(Square square, U64 occupancy){ - return (get_bishop_attacks(square, occupancy) | - get_rook_attacks(square, occupancy)); -} - - -// generate pawn attack -U64 mask_pawn_attacks(int side, Square square) { - U64 bitboard = C64(0); - - bit_set(bitboard, square); - if (side == WHITE) - return noWeOne(bitboard) | noEaOne(bitboard); - else - return soWeOne(bitboard) | soEaOne(bitboard); -} - -U64 mask_knight_attacks(Square square) { - U64 bitboard = C64(0), attacks = C64(0), tmp; - - bit_set(bitboard, square); - tmp = nortOne(nortOne(bitboard)); - attacks |= westOne(tmp) | eastOne(tmp); - tmp = soutOne(soutOne(bitboard)); - attacks |= westOne(tmp) | eastOne(tmp); - tmp = westOne(westOne(bitboard)); - attacks |= soutOne(tmp) | nortOne(tmp); - tmp = eastOne(eastOne(bitboard)); - attacks |= soutOne(tmp) | nortOne(tmp); - - return attacks; -} - -U64 mask_king_attacks(Square square) { - U64 bitboard = C64(0), attacks = C64(0); - - bit_set(bitboard, square); - attacks |= westOne(bitboard) | eastOne(bitboard); - attacks |= soutOne(bitboard) | nortOne(bitboard); - attacks |= soutOne(bitboard) | nortOne(bitboard); - attacks |= soEaOne(bitboard) | noEaOne(bitboard); - attacks |= soWeOne(bitboard) | noWeOne(bitboard); - - return attacks; -} - -U64 mask_slide_attacks(Square square, U64 block, const direction_f dir[4], - int len[4]) { - U64 bitboard = C64(0), attacks = C64(0), tmp; - int i, j; - - bit_set(bitboard, square); - for (i = 0; i < 4; i++) { - for (j = 0, tmp = bitboard; j < len[i]; j++) { - attacks |= tmp = (dir[i])(tmp); - if (tmp & block) - break; - } - } - return attacks; -} - -const direction_f bishop_direction[4] = {noEaOne, noWeOne, soEaOne, soWeOne}; -const direction_f rook_direction[4] = {westOne, soutOne, eastOne, nortOne}; - -U64 mask_bishop_attacks(Square square) { - int tr = square / 8, tf = square % 8; - int len[4] = {MIN(7 - tf, 7 - tr) - 1, MIN(tf, 7 - tr) - 1, - MIN(7 - tf, tr) - 1, MIN(tf, tr) - 1}; - return mask_slide_attacks(square, C64(0), bishop_direction, len); -} - -U64 mask_rook_attacks(Square square) { - int tr = square / 8, tf = square % 8; - int len[4] = {tf - 1, tr - 1, 6 - tf, 6 - tr}; - - return mask_slide_attacks(square, C64(0), rook_direction, len); -} - -U64 bishop_attacks_on_the_fly(Square square, U64 block) { - int tr = square / 8, tf = square % 8; - int len[4] = {MIN(7 - tf, 7 - tr), MIN(tf, 7 - tr), MIN(7 - tf, tr), - MIN(tf, tr)}; - - return mask_slide_attacks(square, block, bishop_direction, len); -} - -U64 rook_attacks_on_the_fly(Square square, U64 block) { - int tr = square / 8, tf = square % 8; - int len[4] = {tf, tr, 7 - tf, 7 - tr}; - - return mask_slide_attacks(square, block, rook_direction, len); -} - -void init_leapers_attacks(void) { - for (Square square = 0; square < 64; square++) { - pawn_attacks[WHITE][square] = mask_pawn_attacks(WHITE, square); - pawn_attacks[BLACK][square] = mask_pawn_attacks(BLACK, square); - knight_attacks[square] = mask_knight_attacks(square); - king_attacks[square] = mask_king_attacks(square); - } -} - -U64 set_occupancy(int index, int bits_in_mask, U64 attack_mask) { - U64 occupancy = C64(0); - - for (int count = 0; count < bits_in_mask; count++) { - Square square = bit_lsb_index(attack_mask); - bit_pop(attack_mask, square); - - if (index & (1 << count)) - bit_set(occupancy, square); - } - - return occupancy; -} - -void init_sliders_attacks_internal(int bishop) { - for (Square square = 0; square < 64; square++) { - U64 attack_mask; - - if (bishop) { - bishop_masks[square] = mask_bishop_attacks(square); - attack_mask = bishop_masks[square]; - } else { - rook_masks[square] = mask_rook_attacks(square); - attack_mask = rook_masks[square]; - } - - int relevant_bits = bit_count(attack_mask); - int occupancy_indicies = 1 << relevant_bits; - - for (int index = 0; index < occupancy_indicies; index++) { - U64 occupancy = set_occupancy(index, relevant_bits, attack_mask); - if (bishop) { - int magic_index = (occupancy * bishop_magic_numbers[square]) >> - (64 - bishop_relevant_bits[square]); - bishop_attacks[square][magic_index] = - bishop_attacks_on_the_fly(square, occupancy); - } else { - int magic_index = hash(occupancy, rook_magic_numbers[square], - rook_relevant_bits[square]); - rook_attacks[square][magic_index] = - rook_attacks_on_the_fly(square, occupancy); - } - } - } -} - -void init_sliders_attacks(void) { - init_sliders_attacks_internal(0); - init_sliders_attacks_internal(1); -} - -// magic numbers - -U64 generate_magic_number() { - return get_random_U64_number() & get_random_U64_number() & - get_random_U64_number(); -} - -U64 find_magic_number(Square square, int relevant_bits, int bishop) { - U64 occupancies[4096], attacks[4096], used_attacks[4096]; - U64 attack_mask = - bishop ? mask_bishop_attacks(square) : mask_rook_attacks(square); - int occupancy_indicies = 1 << relevant_bits; - - for (int index = 0; index < occupancy_indicies; index++) { - occupancies[index] = set_occupancy(index, relevant_bits, attack_mask); - attacks[index] = bishop - ? bishop_attacks_on_the_fly(square, occupancies[index]) - : rook_attacks_on_the_fly(square, occupancies[index]); - } - - for (int random_count = 0; random_count < 100000000; random_count++) { - U64 magic_number = generate_magic_number(); - if (bit_count((attack_mask * magic_number) & C64(0xFF00000000000000)) < 6) - continue; - - memset(used_attacks, C64(0), sizeof(used_attacks)); - int index, fail; - - for (index = 0, fail = 0; !fail && index < occupancy_indicies; index++) { - int magic_index = hash(occupancies[index], magic_number, relevant_bits); - - if (used_attacks[magic_index] == C64(0)) - used_attacks[magic_index] = attacks[index]; - else if (used_attacks[magic_index] != attacks[index]) - fail = 1; - } - - if (!fail) - return magic_number; - } - - return C64(0); -} diff --git a/src/attack/CMakeLists.txt b/src/attack/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(attack OBJECT + attack.c +) + +target_include_directories(attack + PUBLIC "${PROJECT_SOURCE_DIR}/src/include" +) diff --git a/src/attack/attack.c b/src/attack/attack.c @@ -0,0 +1,371 @@ +#include "string.h" + +#include "attack.h" +#include "utils.h" + +#define UNUSED(x) (void)(x) + +// Magic constants + +// clang-format off +const int bishop_relevant_bits[64] = { + 6, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 7, 7, 7, 7, 5, 5, + 5, 5, 7, 9, 9, 7, 5, 5, + 5, 5, 7, 9, 9, 7, 5, 5, + 5, 5, 7, 7, 7, 7, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 5, 5, 5, 5, 5, 5, 6, +}; + +const U64 bishop_magic_numbers[64] = { + C64(0x40040844404084), C64(0x2004208a004208), C64(0x10190041080202), + C64(0x108060845042010), C64(0x581104180800210), C64(0x2112080446200010), + C64(0x1080820820060210), C64(0x3c0808410220200), C64(0x4050404440404), + C64(0x21001420088), C64(0x24d0080801082102), C64(0x1020a0a020400), + C64(0x40308200402), C64(0x4011002100800), C64(0x401484104104005), + C64(0x801010402020200), C64(0x400210c3880100), C64(0x404022024108200), + C64(0x810018200204102), C64(0x4002801a02003), C64(0x85040820080400), + C64(0x810102c808880400), C64(0xe900410884800), C64(0x8002020480840102), + C64(0x220200865090201), C64(0x2010100a02021202), C64(0x152048408022401), + C64(0x20080002081110), C64(0x4001001021004000), C64(0x800040400a011002), + C64(0xe4004081011002), C64(0x1c004001012080), C64(0x8004200962a00220), + C64(0x8422100208500202), C64(0x2000402200300c08), C64(0x8646020080080080), + C64(0x80020a0200100808), C64(0x2010004880111000), C64(0x623000a080011400), + C64(0x42008c0340209202), C64(0x209188240001000), C64(0x400408a884001800), + C64(0x110400a6080400), C64(0x1840060a44020800), C64(0x90080104000041), + C64(0x201011000808101), C64(0x1a2208080504f080), C64(0x8012020600211212), + C64(0x500861011240000), C64(0x180806108200800), C64(0x4000020e01040044), + C64(0x300000261044000a), C64(0x802241102020002), C64(0x20906061210001), + C64(0x5a84841004010310), C64(0x4010801011c04), C64(0xa010109502200), + C64(0x4a02012000), C64(0x500201010098b028), C64(0x8040002811040900), + C64(0x28000010020204), C64(0x6000020202d0240), C64(0x8918844842082200), + C64(0x4010011029020020), +}; + +const int rook_relevant_bits[64] = { + 12, 11, 11, 11, 11, 11, 11, 12, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 12, 11, 11, 11, 11, 11, 11, 12, +}; + +const U64 rook_magic_numbers[64] = { + C64(0x8a80104000800020), C64(0x140002000100040), C64(0x2801880a0017001), + C64(0x100081001000420), C64(0x200020010080420), C64(0x3001c0002010008), + C64(0x8480008002000100), C64(0x2080088004402900), C64(0x800098204000), + C64(0x2024401000200040), C64(0x100802000801000), C64(0x120800800801000), + C64(0x208808088000400), C64(0x2802200800400), C64(0x2200800100020080), + C64(0x801000060821100), C64(0x80044006422000), C64(0x100808020004000), + C64(0x12108a0010204200), C64(0x140848010000802), C64(0x481828014002800), + C64(0x8094004002004100), C64(0x4010040010010802), C64(0x20008806104), + C64(0x100400080208000), C64(0x2040002120081000), C64(0x21200680100081), + C64(0x20100080080080), C64(0x2000a00200410), C64(0x20080800400), + C64(0x80088400100102), C64(0x80004600042881), C64(0x4040008040800020), + C64(0x440003000200801), C64(0x4200011004500), C64(0x188020010100100), + C64(0x14800401802800), C64(0x2080040080800200), C64(0x124080204001001), + C64(0x200046502000484), C64(0x480400080088020), C64(0x1000422010034000), + C64(0x30200100110040), C64(0x100021010009), C64(0x2002080100110004), + C64(0x202008004008002), C64(0x20020004010100), C64(0x2048440040820001), + C64(0x101002200408200), C64(0x40802000401080), C64(0x4008142004410100), + C64(0x2060820c0120200), C64(0x1001004080100), C64(0x20c020080040080), + C64(0x2935610830022400), C64(0x44440041009200), C64(0x280001040802101), + C64(0x2100190040002085), C64(0x80c0084100102001), C64(0x4024081001000421), + C64(0x20030a0244872), C64(0x12001008414402), C64(0x2006104900a0804), + C64(0x1004081002402), +}; + + +// Utility functions + +int hash(U64 key, U64 magic, int relevant_bits) { + return (key * magic) >> (64 - relevant_bits); +} + +// pseudo random numbers + +U32 state = C32(1804289383); + +U32 get_random_U32_number() { + U32 number = state; + + number ^= number << 13; + number ^= number >> 17; + number ^= number << 5; + + return state = number; +} + +U64 get_random_U64_number() { + U64 n1, n2, n3, n4; + + n1 = (U64)(get_random_U32_number()) & C64(0xFFFF); + n2 = (U64)(get_random_U32_number()) & C64(0xFFFF); + n3 = (U64)(get_random_U32_number()) & C64(0xFFFF); + n4 = (U64)(get_random_U32_number()) & C64(0xFFFF); + + return n1 | (n2 << 16) | (n3 << 32) | (n4 << 48); +} + +// pawn attack table [side][square] +U64 pawn_attacks[2][64]; + +// knight attack table [square] +U64 knight_attacks[64]; + +// king attack table [square] +U64 king_attacks[64]; + +// bishop attack mask +U64 bishop_masks[64]; + +// rook attack mask +U64 rook_masks[64]; + +// bishop attack table [square][occupancies] +U64 bishop_attacks[64][512]; // 256 K + +// rook attack table [square][occupancies] +U64 rook_attacks[64][4096]; // 2048K + +U64 get_wpawn_attacks(Square square, U64 occupancy){ + UNUSED(occupancy); + return pawn_attacks[WHITE][square]; +} + +U64 get_bpawn_attacks(Square square, U64 occupancy){ + UNUSED(occupancy); + return pawn_attacks[BLACK][square]; +} + +U64 get_knight_attacks(Square square, U64 occupancy){ + UNUSED(occupancy); + return knight_attacks[square]; +} + +U64 get_king_attacks(Square square, U64 occupancy){ + UNUSED(occupancy); + return king_attacks[square]; +} + +U64 get_bishop_attacks(Square square, U64 occupancy){ + occupancy &= bishop_masks[square]; + occupancy = hash(occupancy, bishop_magic_numbers[square], + bishop_relevant_bits[square]); + return bishop_attacks[square][occupancy]; +} + +U64 get_rook_attacks(Square square, U64 occupancy){ + occupancy &= rook_masks[square]; + occupancy = + hash(occupancy, rook_magic_numbers[square], rook_relevant_bits[square]); + return rook_attacks[square][occupancy]; +} + +U64 get_queen_attacks(Square square, U64 occupancy){ + return (get_bishop_attacks(square, occupancy) | + get_rook_attacks(square, occupancy)); +} + + +// generate pawn attack +U64 mask_pawn_attacks(int side, Square square) { + U64 bitboard = C64(0); + + bit_set(bitboard, square); + if (side == WHITE) + return noWeOne(bitboard) | noEaOne(bitboard); + else + return soWeOne(bitboard) | soEaOne(bitboard); +} + +U64 mask_knight_attacks(Square square) { + U64 bitboard = C64(0), attacks = C64(0), tmp; + + bit_set(bitboard, square); + tmp = nortOne(nortOne(bitboard)); + attacks |= westOne(tmp) | eastOne(tmp); + tmp = soutOne(soutOne(bitboard)); + attacks |= westOne(tmp) | eastOne(tmp); + tmp = westOne(westOne(bitboard)); + attacks |= soutOne(tmp) | nortOne(tmp); + tmp = eastOne(eastOne(bitboard)); + attacks |= soutOne(tmp) | nortOne(tmp); + + return attacks; +} + +U64 mask_king_attacks(Square square) { + U64 bitboard = C64(0), attacks = C64(0); + + bit_set(bitboard, square); + attacks |= westOne(bitboard) | eastOne(bitboard); + attacks |= soutOne(bitboard) | nortOne(bitboard); + attacks |= soutOne(bitboard) | nortOne(bitboard); + attacks |= soEaOne(bitboard) | noEaOne(bitboard); + attacks |= soWeOne(bitboard) | noWeOne(bitboard); + + return attacks; +} + +U64 mask_slide_attacks(Square square, U64 block, const direction_f dir[4], + int len[4]) { + U64 bitboard = C64(0), attacks = C64(0), tmp; + int i, j; + + bit_set(bitboard, square); + for (i = 0; i < 4; i++) { + for (j = 0, tmp = bitboard; j < len[i]; j++) { + attacks |= tmp = (dir[i])(tmp); + if (tmp & block) + break; + } + } + return attacks; +} + +const direction_f bishop_direction[4] = {noEaOne, noWeOne, soEaOne, soWeOne}; +const direction_f rook_direction[4] = {westOne, soutOne, eastOne, nortOne}; + +U64 mask_bishop_attacks(Square square) { + int tr = square / 8, tf = square % 8; + int len[4] = {MIN(7 - tf, 7 - tr) - 1, MIN(tf, 7 - tr) - 1, + MIN(7 - tf, tr) - 1, MIN(tf, tr) - 1}; + return mask_slide_attacks(square, C64(0), bishop_direction, len); +} + +U64 mask_rook_attacks(Square square) { + int tr = square / 8, tf = square % 8; + int len[4] = {tf - 1, tr - 1, 6 - tf, 6 - tr}; + + return mask_slide_attacks(square, C64(0), rook_direction, len); +} + +U64 bishop_attacks_on_the_fly(Square square, U64 block) { + int tr = square / 8, tf = square % 8; + int len[4] = {MIN(7 - tf, 7 - tr), MIN(tf, 7 - tr), MIN(7 - tf, tr), + MIN(tf, tr)}; + + return mask_slide_attacks(square, block, bishop_direction, len); +} + +U64 rook_attacks_on_the_fly(Square square, U64 block) { + int tr = square / 8, tf = square % 8; + int len[4] = {tf, tr, 7 - tf, 7 - tr}; + + return mask_slide_attacks(square, block, rook_direction, len); +} + +void init_leapers_attacks(void) { + for (Square square = 0; square < 64; square++) { + pawn_attacks[WHITE][square] = mask_pawn_attacks(WHITE, square); + pawn_attacks[BLACK][square] = mask_pawn_attacks(BLACK, square); + knight_attacks[square] = mask_knight_attacks(square); + king_attacks[square] = mask_king_attacks(square); + } +} + +U64 set_occupancy(int index, int bits_in_mask, U64 attack_mask) { + U64 occupancy = C64(0); + + for (int count = 0; count < bits_in_mask; count++) { + Square square = bit_lsb_index(attack_mask); + bit_pop(attack_mask, square); + + if (index & (1 << count)) + bit_set(occupancy, square); + } + + return occupancy; +} + +void init_sliders_attacks_internal(int bishop) { + for (Square square = 0; square < 64; square++) { + U64 attack_mask; + + if (bishop) { + bishop_masks[square] = mask_bishop_attacks(square); + attack_mask = bishop_masks[square]; + } else { + rook_masks[square] = mask_rook_attacks(square); + attack_mask = rook_masks[square]; + } + + int relevant_bits = bit_count(attack_mask); + int occupancy_indicies = 1 << relevant_bits; + + for (int index = 0; index < occupancy_indicies; index++) { + U64 occupancy = set_occupancy(index, relevant_bits, attack_mask); + if (bishop) { + int magic_index = (occupancy * bishop_magic_numbers[square]) >> + (64 - bishop_relevant_bits[square]); + bishop_attacks[square][magic_index] = + bishop_attacks_on_the_fly(square, occupancy); + } else { + int magic_index = hash(occupancy, rook_magic_numbers[square], + rook_relevant_bits[square]); + rook_attacks[square][magic_index] = + rook_attacks_on_the_fly(square, occupancy); + } + } + } +} + +void init_sliders_attacks(void) { + init_sliders_attacks_internal(0); + init_sliders_attacks_internal(1); +} + +// magic numbers + +U64 generate_magic_number() { + return get_random_U64_number() & get_random_U64_number() & + get_random_U64_number(); +} + +U64 find_magic_number(Square square, int relevant_bits, int bishop) { + U64 occupancies[4096], attacks[4096], used_attacks[4096]; + U64 attack_mask = + bishop ? mask_bishop_attacks(square) : mask_rook_attacks(square); + int occupancy_indicies = 1 << relevant_bits; + + for (int index = 0; index < occupancy_indicies; index++) { + occupancies[index] = set_occupancy(index, relevant_bits, attack_mask); + attacks[index] = bishop + ? bishop_attacks_on_the_fly(square, occupancies[index]) + : rook_attacks_on_the_fly(square, occupancies[index]); + } + + for (int random_count = 0; random_count < 100000000; random_count++) { + U64 magic_number = generate_magic_number(); + if (bit_count((attack_mask * magic_number) & C64(0xFF00000000000000)) < 6) + continue; + + memset(used_attacks, C64(0), sizeof(used_attacks)); + int index, fail; + + for (index = 0, fail = 0; !fail && index < occupancy_indicies; index++) { + int magic_index = hash(occupancies[index], magic_number, relevant_bits); + + if (used_attacks[magic_index] == C64(0)) + used_attacks[magic_index] = attacks[index]; + else if (used_attacks[magic_index] != attacks[index]) + fail = 1; + } + + if (!fail) + return magic_number; + } + + return C64(0); +} + +void init_attacks(void) { + init_leapers_attacks(); + init_sliders_attacks(); +} diff --git a/src/board/CMakeLists.txt b/src/board/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(board OBJECT + board.c +) + +target_include_directories(board + PUBLIC "${PROJECT_SOURCE_DIR}/src/include" +) diff --git a/src/board.c b/src/board/board.c diff --git a/src/engine.c b/src/engine.c @@ -1,442 +0,0 @@ -#include <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "board.h" -#include "attack.h" -#include "moves.h" -#include "perft.h" -#include "score.h" -#include "utils.h" - -#include <cul/assert.h> -#include <cul/mem.h> - -/* DEBUGGING */ - -// FEN debug positions -#define empty_board "8/8/8/8/8/8/8/8 w - - " -#define start_position \ - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 " -#define tricky_position \ - "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 " -#define killer_position \ - "rnbqkb1r/pp1p1pPp/8/2p1pP2/1P1P4/3P3P/P1P1P3/RNBQKBNR w KQkq e6 0 1" -#define cmk_position \ - "r2q1rk1/ppp2ppp/2n1bn2/2b1p3/3pP3/3P1NPP/PPP1NPB1/R1BQ1RK1 b - - 0 9 " - -#define MAX_PLY 64 - -typedef struct Stats_T *Stats_T; -struct Stats_T { - long nodes; - int ply; - int pv_length[MAX_PLY]; - Move pv_table[MAX_PLY][MAX_PLY]; - Move killer_moves[2][MAX_PLY]; - U32 history_moves[16][64]; -}; - -Stats_T Stats_new(void) { - Stats_T p; - NEW0(p); - return p; -} - -void Stats_free(Stats_T *p) { FREE(*p); } - -int move_score(Stats_T stats, Move move) { - if (move_capture(move)) { - return Score_capture(Piece_piece(move_piece(move)), - Piece_piece(move_piece_capture(move))); - } else { - if (!move_cmp(stats->killer_moves[0][stats->ply], move)) - return 9000; - else if (!move_cmp(stats->killer_moves[1][stats->ply], move)) - return 8000; - else - return stats->history_moves[Piece_index(move_piece(move))] - [move_target(move)]; - } - - return 0; -} - -void move_list_sort(Stats_T stats, MoveList list) { - int score[list->count]; - for (int i = 0; i < list->count; i++) - score[i] = move_score(stats, list->moves[i]); - - for (int i = 0; i < list->count; i++) - for (int j = i + 1; j < list->count; j++) - if (score[i] < score[j]) { - Move t = list->moves[i]; - list->moves[i] = list->moves[j]; - list->moves[j] = t; - - int s = score[i]; - score[i] = score[j]; - score[j] = s; - } -} - -/* SEARCHING */ - -int evaluate(Board board) { - Square square; - eColor side = board_side(board); - U64 occupancy = board_colorBB(board, side); - - int score = 0; - for (int i = 0; i < 6; i++) { - U64 bitboard = board_pieceBB(board, i); - bitboard_for_each_bit(square, bitboard) { - if (bit_get(occupancy, square)) { - score += Score_value(i); - score += Score_position(i, side, square); - } else { - score -= Score_value(i); - score -= Score_position(i, !side, square); - } - } - } - - return score; -} - -int quiescence(Stats_T stats, Board board, int alpha, int beta) { - MoveList moves; - Board copy; - - int eval = evaluate(board); - stats->nodes++; - - if (eval >= beta) { - return beta; - } - - if (eval > alpha) { - alpha = eval; - } - - copy = board_new(); - moves = move_list_generate(NULL, board); - move_list_sort(stats, moves); - - for (int i = 0; i < move_list_size(moves); i++) { - board_copy(board, copy); - - if (move_make(move_list_move(moves, i), copy, 1) == 0) continue; - - stats->ply++; - int score = -quiescence(stats, copy, -beta, -alpha); - stats->ply--; - - if (score >= beta) { - move_list_free(&moves); - board_free(&copy); - return beta; - } - - if (score > alpha) { - alpha = score; - } - } - - move_list_free(&moves); - board_free(&copy); - return alpha; -} - -int negamax(Stats_T stats, Board board, int alpha, int beta, int depth) { - MoveList list; - Board copy; - int isCheck = 0; - int ply = stats->ply; - - stats->pv_length[ply] = ply; - - if (depth == 0) return quiescence(stats, board, alpha, beta); - - if (ply > MAX_PLY - 1) return evaluate(board); - - stats->nodes++; - - copy = board_new(); - list = move_list_generate(NULL, board); - isCheck = board_isCheck(board); - - if (isCheck) depth++; - - int legal_moves = 0; - move_list_sort(stats, list); - for (int i = 0; i < move_list_size(list); i++) { - Move move = move_list_move(list, i); - - board_copy(board, copy); - if (move_make(move, copy, 0) == 0) { - continue; - } - - stats->ply++; - int score = -negamax(stats, copy, -beta, -alpha, depth - 1); - stats->ply--; - legal_moves++; - - if (score >= beta) { - if (!move_capture(move)) { - stats->killer_moves[1][ply] = stats->killer_moves[0][ply]; - stats->killer_moves[0][ply] = move; - } - move_list_free(&list); - board_free(&copy); - return beta; - } - - if (score > alpha) { - if (!move_capture(move)) - stats->history_moves[Piece_index(move_piece(move))] - [move_target(move)] += depth; - alpha = score; - - stats->pv_table[ply][ply] = move; - for (int i = stats->ply + 1; i < stats->pv_length[ply + 1]; i++) - stats->pv_table[ply][i] = stats->pv_table[ply + 1][i]; - stats->pv_length[ply] = stats->pv_length[ply + 1]; - } - } - - if (legal_moves == 0) { - if (isCheck) - return -49000 + stats->ply; - else - return 0; - } - - move_list_free(&list); - board_free(&copy); - return alpha; -} - -void move_print_UCI(Move move) { - printf("%s%s", square_to_coordinates[move_source(move)], - square_to_coordinates[move_target(move)]); - if (move_promote(move)) printf(" %c", Piece_asci(move_piece_promote(move))); -} - -void search_position(Board board, int depth) { - Stats_T stats = Stats_new(); - - for (int crnt = 1; crnt <= depth; crnt++) { - int score = negamax(stats, board, -50000, 50000, crnt); - - printf("info score cp %d depth %d nodes %ld pv ", score, crnt, - stats->nodes); - - for (int i = 0; i < stats->pv_length[0]; i++) { - move_print_UCI(stats->pv_table[0][i]); - printf(" "); - } - printf("\n"); - } - - printf("bestmove "); - move_print_UCI(stats->pv_table[0][0]); - printf("\n"); - - Stats_free(&stats); -} - -void print_info(void) { - printf("id name Stellar\n"); - printf("id author Dimitrije Dobrota\n"); - printf("uciok\n"); -} - -typedef struct Instruction_T *Instruction_T; -struct Instruction_T { - char *command; - char *token; - char *crnt; -}; - -char *Instruction_token_next(Instruction_T self); - -Instruction_T Instruction_new(char *command) { - Instruction_T p; - NEW0(p); - p->command = ALLOC(strlen(command) + 1); - p->token = ALLOC(strlen(command) + 1); - strcpy(p->command, command); - p->crnt = command; - Instruction_token_next(p); - return p; -} - -void Instruction_free(Instruction_T *p) { - FREE((*p)->command); - FREE((*p)->token); - FREE(*p); -} - -char *Instruction_token(Instruction_T self) { return self->token; } -char *Instruction_token_n(Instruction_T self, int n) { - while (isspace(*self->crnt) && *self->crnt != '\0') - self->crnt++; - - if (*self->crnt == '\0') { - *self->token = '\0'; - return NULL; - } - - char *p = self->token; - while (n--) { - while (!isspace(*self->crnt) && *self->crnt != '\0' && - *self->crnt != ';') - *p++ = *self->crnt++; - if (*self->crnt == '\0') { - p++; - break; - } - self->crnt++; - *p++ = ' '; - } - *--p = '\0'; - - return self->token; -} - -char *Instruction_token_next(Instruction_T self) { - return Instruction_token_n(self, 1); -} - -Move parse_move(Board board, char *move_string) { - Move result = {0}; - MoveList moves; - Square source, target; - - source = coordinates_to_square(move_string); - target = coordinates_to_square(move_string + 2); - - moves = move_list_generate(NULL, board); - for (int i = 0; i < moves->count; i++) { - Move move = moves->moves[i]; - if (move_source(move) == source && move_target(move) == target) { - if (move_string[4]) { - if (tolower(Piece_code(move_piece_promote(move))) != - move_string[4]) - continue; - } - result = move; - break; - } - } - - move_list_free(&moves); - return result; -} - -Board Instruction_parse(Instruction_T self, Board board) { - char *token = Instruction_token(self); - - if (!board) board = board_new(); - - do { - if (strcmp(token, "ucinewgame") == 0) { - board = board_fromFEN(board, start_position); - continue; - } - - if (strcmp(token, "quit") == 0) return NULL; - - if (strcmp(token, "position") == 0) { - token = Instruction_token_next(self); - if (token && strcmp(token, "startpos") == 0) { - board = board_fromFEN(board, start_position); - } else if (token && strcmp(token, "fen") == 0) { - token = Instruction_token_n(self, 6); - board = board_fromFEN(board, token); - } else { - printf("Unknown argument after position\n"); - } - // board_print(board); - continue; - } - - if (strcmp(token, "moves") == 0) { - while ((token = Instruction_token_next(self))) { - Move move = parse_move(board, token); - if (!move_cmp(move, (Move){0})) { - move_make(move, board, 0); - } else { - printf("Invalid move %s!\n", token); - } - } - // board_print(board); - return board; - } - - if (strcmp(token, "go") == 0) { - token = Instruction_token_next(self); - int depth = 5; - if (token && strcmp(token, "depth") == 0) { - token = Instruction_token_next(self); - depth = atoi(token); - } else { - printf("Unknown argument after go\n"); - } - search_position(board, depth); - continue; - } - - if (strcmp(token, "isready") == 0) { - printf("readyok\n"); - continue; - } - - if (strcmp(token, "uci") == 0) { - print_info(); - continue; - } - - } while ((token = Instruction_token_next(self))); - - return board; -} - -void uci_loop(void) { - Board board = NULL; - Instruction_T instruction; - char input[200000]; - - setbuf(stdin, NULL); - setbuf(stdout, NULL); - - print_info(); - while (1) { - memset(input, 0, sizeof(input)); - fflush(stdout); - if (!fgets(input, sizeof(input), stdin)) continue; - - instruction = Instruction_new(input); - if (!(board = Instruction_parse(instruction, board))) break; - Instruction_free(&instruction); - } - - Instruction_free(&instruction); - board_free(&board); -} - -/* MAIN */ - -void init_all() { - init_leapers_attacks(); - init_sliders_attacks(); -} - -int main(void) { - init_all(); - uci_loop(); - return 0; -} diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(engine engine.c) + +target_link_libraries(engine + PRIVATE attack + PRIVATE board + PRIVATE moves + PRIVATE score + PRIVATE utils +) + +target_link_libraries(engine PRIVATE "cul") + +set_target_properties(engine PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) diff --git a/src/engine/engine.c b/src/engine/engine.c @@ -0,0 +1,425 @@ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "attack.h" +#include "board.h" +#include "moves.h" +#include "perft.h" +#include "score.h" +#include "utils.h" + +#include <cul/assert.h> +#include <cul/mem.h> + + +#define MAX_PLY 64 + +typedef struct Stats_T *Stats_T; +struct Stats_T { + long nodes; + int ply; + int pv_length[MAX_PLY]; + Move pv_table[MAX_PLY][MAX_PLY]; + Move killer_moves[2][MAX_PLY]; + U32 history_moves[16][64]; +}; + +Stats_T Stats_new(void) { + Stats_T p; + NEW0(p); + return p; +} + +void Stats_free(Stats_T *p) { FREE(*p); } + +int move_score(Stats_T stats, Move move) { + if (move_capture(move)) { + return Score_capture(Piece_piece(move_piece(move)), + Piece_piece(move_piece_capture(move))); + } else { + if (!move_cmp(stats->killer_moves[0][stats->ply], move)) + return 9000; + else if (!move_cmp(stats->killer_moves[1][stats->ply], move)) + return 8000; + else + return stats->history_moves[Piece_index(move_piece(move))] + [move_target(move)]; + } + + return 0; +} + +void move_list_sort(Stats_T stats, MoveList list) { + int score[list->count]; + for (int i = 0; i < list->count; i++) + score[i] = move_score(stats, list->moves[i]); + + for (int i = 0; i < list->count; i++) + for (int j = i + 1; j < list->count; j++) + if (score[i] < score[j]) { + Move t = list->moves[i]; + list->moves[i] = list->moves[j]; + list->moves[j] = t; + + int s = score[i]; + score[i] = score[j]; + score[j] = s; + } +} + +/* SEARCHING */ + +int evaluate(Board board) { + Square square; + eColor side = board_side(board); + U64 occupancy = board_colorBB(board, side); + + int score = 0; + for (int i = 0; i < 6; i++) { + U64 bitboard = board_pieceBB(board, i); + bitboard_for_each_bit(square, bitboard) { + if (bit_get(occupancy, square)) { + score += Score_value(i); + score += Score_position(i, side, square); + } else { + score -= Score_value(i); + score -= Score_position(i, !side, square); + } + } + } + + return score; +} + +int quiescence(Stats_T stats, Board board, int alpha, int beta) { + MoveList moves; + Board copy; + + int eval = evaluate(board); + stats->nodes++; + + if (eval >= beta) { + return beta; + } + + if (eval > alpha) { + alpha = eval; + } + + copy = board_new(); + moves = move_list_generate(NULL, board); + move_list_sort(stats, moves); + + for (int i = 0; i < move_list_size(moves); i++) { + board_copy(board, copy); + + if (move_make(move_list_move(moves, i), copy, 1) == 0) continue; + + stats->ply++; + int score = -quiescence(stats, copy, -beta, -alpha); + stats->ply--; + + if (score >= beta) { + move_list_free(&moves); + board_free(&copy); + return beta; + } + + if (score > alpha) { + alpha = score; + } + } + + move_list_free(&moves); + board_free(&copy); + return alpha; +} + +int negamax(Stats_T stats, Board board, int alpha, int beta, int depth) { + MoveList list; + Board copy; + int isCheck = 0; + int ply = stats->ply; + + stats->pv_length[ply] = ply; + + if (depth == 0) return quiescence(stats, board, alpha, beta); + + if (ply > MAX_PLY - 1) return evaluate(board); + + stats->nodes++; + + copy = board_new(); + list = move_list_generate(NULL, board); + isCheck = board_isCheck(board); + + if (isCheck) depth++; + + int legal_moves = 0; + move_list_sort(stats, list); + for (int i = 0; i < move_list_size(list); i++) { + Move move = move_list_move(list, i); + + board_copy(board, copy); + if (move_make(move, copy, 0) == 0) { + continue; + } + + stats->ply++; + int score = -negamax(stats, copy, -beta, -alpha, depth - 1); + stats->ply--; + legal_moves++; + + if (score >= beta) { + if (!move_capture(move)) { + stats->killer_moves[1][ply] = stats->killer_moves[0][ply]; + stats->killer_moves[0][ply] = move; + } + move_list_free(&list); + board_free(&copy); + return beta; + } + + if (score > alpha) { + if (!move_capture(move)) + stats->history_moves[Piece_index(move_piece(move))] + [move_target(move)] += depth; + alpha = score; + + stats->pv_table[ply][ply] = move; + for (int i = stats->ply + 1; i < stats->pv_length[ply + 1]; i++) + stats->pv_table[ply][i] = stats->pv_table[ply + 1][i]; + stats->pv_length[ply] = stats->pv_length[ply + 1]; + } + } + + if (legal_moves == 0) { + if (isCheck) + return -49000 + stats->ply; + else + return 0; + } + + move_list_free(&list); + board_free(&copy); + return alpha; +} + +void move_print_UCI(Move move) { + printf("%s%s", square_to_coordinates[move_source(move)], + square_to_coordinates[move_target(move)]); + if (move_promote(move)) printf(" %c", Piece_asci(move_piece_promote(move))); +} + +void search_position(Board board, int depth) { + Stats_T stats = Stats_new(); + + for (int crnt = 1; crnt <= depth; crnt++) { + int score = negamax(stats, board, -50000, 50000, crnt); + + printf("info score cp %d depth %d nodes %ld pv ", score, crnt, + stats->nodes); + + for (int i = 0; i < stats->pv_length[0]; i++) { + move_print_UCI(stats->pv_table[0][i]); + printf(" "); + } + printf("\n"); + } + + printf("bestmove "); + move_print_UCI(stats->pv_table[0][0]); + printf("\n"); + + Stats_free(&stats); +} + +void print_info(void) { + printf("id name Stellar\n"); + printf("id author Dimitrije Dobrota\n"); + printf("uciok\n"); +} + +typedef struct Instruction_T *Instruction_T; +struct Instruction_T { + char *command; + char *token; + char *crnt; +}; + +char *Instruction_token_next(Instruction_T self); + +Instruction_T Instruction_new(char *command) { + Instruction_T p; + NEW0(p); + p->command = ALLOC(strlen(command) + 1); + p->token = ALLOC(strlen(command) + 1); + strcpy(p->command, command); + p->crnt = command; + Instruction_token_next(p); + return p; +} + +void Instruction_free(Instruction_T *p) { + FREE((*p)->command); + FREE((*p)->token); + FREE(*p); +} + +char *Instruction_token(Instruction_T self) { return self->token; } +char *Instruction_token_n(Instruction_T self, int n) { + while (isspace(*self->crnt) && *self->crnt != '\0') + self->crnt++; + + if (*self->crnt == '\0') { + *self->token = '\0'; + return NULL; + } + + char *p = self->token; + while (n--) { + while (!isspace(*self->crnt) && *self->crnt != '\0' && + *self->crnt != ';') + *p++ = *self->crnt++; + if (*self->crnt == '\0') { + p++; + break; + } + self->crnt++; + *p++ = ' '; + } + *--p = '\0'; + + return self->token; +} + +char *Instruction_token_next(Instruction_T self) { + return Instruction_token_n(self, 1); +} + +Move parse_move(Board board, char *move_string) { + Move result = {0}; + MoveList moves; + Square source, target; + + source = coordinates_to_square(move_string); + target = coordinates_to_square(move_string + 2); + + moves = move_list_generate(NULL, board); + for (int i = 0; i < moves->count; i++) { + Move move = moves->moves[i]; + if (move_source(move) == source && move_target(move) == target) { + if (move_string[4]) { + if (tolower(Piece_code(move_piece_promote(move))) != + move_string[4]) + continue; + } + result = move; + break; + } + } + + move_list_free(&moves); + return result; +} + +Board Instruction_parse(Instruction_T self, Board board) { + char *token = Instruction_token(self); + + if (!board) board = board_new(); + + do { + if (strcmp(token, "ucinewgame") == 0) { + board = board_fromFEN(board, start_position); + continue; + } + + if (strcmp(token, "quit") == 0) return NULL; + + if (strcmp(token, "position") == 0) { + token = Instruction_token_next(self); + if (token && strcmp(token, "startpos") == 0) { + board = board_fromFEN(board, start_position); + } else if (token && strcmp(token, "fen") == 0) { + token = Instruction_token_n(self, 6); + board = board_fromFEN(board, token); + } else { + printf("Unknown argument after position\n"); + } + // board_print(board); + continue; + } + + if (strcmp(token, "moves") == 0) { + while ((token = Instruction_token_next(self))) { + Move move = parse_move(board, token); + if (!move_cmp(move, (Move){0})) { + move_make(move, board, 0); + } else { + printf("Invalid move %s!\n", token); + } + } + // board_print(board); + return board; + } + + if (strcmp(token, "go") == 0) { + token = Instruction_token_next(self); + int depth = 5; + if (token && strcmp(token, "depth") == 0) { + token = Instruction_token_next(self); + depth = atoi(token); + } else { + printf("Unknown argument after go\n"); + } + search_position(board, depth); + continue; + } + + if (strcmp(token, "isready") == 0) { + printf("readyok\n"); + continue; + } + + if (strcmp(token, "uci") == 0) { + print_info(); + continue; + } + + } while ((token = Instruction_token_next(self))); + + return board; +} + +void uci_loop(void) { + Board board = NULL; + Instruction_T instruction; + char input[200000]; + + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + print_info(); + while (1) { + memset(input, 0, sizeof(input)); + fflush(stdout); + if (!fgets(input, sizeof(input), stdin)) continue; + + instruction = Instruction_new(input); + if (!(board = Instruction_parse(instruction, board))) break; + Instruction_free(&instruction); + } + + Instruction_free(&instruction); + board_free(&board); +} + +/* MAIN */ + +int main(void) { + init_attacks(); + uci_loop(); + return 0; +} diff --git a/src/include/attack.h b/src/include/attack.h @@ -0,0 +1,18 @@ +#ifndef ATTACK_H +#define ATTACK_H + +#include "utils.h" + +void init_attacks(void); +void init_leapers_attacks(void); +void init_sliders_attacks(void); + +U64 get_wpawn_attacks(Square square, U64 occupancy); +U64 get_bpawn_attacks(Square square, U64 occupancy); +U64 get_knight_attacks(Square square, U64 occupancy); +U64 get_king_attacks(Square square, U64 occupancy); +U64 get_bishop_attacks(Square square, U64 occupancy); +U64 get_rook_attacks(Square square, U64 occupancy); +U64 get_queen_attacks(Square square, U64 occupancy); + +#endif diff --git a/include/board.h b/src/include/board.h diff --git a/include/moves.h b/src/include/moves.h diff --git a/include/perft.h b/src/include/perft.h diff --git a/include/score.h b/src/include/score.h diff --git a/src/include/utils.h b/src/include/utils.h @@ -0,0 +1,90 @@ +#ifndef __UTILS_H +#define __UTILS_H + +// useful macros +#define MAX(a, b) ((a > b) ? a : b) +#define MIN(a, b) ((a < b) ? a : b) + +// define number types +typedef unsigned long long U64; // define bitboard data type +#define C64(constantU64) constantU64##ULL // define shorthand for constants + +typedef unsigned int U32; +#define C32(constantU32) constantU32##U + +// useful bit patterns +extern const U64 universe; +extern const U64 notAFile; +extern const U64 notHFile; + +// useful bit functions +#define bit_get(bitboard, square) (((bitboard) >> (square)) & C64(1)) +#define bit_set(bitboard, square) ((bitboard) |= C64(1) << (square)) +#define bit_pop(bitboard, square) ((bitboard) &= ~(C64(1) << (square))) +int bit_count(U64 bitboard); +int bit_lsb_index(U64 bitboard); + +#define bitboard_for_each_bit(var, bb) \ + for (var = bit_lsb_index(bb); bb; bit_pop(bb, var), var = bit_lsb_index(bb)) + +void bitboard_print(U64 bitboard); + +// squares +// clang-format off +enum enumSquare { + a1, b1, c1, d1, e1, f1, g1, h1, + a2, b2, c2, d2, e2, f2, g2, h2, + a3, b3, c3, d3, e3, f3, g3, h3, + a4, b4, c4, d4, e4, f4, g4, h4, + a5, b5, c5, d5, e5, f5, g5, h5, + a6, b6, c6, d6, e6, f6, g6, h6, + a7, b7, c7, d7, e7, f7, g7, h7, + a8, b8, c8, d8, e8, f8, g8, h8, no_sq +}; +// clang-format on +typedef enum enumSquare Square; + +extern const char *square_to_coordinates[]; +Square coordinates_to_square(char *cord); + +// board moving +typedef U64 (*direction_f)(U64); +U64 soutOne(U64 b); +U64 nortOne(U64 b); +U64 eastOne(U64 b); +U64 westOne(U64 b); +U64 soEaOne(U64 b); +U64 soWeOne(U64 b); +U64 noEaOne(U64 b); +U64 noWeOne(U64 b); + +// board rotation +U64 rotateLeft(U64 x, int s); +U64 rotateRight(U64 x, int s); + +// enum types for color and piece type +enum enumColor { + WHITE = 0, + BLACK +}; +enum enumPiece { + PAWN = 0, + KNIGHT, + BISHOP, + ROOK, + QUEEN, + KING +}; + +typedef enum enumColor eColor; +typedef enum enumPiece ePiece; + +int get_time_ms(void); + +typedef U64 (*attack_f)(Square square, U64 occupancy); + +#define empty_board "8/8/8/8/8/8/8/8 w - - " +#define start_position \ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 " + +#endif diff --git a/src/moves/CMakeLists.txt b/src/moves/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(moves OBJECT + moves.c +) + +target_include_directories(moves + PUBLIC "${PROJECT_SOURCE_DIR}/src/include" +) diff --git a/src/moves.c b/src/moves/moves.c diff --git a/src/perft.c b/src/perft.c @@ -1,127 +0,0 @@ -#include <pthread.h> -#include <semaphore.h> -#include <stdio.h> - -#include "moves.h" -#include "perft.h" - -struct MoveList moveList[10]; -long nodes; - -void perft_driver(Board board, struct MoveList *moveList, int depth, - unsigned long long *nodes) { - if (depth == 0) { - (*nodes)++; - return; - } - - MoveList list = move_list_generate(&moveList[depth], board); - Board copy = board_new(); - - for (int i = 0; i < move_list_size(list); i++) { - board_copy(board, copy); - if (!move_make(move_list_move(list, i), copy, 0)) continue; - perft_driver(copy, moveList, depth - 1, nodes); - } - - move_list_reset(list); - board_free(&copy); -} - -void perft_test(Board board, int depth) { - MoveList list = move_list_generate(&moveList[depth], board); - Board copy = board_new(); - long start = get_time_ms(); - - printf("\n Performance test\n\n"); - - nodes = 0; - for (int i = 0; i < move_list_size(list); i++) { - board_copy(board, copy); - Move move = move_list_move(list, i); - if (!move_make(move_list_move(list, i), copy, 0)) continue; - unsigned long long node = 0; - perft_driver(copy, moveList, depth - 1, &node); - printf("%s%s: %llu\n", square_to_coordinates[move_source(move)], - square_to_coordinates[move_target(move)], node); - nodes += node; - } - move_list_reset(list); - board_free(&copy); - - printf("\nNodes searched: %ld\n\n", nodes); - printf("\n Depth: %d\n", depth); - printf(" Nodes: %ld\n", nodes); - printf(" Time: %ld\n\n", get_time_ms() - start); -} - -typedef struct perf_shared perf_shared; -struct perf_shared { - Board board; - MoveList list; - int depth; - sem_t *mutex; - int *index; - sem_t *finish; - unsigned long long *total; -}; - -void *perft_thread(void *arg) { - perf_shared *shared = (perf_shared *)arg; - Board board = board_new(); - unsigned long long node_count = 0; - - struct MoveList moveList[10]; - - while (1) { - sem_wait(shared->mutex); - *shared->total += node_count; - if (*shared->index >= move_list_size(shared->list)) { - sem_post(shared->mutex); - break; - } - Move move = move_list_move(shared->list, (*shared->index)++); - sem_post(shared->mutex); - - board_copy(shared->board, board); - if (!move_make(move, board, 0)) continue; - - node_count = 0; - perft_driver(board, moveList, shared->depth, &node_count); - printf("%s%s: %llu\n", square_to_coordinates[move_source(move)], - square_to_coordinates[move_target(move)], node_count); - } - board_free(&board); - sem_post(shared->finish); - return NULL; -} - -void perft_test_threaded(Board board, int depth) { - MoveList list = move_list_generate(NULL, board); - int size = 8; - - unsigned long long total = 0; - perf_shared shared[size]; - pthread_t threads[size]; - sem_t mutex, finish; - - int index = 0; - sem_init(&mutex, 0, 1); - sem_init(&finish, 0, 0); - for (int i = 0; i < size; i++) { - shared[i].board = board; - shared[i].list = list; - shared[i].depth = depth - 1; - shared[i].mutex = &mutex; - shared[i].index = &index; - shared[i].finish = &finish; - shared[i].total = &total; - pthread_create(threads + i, NULL, perft_thread, (void *)(shared + i)); - } - - for (int i = 0; i < size; i++) - sem_wait(&finish); - move_list_free(&list); - - printf("Nodes processed: %llu\n", total); -} diff --git a/src/perft/CMakeLists.txt b/src/perft/CMakeLists.txt @@ -0,0 +1,17 @@ +add_executable(perft perft.c) + +target_link_libraries(perft + PRIVATE attack + PRIVATE board + PRIVATE moves + PRIVATE score + PRIVATE utils +) + +target_link_libraries(perft PRIVATE "cul") + +set_target_properties(perft PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) diff --git a/src/perft/perft.c b/src/perft/perft.c @@ -0,0 +1,145 @@ +#include <pthread.h> +#include <semaphore.h> +#include <stdio.h> + +#include "attack.h" +#include "board.h" +#include "moves.h" +#include "perft.h" +#include "utils.h" + +struct MoveList moveList[10]; +long nodes; + +void perft_driver(Board board, struct MoveList *moveList, int depth, + unsigned long long *nodes) { + if (depth == 0) { + (*nodes)++; + return; + } + + MoveList list = move_list_generate(&moveList[depth], board); + Board copy = board_new(); + + for (int i = 0; i < move_list_size(list); i++) { + board_copy(board, copy); + if (!move_make(move_list_move(list, i), copy, 0)) continue; + perft_driver(copy, moveList, depth - 1, nodes); + } + + move_list_reset(list); + board_free(&copy); +} + +void perft_test(Board board, int depth) { + MoveList list = move_list_generate(&moveList[depth], board); + Board copy = board_new(); + long start = get_time_ms(); + + printf("\n Performance test\n\n"); + + nodes = 0; + for (int i = 0; i < move_list_size(list); i++) { + board_copy(board, copy); + Move move = move_list_move(list, i); + if (!move_make(move_list_move(list, i), copy, 0)) continue; + unsigned long long node = 0; + perft_driver(copy, moveList, depth - 1, &node); + printf("%s%s: %llu\n", square_to_coordinates[move_source(move)], + square_to_coordinates[move_target(move)], node); + nodes += node; + } + move_list_reset(list); + board_free(&copy); + + printf("\nNodes searched: %ld\n\n", nodes); + printf("\n Depth: %d\n", depth); + printf(" Nodes: %ld\n", nodes); + printf(" Time: %ld\n\n", get_time_ms() - start); +} + +typedef struct perf_shared perf_shared; +struct perf_shared { + Board board; + MoveList list; + int depth; + sem_t *mutex; + int *index; + sem_t *finish; + unsigned long long *total; +}; + +void *perft_thread(void *arg) { + perf_shared *shared = (perf_shared *)arg; + Board board = board_new(); + unsigned long long node_count = 0; + + struct MoveList moveList[10]; + + while (1) { + sem_wait(shared->mutex); + *shared->total += node_count; + if (*shared->index >= move_list_size(shared->list)) { + sem_post(shared->mutex); + break; + } + Move move = move_list_move(shared->list, (*shared->index)++); + sem_post(shared->mutex); + + board_copy(shared->board, board); + if (!move_make(move, board, 0)) continue; + + node_count = 0; + perft_driver(board, moveList, shared->depth, &node_count); + printf("%s%s: %llu\n", square_to_coordinates[move_source(move)], + square_to_coordinates[move_target(move)], node_count); + } + board_free(&board); + sem_post(shared->finish); + return NULL; +} + +void perft_test_threaded(Board board, int depth) { + MoveList list = move_list_generate(NULL, board); + int size = 8; + + unsigned long long total = 0; + perf_shared shared[size]; + pthread_t threads[size]; + sem_t mutex, finish; + + int index = 0; + sem_init(&mutex, 0, 1); + sem_init(&finish, 0, 0); + for (int i = 0; i < size; i++) { + shared[i].board = board; + shared[i].list = list; + shared[i].depth = depth - 1; + shared[i].mutex = &mutex; + shared[i].index = &index; + shared[i].finish = &finish; + shared[i].total = &total; + pthread_create(threads + i, NULL, perft_thread, (void *)(shared + i)); + } + + for (int i = 0; i < size; i++) + sem_wait(&finish); + move_list_free(&list); + + printf("Nodes processed: %llu\n", total); +} + +// FEN debug positions +#define tricky_position \ + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 " +#define killer_position \ + "rnbqkb1r/pp1p1pPp/8/2p1pP2/1P1P4/3P3P/P1P1P3/RNBQKBNR w KQkq e6 0 1" +#define cmk_position \ + "r2q1rk1/ppp2ppp/2n1bn2/2b1p3/3pP3/3P1NPP/PPP1NPB1/R1BQ1RK1 b - - 0 9 " + +int main(void) { + init_attacks(); + Board board = board_new(); + board_fromFEN(board, tricky_position); + perft_test_threaded(board, 5); +} diff --git a/src/score/CMakeLists.txt b/src/score/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(score OBJECT + score.c +) + +target_include_directories(score + PUBLIC "${PROJECT_SOURCE_DIR}/src/include" +) diff --git a/src/score.c b/src/score/score.c diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(utils OBJECT + utils.c +) + +target_include_directories(utils + PUBLIC "${PROJECT_SOURCE_DIR}/src/include" +) diff --git a/src/utils.c b/src/utils/utils.c