Initial commit
This commit is contained in:
16
Makefile
Normal file
16
Makefile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
CFLAGS := -std=c23
|
||||||
|
|
||||||
|
all: tests
|
||||||
|
|
||||||
|
codegen: codegen.c
|
||||||
|
$(CC) -o $@ $(CFLAGS) -DCODEGEN $^
|
||||||
|
|
||||||
|
mbb_rook.h: codegen
|
||||||
|
./codegen
|
||||||
|
|
||||||
|
mbb_bishop.h: codegen
|
||||||
|
./codegen
|
||||||
|
|
||||||
|
tests: tests.c | mbb_rook.h mbb_bishop.h
|
||||||
|
$(CC) -o $@ $(CFLAGS) $^
|
||||||
796
base.h
Normal file
796
base.h
Normal file
@@ -0,0 +1,796 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
/* BIT MANIPULATION
|
||||||
|
* ================ */
|
||||||
|
|
||||||
|
static inline uint64_t popcount(uint64_t n)
|
||||||
|
{
|
||||||
|
// TODO: check of popcountll support
|
||||||
|
return (uint64_t)__builtin_popcountll(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint64_t ctz(uint64_t n)
|
||||||
|
{
|
||||||
|
assert(n != 0);
|
||||||
|
return __builtin_ctzll(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BITBOARD AND INDEX DATATYPES AND HELPERS
|
||||||
|
* ========================================= */
|
||||||
|
|
||||||
|
typedef uint64_t bitboard;
|
||||||
|
|
||||||
|
#define BITBOARD( \
|
||||||
|
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_FMT PRIu64
|
||||||
|
#define BITBOARD_FMT_X PRIx64
|
||||||
|
|
||||||
|
|
||||||
|
typedef uint64_t index;
|
||||||
|
#define INDEX(n) n##ULL
|
||||||
|
#define INDEX_FMT PRIu64
|
||||||
|
|
||||||
|
#define RANK_SHIFT_UP(b, n) ((b) << (index)(n)*8ULL)
|
||||||
|
#define RANK_SHIFT_DOWN(b, n) ((b) >> (index)(n)*8ULL)
|
||||||
|
#define FILE_SHIFT_LEFT(b, n) ((b) >> (index)(n)*1ULL)
|
||||||
|
#define FILE_SHIFT_RIGHT(b, n) ((b) << (index)(n)*1ULL)
|
||||||
|
|
||||||
|
#define FILE_MASK(n) FILE_SHIFT_RIGHT((index)0x0101010101010101ULL, n)
|
||||||
|
#define RANK_MASK(n) RANK_SHIFT_UP((index)0x00000000000000FFULL, n)
|
||||||
|
|
||||||
|
#define INDEX_FROM_RF(rank, file) ((index)(8ULL*(index)(rank) + (index)(file)))
|
||||||
|
#define SQUARE_MASK_FROM_RF(rank, file) ((index)1ULL << INDEX_FROM_RF(rank,file))
|
||||||
|
|
||||||
|
#define SQUARE_MASK_FROM_INDEX(idx) (1ULL<<(idx))
|
||||||
|
|
||||||
|
enum file_index : index {
|
||||||
|
FILE_INDEX_BEGIN,
|
||||||
|
FILE_INDEX_A = FILE_INDEX_BEGIN,
|
||||||
|
FILE_INDEX_B,
|
||||||
|
FILE_INDEX_C,
|
||||||
|
FILE_INDEX_D,
|
||||||
|
FILE_INDEX_E,
|
||||||
|
FILE_INDEX_F,
|
||||||
|
FILE_INDEX_G,
|
||||||
|
FILE_INDEX_H,
|
||||||
|
FILE_INDEX_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum rank_index : index {
|
||||||
|
RANK_INDEX_BEGIN,
|
||||||
|
RANK_INDEX_1 = RANK_INDEX_BEGIN,
|
||||||
|
RANK_INDEX_2,
|
||||||
|
RANK_INDEX_3,
|
||||||
|
RANK_INDEX_4,
|
||||||
|
RANK_INDEX_5,
|
||||||
|
RANK_INDEX_6,
|
||||||
|
RANK_INDEX_7,
|
||||||
|
RANK_INDEX_8,
|
||||||
|
RANK_INDEX_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum : bitboard {
|
||||||
|
FILEMASK_A = FILE_MASK(FILE_INDEX_A),
|
||||||
|
FILEMASK_B = FILE_MASK(FILE_INDEX_B),
|
||||||
|
FILEMASK_C = FILE_MASK(FILE_INDEX_C),
|
||||||
|
FILEMASK_D = FILE_MASK(FILE_INDEX_D),
|
||||||
|
FILEMASK_E = FILE_MASK(FILE_INDEX_E),
|
||||||
|
FILEMASK_F = FILE_MASK(FILE_INDEX_F),
|
||||||
|
FILEMASK_G = FILE_MASK(FILE_INDEX_G),
|
||||||
|
FILEMASK_H = FILE_MASK(FILE_INDEX_H),
|
||||||
|
|
||||||
|
RANKMASK_1 = RANK_MASK(RANK_INDEX_1),
|
||||||
|
RANKMASK_2 = RANK_MASK(RANK_INDEX_2),
|
||||||
|
RANKMASK_3 = RANK_MASK(RANK_INDEX_3),
|
||||||
|
RANKMASK_4 = RANK_MASK(RANK_INDEX_4),
|
||||||
|
RANKMASK_5 = RANK_MASK(RANK_INDEX_5),
|
||||||
|
RANKMASK_6 = RANK_MASK(RANK_INDEX_6),
|
||||||
|
RANKMASK_7 = RANK_MASK(RANK_INDEX_7),
|
||||||
|
RANKMASK_8 = RANK_MASK(RANK_INDEX_8),
|
||||||
|
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_0 = ~0ULL,
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_1 = ~FILEMASK_H,
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_2 = ~(FILEMASK_G | FILEMASK_H),
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_3 = ~(FILEMASK_F | FILEMASK_G | FILEMASK_H),
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_4 = ~(FILEMASK_E | FILEMASK_F | FILEMASK_G | FILEMASK_H),
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_5 = ~(FILEMASK_D | FILEMASK_E | FILEMASK_F | FILEMASK_G | FILEMASK_H),
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_6 = ~(FILEMASK_C | FILEMASK_D | FILEMASK_E | FILEMASK_F | FILEMASK_G | FILEMASK_H),
|
||||||
|
FILE_SHIFT_RIGHT_GUARD_7 = ~(FILEMASK_B | FILEMASK_C | FILEMASK_D | FILEMASK_E | FILEMASK_F | FILEMASK_G | FILEMASK_H),
|
||||||
|
|
||||||
|
FILE_SHIFT_LEFT_GUARD_0 = ~0ULL,
|
||||||
|
FILE_SHIFT_LEFT_GUARD_1 = ~FILEMASK_A,
|
||||||
|
FILE_SHIFT_LEFT_GUARD_2 = ~(FILEMASK_A | FILEMASK_B),
|
||||||
|
FILE_SHIFT_LEFT_GUARD_3 = ~(FILEMASK_A | FILEMASK_B | FILEMASK_C),
|
||||||
|
FILE_SHIFT_LEFT_GUARD_4 = ~(FILEMASK_A | FILEMASK_B | FILEMASK_C | FILEMASK_D),
|
||||||
|
FILE_SHIFT_LEFT_GUARD_5 = ~(FILEMASK_A | FILEMASK_B | FILEMASK_C | FILEMASK_D | FILEMASK_E),
|
||||||
|
FILE_SHIFT_LEFT_GUARD_6 = ~(FILEMASK_A | FILEMASK_B | FILEMASK_C | FILEMASK_D | FILEMASK_E | FILEMASK_F),
|
||||||
|
FILE_SHIFT_LEFT_GUARD_7 = ~(FILEMASK_A | FILEMASK_B | FILEMASK_C | FILEMASK_D | FILEMASK_E | FILEMASK_F | FILEMASK_G),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define STR(x) #x
|
||||||
|
|
||||||
|
#define SQUARE_MASK_PREFIX SQUARE_MASK_
|
||||||
|
#define SQUARES_LIST_BEGIN \
|
||||||
|
X(A, 1)
|
||||||
|
#define SQUARES_LIST \
|
||||||
|
X(A, 1) \
|
||||||
|
X(A, 2) \
|
||||||
|
X(A, 3) \
|
||||||
|
X(A, 4) \
|
||||||
|
X(A, 5) \
|
||||||
|
X(A, 6) \
|
||||||
|
X(A, 7) \
|
||||||
|
X(A, 8) \
|
||||||
|
X(B, 1) \
|
||||||
|
X(B, 2) \
|
||||||
|
X(B, 3) \
|
||||||
|
X(B, 4) \
|
||||||
|
X(B, 5) \
|
||||||
|
X(B, 6) \
|
||||||
|
X(B, 7) \
|
||||||
|
X(B, 8) \
|
||||||
|
X(C, 1) \
|
||||||
|
X(C, 2) \
|
||||||
|
X(C, 3) \
|
||||||
|
X(C, 4) \
|
||||||
|
X(C, 5) \
|
||||||
|
X(C, 6) \
|
||||||
|
X(C, 7) \
|
||||||
|
X(C, 8) \
|
||||||
|
X(D, 1) \
|
||||||
|
X(D, 2) \
|
||||||
|
X(D, 3) \
|
||||||
|
X(D, 4) \
|
||||||
|
X(D, 5) \
|
||||||
|
X(D, 6) \
|
||||||
|
X(D, 7) \
|
||||||
|
X(D, 8) \
|
||||||
|
X(E, 1) \
|
||||||
|
X(E, 2) \
|
||||||
|
X(E, 3) \
|
||||||
|
X(E, 4) \
|
||||||
|
X(E, 5) \
|
||||||
|
X(E, 6) \
|
||||||
|
X(E, 7) \
|
||||||
|
X(E, 8) \
|
||||||
|
X(F, 1) \
|
||||||
|
X(F, 2) \
|
||||||
|
X(F, 3) \
|
||||||
|
X(F, 4) \
|
||||||
|
X(F, 5) \
|
||||||
|
X(F, 6) \
|
||||||
|
X(F, 7) \
|
||||||
|
X(F, 8) \
|
||||||
|
X(G, 1) \
|
||||||
|
X(G, 2) \
|
||||||
|
X(G, 3) \
|
||||||
|
X(G, 4) \
|
||||||
|
X(G, 5) \
|
||||||
|
X(G, 6) \
|
||||||
|
X(G, 7) \
|
||||||
|
X(G, 8) \
|
||||||
|
X(H, 1) \
|
||||||
|
X(H, 2) \
|
||||||
|
X(H, 3) \
|
||||||
|
X(H, 4) \
|
||||||
|
X(H, 5) \
|
||||||
|
X(H, 6) \
|
||||||
|
X(H, 7) \
|
||||||
|
X(H, 8) \
|
||||||
|
|
||||||
|
/* define mask constants: SQUARE_MASK_A1, SQUARE_MASK_A2, ..., SQUARE_MASK_H8 */
|
||||||
|
enum : bitboard {
|
||||||
|
#define X(file, rank) SQUARE_MASK_##file##rank = SQUARE_MASK_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file),
|
||||||
|
SQUARES_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
enum square_index : bitboard {
|
||||||
|
#define X(file, rank) SQUARE_INDEX_##file##rank = INDEX_FROM_RF(RANK_INDEX_##rank, FILE_INDEX_##file),
|
||||||
|
SQUARES_LIST
|
||||||
|
SQUARE_INDEX_COUNT,
|
||||||
|
#undef X
|
||||||
|
/* define iterator begin enum */
|
||||||
|
#define X(file, rank) SQUARE_INDEX_BEGIN = SQUARE_INDEX_##file##rank,
|
||||||
|
SQUARES_LIST_BEGIN
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* square_index_display[SQUARE_INDEX_COUNT] = {
|
||||||
|
#define X(file, rank) \
|
||||||
|
[SQUARE_INDEX_##file##rank] = STR(file##rank),
|
||||||
|
SQUARES_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* square_index_str[SQUARE_INDEX_COUNT] = {
|
||||||
|
#define X(file, rank) \
|
||||||
|
[SQUARE_INDEX_##file##rank] = STR(SQUARE_INDEX_##file##rank),
|
||||||
|
SQUARES_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
#define _FILE_SHIFT_RIGHT_GUARDED(b, n) \
|
||||||
|
((((bitboard)(b)) & FILE_SHIFT_RIGHT_GUARD_##n) << ((index)(n) * 1ULL))
|
||||||
|
|
||||||
|
#define _FILE_SHIFT_LEFT_GUARDED(b, n) \
|
||||||
|
((((bitboard)(b)) & FILE_SHIFT_LEFT_GUARD_##n) >> ((index)(n) * 1ULL))
|
||||||
|
|
||||||
|
/* FILE_SHIFT_RIGHT_n shifts the bitboard right by n ranks without wrapping */
|
||||||
|
#define FILE_SHIFT_RIGHT_1(b) _FILE_SHIFT_RIGHT_GUARDED(b, 1)
|
||||||
|
#define FILE_SHIFT_RIGHT_2(b) _FILE_SHIFT_RIGHT_GUARDED(b, 2)
|
||||||
|
#define FILE_SHIFT_RIGHT_3(b) _FILE_SHIFT_RIGHT_GUARDED(b, 3)
|
||||||
|
#define FILE_SHIFT_RIGHT_4(b) _FILE_SHIFT_RIGHT_GUARDED(b, 4)
|
||||||
|
#define FILE_SHIFT_RIGHT_5(b) _FILE_SHIFT_RIGHT_GUARDED(b, 5)
|
||||||
|
#define FILE_SHIFT_RIGHT_6(b) _FILE_SHIFT_RIGHT_GUARDED(b, 6)
|
||||||
|
#define FILE_SHIFT_RIGHT_7(b) _FILE_SHIFT_RIGHT_GUARDED(b, 7)
|
||||||
|
#define FILE_SHIFT_RIGHT_8(b) _FILE_SHIFT_RIGHT_GUARDED(b, 8)
|
||||||
|
|
||||||
|
/* FILE_SHIFT_LEFT_n shifts the bitboard left by n ranks without wrapping */
|
||||||
|
#define FILE_SHIFT_LEFT_1(b) _FILE_SHIFT_LEFT_GUARDED(b, 1)
|
||||||
|
#define FILE_SHIFT_LEFT_2(b) _FILE_SHIFT_LEFT_GUARDED(b, 2)
|
||||||
|
#define FILE_SHIFT_LEFT_3(b) _FILE_SHIFT_LEFT_GUARDED(b, 3)
|
||||||
|
#define FILE_SHIFT_LEFT_4(b) _FILE_SHIFT_LEFT_GUARDED(b, 4)
|
||||||
|
#define FILE_SHIFT_LEFT_5(b) _FILE_SHIFT_LEFT_GUARDED(b, 5)
|
||||||
|
#define FILE_SHIFT_LEFT_6(b) _FILE_SHIFT_LEFT_GUARDED(b, 6)
|
||||||
|
#define FILE_SHIFT_LEFT_7(b) _FILE_SHIFT_LEFT_GUARDED(b, 7)
|
||||||
|
#define FILE_SHIFT_LEFT_8(b) _FILE_SHIFT_LEFT_GUARDED(b, 8)
|
||||||
|
|
||||||
|
/* RANK_SHIFT_UP_n shifts the bitboard up by n ranks without wrapping */
|
||||||
|
#define RANK_SHIFT_UP_1(b) RANK_SHIFT_UP(b, 1)
|
||||||
|
#define RANK_SHIFT_UP_2(b) RANK_SHIFT_UP(b, 2)
|
||||||
|
#define RANK_SHIFT_UP_3(b) RANK_SHIFT_UP(b, 3)
|
||||||
|
#define RANK_SHIFT_UP_4(b) RANK_SHIFT_UP(b, 4)
|
||||||
|
#define RANK_SHIFT_UP_5(b) RANK_SHIFT_UP(b, 5)
|
||||||
|
#define RANK_SHIFT_UP_6(b) RANK_SHIFT_UP(b, 6)
|
||||||
|
#define RANK_SHIFT_UP_7(b) RANK_SHIFT_UP(b, 7)
|
||||||
|
#define RANK_SHIFT_UP_8(b) RANK_SHIFT_UP(b, 8)
|
||||||
|
|
||||||
|
/* RANK_SHIFT_DOWN_n shifts the bitboard down by n ranks without wrapping */
|
||||||
|
#define RANK_SHIFT_DOWN_1(b) RANK_SHIFT_DOWN(b, 1)
|
||||||
|
#define RANK_SHIFT_DOWN_2(b) RANK_SHIFT_DOWN(b, 2)
|
||||||
|
#define RANK_SHIFT_DOWN_3(b) RANK_SHIFT_DOWN(b, 3)
|
||||||
|
#define RANK_SHIFT_DOWN_4(b) RANK_SHIFT_DOWN(b, 4)
|
||||||
|
#define RANK_SHIFT_DOWN_5(b) RANK_SHIFT_DOWN(b, 5)
|
||||||
|
#define RANK_SHIFT_DOWN_6(b) RANK_SHIFT_DOWN(b, 6)
|
||||||
|
#define RANK_SHIFT_DOWN_7(b) RANK_SHIFT_DOWN(b, 7)
|
||||||
|
#define RANK_SHIFT_DOWN_8(b) RANK_SHIFT_DOWN(b, 8)
|
||||||
|
|
||||||
|
/* MISC
|
||||||
|
* ============*/
|
||||||
|
/* TODO: Put these struct definition in a sensible category */
|
||||||
|
|
||||||
|
/* `struct magic` is used by magic bitboard lookups, see
|
||||||
|
* rook_attacks_from_index and bishop_attacks_from_index */
|
||||||
|
struct magic {
|
||||||
|
bitboard mask;
|
||||||
|
bitboard magic;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* BITBOARD PRIMITIVES
|
||||||
|
* ======================== */
|
||||||
|
|
||||||
|
static index bitboard_lsb(bitboard p)
|
||||||
|
{
|
||||||
|
return __builtin_ffsll(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t bitboard_popcount(bitboard b)
|
||||||
|
{
|
||||||
|
return (uint64_t)__builtin_popcountll(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline index index_to_file(enum square_index p)
|
||||||
|
{
|
||||||
|
return p % 8ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline index index_to_rank(enum square_index p)
|
||||||
|
{
|
||||||
|
return p / 8ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard cardinals_from_index(enum square_index p)
|
||||||
|
{
|
||||||
|
/* might benefit from a lookup table */
|
||||||
|
return (FILE_MASK(index_to_file(p)) | RANK_MASK(index_to_rank(p)))
|
||||||
|
& ~SQUARE_MASK_FROM_INDEX(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard diagonals_from_index(enum square_index sq)
|
||||||
|
{
|
||||||
|
const enum rank_index rank = index_to_rank(sq);
|
||||||
|
const enum file_index file = index_to_file(sq);
|
||||||
|
|
||||||
|
bitboard mask = 0ULL;
|
||||||
|
|
||||||
|
/* Ensure signed-underflow detection rules match your style */
|
||||||
|
_Static_assert(((enum rank_index)0) - ((enum rank_index)1) > ((enum rank_index)0));
|
||||||
|
_Static_assert(((enum file_index)0) - ((enum file_index)1) > ((enum file_index)0));
|
||||||
|
|
||||||
|
enum rank_index r;
|
||||||
|
enum file_index f;
|
||||||
|
for (r = rank+1, f = file+1;
|
||||||
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
|
++r, ++f)
|
||||||
|
{
|
||||||
|
mask |= SQUARE_MASK_FROM_RF(r, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NW (rank+1, file-1) */
|
||||||
|
for (r = rank+1, f = file-1;
|
||||||
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
|
++r, --f)
|
||||||
|
{
|
||||||
|
mask |= SQUARE_MASK_FROM_RF(r, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SE (rank-1, file+1) */
|
||||||
|
for (r = rank-1, f = file+1;
|
||||||
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
|
--r, ++f)
|
||||||
|
{
|
||||||
|
mask |= SQUARE_MASK_FROM_RF(r, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SW (rank-1, file-1) */
|
||||||
|
for (r = rank-1, f = file-1;
|
||||||
|
r <= RANK_INDEX_8 && f <= FILE_INDEX_H;
|
||||||
|
--r, --f)
|
||||||
|
{
|
||||||
|
mask |= SQUARE_MASK_FROM_RF(r, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* PIECE ATTACKS
|
||||||
|
* ================== */
|
||||||
|
|
||||||
|
static bitboard knight_attacks(bitboard p)
|
||||||
|
{
|
||||||
|
const bitboard l1 = FILE_SHIFT_RIGHT_1(p & ~FILEMASK_H);
|
||||||
|
const bitboard l2 = FILE_SHIFT_RIGHT_2(p & ~(FILEMASK_G | FILEMASK_H));
|
||||||
|
const bitboard r1 = FILE_SHIFT_LEFT_1 (p & ~FILEMASK_A);
|
||||||
|
const bitboard r2 = FILE_SHIFT_LEFT_2 (p & ~(FILEMASK_A | FILEMASK_B));
|
||||||
|
const bitboard h1 = l1 | r1;
|
||||||
|
const bitboard h2 = l2 | r2;
|
||||||
|
return RANK_SHIFT_UP_2(h1)
|
||||||
|
| RANK_SHIFT_DOWN_2(h1)
|
||||||
|
| RANK_SHIFT_UP_1(h2)
|
||||||
|
| RANK_SHIFT_DOWN_1(h2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rook_attacks_from_index computes rook attack sets from an index, and its
|
||||||
|
* purpose is code generation (the magic bitboard).
|
||||||
|
*
|
||||||
|
* It does not discriminate between friendly and opponent pieces, the caller
|
||||||
|
* should therefore do something like:
|
||||||
|
* ```
|
||||||
|
* rook_attacks_from_index(p, friendly_occ | opponent_occ) & ~friendly_occ
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
static bitboard rook_attacks_from_index_slow(enum square_index sq, bitboard occ)
|
||||||
|
{
|
||||||
|
const enum rank_index rank = index_to_rank(sq);
|
||||||
|
const enum file_index file = index_to_file(sq);
|
||||||
|
|
||||||
|
bitboard atk = 0ULL;
|
||||||
|
|
||||||
|
/* following loops assume rank and file types are unsigned, which relies on C23 enums */
|
||||||
|
_Static_assert(((enum rank_index)0) - ((enum rank_index)1) > ((enum rank_index)0));
|
||||||
|
_Static_assert(((enum file_index)0) - ((enum file_index)1) > ((enum file_index)0));
|
||||||
|
|
||||||
|
for (enum rank_index walk_rank = rank+1; walk_rank <= RANK_INDEX_8; ++walk_rank) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(walk_rank, file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (enum rank_index walk_rank = rank-1; walk_rank <= RANK_INDEX_8; --walk_rank) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(walk_rank, file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (enum file_index walk_file = file+1; walk_file <= FILE_INDEX_H; ++walk_file) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(rank, walk_file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (enum file_index walk_file = file-1; walk_file <= FILE_INDEX_H; --walk_file) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(rank, walk_file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return atk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard rook_attacks_from_index(enum square_index sq, bitboard occ)
|
||||||
|
{
|
||||||
|
#ifdef CODEGEN
|
||||||
|
return 0ULL;
|
||||||
|
#else
|
||||||
|
#if ! __has_include("mbb_rook.h")
|
||||||
|
#error "compile with -DCODEGEN and run once to generate required header files"
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* defines
|
||||||
|
- `mbb_rook[SQUARE_INDEX_COUNT]`
|
||||||
|
- `bitboard rook_attacks[SQUARE_INDEX_COUNT][1<<12ULL] */
|
||||||
|
#include "mbb_rook.h"
|
||||||
|
|
||||||
|
const struct magic m = mbb_rook[sq];
|
||||||
|
occ &= m.mask;
|
||||||
|
occ *= m.magic;
|
||||||
|
occ >>= (64ULL-12ULL);
|
||||||
|
return rook_attacks[sq][occ];
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard rook_attacks(bitboard p, bitboard occ)
|
||||||
|
{
|
||||||
|
bitboard b = 0ULL;
|
||||||
|
while (p) {
|
||||||
|
const index lsb = bitboard_lsb(p) - 1;
|
||||||
|
p &= ~(1ULL<<(lsb));
|
||||||
|
b |= rook_attacks_from_index(lsb, occ);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bishop_attacks_from_index computes bishop attack sets from an index, and its
|
||||||
|
* purpose is code generation (the magic bitboard).
|
||||||
|
*
|
||||||
|
* It does not discriminate between friendly and opponent pieces, the caller
|
||||||
|
* should therefore do something like:
|
||||||
|
* ```
|
||||||
|
* bishop_attacks_from_index(p, occ) & ~friendly_occ
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
static bitboard bishop_attacks_from_index_slow(enum square_index sq, bitboard occ)
|
||||||
|
{
|
||||||
|
const enum rank_index rank = index_to_rank(sq);
|
||||||
|
const enum file_index file = index_to_file(sq);
|
||||||
|
|
||||||
|
bitboard atk = 0ULL;
|
||||||
|
|
||||||
|
/* following loops assume rank and file types are unsigned, which relies on C23 enums */
|
||||||
|
_Static_assert(((enum rank_index)0) - ((enum rank_index)1) > ((enum rank_index)0));
|
||||||
|
_Static_assert(((enum file_index)0) - ((enum file_index)1) > ((enum file_index)0));
|
||||||
|
|
||||||
|
enum rank_index walk_rank;
|
||||||
|
enum file_index walk_file;
|
||||||
|
for (walk_rank = rank+1, walk_file = file+1;
|
||||||
|
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||||
|
++walk_rank, ++walk_file) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(walk_rank, walk_file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (walk_rank = rank+1, walk_file = file-1;
|
||||||
|
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||||
|
++walk_rank, --walk_file) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(walk_rank, walk_file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (walk_rank = rank-1, walk_file = file+1;
|
||||||
|
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||||
|
--walk_rank, ++walk_file) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(walk_rank, walk_file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (walk_rank = rank-1, walk_file = file-1;
|
||||||
|
walk_rank <= RANK_INDEX_8 && walk_file <= FILE_INDEX_H;
|
||||||
|
--walk_rank, --walk_file) {
|
||||||
|
const bitboard sq = SQUARE_MASK_FROM_RF(walk_rank, walk_file);
|
||||||
|
atk |= sq;
|
||||||
|
if (occ & sq) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return atk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard bishop_attacks_from_index(enum square_index sq, bitboard occ)
|
||||||
|
{
|
||||||
|
#ifdef CODEGEN
|
||||||
|
return 0ULL;
|
||||||
|
#else
|
||||||
|
#if ! __has_include("mbb_bishop.h")
|
||||||
|
#error "compile with -DCODEGEN and run once to generate required header files"
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* defines
|
||||||
|
- `mbb_bishop[SQUARE_INDEX_COUNT]`
|
||||||
|
- `bitboard bishop_attacks[SQUARE_INDEX_COUNT][1<<9ULL] */
|
||||||
|
#include "mbb_bishop.h"
|
||||||
|
const struct magic m = mbb_bishop[sq];
|
||||||
|
occ &= m.mask;
|
||||||
|
occ *= m.magic;
|
||||||
|
occ >>= (64ULL-9ULL);
|
||||||
|
return bishop_attacks[sq][occ];
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard bishop_attacks(bitboard p, bitboard occ)
|
||||||
|
{
|
||||||
|
bitboard b = 0ULL;
|
||||||
|
while (p) {
|
||||||
|
const index lsb = bitboard_lsb(p) - 1;
|
||||||
|
p &= ~(1ULL<<(lsb));
|
||||||
|
b |= bishop_attacks_from_index(lsb, occ);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bitboard pawn_white_moves(bitboard p, bitboard empty)
|
||||||
|
{
|
||||||
|
/* Assumptions:
|
||||||
|
- empty = ~(black_occupied | white_occupied);
|
||||||
|
*/
|
||||||
|
|
||||||
|
const bitboard single_push = RANK_SHIFT_UP(p, 1) & empty;
|
||||||
|
|
||||||
|
const bitboard double_push =
|
||||||
|
RANK_SHIFT_UP(single_push & RANKMASK_3, 1) & empty;
|
||||||
|
|
||||||
|
return single_push | double_push;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bitboard pawn_white_attacks(bitboard p, bitboard ep_targets, bitboard black_occupied)
|
||||||
|
{
|
||||||
|
const bitboard up = RANK_SHIFT_UP(p, 1);
|
||||||
|
const bitboard captures_right = FILE_SHIFT_RIGHT(up, 1) & ~FILEMASK_H;
|
||||||
|
const bitboard captures_left = FILE_SHIFT_LEFT(up, 1) & ~FILEMASK_A;
|
||||||
|
|
||||||
|
return (captures_right | captures_left) & (black_occupied | ep_targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bitboard pawn_black_moves(bitboard p, bitboard empty)
|
||||||
|
{
|
||||||
|
/* Assumptions:
|
||||||
|
- empty = ~(black_occupied | white_occupied);
|
||||||
|
*/
|
||||||
|
|
||||||
|
const bitboard single_push = RANK_SHIFT_DOWN(p, 1) & empty;
|
||||||
|
|
||||||
|
const bitboard double_push =
|
||||||
|
RANK_SHIFT_DOWN(single_push & RANKMASK_6, 1) & empty;
|
||||||
|
|
||||||
|
return single_push | double_push;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bitboard pawn_black_attacks(bitboard p, bitboard ep_targets, bitboard white_occupied)
|
||||||
|
{
|
||||||
|
const bitboard down = RANK_SHIFT_UP(p, 1);
|
||||||
|
const bitboard captures_right = FILE_SHIFT_RIGHT(down, 1) & ~FILEMASK_H;
|
||||||
|
const bitboard captures_left = FILE_SHIFT_LEFT(down, 1) & ~FILEMASK_A;
|
||||||
|
|
||||||
|
return (captures_right | captures_left) & (white_occupied | ep_targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bitboard king_attacks(enum square_index sq)
|
||||||
|
{
|
||||||
|
/* candidate for improvement */
|
||||||
|
const bitboard n = RANK_SHIFT_UP(sq, 1);
|
||||||
|
const bitboard s = RANK_SHIFT_DOWN(sq, 1);
|
||||||
|
const bitboard w = FILE_SHIFT_LEFT(sq, 1);
|
||||||
|
const bitboard e = FILE_SHIFT_RIGHT(sq, 1);
|
||||||
|
|
||||||
|
const bitboard nw = FILE_SHIFT_LEFT(n, 1);
|
||||||
|
const bitboard ne = FILE_SHIFT_RIGHT(n, 1);
|
||||||
|
const bitboard sw = FILE_SHIFT_RIGHT(s, 1);
|
||||||
|
const bitboard se = FILE_SHIFT_LEFT(s, 1);
|
||||||
|
|
||||||
|
return n | s | w | e | nw | ne | sw | se;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* BOARD
|
||||||
|
* ================= */
|
||||||
|
enum player : index {
|
||||||
|
PLAYER_BEGIN,
|
||||||
|
PLAYER_WHITE = PLAYER_BEGIN,
|
||||||
|
PLAYER_BLACK,
|
||||||
|
PLAYER_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum piece : index {
|
||||||
|
PIECE_BEGIN,
|
||||||
|
PIECE_PAWN = PIECE_BEGIN,
|
||||||
|
PIECE_KING,
|
||||||
|
PIECE_QUEEN,
|
||||||
|
PIECE_BISHOP,
|
||||||
|
PIECE_ROOK,
|
||||||
|
PIECE_KNIGHT,
|
||||||
|
PIECE_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char piece_char[PLAYER_COUNT][PIECE_COUNT] = {
|
||||||
|
[PLAYER_WHITE] = {
|
||||||
|
[PIECE_PAWN] = 'P',
|
||||||
|
[PIECE_KING] = 'K',
|
||||||
|
[PIECE_QUEEN] = 'Q',
|
||||||
|
[PIECE_BISHOP] = 'B',
|
||||||
|
[PIECE_ROOK] = 'R',
|
||||||
|
[PIECE_KNIGHT] = 'N',
|
||||||
|
},
|
||||||
|
[PLAYER_BLACK] = {
|
||||||
|
[PIECE_PAWN] = 'p',
|
||||||
|
[PIECE_KING] = 'k',
|
||||||
|
[PIECE_QUEEN] = 'q',
|
||||||
|
[PIECE_BISHOP] = 'b',
|
||||||
|
[PIECE_ROOK] = 'r',
|
||||||
|
[PIECE_KNIGHT] = 'n',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const int piece_unicode[PLAYER_COUNT][PIECE_COUNT] = {
|
||||||
|
[PLAYER_WHITE] = {
|
||||||
|
[PIECE_PAWN] = 0x2659,
|
||||||
|
[PIECE_KING] = 0x2654,
|
||||||
|
[PIECE_QUEEN] = 0x2655,
|
||||||
|
[PIECE_BISHOP] = 0x2657,
|
||||||
|
[PIECE_ROOK] = 0x2656,
|
||||||
|
[PIECE_KNIGHT] = 0x2658,
|
||||||
|
},
|
||||||
|
[PLAYER_BLACK] = {
|
||||||
|
[PIECE_PAWN] = 0x265F,
|
||||||
|
[PIECE_KING] = 0x265A,
|
||||||
|
[PIECE_QUEEN] = 0x265B,
|
||||||
|
[PIECE_BISHOP] = 0x265D,
|
||||||
|
[PIECE_ROOK] = 0x265C,
|
||||||
|
[PIECE_KNIGHT] = 0x265E,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct board {
|
||||||
|
bitboard pieces[PLAYER_COUNT][PIECE_COUNT];
|
||||||
|
bitboard enpassent_targets[PLAYER_COUNT];
|
||||||
|
bitboard occupied[PLAYER_COUNT];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define BOARD_INITIAL (struct board) { \
|
||||||
|
.pieces = { \
|
||||||
|
[PLAYER_WHITE] = { \
|
||||||
|
[PIECE_PAWN] = RANKMASK_2, \
|
||||||
|
[PIECE_ROOK] = SQUARE_MASK_A1 | SQUARE_MASK_H1, \
|
||||||
|
[PIECE_KNIGHT] = SQUARE_MASK_B1 | SQUARE_MASK_G1, \
|
||||||
|
[PIECE_BISHOP] = SQUARE_MASK_C1 | SQUARE_MASK_F1, \
|
||||||
|
[PIECE_QUEEN] = SQUARE_MASK_D1, \
|
||||||
|
[PIECE_KING] = SQUARE_MASK_E1, \
|
||||||
|
}, \
|
||||||
|
[PLAYER_BLACK] = { \
|
||||||
|
[PIECE_PAWN] = RANKMASK_7, \
|
||||||
|
[PIECE_ROOK] = SQUARE_MASK_A8 | SQUARE_MASK_H8, \
|
||||||
|
[PIECE_KNIGHT] = SQUARE_MASK_B8 | SQUARE_MASK_G8, \
|
||||||
|
[PIECE_BISHOP] = SQUARE_MASK_C8 | SQUARE_MASK_F8, \
|
||||||
|
[PIECE_QUEEN] = SQUARE_MASK_D8, \
|
||||||
|
[PIECE_KING] = SQUARE_MASK_E8, \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
void board_print(struct board* b, FILE* out)
|
||||||
|
{
|
||||||
|
int buf[8][8] = {0};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 8; i++) {
|
||||||
|
for (size_t i = 0; i < 8; i++) {
|
||||||
|
buf[i][i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (enum player player = PLAYER_BEGIN; player < PLAYER_COUNT; player++) {
|
||||||
|
for (enum piece piece = PIECE_BEGIN; piece < PIECE_COUNT; piece++) {
|
||||||
|
bitboard x = b->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++) {
|
||||||
|
/* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* debug functions
|
||||||
|
* =============== */
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
const index n = i*8+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");
|
||||||
|
}
|
||||||
|
|
||||||
247
codegen.c
Normal file
247
codegen.c
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* gets the next number with the same bitcount, unless it overflows, in which
|
||||||
|
* it returns the first number with popcount+1 */
|
||||||
|
uint64_t next_magic(uint64_t seed) {
|
||||||
|
|
||||||
|
uint64_t u = seed & -seed; // rightmost 1-bit
|
||||||
|
uint64_t v = seed + u; // add it
|
||||||
|
if (v == 0) {
|
||||||
|
if (seed == ~0ULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return next_magic((1ULL << (popcount(seed)+1)) - 1);
|
||||||
|
}
|
||||||
|
seed = v + (((v ^ seed) / u) >> 2);
|
||||||
|
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
struct magic rook_magics[SQUARE_INDEX_COUNT] = {0ULL};
|
||||||
|
bitboard rook_attacks[SQUARE_INDEX_COUNT][1<<12ULL] = {0};
|
||||||
|
|
||||||
|
index rook_relevant_bits[12] = {0};
|
||||||
|
size_t rook_relevant_bits_count = 0;
|
||||||
|
|
||||||
|
struct magic bishop_magics[SQUARE_INDEX_COUNT] = {0ULL};
|
||||||
|
bitboard bishop_attacks[SQUARE_INDEX_COUNT][1<<9ULL] = {0};
|
||||||
|
|
||||||
|
index bishop_relevant_bits[9] = {0};
|
||||||
|
size_t bishop_relevant_bits_count = 0;
|
||||||
|
|
||||||
|
for (enum square_index sq_index = SQUARE_INDEX_BEGIN; sq_index < SQUARE_INDEX_COUNT; ++sq_index) {
|
||||||
|
enum file_index file = index_to_file(sq_index);
|
||||||
|
enum rank_index rank = index_to_rank(sq_index);
|
||||||
|
fprintf(stderr, "%s ", square_index_display[sq_index]);
|
||||||
|
|
||||||
|
/* ROOKS
|
||||||
|
* ======================= */
|
||||||
|
{
|
||||||
|
/* generate attack mask */
|
||||||
|
bitboard atk_mask = 0;
|
||||||
|
{
|
||||||
|
atk_mask |= FILE_MASK(file) & ~(RANKMASK_1 | RANKMASK_8);
|
||||||
|
atk_mask |= RANK_MASK(rank) & ~(FILEMASK_A | FILEMASK_H);
|
||||||
|
atk_mask &= ~SQUARE_MASK_FROM_RF(rank, file);
|
||||||
|
|
||||||
|
/* populate relevant bits array */
|
||||||
|
rook_relevant_bits_count = 0;
|
||||||
|
bitboard x = atk_mask;
|
||||||
|
while (x) {
|
||||||
|
const index lsb = bitboard_lsb(x) - 1;
|
||||||
|
x &= ~(1ULL<<(lsb));
|
||||||
|
rook_relevant_bits[rook_relevant_bits_count++] = lsb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rook_magics[sq_index].mask = atk_mask;
|
||||||
|
|
||||||
|
/* brute force the magic number */
|
||||||
|
for (;;) {
|
||||||
|
bitboard magic = rand64() & rand64() & rand64();
|
||||||
|
|
||||||
|
bitboard atk = 0ULL;
|
||||||
|
for (bitboard test = 0; test < (1ULL<<rook_relevant_bits_count); test++) {
|
||||||
|
/* map test to an occupany set */
|
||||||
|
bitboard occ = 0ULL;
|
||||||
|
for (size_t i = 0; i < rook_relevant_bits_count; i++) {
|
||||||
|
occ |= ((test >> i) & 1ULL) << rook_relevant_bits[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
atk = rook_attacks_from_index_slow(sq_index, occ);
|
||||||
|
|
||||||
|
const size_t hash = ((occ * magic) >> (64ULL-12));
|
||||||
|
|
||||||
|
if (rook_attacks[sq_index][hash] == 0ULL) {
|
||||||
|
rook_attacks[sq_index][hash] = atk;
|
||||||
|
} else if (rook_attacks[sq_index][hash] != atk) {
|
||||||
|
for (size_t i = 0; i < sizeof rook_attacks[0] / sizeof rook_attacks[0][0]; i++) {
|
||||||
|
rook_attacks[sq_index][i] = 0ULL;
|
||||||
|
}
|
||||||
|
goto next_rook_magic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rook_magics[sq_index].magic = magic;
|
||||||
|
break;
|
||||||
|
|
||||||
|
next_rook_magic:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* BISHOPS
|
||||||
|
* ====================== */
|
||||||
|
{
|
||||||
|
/* generate attack mask */
|
||||||
|
bitboard atk_mask = 0;
|
||||||
|
{
|
||||||
|
atk_mask |= diagonals_from_index(sq_index);
|
||||||
|
atk_mask &= ~(RANKMASK_1 | RANKMASK_8);
|
||||||
|
atk_mask &= ~(FILEMASK_A | FILEMASK_H);
|
||||||
|
atk_mask &= ~SQUARE_MASK_FROM_RF(rank, file);
|
||||||
|
|
||||||
|
/* populate relevant bits array */
|
||||||
|
bishop_relevant_bits_count = 0;
|
||||||
|
bitboard x = atk_mask;
|
||||||
|
while (x) {
|
||||||
|
const index lsb = bitboard_lsb(x) - 1;
|
||||||
|
x &= ~(1ULL<<(lsb));
|
||||||
|
bishop_relevant_bits[bishop_relevant_bits_count++] = lsb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bishop_magics[sq_index].mask = atk_mask;
|
||||||
|
|
||||||
|
/* brute force the magic number */
|
||||||
|
bitboard magic = 0ULL;
|
||||||
|
for (;;) {
|
||||||
|
magic = rand64() & rand64() & rand64();
|
||||||
|
|
||||||
|
bitboard atk = 0ULL;
|
||||||
|
for (bitboard test = 0; test < (1ULL<<bishop_relevant_bits_count); test++) {
|
||||||
|
/* map test to an occupany set */
|
||||||
|
bitboard occ = 0ULL;
|
||||||
|
for (size_t i = 0; i < bishop_relevant_bits_count; i++) {
|
||||||
|
occ |= ((test >> i) & 1ULL) << bishop_relevant_bits[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
atk = bishop_attacks_from_index_slow(sq_index, occ);
|
||||||
|
|
||||||
|
const size_t hash = ((occ * magic) >> (64ULL-9));
|
||||||
|
|
||||||
|
if (bishop_attacks[sq_index][hash] == 0ULL) {
|
||||||
|
bishop_attacks[sq_index][hash] = atk;
|
||||||
|
} else if (bishop_attacks[sq_index][hash] != atk) {
|
||||||
|
for (size_t i = 0; i < sizeof bishop_attacks[0] / sizeof bishop_attacks[0][0]; i++) {
|
||||||
|
bishop_attacks[sq_index][i] = 0ULL;
|
||||||
|
}
|
||||||
|
goto next_bishop_magic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bishop_magics[sq_index].magic = magic;
|
||||||
|
break;
|
||||||
|
|
||||||
|
next_bishop_magic:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- write to files --- */
|
||||||
|
#define TAB " "
|
||||||
|
#define NL "\n"
|
||||||
|
|
||||||
|
{ /* rooks */
|
||||||
|
FILE* f = fopen("mbb_rook.h", "w");
|
||||||
|
if (!f) {
|
||||||
|
perror("fopen");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "#pragma once\n");
|
||||||
|
fprintf(f, "static const struct magic mbb_rook[SQUARE_INDEX_COUNT] = {\n");
|
||||||
|
for (enum square_index i = SQUARE_INDEX_BEGIN; i < SQUARE_INDEX_COUNT; i++) {
|
||||||
|
fprintf(f,
|
||||||
|
TAB "[%s] = (struct magic) {"
|
||||||
|
NL TAB " .magic = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||||
|
NL TAB " .mask = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||||
|
NL TAB "},"
|
||||||
|
NL,
|
||||||
|
square_index_str[i],
|
||||||
|
rook_magics[i].magic,
|
||||||
|
rook_magics[i].mask);
|
||||||
|
}
|
||||||
|
fprintf(f,"};\n");
|
||||||
|
|
||||||
|
fprintf(f,"\n");
|
||||||
|
|
||||||
|
fprintf(f, "static const bitboard rook_attacks[SQUARE_INDEX_COUNT][1ULL<<12ULL] = {\n");
|
||||||
|
for (enum square_index sq = SQUARE_INDEX_BEGIN; sq < SQUARE_INDEX_COUNT; ++sq) {
|
||||||
|
fprintf(f, "[%s] = {\n", square_index_str[sq]);
|
||||||
|
for (size_t i = 0; i < sizeof rook_attacks[sq] / sizeof *rook_attacks[sq]; i++) {
|
||||||
|
fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", rook_attacks[sq][i]);
|
||||||
|
}
|
||||||
|
fprintf(f, "\n},\n");
|
||||||
|
}
|
||||||
|
fprintf(f,"};\n");
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
{ /* bishops */
|
||||||
|
FILE* f = fopen("mbb_bishop.h", "w");
|
||||||
|
if (!f) {
|
||||||
|
perror("fopen");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, "\n");
|
||||||
|
fprintf(f, "static const struct magic mbb_bishop[SQUARE_INDEX_COUNT] = {\n");
|
||||||
|
for (enum square_index i = SQUARE_INDEX_BEGIN; i < SQUARE_INDEX_COUNT; i++) {
|
||||||
|
fprintf(f,
|
||||||
|
TAB "[%s] = (struct magic) {"
|
||||||
|
NL TAB " .magic = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||||
|
NL TAB " .mask = 0x%016"BITBOARD_FMT_X"ULL,"
|
||||||
|
NL TAB "},"
|
||||||
|
NL,
|
||||||
|
square_index_str[i],
|
||||||
|
bishop_magics[i].magic,
|
||||||
|
bishop_magics[i].mask);
|
||||||
|
}
|
||||||
|
fprintf(f,"};\n");
|
||||||
|
|
||||||
|
fprintf(f,"\n");
|
||||||
|
|
||||||
|
fprintf(f, "static const bitboard bishop_attacks[SQUARE_INDEX_COUNT][1ULL<<9ULL] = {\n");
|
||||||
|
for (enum square_index sq = SQUARE_INDEX_BEGIN; sq < SQUARE_INDEX_COUNT; ++sq) {
|
||||||
|
fprintf(f, "[%s] = {\n", square_index_str[sq]);
|
||||||
|
for (size_t i = 0; i < sizeof bishop_attacks[sq] / sizeof *bishop_attacks[sq]; i++) {
|
||||||
|
fprintf(f, "0x%016"BITBOARD_FMT_X"ULL, \n", bishop_attacks[sq][i]);
|
||||||
|
}
|
||||||
|
fprintf(f, "\n},\n");
|
||||||
|
}
|
||||||
|
fprintf(f,"};\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef TAB
|
||||||
|
#undef NL
|
||||||
|
}
|
||||||
403
tests.c
Normal file
403
tests.c
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
|
||||||
|
#include "base.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/* Tests are mostly generated by ChatGPT */
|
||||||
|
|
||||||
|
static void print_rook_test(const char *label,
|
||||||
|
enum square_index sq,
|
||||||
|
bitboard all_occ,
|
||||||
|
bitboard own_occ)
|
||||||
|
{
|
||||||
|
printf("\n%s\n", label);
|
||||||
|
printf("All occ:\n");
|
||||||
|
bitboard_print(all_occ, stdout);
|
||||||
|
printf("Own occ:\n");
|
||||||
|
bitboard_print(own_occ, stdout);
|
||||||
|
|
||||||
|
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
printf("Rook attacks:\n");
|
||||||
|
bitboard_print(attacks, stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_rooks()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_A1;
|
||||||
|
const bitboard rook = SQUARE_MASK_A1;
|
||||||
|
const bitboard all_occ = rook;
|
||||||
|
const bitboard own_occ = rook;
|
||||||
|
|
||||||
|
/* Expected: full rank 1 and file A, except A1 */
|
||||||
|
bitboard expected = (FILEMASK_A | RANKMASK_1) & ~SQUARE_MASK_A1;
|
||||||
|
|
||||||
|
print_rook_test("Test 1: Rook at A1, empty board", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_A1;
|
||||||
|
const bitboard rook = SQUARE_MASK_A1;
|
||||||
|
const bitboard own_block = SQUARE_MASK_A2 | SQUARE_MASK_B1;
|
||||||
|
const bitboard all_occ = rook | own_block;
|
||||||
|
const bitboard own_occ = all_occ;
|
||||||
|
|
||||||
|
/* Expected: no legal moves (immediately blocked both directions) */
|
||||||
|
const bitboard expected = 0ULL;
|
||||||
|
|
||||||
|
print_rook_test("Test 2: Rook at A1, own blockers A2, B1", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_A1;
|
||||||
|
const bitboard rook = SQUARE_MASK_A1;
|
||||||
|
const bitboard enemies = SQUARE_MASK_A3 | SQUARE_MASK_C1;
|
||||||
|
const bitboard all_occ = rook | enemies;
|
||||||
|
const bitboard own_occ = rook;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expected:
|
||||||
|
* - Along file A: A2, A3 (enemy at A3 is capturable, stop there)
|
||||||
|
* - Along rank 1: B1, C1 (enemy at C1 capturable, stop there)
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_A2 | SQUARE_MASK_A3;
|
||||||
|
expected |= SQUARE_MASK_B1 | SQUARE_MASK_C1;
|
||||||
|
|
||||||
|
print_rook_test("Test 3: Rook at A1, enemy blockers A3, C1", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rook Test 6: center rook on empty board */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_E5;
|
||||||
|
const bitboard rook = SQUARE_MASK_E5;
|
||||||
|
const bitboard all_occ = rook;
|
||||||
|
const bitboard own_occ = rook;
|
||||||
|
|
||||||
|
/* Full rank 5 and file E, except E5 itself */
|
||||||
|
bitboard expected = (FILEMASK_E | RANKMASK_5) & ~SQUARE_MASK_E5;
|
||||||
|
|
||||||
|
print_rook_test("Rook Test 6: E5, empty board", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rook Test 7: center rook, mixed blockers on rays */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_E5;
|
||||||
|
const bitboard rook = SQUARE_MASK_E5;
|
||||||
|
|
||||||
|
/* Friendly: E7 and C5; Enemy: E3 and H5 */
|
||||||
|
const bitboard friends = rook | SQUARE_MASK_E7 | SQUARE_MASK_C5;
|
||||||
|
const bitboard enemies = SQUARE_MASK_E3 | SQUARE_MASK_H5;
|
||||||
|
const bitboard all_occ = friends | enemies;
|
||||||
|
const bitboard own_occ = friends;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From E5:
|
||||||
|
* Up: E6, then friendly E7 (stop before E7)
|
||||||
|
* Down: E4, E3 (enemy, included, then stop)
|
||||||
|
* Left: D5, then friendly C5 (stop before C5)
|
||||||
|
* Right:F5, G5, H5 (enemy, included, then stop)
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_E6;
|
||||||
|
expected |= SQUARE_MASK_E4 | SQUARE_MASK_E3;
|
||||||
|
expected |= SQUARE_MASK_D5;
|
||||||
|
expected |= SQUARE_MASK_F5 | SQUARE_MASK_G5 | SQUARE_MASK_H5;
|
||||||
|
|
||||||
|
print_rook_test("Rook Test 7: E5, friends E7/C5, enemies E3/H5", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rook Test 8: edge rook on empty board (top edge, not corner) */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_C8;
|
||||||
|
const bitboard rook = SQUARE_MASK_C8;
|
||||||
|
const bitboard all_occ = rook;
|
||||||
|
const bitboard own_occ = rook;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From C8:
|
||||||
|
* Down file C: C7..C1
|
||||||
|
* Across rank 8: A8,B8,D8,E8,F8,G8,H8
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= (FILEMASK_C & ~SQUARE_MASK_C8);
|
||||||
|
expected |= (RANKMASK_8 & ~SQUARE_MASK_C8);
|
||||||
|
|
||||||
|
print_rook_test("Rook Test 8: C8, empty board", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rook Test 9: rook completely boxed in by friendly orthogonal neighbors */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_D4;
|
||||||
|
const bitboard rook = SQUARE_MASK_D4;
|
||||||
|
const bitboard friends = rook |
|
||||||
|
SQUARE_MASK_D5 | SQUARE_MASK_D3 |
|
||||||
|
SQUARE_MASK_C4 | SQUARE_MASK_E4;
|
||||||
|
const bitboard enemies = 0ULL;
|
||||||
|
const bitboard all_occ = friends | enemies;
|
||||||
|
const bitboard own_occ = friends;
|
||||||
|
|
||||||
|
/* All four rays are immediately blocked by own pieces */
|
||||||
|
const bitboard expected = 0ULL;
|
||||||
|
|
||||||
|
print_rook_test("Rook Test 9: D4, boxed by own pieces at D5/D3/C4/E4",
|
||||||
|
sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rook Test 10: rook on file with non-interfering off-ray pieces */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_A4;
|
||||||
|
const bitboard rook = SQUARE_MASK_A4;
|
||||||
|
|
||||||
|
/* Pieces placed off the rook's rank/file; they should have no effect */
|
||||||
|
const bitboard off_ray = SQUARE_MASK_C1 | SQUARE_MASK_F6 | SQUARE_MASK_H8;
|
||||||
|
(void)off_ray;
|
||||||
|
const bitboard friends = rook | SQUARE_MASK_C1;
|
||||||
|
const bitboard enemies = SQUARE_MASK_F6 | SQUARE_MASK_H8;
|
||||||
|
const bitboard all_occ = friends | enemies;
|
||||||
|
const bitboard own_occ = friends;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From A4:
|
||||||
|
* File A: A1..A8 except A4
|
||||||
|
* Rank 4: B4..H4
|
||||||
|
* Pieces not on file A or rank 4 must not change attacks.
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= (FILEMASK_A | RANKMASK_4) & ~SQUARE_MASK_A4;
|
||||||
|
|
||||||
|
print_rook_test("Rook Test 10: A4, random off-ray pieces C1/F6/H8",
|
||||||
|
sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
bitboard attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void print_bishop_test(const char *label,
|
||||||
|
enum square_index sq,
|
||||||
|
bitboard all_occ,
|
||||||
|
bitboard own_occ)
|
||||||
|
{
|
||||||
|
printf("\n%s\n", label);
|
||||||
|
printf("All occ:\n");
|
||||||
|
bitboard_print(all_occ, stdout);
|
||||||
|
printf("Own occ:\n");
|
||||||
|
bitboard_print(own_occ, stdout);
|
||||||
|
|
||||||
|
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
printf("Bishop attacks:\n");
|
||||||
|
bitboard_print(attacks, stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_bishops(void)
|
||||||
|
{
|
||||||
|
/* Test 1: Bishop at D4 on empty board (only bishop present) */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_D4;
|
||||||
|
const bitboard bishop = SQUARE_MASK_D4;
|
||||||
|
const bitboard all_occ = bishop;
|
||||||
|
const bitboard own_occ = bishop;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expected diagonals from D4:
|
||||||
|
* NE: E5, F6, G7, H8
|
||||||
|
* NW: C5, B6, A7
|
||||||
|
* SE: E3, F2, G1
|
||||||
|
* SW: C3, B2, A1
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_E5 | SQUARE_MASK_F6 | SQUARE_MASK_G7 | SQUARE_MASK_H8;
|
||||||
|
expected |= SQUARE_MASK_C5 | SQUARE_MASK_B6 | SQUARE_MASK_A7;
|
||||||
|
expected |= SQUARE_MASK_E3 | SQUARE_MASK_F2 | SQUARE_MASK_G1;
|
||||||
|
expected |= SQUARE_MASK_C3 | SQUARE_MASK_B2 | SQUARE_MASK_A1;
|
||||||
|
|
||||||
|
print_bishop_test("Bishop Test 1: D4, empty board", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test 2: Bishop at C1 on empty board (only bishop present) */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_C1;
|
||||||
|
const bitboard bishop = SQUARE_MASK_C1;
|
||||||
|
const bitboard all_occ = bishop;
|
||||||
|
const bitboard own_occ = bishop;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From C1, diagonals:
|
||||||
|
* NE: D2, E3, F4, G5, H6
|
||||||
|
* NW: B2, A3
|
||||||
|
* SE / SW: none (edge of board)
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_D2 | SQUARE_MASK_E3 | SQUARE_MASK_F4 |
|
||||||
|
SQUARE_MASK_G5 | SQUARE_MASK_H6;
|
||||||
|
expected |= SQUARE_MASK_B2 | SQUARE_MASK_A3;
|
||||||
|
|
||||||
|
print_bishop_test("Bishop Test 2: C1, empty board", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test 3: Bishop at D4, friendly blockers at F6 and B2 (no enemies) */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_D4;
|
||||||
|
const bitboard bishop = SQUARE_MASK_D4;
|
||||||
|
const bitboard friends = bishop | SQUARE_MASK_F6 | SQUARE_MASK_B2;
|
||||||
|
const bitboard enemies = 0ULL;
|
||||||
|
const bitboard all_occ = friends | enemies;
|
||||||
|
const bitboard own_occ = friends;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From D4:
|
||||||
|
* NE: E5, then blocked by friendly F6 (F6 not included)
|
||||||
|
* SW: C3, then blocked by friendly B2 (B2 not included)
|
||||||
|
* NW: C5, B6, A7 (no blockers)
|
||||||
|
* SE: E3, F2, G1 (no blockers)
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_E5;
|
||||||
|
expected |= SQUARE_MASK_C3;
|
||||||
|
expected |= SQUARE_MASK_C5 | SQUARE_MASK_B6 | SQUARE_MASK_A7;
|
||||||
|
expected |= SQUARE_MASK_E3 | SQUARE_MASK_F2 | SQUARE_MASK_G1;
|
||||||
|
|
||||||
|
print_bishop_test("Bishop Test 3: D4, friendly blockers F6, B2", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test 4: Bishop at D4, enemy blockers at F6 and B2 (no other friends) */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_D4;
|
||||||
|
const bitboard bishop = SQUARE_MASK_D4;
|
||||||
|
const bitboard friends = bishop;
|
||||||
|
const bitboard enemies = SQUARE_MASK_F6 | SQUARE_MASK_B2;
|
||||||
|
const bitboard all_occ = friends | enemies;
|
||||||
|
const bitboard own_occ = friends;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From D4:
|
||||||
|
* NE: E5, F6 (enemy, included, then stop)
|
||||||
|
* SW: C3, B2 (enemy, included, then stop)
|
||||||
|
* NW: C5, B6, A7
|
||||||
|
* SE: E3, F2, G1
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_E5 | SQUARE_MASK_F6;
|
||||||
|
expected |= SQUARE_MASK_C3 | SQUARE_MASK_B2;
|
||||||
|
expected |= SQUARE_MASK_C5 | SQUARE_MASK_B6 | SQUARE_MASK_A7;
|
||||||
|
expected |= SQUARE_MASK_E3 | SQUARE_MASK_F2 | SQUARE_MASK_G1;
|
||||||
|
|
||||||
|
print_bishop_test("Bishop Test 4: D4, enemy blockers F6, B2", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test 5: Bishop at D4, mixed friend/enemy + another friendly bishop elsewhere */
|
||||||
|
{
|
||||||
|
const enum square_index sq = SQUARE_INDEX_D4;
|
||||||
|
const bitboard bishop1 = SQUARE_MASK_D4; /* tested bishop */
|
||||||
|
const bitboard bishop2 = SQUARE_MASK_F4; /* another friendly bishop */
|
||||||
|
const bitboard friends = bishop1 | bishop2 | SQUARE_MASK_F6;
|
||||||
|
const bitboard enemies = SQUARE_MASK_B2;
|
||||||
|
const bitboard all_occ = friends | enemies;
|
||||||
|
const bitboard own_occ = friends;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From D4:
|
||||||
|
* NE: E5, then friendly F6 (stop; F6 not included)
|
||||||
|
* SW: C3, B2 (enemy, included, then stop)
|
||||||
|
* NW: C5, B6, A7
|
||||||
|
* SE: E3, F2, G1
|
||||||
|
* Bishop at F4 is irrelevant; it does not sit on a diagonal from D4.
|
||||||
|
*/
|
||||||
|
bitboard expected = 0ULL;
|
||||||
|
expected |= SQUARE_MASK_E5;
|
||||||
|
expected |= SQUARE_MASK_C3 | SQUARE_MASK_B2;
|
||||||
|
expected |= SQUARE_MASK_C5 | SQUARE_MASK_B6 | SQUARE_MASK_A7;
|
||||||
|
expected |= SQUARE_MASK_E3 | SQUARE_MASK_F2 | SQUARE_MASK_G1;
|
||||||
|
|
||||||
|
print_bishop_test("Bishop Test 5: D4, mixed friend/enemy + extra bishop F4", sq, all_occ, own_occ);
|
||||||
|
|
||||||
|
const bitboard attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
|
||||||
|
assert(attacks == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\nAll bishop_attacks_from_index tests passed.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
test_rooks();
|
||||||
|
test_bishops();
|
||||||
|
|
||||||
|
for (int i = 40; i < 47; i++) {
|
||||||
|
fprintf(stdout, "\033[30;%dm ", i);
|
||||||
|
}
|
||||||
|
fprintf(stdout, "\033[0m\n"); /* reset background color */
|
||||||
|
struct board board = BOARD_INITIAL;
|
||||||
|
|
||||||
|
board_print(&board, stdout);
|
||||||
|
|
||||||
|
printf("\nFile C3:\n");
|
||||||
|
bitboard_print(FILEMASK_C, stdout);
|
||||||
|
|
||||||
|
printf("\nRank 2:\n");
|
||||||
|
bitboard_print(RANKMASK_2, stdout);
|
||||||
|
|
||||||
|
printf("\nKnight at C3 moves:\n");
|
||||||
|
bitboard_print(knight_attacks(SQUARE_MASK_C3), stdout);
|
||||||
|
|
||||||
|
printf("\nKnight at B2 moves:\n");
|
||||||
|
bitboard_print(knight_attacks(SQUARE_MASK_B2), stdout);
|
||||||
|
|
||||||
|
printf("\nKnight at A1, H8 moves:\n");
|
||||||
|
bitboard_print(knight_attacks(SQUARE_MASK_A1 | SQUARE_MASK_H8), stdout);
|
||||||
|
|
||||||
|
printf("\nWhite pawn at E4 moves:\n");
|
||||||
|
bitboard_print(
|
||||||
|
pawn_white_moves(
|
||||||
|
/* p */ SQUARE_MASK_E4,
|
||||||
|
/* empty */ ~0ULL
|
||||||
|
),
|
||||||
|
stdout);
|
||||||
|
|
||||||
|
printf("\nWhite pawns at E2, F4, A3 moves:\n");
|
||||||
|
bitboard_print(
|
||||||
|
pawn_white_moves(
|
||||||
|
/* p */ SQUARE_MASK_E2 | SQUARE_MASK_F4 | SQUARE_MASK_A3,
|
||||||
|
/* empty */ ~0ULL
|
||||||
|
),
|
||||||
|
stdout);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user