| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
2
|
|
|
|
|
|
|
# Games::Chess - represent chess pieces, positions, moves and games |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# AUTHOR |
|
5
|
|
|
|
|
|
|
# Gareth Rees |
|
6
|
|
|
|
|
|
|
# |
|
7
|
|
|
|
|
|
|
# COPYRIGHT |
|
8
|
|
|
|
|
|
|
# Copyright (c) 1999 Gareth Rees. This module is free software: you |
|
9
|
|
|
|
|
|
|
# can distribute and/or modify it under the same terms as Perl itself. |
|
10
|
|
|
|
|
|
|
# |
|
11
|
|
|
|
|
|
|
# $Id: Chess.pm,v 1.5 1999/06/06 18:47:24 gareth Exp $ |
|
12
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
package Games::Chess; |
|
15
|
4
|
|
|
4
|
|
4153
|
use base 'Exporter'; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
494
|
|
|
16
|
4
|
|
|
4
|
|
21
|
use strict; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
152
|
|
|
17
|
4
|
|
|
4
|
|
20
|
use vars qw($RCSID $VERSION $ERRMSG $DEBUG @EXPORT @EXPORT_OK %EXPORT_TAGS); |
|
|
4
|
|
|
|
|
12
|
|
|
|
4
|
|
|
|
|
989
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
$RCSID = q$Id: Chess.pm,v 1.5 1999/06/06 18:47:24 gareth Exp $; |
|
20
|
|
|
|
|
|
|
$VERSION = '0.003'; |
|
21
|
|
|
|
|
|
|
$ERRMSG = ''; |
|
22
|
|
|
|
|
|
|
$DEBUG = 0; |
|
23
|
|
|
|
|
|
|
@EXPORT = (); |
|
24
|
|
|
|
|
|
|
@EXPORT_OK = qw(algebraic_to_xy colour_valid debug errmsg error |
|
25
|
|
|
|
|
|
|
halfmove_count_valid move_number_valid piece_valid xy_valid |
|
26
|
|
|
|
|
|
|
xy_to_algebraic |
|
27
|
|
|
|
|
|
|
EMPTY WHITE BLACK PAWN KNIGHT BISHOP ROOK QUEEN KING); |
|
28
|
|
|
|
|
|
|
%EXPORT_TAGS = |
|
29
|
|
|
|
|
|
|
( colours => [qw(EMPTY WHITE BLACK)], |
|
30
|
|
|
|
|
|
|
pieces => [qw(EMPTY PAWN KNIGHT BISHOP ROOK QUEEN KING)], |
|
31
|
|
|
|
|
|
|
constants => [qw(EMPTY WHITE BLACK PAWN KNIGHT BISHOP ROOK QUEEN KING)], |
|
32
|
|
|
|
|
|
|
functions => [qw(algebraic_to_xy colour_valid debug errmsg |
|
33
|
|
|
|
|
|
|
halfmove_count_valid move_number_valid piece_valid |
|
34
|
|
|
|
|
|
|
xy_valid xy_to_algebraic)], |
|
35
|
|
|
|
|
|
|
); |
|
36
|
|
|
|
|
|
|
|
|
37
|
4
|
|
|
4
|
|
23
|
use constant EMPTY => 0; |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
427
|
|
|
38
|
4
|
|
|
4
|
|
28
|
use constant WHITE => 1; |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
184
|
|
|
39
|
4
|
|
|
4
|
|
21
|
use constant BLACK => 2; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
242
|
|
|
40
|
|
|
|
|
|
|
|
|
41
|
4
|
|
|
4
|
|
20
|
use constant PAWN => 1; |
|
|
4
|
|
|
|
|
15
|
|
|
|
4
|
|
|
|
|
197
|
|
|
42
|
4
|
|
|
4
|
|
20
|
use constant KNIGHT => 2; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
173
|
|
|
43
|
4
|
|
|
4
|
|
19
|
use constant BISHOP => 3; |
|
|
4
|
|
|
|
|
14
|
|
|
|
4
|
|
|
|
|
205
|
|
|
44
|
4
|
|
|
4
|
|
54
|
use constant ROOK => 4; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
161
|
|
|
45
|
4
|
|
|
4
|
|
19
|
use constant QUEEN => 5; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
173
|
|
|
46
|
4
|
|
|
4
|
|
19
|
use constant KING => 6; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
4151
|
|
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub algebraic_to_xy ( $ ) { |
|
49
|
114
|
|
|
114
|
1
|
469
|
my ($sq) = @_; |
|
50
|
114
|
100
|
|
|
|
336
|
$sq =~ /^([a-h])([1-8])$/ |
|
51
|
|
|
|
|
|
|
or return error("$sq does not specify a square in algebraic notation"); |
|
52
|
102
|
|
|
|
|
371
|
return (ord($1) - ord('a'), $2 - 1); |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub colour_valid ( $ ) { |
|
56
|
292
|
|
|
292
|
1
|
317
|
my ($colour) = @_; |
|
57
|
292
|
50
|
66
|
|
|
1366
|
return 1 if $colour == WHITE or $colour == BLACK; |
|
58
|
0
|
|
|
|
|
0
|
return error("colour $colour invalid: must be @{[WHITE]} or @{[BLACK]}"); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub debug ( $ ) { |
|
62
|
4
|
|
|
4
|
1
|
30
|
$DEBUG = shift; |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
sub errmsg () { |
|
66
|
10
|
|
|
10
|
1
|
72
|
return $ERRMSG; |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub error ( $ ) { |
|
70
|
224
|
|
|
224
|
0
|
275
|
$ERRMSG = shift; |
|
71
|
224
|
50
|
|
|
|
394
|
if ($DEBUG > 0) { |
|
72
|
0
|
|
|
|
|
0
|
my ($filename,$line) = (caller(2))[1,2]; |
|
73
|
0
|
|
|
|
|
0
|
my $message = "$ERRMSG at $filename line $line\n"; |
|
74
|
0
|
0
|
|
|
|
0
|
$DEBUG >= 2 ? die($message) : warn($message); |
|
75
|
|
|
|
|
|
|
} |
|
76
|
224
|
|
|
|
|
547
|
return; |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub halfmove_count_valid ( $ ) { |
|
80
|
225
|
|
|
225
|
0
|
333
|
my ($halfmove) = @_; |
|
81
|
225
|
50
|
|
|
|
1131
|
return 1 if $halfmove =~ /^[0-9]+$/; |
|
82
|
0
|
|
|
|
|
0
|
return error("halfmove clock '$halfmove' not a non-negative integer"); |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub move_number_valid ( $ ) { |
|
86
|
425
|
|
|
425
|
0
|
452
|
my ($move) = @_; |
|
87
|
425
|
50
|
33
|
|
|
3027
|
return 1 if $move =~ /^[0-9]+$/ and $move > 0; |
|
88
|
0
|
|
|
|
|
0
|
return error("Fullmove number '$move' not a positive integer"); |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub piece_valid ( $ ) { |
|
92
|
284
|
|
|
284
|
1
|
382
|
my ($piece) = @_; |
|
93
|
284
|
50
|
33
|
|
|
1518
|
return 1 if PAWN <= $piece and $piece <= KING; |
|
94
|
0
|
|
|
|
|
0
|
return error("piece $piece invalid: not between @{[PAWN]} and @{[KING]}"); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
95
|
|
|
|
|
|
|
} |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub xy_to_algebraic ($$) { |
|
98
|
165
|
|
|
165
|
1
|
1791
|
my ($x,$y) = @_; |
|
99
|
165
|
100
|
|
|
|
335
|
return unless xy_valid($x,$y); |
|
100
|
64
|
|
|
|
|
194
|
return chr($x + ord('a')) . ($y + 1); |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub xy_valid ($$) { |
|
104
|
3454
|
|
|
3454
|
1
|
4154
|
my ($x,$y) = @_; |
|
105
|
3454
|
100
|
100
|
|
|
28651
|
return 1 if 0 <= $x and $x < 8 and 0 <= $y and $y < 8; |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
106
|
202
|
|
|
|
|
1277
|
return error("($x,$y) off chessboard: not in the range (0,0) to (7,7)"); |
|
107
|
|
|
|
|
|
|
} |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
110
|
|
|
|
|
|
|
# Games::Chess::Piece - representation of a chess piece |
|
111
|
|
|
|
|
|
|
# A piece is represented as a blessed reference to a byte. |
|
112
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
package Games::Chess::Piece; |
|
115
|
4
|
|
|
4
|
|
22
|
use strict; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
3459
|
|
|
116
|
|
|
|
|
|
|
Games::Chess->import(qw(error piece_valid colour_valid)); |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my @COLOUR_NAMES = qw(empty white black unknown); |
|
119
|
|
|
|
|
|
|
my @PIECE_NAMES = qw(square pawn knight bishop rook queen king unknown); |
|
120
|
|
|
|
|
|
|
my $pieces = 'pnbrqk'; |
|
121
|
|
|
|
|
|
|
my @CODE_PIECE = split '', " $pieces "; |
|
122
|
|
|
|
|
|
|
my $PIECE_CODES = " \U$pieces\E$pieces"; |
|
123
|
|
|
|
|
|
|
my %PIECE_CODES; |
|
124
|
|
|
|
|
|
|
@PIECE_CODES{split '', $PIECE_CODES} = (0, 9..14, 17..22); |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
sub new { |
|
127
|
2975
|
|
|
2975
|
|
6786
|
my ($class,$val) = @_; |
|
128
|
2975
|
|
|
|
|
4707
|
my $self = chr(0); |
|
129
|
2975
|
100
|
33
|
|
|
28648
|
if (@_ < 2) { |
|
|
|
50
|
33
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# Use the default (empty square). |
|
131
|
|
|
|
|
|
|
} elsif (@_ > 3) { |
|
132
|
0
|
|
|
|
|
0
|
return error("Piece->new called with more than 3 arguments"); |
|
133
|
|
|
|
|
|
|
} elsif (@_ == 3) { |
|
134
|
284
|
50
|
|
|
|
539
|
return unless colour_valid($_[1]); |
|
135
|
284
|
50
|
|
|
|
546
|
return unless piece_valid($_[2]); |
|
136
|
284
|
|
|
|
|
502
|
$self = chr(($_[1] << 3) + $_[2]); |
|
137
|
|
|
|
|
|
|
} elsif (UNIVERSAL::isa($val,'Games::Chess::Piece')) { |
|
138
|
13
|
|
|
|
|
21
|
$self = $$val; |
|
139
|
|
|
|
|
|
|
} elsif (exists $PIECE_CODES{$val}) { |
|
140
|
49
|
|
|
|
|
70
|
$self = chr($PIECE_CODES{$val}); |
|
141
|
|
|
|
|
|
|
} elsif ($val !~ /^\d+$/) { |
|
142
|
0
|
|
|
|
|
0
|
return error("Piece->new('$val') invalid: '$val' not a chess piece"); |
|
143
|
|
|
|
|
|
|
} elsif (0 <= $val and $val < 256 and $val == int $val) { |
|
144
|
2628
|
|
|
|
|
3223
|
$self = chr($val); |
|
145
|
|
|
|
|
|
|
} else { |
|
146
|
0
|
|
|
|
|
0
|
return error("Piece->new($val) invalid: $val outside range 0 to 255"); |
|
147
|
|
|
|
|
|
|
} |
|
148
|
2975
|
|
|
|
|
11787
|
bless \$self, $class; |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub code ( $ ) { |
|
152
|
2543
|
|
|
2543
|
|
5145
|
my ($self) = @_; |
|
153
|
2543
|
|
|
|
|
3300
|
my $col = (ord($$self) & 24) >> 3; |
|
154
|
2543
|
|
|
|
|
3698
|
my $code = $CODE_PIECE[ord($$self) & 7]; |
|
155
|
2543
|
100
|
|
|
|
9166
|
return $col == 2 ? $code : uc($code); |
|
156
|
|
|
|
|
|
|
} |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
sub colour ( $ ) { |
|
159
|
393
|
|
|
393
|
|
7479
|
my ($self) = @_; |
|
160
|
393
|
|
|
|
|
1015
|
return (ord($$self) & 24) >> 3; |
|
161
|
|
|
|
|
|
|
} |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub colour_name ( $ ) { |
|
164
|
114
|
|
|
114
|
|
204
|
my ($self) = @_; |
|
165
|
114
|
|
|
|
|
190
|
return $COLOUR_NAMES[$self->colour]; |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub name ( $ ) { |
|
169
|
39
|
|
|
39
|
|
44
|
my ($self) = @_; |
|
170
|
39
|
|
|
|
|
63
|
return join ' ', $self->colour_name, $self->piece_name; |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
sub piece ( $ ) { |
|
174
|
429
|
|
|
429
|
|
873
|
my ($self) = @_; |
|
175
|
429
|
|
|
|
|
4060
|
return ord($$self) & 7; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub piece_name ( $ ) { |
|
179
|
150
|
|
|
150
|
|
169
|
my ($self) = @_; |
|
180
|
150
|
|
|
|
|
254
|
return $PIECE_NAMES[$self->piece]; |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
184
|
|
|
|
|
|
|
# Games::Chess::Move - representation of a chess move |
|
185
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
package Games::Chess::Move; |
|
188
|
4
|
|
|
4
|
|
33
|
use strict; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
1917
|
|
|
189
|
|
|
|
|
|
|
Games::Chess->import(qw(error xy_valid)); |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub new { |
|
192
|
0
|
|
|
0
|
|
0
|
my ($class,$xs,$ys,$xd,$yd,@promotion) = @_; |
|
193
|
0
|
0
|
0
|
|
|
0
|
return unless xy_valid($xs,$ys) and xy_valid($xd,$yd); |
|
194
|
0
|
|
|
|
|
0
|
my $self = { from => [$xs,$ys], to => [$xd,$yd] }; |
|
195
|
0
|
0
|
|
|
|
0
|
if (@promotion) { |
|
196
|
0
|
|
|
|
|
0
|
my $p = Games::Chess::Piece->new(@promotion); |
|
197
|
0
|
0
|
|
|
|
0
|
return unless $p; |
|
198
|
0
|
|
|
|
|
0
|
$self->{'promotion'} = $p; |
|
199
|
|
|
|
|
|
|
} |
|
200
|
0
|
|
|
|
|
0
|
return bless $self, $class; |
|
201
|
|
|
|
|
|
|
} |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub cmp ( $$ ) { |
|
204
|
0
|
|
|
0
|
|
0
|
my ($a,$b) = @_; |
|
205
|
0
|
0
|
|
|
|
0
|
UNIVERSAL::isa($b, 'Games::Chess::Move') |
|
206
|
|
|
|
|
|
|
or return error("Argument to 'cmp' must be of class Games::Chess::Move"); |
|
207
|
|
|
|
|
|
|
return ($a->{'from'}[0] <=> $b->{'from'}[0] |
|
208
|
|
|
|
|
|
|
or $a->{'from'}[1] <=> $b->{'from'}[1] |
|
209
|
|
|
|
|
|
|
or $a->{'to'}[0] <=> $b->{'to'}[0] |
|
210
|
|
|
|
|
|
|
or $a->{'to'}[1] <=> $b->{'to'}[1] |
|
211
|
0
|
|
0
|
|
|
0
|
or do { |
|
212
|
|
|
|
|
|
|
my $ap = $a->{'promotion'}; |
|
213
|
|
|
|
|
|
|
my $bp = $b->{'promotion'}; |
|
214
|
|
|
|
|
|
|
defined $ap ? (defined $bp ? $$ap <=> $$bp : -1) : 1 |
|
215
|
|
|
|
|
|
|
}); |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub from ( $ ) { |
|
219
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
|
220
|
0
|
|
|
|
|
0
|
return @{$self->{'from'}}; |
|
|
0
|
|
|
|
|
0
|
|
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub to ( $ ) { |
|
224
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
|
225
|
0
|
|
|
|
|
0
|
return @{$self->{'to'}}; |
|
|
0
|
|
|
|
|
0
|
|
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
sub promotion ( $ ) { |
|
229
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
|
230
|
0
|
|
|
|
|
0
|
return @{$self->{'promotion'}}; |
|
|
0
|
|
|
|
|
0
|
|
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
234
|
|
|
|
|
|
|
# Games::Chess::Position - representation of a chess position |
|
235
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
package Games::Chess::Position; |
|
238
|
4
|
|
|
4
|
|
22
|
use strict; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
121
|
|
|
239
|
4
|
|
|
4
|
|
18
|
use vars '%gifs'; |
|
|
4
|
|
|
|
|
15
|
|
|
|
4
|
|
|
|
|
16161
|
|
|
240
|
|
|
|
|
|
|
Games::Chess->import(qw(:constants :functions error)); |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
my $init_pos = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub new { |
|
245
|
25
|
|
|
25
|
|
3359
|
my ($class,$val) = @_; |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Passed another Position object? Return a copy. |
|
248
|
25
|
50
|
66
|
|
|
294
|
if (defined $val and UNIVERSAL::isa($val,'Games::Chess::Position')) { |
|
249
|
0
|
|
|
|
|
0
|
return bless { %$val }, $class; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# We've been passed a board position in Forsythe-Edwards Notation (FEN). |
|
253
|
25
|
|
|
|
|
48
|
my $self = { }; |
|
254
|
25
|
100
|
|
|
|
72
|
$val = $init_pos unless $val; |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# Split the FEN string into fields. |
|
257
|
25
|
|
|
|
|
143
|
my @fields = split ' ', $val; |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# First element is board description: split into into ranks. |
|
260
|
25
|
|
|
|
|
155
|
my @ranks = split '/', $fields[0]; |
|
261
|
25
|
50
|
|
|
|
82
|
@ranks == 8 or |
|
262
|
|
|
|
|
|
|
return error("Position '$fields[0]' does not have 8 ranks"); |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# Turn each rank into an array of 8 piece codes. |
|
265
|
25
|
|
|
|
|
59
|
foreach my $r (0 .. 7) { |
|
266
|
200
|
|
|
|
|
290
|
my $rank = $ranks[$r]; |
|
267
|
200
|
|
|
|
|
666
|
$rank =~ s/(\d)/' ' x $1/eg; |
|
|
208
|
|
|
|
|
670
|
|
|
268
|
200
|
50
|
|
|
|
442
|
length $rank == 8 |
|
269
|
|
|
|
|
|
|
or return error("Rank $r '$rank' does not have 8 squares"); |
|
270
|
200
|
|
|
|
|
516
|
$ranks[$r] = [ map { $PIECE_CODES{$_} } split '', $rank ]; |
|
|
1600
|
|
|
|
|
2516
|
|
|
271
|
200
|
50
|
|
|
|
378
|
@{$ranks[$r]} == 8 |
|
|
200
|
|
|
|
|
646
|
|
|
272
|
|
|
|
|
|
|
or return error("Rank $r '$rank' contains an invalid piece code"); |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# Transform the 2-d array and assemble into the board. |
|
276
|
25
|
|
|
|
|
82
|
$self->{'board'} = pack('C64', map { $ranks[7-$_%8][int($_/8)] } 0 .. 63); |
|
|
1600
|
|
|
|
|
3059
|
|
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Active color (defaults to white). |
|
279
|
25
|
50
|
|
|
|
102
|
$fields[1] = 'w' unless defined $fields[1]; |
|
280
|
25
|
100
|
|
|
|
75
|
if ($fields[1] eq 'w') { |
|
|
|
50
|
|
|
|
|
|
|
281
|
21
|
|
|
|
|
84
|
$self->{'player_to_move'} = &WHITE; |
|
282
|
|
|
|
|
|
|
} elsif ($fields[1] eq 'b') { |
|
283
|
4
|
|
|
|
|
17
|
$self->{'player_to_move'} = &BLACK; |
|
284
|
|
|
|
|
|
|
} else { |
|
285
|
0
|
|
|
|
|
0
|
return error("Invalid player to move: '$fields[1]'"); |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Castling availability (defaults to none). |
|
289
|
25
|
50
|
|
|
|
66
|
$fields[2] = '-' unless defined $fields[2]; |
|
290
|
25
|
100
|
|
|
|
62
|
unless ($fields[2] eq '-') { |
|
291
|
13
|
50
|
|
|
|
113
|
(join '', sort split '', $fields[2]) eq $fields[2] |
|
292
|
|
|
|
|
|
|
or return error("Castling availability '$fields[2]' not sorted"); |
|
293
|
13
|
|
|
|
|
52
|
foreach (split '', $fields[2]) { |
|
294
|
38
|
50
|
|
|
|
123
|
/^[KQkq]$/ or return error("Castling availability '$_' not KQkq"); |
|
295
|
38
|
|
|
|
|
169
|
$self->{'can_castle'}{$_} = 1; |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
} |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# En passant target square (default none). |
|
300
|
25
|
50
|
|
|
|
82
|
$fields[3] = '-' unless defined $fields[3]; |
|
301
|
25
|
100
|
|
|
|
69
|
unless ($fields[3] eq '-') { |
|
302
|
2
|
|
|
|
|
12
|
my @square = algebraic_to_xy($fields[3]); |
|
303
|
2
|
50
|
|
|
|
9
|
return unless @square == 2; |
|
304
|
2
|
|
|
|
|
8
|
$self->{'en_passant'} = [@square]; |
|
305
|
|
|
|
|
|
|
} |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
# Half-move clock (default 0). |
|
308
|
25
|
50
|
|
|
|
55
|
$fields[4] = '0' unless defined $fields[4]; |
|
309
|
25
|
50
|
|
|
|
67
|
return unless halfmove_count_valid($fields[4]); |
|
310
|
25
|
|
|
|
|
74
|
$self->{'halfmove'} = $fields[4]; |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# Fullmove number (default 1). |
|
313
|
25
|
50
|
|
|
|
73
|
$fields[5] = '1' unless defined $fields[5]; |
|
314
|
25
|
50
|
|
|
|
82
|
return unless move_number_valid($fields[5]); |
|
315
|
25
|
|
|
|
|
70
|
$self->{'move'} = $fields[5]; |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# All done. |
|
318
|
25
|
|
|
|
|
200
|
return bless $self, $class; |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub at { |
|
322
|
2748
|
|
|
2748
|
|
6550
|
my ($self,$x,$y,@piece) = @_; |
|
323
|
2748
|
50
|
|
|
|
4379
|
return unless xy_valid($x,$y); |
|
324
|
2748
|
100
|
|
|
|
11582
|
return Games::Chess::Piece->new(vec($self->{'board'}, 8 * $x + $y, 8)) |
|
325
|
|
|
|
|
|
|
unless @piece; |
|
326
|
120
|
|
|
|
|
317
|
my $p = Games::Chess::Piece->new(@piece); |
|
327
|
120
|
50
|
|
|
|
256
|
return unless defined $p; |
|
328
|
120
|
|
|
|
|
416
|
vec($self->{'board'}, 8 * $x + $y, 8) = ord $$p; |
|
329
|
120
|
|
|
|
|
406
|
return 1; |
|
330
|
|
|
|
|
|
|
} |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub board ( $ ) { |
|
333
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
|
334
|
0
|
|
|
|
|
0
|
return $self->{'board'}; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub can_castle { |
|
338
|
116
|
|
|
116
|
|
609
|
my ($self,$colour,$piece,$can_castle) = @_; |
|
339
|
116
|
|
|
|
|
295
|
my $p = Games::Chess::Piece->new($colour,$piece); |
|
340
|
116
|
50
|
|
|
|
257
|
return unless defined $p; |
|
341
|
116
|
|
|
|
|
237
|
my $code = $p->code; |
|
342
|
116
|
50
|
|
|
|
379
|
$code =~ /^[KQkq]$/ or return |
|
343
|
|
|
|
|
|
|
error("can_castle($colour,$piece) invalid: must be king or queen"); |
|
344
|
116
|
100
|
|
|
|
492
|
return defined $self->{'can_castle'}{$code} unless defined $can_castle; |
|
345
|
32
|
100
|
|
|
|
57
|
if ($can_castle) { |
|
346
|
16
|
|
|
|
|
35
|
$self->{'can_castle'}{$code} = 1; |
|
347
|
|
|
|
|
|
|
} else { |
|
348
|
16
|
|
|
|
|
38
|
delete $self->{'can_castle'}{$code}; |
|
349
|
|
|
|
|
|
|
} |
|
350
|
32
|
|
|
|
|
79
|
return 1; |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
sub clear ( $$$ ) { |
|
354
|
120
|
|
|
120
|
|
1126
|
my ($self,$x,$y) = @_; |
|
355
|
120
|
50
|
|
|
|
215
|
return unless xy_valid($x,$y); |
|
356
|
120
|
|
|
|
|
445
|
vec($self->{'board'}, 8 * $x + $y, 8) = 0; |
|
357
|
120
|
|
|
|
|
328
|
return 1; |
|
358
|
|
|
|
|
|
|
} |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
sub en_passant { |
|
361
|
516
|
|
|
516
|
|
2719
|
my ($self,@en_passant) = @_; |
|
362
|
516
|
|
|
|
|
741
|
my $ep = $self->{'en_passant'}; |
|
363
|
516
|
100
|
|
|
|
1788
|
return defined $ep ? @$ep : () unless @en_passant; |
|
|
|
100
|
|
|
|
|
|
|
364
|
256
|
50
|
|
|
|
488
|
return unless xy_valid(@en_passant); |
|
365
|
256
|
|
|
|
|
711
|
$self->{'en_passant'} = [@en_passant]; |
|
366
|
256
|
|
|
|
|
639
|
return 1; |
|
367
|
|
|
|
|
|
|
} |
|
368
|
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub halfmove_clock { |
|
370
|
404
|
|
|
404
|
|
1514
|
my ($self,$halfmove) = @_; |
|
371
|
404
|
100
|
|
|
|
1032
|
return $self->{'halfmove'} unless defined $halfmove; |
|
372
|
200
|
50
|
|
|
|
308
|
return unless halfmove_count_valid($halfmove); |
|
373
|
200
|
|
|
|
|
392
|
$self->{'halfmove'} = $halfmove; |
|
374
|
200
|
|
|
|
|
345
|
return 1; |
|
375
|
|
|
|
|
|
|
} |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
sub move_number { |
|
378
|
804
|
|
|
804
|
|
2860
|
my ($self,$move) = @_; |
|
379
|
804
|
100
|
|
|
|
2063
|
return $self->{'move'} unless defined $move; |
|
380
|
400
|
50
|
|
|
|
595
|
return unless move_number_valid($move); |
|
381
|
400
|
|
|
|
|
585
|
$self->{'move'} = $move; |
|
382
|
400
|
|
|
|
|
645
|
return 1; |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
sub player_to_move { |
|
386
|
20
|
|
|
20
|
|
105
|
my ($self,$colour) = @_; |
|
387
|
20
|
100
|
|
|
|
61
|
return $self->{'player_to_move'} unless defined $colour; |
|
388
|
8
|
50
|
|
|
|
18
|
return unless colour_valid($colour); |
|
389
|
8
|
|
|
|
|
13
|
$self->{'player_to_move'} = $colour; |
|
390
|
8
|
|
|
|
|
17
|
return 1; |
|
391
|
|
|
|
|
|
|
} |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
my @CASTLE_TESTS = |
|
394
|
|
|
|
|
|
|
( |
|
395
|
|
|
|
|
|
|
[ &WHITE, &KING, { 'e1' => 'K', 'h1' => 'R' } ], |
|
396
|
|
|
|
|
|
|
[ &WHITE, &QUEEN, { 'e1' => 'K', 'a1' => 'R' } ], |
|
397
|
|
|
|
|
|
|
[ &BLACK, &KING, { 'e8' => 'k', 'h8' => 'r' } ], |
|
398
|
|
|
|
|
|
|
[ &BLACK, &QUEEN, { 'e8' => 'k', 'a8' => 'r' } ], |
|
399
|
|
|
|
|
|
|
); |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
sub validate ( $ ) { |
|
402
|
19
|
|
|
19
|
|
177
|
my ($self) = @_; |
|
403
|
19
|
|
|
|
|
21
|
my (%n,%m); |
|
404
|
19
|
|
|
|
|
198
|
@n{split '', $PIECE_CODES} = (0) x 13; |
|
405
|
19
|
|
|
|
|
151
|
@m{split '', $PIECE_CODES} = (0) x 13; |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
# Count the number of each type of piece. |
|
408
|
19
|
|
|
|
|
62
|
foreach my $x (0 .. 7) { |
|
409
|
152
|
|
|
|
|
250
|
foreach my $y (0 .. 7) { |
|
410
|
1216
|
|
|
|
|
3017
|
++$n{$self->at($x,$y)->code}; |
|
411
|
|
|
|
|
|
|
} |
|
412
|
152
|
|
|
|
|
348
|
++$m{$self->at($x,0)->code}; |
|
413
|
152
|
|
|
|
|
444
|
++$m{$self->at($x,7)->code}; |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# More than 8 pawns per side? |
|
417
|
19
|
100
|
|
|
|
83
|
$n{p} <= 8 or return error("Black has $n{p} pawns"); |
|
418
|
18
|
100
|
|
|
|
56
|
$n{P} <= 8 or return error("White has $n{P} pawns"); |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# Pawn + promoted piece count plausible? |
|
421
|
17
|
100
|
|
|
|
148
|
($n{'p'} + (2<$n{'n'} ? $n{'n'}-2 : 0) + (2<$n{'b'} ? $n{'b'}-2 : 0) |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
+ (2<$n{'r'} ? $n{'r'}-2 : 0) + (1<$n{'q'} ? $n{'q'}-1 : 0) <= 8) |
|
423
|
|
|
|
|
|
|
or return error("Black has more than 8 pawns plus promoted pieces"); |
|
424
|
16
|
100
|
|
|
|
216
|
($n{'P'} + (2<$n{'N'} ? $n{'N'}-2 : 0) + (2<$n{'B'} ? $n{'B'}-2 : 0) |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
+ (2<$n{'R'} ? $n{'R'}-2 : 0) + (1<$n{'Q'} ? $n{'Q'}-1 : 0) <= 8) |
|
426
|
|
|
|
|
|
|
or return error("White has more than 8 pawns plus promoted pieces"); |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# Not exactly 1 king per side? |
|
429
|
15
|
100
|
|
|
|
43
|
$n{'k'} == 1 or return error("Black has $n{'k'} kings"); |
|
430
|
14
|
100
|
|
|
|
41
|
$n{'K'} == 1 or return error("White has $n{'K'} kings"); |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# Pawns on ranks 1 or 8? |
|
433
|
13
|
100
|
|
|
|
37
|
$m{'p'} == 0 or return error("Black has a pawn on rank 1 or rank 8"); |
|
434
|
11
|
100
|
|
|
|
33
|
$m{'P'} == 0 or return error("White has a pawn on rank 1 or rank 8"); |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
# Impossible en passant target square? |
|
437
|
9
|
|
|
|
|
21
|
my $ep = $self->{'en_passant'}; |
|
438
|
9
|
50
|
|
|
|
33
|
if ($ep) { |
|
439
|
0
|
0
|
|
|
|
0
|
if ($self->{'player_to_move'} == &WHITE) { |
|
440
|
0
|
0
|
|
|
|
0
|
$ep->[1] == 5 or return |
|
441
|
|
|
|
|
|
|
error("White to move but EP square is @$ep"); |
|
442
|
0
|
0
|
|
|
|
0
|
$self->at($ep->[0],6)->code == ' ' or return |
|
443
|
|
|
|
|
|
|
error("EP square is @$ep but rank 7 is not empty"); |
|
444
|
0
|
0
|
|
|
|
0
|
$self->at($ep->[0],5)->code == ' ' or return |
|
445
|
|
|
|
|
|
|
error("EP square is @$ep but is not empty"); |
|
446
|
0
|
0
|
|
|
|
0
|
$self->at($ep->[0],4)->code == 'p' or return |
|
447
|
|
|
|
|
|
|
error("EP square is @$ep but rank 5 does not contain a black pawn"); |
|
448
|
|
|
|
|
|
|
} else { |
|
449
|
0
|
0
|
|
|
|
0
|
$ep->[1] == 2 or return |
|
450
|
|
|
|
|
|
|
error("Black to move but EP square is @$ep"); |
|
451
|
0
|
0
|
|
|
|
0
|
$self->at($ep->[0],1)->code == ' ' or return |
|
452
|
|
|
|
|
|
|
error("EP square is @$ep but rank 2 is not empty"); |
|
453
|
0
|
0
|
|
|
|
0
|
$self->at($ep->[0],2)->code == ' ' or return |
|
454
|
|
|
|
|
|
|
error("EP square is @$ep but is not empty"); |
|
455
|
0
|
0
|
|
|
|
0
|
$self->at($ep->[0],3)->code == 'P' or return |
|
456
|
|
|
|
|
|
|
error("EP square is @$ep but rank 4 does not contain a white pawn"); |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
} |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# Castling availability inconsistent with position? |
|
461
|
9
|
|
|
|
|
27
|
foreach my $c (@CASTLE_TESTS) { |
|
462
|
36
|
|
|
|
|
108
|
my $p = Games::Chess::Piece->new($c->[0], $c->[1]); |
|
463
|
36
|
100
|
|
|
|
114
|
if ($self->can_castle($c->[0], $c->[1])) { |
|
464
|
18
|
|
|
|
|
27
|
foreach my $sq (keys %{$c->[2]}) { |
|
|
18
|
|
|
|
|
67
|
|
|
465
|
36
|
|
|
|
|
80
|
my $colour = $p->colour_name; |
|
466
|
36
|
|
|
|
|
88
|
my $side = $p->piece_name; |
|
467
|
36
|
|
|
|
|
66
|
my $required = $c->[2]{$sq}; |
|
468
|
36
|
|
|
|
|
115
|
my $req_name = Games::Chess::Piece->new($required)->piece_name; |
|
469
|
36
|
50
|
|
|
|
92
|
$self->at(algebraic_to_xy($sq))->code eq $required or return |
|
470
|
|
|
|
|
|
|
error("$colour can castle ${side}side but no $req_name on $sq"); |
|
471
|
|
|
|
|
|
|
} |
|
472
|
|
|
|
|
|
|
} |
|
473
|
|
|
|
|
|
|
} |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# Check halfmove count and move number. |
|
476
|
9
|
|
|
|
|
24
|
my $h = $self->{'halfmove'}; |
|
477
|
9
|
50
|
|
|
|
38
|
0 <= $h or return error("Negative halfmove count $h"); |
|
478
|
9
|
50
|
|
|
|
29
|
$h == int $h or return error("Non-integer halfmove count $h"); |
|
479
|
9
|
50
|
|
|
|
24
|
$h <= 50 or return error("Halfmove count $h > 50: game should have drawn"); |
|
480
|
9
|
|
|
|
|
54
|
my $m = $self->{'move'}; |
|
481
|
9
|
50
|
|
|
|
25
|
1 <= $m or return error("Move number $m not positive"); |
|
482
|
9
|
50
|
|
|
|
19
|
$m == int $m or return error("Non-integer move count $m"); |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
# Everything checks out OK. |
|
485
|
9
|
|
|
|
|
63
|
return 1; |
|
486
|
|
|
|
|
|
|
} |
|
487
|
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
489
|
|
|
|
|
|
|
# Output Games::Chess::Position in varying formats. |
|
490
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
491
|
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
sub to_FEN ( $ ) { |
|
493
|
5
|
|
|
5
|
|
44
|
my ($self) = @_; |
|
494
|
40
|
|
|
|
|
45
|
my $position = join '/', map { |
|
495
|
5
|
|
|
|
|
18
|
my $y = $_; |
|
496
|
40
|
|
|
|
|
59
|
my $rank = join '', map { $self->at($_,$y)->code } 0 .. 7; |
|
|
320
|
|
|
|
|
592
|
|
|
497
|
40
|
|
|
|
|
211
|
$rank =~ s/( +)/length $1/eg; |
|
|
48
|
|
|
|
|
119
|
|
|
498
|
40
|
|
|
|
|
105
|
$rank; |
|
499
|
|
|
|
|
|
|
} reverse 0 .. 7; |
|
500
|
|
|
|
|
|
|
return join ' ', |
|
501
|
|
|
|
|
|
|
( $position, |
|
502
|
|
|
|
|
|
|
( $self->{'player_to_move'} == &BLACK ? 'b' : 'w'), |
|
503
|
0
|
|
|
|
|
0
|
( join '', sort keys %{$self->{'can_castle'}} or '-' ), |
|
504
|
|
|
|
|
|
|
( defined $self->{'en_passant'} |
|
505
|
5
|
100
|
100
|
|
|
29
|
? xy_to_algebraic(@{$self->{'en_passant'}}) : '-' ), |
|
|
|
50
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
$self->{'halfmove'}, |
|
507
|
|
|
|
|
|
|
$self->{'move'} ); |
|
508
|
|
|
|
|
|
|
} |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
sub to_text ( $ ) { |
|
511
|
4
|
|
|
4
|
|
11
|
my ($self) = @_; |
|
512
|
32
|
|
|
|
|
43
|
join "\n", map { |
|
513
|
4
|
|
|
|
|
11
|
my $y = $_; |
|
514
|
256
|
|
|
|
|
469
|
join ' ', map { |
|
515
|
32
|
|
|
|
|
58
|
my $sq = $self->at($_,$y)->code; |
|
516
|
256
|
100
|
100
|
|
|
1109
|
$sq = '.' if $sq eq ' ' and ($y + $_) % 2 == 0; |
|
517
|
256
|
|
|
|
|
622
|
$sq; |
|
518
|
|
|
|
|
|
|
} 0 .. 7; |
|
519
|
|
|
|
|
|
|
} reverse 0 .. 7; |
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
# Width and height of the GIF images for the pieces. |
|
523
|
|
|
|
|
|
|
my ($width,$height) = (33,33); |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
sub to_GIF ( $ ) { |
|
526
|
1
|
|
|
1
|
|
5
|
my ($self) = shift; |
|
527
|
1
|
|
|
|
|
573
|
require GD; |
|
528
|
0
|
|
|
|
|
|
my %opts = ( lmargin => 20, bmargin => 20, border => 2, |
|
529
|
|
|
|
|
|
|
font => GD::Font->Giant, letters => 1, @_ ); |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
# Check options. |
|
532
|
0
|
0
|
|
|
|
|
$opts{lmargin} = $opts{bmargin} = 0 unless $opts{letters}; |
|
533
|
0
|
|
|
|
|
|
foreach (qw(lmargin bmargin border)) { |
|
534
|
0
|
0
|
|
|
|
|
0 <= $opts{$_} or return error("Option $_ $opts{$_} must be >= 0."); |
|
535
|
|
|
|
|
|
|
} |
|
536
|
0
|
0
|
|
|
|
|
UNIVERSAL::isa($opts{font}, 'GD::Font') |
|
537
|
|
|
|
|
|
|
or return error("$opts{font} does not belong to the GD::Font class."); |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
# Image parameters: |
|
540
|
|
|
|
|
|
|
# $iwidth Total image width |
|
541
|
|
|
|
|
|
|
# $iheight Total image height |
|
542
|
0
|
|
|
|
|
|
my ($iwidth, $iheight) = ($opts{lmargin} + 8 * $width + 2 * $opts{border}, |
|
543
|
|
|
|
|
|
|
8 * $height + $opts{bmargin} + 2 * $opts{border}); |
|
544
|
0
|
|
|
|
|
|
my $img = GD::Image->new($iwidth, $iheight); |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
# Colours: |
|
547
|
|
|
|
|
|
|
# $white White squares on the chess board |
|
548
|
|
|
|
|
|
|
# $grey Black squares on the chess board |
|
549
|
|
|
|
|
|
|
# $black The border and the lettering |
|
550
|
|
|
|
|
|
|
# $transparent The margins |
|
551
|
0
|
|
|
|
|
|
my $white = $img->colorAllocate(255,255,255); |
|
552
|
0
|
|
|
|
|
|
my $grey = $img->colorAllocate(191,191,191); |
|
553
|
0
|
|
|
|
|
|
my $black = $img->colorAllocate(0,0,0); |
|
554
|
0
|
|
|
|
|
|
my $transparent = $img->colorAllocate(255,192,192); |
|
555
|
0
|
|
|
|
|
|
$img->transparent($transparent); |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
# Colour the board and the margins; draw a border round the board. |
|
558
|
0
|
|
|
|
|
|
$img->filledRectangle(0, 0, $iwidth-1, $iheight-1, $transparent); |
|
559
|
0
|
|
|
|
|
|
$img->filledRectangle($opts{lmargin}, 0, $iwidth-1, |
|
560
|
|
|
|
|
|
|
$iheight-1-$opts{bmargin}, $white); |
|
561
|
0
|
|
|
|
|
|
for (my $i = 0; $i < $opts{border}; ++$i) { |
|
562
|
0
|
|
|
|
|
|
$img->rectangle($opts{lmargin} + $i, $i, $iwidth - 1 - $i, |
|
563
|
|
|
|
|
|
|
$iheight - 1 - $opts{bmargin} - $i, $black); |
|
564
|
|
|
|
|
|
|
} |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
# Draw the file letters a-h and the rank numbers 1-8. |
|
567
|
0
|
0
|
|
|
|
|
if ($opts{letters}) { |
|
568
|
0
|
|
|
|
|
|
my ($fw,$fh) = ($opts{font}->width, $opts{font}->height); |
|
569
|
0
|
|
|
|
|
|
foreach my $n (0 .. 7) { |
|
570
|
0
|
|
|
|
|
|
$img->string($opts{font}, ($opts{lmargin} - $fw) / 2, |
|
571
|
|
|
|
|
|
|
$opts{border} + $n * $height + ($height - $fh) / 2, |
|
572
|
|
|
|
|
|
|
8 - $n, $black); |
|
573
|
0
|
|
|
|
|
|
$img->string($opts{font}, |
|
574
|
|
|
|
|
|
|
$opts{lmargin} + $opts{border} + $n*$width + ($width-$fw)/2, |
|
575
|
|
|
|
|
|
|
$iheight - $opts{bmargin} + ($opts{bmargin}-$fh)/2, |
|
576
|
|
|
|
|
|
|
chr(ord('a')+$n), $black); |
|
577
|
|
|
|
|
|
|
} |
|
578
|
|
|
|
|
|
|
} |
|
579
|
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
# Draw the backgrounds to the black squares and draw the pieces. |
|
581
|
0
|
|
|
|
|
|
my $gifs = piece_gifs(); |
|
582
|
0
|
|
|
|
|
|
foreach my $x (0 .. 7) { |
|
583
|
0
|
|
|
|
|
|
foreach my $y (0 .. 7) { |
|
584
|
0
|
|
|
|
|
|
my ($left,$top) = ($opts{lmargin} + $opts{border} + $x * $width, |
|
585
|
|
|
|
|
|
|
(7 - $y) * $height + $opts{border}); |
|
586
|
0
|
0
|
|
|
|
|
$img->filledRectangle($left,$top,$left+$width-1,$top+$height-1,$grey) |
|
587
|
|
|
|
|
|
|
if ($x + $y) % 2 == 0; |
|
588
|
0
|
|
|
|
|
|
my $c = $self->at($x,$y)->code; |
|
589
|
0
|
0
|
|
|
|
|
next if $c eq ' '; |
|
590
|
0
|
|
|
|
|
|
$img->copy($gifs->{$c}, $left, $top, 0, 0, $width, $height); |
|
591
|
|
|
|
|
|
|
} |
|
592
|
|
|
|
|
|
|
} |
|
593
|
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# Convert image to GIF and return. |
|
595
|
0
|
|
|
|
|
|
return $img->gif; |
|
596
|
|
|
|
|
|
|
} |
|
597
|
|
|
|
|
|
|
|
|
598
|
4
|
|
|
4
|
|
33
|
use vars '%gifs'; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
1895
|
|
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
my %piece_images = |
|
601
|
|
|
|
|
|
|
( 'p' => '5555555555555555555555555555555555555555ff75555555555555fff7555555555555dfff5555555555555fff7555555555555dfff5555555555555fff755555555555ffffff7555555555fffffff755555555ffffffff75555555fffffffff7555555dfffffffff5555555dffffffff5555555555dfff5555555555555fff7555555555555dfff555555555555dffff555555555555ffff755555555555dffff555555555555ffff755555555555dffff555555555555ffff755555555555dffff555555555555ffff75555555555dffffff555555555ffffffff75555555fffffffff7555555dfffffffff555555dffffffffff555555ffffffffff75555555555555555555555555555555555510', |
|
602
|
|
|
|
|
|
|
'n' => '5555555555555555555555f755555555555557df5555555555555dffff755555555555ffefff7555555555dfffffff555555555fffffaff55555555dfffffaef5555555dffffffaef5555555fffffffaef555555ffffffffaef55555dffffffffaef5555dffaffffffaf75555ffbefffffbaf7555ffffffffffbef555fffffffffffaef55ffffffffffffae75ffffffffffffbaf5dfffbffffffffbe75fbebfffffffffae5dffbf75dffffffaf5ff7f75dffffffbe75ffd75dfffffffaf555555dffffffffe755555fffffffffbf55555ffffffffffe75555dfffffffffbf5555dfffffffffff75555ffffffffffff5555dfffffffffff55555fffffffffff755555dfffffffff55555555555555555510', |
|
603
|
|
|
|
|
|
|
'b' => '55555555555555555555555f55f555555555555ff5ff55555555555df7df755555555555ff5ff55555555555dffff755555555555fffbf75555555555ffffaf7555555555ffffbaf555555555fffffbaf55555555dfffffbe75555555dffffffbe75555555fffffffaf5555555ffffffffa7555555dffffffffe5555555ffffffffb7555555dffffffffe5555555ffffffffb75555555fffffffff5555555dffffffff75555555ffffffff755555555ffaaaef755555555daaffbae555555555dffffff555555555dfffffff555555555ffaaaef755555555daaffbae555555fffeffffffeff755ffffffffffffff75fffffffffffffff7dfffffffffffffff5dfffff755ffffff5555555555555555510', |
|
604
|
|
|
|
|
|
|
'r' => '55555555555555555555555555555555555fff55df55dff7555dfff5dff5dfff5555ffffffffffff7555dffffffffffff5555ffffffffffff7555dffffffffffff55555ffffbaffff7555555dffaaaeff55555555fbaefaaf755555555beffffa755555555dfffffff555555555fffffff755555555dfffffff555555555fffffff755555555dfffffff555555555fffffff755555555dfffffff555555555fffffff755555555dfffffff555555555fffaeff755555555dfbaaaff55555555dfaafbaef5555555dbaffffbaf555555dffffffffff55555dfffffffffff5555dffffffffffff555dfffffffffffff55dffffffffffffff55ffffffffffffff75dffffffffffffff5555555555555555510', |
|
605
|
|
|
|
|
|
|
'q' => '555555555555555555555f75555f755555555ff7555ff75555555dff555dff55555555ff7555ff755555555f75555f755555555df5555df555555555f75555f755555555dff55dff555555555ff755ff75555f755dfffdfff555fff755fff7fff755ffff55dfffffff55dfff75fffffffff5dffdfffffffffffffff5dffffffffffffff55dfffffffffffff555ffffffaefffff755dfffbaaaaaffff555dfaaafffbaaef5555faffffffffbe75555fffffbfffff75555dffffbafffff55555fffffbfffff75555dfffffffffff55555fffffffffff75555dfffbaaaffff55555dfaaeffaaef555555faffffffbe755555dffffffffff555555dfffffffff55555555fffffff75555555555555555555510', |
|
606
|
|
|
|
|
|
|
'k' => '555555dfff555555555555dffff555555555555fabe755555555555dbeaf555555555555fbaf755555555555dbeaf555555555555fabe7555555dfff7dffff5ffff5dfffffffffffffffdffbafffbfffbafffffbeaeffeffaeeffffbefbeffffaffaffffaffaefffaefbefffbeffbfffbfffaffffbeffefffeffafffffbefbeffbffaffffffbffbffbefbfff7fffaffeffefbeff7dffbefbaaaffafff5dffbaaafbaaafff55dfbaefffffaaff555dfefffbffffef5555dffffbafffff55555dffffbfffff555555ffffffffff755555dffffffffff555555ffffbaffff755555dffbaaaafff555555faaaffbaae755555dffffffffff555555dfffffffff5555555dffffffff55555555dfffffff555510', |
|
607
|
|
|
|
|
|
|
'P' => '5555555555555555555555555555555555555555ff75555555555555fff7555555555555dbaf5555555555555fae7555555555555dbaf5555555555555fbf755555555555fffeff7555555555ffbaaff755555555fbaaaaaf75555555faaaaaaae7555555dfffffffff55555555ffffffff5555555555dfef5555555555555fbf7555555555555dbaf555555555555dfaef555555555555faae755555555555dbaaf555555555555faae755555555555dbaaf555555555555faae755555555555dfaef555555555555fbaf75555555555dffaeff555555555fffaaeff75555555ffaaaaaef7555555dbaaaaaaaf555555dffffffffff555555ffffffffff75555555555555555555555555555555555510', |
|
608
|
|
|
|
|
|
|
'N' => '5555555555555555555555f755555555555555df55555555555555dfff755555555555dfffff75555555555ffaffff555555555ffbaaeff55555555dfbaaaaff5555555dfaaaaaaef5555555fbaaaaaaef555555fbaaaaaaaef55555dbefaaaaaaf75555dfafbaaaaaaf75555faefaaaaaaef5555faaaaaaaaaaef555fbaaaaaaaaaae755fbaaaaaaaaaaaf75dbaaaaaebaaaaef5dbaaaffffaaaaae75feaaffffaaaaaaf5dbabf75dbaaaaaef5ffff75dfaaaaaaf75fff55dfaaaaaaaf555555dfaaaaaaae755555fbaaaaaaaef55555fbaaaaaaaaf75555dbaaaaaaaaef5555dfaaaaaaaaaf75555fbaaaaaaaaef5555dbaaffffbaaf55555fffffffffff755555dfffffffff55555555555555555510', |
|
609
|
|
|
|
|
|
|
'B' => '55555555555555555555555f55f555555555555ff5ff55555555555df7df755555555555ff5ff55555555555dffff755555555555fefff75555555555faefaf7555555555dbafbaf555555555fbaaebef55555555dbaaafbe75555555dfaaaafaf75555555faaaaafaf5555555fbaaaaefe7555555dbaaaaaebf5555555faaaaaaff7555555dbaaaaaaff5555555fbaaaaaef75555555faaaaaaff5555555dbaaaaaef75555555ffffffff755555555fffffff755555555dfaabaef555555555fbafbaf755555555dfaabaef555555555fffffff755555555dfffffff55555dfffffaaaefffff5dfffffefffefffff5baaaaeffffaaaaa7dfffffffffffffff5dfffff755ffffff5555555555555555510', |
|
610
|
|
|
|
|
|
|
'R' => '55555555555555555555555555555555555fff55df55dff7555dfff5dff5dfff5555faeffbafffae7555dbaafbaafbaaf5555faaaaaaaaaae7555dfbaaaaaaaaff55555fbaaefaaaf7555555dbafffbaf55555555fefbaffe755555555ffaaaef755555555dfaaaaef555555555faaaaae755555555dbaaaaaf555555555faaaaae755555555dbaaaaaf555555555faaaaae755555555dbaaaaaf555555555faaaaae755555555dbaefaaf555555555fafffbe755555555dffbafff55555555dffaaaeff5555555dfaaaaaaef555555dfaaaaaaaef55555dfaaaaaaaaef5555dfaaaaaaaaaef555dfaaaaaaaaaaef55dfaaaaaaaaaaaef55ffffffffffffff75dffffffffffffff5555555555555555510', |
|
611
|
|
|
|
|
|
|
'Q' => '555555555555555555555f75555f755555555ff7555ff75555555dbf555dbf55555555ff7555ff755555555f75555f755555555df5555df555555555f75555f755555555dff55dff555555555fe755fe75555f755dbffdfbf555fff755faf7fbe755ffbf55dbaffbaf55dbfff5dbaeffaaf5dffdffffaaefaaeffff5dfffbaaeaaaffff55dbfbaaaaaaaefe555fbaaaaaaaaaae755dfaaffffffbaef555dffffffffffff5555fffaaaaaaeff75555faaaaeaaaae75555dbaaaefaaaaf55555faaaaeaaaae75555dbaaaaaaaaaf55555faafffffbae75555dfffffffffff55555dffbaaaafff555555fbaaaaaaaf755555dffbaaaafff555555dfffffffff55555555fffffff75555555555555555555510', |
|
612
|
|
|
|
|
|
|
'K' => '555555dfff555555555555dffff555555555555ffff755555555555dfaef555555555555ffff755555555555dbfbf555555555555fefe7555555dfff7dffff5ffff5dffffffbafffffffdfbaafffffffbaafffbaaaefffffaaaaffbaaaaebaafaaaaaffaaaaaebafaaaaaefbaaaaafaebaaaaaffaaaaaababaaaaaefbaaaaaebfaaaaaaffbaaaaafebaaaaae7fbaaaaabbaaaaae7dfaaaaaefaaaaaef5dfaaeffffffaaaf55dfffffffffffff555dffbaaaaaafff5555dfaaaabaaaef55555dbaaafbaaaf555555faaaabaaae755555dbaaaaaaaaf555555faefffffbe755555dffffffffff555555fffaaaaaff755555dfaaaaaaaaf555555dfbaaaaaef5555555dffffffff55555555dfffffff555510', |
|
613
|
|
|
|
|
|
|
); |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
sub piece_gifs () { |
|
616
|
0
|
0
|
|
0
|
|
|
unless (%gifs) { |
|
617
|
|
|
|
|
|
|
# Create GIF image files for the 12 pieces. |
|
618
|
0
|
|
|
|
|
|
foreach my $code (keys %PIECE_CODES) { |
|
619
|
0
|
0
|
|
|
|
|
next if $code eq ' '; |
|
620
|
0
|
|
|
|
|
|
$gifs{$code} = GD::Image->new($width,$height); |
|
621
|
0
|
|
|
|
|
|
my $white = $gifs{$code}->colorAllocate(255,255,255); |
|
622
|
0
|
|
|
|
|
|
my $black = $gifs{$code}->colorAllocate(0,0,0); |
|
623
|
0
|
|
|
|
|
|
my $transparent = $gifs{$code}->colorAllocate(0,255,0); |
|
624
|
0
|
|
|
|
|
|
$gifs{$code}->transparent($transparent); |
|
625
|
0
|
|
|
|
|
|
my $v = pack('h*', $piece_images{$code}); |
|
626
|
0
|
|
|
|
|
|
foreach my $x (0 .. $width-1) { |
|
627
|
0
|
|
|
|
|
|
foreach my $y (0 .. $width-1) { |
|
628
|
0
|
|
|
|
|
|
$gifs{$code}->setPixel($x,$y,($transparent,$white,$black) |
|
629
|
|
|
|
|
|
|
[vec($v, $y * 33 + $x, 2) - 1]); |
|
630
|
|
|
|
|
|
|
} |
|
631
|
|
|
|
|
|
|
} |
|
632
|
|
|
|
|
|
|
} |
|
633
|
|
|
|
|
|
|
} |
|
634
|
0
|
|
|
|
|
|
return \%gifs; |
|
635
|
|
|
|
|
|
|
} |
|
636
|
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
1; |