File Coverage

blib/lib/Chess/Board.pm
Criterion Covered Total %
statement 144 157 91.7
branch 58 84 69.0
condition 37 54 68.5
subroutine 24 24 100.0
pod 14 16 87.5
total 277 335 82.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Chess::Board - an object representation of a chessboard
4              
5             =head1 SYNOPSIS
6              
7             $light = Chess::Board->get_color('h1');
8             $dark = Chess::Board->get_color('a1');
9             $e3 = Chess::Board->square_down_from('e4');
10             $e5 = Chess::Board->square_up_from('e4');
11             $d4 = Chess::Board->square_left_of('e4');
12             $f4 = Chess::Board->square_right_of('e4');
13             $board = Chess::Board->new();
14             $is_valid = Chess::Board->square_is_valid($sq);
15             if ($is_valid) {
16             $board->set_piece_at($sq, $piece);
17             $clone = $board->clone();
18             $piece = $clone->get_piece_at($sq);
19             $clone->set_piece_at($sq, undef);
20             $clone->set_piece_at(Chess::Board->square_up_from($sq), $piece);
21             }
22              
23             =head1 DESCRIPTION
24              
25             The Chess module provides a framework for writing Chess programs with Perl.
26              
27             This class forms part of the framework, but it could also be used by
28             itself, even to put objects that aren't subclasses of L on it.
29              
30             =head1 METHODS
31              
32             =head2 Construction
33              
34             =over 4
35              
36             =item new()
37              
38             Takes no arguments. Returns a blessed Chess::Board object reference. This
39             reference can be used to call any of the methods listed in L.
40              
41             $board = Chess::Board->new();
42              
43             See also L to construct a new Chess::Board from an existing one.
44              
45             =back
46              
47             =head2 Class methods
48              
49             =over 4
50              
51             =item square_is_valid()
52              
53             Takes a single scalar parameter with the square to be tested. Returns true if
54             the given square falls within the range a1-h8. Returns false otherwise.
55             It is case-insensitive, though all functions that return squares will return
56             lower-case.
57              
58             if (Chess::Board->square_is_valid($sq)) {
59             # call method requiring valid square
60             }
61              
62             =item get_color_of()
63              
64             Takes a single scalar parameter containing the square whose color is requested.
65             Returns a scalar containing either of the strings 'light' or 'dark'. Returns
66             C and prints a warning to STDERR (see L) if the
67             square is not valid.
68              
69             $light = Chess::Board->get_color_of("h1");
70             $dark = Chess::Board->get_color_of("a1");
71              
72             =item square_left_of()
73              
74             Takes a single scalar parameter containing the square right of the requested
75             square. Returns a string containing the square left of the parameter. Returns
76             C and prints a warning to STDERR (see L) if the
77             square is not valid. Returns undef (but doesn't print a warning) if there is
78             no square left of the given square.
79              
80             $d4 = Chess::Board->square_left_of("e4");
81              
82             =item square_right_of()
83              
84             Takes a single scalar parameter containing the square left of the requested
85             square. Returns a string containing the square right of the parameter. Returns
86             C and prints a warning to STDERR (see L) if the
87             square is not valid. Returns undef (but doesn't print a warning) if there is
88             no square right of the given square.
89              
90             $f4 = Chess::Board->square_left_of("e4");
91              
92             =item square_up_from()
93              
94             Takes a single scalar parameter containing the square down from the requested
95             square. Returns a string containing the square up from the parameter. Returns
96             C and prints a warning to STDERR (see L) if the
97             square is not valid. Returns undef (but doesn't print a warning) if there is
98             no square up from the given square.
99              
100             $e5 = Chess::Board->square_up_from("e4");
101              
102             =item square_down_from()
103              
104             Takes a single scalar parameter containing the square up from the requested
105             square. Returns a string containing the square down from the parameter. Returns
106             C and prints a warning to STDERR (see L) if the
107             square is not valid. Returns undef (but doesn't print a warning) if there is
108             no square down from the given square.
109              
110             $e3 = Chess::Board->square_down_from("e4");
111              
112             =item horz_distance()
113              
114             Takes a single scalar parameter containing the square to calculate distance
115             from. Returns the horizontal distance in squares between the two points.
116              
117             =item vert_distance()
118              
119             Takes a single scalar parameter containing the square to calculate distance
120             from. Returns the vertical distance in squares between the two points.
121              
122             =item squares_in_line()
123              
124             Takes two scalar parameters containing two distinct endpoints in a line.
125             Returns a list of scalars in lower-case with an entry for each square in that
126             line, or C if the two endpoints do not define a line. In the case where
127             both squares are the same, will return a list containing that square.
128              
129             =back
130              
131             =head2 Object methods
132              
133             =over 4
134              
135             =item clone()
136              
137             Takes no arguments. Returns a blessed Chess::Board object reference which is
138             identical to the caller object. However, it is a I which allows
139             the clone()'d object to be manipulated separately of the caller object.
140              
141             =item line_is_open()
142              
143             Takes two scalar arguments, valid squares defining the endpoints of a line
144             on the Chess::Board. Returns true if there are no pieces on either of the
145             endpoints, or on any of the intervening squares. Returns false if the line
146             is blocked by one or more pieces, and C if the two squares do not
147             define endpoints of a line. In the case where both squares are equal, will
148             return true if the square is empty and false otherwise.
149              
150             =item get_piece_at()
151              
152             Takes a single scalar argument containing the square to retrieve the piece
153             from. Returns a scalar representing the piece on that square, or C if
154             there is none. Returns C and prints a warning to STDERR (See
155             L) if the provided square is not valid.
156              
157             =item set_piece_at()
158              
159             Takes two scalar arguments: the square whose piece to set, and a scalar
160             representing the piece to place there. Usually this will be a subclass of
161             C, but could be something else if the board is being used
162             stand-alone. See L for more information on
163             using other things as pieces. Sets the piece at that square if the square is
164             valid, and prints a warning to STDERR (see L) otherwise.
165              
166             =back
167              
168             =head1 DIAGNOSTICS
169              
170             =over 4
171              
172             =item 'q9' is not a valid square
173              
174             The function which generated this message was called with a square outside
175             the range a1-h8, causing it to return C. Use the class method
176             L to validate the square before passing it to any
177             method requiring a valid square.
178              
179             =item Invalid Chess::Board reference
180              
181             The function which generated this message was passed an invalid Chess::Board
182             reference. Make sure that the function call is passing a reference obtained
183             either from a call to L or to L, and that the reference
184             refers to a defined value.
185              
186             =item Can't modify this board. Use Chess::Board->new() instead.
187              
188             The program contains a reference to a Chess::Board that wasn't obtained through
189             a call to L or L. Make sure that all references have
190             been obtained through these methods.
191              
192             =back
193              
194             =head1 BUGS
195              
196             Please report any bugs to the author.
197              
198             =head1 AUTHOR
199              
200             Brian Richardson
201              
202             =head1 COPYRIGHT
203              
204             Copyright (c) 2002, 2005 Brian Richardson. All rights reserved. This module is
205             Free Software. It may be modified and redistributed under the same terms as
206             Perl itself.
207              
208             =cut
209             package Chess::Board;
210              
211 12     12   27109 use Carp;
  12         26  
  12         1204  
212 12     12   66 use strict;
  12         23  
  12         518  
213              
214 12     12   78 use constant IDX_EMPTY_BOARD => -1;
  12         21  
  12         43319  
215              
216             {
217             my $_r_empty_board = _init_empty_board();
218             my $_r_empty_board_arr;
219             my @_boards = ( );
220              
221             sub _init_empty_board {
222 12     12   80 for (my $y = 0; $y < 8; $y++) {
223 96 100       220 my $color = $y % 2 ? 'light' : 'dark';
224 96         210 for (my $x = 0; $x < 8; $x += 2) {
225 384         1118 $_r_empty_board_arr->[$y][$x] = { color => $color,
226             piece => undef };
227 384 100       716 $color = ($color eq 'light' ? 'dark' : 'light');
228 384         1203 $_r_empty_board_arr->[$y][$x+1] = { color => $color,
229             piece => undef };
230 384 100       1237 $color = ($color eq 'light' ? 'dark' : 'light');
231             }
232             }
233 12         27 my $i = IDX_EMPTY_BOARD;
234 12         58 return bless \$i, 'Chess::Board';
235             }
236              
237             sub _get_board_array_ref {
238 9676     9676   11823 my ($i) = @_;
239 9676 100       22586 return $_r_empty_board_arr if ($i == IDX_EMPTY_BOARD);
240 9668         16478 return $_boards[$i];
241             }
242              
243             sub new {
244 5     5 1 692 return $_r_empty_board->clone();
245             }
246              
247             sub clone {
248 500     500 1 1360 my ($clonee) = @_;
249 500   33     4366 my $class = ref($clonee) || croak "Invalid Chess::Board reference";
250 500         1065 my $r_board_arr = _get_board_array_ref($$clonee);
251 500 50       1199 croak "Invalid Chess::Board reference" unless ($r_board_arr);
252 500         563 my $obj_data;
253 500         1355 for (my $y = 0; $y < 8; $y++) {
254 4000         8188 for (my $x = 0; $x < 8; $x++) {
255 32000         58398 my $color = $r_board_arr->[$y][$x]{color};
256 32000         44993 my $piece = $r_board_arr->[$y][$x]{piece};
257 32000 100 66     140574 $piece = $piece->clone() if (defined($piece) &&
258             $piece->can('clone'));
259 32000         168165 $obj_data->[$y][$x] = { color => $color,
260             piece => $piece };
261             }
262             }
263 500         928 push @_boards, $obj_data;
264 500         753 my $i = $#_boards;
265 500         2040 return bless \$i, $class;
266             }
267              
268             sub DESTROY {
269 269     269   441 my ($caller) = @_;
270 269 50 33     8715 $_boards[$$caller] = undef if (defined($caller) && $$caller >= 0);
271             }
272             }
273              
274             sub _get_square_coords {
275 83430     83430   99220 my ($sq) = @_;
276 83430 50       165289 if (!Chess::Board->square_is_valid($sq)) {
277 0         0 carp "'$sq' is not a valid square";
278 0         0 return undef;
279             }
280 83430         186420 my $x = ord(lc substr($sq, 0, 1)) - ord('a');
281 83430         126215 my $y = substr($sq, 1, 1) - 1;
282 83430         147208 return ($x, $y);
283             }
284              
285             sub _coords_to_square {
286 44077     44077   52768 my ($x, $y) = @_;
287 44077         79818 my $sq = chr(ord('a') + $x) . ($y + 1);
288 44077         91401 return $sq;
289             }
290              
291             sub square_is_valid {
292 94034     94034 1 147116 my (undef, $sq) = @_;
293 94034         362143 return $sq =~ /^[A-Ha-h][1-8]$/;
294             }
295              
296             sub get_color_of {
297 3     3 1 9 my (undef, $sq) = @_;
298 3         6 my $r_board_arr = _get_board_array_ref(IDX_EMPTY_BOARD);
299 3         8 my ($x, $y) = _get_square_coords($sq);
300 3 50 33     16 if (defined($x) && defined($y)) {
301 3         21 return $r_board_arr->[$y][$x]{color};
302             }
303             else {
304 0         0 return undef;
305             }
306             }
307              
308             sub add_horz_distance {
309 22965     22965 0 33441 my (undef, $sq, $dist) = @_;
310 22965         37963 my ($x, $y) = _get_square_coords($sq);
311 22965 50 33     100759 return undef unless (defined($x) && defined($y));
312 22965         25639 $x += $dist;
313 22965 100 100     89706 return undef unless (($x >= 0) && ($x <= 7));
314 21985         36909 $sq = _coords_to_square($x, $y);
315 21985         50403 return $sq;
316             }
317              
318             sub add_vert_distance {
319 23068     23068 0 40327 my (undef, $sq, $dist) = @_;
320 23068         40253 my ($x, $y) = _get_square_coords($sq);
321 23068 50 33     93800 return undef unless (defined($x) && defined($y));
322 23068         25449 $y += $dist;
323 23068 100 100     99460 return undef unless (($y >= 0) && ($y <= 7));
324 22092         40994 $sq = _coords_to_square($x, $y);
325 22092         64844 return $sq;
326             }
327              
328             sub horz_distance {
329 4361     4361 1 8271 my (undef, $sq1, $sq2) = @_;
330 4361         7771 my ($x1, $y1) = _get_square_coords($sq1);
331 4361         8388 my ($x2, $y2) = _get_square_coords($sq2);
332 4361         13058 return $x2 - $x1;
333             }
334              
335             sub vert_distance {
336 4308     4308 1 7409 my (undef, $sq1, $sq2) = @_;
337 4308         7110 my ($x1, $y1) = _get_square_coords($sq1);
338 4308         7745 my ($x2, $y2) = _get_square_coords($sq2);
339 4308         11769 return $y2 - $y1;
340             }
341              
342             sub square_left_of {
343 5708     5708 1 8921 my (undef, $sq) = @_;
344 5708         13064 return Chess::Board->add_horz_distance($sq, -1);
345             }
346              
347             sub square_right_of {
348 12892     12892 1 25621 my (undef, $sq) = @_;
349 12892         27340 return Chess::Board->add_horz_distance($sq, 1);
350             }
351              
352             sub square_down_from {
353 6393     6393 1 9345 my (undef, $sq) = @_;
354 6393         14877 return Chess::Board->add_vert_distance($sq, -1);
355             }
356              
357             sub square_up_from {
358 12388     12388 1 18429 my (undef, $sq) = @_;
359 12388         24069 return Chess::Board->add_vert_distance($sq, 1);
360             }
361              
362             sub squares_in_line {
363 5301     5301 1 10650 my (undef, $sq1, $sq2) = @_;
364 5301         9267 my ($x1, $y1) = _get_square_coords($sq1);
365 5301         10049 my ($x2, $y2) = _get_square_coords($sq2);
366 5301         7615 my $hdist = abs($x2 - $x1);
367 5301         6690 my $vdist = abs($y2 - $y1);
368 5301 50 100     22477 return undef unless ($hdist == 0 || $vdist == 0 || $hdist == $vdist);
      66        
369 5301 100 100     20334 return ($sq1) unless($hdist || $vdist);
370 3985 100       8358 my $hdelta = $hdist ? $hdist / ($x2 - $x1) : 0;
371 3985 100       7297 my $vdelta = $vdist ? $vdist / ($y2 - $y1) : 0;
372 3985         4453 my @squares;
373 3985         5094 my $sq = $sq1;
374 3985         6006 push @squares, $sq;
375 3985 100 100     27694 if ($vdist and $hdelta == 0) {
    100 66        
    50          
376 1014         2516 for (my $i = 0; $i < $vdist; $i++) {
377 7098 50       19846 $sq = $vdelta > 0 ? Chess::Board->square_up_from($sq) :
378             Chess::Board->square_down_from($sq);
379 7098         20747 push @squares, $sq;
380             }
381             }
382             elsif ($hdist and $vdelta == 0) {
383 1022         2525 for (my $i = 0; $i < $hdist; $i++) {
384 7154 50       19382 $sq = $hdelta > 0 ? Chess::Board->square_right_of($sq) :
385             Chess::Board->square_left_of($sq);
386 7154         23937 push @squares, $sq;
387             }
388             }
389             elsif ($hdist == $vdist) {
390 1949         4480 for (my $i = 0; $i < $hdist; $i++) {
391 6203 100       17254 my $tsq = $hdelta > 0 ? Chess::Board->square_right_of($sq) :
392             Chess::Board->square_left_of($sq);
393 6203 100       19340 $sq = $vdelta > 0 ? Chess::Board->square_up_from($tsq) :
394             Chess::Board->square_down_from($tsq);
395 6203         19895 push @squares, $sq;
396             }
397             }
398 3985         24300 return @squares;
399             }
400              
401             sub get_piece_at {
402 7822     7822 1 13603 my ($self, $sq) = @_;
403 7822 50       16855 if (!Chess::Board->square_is_valid($sq)) {
404 0         0 carp "'$sq' is not a valid square";
405 0         0 return undef;
406             }
407 7822         15263 my ($x, $y) = _get_square_coords($sq);
408 7822 50       17555 croak "Invalid Chess::Board reference" unless (ref($self));
409 7822 50       15692 return undef if $$self == IDX_EMPTY_BOARD;
410 7822         13537 my $r_board_arr = _get_board_array_ref($$self);
411 7822 50       16201 croak "Invalid Chess::Board reference" unless (defined($r_board_arr));
412 7822         24302 return $r_board_arr->[$y][$x]{piece};
413             }
414              
415             sub set_piece_at {
416 1070     1070 1 2234 my ($self, $sq, $piece) = @_;
417 1070 50       2600 if (!Chess::Board->square_is_valid($sq)) {
418 0         0 carp "'$sq' is not a valid square";
419 0         0 return undef;
420             }
421 1070         2387 my ($x, $y) = _get_square_coords($sq);
422 1070 50       2571 croak "Invalid Chess::Board reference" unless (ref($self));
423 1070 50       2301 if ($$self == IDX_EMPTY_BOARD) {
424 0         0 carp "Can't modify this board. Use Chess::Board->new() instead";
425 0         0 return;
426             }
427 1070         1926 my $r_board_arr = _get_board_array_ref($$self);
428 1070 50       2418 croak "Invalid Chess::Board reference" unless (defined($r_board_arr));
429 1070         3565 $r_board_arr->[$y][$x]{piece} = $piece;
430             }
431              
432             sub line_is_open {
433 281     281 1 1613 my ($self, $sq1, $sq2) = @_;
434 281 50 33     850 if (!Chess::Board->square_is_valid($sq1) || !Chess::Board->square_is_valid($sq2)) {
435 0         0 carp "'$sq1' is not a valid square";
436 0         0 return undef;
437             }
438 281 50       908 croak "Invalid Chess::Board reference" unless (ref($self));
439 281 50       696 return 1 if $$self == IDX_EMPTY_BOARD;
440 281         615 my ($x1, $y1) = _get_square_coords($sq1);
441 281         624 my ($x2, $y2) = _get_square_coords($sq2);
442 281         564 my $hdist = abs($x2 - $x1);
443 281         511 my $vdist = abs($y2 - $y1);
444 281 50 100     1658 return undef unless ($hdist == 0 || $vdist == 0 || $hdist == $vdist);
      66        
445 281 100       836 my $hdelta = $hdist ? $hdist / ($x2 - $x1) : 0;
446 281 100       786 my $vdelta = $vdist ? $vdist / ($y2 - $y1) : 0;
447 281         367 my $xcurr = $x1;
448 281         350 my $ycurr = $y1;
449 281         621 my $r_board_arr = _get_board_array_ref($$self);
450 281 50       760 croak "Invalid Chess::Board reference" unless (defined($r_board_arr));
451 281 50 66     1150 if (($hdist == 0) && ($hdist == $vdist)) {
452 0 0       0 return 0 if (defined($r_board_arr->[$ycurr][$xcurr]{piece}));
453 0         0 return 1;
454             }
455 281   100     1164 while (($xcurr != $x2) || ($ycurr != $y2)) {
456 657 100       2287 return 0 if (defined($r_board_arr->[$ycurr][$xcurr]{piece}));
457 522         618 $xcurr += $hdelta;
458 522         1687 $ycurr += $vdelta;
459             }
460 146         1235 return 1;
461             }
462              
463             1;