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; |