-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtests.c
More file actions
523 lines (433 loc) · 16.7 KB
/
tests.c
File metadata and controls
523 lines (433 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
#define FEATURE_STOPPABLE_SEARCH
#define FEATURE_USE_PRINTF
#undef NSTATS
#define _XOPEN_SOURCE 500
#include <unistd.h> /* usleep */
#include <pthread.h>
#include "engine.h"
#include "board_print.h"
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
/* Tests are mostly generated by ChatGPT */
#define test_expect(expr) if (!(expr)) { fprintf(stderr, "test failed: expected <" #expr ">\n"); exit(EXIT_FAILURE); }
static void print_rook_test(const char *label,
Sq8 sq,
Bb64 all_occ,
Bb64 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 Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
printf("Rook attacks:\n");
bitboard_print(attacks, stdout);
}
static void test_rooks()
{
{
const Sq8 sq = SQ_A1;
const Bb64 rook = SQMASK_A1;
const Bb64 all_occ = rook;
const Bb64 own_occ = rook;
/* Expected: full rank 1 and file A, except A1 */
Bb64 expected = (FILE_MASK_A | RANK_MASK_1) & ~SQMASK_A1;
print_rook_test("Test 1: Rook at A1, empty board", sq, all_occ, own_occ);
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
{
const Sq8 sq = SQ_A1;
const Bb64 rook = SQMASK_A1;
const Bb64 own_block = SQMASK_A2 | SQMASK_B1;
const Bb64 all_occ = rook | own_block;
const Bb64 own_occ = all_occ;
/* Expected: no legal moves (immediately blocked both directions) */
const Bb64 expected = 0ULL;
print_rook_test("Test 2: Rook at A1, own blockers A2, B1", sq, all_occ, own_occ);
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
{
const Sq8 sq = SQ_A1;
const Bb64 rook = SQMASK_A1;
const Bb64 enemies = SQMASK_A3 | SQMASK_C1;
const Bb64 all_occ = rook | enemies;
const Bb64 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)
*/
Bb64 expected = 0ULL;
expected |= SQMASK_A2 | SQMASK_A3;
expected |= SQMASK_B1 | SQMASK_C1;
print_rook_test("Test 3: Rook at A1, enemy blockers A3, C1", sq, all_occ, own_occ);
const Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Rook Test 6: center rook on empty board */
{
const Sq8 sq = SQ_E5;
const Bb64 rook = SQMASK_E5;
const Bb64 all_occ = rook;
const Bb64 own_occ = rook;
/* Full rank 5 and file E, except E5 itself */
Bb64 expected = (FILE_MASK_E | RANK_MASK_5) & ~SQMASK_E5;
print_rook_test("Rook Test 6: E5, empty board", sq, all_occ, own_occ);
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Rook Test 7: center rook, mixed blockers on rays */
{
const Sq8 sq = SQ_E5;
const Bb64 rook = SQMASK_E5;
/* Friendly: E7 and C5; Enemy: E3 and H5 */
const Bb64 friends = rook | SQMASK_E7 | SQMASK_C5;
const Bb64 enemies = SQMASK_E3 | SQMASK_H5;
const Bb64 all_occ = friends | enemies;
const Bb64 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)
*/
Bb64 expected = 0ULL;
expected |= SQMASK_E6;
expected |= SQMASK_E4 | SQMASK_E3;
expected |= SQMASK_D5;
expected |= SQMASK_F5 | SQMASK_G5 | SQMASK_H5;
print_rook_test("Rook Test 7: E5, friends E7/C5, enemies E3/H5", sq, all_occ, own_occ);
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Rook Test 8: edge rook on empty board (top edge, not corner) */
{
const Sq8 sq = SQ_C8;
const Bb64 rook = SQMASK_C8;
const Bb64 all_occ = rook;
const Bb64 own_occ = rook;
/*
* From C8:
* Down file C: C7..C1
* Across rank 8: A8,B8,D8,E8,F8,G8,H8
*/
Bb64 expected = 0ULL;
expected |= (FILE_MASK_C & ~SQMASK_C8);
expected |= (RANK_MASK_8 & ~SQMASK_C8);
print_rook_test("Rook Test 8: C8, empty board", sq, all_occ, own_occ);
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Rook Test 9: rook completely boxed in by friendly orthogonal neighbors */
{
const Sq8 sq = SQ_D4;
const Bb64 rook = SQMASK_D4;
const Bb64 friends = rook |
SQMASK_D5 | SQMASK_D3 |
SQMASK_C4 | SQMASK_E4;
const Bb64 enemies = 0ULL;
const Bb64 all_occ = friends | enemies;
const Bb64 own_occ = friends;
/* All four rays are immediately blocked by own pieces */
const Bb64 expected = 0ULL;
print_rook_test("Rook Test 9: D4, boxed by own pieces at D5/D3/C4/E4",
sq, all_occ, own_occ);
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Rook Test 10: rook on file with non-interfering off-ray pieces */
{
const Sq8 sq = SQ_A4;
const Bb64 rook = SQMASK_A4;
/* Pieces placed off the rook's rank/file; they should have no effect */
const Bb64 off_ray = SQMASK_C1 | SQMASK_F6 | SQMASK_H8;
(void)off_ray;
const Bb64 friends = rook | SQMASK_C1;
const Bb64 enemies = SQMASK_F6 | SQMASK_H8;
const Bb64 all_occ = friends | enemies;
const Bb64 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.
*/
Bb64 expected = 0ULL;
expected |= (FILE_MASK_A | RANK_MASK_4) & ~SQMASK_A4;
print_rook_test("Rook Test 10: A4, random off-ray pieces C1/F6/H8",
sq, all_occ, own_occ);
Bb64 attacks = rook_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
}
static void print_bishop_test(const char *label,
Sq8 sq,
Bb64 all_occ,
Bb64 own_occ)
{
fprintf(stderr, "\n%s\n", label);
fprintf(stderr, "All occ:\n");
bitboard_print(all_occ, stderr);
fprintf(stderr, "Own occ:\n");
bitboard_print(own_occ, stderr);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
fprintf(stderr, "Bishop attacks:\n");
bitboard_print(attacks, stderr);
}
static void test_bishops(void)
{
/* Test 1: Bishop at D4 on empty board (only bishop present) */
{
const Sq8 sq = SQ_D4;
const Bb64 bishop = SQMASK_D4;
const Bb64 all_occ = bishop;
const Bb64 own_occ = bishop;
/*
* Expected diagonals from D4:
* NE: E5, F6, G7, H8
* NW: C5, B6, A7
* SE: E3, F2, G1
* SW: C3, B2, A1
*/
Bb64 expected = 0ULL;
expected |= SQMASK_E5 | SQMASK_F6 | SQMASK_G7 | SQMASK_H8;
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
expected |= SQMASK_C3 | SQMASK_B2 | SQMASK_A1;
print_bishop_test("Bishop Test 1: D4, empty board", sq, all_occ, own_occ);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Test 2: Bishop at C1 on empty board (only bishop present) */
{
const Sq8 sq = SQ_C1;
const Bb64 bishop = SQMASK_C1;
const Bb64 all_occ = bishop;
const Bb64 own_occ = bishop;
/*
* From C1, diagonals:
* NE: D2, E3, F4, G5, H6
* NW: B2, A3
* SE / SW: none (edge of board)
*/
Bb64 expected = 0ULL;
expected |= SQMASK_D2 | SQMASK_E3 | SQMASK_F4 |
SQMASK_G5 | SQMASK_H6;
expected |= SQMASK_B2 | SQMASK_A3;
print_bishop_test("Bishop Test 2: C1, empty board", sq, all_occ, own_occ);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Test 3: Bishop at D4, friendly blockers at F6 and B2 (no enemies) */
{
const Sq8 sq = SQ_D4;
const Bb64 bishop = SQMASK_D4;
const Bb64 friends = bishop | SQMASK_F6 | SQMASK_B2;
const Bb64 enemies = 0ULL;
const Bb64 all_occ = friends | enemies;
const Bb64 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)
*/
Bb64 expected = 0ULL;
expected |= SQMASK_E5;
expected |= SQMASK_C3;
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
print_bishop_test("Bishop Test 3: D4, friendly blockers F6, B2", sq, all_occ, own_occ);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Test 4: Bishop at D4, enemy blockers at F6 and B2 (no other friends) */
{
const Sq8 sq = SQ_D4;
const Bb64 bishop = SQMASK_D4;
const Bb64 friends = bishop;
const Bb64 enemies = SQMASK_F6 | SQMASK_B2;
const Bb64 all_occ = friends | enemies;
const Bb64 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
*/
Bb64 expected = 0ULL;
expected |= SQMASK_E5 | SQMASK_F6;
expected |= SQMASK_C3 | SQMASK_B2;
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
print_bishop_test("Bishop Test 4: D4, enemy blockers F6, B2", sq, all_occ, own_occ);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Test 5: Bishop at D4, mixed friend/enemy + another friendly bishop elsewhere */
{
const Sq8 sq = SQ_D4;
const Bb64 bishop1 = SQMASK_D4; /* tested bishop */
const Bb64 bishop2 = SQMASK_F4; /* another friendly bishop */
const Bb64 friends = bishop1 | bishop2 | SQMASK_F6;
const Bb64 enemies = SQMASK_B2;
const Bb64 all_occ = friends | enemies;
const Bb64 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.
*/
Bb64 expected = 0ULL;
expected |= SQMASK_E5;
expected |= SQMASK_C3 | SQMASK_B2;
expected |= SQMASK_C5 | SQMASK_B6 | SQMASK_A7;
expected |= SQMASK_E3 | SQMASK_F2 | SQMASK_G1;
print_bishop_test("Bishop Test 5: D4, mixed friend/enemy + extra bishop F4", sq, all_occ, own_occ);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
test_expect(attacks == expected);
}
/* Test 6: Bishop at H8, no occupancy */
{
const Sq8 sq = SQ_H8;
const Bb64 all_occ = 0ULL;
const Bb64 own_occ = MASK_FROM_SQ(sq);
/*
* 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.
*/
Bb64 expected = 0ULL;
expected = SQMASK_G7 | SQMASK_F6 | SQMASK_E5 | SQMASK_D4 |
SQMASK_C3 | SQMASK_B2 | SQMASK_A1;
print_bishop_test("Bishop Test 6: H8, no occupancy", sq, all_occ, own_occ);
const Bb64 attacks = bishop_attacks_from_index(sq, all_occ) & ~own_occ;
if (attacks != expected) {
bitboard_print(attacks, stderr);
}
test_expect(attacks == expected);
}
printf("\nAll bishop_attacks_from_index tests passed.\n");
}
struct timeout_params {
struct searching_flag* x;
uint32_t v;
useconds_t us;
};
void* set_after_timeout(void* x)
{
struct timeout_params* p = x;
usleep(p->us);
searching_stop(p->x);
return NULL;
}
int main()
{
bool const print_threats = true;
printf("sizeof pos: %zu\n", sizeof (struct pos));
printf("sizeof tt: %zu\n", sizeof (struct tt));
printf("sizeof board: %zu\n", sizeof (struct board));
printf("sizeof search_option: %zu\n", sizeof (struct search_option));
printf("sizeof all tt entries: %zu\n", (1<<26) * sizeof (struct search_option));
#if 0
test_rooks();
test_bishops();
#endif
for (int i = 40; i < 47; i++) {
fprintf(stdout, "\033[30;%dm ", i);
}
fprintf(stdout, "\033[0m\n"); /* reset background color */
/* board could be too big for the stack */
struct board* b = malloc(sizeof *b);
if (!b) {
abort();
}
*b = BOARD_INIT;
board_init(b);
//board_load_fen_unsafe(b, "1n1q1rk1/r1p2P2/1p1pp2p/pB2P3/2P5/PPN5/6b1/3QK1NR b - - 0 1");
//board_load_fen_unsafe(b, "8/8/2kr4/6R1/4K3/6P1/8/8 b - - 0 1");
//board_load_fen_unsafe(b, "8/8/5R2/8/2K3PP/1B2k3/8/8 b - - 1 4");
//board_load_fen_unsafe(b, "4r1k1/b1P1B3/P5pK/1p4P1/7q/8/4p3/1R6 w - - 1 54");
//board_print_fen(b->pos, stdout);
board_print(&b->pos, NULL, stdout, print_threats);
for (int turn = 0; turn < 200; ++turn) {
/*
move_count = 0;
all_pseudolegal_moves(&b->pos, MG_ALL, b->pos.moving_side, &move_count, moves);
if (move_count == 0) {
printf("no moves for %s, aborting\n", side_str[b->pos.moving_side]);
board_print(&b->pos, NULL, stdout, print_threats);
b->pos.moving_side = other_side(b->pos.moving_side);
board_print(&b->pos, NULL, stdout, print_threats);
break;
}
*/
pthread_t timer;
struct searching_flag searching;
searching_start(&searching);
//atomic_init(&searching, 1);
#if 1
struct timeout_params timer_params = {
.x = &searching,
.v = false,
.us = 2*1000*1000,
};
pthread_create(&timer, NULL, set_after_timeout, &timer_params);
#endif
struct search_result sr = search(b, b->pos.moving_side, 25, &searching);
struct move move = sr.move;
Score16 const score = sr.score;
printf("move %d: {\n"
" .from = %s, (%s)\n"
" .to = %s,\n"
" .score = %d,\n"
" .mask = "
"",
turn,
sq8_display[move.from],
piece_str[b->mailbox[move.from]],
sq8_display[move.to],
score
);
printf("\n}\n");
enum move_result const r = board_move(b, move);
/* illegal board state from an engine move (i.e. hanging king) means checkmate */
if (!board_is_legal(b)) {
printf("checkmate!\n");
break;
}
#if 1
board_print_fen(&b->pos, stdout);
tt_print_stats(&b->tt, stdout);
board_print(&b->pos, &move, stdout, print_threats);
fprintf(stderr, "board hist len: %zu\n", b->hist.length);
fprintf(stderr, "\n------------------------\n\n\n");
#endif
if (r == MR_STALEMATE) {
printf("stalemate\n");
break;
}
if (b->pos.pieces[SIDE_WHITE][PIECE_KING] == 0ULL) {
printf("white king gone!!\n");
exit(EXIT_FAILURE);
}
if (b->pos.pieces[SIDE_BLACK][PIECE_KING] == 0ULL) {
printf("black king gone!!\n");
exit(EXIT_FAILURE);
}
}
return EXIT_SUCCESS;
}