777571e121d1ba5fc7301d54a6a6b7e1926cb202
[remoteglot] / Position.pm
1 #! /usr/bin/perl
2 #
3 # There are too many chess modules on CPAN already, so here's another one...
4 #
5 use strict;
6 use warnings;
7
8 require 'Board.pm';
9
10 package Position;
11
12 # Takes in a FICS style 12-type position.
13 sub new {
14         my ($class, $str) = @_;
15         my $pos = {};
16         my (@x) = split / /, $str;
17
18         $pos->{'board'} = Board->new(@x[1..8]);
19         $pos->{'toplay'} = $x[9];
20         $pos->{'ep_file_num'} = $x[10];
21         $pos->{'white_castle_k'} = $x[11];
22         $pos->{'white_castle_q'} = $x[12];
23         $pos->{'black_castle_k'} = $x[13];
24         $pos->{'black_castle_q'} = $x[14];
25         $pos->{'time_since_100move_rule_reset'} = $x[15];
26         $pos->{'player_w'} = $x[17];
27         $pos->{'player_b'} = $x[18];
28         $pos->{'player_w'} =~ s/^W?[FCIG]M//;
29         $pos->{'player_b'} =~ s/^W?[FCIG]M//;
30         $pos->{'move_num'} = $x[26];
31         $pos->{'last_move'} = $x[29];
32
33         bless $pos, $class;
34         return $pos;
35 }
36
37 sub start_pos {
38         my ($class, $white, $black) = @_;
39         return $class->new("<12> rnbqkbnr pppppppp -------- -------- -------- -------- PPPPPPPP RNBQKBNR W -1 1 1 1 1 0 dummygamenum $white $black -2 dummytime dummyincrement 39 39 dummytime dummytime 1 none (0:00) none 0 0 0");
40 }
41
42 sub fen {
43         my $pos = shift;
44
45         # the board itself
46         my $fen = $pos->{'board'}->fen();
47
48         # white/black to move
49         $fen .= " ";
50         $fen .= lc($pos->{'toplay'});
51
52         # castling
53         my $castling = "";
54         $castling .= "K" if ($pos->{'white_castle_k'} == 1);
55         $castling .= "Q" if ($pos->{'white_castle_q'} == 1);
56         $castling .= "k" if ($pos->{'black_castle_k'} == 1);
57         $castling .= "q" if ($pos->{'black_castle_q'} == 1);
58         $castling = "-" if ($castling eq "");
59         # $castling = "-"; # chess960
60         $fen .= " ";
61         $fen .= $castling;
62
63         # en passant
64         my $ep = "-";
65         if ($pos->{'ep_file_num'} != -1) {
66                 my $col = $pos->{'ep_file_num'};
67                 my $nep = (qw(a b c d e f g h))[$col];
68
69                 if ($pos->{'toplay'} eq 'B') {
70                         $nep .= "3";
71                 } else {
72                         $nep .= "6";
73                 }
74
75                 #
76                 # Showing the en passant square when actually no capture can be made
77                 # seems to confuse at least Rybka. Thus, check if there's actually
78                 # a pawn of the opposite side that can do the en passant move, and if
79                 # not, just lie -- it doesn't matter anyway. I'm unsure what's the
80                 # "right" thing as per the standard, though.
81                 #
82                 if ($pos->{'toplay'} eq 'B') {
83                         $ep = $nep if ($col > 0 && $pos->{'board'}[4][$col-1] eq 'p');
84                         $ep = $nep if ($col < 7 && $pos->{'board'}[4][$col+1] eq 'p');
85                 } else {
86                         $ep = $nep if ($col > 0 && $pos->{'board'}[3][$col-1] eq 'P');
87                         $ep = $nep if ($col < 7 && $pos->{'board'}[3][$col+1] eq 'P');
88                 }
89         }
90         $fen .= " ";
91         $fen .= $ep;
92
93         # half-move clock
94         $fen .= " ";
95         $fen .= $pos->{'time_since_100move_rule_reset'};
96
97         # full-move clock
98         $fen .= " ";
99         $fen .= $pos->{'move_num'};
100
101         return $fen;
102 }
103
104 sub to_json_hash {
105         my $pos = shift;
106         return { %$pos, board => undef, fen => $pos->fen() };
107 }
108
109 sub parse_pretty_move {
110         my ($pos, $move) = @_;
111         return $pos->{'board'}->parse_pretty_move($move, $pos->{'toplay'});
112 }
113
114 # Returns a new Position object.
115 sub make_move {
116         my ($pos, $from_row, $from_col, $to_row, $to_col, $promo) = @_;
117
118         my $from_square = _pos_to_square($from_row, $from_col);
119         my $to_square = _pos_to_square($to_row, $to_col);
120
121         my $np = {};
122         $np->{'board'} = $pos->{'board'}->make_move($from_row, $from_col, $to_row, $to_col, $promo);
123         if ($pos->{'toplay'} eq 'W') {
124                 $np->{'toplay'} = 'B';
125                 $np->{'move_num'} = $pos->{'move_num'};
126         } else {
127                 $np->{'toplay'} = 'W';
128                 $np->{'move_num'} = $pos->{'move_num'} + 1;
129         }
130
131         my $piece = $pos->{'board'}[$from_row][$from_col];
132         my $dest_piece = $pos->{'board'}[$to_row][$to_col];
133
134         # Find out if this was a two-step pawn move.
135         if (lc($piece) eq 'p' && abs($from_row - $to_row) == 2) {
136                 $np->{'ep_file_num'} = $from_col;
137         } else {
138                 $np->{'ep_file_num'} = -1;
139         }
140
141         # Castling rights.
142         $np->{'white_castle_k'} = $pos->{'white_castle_k'};
143         $np->{'white_castle_q'} = $pos->{'white_castle_q'};
144         $np->{'black_castle_k'} = $pos->{'black_castle_k'};
145         $np->{'black_castle_q'} = $pos->{'black_castle_q'};
146         if ($piece eq 'K') {
147                 $np->{'white_castle_k'} = 0;
148                 $np->{'white_castle_q'} = 0;
149         } elsif ($piece eq 'k') {
150                 $np->{'black_castle_k'} = 0;
151                 $np->{'black_castle_q'} = 0;
152         } elsif ($from_square eq 'a1' || $to_square eq 'a1') {
153                 $np->{'white_castle_q'} = 0;
154         } elsif ($from_square eq 'h1' || $to_square eq 'h1') {
155                 $np->{'white_castle_k'} = 0;
156         } elsif ($from_square eq 'a8' || $to_square eq 'a8') {
157                 $np->{'black_castle_q'} = 0;
158         } elsif ($from_square eq 'h8' || $to_square eq 'h8') {
159                 $np->{'black_castle_k'} = 0;
160         }
161
162         # 50-move rule.
163         if (lc($piece) eq 'p' || $dest_piece ne '-') {
164                 $np->{'time_since_100move_rule_reset'} = 0;
165         } else {
166                 $np->{'time_since_100move_rule_reset'} = $pos->{'time_since_100move_rule_reset'} + 1;
167         }
168         $np->{'player_w'} = $pos->{'player_w'};
169         $np->{'player_b'} = $pos->{'player_b'};
170         my ($move, $nb) = $pos->{'board'}->prettyprint_move($from_row, $from_col, $to_row, $to_col, $promo);
171         $np->{'last_move'} = $move;
172         return bless $np;
173 }
174
175 sub _pos_to_square {
176         my ($row, $col) = @_;
177         return sprintf("%c%d", ord('a') + $col, 8 - $row);
178 }
179
180 sub apply_uci_pv {
181         my ($pos, @pv) = @_;
182
183         my $pvpos = $pos;
184         for my $pv_move (@pv) {
185                 my ($from_row, $from_col, $to_row, $to_col, $promo) = _parse_uci_move($pv_move);
186                 $pvpos = $pvpos->make_move($from_row, $from_col, $to_row, $to_col, $promo);
187         }
188
189         return $pvpos;
190 }
191
192 sub _col_letter_to_num {
193         return ord(shift) - ord('a');
194 }
195
196 sub _row_letter_to_num {
197         return 7 - (ord(shift) - ord('1'));
198 }
199
200 sub _parse_uci_move {
201         my $move = shift;
202         my $from_col = _col_letter_to_num(substr($move, 0, 1));
203         my $from_row = _row_letter_to_num(substr($move, 1, 1));
204         my $to_col   = _col_letter_to_num(substr($move, 2, 1));
205         my $to_row   = _row_letter_to_num(substr($move, 3, 1));
206         my $promo    = substr($move, 4, 1);
207         return ($from_row, $from_col, $to_row, $to_col, $promo);
208 }
209
210 1;