File Coverage

blib/lib/Chess4p/Board.pm
Criterion Covered Total %
statement 686 721 95.1
branch 339 380 89.2
condition 83 109 76.1
subroutine 68 70 97.1
pod 17 19 89.4
total 1193 1299 91.8


line stmt bran cond sub pod time code
1             # -*- mode: cperl -*-
2             package Chess4p::Board;
3              
4 4     4   67 use v5.36;
  4         14  
5              
6 4     4   21 use Carp;
  4         9  
  4         345  
7 4     4   26 use List::Util qw( max );
  4         8  
  4         324  
8              
9 4     4   25 use Scalar::Util qw(reftype refaddr);
  4         8  
  4         267  
10              
11 4     4   23 use Chess4p;
  4         7  
  4         442  
12              
13 4         2450 use Chess4p::Common qw(FILE_A FILE_B FILE_C FILE_D
14             FILE_E FILE_F FILE_G FILE_H
15             RANK_1 RANK_2 RANK_3 RANK_4
16             RANK_5 RANK_6 RANK_7 RANK_8
17             A1 B1 C1 D1 E1 F1 G1 H1
18             A2 B2 C2 D2 E2 F2 G2 H2
19             A3 B3 C3 D3 E3 F3 G3 H3
20             A4 B4 C4 D4 E4 F4 G4 H4
21             A5 B5 C5 D5 E5 F5 G5 H5
22             A6 B6 C6 D6 E6 F6 G6 H6
23             A7 B7 C7 D7 E7 F7 G7 H7
24             A8 B8 C8 D8 E8 F8 G8 H8
25             EMPTY WP WN WB WR WQ WK
26             BP BN BB BR BQ BK
27             %square_names
28             %square_numbers
29 4     4   22 );
  4         7  
30              
31 4     4   32 use overload ('""', => 'ascii');
  4         9  
  4         25  
32              
33              
34             my $bb_empty = 0;
35              
36             my $bb_all;
37             my $bb_file_a;
38             my $bb_file_b;
39             my $bb_file_c;
40             my $bb_file_d;
41             my $bb_file_e;
42             my $bb_file_f;
43             my $bb_file_g;
44             my $bb_file_h;
45             {
46 4     4   480 no warnings "portable";
  4         6  
  4         52793  
47             $bb_all = 0xffff_ffff_ffff_ffff;
48             $bb_file_a = 0x0101_0101_0101_0101 << FILE_A;
49             $bb_file_b = 0x0101_0101_0101_0101 << FILE_B;
50             $bb_file_c = 0x0101_0101_0101_0101 << FILE_C;
51             $bb_file_d = 0x0101_0101_0101_0101 << FILE_D;
52             $bb_file_e = 0x0101_0101_0101_0101 << FILE_E;
53             $bb_file_f = 0x0101_0101_0101_0101 << FILE_F;
54             $bb_file_g = 0x0101_0101_0101_0101 << FILE_G;
55             $bb_file_h = 0x0101_0101_0101_0101 << FILE_H;
56             }
57             my @bb_files = ($bb_file_a, $bb_file_b, $bb_file_c, $bb_file_d, $bb_file_e, $bb_file_f, $bb_file_g, $bb_file_h);
58              
59             my $bb_rank_1 = 0xff << (8 * RANK_1);
60             my $bb_rank_2 = 0xff << (8 * RANK_2);
61             my $bb_rank_3 = 0xff << (8 * RANK_3);
62             my $bb_rank_4 = 0xff << (8 * RANK_4);
63             my $bb_rank_5 = 0xff << (8 * RANK_5);
64             my $bb_rank_6 = 0xff << (8 * RANK_6);
65             my $bb_rank_7 = 0xff << (8 * RANK_7);
66             my $bb_rank_8 = 0xff << (8 * RANK_8);
67             my @bb_ranks = ($bb_rank_1, $bb_rank_2, $bb_rank_3, $bb_rank_4, $bb_rank_5, $bb_rank_6, $bb_rank_7, $bb_rank_8);
68              
69             # Each has a single 0-bit at position 0..63, corresponding to a1-h1, a2-h2, ..., a8-h8
70             my @bb_squares = map { ~(1 << $_) } 0..63;
71              
72             my $bb_e1 = ~$bb_squares[E1];
73             my $bb_e8 = ~$bb_squares[E8];
74              
75              
76             sub _square_mirror { ## no critic (Subroutines::RequireArgUnpacking)
77             # mirrors the square vertically
78 260     260   465 $_[0] ^ 0x38;
79             }
80              
81             my @squares_180 = map { _square_mirror($_) } 0..63;
82              
83             my @bb_knight_attacks;
84             for my $sqr (A1 .. H8) {
85             # tabulate knight attacks from each square
86             $bb_knight_attacks[$sqr] = _step_attacks($sqr, [17, 15, 10, 6, -17, -15, -10, -6]);
87             }
88              
89             my @bb_king_attacks;
90             for my $sqr (A1 .. H8) {
91             # tabulate king attacks from each square
92             $bb_king_attacks[$sqr] = _step_attacks($sqr, [9, 8, 7, 1, -9, -8, -7, -1]);
93             }
94              
95             my @bb_pawn_attacks_w;
96             my @bb_pawn_attacks_b;
97             for my $sqr (A1 .. H8) {
98             # tabulate pawn attacks from each square for each side
99             # note that edge squares need to be tabulated too, even
100             # when there are no attacks from there, to avoid accessing
101             # undefined values in e.g. castling move gen.
102             $bb_pawn_attacks_w[$sqr] = _step_attacks($sqr, [7, 9]);
103             $bb_pawn_attacks_b[$sqr] = _step_attacks($sqr, [-7, -9]);
104             }
105              
106              
107             ### Free functions
108              
109             sub _make_bb { ## no critic (Subroutines::RequireArgUnpacking)
110             # make a bitboard from a list of squares
111             # useful for testing
112 49     49   5400 my $result = 0;
113 49         126 for (@_) {
114 154         285 $result |= ~$bb_squares[$_];
115             }
116 49         203 $result;
117             }
118              
119             sub _print_bb { ## no critic (Subroutines::RequireArgUnpacking)
120             # bitboard as a string
121             # useful for debugging
122 26     26   129 my $bb = $_[0];
123 26         54 my $result;
124 26         71 for my $sqr (@squares_180) {
125 1664         2305 my $mask = ~$bb_squares[$sqr];
126 1664 100       2836 if ($bb & $mask) {
127 187         292 $result .= "1";
128             }
129             else {
130 1477         1929 $result .= ".";
131             }
132 1664 100       2618 unless ($mask & $bb_file_h) {
133 1456         2500 $result .= " ";
134             }
135             else {
136 208 100       592 $result .= "\n" unless $sqr == H1;
137             }
138             }
139 26         224 $result;
140             }
141              
142             sub _carry_rippler_iter {
143             # iterate over subsets of a bitboard
144 833     833   2068 my ($mask) = @_;
145 833         1239 my $subset = $bb_empty;
146 833         1157 my $done = 0;
147              
148             return sub {
149 44869 100   44869   90049 return undef if $done;
150 44036         60329 my $out = $subset;
151 44036         64093 $subset = ($subset - $mask) & $mask; # carry-rippler step
152 44036 100       85347 $done = 1 if $subset == 0; # stop after we've generated all subsets
153 44036         95368 return $out;
154 833         5911 };
155             }
156              
157             sub _step_attacks { ## no critic (Subroutines::RequireArgUnpacking)
158 1025     1025   1360 my $sqr = $_[0];
159 1025         1353 my $deltas = $_[1]; # ref
160 1025         1382 my $occupied = $_[2];
161 1025         1800 _sliding_attacks($sqr, $deltas, $bb_all);
162             }
163              
164             sub _sliding_attacks { ## no critic (Subroutines::RequireArgUnpacking)
165 45889     45889   63633 my $sqr = $_[0];
166 45889         62872 my $deltas = $_[1]; # ref
167 45889         64292 my $occupied = $_[2];
168 45889         65850 my $result = $bb_empty;
169              
170 45889         78046 for my $delta (@$deltas) {
171 137346         185654 my $s = $sqr;
172 137346         181860 while (1) {
173 255234         337973 $s += $delta;
174 255234 100 100     728759 last unless ($s >= 0 && $s < 64);
175 222858 100       380663 last if (_square_distance($s, $s - $delta) > 2);
176 199458         316610 $result = $result | ~$bb_squares[$s];
177 199458 100       412222 last if $occupied & ~$bb_squares[$s];
178             }
179             }
180              
181 45889         198602 $result;
182             }
183              
184             sub _square_file { ## no critic (Subroutines::RequireArgUnpacking)
185 446800     446800   581663 my $sqr = $_[0];
186 446800         777134 $sqr & 7;
187             }
188              
189             sub _square_rank { ## no critic (Subroutines::RequireArgUnpacking)
190 447001     447001   582823 my $sqr = $_[0];
191 447001         1027639 $sqr >> 3
192             }
193              
194             sub _square_distance { ## no critic (Subroutines::RequireArgUnpacking)
195             # number of king steps from a to b
196 222979     222979   294118 my $sqr_a = $_[0];
197 222979         286930 my $sqr_b = $_[1];
198 222979         343697 max(abs(_square_file($sqr_a) - _square_file($sqr_b)), abs(_square_rank($sqr_a) - _square_rank($sqr_b)))
199             }
200              
201             sub _shift_down { ## no critic (Subroutines::RequireArgUnpacking)
202 3     3   9 my $bb = $_[0];
203 3         11 $bb >> 8;
204             }
205              
206             sub _shift_up { ## no critic (Subroutines::RequireArgUnpacking)
207 3     3   9 my $bb = $_[0];
208 3         11 ($bb << 8) & $bb_all;
209             }
210              
211             sub _edges { ## no critic (Subroutines::RequireArgUnpacking)
212 836     836   1230 my $sqr = $_[0];
213 836         2028 return ((($bb_rank_1 | $bb_rank_8) & ~$bb_ranks[_square_rank($sqr) ]) |
214             (($bb_file_a | $bb_file_h) & ~$bb_files[_square_file($sqr)]));
215             }
216              
217             sub _attack_table {
218             # for pre-computed attack tables
219 13     13   934 my ($deltas) = @_;
220 13         30 my @mask_table;
221             my @attack_table;
222              
223 13         47 for my $square (A1..H8) {
224 832         1464 my %attacks;
225 832         1753 my $mask = _sliding_attacks($square, $deltas, 0) & ~_edges($square);
226 832         2256 my $next = _carry_rippler_iter($mask);
227 832         1831 while (defined(my $subset = $next->())) {
228 44032         74549 $attacks{$subset} = _sliding_attacks($square, $deltas, $subset);
229             }
230 832         2642 push @attack_table, \%attacks;
231 832         6342 push @mask_table, $mask;
232             }
233              
234 13         89 return (\@mask_table, \@attack_table);
235             }
236              
237              
238             my ($BB_DIAG_MASKS, $BB_DIAG_ATTACKS) = _attack_table([-9, -7, 7, 9]);
239             my ($BB_FILE_MASKS, $BB_FILE_ATTACKS) = _attack_table([-8, 8]);
240             my ($BB_RANK_MASKS, $BB_RANK_ATTACKS) = _attack_table([-1, 1]);
241              
242              
243             sub _rays {
244 5     5   13 my @rays;
245 5         84 for (my $a = 0; $a < @bb_squares; $a++) {
246 320         580 my $bb_a = ~$bb_squares[$a];
247 320         426 my @rays_row;
248 320         772 for (my $b = 0; $b < @bb_squares; $b++) {
249 20480         29185 my $bb_b = ~$bb_squares[$b];
250 20480 100       52232 if ($$BB_DIAG_ATTACKS[$a]->{0} & $bb_b) {
    100          
    100          
251 2800         9198 push @rays_row, $$BB_DIAG_ATTACKS[$a]->{0} & $$BB_DIAG_ATTACKS[$b]->{0} | $bb_a | $bb_b;
252             }
253             elsif ($$BB_RANK_ATTACKS[$a]->{0} & $bb_b) {
254 2240         6483 push @rays_row, $$BB_RANK_ATTACKS[$a]->{0} | $bb_a;
255             }
256             elsif ($$BB_FILE_ATTACKS[$a]->{0} & $bb_b) {
257 2240         5964 push @rays_row, $$BB_FILE_ATTACKS[$a]->{0} | $bb_a;
258             }
259             else {
260 13200         30339 push @rays_row, $bb_empty;
261             }
262             }
263 320         1108 push @rays, \@rays_row;
264             }
265 5         49 \@rays;
266             }
267              
268             my $BB_RAYS = _rays();
269              
270             sub _ray {
271 56     56   117 my ($a, $b) = @_;
272 56         149 my $aref = $$BB_RAYS[$a];
273 56         183 $$aref[$b];
274             }
275              
276             sub _between {
277 162     162   8163 my ($a, $b) = @_;
278 162         344 my $aref = $$BB_RAYS[$a];
279 162         440 my $bb = $$aref[$b] & (($bb_all << $a) ^ ($bb_all << $b));
280 162         412 $bb & ($bb - 1);
281             }
282              
283             sub _msb {
284 304     304   556 my ($x) = @_;
285 304 100       714 return -1 if $x == 0;
286 277         364 my $pos = 0;
287             ## no critic (ValuesAndExpressions::ProhibitCommaSeparatedStatements)
288 277 100       587 $pos += 32, $x >>= 32 if $x >> 32;
289 277 100       550 $pos += 16, $x >>= 16 if $x >> 16;
290 277 100       540 $pos += 8, $x >>= 8 if $x >> 8;
291 277 100       655 $pos += 4, $x >>= 4 if $x >> 4;
292 277 100       569 $pos += 2, $x >>= 2 if $x >> 2;
293 277 100       569 $pos += 1 if $x >> 1;
294 277         670 return $pos;
295             }
296              
297             sub _lsb {
298 3     3   7 my ($x) = @_;
299 3         9 _msb($x & -$x);
300             }
301              
302              
303             # FEN characters
304             my @fen_chars = qw(. P N B R Q K p n b r q k);
305              
306             # FEN char -> Piece
307             my %fen_chars_to_piece_code = (
308             r => BR, n => BN, b => BB, q => BQ, k => BK, p => BP,
309             P => WP, R => WR, N => WN, B => WB, Q => WQ, K => WK,
310             );
311              
312              
313             ### Private instance methods
314              
315             sub _clean_castling_rights {
316             # returns bitboard with the corner squares set if the
317             # rook on that square can potentially castle.
318             # the given castling rights may be reduced, depending on the position
319 60     60   136 my $pos = shift;
320 60         195 my $white_castling = $pos->{castling_rights} & $pos->{bb}{WR()};
321 60         192 my $black_castling = $pos->{castling_rights} & $pos->{bb}{BR()};
322              
323 60 100       244 unless ($pos->{bb}{WK()} & ~$bb_squares[E1()]) {
324 14         61 $white_castling = 0;
325             }
326 60 100       222 unless ($pos->{bb}{BK()} & ~$bb_squares[E8()]) {
327 14         44 $black_castling = 0;
328             }
329              
330 60         238 return $white_castling | $black_castling;
331             }
332              
333              
334             sub _valid_ep_square {
335             # return e.p. square if valid, else undef
336 12     12   27 my $pos = shift;
337 12 100       61 return undef unless $pos->{ep_square};
338              
339 2         12 my $ep_rank;
340             my $pawn_mask; # a pawn - that made the last move - must be at this square
341 2         0 my $seventh_rank_mask; # the square that must have been left empty by the last move
342 2 100       11 if ($pos->{to_move} eq 'w') {
343 1         5 $ep_rank = RANK_6;
344 1         7 $pawn_mask = _shift_down($bb_squares[$pos->{ep_square}]);
345 1         6 $seventh_rank_mask = _shift_up($bb_squares[$pos->{ep_square}]);
346             }
347             else {
348 1         5 $ep_rank = RANK_3;
349 1         6 $pawn_mask = _shift_up($bb_squares[$pos->{ep_square}]);
350 1         6 $seventh_rank_mask = _shift_down($bb_squares[$pos->{ep_square}]);
351             }
352             # e.p. square must be on 3rd / 6th rank
353 2 50       10 if (_square_rank($pos->{ep_square}) != $ep_rank) {
354 0         0 return undef;
355             }
356             # require a pawn that moved past the e.p square
357 2 50 66     16 if ($pos->{to_move} eq 'w' && !($pos->{bb}{BP()} & $pawn_mask)) {
358 0         0 return undef;
359             }
360 2 50 66     16 if ($pos->{to_move} eq 'b' && !($pos->{bb}{WP()} & $pawn_mask)) {
361 0         0 return undef;
362             }
363             # e.p. square must be empty
364 2 50       13 if ($pos->{bb}{all} & $bb_squares[$pos->{ep_square}]) {
365 2         7 return undef;
366             }
367             # square that was just emptied by the last move
368 0 0       0 if ($pos->{bb}{all} & $seventh_rank_mask) {
369 0         0 return undef;
370             }
371             # OK
372 0         0 return $pos->{ep_square};
373             }
374              
375             sub _build_bitboards_from_table {
376             # bb goes from a1-h1, a2-h2, ..., a8-h8
377 39     39   110 my $pos = shift;
378             # all BBs = 0
379 39         136 for my $p (WP .. BK) {
380 468         1265 $pos->{bb}{$p} = 0;
381             }
382 39         126 for my $sq (0..63) {
383 2496         3639 my $pc = $pos->{table}[$sq];
384 2496 100       4515 next if $pc == EMPTY;
385 877         1625 $pos->{bb}{$pc} |= 1 << ($sq);
386             }
387 39         126 for my $p (WP .. WK) {
388 234         544 $pos->{bb}{White} |= $pos->{bb}{$p};
389             }
390 39         112 for my $p (BP .. BK) {
391 234         552 $pos->{bb}{Black} |= $pos->{bb}{$p};
392             }
393             $pos->{bb}{all} = $pos->{bb}{White}
394 39         181 | $pos->{bb}{Black};
395             }
396              
397             # use for looping over set bits = squares
398             sub _pop_lsb_index {
399 3212     3212   5415 my (undef, $bbref) = @_;
400              
401 3212         4635 my $bb = $$bbref;
402 3212 50       5884 return -1 if $bb == 0;
403              
404             # isolate least significant 1 bit
405 3212         5222 my $lsb = $bb & (-$bb);
406              
407             # remove it
408 3212         5172 $$bbref = $bb ^ $lsb;
409              
410             # position of the lsb
411             # simplest portable approach: count trailing zeros with a loop
412 3212         4206 my $i = 0;
413 3212         6082 while (($lsb >> $i) != 1) {
414 97592         162491 $i++;
415             }
416 3212         6107 return $i;
417             }
418              
419             sub _bb_count_1s {
420 89     89   206 my ($pos, $pcs) = @_;
421              
422 89         233 my $bbref = $pos->{bb}{$pcs};
423 89         134 my $bb = $bbref;
424 89 100       199 return 0 if $bb == 0;
425              
426             # make a copy
427 86         134 my $lsb = $bb;
428              
429             # count 1's
430 86         140 my $count = 0;
431 86 100       198 if ($lsb % 2 != 0) {
432 15         31 $count++;
433             }
434 86         213 while (($lsb = ($lsb >> 1)) > 0) {
435 2980 100       6950 if ($lsb % 2 != 0) {
436 532         997 $count++;
437             }
438             }
439 86         321 return $count;
440             }
441              
442             sub _occupied {
443 3     3   16 my ($pos, $side) = @_;
444 3 50       28 return $pos->{to_move} eq 'w' ? $pos->{bb}{White} : $pos->{bb}{Black};
445             }
446              
447             sub _opponent { ## no critic (Subroutines::RequireArgUnpacking)
448 7 100   7   46 return $_[0]->{to_move} eq 'w' ? 'b' : 'w';
449             }
450              
451             # a debugging aid, use from unit tests
452             sub _check_consistency {
453 37     37   3302 my $pos = shift;
454              
455             # keys = squares, values = pieces
456 37         122 my %h_table;
457             my %h_bb;
458              
459 37         142 for my $pcs (WP .. BK) {
460 444         718 my $i = 0;
461 444         1101 while ($i <= 63) {
462 28416 100       54658 if ($pcs == $pos->{table}[$i]) {
463 821         2292 $h_table{$i} = $pcs;
464             }
465 28416         50473 $i++;
466             }
467 444         1173 my $work_bits = $pos->{bb}{$pcs};
468             #_print_bb($work_bits);
469 444         998 while ($work_bits) {
470 821         2072 my $sq = $pos->_pop_lsb_index(\$work_bits);
471 821         3336 $h_bb{$sq} = $pcs;
472             }
473             }
474              
475 37         122 my $bb_size = keys %h_bb;
476 37         78 my $tb_size = keys %h_table;
477            
478 37 50       185 if ($bb_size != $tb_size) {
479 0         0 warn "bitboard has $bb_size elements, table has $tb_size elements";
480 0         0 warn "table: " . join (',', sort keys %h_table);
481 0         0 warn "bitboard:" . join (',', sort keys %h_bb);
482 0         0 return 0;
483             }
484            
485 37         438 for my $key (keys %h_table) {
486 821 50       2274 if (not exists $h_bb{$key}) {
    50          
487 0         0 warn "Square $key found in table but not in bitboard";
488 0         0 return 0;
489             } elsif ($h_table{$key} != $h_bb{$key}) {
490 0         0 warn "Square $key points to $h_table{$key} in table but to $h_bb{$key} in bitboard";
491 0         0 return 0;
492             }
493             }
494              
495 37         715 return 1;
496             }
497              
498             sub _get_attackers {
499             # get the attackers from side on square
500 222     222   2378 my ($pos, $side, $square, $occupied) = @_;
501              
502 222 100       690 my $attackers = $bb_king_attacks[$square] & ($side eq 'w' ? $pos->{bb}{WK()} : $pos->{bb}{BK()});
503 222 100       629 $attackers |= $bb_knight_attacks[$square] & ($side eq 'w' ? $pos->{bb}{WN()} : $pos->{bb}{BN()});
504 222 100       534 $attackers |= $bb_pawn_attacks_b[$square] & $pos->{bb}{WP()} if $side eq 'w';
505 222 100       583 $attackers |= $bb_pawn_attacks_w[$square] & $pos->{bb}{BP()} if $side eq 'b';
506              
507 222   66     833 $occupied //= $pos->{bb}{all};
508            
509 222         432 my $rank_pieces = $$BB_RANK_MASKS[$square] & $occupied;
510 222         414 my $file_pieces = $$BB_FILE_MASKS[$square] & $occupied;
511 222         392 my $diag_pieces = $$BB_DIAG_MASKS[$square] & $occupied;
512              
513 222 100       580 my $queens_and_rooks = ($side eq 'w' ? $pos->{bb}{WR()} | $pos->{bb}{WQ()} : $pos->{bb}{BR()} | $pos->{bb}{BQ()});
514 222 100       582 my $queens_and_bishops = ($side eq 'w' ? $pos->{bb}{WB()} | $pos->{bb}{WQ()} : $pos->{bb}{BB()} | $pos->{bb}{BQ()});
515            
516 222         790 $attackers |= $$BB_RANK_ATTACKS[$square]->{$rank_pieces} & $queens_and_rooks;
517 222         687 $attackers |= $$BB_FILE_ATTACKS[$square]->{$file_pieces} & $queens_and_rooks;
518 222         702 $attackers |= $$BB_DIAG_ATTACKS[$square]->{$diag_pieces} & $queens_and_bishops;
519            
520 222         689 return $attackers;
521             }
522              
523             sub _attacked_for_king {
524             # return true iff any of the squares in bb are attacked by the side NOT to move
525 52     52   138 my ($pos, $bb, $occupied) = @_;
526 52         83 my $_bb = $bb;
527 52         126 while ($_bb) {
528 75         151 my $sq = $pos->_pop_lsb_index(\$_bb);
529 75 100       205 my $side = ($pos->{to_move} eq 'w' ? 'b' : 'w');
530 75 100       179 if ($pos->_get_attackers($side, $sq, $occupied)) {
531 5         36 return 1;
532             }
533             }
534 47         295 return 0;
535             }
536              
537             sub _generate_castling_moves {
538 57     57   117 my $pos = shift;
539 57         127 my $result = shift; # array ref to which moves will be added
540 57         106 my $from_bb_filter = shift;
541 57         101 my $to_bb_filter = shift;
542              
543 57         128 my $side = $pos->{to_move};
544 57 100       162 my $backrank = ($side eq 'w' ? $bb_rank_1 : $bb_rank_8);
545 57 100       221 my $king = ($side eq 'w' ? $pos->{bb}{WK()} : $pos->{bb}{BK()});
546 57         104 $king &= $from_bb_filter;
547              
548 57 100       196 return unless $king;
549            
550 45         128 my $bb_c = $bb_file_c & $backrank;
551 45         87 my $bb_d = $bb_file_d & $backrank;
552 45         120 my $bb_f = $bb_file_f & $backrank;
553 45         157 my $bb_g = $bb_file_g & $backrank;
554              
555 45         191 my $candidates = $pos->_clean_castling_rights() & $backrank;
556 45         136 while ($candidates) {
557 60         139 my $candi = $pos->_pop_lsb_index(\$candidates);
558 60         185 my $rook = ~$bb_squares[$candi];
559 60         146 my $q_side = $rook < $king;
560 60 100       175 my $king_to = ($q_side ? $bb_c : $bb_g);
561 60 100       159 my $rook_to = ($q_side ? $bb_d : $bb_f);
562 60         202 my $king_path = _between(_msb($king), _msb($king_to));
563 60         137 my $rook_path = _between($candi, _msb($rook_to));
564              
565 60 100 100     431 unless ( ($king ^ $rook ^ $pos->{bb}{all}) & ($king_path | $rook_path | $king_to | $rook_to)
      100        
566             || ($pos->_attacked_for_king($king_path | $king, $king ^ $pos->{bb}{all}))
567             || ($pos->_attacked_for_king($king_to, $king ^ $rook ^ $rook_to ^ $pos->{bb}{all})) ) {
568 21         59 my $from = _msb($king);
569 21 100 66     159 if ($from == E1 && $pos->{bb}{WK()} & $bb_e1) {
    50 33        
570 11 100       80 push (@$result, Chess4p::Move->new(E1, G1)) if $candi == H1;
571 11 100       92 push (@$result, Chess4p::Move->new(E1, C1)) if $candi == A1;
572             }
573             elsif ($from == E8 && $pos->{bb}{BK()} & $bb_e8) {
574 10 100       80 push (@$result, Chess4p::Move->new(E8, G8)) if $candi == H8;
575 10 100       45 push (@$result, Chess4p::Move->new(E8, C8)) if $candi == A8;
576             }
577             }
578             }
579             }
580              
581             sub _ep_skewered {
582             # Handle the special case where the king would be in check if the
583             # pawn and its capturer both disappear from the rank.
584             # capturer = from square of the e.p.-capturing pawn.
585             # Vertical skewers of the captured pawn are not possible.
586             # Pins on the capturer are not handled elsewhere.
587 7     7   67 my ($pos, $king, $capturer) = @_;
588 7 50       27 croak "check for skewered e.p. done without e.p. square" unless $pos->{ep_square};
589              
590 7 100       33 my $last_double = $pos->{ep_square} + ($pos->{to_move} eq 'w' ? -8 : 8);
591             my $occupancy = $pos->{bb}{all} & $bb_squares[$last_double] & $bb_squares[$capturer]
592 7         32 | ~$bb_squares[$pos->{ep_square}];
593              
594             # Horizontal attack on the fifth or fourth rank.
595 7         15 my $horizontal_attackers;
596 7 100       23 if ($pos->{to_move} eq 'w') {
597 6         15 $horizontal_attackers = $pos->{bb}{BQ()} | $pos->{bb}{BR()};
598             }
599             else {
600 1         5 $horizontal_attackers = $pos->{bb}{WQ()} | $pos->{bb}{WR()};
601             }
602 7 100       45 return 1 if ($$BB_RANK_ATTACKS[$king]->{$occupancy & $$BB_RANK_MASKS[$king]} & $horizontal_attackers);
603              
604             # Diagonal skewers. These are not actually possible in a real game,
605             # because if the latest double pawn move covers a diagonal attack,
606             # then the other side would have been in check already.
607 5         10 my $diagonal_attackers;
608 5 100       31 if ($pos->{to_move} eq 'w') {
609 4         11 $diagonal_attackers = $pos->{bb}{BQ()} | $pos->{bb}{BB()};
610             }
611             else {
612 1         4 $diagonal_attackers = $pos->{bb}{WQ()} | $pos->{bb}{WB()};
613             }
614 5 50       27 return 1 if ($$BB_DIAG_ATTACKS[$king]->{$occupancy & $$BB_DIAG_MASKS[$king]} & $diagonal_attackers);
615              
616 5         31 return 0;
617             }
618              
619             sub _is_ep_move {
620 835     835   1359 my ($pos, $move) = @_;
621              
622 835 100       2344 return 0 unless $pos->{ep_square};
623            
624             # Check if the given (pseudo-legal) move is an e.p. capture.
625 116         278 my $pawns = $pos->{bb}{WP()} | $pos->{bb}{BP()};
626 116         265 my $diff = abs($move->to() - $move->from());
627              
628             return ($pos->{ep_square} == $move->to() &&
629             ($pawns & ~$bb_squares[$move->from()]) &&
630             ($diff == 7 || $diff == 9) &&
631 116   33     329 !($pos->{bb}{all} & ~$bb_squares[$move->to()]));
632             }
633              
634             sub _pin_mask {
635 8     8   28 my ($pos, $side, $square) = @_;
636            
637 8 100       34 my $king = $side eq 'w' ? $pos->{bb}{WK()} : $pos->{bb}{BK()};
638 8         32 $king = _msb($king);
639 8 50       134 return $bb_all if not $king;
640              
641 8         23 my $square_mask = ~$bb_squares[$square];
642 8 100       34 my $rooks_queens = $side eq 'w' ? $pos->{bb}{BR()} | $pos->{bb}{BQ()} : $pos->{bb}{WR()} | $pos->{bb}{WQ()};
643 8 100       31 my $bishops_queens = $side eq 'w' ? $pos->{bb}{BB()} | $pos->{bb}{BQ()} : $pos->{bb}{WB()} | $pos->{bb}{WQ()};
644              
645 8         41 for my $pair (
646             [$BB_FILE_ATTACKS, $rooks_queens],
647             [$BB_RANK_ATTACKS, $rooks_queens],
648             [$BB_DIAG_ATTACKS, $bishops_queens]
649             ) {
650 22         66 my ($attacks, $sliders) = @$pair;
651 22         49 my $rays = $attacks->[$king]{0};
652 22 100       52 if ($rays & $square_mask) {
653 8 100       55 my $snipers = $rays & $sliders & ($side eq 'w' ? $pos->{bb}{Black} : $pos->{bb}{White});
654 8         25 while ($snipers) {
655 3         7 my $sniper = $pos->_pop_lsb_index(\$snipers);
656 3         7 my $occupied_with_square = $pos->{bb}{all} | $square_mask;
657 3         6 my $mask = _between($sniper, $king) & $occupied_with_square;
658 3 100       12 return _ray($king, $sniper) if $mask == $square_mask;
659             }
660             }
661             }
662            
663 7         45 return $bb_all;
664             }
665              
666             sub _is_safe {
667             # is the move safe?
668             # it's assumed that if the king was in check before the move, then the move evades that check
669 951     951   3648 my ($pos, $king, $blockers, $from, $to) = @_;
670 951 100       2391 if ($from == $king) {
    100          
671             # castling
672 117 100       309 my $opponent = $pos->{to_move} eq 'w' ? 'b' : 'w';
673 117 100       251 return 1 if _square_distance($from, $to) > 1;
674 101 100       266 return 1 if $pos->_get_attackers($opponent, $to) == $bb_empty;
675 13         60 return 0; # $to is attacked by opponent
676             }
677             elsif ($pos->_is_ep_move(Chess4p::Move->new($from, $to))) {
678 7         36 my $result = $pos->_pin_mask($pos->{to_move}, $from) & ~$bb_squares[$to];
679 7   100     46 return $result && !$pos->_ep_skewered($king, $from);
680             }
681             else {
682 827 100       2582 return 1 unless $blockers & ~$bb_squares[$from]; # the piece was not blocking a check -> YES
683 41 100       104 return 1 if _ray($from, $to) & ~$bb_squares[$king]; # the blocker keeps blocking -> YES
684 34         184 return 0; # it's a blocker, and this move would unblock -> NO
685             }
686             }
687              
688             sub _is_zeroing {
689             # Check if given pseudo-legal move is capture or pawn move
690 5     5   15 my ($pos, $move) = @_;
691 5         18 my $touched = ~$bb_squares[$move->from()] ^ ~$bb_squares[$move->to()];
692 5 100       62 my $pawns = $pos->{to_move} eq 'w' ? $pos->{bb}{WP()} : $pos->{bb}{BP()};
693 5 100       40 $touched & $pawns || $touched & $pos->_occupied($pos->_opponent());
694             }
695              
696             sub _debug_state {
697             # use for testing
698 10     10   8736 my $pos = shift;
699             my $out = join '',
700 80 100       235 map { defined $_ ? $_ : 'undef' }
701             $pos->{ep_square},
702             $pos->{to_move},
703             $pos->{castling_rights},
704             $pos->{halfmove_clock},
705             $pos->{fullmove_number},
706             $pos->{bb}{all},
707             $pos->{bb}{White},
708 10         112 $pos->{bb}{Black};
709 10         49 for my $pcs (WP .. BK) {
710 120   50     325 $out .= $pos->{bb}{$pcs} // 'undef';
711             }
712 10         38 for my $sqr (A1 .. H8) {
713 640         1069 $out .= $pos->{table}[$sqr];
714             }
715 10         25 my $sz = 0;
716 10 100       48 $sz = @{$pos->{stack}} if $pos->{stack};
  5         17  
717 10         25 $out .= $sz;
718 10         17 $sz = 0;
719 10 100       38 $sz = @{$pos->{move_stack}} if $pos->{move_stack};
  5         15  
720 10         27 $out .= $sz;
721 10         53 $out;
722             }
723              
724            
725             ### Constructors
726              
727             sub _new {
728 39     39   177 my ($class, $pos) = @_;
729 39         629 return bless $pos, $class;
730             }
731              
732             sub empty {
733 1     1 1 3 my ($class) = @_;
734              
735 1         3 my $pos = {};
736 1         7 $pos->{table} = [ (EMPTY) x 64 ];
737 1         4 $pos->{to_move} = 'w';
738 1         4 $pos->{castling_rights} = $bb_empty;
739 1         2 $pos->{halfmove_clock} = 0;
740 1         3 $pos->{fullmove_number} = 1;
741 1         4 $pos->{stack} = ();
742 1         3 $pos->{move_stack} = ();
743              
744 1         4 _build_bitboards_from_table($pos);
745            
746 1         7 return $class->_new($pos);
747             }
748              
749             sub fromFen {
750 38     38 1 37645 my ($class, $fen) = @_;
751              
752 38         125 my $pos = {};
753              
754 38 100       228 if (not defined $fen) {
755             # default position
756             # table goes from a1-h1, a2-h2, ..., a8-h8
757 1         36 $pos->{table} = [WR,WN,WB,WQ,WK,WB,WN,WR,
758             (WP) x 8, (EMPTY) x 32, (BP) x 8,
759             BR,BN,BB,BQ,BK,BB,BN,BR
760             ];
761 1         5 $pos->{to_move} = 'w';
762 1         6 $pos->{castling_rights} = ~$bb_squares[A1] | ~$bb_squares[H1] | ~$bb_squares[A8] | ~$bb_squares[H8];
763 1         3 $pos->{halfmove_clock} = 0;
764 1         4 $pos->{fullmove_number} = 1;
765             }
766             else {
767 37         270 my @parts = split / /, $fen;
768              
769 37         201 my @rows = split "/", $parts[0];
770              
771 37 100       202 if ($#rows != 7) {
772             # missing rows - pad with empty squares
773 1         6 for (0 .. 6 - $#rows) {
774 2         6 push @rows, "8";
775             }
776             }
777              
778 37         141 $pos->{to_move} = $parts[1];
779              
780 37         151 $pos->{castling_rights} = $bb_empty;
781 37 100       152 if (defined $parts[2]) {
782 36 100       269 $pos->{castling_rights} |= ~$bb_squares[H1] if $parts[2] =~ /K/;
783 36 100       233 $pos->{castling_rights} |= ~$bb_squares[A1] if $parts[2] =~ /Q/;
784 36 100       219 $pos->{castling_rights} |= ~$bb_squares[H8] if $parts[2] =~ /k/;
785 36 100       194 $pos->{castling_rights} |= ~$bb_squares[A8] if $parts[2] =~ /q/;
786             }
787              
788 37 100       134 if (defined $parts[2]) {
789 36 100       120 if ($parts[3] eq '-') {
790 29         100 $pos->{ep_square} = undef;
791             } else {
792 7         40 $pos->{ep_square} = $square_numbers{$parts[3]};
793             }
794             }
795              
796 37         115 $pos->{halfmove_clock} = $parts[4];
797 37         197 $pos->{fullmove_number} = $parts[5];
798            
799 37         152 for my $row (0 .. $#rows) { # 8th row first in, Q before K
800 296         509 my $i = 64 - (($row + 1) * 8);
801 296         769 my @items = split //, $rows[$row];
802 296         552 for my $col (0 .. $#items) {
803 1229 100       2765 if ($items[$col] =~ /(\d+)/) {
804             # empty squares
805 384         1021 for my $j (1 .. $1) {
806 1523         2759 $pos->{table}[$i++] = EMPTY;
807             }
808             } else {
809 845         2530 $pos->{table}[$i++] = $fen_chars_to_piece_code{$items[$col]};
810             }
811             }
812             }
813             }
814              
815 38         139 $pos->{stack} = ();
816 38         119 $pos->{move_stack} = ();
817              
818 38         211 _build_bitboards_from_table($pos);
819            
820 38         211 return $class->_new($pos);
821             }
822              
823              
824             ### Public instance methods
825              
826             sub to_move {
827 6     6 0 19 my $pos = shift;
828 6         37 return $pos->{to_move};
829             }
830              
831             sub kingside_castling_right { ## no critic (Subroutines::RequireArgUnpacking)
832 20     20 1 48 my $pos = $_[0];
833 20 100       88 if ($_[1] eq 'w') {
834 10         68 return $pos->{castling_rights} & ~$bb_squares[H1];
835             } else {
836 10         65 return $pos->{castling_rights} & ~$bb_squares[H8];
837             }
838             }
839              
840             sub queenside_castling_right { ## no critic (Subroutines::RequireArgUnpacking)
841 20     20 1 42 my $pos = $_[0];
842 20 100       60 if ($_[1] eq 'w') {
843 10         54 return $pos->{castling_rights} & ~$bb_squares[A1];
844             } else {
845 10         54 return $pos->{castling_rights} & ~$bb_squares[A8];
846             }
847             }
848              
849             sub ep_square {
850 3     3 1 12 my $pos = shift;
851 3         24 return $pos->{ep_square};
852             }
853              
854             sub fullmove_number {
855 3     3 1 10 my $pos = shift;
856 3         24 return $pos->{fullmove_number};
857             }
858              
859             sub halfmove_clock {
860 3     3 1 10 my $pos = shift;
861 3         23 return $pos->{halfmove_clock};
862             }
863              
864             sub ascii {
865 30     30 1 110 my $pos = shift;
866 30         85 my $result = "";
867 30         150 for (my $row = 7; $row >= 0; $row--) {
868 240         544 for (my $col = 0; $col <= 7; $col++) {
869 1920         2718 my $i = $row * 8 + $col;
870 1920 100       3433 if ($pos->{table}[$i] == EMPTY) {
871 1258         1802 $result .= ".";
872             } else {
873 662         1208 $result .= "$fen_chars[$pos->{table}[$i]]";
874             }
875 1920 100       3635 if ($col < 7) {
876 1680         3326 $result .= " ";
877             }
878             }
879 240 100       476 if ($row > 0) {
880 210         500 $result .= "\n";
881             }
882             }
883 30         266 return $result;
884             }
885              
886             sub fen {
887 6     6 1 20 my $pos = shift;
888 6         16 my $result = "";
889 6         31 my $empties = 0;
890 6         22 for my $sqr (@squares_180) {
891 384         697 my $pcs = $pos->piece_at($sqr);
892 384 100       759 if ($pcs eq '.') {
893 268         367 $empties++;
894             }
895             else {
896 116 100       243 if ($empties) {
897 5         11 $result .= $empties;
898 5         9 $empties = 0;
899             }
900 116         189 $result .= $pcs;
901             }
902 384 100       893 if (~$bb_squares[$sqr] & $bb_file_h) {
903 48 100       99 if ($empties) {
904 34         62 $result .= $empties;
905 34         63 $empties = 0;
906             }
907 48 100       140 $result .= '/' unless $sqr == H1;
908             }
909             }
910              
911 6 100       25 my $castling = ($pos->kingside_castling_right('w') ? 'K' : '')
    100          
    100          
    100          
912             .($pos->queenside_castling_right('w') ? 'Q' : '')
913             .($pos->kingside_castling_right('b') ? 'k' : '')
914             .($pos->queenside_castling_right('b') ? 'q' : '')
915             ;
916            
917 6 100       25 $castling = '-' unless $castling;
918              
919 6   100     35 my $ep = $pos->{ep_square} || '-';
920 6 100       25 $ep = $square_names{$ep} unless $ep eq '-';
921              
922 6         49 $result .= " $pos->{to_move} $castling $ep $pos->{halfmove_clock} $pos->{fullmove_number}";
923            
924 6         145 return $result;
925             }
926              
927             sub piece_at {
928 398     398 1 751 my ($pos, $i) = @_;
929 398         895 return $fen_chars[$pos->{table}[$i]];
930             }
931              
932             sub set_piece_at {
933 7     7 1 26 my ($pos, $sqr, $pcs) = @_;
934 7         24 $pos->remove_piece_at($sqr);
935 7         19 $pos->{table}[$sqr] = $pcs;
936 7         16 my $mask = ~$bb_squares[$sqr];
937 7         20 $pos->{bb}{$pcs} |= $mask;
938              
939             # update other bb's
940 7 100       25 if ($pcs <= 6) {
941 6         15 $pos->{bb}{White} |= $mask;
942 6         21 $pos->{bb}{all} |= $mask;
943             }
944             else {
945 1         5 $pos->{bb}{Black} |= $mask;
946 1         4 $pos->{bb}{all} |= $mask;
947             }
948             }
949              
950             sub remove_piece_at {
951 19     19 1 51 my ($pos, $sqr) = @_;
952 19         55 my $pcs = $pos->{table}[$sqr];
953 19 100       63 return EMPTY if $pcs == EMPTY;
954              
955             # set 0 in bb for that square
956 11         28 my $mask = $bb_squares[$sqr];
957 11         32 $pos->{bb}{$pcs} &= $mask;
958              
959             # clear square in table too
960 11         27 $pos->{table}[$sqr] = EMPTY;
961              
962             # update other bb's
963 11 100       36 if ($pcs <= 6) {
964 8         22 $pos->{bb}{White} &= $mask;
965 8         23 $pos->{bb}{all} &= $mask;
966             }
967             else {
968 3         8 $pos->{bb}{Black} &= $mask;
969 3         7 $pos->{bb}{all} &= $mask;
970             }
971              
972 11         30 return $pcs;
973             }
974              
975             sub errors {
976 19     19 1 91 my $pos = shift;
977              
978 19 100       139 if ($pos->{bb}{WK()} == 0) {
979 2         14 return "WK missing";
980             }
981 17 50       69 if ($pos->{bb}{BK()} == 0) {
982 0         0 return "BK missing";
983             }
984 17 100       85 if ($pos->_bb_count_1s(WK()) > 1) {
985 1         8 return "Too many WKs";
986             }
987 16 100       82 if ($pos->_bb_count_1s(BK()) > 1) {
988 1         13 return "Too many BKs";
989             }
990 15 100       47 if ($pos->_bb_count_1s(WP()) > 8) {
991 1         9 return "Too many WPs";
992             }
993 14 50       48 if ($pos->_bb_count_1s(BP()) > 8) {
994 0         0 return "Too many BPs";
995             }
996 14 100       48 if ($pos->_bb_count_1s('White') > 16) {
997 1         10 return "Too many White stones";
998             }
999 13 50       45 if ($pos->_bb_count_1s('Black') > 16) {
1000 0         0 return "Too many Black stones";
1001             }
1002 13 100       79 if (($pos->{bb}{WP()} | $pos->{bb}{BP()}) & ($bb_rank_1 | $bb_rank_8)) {
1003 1         8 return "Pawns on back rank";
1004             }
1005              
1006             # TODO side to move giving check
1007            
1008 12         60 my $valid_ep_sqr = $pos->_valid_ep_square();
1009 12 100       40 if ($pos->{ep_square}) {
1010 2 50 33     25 if (! defined $valid_ep_sqr || $valid_ep_sqr != $pos->{ep_square}) {
1011 2         16 return "Invalid e.p. square";
1012             }
1013             }
1014              
1015 10 100       46 if ($pos->{castling_rights} != $pos->_clean_castling_rights()) {
1016 2         15 return "Invalid castling rights";
1017             }
1018              
1019             # OK
1020 8         64 return undef;
1021             }
1022              
1023             sub _pseudo_legal_moves_iter {
1024             # iterator for pseudo-legal moves
1025             # optionally filtered by bitboard
1026 57     57   2081 my $pos = shift;
1027 57         180 my $from_bb_filter = shift;
1028 57         116 my $to_bb_filter = shift;
1029 57   66     351 $from_bb_filter //= $bb_all;
1030 57   66     285 $to_bb_filter //= $bb_all;
1031             # closure state
1032 57         99 my $ep_capturers;
1033 57 100       211 if ($pos->{ep_square}) {
1034 12 50       75 if ($bb_squares[$pos->{ep_square}] & ~$pos->{bb}{all}) { # empty target square
1035             $ep_capturers = $pos->{to_move} eq 'w'
1036             ? $pos->{bb}{WP()} & $bb_pawn_attacks_b[$pos->{ep_square}] & $bb_rank_5
1037 12 100       69 : $pos->{bb}{BP()} & $bb_pawn_attacks_w[$pos->{ep_square}] & $bb_rank_4;
1038             }
1039             }
1040 57 100       303 my $pawn_capturers = $pos->{to_move} eq 'w' ? $pos->{bb}{WP()} : $pos->{bb}{BP()};
1041 57         138 my $targets;
1042             my $_sq;
1043 57 100       166 if ($pawn_capturers) {
1044 52         205 $_sq = $pos->_pop_lsb_index(\$pawn_capturers);
1045 52 100       316 $targets = $pos->{to_move} eq 'w' ? $bb_pawn_attacks_w[$_sq] & $pos->{bb}{Black} : $bb_pawn_attacks_b[$_sq] & $pos->{bb}{White};
1046 52         135 $targets &= $to_bb_filter;
1047             }
1048 57         93 my $promo_index = 0;
1049 57         94 my $promo_from;
1050             my $promo_to;
1051 57         228 my @promo = qw (Q R B N);
1052 57         126 my $single_moves = undef;
1053 57         96 my $double_moves = undef;
1054 57 100       171 if ($pos->{to_move} eq 'w') {
1055 38         119 $single_moves = $pos->{bb}{WP()} << 8 & ~$pos->{bb}{all};
1056 38         101 $double_moves = $single_moves << 8 & ~$pos->{bb}{all} & $bb_rank_4;
1057             }
1058             else {
1059 19         81 $single_moves = $pos->{bb}{BP()} >> 8 & ~$pos->{bb}{all};
1060 19         66 $double_moves = $single_moves >> 8 & ~$pos->{bb}{all} & $bb_rank_5;
1061             }
1062 57         132 $single_moves &= $to_bb_filter;
1063 57         99 $double_moves &= $to_bb_filter;
1064              
1065 57         117 my @castling_moves;
1066 57         337 $pos->_generate_castling_moves(\@castling_moves, $from_bb_filter, $to_bb_filter);
1067              
1068             # non-pawn moves
1069             # work_bits is a bitboard of potential from-squares
1070 57 100       304 my $work_bits = $pos->{to_move} eq 'w' ? $pos->{bb}{White} & ~$pos->{bb}{WP()} : $pos->{bb}{Black} & ~$pos->{bb}{BP()};
1071 57         131 $work_bits &= $from_bb_filter;
1072 57         170 my $attack_bits;
1073             my $sq;
1074 57         0 my $pcs;
1075              
1076             return sub {
1077 1255 100 66 1255   3143 if ($promo_index > 0 && $promo_index < 4) {
1078 42         107 my $result = Chess4p::Move->new($promo_from, $promo_to, $promo[$promo_index++]);
1079 42 100       99 $promo_index = 0 if $promo_index == 4; # in case of 2 capturing targets on 1st/8th rank
1080 42         111 return $result;
1081             }
1082 1213 100       2533 if ($ep_capturers) {
1083 9         39 my $sq = $pos->_pop_lsb_index(\$ep_capturers);
1084 9         87 return Chess4p::Move->new($sq, $pos->{ep_square});
1085             }
1086              
1087 1204   100     3113 while ($pawn_capturers && !$targets) {
1088             # try for a capturer with any target...
1089 231         550 $_sq = $pos->_pop_lsb_index(\$pawn_capturers);
1090 231 100       725 $targets = $pos->{to_move} eq 'w' ? $bb_pawn_attacks_w[$_sq] & $pos->{bb}{Black} : $bb_pawn_attacks_b[$_sq] & $pos->{bb}{White};
1091 231         746 $targets &= $to_bb_filter;
1092             }
1093            
1094 1204 100 100     3740 if ($pawn_capturers || $targets) {
1095 28         73 my $to = $pos->_pop_lsb_index(\$targets);
1096 28         98 my $rk = _square_rank($to);
1097 28 100 100     113 if ($rk == RANK_1 || $rk == RANK_8) {
1098 10         22 $promo_from = $_sq;
1099 10         48 $promo_to = $to;
1100             # promo_index needs to handle two-way capture!
1101 10         44 return Chess4p::Move->new($promo_from, $promo_to, $promo[$promo_index++]);
1102             } else {
1103 18         68 return Chess4p::Move->new($_sq, $to);
1104             }
1105             }
1106 1176 100       2120 if ($single_moves) {
1107 173         423 my $to = $pos->_pop_lsb_index(\$single_moves);
1108 173 100       563 my $sq = $pos->{to_move} eq 'w' ? $to - 8 : $to + 8;
1109 173         380 my $rk = _square_rank($to);
1110 173 100 100     656 if ($rk == RANK_1 || $rk == RANK_8) {
1111 4         6 $promo_from = $sq;#$_sq;
1112 4         8 $promo_to = $to;
1113 4         18 return Chess4p::Move->new($promo_from, $promo_to, $promo[$promo_index++]);
1114             } else {
1115 169         583 return Chess4p::Move->new($sq, $to);
1116             }
1117             }
1118 1003 100       1806 if ($double_moves) {
1119 130         358 my $to = $pos->_pop_lsb_index(\$double_moves);
1120 130 100       371 my $sq = $pos->{to_move} eq 'w' ? $to - 16 : $to + 16;
1121 130         368 return Chess4p::Move->new($sq, $to);
1122             }
1123            
1124 873   100     2137 while ($work_bits || $attack_bits) {
1125 958 100       1668 if (!$attack_bits) {
1126 289         633 $sq = $pos->_pop_lsb_index(\$work_bits);
1127 289         761 $pcs = $pos->{table}[$sq];
1128 289 100 100     2056 if ($pcs == WN || $pcs == BN) {
    100 100        
    100 100        
    100 100        
    50 66        
1129 72         197 $attack_bits = $bb_knight_attacks[$sq];
1130             } elsif ($pcs == WK || $pcs == BK) {
1131 35         88 $attack_bits = $bb_king_attacks[$sq];
1132             } elsif ($pcs == WB || $pcs == BB) {
1133 68         526 $attack_bits = $$BB_DIAG_ATTACKS[$sq]->{$$BB_DIAG_MASKS[$sq] & $pos->{bb}{all}};
1134             } elsif ($pcs == WR || $pcs == BR) {
1135 77         586 $attack_bits = $$BB_RANK_ATTACKS[$sq]->{$$BB_RANK_MASKS[$sq] & $pos->{bb}{all}};
1136 77         565 $attack_bits |= $$BB_FILE_ATTACKS[$sq]->{$$BB_FILE_MASKS[$sq] & $pos->{bb}{all}};
1137             } elsif ($pcs == WQ || $pcs == BQ) {
1138 37         229 $attack_bits = $$BB_RANK_ATTACKS[$sq]->{$$BB_RANK_MASKS[$sq] & $pos->{bb}{all}};
1139 37         235 $attack_bits |= $$BB_FILE_ATTACKS[$sq]->{$$BB_FILE_MASKS[$sq] & $pos->{bb}{all}};
1140 37         236 $attack_bits |= $$BB_DIAG_ATTACKS[$sq]->{$$BB_DIAG_MASKS[$sq] & $pos->{bb}{all}};
1141             }
1142 289         469 $attack_bits &= $to_bb_filter;
1143             }
1144 958         1930 while ($attack_bits) {
1145 1282         2746 my $to = $pos->_pop_lsb_index(\$attack_bits);
1146 1282 100 100     5043 if ($pos->{table}[$to] == EMPTY || ($pos->{to_move} eq 'w' ? $pos->{table}[$to] >= 7 : $pos->{table}[$to] < 7)) {
    100          
1147             # not own stone - we can go there
1148 805         2326 return Chess4p::Move->new($sq, $to);
1149             }
1150             }
1151             }
1152              
1153 68 100       262 if (my $any = pop(@castling_moves)) {
1154 21         65 return $any;
1155             }
1156              
1157 47         147 return undef;
1158             }
1159 57         919 }
1160              
1161             sub _evasions_iter {
1162 37     37   117 my ($pos, $king, $checkers) = @_;
1163 37         285 my $sliders = $checkers & ($pos->{bb}{WQ()} | $pos->{bb}{BQ()} | ($pos->{bb}{WR()}) | ($pos->{bb}{BR()}) | ($pos->{bb}{WB()}) | ($pos->{bb}{BB()}) );
1164 37         74 my $attacked = $bb_empty;
1165 37         124 my $one_checker = _msb($checkers);
1166 37         59 my $target;
1167 37 100       169 if (~$bb_squares[$one_checker] == $checkers) {
1168             # a single checker
1169             # target squares: block or capture the checker
1170 11         35 $target = _between($king, $one_checker) | $checkers;
1171             }
1172 37         91 my $block_iter;
1173 37 100       131 if ($target) {
1174             # set up iterator
1175 11         48 $block_iter = $pos->_pseudo_legal_moves_iter(~_make_bb($king), $target);
1176             }
1177 37         125 while ($sliders) {
1178 7         23 my $checker = $pos->_pop_lsb_index(\$sliders);
1179 7         33 $attacked |= _ray($king, $checker) & $bb_squares[$checker];
1180             }
1181 37 100       131 my $our_stones = ($pos->{to_move} eq 'w' ? $pos->{bb}{White} : $pos->{bb}{Black});
1182             # bitboard for the king's flight squares
1183 37         149 my $move_away_bb = $bb_king_attacks[$king] & ~$attacked & ~$our_stones;
1184            
1185             return sub {
1186 63     63   151 while ($move_away_bb) {
1187 31         49 my $sq = $pos->_pop_lsb_index(\$move_away_bb);
1188 31         96 return Chess4p::Move->new($king, $sq);
1189             }
1190 32 50       97 if ($target) {
1191 32         60 while (defined(my $m = $block_iter->())) {
1192 21         63 return $m;
1193             }
1194             }
1195 11         41 return undef;
1196             }
1197 37         305 }
1198              
1199             sub _slider_blockers {
1200 37     37   191 my ($pos, $king) = @_;
1201 37 100       140 my $opponent = $pos->{to_move} eq 'w' ? 'b' : 'w';
1202 37 100       165 my $rooks_queens = $opponent eq 'w' ? $pos->{bb}{WQ()} : $pos->{bb}{BQ()};
1203 37 100       145 $rooks_queens |= $opponent eq 'w' ? $pos->{bb}{WR()} : $pos->{bb}{BR()};
1204 37 100       154 my $bishops_queens = $opponent eq 'w' ? $pos->{bb}{WQ()} : $pos->{bb}{BQ()};
1205 37 100       134 $bishops_queens |= $opponent eq 'w' ? $pos->{bb}{WB()} : $pos->{bb}{BB()};
1206             my $snipers = (($$BB_RANK_ATTACKS[$king]->{0} & $rooks_queens) |
1207             ($$BB_FILE_ATTACKS[$king]->{0} & $rooks_queens) |
1208 37         362 ($$BB_DIAG_ATTACKS[$king]->{0} & $bishops_queens));
1209 37         70 my $blockers = $bb_empty;
1210 37         126 while ($snipers) {
1211 21         69 my $sq = $pos->_pop_lsb_index(\$snipers);
1212             # bb is the blocking stones
1213 21         91 my $bb = _between($king, $sq) & $pos->{bb}{all};
1214             # Add to blockers if exactly one piece in-between.
1215 21 100 100     134 if ($bb && (~$bb_squares[_msb($bb)] == $bb)) {
1216 9         30 $blockers |= $bb;
1217             }
1218             }
1219 37         93 $blockers;
1220             }
1221              
1222             sub legal_moves_iter {
1223             # iterate over legal moves from board position
1224 36     36 1 18739 my ($pos) = @_;
1225 36         225 my $moves_iter = $pos->_pseudo_legal_moves_iter();
1226            
1227 36         142 my $side = $pos->{to_move};
1228 36 100       188 my $opponent = $side eq 'w' ? 'b' : 'w';
1229 36 100       132 my $king = $side eq 'w' ? $pos->{bb}{WK()} : $pos->{bb}{BK()};
1230 36         109 $king = _msb($king);
1231 36         168 my $blockers = $pos->_slider_blockers($king);
1232 36         170 my $checkers = $pos->_get_attackers($opponent, $king);
1233 36         201 my $evasions_iter = $pos->_evasions_iter($king, $checkers);
1234              
1235             return sub {
1236 938 100   938   1959 if ($checkers) {
1237 55         107 while (defined(my $m = $evasions_iter->())) {
1238 51 100       128 if ($pos->_is_safe($king, $blockers, $m->{from}, $m->{to})) {
1239 45         133 return $m;
1240             }
1241             }
1242             }
1243             else {
1244 883         1457 while (defined(my $m = $moves_iter->())) {
1245 899 100       2403 if ($pos->_is_safe($king, $blockers, $m->{from}, $m->{to})) {
1246 857         2889 return $m;
1247             }
1248             }
1249             }
1250 36         94 return undef;
1251             }
1252 36         326 }
1253              
1254             sub legal_moves {
1255 0     0 1 0 my $pos = shift;
1256 0         0 my @result;
1257 0         0 my $moves_iter = $pos->legal_moves_iter();
1258 0         0 while (defined(my $m = $moves_iter->())) {
1259 0         0 push (@result, $m);
1260             }
1261 0         0 \@result;
1262             }
1263              
1264             sub push_move_uci {
1265 0     0 0 0 my ($pos, $move) = @_;
1266 0         0 my $from = $square_numbers{substr($move, 0, 2)};
1267 0         0 my $to = $square_numbers{substr($move, 2, 2)};
1268 0         0 my $promoted;
1269 0 0       0 $promoted = uc(substr($move, 4)) if length($move) > 4;
1270 0         0 $pos->push_move(Chess4p::Move->new($from, $to, $promoted));
1271             }
1272              
1273             sub push_move {
1274 5     5 1 15 my ($pos, $move) = @_;
1275 5         27 $pos->{castling_rights} = $pos->_clean_castling_rights();
1276 5         14 push (@{$pos->{move_stack}}, $move);
  5         19  
1277 5         13 my @table;
1278 5         14 for my $sqr (A1..H8) {
1279 320         762 $table[$sqr] = $pos->{table}[$sqr];
1280             }
1281 5         11 my %bitboards;
1282 5         25 $bitboards{WK()} = $pos->{bb}{WK()};
1283 5         22 $bitboards{WQ()} = $pos->{bb}{WQ()};
1284 5         18 $bitboards{WR()} = $pos->{bb}{WR()};
1285 5         16 $bitboards{WB()} = $pos->{bb}{WB()};
1286 5         15 $bitboards{WN()} = $pos->{bb}{WN()};
1287 5         13 $bitboards{WP()} = $pos->{bb}{WP()};
1288 5         20 $bitboards{BK()} = $pos->{bb}{BK()};
1289 5         16 $bitboards{BQ()} = $pos->{bb}{BQ()};
1290 5         16 $bitboards{BR()} = $pos->{bb}{BR()};
1291 5         17 $bitboards{BB()} = $pos->{bb}{BB()};
1292 5         13 $bitboards{BN()} = $pos->{bb}{BN()};
1293 5         20 $bitboards{BP()} = $pos->{bb}{BP()};
1294 5         18 $bitboards{White} = $pos->{bb}{White};
1295 5         16 $bitboards{Black} = $pos->{bb}{Black};
1296 5         18 $bitboards{all} = $pos->{bb}{all};
1297            
1298             my %state = (to_move => $pos->{to_move},
1299             castling_rights => $pos->{castling_rights},
1300             halfmove_clock => $pos->{halfmove_clock},
1301             fullmove_number => $pos->{fullmove_number},
1302             bitboards => \%bitboards,
1303             table => \@table,
1304             ep_square => $pos->{ep_square},
1305 5         62 );
1306 5         14 push (@{$pos->{stack}}, \%state);
  5         18  
1307 5         45 my $ep_square = $pos->{ep_square};
1308 5         13 $pos->{ep_square} = undef;
1309 5         10 $pos->{halfmove_clock}++;
1310 5 100       24 $pos->{fullmove_number}++ if $pos->{to_move} eq 'b';
1311 5 50       32 unless ($move) {
1312             # null move - swap turns and reset en passant square.
1313 0 0       0 $pos->{to_move} = $pos->{to_move} eq 'w' ? 'b' : 'w';
1314 0         0 return;
1315             }
1316             # reset halfmove clock if needed
1317 5 50       39 $pos->{halfmove_clock} = 0 if $pos->_is_zeroing($move);
1318 5         18 my $from_bb = ~$bb_squares[$move->from()];
1319 5         14 my $to_bb = ~$bb_squares[$move->to()];
1320             # promoted = pos->promoted & from_bb
1321 5         13 my $pcs = $pos->remove_piece_at($move->from());
1322 5 50       16 croak "push needs a pseudo-legal move, but got $move" unless $pcs;
1323 5         16 my $capture_sqr = $move->to();
1324 5         32 my $captured_pcs = $pos->piece_at($capture_sqr);
1325             # NNN eq '.' for empty !!
1326             # TODO maybe that should be changed
1327              
1328             # Update castling rights
1329 5         21 $pos->{castling_rights} &= ~$to_bb & ~$from_bb;
1330 5 100 66     36 if (($pcs == WK || $pcs == BK)) { # && ! promoted
1331 2 50       7 $pos->{castling_rights} &= ~$bb_rank_1 if $pos->{to_move} eq 'w';
1332 2 50       10 $pos->{castling_rights} &= ~$bb_rank_8 if $pos->{to_move} eq 'b';
1333             }
1334             # special pawn moves
1335 5 100 100     49 if ($pcs == WP || $pcs == BP) {
1336 3         12 my $diff = $move->to() - $move->from();
1337 3 50 33     57 if ($diff == 16 && _square_rank($move->from()) == RANK_2) {
    50 33        
    100 66        
      66        
      33        
      33        
1338 0         0 $pos->{ep_square} = $move->from() + 8;
1339             }
1340             elsif ($diff == -16 && _square_rank($move->from()) == RANK_7) {
1341 0         0 $pos->{ep_square} = $move->from() - 8;
1342             }
1343             elsif (defined $ep_square && $move->to() == $ep_square && $captured_pcs eq '.' && (abs($diff) == 7 || abs($diff) == 9)) {
1344             # remove pawn captured e.p.
1345 1 50       6 my $down = $pos->{to_move} eq 'w' ? -8 : 8;
1346 1         3 $capture_sqr = $move->to() + $down;
1347 1         4 $captured_pcs = $pos->remove_piece_at($capture_sqr);
1348             }
1349             }
1350 5 100       17 if ($move->promotion()) {
1351             # promoted = 1;
1352 2 100       15 if ($pos->{to_move} eq 'w') {
1353 1         6 $pcs = $fen_chars_to_piece_code{$move->promotion()};
1354             }
1355             else {
1356 1         5 $pcs = $fen_chars_to_piece_code{lc($move->promotion())};
1357             }
1358             }
1359             # castling
1360 5   100     40 my $castling = ($pcs == WK || $pcs == BK) && _square_distance($move->from(), $move->to()) > 1;
1361 5 100       16 if ($castling) {
1362 1         5 my $a_side = _square_file($move->to()) < _square_file($move->from());
1363 1         5 $pos->remove_piece_at($move->from());
1364             # TODO
1365             # the king is not allowed to capture with castling, so next line should be removable?
1366             # (python-chess)
1367 1         4 $pos->remove_piece_at($move->to());
1368             ## no critic (ValuesAndExpressions::ProhibitCommaSeparatedStatements)
1369 1 50       4 if ($a_side) {
1370 0 0       0 $pos->set_piece_at(C1, WK), $pos->set_piece_at(D1, WR), $pos->remove_piece_at(A1) if $pos->{to_move} eq 'w';
1371 0 0       0 $pos->set_piece_at(C8, BK), $pos->set_piece_at(D8, BR), $pos->remove_piece_at(A8) if $pos->{to_move} eq 'b';
1372             }
1373             else {
1374 1 50       9 $pos->set_piece_at(G1, WK), $pos->set_piece_at(F1, WR), $pos->remove_piece_at(H1) if $pos->{to_move} eq 'w';
1375 1 50       6 $pos->set_piece_at(G8, BK), $pos->set_piece_at(F8, BR), $pos->remove_piece_at(H8) if $pos->{to_move} eq 'b';
1376             }
1377             }
1378             else {
1379 4         11 $pos->set_piece_at($move->to(), $pcs);
1380             }
1381             # swap turn
1382 5         28 $pos->{to_move} = $pos->_opponent();
1383             }
1384              
1385             sub pop_move {
1386 5     5 1 8314 my ($pos) = @_;
1387 5         12 my $move = pop @{$pos->{move_stack}};
  5         28  
1388 5         11 my $href = pop @{$pos->{stack}};
  5         18  
1389 5         23 $pos->{to_move} = $href->{to_move};
1390 5         17 $pos->{castling_rights} = $href->{castling_rights};
1391 5         21 $pos->{halfmove_clock} = $href->{halfmove_clock};
1392 5         14 $pos->{fullmove_number} = $href->{fullmove_number};
1393 5         48 $pos->{bb} = $href->{bitboards};
1394 5         84 $pos->{table} = $href->{table};
1395 5         14 $pos->{ep_square} = $href->{ep_square};
1396 5         37 return $move;
1397             }
1398              
1399              
1400              
1401              
1402             1;
1403              
1404             __END__