-
Notifications
You must be signed in to change notification settings - Fork 0
/
jangine.cpp
1791 lines (1502 loc) · 71 KB
/
jangine.cpp
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
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <iostream>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdbool>
#include <cstdint>
#include <cinttypes>
#include <cmath>
#include <ctime>
#include <random>
#include <unistd.h>
#include <map>
#include <set>
#include <deque>
#include <vector>
#include <sstream>
#include <algorithm>
typedef int_fast16_t num;
#define SIMPLE_EVAL // TODO: just-material eval, piece-square-eval, and turochamp eval
#define NO_QUIES 0
#define QUIES_DEPTH 0 // TODO: limit search of quiescence captures
#define MAX_KILLER_MOVES 1
#define NO_PRINCIPAL_VARIATION_SEARCH 0 // seems to actually be slower since my leaf eval is so cheap
#pragma GCC diagnostic ignored "-Wnarrowing"
num PAWN = 1, KNIGHT = 2, BISHOP = 4, ROOK = 8, QUEEN = 16, KING = 32, WHITE = 64, BLACK = 128;
num KING_EARLYGAME = 8001, KING_ENDGAME = 8002, PAWN_EARLYGAME = 9001, PAWN_ENDGAME = 9002;
num COLORBLIND = ~(WHITE | BLACK); // use 'piece & COLORBLIND' to remove color from a piece
num inf = 20000; num inf_king = 26000;
num OOB = 256; num OOV = 0; // out-of-bounds for "10x12" (12x10) boards and OOB value
std::map<num, char> piece_to_letter = {{0, ' '}, {1, ' '}, {2, 'N'}, {4, 'B'}, {8, 'R'}, {16, 'Q'}, {32, 'K'}};
std::map<num, std::string> id_to_unicode = {
{ 0, "."},
{ 65, "♙"}, { 66, "♘"}, { 68, "♗"}, { 72, "♖"}, { 80, "♕"}, { 96, "♔"},
{129, "♟"}, {130, "♞"}, {132, "♝"}, {136, "♜"}, {144, "♛"}, {160, "♚"},
};
num PIECE_SQUARE_TABLES[257][120] = {0}; // C array is much faster to query than a C++ map
std::map<num, std::array<num, 120>> PIECE_SQUARE_TABLES_SOURCE = {
{PAWN_EARLYGAME, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, 0, 0, 0, 0, 0, 0, 0, 0, OOV,
OOV, 60, 80, 80, 80, 80, 80, 80, 60, OOV,
OOV, 20, 40, 40, 40, 40, 40, 40, 40, OOV,
OOV, 0, 0, 0, 10, 10, 10, 10, 10, OOV,
OOV, 5, 0, 10, 30, 30, 0, 0, 0, OOV,
OOV, 5, 0, 0, 0, 0, 0, 0, 0, OOV,
OOV, 0, 0, 0, -20, -20, 30, 30, 15, OOV,
OOV, 0, 0, 0, 0, 0, 0, 0, 0, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
{PAWN_ENDGAME, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, 0, 0, 0, 0, 0, 0, 0, 0, OOV,
OOV, 80, 100, 100, 100, 100, 100, 100, 80, OOV,
OOV, 40, 60, 60, 60, 60, 60, 60, 40, OOV,
OOV, 30, 40, 40, 40, 40, 40, 40, 30, OOV,
OOV, 10, 20, 20, 20, 20, 20, 20, 10, OOV,
OOV, 5, 10, 10, 10, 10, 10, 10, 5, OOV,
OOV, 0, 0, 0, 0, 0, 0, 0, 0, OOV,
OOV, 0, 0, 0, 0, 0, 0, 0, 0, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
{KNIGHT, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, -60, -40, -30, -30, -30, -30, -40, -60, OOV,
OOV, -40, -20, 0, 0, 0, 0, -20, -40, OOV,
OOV, -30, 0, 10, 15, 15, 10, 0, -30, OOV,
OOV, -30, 0, 15, 20, 20, 15, 0, -30, OOV,
OOV, -30, 0, 15, 20, 20, 15, 0, -30, OOV,
OOV, -30, 5, 15, 15, 15, 15, 5, -30, OOV,
OOV, -40, -20, 0, 10, 10, 0, -20, -40, OOV,
OOV, -60, -40, -30, -30, -30, -30, -40, -60, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
// bishop isn't actually bad on the bad row but prevents castling
{BISHOP, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, -20, -10, -10, -10, -10, -10, -10, -20, OOV,
OOV, -10, 0, 0, 0, 0, 0, 0, -10, OOV,
OOV, -10, 0, 5, 10, 10, 5, 0, -10, OOV,
OOV, -10, 15, 5, 10, 10, 5, 15, -10, OOV,
OOV, -10, 0, 15, 10, 10, 15, 0, -10, OOV,
OOV, -10, 10, 10, 10, 10, 10, 10, -10, OOV,
OOV, -10, 5, 0, 0, 0, 0, 5, -10, OOV,
OOV, -20, -10, -10, -10, -10, -20, -10, -20, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
{ROOK, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, -5, 0, 0, 0, 0, 0, 0, -5, OOV,
OOV, -5, 20, 20, 20, 20, 20, 20, -5, OOV,
OOV, -5, 0, 0, 10, 10, 0, 0, -5, OOV,
OOV, -5, 0, 0, 10, 10, 0, 0, -5, OOV,
OOV, -5, 0, 0, 10, 10, 0, 0, -5, OOV,
OOV, -5, 0, 0, 10, 10, 0, 0, -5, OOV,
OOV, -5, 0, 0, 10, 10, 0, 0, -5, OOV,
OOV, -5, 0, 0, 10, 10, 0, 0, -5, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
// TODO: avoid capturing b7?
{QUEEN, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, -20, -10, -10, -5, -5, -10, -10, -20, OOV,
OOV, -10, 0, 0, 0, 0, 0, 0, -10, OOV,
OOV, -10, 0, 5, 5, 5, 5, 0, -10, OOV,
OOV, -5, 0, 5, 5, 5, 5, 0, -5, OOV,
OOV, 0, 0, 5, 5, 5, 5, 0, -5, OOV,
OOV, -10, 5, 5, 5, 5, 5, 0, -10, OOV,
OOV, -10, 0, 5, 0, 0, 0, 0, -10, OOV,
OOV, -20, -10, -10, -5, -5, -10, -10, -20, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
{KING_EARLYGAME, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, -20, -20, -20, -20, -20, -20, -20, -20, OOV,
OOV, -20, -20, -20, -20, -20, -20, -20, -20, OOV,
OOV, -20, -20, -20, -20, -20, -20, -20, -20, OOV,
OOV, -20, -20, -20, -20, -20, -20, -20, -20, OOV,
OOV, -20, -20, -20, -20, -20, -20, -20, -20, OOV,
OOV, -20, -20, -20, -20, -20, -20, -20, -20, OOV,
OOV, 20, 20, 10, 0, 0, 0, 40, 40, OOV,
OOV, 20, 30, 20, 0, 10, 0, 50, 40, OOV, // strongly encourage short castling
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}},
{KING_ENDGAME, std::array<num, 120>{
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, 10, 20, 30, 40, 40, 30, 20, 10, OOV,
OOV, 20, 30, 40, 40, 40, 40, 30, 20, OOV,
OOV, 30, 40, 50, 50, 50, 50, 40, 30, OOV,
OOV, 20, 30, 40, 50, 50, 40, 30, 20, OOV,
OOV, 10, 10, 30, 40, 40, 30, 10, 10, OOV,
OOV, 0, 0, 20, 30, 30, 20, 0, 0, OOV,
OOV, -30, -30, 0, 0, 0, 0, -30, -30, OOV,
OOV, -30, -30, -30, -30, -30, -30, -30, -30, OOV, // encourage going to the center
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV, OOV,
}}
};
typedef struct CASTLINGRIGHTS {
bool lc; // left-castling still possible (long castling)
bool rc; // right-castling still possible (short castling)
} CASTLINGRIGHTS;
typedef struct Move {
int8_t from;
int8_t to;
char prom; // promote to piece, castle, or en-passant
// needed for std::map
bool operator<( const Move & that ) const {
return (this->from << 16) + (this->to << 8) + this->prom <
(that.from << 16) + (that.to << 8) + that.prom;
}
bool operator==( const Move& that ) const {
return this->from == that.from and this->to == that.to and this->prom == that.prom;
}
} Move;
// Store captured piece and new castling rights so move can be undone
typedef struct PiecePlusCatling {
num hit_piece;
CASTLINGRIGHTS c_rights_w;
CASTLINGRIGHTS c_rights_b;
} PiecePlusCatling;
int8_t tte_exact = 0; int8_t tte_alpha = 1; int8_t tte_beta = 2;
typedef struct TTEntry {
int64_t zobrint_hash;
Move move; // either the best move found if an exact node, or a node good enough to cause a cutoff
int16_t value;
int8_t tte_flag; // best, alpha, or beta value
int8_t depth_left;
// TODO: why not unsigned
} TTEntry;
CASTLINGRIGHTS CASTLINGWHITE = {true, true};
CASTLINGRIGHTS CASTLINGBLACK = {true, true};
Move NULLMOVE = {0};
Move LASTMOVE = {0};
Move LASTMOVE_GAME = {0};
TTEntry TRANSPOS_TABLE[16777216] = {0}; // 4+3+2+1+1 = 11 bits -> 11 * 2**24 bytes = 176 MiB
#define ZOB_MASK 0xffffff
num PIECEDIRS[65][9] = {0};
bool PIECESLIDE[65] = {0};
num PIECEVALS[257] = {0};
num board[120] = {0};
num board_eval = 0; // takes about 10% of compute time
int num_moves = 0;
bool IM_WHITE = false;
bool started = true;
num MODE_UCI = 1; num MODE_XBOARD = 2; num MODE_XJ = 3; num MODE = MODE_UCI;
num VERBOSITY = 0; // 0: bestmove only, 1: uci INFO, 2: print boards/state, 3: all lines, 4: tons of output
int64_t NODES_NORMAL = 0;
int64_t NODES_QUIES = 0;
void pprint() {
if (VERBOSITY < 2) return;
for (num i = 0; i < 8; ++i) {
printf(" %ld ", (8 - i));
for (num j = 0; j < 8; ++j) {
std::string s = id_to_unicode[board[10*i+20+j+1]];
printf("%s ", s.c_str());
}
printf(" \n");
}
printf(" a b c d e f g h\n\n");
}
bool startswith(const char *pre, const char *str) {
size_t lenpre = strlen(pre), lenstr = strlen(str);
return lenstr >= lenpre and strncmp(pre, str, lenpre) == 0;
}
bool input_is_move(const char* s) {
if (strlen(s) < 5 || strlen(s) > 6) // newline character
return false;
return 'a' <= s[0] and s[0] <= 'h' and 'a' <= s[2] and s[2] <= 'h' and
'1' <= s[1] and s[1] <= '8' and '1' <= s[3] and s[3] <= '8';
}
num default_board[120] = { // https://www.chessprogramming.org/10x12_Board
OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB,
OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB,
OOB, BLACK + ROOK, BLACK + KNIGHT, BLACK + BISHOP, BLACK + QUEEN,
BLACK + KING, BLACK + BISHOP, BLACK + KNIGHT, BLACK + ROOK, OOB,
OOB, BLACK + PAWN, BLACK + PAWN, BLACK + PAWN, BLACK + PAWN,
BLACK + PAWN, BLACK + PAWN, BLACK + PAWN, BLACK + PAWN, OOB,
OOB, 0, 0, 0, 0, 0, 0, 0, 0, OOB,
OOB, 0, 0, 0, 0, 0, 0, 0, 0, OOB,
OOB, 0, 0, 0, 0, 0, 0, 0, 0, OOB,
OOB, 0, 0, 0, 0, 0, 0, 0, 0, OOB,
OOB, WHITE + PAWN, WHITE + PAWN, WHITE + PAWN, WHITE + PAWN,
WHITE + PAWN, WHITE + PAWN, WHITE + PAWN, WHITE + PAWN, OOB,
OOB, WHITE + ROOK, WHITE + KNIGHT, WHITE + BISHOP, WHITE + QUEEN,
WHITE + KING, WHITE + BISHOP, WHITE + KNIGHT, WHITE + ROOK, OOB,
OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB,
OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB, OOB
};
num KINGPOS_WHITE = 95;
num KINGPOS_BLACK = 25;
int64_t zobrint_random_table[257][120] = {0};
// TODO: handle as TTEntry flag instead?
std::set<int64_t> board_positions_seen;
int64_t zobrint_hash = 0;
void init_zobrint() {
std::mt19937_64 e2(5489u); // fixed default seed
std::uniform_int_distribution<long long int> dist(0); // undefined for in64_t :/
std::array<num, 13> pieces = {
1, // keep zobrint_random_table[1][0] for who to move
WHITE + PAWN, WHITE + KNIGHT, WHITE + BISHOP, WHITE + ROOK, WHITE + QUEEN, WHITE + KING,
BLACK + PAWN, BLACK + KNIGHT, BLACK + BISHOP, BLACK + ROOK, BLACK + QUEEN, BLACK + KING,
};
// note that "0" entries have no effect on the final result (for example in zrt[0] for empty squares)
for (const num& piece : pieces)
for (int i = 0; i < 64; ++i)
zobrint_random_table[piece][10*(i/8 + 2) + (i % 8) + 1] = dist(e2);
zobrint_random_table[1][0] = zobrint_random_table[1][21]; // for side-to-move
}
int64_t board_to_zobrint_hash() {
int64_t zobrint_hash_ = 0;
for (int i = 0; i < 120; ++i)
zobrint_hash_ ^= zobrint_random_table[board[i]][i];
if (not IM_WHITE)
zobrint_hash_ ^= zobrint_random_table[1][0];
// TODO: Add en passant ranks and castling rights
// Okay to implement this without castling rights and en passant at first
// because this is only used for repetition detection and PV store, not for actual transpositions
// We just want to avoid voluntarily going into repeated positions in otherwise winning endgames
return zobrint_hash_;
}
void set_piece_square_table(bool is_endgame = false) {
for (num j = 0; j < 120; j++) {
PIECE_SQUARE_TABLES[0][j] = 0;
PIECE_SQUARE_TABLES[OOB][j] = 0;
PIECE_SQUARE_TABLES[KNIGHT][j] = PIECE_SQUARE_TABLES_SOURCE[KNIGHT][j];
PIECE_SQUARE_TABLES[BISHOP][j] = PIECE_SQUARE_TABLES_SOURCE[BISHOP][j];
PIECE_SQUARE_TABLES[ ROOK][j] = PIECE_SQUARE_TABLES_SOURCE[ ROOK][j];
PIECE_SQUARE_TABLES[ QUEEN][j] = PIECE_SQUARE_TABLES_SOURCE[ QUEEN][j];
PIECE_SQUARE_TABLES[KING][j] = PIECE_SQUARE_TABLES_SOURCE[is_endgame ? KING_ENDGAME : KING_EARLYGAME][j];
PIECE_SQUARE_TABLES[PAWN][j] = PIECE_SQUARE_TABLES_SOURCE[is_endgame ? PAWN_ENDGAME : PAWN_EARLYGAME][j];
for (num piece = PAWN; piece <= KING; piece <<= 1) {
num j_black = 10 * (11 - (j / 10)) + (j % 10);
PIECE_SQUARE_TABLES[piece + WHITE][j ] = PIECE_SQUARE_TABLES[piece][j];
PIECE_SQUARE_TABLES[piece + BLACK][j_black] = -PIECE_SQUARE_TABLES[piece][j];
}
}
}
void board_initial_position() { // setting up a game
for (int i = 0; i < 120; ++i)
board[i] = default_board[i];
set_piece_square_table();
KINGPOS_WHITE = 95;
KINGPOS_BLACK = 25;
CASTLINGWHITE = {true, true};
CASTLINGBLACK = {true, true};
board_eval = 0;
// TODO: also reset LASTMOVE, although that would lead to tablebase hits on the first move which would cause a super slowdown :/
IM_WHITE = true;
zobrint_hash = board_to_zobrint_hash();
board_positions_seen.clear();
board_positions_seen.insert(zobrint_hash);
}
void board_clear() { // setting up random positions
board_initial_position();
zobrint_hash = 0;
for (int i = 2; i < 10; ++i)
for (int j = 1; j < 9; ++j)
board[10*i+j] = 0;
CASTLINGWHITE = {false, false};
CASTLINGBLACK = {false, false};
}
void board_from_fen(const char* fen) { // setting up a game
const char* c = fen;
board_clear();
num i = 2;
num j = 1;
// board
while (*c != ' ') {
if (*c == '/') { i++; j = 0; }
if (*c == '1') j += 0;
if (*c == '2') j += 1;
if (*c == '3') j += 2;
if (*c == '4') j += 3;
if (*c == '5') j += 4;
if (*c == '6') j += 5;
if (*c == '7') j += 6;
if (*c == '8') j += 7;
if (*c == 'P') board[10*i+j] = WHITE + PAWN;
if (*c == 'p') board[10*i+j] = BLACK + PAWN;
if (*c == 'N') board[10*i+j] = WHITE + KNIGHT;
if (*c == 'n') board[10*i+j] = BLACK + KNIGHT;
if (*c == 'B') board[10*i+j] = WHITE + BISHOP;
if (*c == 'b') board[10*i+j] = BLACK + BISHOP;
if (*c == 'R') board[10*i+j] = WHITE + ROOK;
if (*c == 'r') board[10*i+j] = BLACK + ROOK;
if (*c == 'Q') board[10*i+j] = WHITE + QUEEN;
if (*c == 'q') board[10*i+j] = BLACK + QUEEN;
if (*c == 'K') {
board[10*i+j] = WHITE + KING;
KINGPOS_WHITE = 10*i+j;
}
if (*c == 'k') {
board[10*i+j] = BLACK + KING;
KINGPOS_BLACK = 10*i+j;
}
j++;
c++;
}
// side to move
// if you are a real C pro, replace this block with (*((++c)++) == 'w') ;)
c++;
IM_WHITE = (*c == 'w');
num_moves += 30 + (num_moves % 2 != IM_WHITE);
c++;
// castling => board_clear() sets it all to false
c++;
while (*c != ' ') {
if (*c == 'Q') CASTLINGWHITE.lc = true;
if (*c == 'K') CASTLINGWHITE.rc = true;
if (*c == 'q') CASTLINGBLACK.lc = true;
if (*c == 'k') CASTLINGBLACK.rc = true;
c++;
}
// en passant target square
c++;
if (*c != '-') {
num j = (*c - 'a') + 1;
c++;
num i = ('8' - *c) + 2;
bool was_white = !!(board[10*(i-1)+j] & WHITE);
Move mv = {was_white ? 10*(i+1)+j : 10*(i-1)+j, was_white ? 10*(i-1)+j : 10*(i+1)+j};
LASTMOVE_GAME = mv;
} else {
LASTMOVE_GAME = {0};
}
// TODO ignore halfmove/fullmove clocks for now since no global move variable yet
zobrint_hash = board_to_zobrint_hash();
board_positions_seen.insert(zobrint_hash);
}
num SEARCH_ADAPTIVE_DEPTH = 6; // how many plies to search
num MAX_SEARCH_DEPTH = 99; // has to be somewhat low because depth_left is stored as 8-bit number
num NODE_DEPTH = 0;
Move KILLER_TABLE[99][MAX_KILLER_MOVES] = {0}; // table of killer moves for each search depth
uint64_t SEARCH_START_CLOCK = 0;
uint64_t OWN_CLOCK_REMAINING = 180000; // 180 seconds remaining
uint64_t OWN_CLOCK_INCREMENT = 0;
uint64_t TIME_BUDGET_THRESHOLD = 999999999999999;
typedef struct GenMoves {
Move* captures;
Move* captures_end;
Move* quiets;
Move* quiets_end;
} GenMoves;
typedef struct ValuePlusMove {
num value;
Move move;
} ValuePlusMove;
std::string move_to_str(Move mv, bool algebraic = false, bool raw = false) {
char c0 = 'a' + (char) (mv.from % 10 - 1);
char c1 = '0' + (char) (10 - (mv.from / 10)); // 2..9 are valid rows
char c2 = 'a' + (char) (mv.to % 10 - 1);
char c3 = '0' + (char) (10 - (mv.to / 10));
char c4 = (char)(mv.prom ? mv.prom : ' ');
if (!algebraic) {
if (!raw && (c4 == ' ' || c4 == 'c' || c4 == 'e'))
return std::string{c0, c1, c2, c3};
return std::string{c0, c1, c2, c3, c4};
}
num piece = board[mv.from];
num hit_piece = board[mv.to];
char alg0 = piece_to_letter[piece & COLORBLIND];
char alg1 = (hit_piece or mv.prom == 'e') ? 'x' : ' ';
return std::string{alg0, alg1, c2, c3, ' ', ' ', '(', c0, c1, c2, c3, c4, ')'};
}
inline num eval_material(num piece_with_color) {
return PIECEVALS[piece_with_color];
}
inline num eval_piece_on_square(num piece_with_color, num i) {
return PIECE_SQUARE_TABLES[piece_with_color][i];
}
// https://www.chessprogramming.org/Simplified_Evaluation_Function
// Returns centipawn value to a given board position, from WHITE's perspective
// TODO: breakdown into component numbers for better testability
num initial_eval() {
num eval = 0;
// material counting
for (num i = 0; i < 120; ++i)
eval += eval_material(board[i]);
// https://www.chessprogramming.org/Piece-Square_Tables
// piece positioning using piece-square-tables
for (num i = 0; i < 120; ++i)
eval += eval_piece_on_square(board[i], i);
return eval;
}
inline PiecePlusCatling make_move(Move mv) // apparently 7% of time spent in make and unmake
{
if (mv == NULLMOVE)
printf("XXX DANGEROUS! NULL MOVE\n");
num piece = board[mv.from];
num hit_piece = board[mv.to];
board[mv.from] = 0;
board[mv.to] = piece;
board_eval -= eval_material(hit_piece) + eval_piece_on_square(hit_piece, mv.to); // remove captured piece. ignores empty squares (0s) // always empty for en passant
board_eval += eval_piece_on_square(piece, mv.to) - eval_piece_on_square(piece, mv.from); // adjust position values of moved piece
zobrint_hash ^= zobrint_random_table[piece][mv.from]; // xor OUT piece at initial square
zobrint_hash ^= zobrint_random_table[piece][mv.to]; // xor IN piece at destination square
zobrint_hash ^= zobrint_random_table[hit_piece][mv.to]; // xor OUT captured piece (0 if square empty)
if (mv.prom) {
if (mv.prom == 'e') { // en passant
num ep_square = 10 * (mv.from / 10) + (mv.to % 10); // square of the CAPTURED/en-passanted pawn // faster than ternary ? : // can we use LASTMOVE here?
board_eval -= eval_material(board[ep_square]) + eval_piece_on_square(board[ep_square], ep_square); // captured pawn did not get removed earlier
zobrint_hash ^= zobrint_random_table[board[ep_square]][ep_square]; // xor OUT captured pawn
board[ep_square] = 0;
} else if (mv.prom == 'c') { // castling -- rook part
bool is_short_castling = mv.to > mv.from; // otherwise long castling
num rookpos_new = is_short_castling ? (mv.from + 1) : (mv.from - 1); // tests faster because short-castle gets branch-predicted => store castle direction in prom?
num rookpos_old = is_short_castling ? (mv.from + 3) : (mv.from - 4);
board[rookpos_new] = board[rookpos_old];
board[rookpos_old] = 0;
board_eval += eval_piece_on_square(board[rookpos_new], rookpos_new) - eval_piece_on_square(board[rookpos_new], rookpos_old); // rook positioning
zobrint_hash ^= zobrint_random_table[board[rookpos_new]][rookpos_old]; // xor OUT old rook position
zobrint_hash ^= zobrint_random_table[board[rookpos_new]][rookpos_new]; // xor IN new rook position
} else { // pawn promotion
num p = (mv.to < 30 ? WHITE : BLACK);
switch (mv.prom) {
case 'q': p += QUEEN; break;
case 'r': p += ROOK; break;
case 'b': p += BISHOP; break;
case 'n': p += KNIGHT; break;
}
board[mv.to] = p;
board_eval += eval_material(p) - eval_material(piece);
board_eval += eval_piece_on_square(p, mv.to) - eval_piece_on_square(piece, mv.to); // t0/t1 to compensate for wrong pieceval earlier
zobrint_hash ^= zobrint_random_table[piece][mv.to]; // xor OUT promoting pawn
zobrint_hash ^= zobrint_random_table[ p][mv.to]; // xor IN promoted piece
}
}
CASTLINGRIGHTS old_cr_w = CASTLINGWHITE;
CASTLINGRIGHTS old_cr_b = CASTLINGBLACK;
// lose right to castle if king or rook moves
CASTLINGBLACK = {CASTLINGBLACK.lc and mv.from != 21 and mv.from != 25, CASTLINGBLACK.rc and mv.from != 25 and mv.from != 28};
CASTLINGWHITE = {CASTLINGWHITE.lc and mv.from != 91 and mv.from != 95, CASTLINGWHITE.rc and mv.from != 95 and mv.from != 98};
if (piece == WHITE + KING) KINGPOS_WHITE = mv.to;
else if (piece == BLACK + KING) KINGPOS_BLACK = mv.to;
zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move
return {hit_piece, old_cr_w, old_cr_b};
}
// TODO XXX TODO: move LASTMOVE setting to make_move/unmake_move, and maybe return it instead of setting global
inline PiecePlusCatling make_null_move()
{
// TODO: cannot null move if in check
zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move
return {0, CASTLINGWHITE, CASTLINGBLACK};
}
inline void unmake_move(Move mv, num hit_piece, CASTLINGRIGHTS c_rights_w, CASTLINGRIGHTS c_rights_b)
{
num piece = board[mv.to];
board[mv.to] = hit_piece;
board[mv.from] = piece;
board_eval += eval_material(hit_piece) + eval_piece_on_square(hit_piece, mv.to); // add captured piece back in
board_eval -= eval_piece_on_square(piece, mv.to) - eval_piece_on_square(piece, mv.from); // adjust position values of moved piece
zobrint_hash ^= zobrint_random_table[piece][mv.from];
zobrint_hash ^= zobrint_random_table[piece][mv.to];
zobrint_hash ^= zobrint_random_table[hit_piece][mv.to];
if (mv.prom)
{
if (mv.prom == 'e') { // en passant
num ep_square = 10 * (mv.from / 10) + (mv.to % 10); // square of the CAPTURED/en-passanted pawn
board[ep_square] = PAWN + (ep_square < 60 ? BLACK : WHITE);
board_eval += eval_material(board[ep_square]) + eval_piece_on_square(board[ep_square], ep_square); // captured pawn did not get removed earlier
zobrint_hash ^= zobrint_random_table[board[ep_square]][ep_square];
} else if (mv.prom == 'c') { // castling -- undo rook move
bool is_short_castling = mv.to > mv.from; // otherwise long castling
num rookpos_new = is_short_castling ? (mv.from + 1) : (mv.from - 1);
num rookpos_old = is_short_castling ? (mv.from + 3) : (mv.from - 4);
board[rookpos_old] = board[rookpos_new];
board[rookpos_new] = 0;
board_eval += eval_piece_on_square(board[rookpos_old], rookpos_old) - eval_piece_on_square(board[rookpos_old], rookpos_new); // rook positioning
zobrint_hash ^= zobrint_random_table[board[rookpos_old]][rookpos_old];
zobrint_hash ^= zobrint_random_table[board[rookpos_old]][rookpos_new];
} else { // promotion
num old_pawn = PAWN + (mv.to < 30 ? WHITE : BLACK);
board[mv.from] = old_pawn;
board_eval -= eval_material(piece) - eval_material(old_pawn);
board_eval -= eval_piece_on_square(piece, mv.from) - eval_piece_on_square(old_pawn, mv.from); // to compensate for wrong pieceval earlier
zobrint_hash ^= zobrint_random_table[old_pawn][mv.from];
zobrint_hash ^= zobrint_random_table[ piece][mv.from];
}
}
CASTLINGWHITE = c_rights_w;
CASTLINGBLACK = c_rights_b;
if (piece == WHITE + KING) KINGPOS_WHITE = mv.from;
else if (piece == BLACK + KING) KINGPOS_BLACK = mv.from;
zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move
}
inline void unmake_null_move(CASTLINGRIGHTS c_rights_w, CASTLINGRIGHTS c_rights_b)
{
CASTLINGWHITE = c_rights_w;
CASTLINGBLACK = c_rights_b;
zobrint_hash ^= zobrint_random_table[1][0]; // switch side to move
}
void make_move_str(const char* mv)
{
num a = 8 - (mv[1] - '0');
num b = (num)(mv[0] - 'a');
num c = 8 - (mv[3] - '0');
num d = (num)(mv[2] - 'a');
char e = mv[4];
if (abs(d - b) > 1 and board[10*a+20+b+1] & KING) // castling
e = 'c';
if (b != d and board[10*a+20+b+1] & PAWN and not board[10*c+20+d+1]) // en passant (diagonal pawn move to empty square)
e = 'e';
if (e == '\n')
e = '\0';
Move to_mv = {10*a + 20 + b + 1, 10*c + 20 + d + 1, e};
make_move(to_mv);
LASTMOVE_GAME = to_mv; // copies the struct
board_positions_seen.insert(zobrint_hash);
}
void printf_move_eval(num depth, ValuePlusMove rec, bool accurate) // print eval of move (before call to make_move)
{
if (VERBOSITY < 1) return;
// TODO support xboard
if (MODE == MODE_UCI)
printf("info depth %ld score cp %ld nodes %ld pv", depth, rec.value, NODES_NORMAL + NODES_QUIES);
else if (MODE == MODE_XJ) {
double time_expired = (1000.0 * std::clock()) / CLOCKS_PER_SEC - SEARCH_START_CLOCK; // millis
std::string move_str = move_to_str(rec.move, true);
const char* cstr = move_str.c_str();
printf("MOVE %15s |", cstr);
printf(" EVAL %7.2f %c | D%2ld | NN%9ld | NQ%9ld | t%7.3f | VAR ... ",
(float)(rec.value) / 100, accurate ? ' ' : '?', SEARCH_ADAPTIVE_DEPTH, NODES_NORMAL, NODES_QUIES, time_expired / 1000);
}
// reveal PV from hash table, this saves work of returning a std::deque
std::deque<Move> moves;
std::deque<PiecePlusCatling> ppcs;
num i = 0; // prevent infinite repetition
// TODO: revealing PV from hash table is faster but cuts off parts of it
while ((TRANSPOS_TABLE[zobrint_hash & ZOB_MASK].zobrint_hash == zobrint_hash) && (i < 12)) {
Move mv = TRANSPOS_TABLE[zobrint_hash & ZOB_MASK].move;
// if (i == 0 && !(rec.move == mv)) std::exit(2);
if (MODE == MODE_UCI)
std::cout << " " << move_to_str(mv, false);
else if (MODE == MODE_XJ) {
if (i >= 1)
std::cout << move_to_str(mv, true) << " ";
}
PiecePlusCatling ppc = make_move(mv); // updates zobrist_hash
moves.push_back(mv);
ppcs.push_back(ppc);
++i;
}
// TODO: check pesudo-legality -> re-use pv_in_hash_table as function
// TODO: check this after every mode + only print this in MODE_XJ
// if ((TRANSPOS_TABLE[zobrint_hash & ZOB_MASK].zobrint_hash != zobrint_hash) and (TRANSPOS_TABLE[zobrint_hash & ZOB_MASK].zobrint_hash)
// and not (TRANSPOS_TABLE[zobrint_hash & ZOB_MASK].move == NULLMOVE))
// std::cout << "[HASH COLLISION] ";
for (i = moves.size() - 1; i >= 0; i--) {
PiecePlusCatling ppc = ppcs[i];
unmake_move(moves[i], ppc.hit_piece, ppc.c_rights_w, ppc.c_rights_b);
}
std::cout << std::endl;
}
bool square_in_check(num COLOR, num i)
{
num OPPONENT_COLOR = COLOR == WHITE ? BLACK : WHITE;
num UP = COLOR == WHITE ? -10 : 10;
// enemy pawns
if (board[i+UP+1] == PAWN + OPPONENT_COLOR)
return true;
if (board[i+UP-1] == PAWN + OPPONENT_COLOR)
return true;
for (num piece = KNIGHT; piece <= KING; piece <<= 1) // enemy pieces incl king
for (num l = 0; PIECEDIRS[piece][l] != 0; ++l)
for (num sq = i;;)
{
sq += PIECEDIRS[piece][l];
if (board[sq] == OOB) // axis goes off board
break;
if (board[sq] == OPPONENT_COLOR + piece) // opponent piece of right type on axis
return true;
if (board[sq]) // irrelevant piece along axis
break;
if (not PIECESLIDE[piece])
break;
}
return false;
}
bool king_in_check(num COLOR) // 36% of time spent in this function
{
// keeping track of king position enables a HUGE speedup!
num KINGPOS = COLOR == WHITE ? KINGPOS_WHITE : KINGPOS_BLACK;
return square_in_check(COLOR, KINGPOS);
}
// TODO TODO TODO replace with vector push_back OR AT LEAST fewer indirect mallocs
inline Move* move_store(Move* mvsend, num a, num b, num c)
{
mvsend->from = a; mvsend->to = b; mvsend->prom = c;
return ++mvsend;
}
inline Move* move_store_maybe_promote(Move* mvsend, bool is_promrank, num a, num b)
{
if (is_promrank) {
mvsend = move_store(mvsend, a, b, 'q'); /* best */
mvsend = move_store(mvsend, a, b, 'n'); /* also likely */
mvsend = move_store(mvsend, a, b, 'r');
return move_store(mvsend, a, b, 'b');
} else
return move_store(mvsend, a, b, '\0');
}
inline bool is_pseudo_legal(num COLOR, Move mv)
{
if (!(board[mv.from] & COLOR)) // moving empty/enemy piece (impossible)
return false;
if (board[mv.to] & COLOR) // self-capture (impossible)
return false;
num NCOLOR = COLOR == WHITE ? BLACK : WHITE;
num bijpiece = board[mv.from] & COLORBLIND;
if (mv.prom) {
if (mv.prom == 'c') { // castling
if (bijpiece != KING)
return false;
// castling (normal king movement is covered by PIECEDIRS)
CASTLINGRIGHTS castlingrights = COLOR == WHITE ? CASTLINGWHITE : CASTLINGBLACK;
num CASTLERANK = COLOR == WHITE ? 90 : 20;
if (mv.from + 2 == mv.to) // short castle O-O
return (castlingrights.rc && !board[CASTLERANK+6] && !board[CASTLERANK+7] && board[CASTLERANK+8] == COLOR + ROOK);
if (mv.from - 2 == mv.to) // long castle O-O-O
return (castlingrights.lc && !board[CASTLERANK+2] && !board[CASTLERANK+3] && !board[CASTLERANK+4] && board[CASTLERANK+1] == COLOR + ROOK);
return false; // this line should be unreachable
}
else {
if (bijpiece != PAWN)
return false;
if (mv.prom == 'e') { // en passant -- capture opponent pawn (this check replaces regular diagonal capture)
num ADD = COLOR == WHITE ? -10 : 10; // "up"
return (LASTMOVE.from-ADD-ADD == LASTMOVE.to) && (board[LASTMOVE.to] == NCOLOR + PAWN) &&
(LASTMOVE.to == mv.from-1 || LASTMOVE.to == mv.from+1) && (mv.to == LASTMOVE.from-ADD);
}
// intentional fall-through, we still have to check if the requested forward/diagonal movement is possible for promotions
}
}
if (bijpiece == PAWN) {
num ADD = COLOR == WHITE ? -10 : 10; // "up"
num PROMRANK = COLOR == WHITE ? 30 : 80;
if ((PROMRANK < mv.from && mv.from < PROMRANK + 10) ^ (mv.prom != '\0')) // (on 7th rank) iff (has promotion piece set)
return false; // importantly, mv.prom == 'e' was handled earlier
if (mv.from + ADD == mv.to) // 1 square forward (needs empty square)
return !board[mv.to];
if ((mv.from + ADD + 1 == mv.to) || (mv.from + ADD - 1 == mv.to)) // if not en passant: can only move diagonally if occupied
return !!board[mv.to];
num STARTRANK = COLOR == WHITE ? 80 : 30;
if (mv.from + ADD + ADD == mv.to) // 2 squares forward (needs empty squares + startrank)
return !board[mv.from + ADD] && !board[mv.from + ADD + ADD] && STARTRANK < mv.from && mv.from < STARTRANK + 10;
} else
for (num l = 0; PIECEDIRS[bijpiece][l] != 0; ++l) {
for (num sq = mv.from;;)
{
sq += PIECEDIRS[bijpiece][l];
if (sq == mv.to) // move is at least theoretically possible ("pseudo-legal")
return true;
if (board[sq])
break;
if (not PIECESLIDE[bijpiece])
break;
}
}
return false;
}
// generates all captures if captures=true, generates all quiet moves if captures=false
inline GenMoves gen_moves_maybe_legal(num COLOR, bool do_captures, bool do_quiets) // 30% of time spent here
{
// TODO: move these to global variables? and just overwrite?
// TODO: generally avoid double indirection here
Move* captures = do_captures ? ((Move*)malloc(125 * sizeof(Move))) : NULL; // malloc appears faster than global array
Move* captures_end = captures;
Move* quiets = do_quiets ? ((Move*)malloc(125 * sizeof(Move))) : NULL; // weird 10% slowdown only if BOTH calloc and >=126
Move* quiets_end = quiets;
num NCOLOR = WHITE; // opponent color
num ADD = 10; // "up"
num STARTRANK = 30;
num PROMRANK = 80;
num EPRANK = 60;
num CASTLERANK = 20;
CASTLINGRIGHTS castlingrights = CASTLINGBLACK;
if (COLOR == WHITE) { // maybe make 2 structs with these constants?
NCOLOR = BLACK;
ADD = -10;
STARTRANK = 80;
PROMRANK = 30;
EPRANK = 50;
CASTLERANK = 90;
castlingrights = CASTLINGWHITE;
}
if (do_quiets) { // castling moves
// TODO: FIX THIS!!! TRIES TO CASTLE WHEN BISHOP ON C8
// INPUT: position startpos moves d2d4 b8c6 e2e4 d7d5 e4d5 d8d5 g1f3 d5e4 f1e2
// MOVE B f5 (c8f5 ) | KH 4 | EVAL 0.45 | VAR ... c4 (c2c4 ) Kxc8 (e8c8c)
if (castlingrights.rc and board[CASTLERANK+5] == COLOR + KING and board[CASTLERANK+8] == COLOR + ROOK) // short castle O-O
if (not board[CASTLERANK+6] and not board[CASTLERANK+7])
if (not square_in_check(COLOR, CASTLERANK+5) and not square_in_check(COLOR, CASTLERANK+6) and not square_in_check(COLOR, CASTLERANK+7))
quiets_end = move_store(quiets_end, CASTLERANK+5, CASTLERANK+7, 'c');
if (castlingrights.lc and board[CASTLERANK+5] == COLOR + KING and board[CASTLERANK+1] == COLOR + ROOK) { // long castle O-O-O
if (not board[CASTLERANK+2] and not board[CASTLERANK+3] and not board[CASTLERANK+4])
if (not square_in_check(COLOR, CASTLERANK+3) and not square_in_check(COLOR, CASTLERANK+4) and not square_in_check(COLOR, CASTLERANK+5))
quiets_end = move_store(quiets_end, CASTLERANK+5, CASTLERANK+3, 'c');
}
}
// I made the mistake of using mailbox addressing here in 2015, so looping over all 64 squares (most empty) will always be slow
// I ended up switching this engine to 12x10 boards for efficiency, but for bitboards I would probably do a new engine
for (num i = 0; i < 120; ++i) { // i = 21; i < 99 is SLOWER for some reason! :(
if (not (board[i] & COLOR)) // also covers board[i] == OOB
continue;
if (board[i] & PAWN) { // pawn moves
if (do_captures) {
// diagonal captures
if (board[i+ADD+1] & NCOLOR)
captures_end = move_store_maybe_promote(captures_end, PROMRANK < i and i < PROMRANK + 10, i, i+ADD+1);
if (board[i+ADD-1] & NCOLOR)
captures_end = move_store_maybe_promote(captures_end, PROMRANK < i and i < PROMRANK + 10, i, i+ADD-1);
if (EPRANK < i and i < EPRANK + 10) { // en passant capture
if (LASTMOVE.from == i+ADD+ADD-1 and LASTMOVE.to == i-1 and board[i-1] & PAWN)
captures_end = move_store(captures_end, i, i+ADD-1, 'e');
if (LASTMOVE.from == i+ADD+ADD+1 and LASTMOVE.to == i+1 and board[i+1] & PAWN)
captures_end = move_store(captures_end, i, i+ADD+1, 'e');
}
}
if (do_quiets)
if (not board[i+ADD]) { // 1 square forward
quiets_end = move_store_maybe_promote(quiets_end, PROMRANK < i and i < PROMRANK + 10, i, i+ADD);
if (STARTRANK < i and i < STARTRANK + 10 and not board[i+ADD+ADD]) // 2 squares forward
quiets_end = move_store(quiets_end, i, i+ADD+ADD, '\0');
}
} else { // piece moves
num bijpiece = board[i] & COLORBLIND;
for (num l = 0; PIECEDIRS[bijpiece][l] != 0; ++l) {
for (num sq = i;;)
{
sq += PIECEDIRS[bijpiece][l];
if (board[sq] == OOB) // out of bounds
break;
if (board[sq] & COLOR) // move to square with own piece (illegal)
break;
if (board[sq] & NCOLOR) { // move to square with enemy piece (capture)
if (do_captures)
captures_end = move_store(captures_end, i, sq, '\0');
break;
}
if (do_quiets) // move to empty square // board[sq] HAS TO BE 0 at this point
quiets_end = move_store(quiets_end, i, sq, '\0');
if (not PIECESLIDE[bijpiece])
break;
}
}
}
}
return {captures, captures_end, quiets, quiets_end};
}
inline void free_GenMoves(GenMoves gl)
{
free(gl.captures);
free(gl.quiets);
}
int move_order_mvv_lva(const void* a, const void* b)
{
Move mv_a = *((Move *)a);
Move mv_b = *((Move *)b);
return 1000 * PIECEVALS[board[mv_b.to] & COLORBLIND] - PIECEVALS[board[mv_b.from] & COLORBLIND]
- (1000 * PIECEVALS[board[mv_a.to] & COLORBLIND] - PIECEVALS[board[mv_a.from] & COLORBLIND]);
}
inline void store_hash_entry(Move move, num value, int8_t tte_flag, num depth, num depth_left, bool is_quies)
{
// https://crypto.stackexchange.com/questions/27370/formula-for-the-number-of-expected-collisions
// TODO: calculate expected number of collisions
// TODO: why nullmove??
if (!(move == NULLMOVE) && !is_quies)
TRANSPOS_TABLE[zobrint_hash & ZOB_MASK] = {zobrint_hash, move, value, tte_flag, depth_left};
}
// non-negamax quiescent alpha-beta minimax search
// https://www.chessprogramming.org/Quiescence_Search
ValuePlusMove negamax(num COLOR, num alpha, num beta, num adaptive, bool is_quies, bool can_null_move, num depth, bool lines, bool lines_accurate) // 22% of time spent here
{
NODES_NORMAL += !is_quies;
NODES_QUIES += is_quies;
// treat all repeated positions as an instant draw to avoid repetitions when winning and encourage when losing
if ((depth >= 1) and board_positions_seen.count(zobrint_hash))
return {0, {0}};
if (is_quies) {
// TODO: limit quiescence search depth somehow
if (NO_QUIES)
return {COLOR == WHITE ? board_eval : -board_eval, {0}};
}
else if (depth >= 1) {
TTEntry tte = TRANSPOS_TABLE[zobrint_hash & ZOB_MASK];
if (tte.zobrint_hash == zobrint_hash)
if (tte.depth_left >= adaptive) {
if (tte.tte_flag == tte_exact)
return {tte.value, tte.move};
if (tte.tte_flag == tte_alpha and tte.value <= alpha)
return {alpha, tte.move};
if (tte.tte_flag == tte_beta and tte.value >= beta)
return {beta, tte.move};
}
// Main search is done, do quiescence search at the leaf nodes
if (adaptive <= 0)
return negamax(COLOR, alpha, beta, adaptive, true, false, depth, lines, lines_accurate);
}
ValuePlusMove best = {-inf-1, {0}};
if (is_quies) {
// "standing pat": to compensate for not considering non-capture moves, at least one move should be
// better than doing no move ("null move") -> avoids senseless captures, but vulnerable to zugzwang
best = {COLOR == WHITE ? board_eval : -board_eval, {0}};