Make engine dep-free/standalone and add Wasm target
This commit is contained in:
15
Makefile
15
Makefile
@@ -2,13 +2,17 @@
|
|||||||
BUILD ?= debug
|
BUILD ?= debug
|
||||||
CC := clang
|
CC := clang
|
||||||
|
|
||||||
CFLAGS.gcc.release := -O3
|
|
||||||
CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address
|
|
||||||
CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function
|
CFLAGS.gcc := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function
|
||||||
|
CFLAGS.gcc.release := -Ofast
|
||||||
|
CFLAGS.gcc.debug := -ggdb -O0 -fsanitize=address
|
||||||
|
|
||||||
CFLAGS.clang.release := -O3
|
|
||||||
CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address
|
|
||||||
CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion
|
CFLAGS.clang := -std=c23 -Wall -Wextra -Wconversion -Wno-unused-function -Wimplicit-int-conversion
|
||||||
|
CFLAGS.clang.release := -Ofast
|
||||||
|
CFLAGS.clang.debug := -ggdb -O0 -fsanitize=address
|
||||||
|
CFLAGS.clang.wasm := \
|
||||||
|
--target=wasm32-unknown-unknown -O3 -nostdlib \
|
||||||
|
-Wl,--export-all \
|
||||||
|
-Wl,--no-entry
|
||||||
|
|
||||||
CFLAGS := $(CFLAGS.$(CC)) $(CFLAGS.$(CC).$(BUILD))
|
CFLAGS := $(CFLAGS.$(CC)) $(CFLAGS.$(CC).$(BUILD))
|
||||||
|
|
||||||
@@ -17,6 +21,9 @@ all: tests
|
|||||||
codegen: codegen.c
|
codegen: codegen.c
|
||||||
$(CC) -o $@ $(CFLAGS) $^
|
$(CC) -o $@ $(CFLAGS) $^
|
||||||
|
|
||||||
|
wasm: wasm-compat.c
|
||||||
|
$(CC) -DWASM -o chess.wasm wasm-compat.c $(CFLAGS.$(CC)) $(CFLAGS.$(CC).wasm)
|
||||||
|
|
||||||
mbb_rook.h: codegen
|
mbb_rook.h: codegen
|
||||||
./codegen
|
./codegen
|
||||||
|
|
||||||
|
|||||||
532
base.h
532
base.h
@@ -1,32 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ctype.h>
|
#include "libc-lite.h"
|
||||||
#include <math.h>
|
#include "sys.h"
|
||||||
#include <locale.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
#define index string_index
|
#ifndef NDEBUG
|
||||||
#include <string.h>
|
#define assert(expr) \
|
||||||
#undef index
|
((expr) ? 0 : (__builtin_trap(), 0))
|
||||||
|
#else
|
||||||
static struct {
|
#define assert(...) (void)0
|
||||||
/* all globals are zero by default in C */
|
#endif
|
||||||
uint64_t tt_collisions;
|
|
||||||
uint64_t tt_hits;
|
|
||||||
uint64_t tt_probes;
|
|
||||||
} global_stats;
|
|
||||||
|
|
||||||
static void print_stats(FILE* out)
|
|
||||||
{
|
|
||||||
fprintf(out, "Stats:\n");
|
|
||||||
fprintf(out, "tt collisions: %lu\n", global_stats.tt_collisions);
|
|
||||||
fprintf(out, "tt hits: %lu\n", global_stats.tt_hits);
|
|
||||||
fprintf(out, "tt probes: %lu\n", global_stats.tt_probes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BIT MANIPULATION
|
/* BIT MANIPULATION
|
||||||
* ================ */
|
* ================ */
|
||||||
@@ -43,23 +27,6 @@ static inline uint64_t ctz(uint64_t n)
|
|||||||
return (uint64_t)__builtin_ctzll(n);
|
return (uint64_t)__builtin_ctzll(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**/
|
|
||||||
static uint64_t rand64() {
|
|
||||||
union {
|
|
||||||
uint64_t v;
|
|
||||||
uint16_t vs[4];
|
|
||||||
} x = {
|
|
||||||
.vs = {
|
|
||||||
(uint16_t)rand(),
|
|
||||||
(uint16_t)rand(),
|
|
||||||
(uint16_t)rand(),
|
|
||||||
(uint16_t)rand(),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return x.v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BITBOARD AND INDEX DATATYPES AND HELPERS
|
/* BITBOARD AND INDEX DATATYPES AND HELPERS
|
||||||
* ========================================= */
|
* ========================================= */
|
||||||
|
|
||||||
@@ -86,12 +53,9 @@ h2##g2##f2##e2##d2##c2##b2##a2##\
|
|||||||
h1##g1##f1##e1##d1##c1##b1##a1##\
|
h1##g1##f1##e1##d1##c1##b1##a1##\
|
||||||
ULL
|
ULL
|
||||||
|
|
||||||
#define BITBOARD_FMT PRIu64
|
|
||||||
#define BITBOARD_FMT_X PRIx64
|
|
||||||
|
|
||||||
typedef uint8_t index;
|
typedef uint8_t index;
|
||||||
#define INDEX(n) n##ULL
|
#define INDEX(n) n##ULL
|
||||||
#define INDEX_FMT PRIu8
|
|
||||||
|
|
||||||
#define RANK_SHIFT_UP(b, n) ((b) << ((index)(n)*8U))
|
#define RANK_SHIFT_UP(b, n) ((b) << ((index)(n)*8U))
|
||||||
#define RANK_SHIFT_DOWN(b, n) ((b) >> ((index)(n)*8U))
|
#define RANK_SHIFT_DOWN(b, n) ((b) >> ((index)(n)*8U))
|
||||||
@@ -124,6 +88,17 @@ enum file_index : index {
|
|||||||
FILE_INDEX_COUNT,
|
FILE_INDEX_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
char const file_index_char[FILE_INDEX_COUNT] = {
|
||||||
|
[FILE_INDEX_A] = 'A',
|
||||||
|
[FILE_INDEX_B] = 'B',
|
||||||
|
[FILE_INDEX_C] = 'C',
|
||||||
|
[FILE_INDEX_D] = 'D',
|
||||||
|
[FILE_INDEX_E] = 'E',
|
||||||
|
[FILE_INDEX_F] = 'F',
|
||||||
|
[FILE_INDEX_G] = 'G',
|
||||||
|
[FILE_INDEX_H] = 'H',
|
||||||
|
};
|
||||||
|
|
||||||
enum rank_index : index {
|
enum rank_index : index {
|
||||||
RANK_INDEX_BEGIN,
|
RANK_INDEX_BEGIN,
|
||||||
RANK_INDEX_1 = RANK_INDEX_BEGIN,
|
RANK_INDEX_1 = RANK_INDEX_BEGIN,
|
||||||
@@ -137,6 +112,17 @@ enum rank_index : index {
|
|||||||
RANK_INDEX_COUNT,
|
RANK_INDEX_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
char const rank_index_char[RANK_INDEX_COUNT] = {
|
||||||
|
[RANK_INDEX_1] = '1',
|
||||||
|
[RANK_INDEX_2] = '2',
|
||||||
|
[RANK_INDEX_3] = '3',
|
||||||
|
[RANK_INDEX_4] = '4',
|
||||||
|
[RANK_INDEX_5] = '5',
|
||||||
|
[RANK_INDEX_6] = '6',
|
||||||
|
[RANK_INDEX_7] = '7',
|
||||||
|
[RANK_INDEX_8] = '8',
|
||||||
|
};
|
||||||
|
|
||||||
#define STR(x) #x
|
#define STR(x) #x
|
||||||
|
|
||||||
#define SQ_MASK_PREFIX SQ_MASK_
|
#define SQ_MASK_PREFIX SQ_MASK_
|
||||||
@@ -330,7 +316,7 @@ enum : bitboard {
|
|||||||
* Player
|
* Player
|
||||||
* ===========
|
* ===========
|
||||||
* */
|
* */
|
||||||
enum player : size_t {
|
enum player : uint8_t {
|
||||||
PLAYER_BEGIN,
|
PLAYER_BEGIN,
|
||||||
PLAYER_WHITE = PLAYER_BEGIN,
|
PLAYER_WHITE = PLAYER_BEGIN,
|
||||||
PLAYER_BLACK,
|
PLAYER_BLACK,
|
||||||
@@ -885,7 +871,16 @@ struct search_option {
|
|||||||
#define TT_ADDRESS_BITS 24
|
#define TT_ADDRESS_BITS 24
|
||||||
#define TT_ENTRIES (1ULL<<TT_ADDRESS_BITS)
|
#define TT_ENTRIES (1ULL<<TT_ADDRESS_BITS)
|
||||||
struct tt {
|
struct tt {
|
||||||
struct search_option* entries; /* must be initialized somewhere */
|
struct search_option* entries/*[TT_ENTRIES]*/; /* must be initialized somewhere */
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
/* stats */
|
||||||
|
uint64_t collisions;
|
||||||
|
uint64_t hits;
|
||||||
|
uint64_t probes;
|
||||||
|
uint64_t overwritten;
|
||||||
|
uint64_t insertions;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -906,7 +901,7 @@ struct board {
|
|||||||
|
|
||||||
/* used for repeated board state detection only */
|
/* used for repeated board state detection only */
|
||||||
struct history {
|
struct history {
|
||||||
struct pos items[4096];
|
struct pos items[64];
|
||||||
size_t length;
|
size_t length;
|
||||||
} hist;
|
} hist;
|
||||||
|
|
||||||
@@ -936,7 +931,7 @@ static void move_compute_appeal(struct move* m,
|
|||||||
m->appeal = 16*n - (uint8_t)piece_value[atk];
|
m->appeal = 16*n - (uint8_t)piece_value[atk];
|
||||||
}
|
}
|
||||||
|
|
||||||
#define BOARD_INITIAL (struct board) { \
|
#define BOARD_INIT (struct board) { \
|
||||||
.pos = { \
|
.pos = { \
|
||||||
.fullmoves = 1, \
|
.fullmoves = 1, \
|
||||||
.pieces = { \
|
.pieces = { \
|
||||||
@@ -1006,81 +1001,6 @@ static void move_compute_appeal(struct move* m,
|
|||||||
.hist = {0}, \
|
.hist = {0}, \
|
||||||
}
|
}
|
||||||
|
|
||||||
static void board_print_fen(struct pos const* pos, FILE* out)
|
|
||||||
{
|
|
||||||
int buf[8][8] = {0};
|
|
||||||
|
|
||||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
|
||||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
|
||||||
bitboard x = pos->pieces[player][piece];
|
|
||||||
|
|
||||||
for (index i = 7; i < 8; i--) {
|
|
||||||
for (index j = 0; j < 8; ++j) {
|
|
||||||
if (x & (1ULL<<(i*8+j))) {
|
|
||||||
buf[i][j] = piece_char[player][piece];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (enum rank_index i = RANK_INDEX_8; i < RANK_INDEX_COUNT; --i) {
|
|
||||||
int consequtive_empty = 0;
|
|
||||||
for (enum file_index j = FILE_INDEX_A; j < FILE_INDEX_COUNT; ++j) {
|
|
||||||
if (buf[i][j]) {
|
|
||||||
if (consequtive_empty) {
|
|
||||||
fprintf(out, "%d", consequtive_empty);
|
|
||||||
consequtive_empty = 0;
|
|
||||||
}
|
|
||||||
fprintf(out, "%lc", buf[i][j]);
|
|
||||||
} else {
|
|
||||||
consequtive_empty += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (consequtive_empty) {
|
|
||||||
fprintf(out, "%d", consequtive_empty);
|
|
||||||
}
|
|
||||||
if (i > 0)
|
|
||||||
fprintf(out, "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(out, " %c ", pos->player == PLAYER_WHITE ? 'w' : 'b');
|
|
||||||
|
|
||||||
bool any_castle = false;
|
|
||||||
if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE]) {
|
|
||||||
fprintf(out, "K");
|
|
||||||
any_castle = true;
|
|
||||||
}
|
|
||||||
if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE]) {
|
|
||||||
fprintf(out, "Q");
|
|
||||||
any_castle = true;
|
|
||||||
}
|
|
||||||
if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE]) {
|
|
||||||
fprintf(out, "k");
|
|
||||||
any_castle = true;
|
|
||||||
}
|
|
||||||
if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE]) {
|
|
||||||
fprintf(out, "q");
|
|
||||||
any_castle = true;
|
|
||||||
}
|
|
||||||
if (!any_castle) {
|
|
||||||
fprintf(out, "-");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos->ep_targets) {
|
|
||||||
/* should be ep target square in algebraic notation */
|
|
||||||
fprintf(stderr, "not implemented: fen with en passent squares\n");
|
|
||||||
fprintf(out, "<TODO>");
|
|
||||||
} else {
|
|
||||||
fprintf(out, " -");
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(out, " %d", pos->halfmoves);
|
|
||||||
fprintf(out, " %d", pos->fullmoves);
|
|
||||||
|
|
||||||
fprintf(out, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
||||||
{
|
{
|
||||||
/* TODO: this function is not tested for malicious/corrupted inputs */
|
/* TODO: this function is not tested for malicious/corrupted inputs */
|
||||||
@@ -1093,17 +1013,17 @@ static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
|||||||
.i = 0,
|
.i = 0,
|
||||||
.len = strlen(fen_str),
|
.len = strlen(fen_str),
|
||||||
};
|
};
|
||||||
#define BUF_GETCHAR(x) (fprintf(stderr, "[%c]", x.buf[x.i]), assert(x.i < x.len), x.buf[x.i++])
|
#define BUF_GETCHAR(x) (assert(x.i < x.len), x.buf[x.i++])
|
||||||
|
|
||||||
memset(&b->pos, 0, sizeof b->pos);
|
my_memset(&b->pos, 0, sizeof b->pos);
|
||||||
b->pos.hash = ~0ULL;
|
b->pos.hash = ~0ULL;
|
||||||
|
|
||||||
for (enum rank_index ri = RANK_INDEX_8; ri < RANK_INDEX_COUNT; --ri) {
|
for (enum rank_index ri = RANK_INDEX_8; ri < RANK_INDEX_COUNT; --ri) {
|
||||||
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
||||||
char const ch = BUF_GETCHAR(fen);
|
char const ch = BUF_GETCHAR(fen);
|
||||||
if (isdigit(ch)) {
|
if (my_isdigit(ch)) {
|
||||||
if (ch == '0') {
|
if (ch == '0') {
|
||||||
abort();
|
__builtin_trap();
|
||||||
}
|
}
|
||||||
fi += ch - '0' - 1;
|
fi += ch - '0' - 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -1124,14 +1044,14 @@ static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
|||||||
} else if (ch == 'b') {
|
} else if (ch == 'b') {
|
||||||
b->pos.player = PLAYER_BLACK;
|
b->pos.player = PLAYER_BLACK;
|
||||||
} else {
|
} else {
|
||||||
abort();
|
__builtin_trap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ /* castling */
|
{ /* castling */
|
||||||
char ch = BUF_GETCHAR(fen);
|
char ch = BUF_GETCHAR(fen);
|
||||||
if (ch != ' ') {
|
if (ch != ' ') {
|
||||||
abort();
|
__builtin_trap();
|
||||||
}
|
}
|
||||||
b->pos.castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE] = true;
|
b->pos.castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE] = true;
|
||||||
b->pos.castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE] = true;
|
b->pos.castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE] = true;
|
||||||
@@ -1147,8 +1067,7 @@ static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
|||||||
case ' ': break;
|
case ' ': break;
|
||||||
case '-': break;
|
case '-': break;
|
||||||
default: {
|
default: {
|
||||||
fprintf(stderr, "unexpected char '%c'\n", ch);
|
__builtin_trap();
|
||||||
abort();
|
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
} while (ch != ' ');
|
} while (ch != ' ');
|
||||||
@@ -1157,8 +1076,7 @@ static bool board_load_fen_unsafe(struct board* b, char const* fen_str)
|
|||||||
{ /* en passent */
|
{ /* en passent */
|
||||||
char const ch = BUF_GETCHAR(fen);
|
char const ch = BUF_GETCHAR(fen);
|
||||||
if (ch != '-') {
|
if (ch != '-') {
|
||||||
fprintf(stderr, "ep targets in fen not implemented yet\n");
|
__builtin_trap();
|
||||||
abort();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1522,14 +1440,14 @@ static void init_zobrist()
|
|||||||
for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) {
|
for (enum square_index sq = SQ_INDEX_BEGIN; sq < SQ_INDEX_COUNT; ++sq) {
|
||||||
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) {
|
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) {
|
||||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||||
zobrist.piece_keys[sq][pl][piece] = rand64();
|
zobrist.piece_keys[sq][pl][piece] = my_rand64();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zobrist.ep_targets[sq] = rand64();
|
zobrist.ep_targets[sq] = my_rand64();
|
||||||
}
|
}
|
||||||
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) {
|
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) {
|
||||||
for (enum castle_direction d = CASTLE_BEGIN; d < CASTLE_COUNT; ++d) {
|
for (enum castle_direction d = CASTLE_BEGIN; d < CASTLE_COUNT; ++d) {
|
||||||
zobrist.castling_keys[pl][d] = rand64();
|
zobrist.castling_keys[pl][d] = my_rand64();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
zobrist.init = true;
|
zobrist.init = true;
|
||||||
@@ -1573,10 +1491,20 @@ static inline uint64_t tt_hash_switch_player(uint64_t hash)
|
|||||||
return ~hash;
|
return ~hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline struct search_option tt_get(struct tt const* tt, uint64_t hash)
|
static inline struct search_option tt_get(struct tt* tt, uint64_t hash)
|
||||||
{
|
{
|
||||||
uint64_t const addr = hash % TT_ENTRIES;
|
uint64_t const addr = hash % TT_ENTRIES;
|
||||||
return tt->entries[addr];
|
struct search_option tte = tt->entries[addr];
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
tt->probes += 1;
|
||||||
|
if (tte.init && tte.hash == hash) {
|
||||||
|
tt->hits += 1;
|
||||||
|
} else if (tte.init && tte.hash != hash) {
|
||||||
|
tt->collisions += 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return tte;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so)
|
static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option so)
|
||||||
@@ -1584,17 +1512,19 @@ static inline void tt_insert(struct tt* tt, uint64_t hash, struct search_option
|
|||||||
uint64_t const addr = hash % TT_ENTRIES;
|
uint64_t const addr = hash % TT_ENTRIES;
|
||||||
so.init = true;
|
so.init = true;
|
||||||
tt->entries[addr] = so;
|
tt->entries[addr] = so;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
tt->insertions += 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
enum move_result {
|
enum move_result {
|
||||||
MR_NORMAL,
|
MR_NORMAL,
|
||||||
MR_CHECK,
|
MR_CHECK,
|
||||||
|
MR_REPEATS, /* this board state has been observed before */
|
||||||
MR_STALEMATE,
|
MR_STALEMATE,
|
||||||
MR_CHECKMATE,
|
MR_CHECKMATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void board_print(const struct pos* pos, struct move* move, FILE* out);
|
|
||||||
|
|
||||||
/* does not check validity */
|
/* does not check validity */
|
||||||
static enum move_result board_move(struct pos* restrict pos,
|
static enum move_result board_move(struct pos* restrict pos,
|
||||||
struct history* restrict hist,
|
struct history* restrict hist,
|
||||||
@@ -1604,20 +1534,6 @@ static enum move_result board_move(struct pos* restrict pos,
|
|||||||
enum player const us = pos->player;
|
enum player const us = pos->player;
|
||||||
enum player const them = opposite_player(us);
|
enum player const them = opposite_player(us);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (SQ_MASK_FROM_INDEX(move.to) & pos->pieces[them][PIECE_KING]) {
|
|
||||||
fprintf(stderr, "fatal error: king capture\n");
|
|
||||||
board_print_fen(pos, stderr);
|
|
||||||
board_print(pos, &move, stderr);
|
|
||||||
for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
|
||||||
if (pos->pieces[us][p] & SQ_MASK_FROM_INDEX(move.from)) {
|
|
||||||
fprintf(stderr, "%s found on %s\n", piece_str[p], square_index_display[move.from]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
__builtin_trap();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum piece const from_piece = mailbox[move.from];
|
enum piece const from_piece = mailbox[move.from];
|
||||||
enum piece const to_piece = mailbox[move.to];
|
enum piece const to_piece = mailbox[move.to];
|
||||||
|
|
||||||
@@ -1749,26 +1665,35 @@ static enum move_result board_move(struct pos* restrict pos,
|
|||||||
pos->fullmoves += (pos->player == PLAYER_BLACK);
|
pos->fullmoves += (pos->player == PLAYER_BLACK);
|
||||||
pos->halfmoves += 1;
|
pos->halfmoves += 1;
|
||||||
|
|
||||||
assert(hist->length < 4096);
|
assert(hist->length < 64);
|
||||||
|
int repetitions = 0;
|
||||||
for (size_t i = 0; i < hist->length; ++i) {
|
for (size_t i = 0; i < hist->length; ++i) {
|
||||||
_Static_assert(sizeof *pos == sizeof hist->items[i]);
|
_Static_assert(sizeof *pos == sizeof hist->items[i]);
|
||||||
if (!memcmp(&hist->items[i].pieces, &pos->pieces, sizeof pos->pieces)
|
if (!my_memcmp(&hist->items[i].pieces, &pos->pieces, sizeof pos->pieces)
|
||||||
&& !memcmp(&hist->items[i].castling_illegal, &pos->castling_illegal, sizeof pos->castling_illegal)
|
&& !my_memcmp(&hist->items[i].castling_illegal, &pos->castling_illegal, sizeof pos->castling_illegal)
|
||||||
&& hist->items[i].player == pos->player
|
&& hist->items[i].player == pos->player
|
||||||
&& hist->items[i].ep_targets == pos->ep_targets)
|
&& hist->items[i].ep_targets == pos->ep_targets)
|
||||||
{
|
{
|
||||||
return MR_STALEMATE;
|
repetitions += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hist->items[hist->length++] = *pos;
|
hist->items[hist->length++] = *pos;
|
||||||
|
|
||||||
if (pos->halfmoves > 50) {
|
if (repetitions >= 3 || pos->halfmoves > 50) {
|
||||||
|
return MR_STALEMATE;
|
||||||
|
}
|
||||||
|
else if (repetitions > 0) {
|
||||||
|
return MR_REPEATS;
|
||||||
|
}
|
||||||
|
else if (pos->halfmoves > 50) {
|
||||||
return MR_STALEMATE;
|
return MR_STALEMATE;
|
||||||
}
|
}
|
||||||
|
else if (attacks_to(pos, pos->pieces[them][PIECE_KING], 0ULL, 0ULL)
|
||||||
if (attacks_to(pos, pos->pieces[them][PIECE_KING], 0ULL, 0ULL) & ~pos->occupied[them]) {
|
& ~pos->occupied[them]) {
|
||||||
return MR_CHECK;
|
return MR_CHECK;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return MR_NORMAL;
|
return MR_NORMAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1789,41 +1714,60 @@ static enum move_result board_move_2(struct board* b, struct move move)
|
|||||||
* */
|
* */
|
||||||
static double board_score_heuristic(struct pos const* pos)
|
static double board_score_heuristic(struct pos const* pos)
|
||||||
{
|
{
|
||||||
|
/* this function always evaluates from white's perspective before
|
||||||
|
eventually flipping the sign */
|
||||||
double score = 0.0;
|
double score = 0.0;
|
||||||
|
|
||||||
#define BOARD_CENTER ((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \
|
/* evaluations.h defines:
|
||||||
& ~(RANK_MASK_1 | RANK_MASK_2 | RANK_MASK_7 | RANK_MASK_8))
|
- POSITIONAL_MODIFIER_COUNT
|
||||||
static bitboard const positional_modifier_area[PIECE_COUNT] = {
|
- static struct {bitboard const area; double const val} const
|
||||||
[PIECE_PAWN] = BOARD_CENTER,
|
positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT];
|
||||||
[PIECE_KNIGHT] = BOARD_CENTER,
|
* */
|
||||||
[PIECE_QUEEN] = RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6,
|
#include "evaluations.h"
|
||||||
};
|
|
||||||
#undef BOARD_CENTER
|
|
||||||
static double const positional_modifier_factor[PIECE_COUNT] = {
|
|
||||||
[PIECE_PAWN] = 0.02,
|
|
||||||
[PIECE_KNIGHT] = 0.02,
|
|
||||||
[PIECE_QUEEN] = -0.03,
|
|
||||||
};
|
|
||||||
|
|
||||||
bitboard const white_threats = all_threats_from_player(pos, PLAYER_WHITE);
|
|
||||||
bitboard const black_threats = all_threats_from_player(pos, PLAYER_BLACK);
|
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"
|
|
||||||
for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
for (enum piece p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
||||||
score += 0.002*piece_value[p] *
|
/* raw material value */
|
||||||
((double)bitboard_popcount(white_threats & pos->pieces[PLAYER_BLACK][p]) -
|
|
||||||
(double)bitboard_popcount(black_threats & pos->pieces[PLAYER_WHITE][p]));
|
|
||||||
|
|
||||||
score += piece_value[p] *
|
score += piece_value[p] *
|
||||||
((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p]) -
|
((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p]) -
|
||||||
(double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p]));
|
(double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p]));
|
||||||
|
|
||||||
score += positional_modifier_factor[p] *
|
/* pawns defending pieces are desired */
|
||||||
((double)bitboard_popcount(pos->pieces[PLAYER_WHITE][p] & positional_modifier_area[p]) -
|
score += 0.05 * (
|
||||||
(double)bitboard_popcount(pos->pieces[PLAYER_BLACK][p] & positional_modifier_area[p]));
|
(double)bitboard_popcount(
|
||||||
|
pawn_attacks_white(pos->pieces[PLAYER_WHITE][PIECE_PAWN])
|
||||||
|
& pos->pieces[PLAYER_WHITE][p]
|
||||||
|
)
|
||||||
|
- (double)bitboard_popcount(
|
||||||
|
pawn_attacks_black(pos->pieces[PLAYER_WHITE][PIECE_PAWN])
|
||||||
|
& pos->pieces[PLAYER_WHITE][p]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* positional bonus, see evaluations.h */
|
||||||
|
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
|
||||||
|
score += positional_modifier[PLAYER_WHITE][i][p].val *
|
||||||
|
(
|
||||||
|
(double)bitboard_popcount(
|
||||||
|
pos->pieces[PLAYER_WHITE][p]
|
||||||
|
& positional_modifier[PLAYER_WHITE][i][p].area
|
||||||
|
)
|
||||||
|
- (double)bitboard_popcount(
|
||||||
|
pos->pieces[PLAYER_BLACK][p]
|
||||||
|
& positional_modifier[PLAYER_BLACK][i][p].area
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stacked pawns are bad */
|
||||||
|
const double k = 0.30;
|
||||||
|
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
||||||
|
uint64_t wstk = bitboard_popcount(pos->pieces[PLAYER_WHITE][PIECE_PAWN] & FILE_MASK(fi));
|
||||||
|
uint64_t bstk = bitboard_popcount(pos->pieces[PLAYER_BLACK][PIECE_PAWN] & FILE_MASK(fi));
|
||||||
|
|
||||||
|
score -= k * (double)(wstk - (wstk == 1));
|
||||||
|
score += k * (double)(bstk - (bstk == 1));
|
||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
double sign = (pos->player == PLAYER_WHITE) ? 1.0 : -1.0;
|
double sign = (pos->player == PLAYER_WHITE) ? 1.0 : -1.0;
|
||||||
|
|
||||||
@@ -1870,17 +1814,11 @@ double quiesce(struct pos const* pos,
|
|||||||
|
|
||||||
size_t move_count = 0;
|
size_t move_count = 0;
|
||||||
|
|
||||||
struct move* moves = malloc(sizeof(struct move[MOVE_MAX]));
|
struct move moves[MOVE_MAX];
|
||||||
if (!moves) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*struct move moves[MOVE_MAX];*/
|
|
||||||
|
|
||||||
all_moves(pos, us, &move_count, moves);
|
all_moves(pos, us, &move_count, moves);
|
||||||
if (move_count == 0) {
|
if (move_count == 0) {
|
||||||
/* TODO: detect stalemate */
|
/* TODO: detect stalemate */
|
||||||
free(moves);
|
|
||||||
return -(999.0 + (double)depth);
|
return -(999.0 + (double)depth);
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < move_count; ++i) {
|
for (size_t i = 0; i < move_count; ++i) {
|
||||||
@@ -1893,31 +1831,18 @@ double quiesce(struct pos const* pos,
|
|||||||
if ((m.attr & MATTR_CAPTURE) == 0ULL) {
|
if ((m.attr & MATTR_CAPTURE) == 0ULL) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
struct pos poscpy = *pos;
|
||||||
|
|
||||||
|
enum piece mailbox_cpy[SQ_INDEX_COUNT];
|
||||||
|
my_memcpy(mailbox_cpy, mailbox, sizeof (enum piece[SQ_INDEX_COUNT]));
|
||||||
|
|
||||||
/* TODO: make lean apply/undo mechanism instead of copying,
|
|
||||||
* use of malloc is particularly horrendous */
|
|
||||||
struct pos* poscpy = malloc(sizeof *poscpy);
|
|
||||||
if (!poscpy) {
|
|
||||||
perror("malloc");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
*poscpy = *pos;
|
|
||||||
enum piece* mailbox_cpy = malloc(sizeof (enum piece[SQ_INDEX_COUNT]));
|
|
||||||
if (!mailbox_cpy) {
|
|
||||||
perror("malloc");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
memcpy(mailbox_cpy, mailbox, sizeof (enum piece[SQ_INDEX_COUNT]));
|
|
||||||
|
|
||||||
/* history is irrelevant when all moves are captures */
|
/* history is irrelevant when all moves are captures */
|
||||||
static struct history hist;
|
static struct history hist;
|
||||||
hist.length = 0;
|
hist.length = 0;
|
||||||
(void)board_move(poscpy, &hist, mailbox_cpy, m);
|
(void)board_move(&poscpy, &hist, mailbox_cpy, m);
|
||||||
|
|
||||||
score = -quiesce(poscpy, mailbox_cpy, them, -beta, -alpha, depth - 1);
|
score = -quiesce(&poscpy, mailbox_cpy, them, -beta, -alpha, depth - 1);
|
||||||
|
|
||||||
free(poscpy);
|
|
||||||
free(mailbox_cpy);
|
|
||||||
|
|
||||||
if (score >= beta) {
|
if (score >= beta) {
|
||||||
highscore = score;
|
highscore = score;
|
||||||
@@ -1931,29 +1856,25 @@ double quiesce(struct pos const* pos,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(moves);
|
|
||||||
|
|
||||||
return highscore;
|
return highscore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
struct search_option alphabeta_search(struct pos const* pos,
|
struct search_option alphabeta_search(struct pos const* pos,
|
||||||
struct history* hist,
|
struct history* hist,
|
||||||
struct tt* tt,
|
struct tt* tt,
|
||||||
enum piece mailbox[restrict static SQ_INDEX_COUNT],
|
enum piece mailbox[restrict static SQ_INDEX_COUNT],
|
||||||
enum player us,
|
enum player us,
|
||||||
int8_t depth,
|
int8_t depth,
|
||||||
|
uint64_t mattr_filter,
|
||||||
double alpha,
|
double alpha,
|
||||||
double beta)
|
double beta)
|
||||||
{
|
{
|
||||||
|
|
||||||
const double alpha_orig = alpha;
|
|
||||||
|
|
||||||
// Terminal / leaf
|
|
||||||
if (depth <= 0) {
|
if (depth <= 0) {
|
||||||
return (struct search_option) {
|
return (struct search_option) {
|
||||||
/*.score = quiesce(pos, mailbox, us, alpha, beta, depth),*/
|
.score = quiesce(pos, mailbox, us, alpha, beta, 0),
|
||||||
.score = board_score_heuristic(pos),
|
/*.score = board_score_heuristic(pos),*/
|
||||||
.move = (struct move){0},
|
.move = (struct move){0},
|
||||||
.depth = 0,
|
.depth = 0,
|
||||||
.hash = pos->hash,
|
.hash = pos->hash,
|
||||||
@@ -1962,17 +1883,9 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TT probe at current node */
|
double const alpha_orig = alpha;
|
||||||
global_stats.tt_probes += 1;
|
|
||||||
struct search_option tte = tt_get(tt, pos->hash);
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
struct search_option tte = tt_get(tt, pos->hash);
|
||||||
if (tte.init && tte.hash == pos->hash) {
|
|
||||||
global_stats.tt_hits += 1;
|
|
||||||
} else if (tte.init && tte.hash != pos->hash) {
|
|
||||||
global_stats.tt_collisions += 1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (tte.init && tte.hash == pos->hash && tte.depth >= depth) {
|
if (tte.init && tte.hash == pos->hash && tte.depth >= depth) {
|
||||||
if (tte.flag == TT_EXACT) {
|
if (tte.flag == TT_EXACT) {
|
||||||
@@ -1992,7 +1905,6 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
|
|
||||||
if (move_count == 0) {
|
if (move_count == 0) {
|
||||||
/* TODO: reusing mate distances correctly needs ply normalization */
|
/* TODO: reusing mate distances correctly needs ply normalization */
|
||||||
/* TODO: detect stalemate */
|
|
||||||
double score = 0;
|
double score = 0;
|
||||||
if (attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) != 0ULL) {
|
if (attacks_to(pos, pos->pieces[us][PIECE_KING], 0ULL, 0ULL) != 0ULL) {
|
||||||
score = -(999.0 + (double)depth);
|
score = -(999.0 + (double)depth);
|
||||||
@@ -2027,16 +1939,20 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
while (move_count) {
|
while (move_count) {
|
||||||
struct move m = moves_linear_search(moves, &move_count);
|
struct move m = moves_linear_search(moves, &move_count);
|
||||||
|
|
||||||
|
if (mattr_filter && !(m.attr & mattr_filter)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* TODO: make lean apply/undo mechanism instead of copying */
|
/* TODO: make lean apply/undo mechanism instead of copying */
|
||||||
struct pos poscpy = *pos;
|
struct pos poscpy = *pos;
|
||||||
enum piece mailbox_cpy[SQ_INDEX_COUNT];
|
enum piece mailbox_cpy[SQ_INDEX_COUNT];
|
||||||
memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy);
|
my_memcpy(mailbox_cpy, mailbox, sizeof mailbox_cpy);
|
||||||
size_t old_hist_length = hist->length;
|
size_t old_hist_length = hist->length;
|
||||||
|
|
||||||
enum move_result const r = board_move(&poscpy, hist, mailbox_cpy, m);
|
enum move_result const r = board_move(&poscpy, hist, mailbox_cpy, m);
|
||||||
|
|
||||||
double score;
|
double score;
|
||||||
if (r == MR_STALEMATE) {
|
if (r == MR_STALEMATE || r == MR_REPEATS) {
|
||||||
score = 0.0;
|
score = 0.0;
|
||||||
} else {
|
} else {
|
||||||
score = -alphabeta_search(&poscpy,
|
score = -alphabeta_search(&poscpy,
|
||||||
@@ -2045,6 +1961,7 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
mailbox_cpy,
|
mailbox_cpy,
|
||||||
opposite_player(us),
|
opposite_player(us),
|
||||||
depth - 1,
|
depth - 1,
|
||||||
|
mattr_filter,
|
||||||
-beta,
|
-beta,
|
||||||
-alpha).score;
|
-alpha).score;
|
||||||
}
|
}
|
||||||
@@ -2089,15 +2006,19 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct move search(struct board* b, enum player us, int8_t max_depth)
|
static struct search_result {struct move move; double score;}
|
||||||
|
search(struct board* b, enum player us, int8_t max_depth)
|
||||||
{
|
{
|
||||||
|
#if 1
|
||||||
if (b->tt.entries == NULL) {
|
if (b->tt.entries == NULL) {
|
||||||
b->tt.entries = calloc(TT_ENTRIES, sizeof b->tt.entries[0]);
|
b->tt.entries = sys_mmap_anon_shared(TT_ENTRIES * sizeof b->tt.entries[0],
|
||||||
|
SYS_PROT_READ | SYS_PROT_WRITE,
|
||||||
|
SYS_MADV_RANDOM);
|
||||||
if (b->tt.entries == NULL) {
|
if (b->tt.entries == NULL) {
|
||||||
perror("calloc");
|
__builtin_trap();
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
struct move best_move = {0};
|
struct move best_move = {0};
|
||||||
|
|
||||||
@@ -2106,13 +2027,13 @@ struct move search(struct board* b, enum player us, int8_t max_depth)
|
|||||||
#define SCORE_INF 1e80
|
#define SCORE_INF 1e80
|
||||||
|
|
||||||
for (int8_t d = 1; d <= max_depth; ++d) {
|
for (int8_t d = 1; d <= max_depth; ++d) {
|
||||||
double window = 0.5;
|
double window = 2.0;
|
||||||
double alpha = score - window;
|
double alpha = score - window;
|
||||||
double beta = score + window;
|
double beta = score + window;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
struct search_option so =
|
struct search_option so =
|
||||||
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta);
|
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, 0, alpha, beta);
|
||||||
|
|
||||||
if (so.score > alpha && so.score < beta) {
|
if (so.score > alpha && so.score < beta) {
|
||||||
score = so.score;
|
score = so.score;
|
||||||
@@ -2132,136 +2053,11 @@ struct move search(struct board* b, enum player us, int8_t max_depth)
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
fprintf(stderr, "depth: %d\n", d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef SCORE_INF
|
#undef SCORE_INF
|
||||||
|
|
||||||
return best_move;
|
return (struct search_result){.move = best_move, .score = score};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
|
||||||
{
|
|
||||||
int buf[8][8] = {0};
|
|
||||||
|
|
||||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
|
||||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
|
||||||
bitboard x = pos->pieces[player][piece];
|
|
||||||
|
|
||||||
for (index i = 7; i < 8; i--) {
|
|
||||||
for (index j = 0; j < 8; ++j) {
|
|
||||||
if (x & (1ULL<<(i*8+j))) {
|
|
||||||
buf[i][j] = piece_unicode[player][piece];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */
|
|
||||||
setlocale(LC_ALL, "");
|
|
||||||
for (index i = 7; i < 8; i--) {
|
|
||||||
fprintf(out, "\033[0m"); /* reset background color */
|
|
||||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
|
||||||
for (index j = 0; j < 8; ++j) {
|
|
||||||
index const n = INDEX_FROM_RF(i,j);
|
|
||||||
if (move && n == move->from) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 104); /* 44: blue*/
|
|
||||||
} else if (move && n == move->to) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
|
||||||
} else {
|
|
||||||
/* 45: magenta, 47: white */
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
|
||||||
}
|
|
||||||
if (buf[i][j]) {
|
|
||||||
fprintf(out, "%lc ", buf[i][j]);
|
|
||||||
} else {
|
|
||||||
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
|
||||||
is not working when buf[i][j] = ' ' */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(out, "\033[0m"); /* reset background color */
|
|
||||||
fprintf(out, "\n");
|
|
||||||
}
|
|
||||||
fprintf(out, " A B C D E F G H \n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bitboard_print(bitboard b, FILE* out)
|
|
||||||
{
|
|
||||||
setlocale(LC_ALL, "");
|
|
||||||
fprintf(out, "\n");
|
|
||||||
for (index i = 7; i < 8; i--) {
|
|
||||||
fprintf(out, "\033[0m"); /* reset background color */
|
|
||||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
|
||||||
for (index j = 0; j < 8; ++j) {
|
|
||||||
index const n = INDEX_FROM_RF(i,j);
|
|
||||||
int color;
|
|
||||||
if ((b >> n) & 1) {
|
|
||||||
/* 41: red */
|
|
||||||
color = 41;
|
|
||||||
} else {
|
|
||||||
/* 45: magenta, 47: white */
|
|
||||||
color = (i+j)%2 ? 45 : 47;
|
|
||||||
}
|
|
||||||
fprintf(out, "\033[30;%dm", color);
|
|
||||||
fprintf(out, " ");
|
|
||||||
}
|
|
||||||
fprintf(out, "\033[0m"); /* reset background color */
|
|
||||||
fprintf(out, "\n");
|
|
||||||
}
|
|
||||||
fprintf(out, " A B C D E F G H \n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void board_print_threats(const struct pos* pos, FILE* out, struct move* move)
|
|
||||||
{
|
|
||||||
enum player const player = pos->player;
|
|
||||||
int buf[8][8] = {0};
|
|
||||||
|
|
||||||
bitboard const threats = all_threats_from_player(pos, player);
|
|
||||||
|
|
||||||
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
|
||||||
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
|
||||||
bitboard x = pos->pieces[player][piece];
|
|
||||||
|
|
||||||
for (index i = 7; i < 8; i--) {
|
|
||||||
for (index j = 0; j < 8; ++j) {
|
|
||||||
if (x & (1ULL<<(i*8+j))) {
|
|
||||||
buf[i][j] = piece_unicode[player][piece];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */
|
|
||||||
setlocale(LC_ALL, ""); /* needed for unicode symbols */
|
|
||||||
for (index i = 7; i < 8; i--) {
|
|
||||||
fprintf(out, "\033[0m"); /* reset background color */
|
|
||||||
fprintf(out, "%"INDEX_FMT" ", i+1);
|
|
||||||
for (index j = 0; j < 8; ++j) {
|
|
||||||
index const n = INDEX_FROM_RF(i,j);
|
|
||||||
|
|
||||||
if (move && n == move->from) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 44: blue*/
|
|
||||||
} else if (move && n == move->to) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
|
||||||
}
|
|
||||||
else if ((threats >> n) & 1) {
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, 41); /* 41: red */
|
|
||||||
} else {
|
|
||||||
/* 45: magenta, 47: white */
|
|
||||||
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
|
||||||
}
|
|
||||||
if (buf[i][j]) {
|
|
||||||
fprintf(out, "%lc ", buf[i][j]);
|
|
||||||
} else {
|
|
||||||
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
|
||||||
is not working when buf[i][j] = ' ' */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(out, "\033[0m"); /* reset background color */
|
|
||||||
fprintf(out, "\n");
|
|
||||||
}
|
|
||||||
fprintf(out, " A B C D E F G H \n");
|
|
||||||
}
|
|
||||||
|
|||||||
244
board_print.h
Normal file
244
board_print.h
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <locale.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#define BITBOARD_FMT PRIu64
|
||||||
|
#define BITBOARD_FMT_X PRIx64
|
||||||
|
|
||||||
|
#define INDEX_FMT PRIu8
|
||||||
|
|
||||||
|
static void bitboard_print(bitboard b, FILE* out)
|
||||||
|
{
|
||||||
|
setlocale(LC_ALL, "");
|
||||||
|
fprintf(out, "\n");
|
||||||
|
for (index i = 7; i < 8; i--) {
|
||||||
|
fprintf(out, "\033[0m"); /* reset background color */
|
||||||
|
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||||
|
for (index j = 0; j < 8; ++j) {
|
||||||
|
index const n = INDEX_FROM_RF(i,j);
|
||||||
|
int color;
|
||||||
|
if ((b >> n) & 1) {
|
||||||
|
/* 41: red */
|
||||||
|
color = 41;
|
||||||
|
} else {
|
||||||
|
/* 45: magenta, 47: white */
|
||||||
|
color = (i+j)%2 ? 45 : 47;
|
||||||
|
}
|
||||||
|
fprintf(out, "\033[30;%dm", color);
|
||||||
|
fprintf(out, " ");
|
||||||
|
}
|
||||||
|
fprintf(out, "\033[0m"); /* reset background color */
|
||||||
|
fprintf(out, "\n");
|
||||||
|
}
|
||||||
|
fprintf(out, " A B C D E F G H \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void board_print_threats(const struct pos* pos, FILE* out, struct move* move)
|
||||||
|
{
|
||||||
|
enum player const player = pos->player;
|
||||||
|
int buf[8][8] = {0};
|
||||||
|
|
||||||
|
bitboard const threats = all_threats_from_player(pos, player);
|
||||||
|
|
||||||
|
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||||
|
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||||
|
bitboard x = pos->pieces[player][piece];
|
||||||
|
|
||||||
|
for (index i = 7; i < 8; i--) {
|
||||||
|
for (index j = 0; j < 8; ++j) {
|
||||||
|
if (x & (1ULL<<(i*8+j))) {
|
||||||
|
buf[i][j] = piece_unicode[player][piece];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */
|
||||||
|
setlocale(LC_ALL, ""); /* needed for unicode symbols */
|
||||||
|
for (index i = 7; i < 8; i--) {
|
||||||
|
fprintf(out, "\033[0m"); /* reset background color */
|
||||||
|
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||||
|
for (index j = 0; j < 8; ++j) {
|
||||||
|
index const n = INDEX_FROM_RF(i,j);
|
||||||
|
|
||||||
|
if (move && n == move->from) {
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, 44); /* 44: blue*/
|
||||||
|
} else if (move && n == move->to) {
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
||||||
|
}
|
||||||
|
else if ((threats >> n) & 1) {
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, 41); /* 41: red */
|
||||||
|
} else {
|
||||||
|
/* 45: magenta, 47: white */
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
||||||
|
}
|
||||||
|
if (buf[i][j]) {
|
||||||
|
fprintf(out, "%lc ", buf[i][j]);
|
||||||
|
} else {
|
||||||
|
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
||||||
|
is not working when buf[i][j] = ' ' */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(out, "\033[0m"); /* reset background color */
|
||||||
|
fprintf(out, "\n");
|
||||||
|
}
|
||||||
|
fprintf(out, " A B C D E F G H \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tt_print_stats(struct tt* tt, FILE* out)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
double sat = 0;
|
||||||
|
for (size_t i = 0; i < TT_ENTRIES; ++i) {
|
||||||
|
if (tt->entries[i].init)
|
||||||
|
sat += 1.0;
|
||||||
|
}
|
||||||
|
double const pct = sat/TT_ENTRIES;
|
||||||
|
|
||||||
|
fprintf(out, "---- Stats ---\n");
|
||||||
|
fprintf(out, "tt collisions: %"PRIu64"\n", tt->collisions);
|
||||||
|
fprintf(out, "tt hits: %"PRIu64"\n", tt->hits);
|
||||||
|
fprintf(out, "tt probes: %"PRIu64"\n", tt->probes);
|
||||||
|
fprintf(out, "tt insertions: %"PRIu64"\n", tt->insertions);
|
||||||
|
fprintf(out, "saturation: %.02lf\n", pct);
|
||||||
|
#else
|
||||||
|
fprintf(out, "stats not available with NDEBUG\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void board_print_fen(struct pos const* pos, FILE* out)
|
||||||
|
{
|
||||||
|
int buf[8][8] = {0};
|
||||||
|
|
||||||
|
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||||
|
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||||
|
bitboard x = pos->pieces[player][piece];
|
||||||
|
|
||||||
|
for (index i = 7; i < 8; i--) {
|
||||||
|
for (index j = 0; j < 8; ++j) {
|
||||||
|
if (x & (1ULL<<(i*8+j))) {
|
||||||
|
buf[i][j] = piece_char[player][piece];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (enum rank_index i = RANK_INDEX_8; i < RANK_INDEX_COUNT; --i) {
|
||||||
|
int consequtive_empty = 0;
|
||||||
|
for (enum file_index j = FILE_INDEX_A; j < FILE_INDEX_COUNT; ++j) {
|
||||||
|
if (buf[i][j]) {
|
||||||
|
if (consequtive_empty) {
|
||||||
|
fprintf(out, "%d", consequtive_empty);
|
||||||
|
consequtive_empty = 0;
|
||||||
|
}
|
||||||
|
fprintf(out, "%lc", buf[i][j]);
|
||||||
|
} else {
|
||||||
|
consequtive_empty += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (consequtive_empty) {
|
||||||
|
fprintf(out, "%d", consequtive_empty);
|
||||||
|
}
|
||||||
|
if (i > 0)
|
||||||
|
fprintf(out, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(out, " %c ", pos->player == PLAYER_WHITE ? 'w' : 'b');
|
||||||
|
|
||||||
|
bool any_castle = false;
|
||||||
|
if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_KINGSIDE]) {
|
||||||
|
fprintf(out, "K");
|
||||||
|
any_castle = true;
|
||||||
|
}
|
||||||
|
if (!pos->castling_illegal[PLAYER_WHITE][CASTLE_QUEENSIDE]) {
|
||||||
|
fprintf(out, "Q");
|
||||||
|
any_castle = true;
|
||||||
|
}
|
||||||
|
if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_KINGSIDE]) {
|
||||||
|
fprintf(out, "k");
|
||||||
|
any_castle = true;
|
||||||
|
}
|
||||||
|
if (!pos->castling_illegal[PLAYER_BLACK][CASTLE_QUEENSIDE]) {
|
||||||
|
fprintf(out, "q");
|
||||||
|
any_castle = true;
|
||||||
|
}
|
||||||
|
if (!any_castle) {
|
||||||
|
fprintf(out, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos->ep_targets) {
|
||||||
|
/* should be ep target square in algebraic notation */
|
||||||
|
enum square_index const sqi = bitboard_lsb(pos->ep_targets);
|
||||||
|
|
||||||
|
assert(sqi >= SQ_INDEX_BEGIN && sqi < SQ_INDEX_COUNT);
|
||||||
|
|
||||||
|
enum file_index const fi = index_to_file(sqi);
|
||||||
|
enum rank_index const ri = index_to_rank(sqi);
|
||||||
|
|
||||||
|
int const fch = tolower(file_index_char[fi]);
|
||||||
|
int const rch = tolower(rank_index_char[ri]);
|
||||||
|
|
||||||
|
assert(fch >= 'a' && fch <= 'h');
|
||||||
|
assert(rch >= '1' && rch <= '8');
|
||||||
|
|
||||||
|
fprintf(out, " %c%c", fch, rch);
|
||||||
|
} else {
|
||||||
|
fprintf(out, " -");
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(out, " %d", pos->halfmoves);
|
||||||
|
fprintf(out, " %d", pos->fullmoves);
|
||||||
|
|
||||||
|
fprintf(out, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void board_print(const struct pos* pos, struct move* move, FILE* out)
|
||||||
|
{
|
||||||
|
int buf[8][8] = {0};
|
||||||
|
|
||||||
|
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; ++player) {
|
||||||
|
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; ++piece) {
|
||||||
|
bitboard x = pos->pieces[player][piece];
|
||||||
|
|
||||||
|
for (index i = 7; i < 8; i--) {
|
||||||
|
for (index j = 0; j < 8; ++j) {
|
||||||
|
if (x & (1ULL<<(i*8+j))) {
|
||||||
|
buf[i][j] = piece_unicode[player][piece];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* see: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors */
|
||||||
|
setlocale(LC_ALL, "");
|
||||||
|
for (index i = 7; i < 8; i--) {
|
||||||
|
fprintf(out, "\033[0m"); /* reset background color */
|
||||||
|
fprintf(out, "%"INDEX_FMT" ", i+1);
|
||||||
|
for (index j = 0; j < 8; ++j) {
|
||||||
|
index const n = INDEX_FROM_RF(i,j);
|
||||||
|
if (move && n == move->from) {
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, 104); /* 44: blue*/
|
||||||
|
} else if (move && n == move->to) {
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, 44); /* 104: bright blue*/
|
||||||
|
} else {
|
||||||
|
/* 45: magenta, 47: white */
|
||||||
|
fprintf(out, "\033[%d;%dm", 30, (i+j) % 2 ? 45 : 47);
|
||||||
|
}
|
||||||
|
if (buf[i][j]) {
|
||||||
|
fprintf(out, "%lc ", buf[i][j]);
|
||||||
|
} else {
|
||||||
|
fprintf(out, " "); /* idk why this hack is needed but "%lc "
|
||||||
|
is not working when buf[i][j] = ' ' */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(out, "\033[0m"); /* reset background color */
|
||||||
|
fprintf(out, "\n");
|
||||||
|
}
|
||||||
|
fprintf(out, " A B C D E F G H \n");
|
||||||
|
}
|
||||||
|
|
||||||
35
chess.html
Normal file
35
chess.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Wasm Loader</title>
|
||||||
|
<script type="module" src="./chess.js" defer></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, 60px);
|
||||||
|
grid-template-rows: repeat(8, 60px);
|
||||||
|
width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 57px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square.light {
|
||||||
|
background: #f0d9b5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square.dark {
|
||||||
|
background: #b58863;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="board"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
232
chess.js
Normal file
232
chess.js
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
|
||||||
|
const WasmHost = (() => {
|
||||||
|
async function load(url, imports = {}) {
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
const bytes = await res.arrayBuffer();
|
||||||
|
const result = await WebAssembly.instantiate(bytes, imports);
|
||||||
|
const instance = result.instance ?? result;
|
||||||
|
return { instance, exports: instance.exports };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFn(exportsObj, name) {
|
||||||
|
const fn = exportsObj[name];
|
||||||
|
if (typeof fn !== "function") {
|
||||||
|
throw new Error(`Export "${name}" is not a function`);
|
||||||
|
}
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { load, getFn };
|
||||||
|
})();
|
||||||
|
|
||||||
|
/*
|
||||||
|
static inline struct move wasm_MoveTo_move(uint64_t m)
|
||||||
|
{
|
||||||
|
return (struct move) {
|
||||||
|
.appeal = (m >> 24) & 0xFF,
|
||||||
|
.attr = (m >> 16) & 0xFF,
|
||||||
|
.from = (m >> 8) & 0xFF,
|
||||||
|
.to = (m >> 0) & 0xFF,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const MoveResult = Object.freeze({
|
||||||
|
Normal: 0,
|
||||||
|
Check: 1,
|
||||||
|
Repeats: 2,
|
||||||
|
Stalemate: 3,
|
||||||
|
Checkmate: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _moveResultName = [
|
||||||
|
"Normal",
|
||||||
|
"Check",
|
||||||
|
"Repeats",
|
||||||
|
"Stalemate",
|
||||||
|
"Checkmate",
|
||||||
|
];
|
||||||
|
|
||||||
|
const Player = Object.freeze({
|
||||||
|
White: 0,
|
||||||
|
Black: 1,
|
||||||
|
None: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _playerName = [
|
||||||
|
"White",
|
||||||
|
"Black",
|
||||||
|
"None",
|
||||||
|
];
|
||||||
|
|
||||||
|
const Piece = Object.freeze({
|
||||||
|
Pawn: 0,
|
||||||
|
King: 1,
|
||||||
|
Queen: 2,
|
||||||
|
Bishop: 3,
|
||||||
|
Rook: 4,
|
||||||
|
Knight: 5,
|
||||||
|
Empty: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _pieceName = [
|
||||||
|
"Pawn",
|
||||||
|
"King",
|
||||||
|
"Queen",
|
||||||
|
"Bishop",
|
||||||
|
"Rook",
|
||||||
|
"Knight",
|
||||||
|
"Empty",
|
||||||
|
];
|
||||||
|
|
||||||
|
const _pieceChar = [
|
||||||
|
'P',
|
||||||
|
'K',
|
||||||
|
'Q',
|
||||||
|
'B',
|
||||||
|
'R',
|
||||||
|
'N',
|
||||||
|
' ',
|
||||||
|
];
|
||||||
|
|
||||||
|
const _pieceUnicode = [
|
||||||
|
['♙', '♔', '♕', '♗', '♖', '♘',], // white
|
||||||
|
['♟', '♚', '♛', '♝', '♜', '♞',], // black
|
||||||
|
['' , '', '', '', '', '', ], // none
|
||||||
|
];
|
||||||
|
|
||||||
|
const indexDeserialize = (n) => ({
|
||||||
|
rank: Math.floor(n / 8), // every day we stray further from God
|
||||||
|
file: (n % 8),
|
||||||
|
fileChar() { return ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'][this.file]; },
|
||||||
|
rankChar() { return ['1', '2', '3', '4', '5', '6', '7', '8'][this.rank]; },
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const indexFromCoord = (rank, file) => (8*rank + file);
|
||||||
|
|
||||||
|
const indexSerialize = (index) => (indexFromCoord(index.rank, index.file));
|
||||||
|
|
||||||
|
const moveDeserialize = (n) => ({
|
||||||
|
appeal: Number((n >> 24n) & 0xFFn),
|
||||||
|
attr: Number((n >> 16n) & 0xFFn),
|
||||||
|
from: indexDeserialize(Number((n >> 8n) & 0xFFn)),
|
||||||
|
to: indexDeserialize(Number((n >> 0n) & 0xFFn)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const moveSerialize = (move) => ((move.from & 0xFF) << 8) | ((move.to & 0xFF));
|
||||||
|
|
||||||
|
const squareDeserialize = (n) => ({
|
||||||
|
player: n == -1 ? Player.None : (n >> 8) & 0xFF,
|
||||||
|
piece: n == -1 ? Piece.Empty : (n & 0xFF),
|
||||||
|
playerName() {
|
||||||
|
if (this.player === Player.None) {
|
||||||
|
return "Empty";
|
||||||
|
} else {
|
||||||
|
return _playerName[this.player];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pieceName() {
|
||||||
|
if (this.player === Player.None) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return this.playerName() + _pieceName[this.piece];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pieceUnicode() {
|
||||||
|
return _pieceUnicode[this.player][this.piece];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
const { exports } = await WasmHost.load("./chess.wasm", {});
|
||||||
|
|
||||||
|
const wb_init = WasmHost.getFn(exports, "wb_init");
|
||||||
|
const wb_move = WasmHost.getFn(exports, "wb_move");
|
||||||
|
const wb_search = WasmHost.getFn(exports, "wb_search");
|
||||||
|
const wb_board_at = WasmHost.getFn(exports, "wb_board_at");
|
||||||
|
|
||||||
|
wb_init();
|
||||||
|
|
||||||
|
const drawBoard = () => {
|
||||||
|
const board = document.getElementById("board");
|
||||||
|
board.replaceChildren();
|
||||||
|
const filesChars = ["a","b","c","d","e","f","g","h"];
|
||||||
|
const ranksChars = ["8","7","6","5","4","3","2","1"];
|
||||||
|
|
||||||
|
for (let rank = 7; rank >= 0; --rank) {
|
||||||
|
for (let file = 0; file < 8; ++file) {
|
||||||
|
const square = document.createElement("div");
|
||||||
|
square.className =
|
||||||
|
"square " + ((file + rank) % 2 ? "dark" : "light");
|
||||||
|
square.dataset.file = filesChars[file];
|
||||||
|
square.dataset.rank = ranksChars[rank];
|
||||||
|
|
||||||
|
const x = wb_board_at(indexFromCoord(rank, file));
|
||||||
|
const sq = squareDeserialize(x);
|
||||||
|
square.textContent = sq.pieceUnicode();
|
||||||
|
|
||||||
|
board.appendChild(square);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
// next task, browser had a chance to repaint
|
||||||
|
}, 0.01);
|
||||||
|
}
|
||||||
|
drawBoard();
|
||||||
|
|
||||||
|
const nextFrame = () => new Promise(requestAnimationFrame);
|
||||||
|
|
||||||
|
for (let i = 0; i < 200; ++i) {
|
||||||
|
drawBoard();
|
||||||
|
|
||||||
|
await nextFrame();
|
||||||
|
|
||||||
|
const m = wb_search(7);
|
||||||
|
const move = moveDeserialize(m);
|
||||||
|
|
||||||
|
console.log(move.from);
|
||||||
|
console.log(move.to);
|
||||||
|
|
||||||
|
const fromSqEnc = wb_board_at(indexSerialize(move.from));
|
||||||
|
const toSqEnc = wb_board_at(indexSerialize(move.to));
|
||||||
|
|
||||||
|
console.log(fromSqEnc);
|
||||||
|
console.log(toSqEnc);
|
||||||
|
|
||||||
|
const fromSq = squareDeserialize(fromSqEnc);
|
||||||
|
const toSq = squareDeserialize(toSqEnc);
|
||||||
|
|
||||||
|
console.log(fromSq)
|
||||||
|
console.log(toSq)
|
||||||
|
|
||||||
|
console.log("from-player:", fromSq.playerName());
|
||||||
|
console.log("from-piece:", fromSq.pieceName());
|
||||||
|
|
||||||
|
console.log("to-player:", toSq.playerName());
|
||||||
|
console.log("to-piece:", toSq.pieceName());
|
||||||
|
|
||||||
|
console.log(m);
|
||||||
|
const f = move.from;
|
||||||
|
const t = move.to;
|
||||||
|
console.log("from:", move.from.fileChar(), move.from.rankChar(),
|
||||||
|
"to:", move.to.fileChar(), move.to.rankChar());
|
||||||
|
|
||||||
|
const mr = wb_move(m);
|
||||||
|
if (mr == MoveResult.Stalemate || mr == MoveResult.Checkmate) {
|
||||||
|
console.log(_moveResultName[mr]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
202
evaluations.h
Normal file
202
evaluations.h
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/* TODO: candidate for code-generation */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define BITBOARD_WHITE( \
|
||||||
|
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||||
|
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||||
|
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||||
|
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||||
|
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||||
|
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||||
|
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||||
|
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||||
|
(bitboard)\
|
||||||
|
0b##\
|
||||||
|
h8##g8##f8##e8##d8##c8##b8##a8##\
|
||||||
|
h7##g7##f7##e7##d7##c7##b7##a7##\
|
||||||
|
h6##g6##f6##e6##d6##c6##b6##a6##\
|
||||||
|
h5##g5##f5##e5##d5##c5##b5##a5##\
|
||||||
|
h4##g4##f4##e4##d4##c4##b4##a4##\
|
||||||
|
h3##g3##f3##e3##d3##c3##b3##a3##\
|
||||||
|
h2##g2##f2##e2##d2##c2##b2##a2##\
|
||||||
|
h1##g1##f1##e1##d1##c1##b1##a1##\
|
||||||
|
ULL
|
||||||
|
|
||||||
|
#define BITBOARD_BLACK( \
|
||||||
|
a8,b8,c8,d8,e8,f8,g8,h8, \
|
||||||
|
a7,b7,c7,d7,e7,f7,g7,h7, \
|
||||||
|
a6,b6,c6,d6,e6,f6,g6,h6, \
|
||||||
|
a5,b5,c5,d5,e5,f5,g5,h5, \
|
||||||
|
a4,b4,c4,d4,e4,f4,g4,h4, \
|
||||||
|
a3,b3,c3,d3,e3,f3,g3,h3, \
|
||||||
|
a2,b2,c2,d2,e2,f2,g2,h2, \
|
||||||
|
a1,b1,c1,d1,e1,f1,g1,h1) \
|
||||||
|
(bitboard)\
|
||||||
|
0b##\
|
||||||
|
h1##g1##f1##e1##d1##c1##b1##a1##\
|
||||||
|
h2##g2##f2##e2##d2##c2##b2##a2##\
|
||||||
|
h3##g3##f3##e3##d3##c3##b3##a3##\
|
||||||
|
h4##g4##f4##e4##d4##c4##b4##a4##\
|
||||||
|
h5##g5##f5##e5##d5##c5##b5##a5##\
|
||||||
|
h6##g6##f6##e6##d6##c6##b6##a6##\
|
||||||
|
h7##g7##f7##e7##d7##c7##b7##a7##\
|
||||||
|
h8##g8##f8##e8##d8##c8##b8##a8##\
|
||||||
|
ULL
|
||||||
|
|
||||||
|
#define RELATIVE_DIAGONAL_A1_H8 \
|
||||||
|
RELATIVE_BITBOARD( \
|
||||||
|
0,0,0,0,0,0,0,1, \
|
||||||
|
0,0,0,0,0,0,1,0, \
|
||||||
|
0,0,0,0,0,1,0,0, \
|
||||||
|
0,0,0,0,1,0,0,0, \
|
||||||
|
0,0,0,1,0,0,0,0, \
|
||||||
|
0,0,1,0,0,0,0,0, \
|
||||||
|
0,1,0,0,0,0,0,0, \
|
||||||
|
1,0,0,0,0,0,0,0)
|
||||||
|
|
||||||
|
#define RELATIVE_DIAGONAL_A8_H1 \
|
||||||
|
RELATIVE_BITBOARD( \
|
||||||
|
1,0,0,0,0,0,0,0, \
|
||||||
|
0,1,0,0,0,0,0,0, \
|
||||||
|
0,0,1,0,0,0,0,0, \
|
||||||
|
0,0,0,1,0,0,0,0, \
|
||||||
|
0,0,0,0,1,0,0,0, \
|
||||||
|
0,0,0,0,0,1,0,0, \
|
||||||
|
0,0,0,0,0,0,1,0, \
|
||||||
|
0,0,0,0,0,0,0,1)
|
||||||
|
|
||||||
|
#define RELATIVE_BISHOP_KING_ATTACK \
|
||||||
|
RELATIVE_BITBOARD( \
|
||||||
|
0,0,0,0,0,0,1,1, \
|
||||||
|
0,0,0,0,0,1,1,0, \
|
||||||
|
0,0,0,0,1,1,0,0, \
|
||||||
|
0,0,0,1,1,0,0,0, \
|
||||||
|
0,0,1,1,0,0,0,0, \
|
||||||
|
0,1,1,0,0,0,0,0, \
|
||||||
|
1,1,0,0,0,0,0,0, \
|
||||||
|
1,0,0,0,0,0,0,0)
|
||||||
|
|
||||||
|
#define RELATIVE_BISHOP_QUEEN_ATTACK \
|
||||||
|
RELATIVE_BITBOARD( \
|
||||||
|
1,1,0,0,0,0,0,0, \
|
||||||
|
0,1,1,0,0,0,0,0, \
|
||||||
|
0,0,1,1,0,0,0,0, \
|
||||||
|
0,0,0,1,1,0,0,0, \
|
||||||
|
0,0,0,0,1,1,0,0, \
|
||||||
|
0,0,0,0,0,1,1,0, \
|
||||||
|
0,0,0,0,0,0,1,1, \
|
||||||
|
0,0,0,0,0,0,0,1)
|
||||||
|
|
||||||
|
#define RELATIVE_KING_CASTLE_KINGSIDE \
|
||||||
|
RELATIVE_BITBOARD( \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,1,0)
|
||||||
|
|
||||||
|
#define RELATIVE_KING_CASTLE_QUEENSIDE \
|
||||||
|
RELATIVE_BITBOARD( \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,1,1,0,0,0,0,0)
|
||||||
|
|
||||||
|
#define CORNERS \
|
||||||
|
BITBOARD( \
|
||||||
|
1,0,0,0,0,0,0,1, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
1,0,0,0,0,0,0,1)
|
||||||
|
|
||||||
|
#define RELATIVE_PAWN_SAFE_ZONE \
|
||||||
|
BITBOARD( \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
0,0,0,0,0,0,0,0, \
|
||||||
|
1,0,0,0,0,0,0,0, \
|
||||||
|
1,0,0,1,1,0,0,0, \
|
||||||
|
1,1,1,1,1,0,0,0, \
|
||||||
|
1,1,1,1,1,0,0,1, \
|
||||||
|
1,1,1,1,1,1,1,1, \
|
||||||
|
0,0,0,0,0,0,0,0)
|
||||||
|
|
||||||
|
#define BOARD_CENTER_4X4 \
|
||||||
|
((FILE_MASK_C | FILE_MASK_D | FILE_MASK_E | FILE_MASK_F) \
|
||||||
|
& (RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6))
|
||||||
|
|
||||||
|
#define BOARD_CENTER_2X2 \
|
||||||
|
((FILE_MASK_D | FILE_MASK_E) \
|
||||||
|
& (RANK_MASK_4 | RANK_MASK_5))
|
||||||
|
|
||||||
|
|
||||||
|
#define POSITIONAL_BONUS_0 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, 0.02, BOARD_CENTER_4X4) \
|
||||||
|
X(PIECE_KNIGHT, 0.05, BOARD_CENTER_4X4) \
|
||||||
|
X(PIECE_BISHOP, 0.05, RELATIVE_DIAGONAL_A1_H8 | RELATIVE_DIAGONAL_A8_H1) \
|
||||||
|
X(PIECE_KING, 0.15, RELATIVE_KING_CASTLE_KINGSIDE) \
|
||||||
|
X(PIECE_QUEEN, -0.10, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define POSITIONAL_BONUS_1 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, 0.02, BOARD_CENTER_2X2) \
|
||||||
|
X(PIECE_BISHOP, 0.05, RELATIVE_BISHOP_KING_ATTACK) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define POSITIONAL_BONUS_2 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_PAWN, -0.10, ~RELATIVE_PAWN_SAFE_ZONE) \
|
||||||
|
X(PIECE_BISHOP, 0.05, CORNERS) \
|
||||||
|
/**/
|
||||||
|
|
||||||
|
#define POSITIONAL_BONUS_3 \
|
||||||
|
/* piece bonus area*/ \
|
||||||
|
X(PIECE_BISHOP, 0.02, RELATIVE_BISHOP_QUEEN_ATTACK)
|
||||||
|
|
||||||
|
#define POSITIONAL_MODIFIER_COUNT 4
|
||||||
|
static struct {bitboard const area; double const val;} const
|
||||||
|
positional_modifier[PLAYER_COUNT][POSITIONAL_MODIFIER_COUNT][PIECE_COUNT] = {
|
||||||
|
#define X(p, b, a) [p] = {.area = (a), .val = b},
|
||||||
|
#define RELATIVE_BITBOARD BITBOARD_WHITE
|
||||||
|
[PLAYER_WHITE] = {
|
||||||
|
{POSITIONAL_BONUS_0},
|
||||||
|
{POSITIONAL_BONUS_1},
|
||||||
|
{POSITIONAL_BONUS_2},
|
||||||
|
{POSITIONAL_BONUS_3},
|
||||||
|
},
|
||||||
|
[PLAYER_BLACK] = {
|
||||||
|
#undef RELATIVE_BITBOARD
|
||||||
|
#define RELATIVE_BITBOARD BITBOARD_BLACK
|
||||||
|
{POSITIONAL_BONUS_0},
|
||||||
|
{POSITIONAL_BONUS_1},
|
||||||
|
{POSITIONAL_BONUS_2},
|
||||||
|
{POSITIONAL_BONUS_3},
|
||||||
|
}
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#undef CORNERS
|
||||||
|
#undef BOARD_CENTER_4X4
|
||||||
|
#undef BOARD_CENTER_2X2
|
||||||
|
#undef BITBOARD_WHITE
|
||||||
|
#undef BITBOARD_BLACK
|
||||||
|
#undef RELATIVE_DIAGONAL_A1_H8
|
||||||
|
#undef RELATIVE_DIAGONAL_A8_H1
|
||||||
|
#undef RELATIVE_BISHOP_KING_ATTACK
|
||||||
|
#undef RELATIVE_BISHOP_QUEEN_ATTACK
|
||||||
|
#undef RELATIVE_KING_CASTLE_KINGSIDE
|
||||||
|
#undef RELATIVE_KING_CASTLE_QUEENSIDE
|
||||||
80
libc-lite.h
Normal file
80
libc-lite.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static uint64_t g_rand_seed = 1;
|
||||||
|
|
||||||
|
void my_srand(uint64_t n)
|
||||||
|
{
|
||||||
|
g_rand_seed = n == 0 ? 1 : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t my_rand64()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Xorshift RNG [1] + multiply
|
||||||
|
* [1]: https://www.jstatsoft.org/article/view/v008i14/916 */
|
||||||
|
uint64_t x = g_rand_seed;
|
||||||
|
x ^= x >> 12ULL;
|
||||||
|
x ^= x << 25ULL;
|
||||||
|
x ^= x >> 27ULL;
|
||||||
|
g_rand_seed = x;
|
||||||
|
return x * 2685821657736338717ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int my_tolower(int ch)
|
||||||
|
{
|
||||||
|
return ch >= 'A' && ch <= 'Z'
|
||||||
|
? ch - 'A' + 'a'
|
||||||
|
: ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int my_isdigit(int ch)
|
||||||
|
{
|
||||||
|
return ch >= '0' && ch <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
void* my_memset(void *b, int c, size_t len)
|
||||||
|
{
|
||||||
|
unsigned char* x = b;
|
||||||
|
unsigned char ch = (unsigned char)c;
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
x[i] = ch;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* my_memcpy(void* restrict dst, void const* restrict src, size_t n)
|
||||||
|
{
|
||||||
|
uint8_t* d = dst;
|
||||||
|
uint8_t const* s = src;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
d[i] = s[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
int my_memcmp(void const* s1, void const* s2, size_t n)
|
||||||
|
{
|
||||||
|
/* todo: SIMD optimize or use 64 bit strides initially if optimizer
|
||||||
|
doesn't catch this */
|
||||||
|
int8_t const* a = s1;
|
||||||
|
int8_t const* b = s2;
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
int8_t diff = a[i] - b[i];
|
||||||
|
if (diff)
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t strlen(char const* s)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
while (s[n++])
|
||||||
|
;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
194
sys.h
Normal file
194
sys.h
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(WASM)
|
||||||
|
#define PLATFORM_STR "Wasm"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum { /* TODO */
|
||||||
|
SYS_PROT_NONE = 0,
|
||||||
|
SYS_PROT_READ = 0,
|
||||||
|
SYS_PROT_WRITE = 0,
|
||||||
|
SYS_PROT_EXEC = 0,
|
||||||
|
SYS_MADV_RANDOM = 0,
|
||||||
|
SYS_MADV_SEQUENTIAL = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t g_buf[1024*1024*1024];
|
||||||
|
static size_t g_buf_len = 0;
|
||||||
|
|
||||||
|
static void* sys_mmap_anon_shared(size_t size, int, int)
|
||||||
|
{
|
||||||
|
/* FIXME: this program relies on few memory allocations, a simple bump
|
||||||
|
* allocator works for now, but will cause memory leaks in the future */
|
||||||
|
|
||||||
|
size = (size + 7ULL) & ~7ULL;
|
||||||
|
|
||||||
|
if (g_buf_len + size > sizeof g_buf) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void* x = &g_buf[g_buf_len];
|
||||||
|
g_buf_len += size;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__unix__) || defined(__unix) || \
|
||||||
|
(defined(__APPLE__) && defined(__MACH__))
|
||||||
|
#define PLATFORM_STR "Posix"
|
||||||
|
|
||||||
|
#define _DARWIN_C_SOURCE 1 /* defines MAP_ANON */
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#define index string_index
|
||||||
|
#include <string.h>
|
||||||
|
#undef index
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SYS_PROT_NONE = PROT_NONE,
|
||||||
|
SYS_PROT_READ = PROT_READ,
|
||||||
|
SYS_PROT_WRITE = PROT_WRITE,
|
||||||
|
SYS_PROT_EXEC = PROT_EXEC,
|
||||||
|
|
||||||
|
SYS_MADV_RANDOM = MADV_RANDOM,
|
||||||
|
SYS_MADV_SEQUENTIAL = MADV_SEQUENTIAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void* sys_mmap_anon_shared(size_t size, int prot, int madv)
|
||||||
|
{
|
||||||
|
void* x = mmap(NULL, size, prot, MAP_ANON | MAP_SHARED, -1, 0);
|
||||||
|
if (x == MAP_FAILED) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
fprintf(stderr, "%s (" PLATFORM_STR "): %s\n", __func__, strerror(errno));
|
||||||
|
#endif
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mmap is de-facto zero filled on all platforms ^1, but zero it just in case
|
||||||
|
[1]: https://stackoverflow.com/a/65084762 */
|
||||||
|
memset(x, 0, size);
|
||||||
|
|
||||||
|
if (madv) {
|
||||||
|
int ok = madvise(x, size, madv);
|
||||||
|
if (ok == -1) {
|
||||||
|
fprintf(stderr, "%s (" PLATFORM_STR "): %s\n", __func__, strerror(errno));
|
||||||
|
munmap(x, size);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
#define PLATFORM_STR "Win32"
|
||||||
|
|
||||||
|
/* TODO: this is untested */
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SYS_PROT_NONE = 0,
|
||||||
|
SYS_PROT_READ = 1 << 0,
|
||||||
|
SYS_PROT_WRITE = 1 << 1,
|
||||||
|
SYS_PROT_EXEC = 1 << 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DWORD sys_win_page_protect_from_prot(int prot)
|
||||||
|
{
|
||||||
|
const int r = (prot & SYS_PROT_READ) != 0;
|
||||||
|
const int w = (prot & SYS_PROT_WRITE) != 0;
|
||||||
|
const int x = (prot & SYS_PROT_EXEC) != 0;
|
||||||
|
|
||||||
|
if (!r && !w && !x)
|
||||||
|
return PAGE_NOACCESS;
|
||||||
|
|
||||||
|
if (x) {
|
||||||
|
if (w) return PAGE_EXECUTE_READWRITE;
|
||||||
|
if (r) return PAGE_EXECUTE_READ;
|
||||||
|
return PAGE_EXECUTE;
|
||||||
|
} else {
|
||||||
|
if (w) return PAGE_READWRITE; /* no write-only pages on Windows */
|
||||||
|
if (r) return PAGE_READONLY;
|
||||||
|
return PAGE_NOACCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sys_win_debug_print_last_error(const char* func_name)
|
||||||
|
{
|
||||||
|
#ifdef NDEBUG
|
||||||
|
(void)func_name
|
||||||
|
#else
|
||||||
|
DWORD err = GetLastError();
|
||||||
|
char* msg = NULL;
|
||||||
|
|
||||||
|
FormatMessageA(
|
||||||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||||
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
NULL,
|
||||||
|
err,
|
||||||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(LPSTR)&msg,
|
||||||
|
0,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (msg) {
|
||||||
|
fprintf(stderr, "%s (" PLATFORM_STR "): %s", func_name, msg);
|
||||||
|
LocalFree(msg);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "%s (" PLATFORM_STR "): GetLastError=%lu\n",
|
||||||
|
func_name, (unsigned long)err);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* sys_mmap_anon_shared(size_t size, int prot)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
DWORD protect = sys_win_page_protect_from_prot(prot);
|
||||||
|
|
||||||
|
/* VirtualAlloc doesn't support PAGE_NOACCESS */
|
||||||
|
DWORD alloc_protect = (protect == PAGE_NOACCESS)
|
||||||
|
? PAGE_READWRITE
|
||||||
|
: protect;
|
||||||
|
|
||||||
|
void* x = VirtualAlloc(
|
||||||
|
NULL,
|
||||||
|
size,
|
||||||
|
MEM_RESERVE | MEM_COMMIT,
|
||||||
|
alloc_protect
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!x) {
|
||||||
|
sys_win_debug_print_last_error(__func__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protect == PAGE_NOACCESS) {
|
||||||
|
DWORD oldp;
|
||||||
|
if (!VirtualProtect(p, size, PAGE_NOACCESS, &oldp)) {
|
||||||
|
sys_win_debug_print_last_error(__func__);
|
||||||
|
VirtualFree(p, 0, MEM_RELEASE);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#error Unsupported platform
|
||||||
|
|
||||||
|
#endif
|
||||||
49
tests.c
49
tests.c
@@ -3,9 +3,11 @@
|
|||||||
#include <unistd.h> /* usleep */
|
#include <unistd.h> /* usleep */
|
||||||
|
|
||||||
#include "base.h"
|
#include "base.h"
|
||||||
|
#include "board_print.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
/* Tests are mostly generated by ChatGPT */
|
/* Tests are mostly generated by ChatGPT */
|
||||||
|
|
||||||
@@ -388,9 +390,7 @@ static void test_bishops(void)
|
|||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|
||||||
printf("sizeof board: %zu\n", sizeof (struct board));
|
|
||||||
printf("sizeof pos: %zu\n", sizeof (struct pos));
|
printf("sizeof pos: %zu\n", sizeof (struct pos));
|
||||||
printf("sizeof mailbox: %zu\n", sizeof (struct board){0}.mailbox);
|
|
||||||
printf("sizeof tt: %zu\n", sizeof (struct tt));
|
printf("sizeof tt: %zu\n", sizeof (struct tt));
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
@@ -402,49 +402,60 @@ int main()
|
|||||||
fprintf(stdout, "\033[30;%dm ", i);
|
fprintf(stdout, "\033[30;%dm ", i);
|
||||||
}
|
}
|
||||||
fprintf(stdout, "\033[0m\n"); /* reset background color */
|
fprintf(stdout, "\033[0m\n"); /* reset background color */
|
||||||
struct board board = BOARD_INITIAL;
|
|
||||||
|
/* board is too big for the stack */
|
||||||
|
struct board* b = malloc(sizeof *b);
|
||||||
|
if (!b) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
*b = BOARD_INIT;
|
||||||
|
|
||||||
//board_load_fen_unsafe(&board, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1");
|
//board_load_fen_unsafe(&board, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1");
|
||||||
//board_load_fen_unsafe(&board, "5R2/7k/P7/6pp/3B4/1PPK2bP/4r3/8 b - - 3 57");
|
//board_load_fen_unsafe(&board, "5R2/7k/P7/6pp/3B4/1PPK2bP/4r3/8 b - - 3 57");
|
||||||
board_print_fen(&board.pos, stdout);
|
board_print_fen(&b->pos, stdout);
|
||||||
board_print(&board.pos, NULL, stdout);
|
board_print(&b->pos, NULL, stdout);
|
||||||
|
|
||||||
struct move moves[MOVE_MAX];
|
struct move moves[MOVE_MAX];
|
||||||
|
|
||||||
for (int turn = 0; turn < 200; ++turn) {
|
for (int turn = 0; turn < 200; ++turn) {
|
||||||
size_t move_count = 0;
|
size_t move_count = 0;
|
||||||
all_moves(&board.pos, board.pos.player, &move_count, moves);
|
all_moves(&b->pos, b->pos.player, &move_count, moves);
|
||||||
|
|
||||||
if (move_count == 0) {
|
if (move_count == 0) {
|
||||||
printf("no moves for %s, aborting\n", player_str[board.pos.player]);
|
printf("no moves for %s, aborting\n", player_str[b->pos.player]);
|
||||||
board_print_threats(&board.pos, stdout, NULL);
|
board_print_threats(&b->pos, stdout, NULL);
|
||||||
board.pos.player = opposite_player(board.pos.player);
|
b->pos.player = opposite_player(b->pos.player);
|
||||||
board_print_threats(&board.pos, stdout, NULL);
|
board_print_threats(&b->pos, stdout, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//struct move move = moves[0];
|
//struct move move = moves[0];
|
||||||
struct move move = search(&board, board.pos.player, 8);
|
struct search_result sr = search(b, b->pos.player, 7);
|
||||||
|
|
||||||
|
struct move move = sr.move;
|
||||||
|
double const score = sr.score;
|
||||||
|
|
||||||
printf("move %d: {\n"
|
printf("move %d: {\n"
|
||||||
" .from = %s, (%s)\n"
|
" .from = %s, (%s)\n"
|
||||||
" .to = %s,\n"
|
" .to = %s,\n"
|
||||||
|
" .score = %lf,\n"
|
||||||
" .mask = ",
|
" .mask = ",
|
||||||
turn,
|
turn,
|
||||||
square_index_display[move.from],
|
square_index_display[move.from],
|
||||||
piece_str[board.mailbox[move.from]],
|
piece_str[b->mailbox[move.from]],
|
||||||
square_index_display[move.to]
|
square_index_display[move.to],
|
||||||
|
score
|
||||||
);
|
);
|
||||||
if (move.attr & MATTR_CAPTURE) printf("MATTR_CAPTURE ");
|
if (move.attr & MATTR_CAPTURE) printf("MATTR_CAPTURE ");
|
||||||
if (move.attr & MATTR_PROMOTE) printf("MATTR_PROMOTE ");
|
if (move.attr & MATTR_PROMOTE) printf("MATTR_PROMOTE ");
|
||||||
printf("\n}\n");
|
printf("\n}\n");
|
||||||
|
|
||||||
enum move_result const r = board_move_2(&board, move);
|
enum move_result const r = board_move_2(b, move);
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
board_print_fen(&board.pos, stdout);
|
board_print_fen(&b->pos, stdout);
|
||||||
print_stats(stdout);
|
tt_print_stats(&b->tt, stdout);
|
||||||
board_print(&board.pos, &move, stdout);
|
board_print(&b->pos, &move, stdout);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (r == MR_STALEMATE) {
|
if (r == MR_STALEMATE) {
|
||||||
@@ -452,11 +463,11 @@ int main()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (board.pos.pieces[PLAYER_WHITE][PIECE_KING] == 0ULL) {
|
if (b->pos.pieces[PLAYER_WHITE][PIECE_KING] == 0ULL) {
|
||||||
printf("white king gone!!\n");
|
printf("white king gone!!\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (board.pos.pieces[PLAYER_BLACK][PIECE_KING] == 0ULL) {
|
if (b->pos.pieces[PLAYER_BLACK][PIECE_KING] == 0ULL) {
|
||||||
printf("black king gone!!\n");
|
printf("black king gone!!\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
71
wasm-compat.c
Normal file
71
wasm-compat.c
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static struct search_option g_tt_buf[TT_ENTRIES];
|
||||||
|
|
||||||
|
static struct board g_board;
|
||||||
|
|
||||||
|
static inline uint32_t move_serialize(struct move m)
|
||||||
|
{
|
||||||
|
_Static_assert(sizeof m.from * CHAR_BIT == 8,
|
||||||
|
"this must be checked if struct move's `from` changes");
|
||||||
|
_Static_assert(sizeof m.to * CHAR_BIT == 8,
|
||||||
|
"this must be checked if struct move's `to` changes");
|
||||||
|
return ((uint32_t)m.appeal << 24ULL)
|
||||||
|
| ((uint32_t)m.attr << 16ULL)
|
||||||
|
| ((uint32_t)m.from << 8ULL)
|
||||||
|
| ((uint32_t)m.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct move move_deserialize(uint64_t m)
|
||||||
|
{
|
||||||
|
return (struct move) {
|
||||||
|
/* appeal and attributes are ignored regardless */
|
||||||
|
/*
|
||||||
|
.appeal = (m >> 24) & 0xFF,
|
||||||
|
.attr = (m >> 16) & 0xFF,
|
||||||
|
*/
|
||||||
|
.from = (m >> 8) & 0xFF,
|
||||||
|
.to = (m >> 0) & 0xFF,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t wb_search(int8_t max_depth)
|
||||||
|
{
|
||||||
|
struct search_result const sr = search(&g_board, g_board.pos.player, max_depth);
|
||||||
|
return move_serialize(sr.move);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum move_result wb_move(uint32_t move)
|
||||||
|
{
|
||||||
|
struct move m = move_deserialize(move);
|
||||||
|
enum move_result const r = board_move_2(&g_board, m);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wb_init()
|
||||||
|
{
|
||||||
|
g_board = BOARD_INIT;
|
||||||
|
g_board.tt.entries = g_tt_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t player_piece_serialize(enum player c, enum piece pz)
|
||||||
|
{
|
||||||
|
return ((c & 0xFF) << 8U)
|
||||||
|
| (pz & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t wb_board_at(index at)
|
||||||
|
{
|
||||||
|
bitboard const m = SQ_MASK_FROM_INDEX(at);
|
||||||
|
for (enum player pl = PLAYER_BEGIN; pl < PLAYER_COUNT; ++pl) {
|
||||||
|
for (enum piece pz = PIECE_BEGIN; pz < PIECE_COUNT; ++pz) {
|
||||||
|
if (g_board.pos.pieces[pl][pz] & m) {
|
||||||
|
return player_piece_serialize(pl, pz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user