| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 5 |  |  | 5 |  | 341047 | use strict; | 
|  | 5 |  |  |  |  | 58 |  | 
|  | 5 |  |  |  |  | 143 |  | 
| 2 | 5 |  |  | 5 |  | 27 | use warnings; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 247 |  | 
| 3 |  |  |  |  |  |  | package Games::Goban 1.103; | 
| 4 |  |  |  |  |  |  | # ABSTRACT: Board for playing go, renju, othello, etc. | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 5 |  |  | 5 |  | 104 | use 5.006; | 
|  | 5 |  |  |  |  | 16 |  | 
| 7 | 5 |  |  | 5 |  | 30 | use Carp; | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 12084 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | my $ORIGIN     = ord('a'); | 
| 10 |  |  |  |  |  |  | my $piececlass = 'Games::Goban::Piece'; | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | our %types = ( | 
| 13 |  |  |  |  |  |  | go      => 1, | 
| 14 |  |  |  |  |  |  | othello => 2, | 
| 15 |  |  |  |  |  |  | renju   => 4, | 
| 16 |  |  |  |  |  |  | gomoku  => 4, | 
| 17 |  |  |  |  |  |  | ); | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | our %defaults = ( | 
| 20 |  |  |  |  |  |  | game    => 'go', | 
| 21 |  |  |  |  |  |  | size    => 19, | 
| 22 |  |  |  |  |  |  | white   => 'Miss White', | 
| 23 |  |  |  |  |  |  | black   => 'Mr. Black', | 
| 24 |  |  |  |  |  |  | skip_i  => 0, | 
| 25 |  |  |  |  |  |  | referee => sub { 1 } | 
| 26 |  |  |  |  |  |  | ); | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | #pod =head1 SYNOPSIS | 
| 29 |  |  |  |  |  |  | #pod | 
| 30 |  |  |  |  |  |  | #pod   use Games::Goban; | 
| 31 |  |  |  |  |  |  | #pod   my $board = new Games::Goban ( | 
| 32 |  |  |  |  |  |  | #pod     size  => 19, | 
| 33 |  |  |  |  |  |  | #pod     game  => "go", | 
| 34 |  |  |  |  |  |  | #pod     white => "Seigen, Go", | 
| 35 |  |  |  |  |  |  | #pod     black => "Minoru, Kitani", | 
| 36 |  |  |  |  |  |  | #pod     referee => \&Games::Goban::Rules::Go, | 
| 37 |  |  |  |  |  |  | #pod   ); | 
| 38 |  |  |  |  |  |  | #pod | 
| 39 |  |  |  |  |  |  | #pod   $board->move("pd"); $board->move("dd"); | 
| 40 |  |  |  |  |  |  | #pod   print $board->as_sgf; | 
| 41 |  |  |  |  |  |  | #pod | 
| 42 |  |  |  |  |  |  | #pod =head1 DESCRIPTION | 
| 43 |  |  |  |  |  |  | #pod | 
| 44 |  |  |  |  |  |  | #pod This is a generic module for handling goban-based board games. | 
| 45 |  |  |  |  |  |  | #pod Theoretically, it can be used to handle many of the other games which | 
| 46 |  |  |  |  |  |  | #pod can use Smart Game Format (SGF) but I want to keep it reasonably | 
| 47 |  |  |  |  |  |  | #pod restricted in order to keep it simple. | 
| 48 |  |  |  |  |  |  | #pod | 
| 49 |  |  |  |  |  |  | #pod =head1 METHODS | 
| 50 |  |  |  |  |  |  | #pod | 
| 51 |  |  |  |  |  |  | #pod =head2 new(%options); | 
| 52 |  |  |  |  |  |  | #pod | 
| 53 |  |  |  |  |  |  | #pod Creates and initializes a new goban. The options and their legal | 
| 54 |  |  |  |  |  |  | #pod values (* marks defaults): | 
| 55 |  |  |  |  |  |  | #pod | 
| 56 |  |  |  |  |  |  | #pod   size       Any integer between 5 and 26, default: 19 | 
| 57 |  |  |  |  |  |  | #pod   game       *go, othello, renju, gomoku | 
| 58 |  |  |  |  |  |  | #pod   white      Any text, default: "Miss White" | 
| 59 |  |  |  |  |  |  | #pod   black      Any text, default: "Mr Black" | 
| 60 |  |  |  |  |  |  | #pod   skip_i     Truth value; whether 'i' should be skipped; false by default | 
| 61 |  |  |  |  |  |  | #pod   referee    Any subroutine, default: sub {1} # (All moves are valid) | 
| 62 |  |  |  |  |  |  | #pod | 
| 63 |  |  |  |  |  |  | #pod The referee subroutine takes a board object and a piece object, and | 
| 64 |  |  |  |  |  |  | #pod determines whether or not the move is legal. It also reports if the | 
| 65 |  |  |  |  |  |  | #pod game is won. | 
| 66 |  |  |  |  |  |  | #pod | 
| 67 |  |  |  |  |  |  | #pod =cut | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub new { | 
| 70 | 9 |  |  | 9 | 1 | 1300 | my $class = shift; | 
| 71 | 9 |  |  |  |  | 78 | my %opts = (%defaults, @_); | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 9 | 50 | 33 |  |  | 122 | unless (($opts{size} !~ /\D/) and ($opts{size} > 4) and ($opts{size} <= 26)) { | 
|  |  |  | 33 |  |  |  |  | 
| 74 | 0 |  |  |  |  | 0 | croak "Illegal size $opts{size} (must be integer > 4)"; | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 9 |  |  |  |  | 35 | $opts{game} = lc $opts{game}; | 
| 78 | 9 | 50 |  |  |  | 40 | croak "Unknown game $opts{game}" unless exists $types{ $opts{game} }; | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | my $board = bless { | 
| 81 |  |  |  |  |  |  | move        => 1, | 
| 82 |  |  |  |  |  |  | moves       => [], | 
| 83 |  |  |  |  |  |  | turn        => 'b', | 
| 84 |  |  |  |  |  |  | game        => $opts{game}, | 
| 85 |  |  |  |  |  |  | size        => $opts{size}, | 
| 86 |  |  |  |  |  |  | black       => $opts{black}, | 
| 87 |  |  |  |  |  |  | white       => $opts{white}, | 
| 88 |  |  |  |  |  |  | skip_i      => $opts{skip_i}, | 
| 89 |  |  |  |  |  |  | referee     => $opts{referee}, | 
| 90 | 9 |  |  |  |  | 88 | callbacks   => {}, | 
| 91 |  |  |  |  |  |  | magiccookie => "a0000", | 
| 92 |  |  |  |  |  |  | }, $class; | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 9 |  |  |  |  | 44 | for (0 .. ($opts{size} - 1)) { | 
| 95 | 161 |  |  |  |  | 202 | push @{ $board->{board} }, [ (undef) x $opts{size} ]; | 
|  | 161 |  |  |  |  | 425 |  | 
| 96 |  |  |  |  |  |  | } | 
| 97 | 9 |  |  |  |  | 37 | $board->{hoshi} = $board->_calc_hoshi; | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 9 |  |  |  |  | 42 | return $board; | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | #pod =head2 move | 
| 103 |  |  |  |  |  |  | #pod | 
| 104 |  |  |  |  |  |  | #pod     $ok = $board->move($position) | 
| 105 |  |  |  |  |  |  | #pod | 
| 106 |  |  |  |  |  |  | #pod Takes a move, creates a Games::Goban::Piece object, and attempts to | 
| 107 |  |  |  |  |  |  | #pod place it on the board, subject to the constraints of the I. | 
| 108 |  |  |  |  |  |  | #pod If this is not successful, it returns C<0> and sets C<$@> to be an error | 
| 109 |  |  |  |  |  |  | #pod message explaining why the move could not be made. If successful, | 
| 110 |  |  |  |  |  |  | #pod updates the board, updates the move number and the turn, and returns | 
| 111 |  |  |  |  |  |  | #pod true. | 
| 112 |  |  |  |  |  |  | #pod | 
| 113 |  |  |  |  |  |  | #pod =cut | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | sub move { | 
| 116 | 10 |  |  | 10 | 1 | 696 | my ($self, $move) = @_; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 10 |  |  |  |  | 26 | my ($x, $y) = $self->_pos2grid($move, $self->skip_i); | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 10 |  |  |  |  | 37 | $self->_check_pos($move); | 
| 121 | 9 |  |  |  |  | 25 | my $stat = $self->{referee}->($self, $move); | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 9 | 50 |  |  |  | 22 | return $stat if !$stat; | 
| 124 |  |  |  |  |  |  | $self->{board}[$x][$y] = bless { | 
| 125 |  |  |  |  |  |  | colour => $self->{turn}, | 
| 126 |  |  |  |  |  |  | move   => $self->{move}, | 
| 127 | 9 |  |  |  |  | 49 | xy     => [ $x, $y ], | 
| 128 |  |  |  |  |  |  | board  => $self | 
| 129 |  |  |  |  |  |  | }, | 
| 130 |  |  |  |  |  |  | "Games::Goban::Piece"; | 
| 131 | 9 |  |  |  |  | 32 | push @{ $self->{moves} }, | 
| 132 |  |  |  |  |  |  | { | 
| 133 |  |  |  |  |  |  | player => $self->{turn}, | 
| 134 | 9 |  |  |  |  | 16 | piece  => $self->{board}[$x][$y] | 
| 135 |  |  |  |  |  |  | }; | 
| 136 | 9 |  |  |  |  | 17 | $self->{move}++; | 
| 137 | 9 | 100 |  |  |  | 26 | $self->{turn} = $self->{turn} eq "b" ? "w" : "b"; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 9 |  |  |  |  | 14 | while (my ($key, $cb) = each %{ $self->{callbacks} }) { $cb->($key, $self) } | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 11 |  |  |  |  | 36 |  | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 9 |  |  |  |  | 20 | return 1; | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | #pod =head2 pass | 
| 145 |  |  |  |  |  |  | #pod | 
| 146 |  |  |  |  |  |  | #pod This method causes the current player to pass.  At present, nothing happens for | 
| 147 |  |  |  |  |  |  | #pod two subsequent passes. | 
| 148 |  |  |  |  |  |  | #pod | 
| 149 |  |  |  |  |  |  | #pod =cut | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | sub pass { | 
| 152 | 2 |  |  | 2 | 1 | 7 | my $self = shift; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 2 |  |  |  |  | 6 | push @{ $self->{moves} }, | 
| 155 |  |  |  |  |  |  | { | 
| 156 |  |  |  |  |  |  | player => $self->{turn}, | 
| 157 | 2 |  |  |  |  | 3 | piece  => undef | 
| 158 |  |  |  |  |  |  | }; | 
| 159 | 2 |  |  |  |  | 4 | $self->{move}++; | 
| 160 | 2 | 100 |  |  |  | 5 | $self->{turn} = $self->{turn} eq "b" ? "w" : "b"; | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | #pod =head2 get | 
| 164 |  |  |  |  |  |  | #pod | 
| 165 |  |  |  |  |  |  | #pod     $move = $board->get($position) | 
| 166 |  |  |  |  |  |  | #pod | 
| 167 |  |  |  |  |  |  | #pod Gets the C object at the given location, if there | 
| 168 |  |  |  |  |  |  | #pod is one. Locations are specified as per SGF - a 19x19 board starts from | 
| 169 |  |  |  |  |  |  | #pod C in the top left corner, with C in the bottom right.  (If the skip_i | 
| 170 |  |  |  |  |  |  | #pod option was set while creating the board, C is the bottom right and there | 
| 171 |  |  |  |  |  |  | #pod are no C positions.  This allows for traditional notation.) | 
| 172 |  |  |  |  |  |  | #pod | 
| 173 |  |  |  |  |  |  | #pod =cut | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub get { | 
| 176 | 447 |  |  | 447 | 1 | 1238 | my ($self, $pos) = @_; | 
| 177 | 447 |  |  |  |  | 732 | my ($x, $y) = $self->_pos2grid($pos, $self->skip_i); | 
| 178 | 447 |  |  |  |  | 955 | $self->_check_grid($x, $y); | 
| 179 |  |  |  |  |  |  |  | 
| 180 | 447 |  |  |  |  | 869 | return $self->{board}[$x][$y]; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | #pod =head2 size | 
| 184 |  |  |  |  |  |  | #pod | 
| 185 |  |  |  |  |  |  | #pod     $size = $board->size | 
| 186 |  |  |  |  |  |  | #pod | 
| 187 |  |  |  |  |  |  | #pod Returns the size of the goban. | 
| 188 |  |  |  |  |  |  | #pod | 
| 189 |  |  |  |  |  |  | #pod =cut | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 996 |  |  | 996 | 1 | 19382 | sub size { $_[0]->{size} } | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | #pod =head2 hoshi | 
| 194 |  |  |  |  |  |  | #pod | 
| 195 |  |  |  |  |  |  | #pod   @hoshi_points = $board->hoshi | 
| 196 |  |  |  |  |  |  | #pod | 
| 197 |  |  |  |  |  |  | #pod Returns a list of hoshi points. | 
| 198 |  |  |  |  |  |  | #pod | 
| 199 |  |  |  |  |  |  | #pod =cut | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | sub hoshi { | 
| 202 | 441 |  |  | 441 | 1 | 521 | my $self = shift; | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 441 |  |  |  |  | 519 | map { $self->_grid2pos(@$_, $self->skip_i) } @{ $self->{hoshi} }; | 
|  | 3645 |  |  |  |  | 6200 |  | 
|  | 441 |  |  |  |  | 772 |  | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | #pod =head2 is_hoshi | 
| 208 |  |  |  |  |  |  | #pod | 
| 209 |  |  |  |  |  |  | #pod   $star = $board->is_hoshi('dp') | 
| 210 |  |  |  |  |  |  | #pod | 
| 211 |  |  |  |  |  |  | #pod Returns true if the named position is a hoshi (star) point. | 
| 212 |  |  |  |  |  |  | #pod | 
| 213 |  |  |  |  |  |  | #pod =cut | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | sub is_hoshi { | 
| 216 | 437 |  |  | 437 | 1 | 567 | my $board = shift; | 
| 217 | 437 |  |  |  |  | 553 | my $point = shift; | 
| 218 | 437 | 100 |  |  |  | 615 | return 1 if grep { /^$point$/ } $board->hoshi; | 
|  | 3613 |  |  |  |  | 11507 |  | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | #pod =head2 as_sgf | 
| 222 |  |  |  |  |  |  | #pod | 
| 223 |  |  |  |  |  |  | #pod     $sgf = $board->as_sgf; | 
| 224 |  |  |  |  |  |  | #pod | 
| 225 |  |  |  |  |  |  | #pod Returns a representation of the board as an SGF (Smart Game Format) file. | 
| 226 |  |  |  |  |  |  | #pod | 
| 227 |  |  |  |  |  |  | #pod =cut | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | sub as_sgf { | 
| 230 | 2 |  |  | 2 | 1 | 8 | my $self = shift; | 
| 231 | 2 |  |  |  |  | 4 | my $sgf; | 
| 232 |  |  |  |  |  |  |  | 
| 233 | 2 |  |  |  |  | 13 | $sgf | 
| 234 |  |  |  |  |  |  | .= "(;GM[$types{$self->{game}}]FF[4]AP[Games::Goban]SZ[$self->{size}]PB[$self->{black}]PW[$self->{white}]\n"; | 
| 235 | 2 |  |  |  |  | 5 | foreach (@{ $self->{moves} }) { | 
|  | 2 |  |  |  |  | 7 |  | 
| 236 |  |  |  |  |  |  | $sgf .= q{;} | 
| 237 |  |  |  |  |  |  | . uc($_->{player}) . q<[> | 
| 238 | 8 | 100 |  |  |  | 30 | . ($_->{piece} ? $self->_grid2pos(@{ $_->{piece}->_xy }, 0) : q{}) . q<]>; | 
|  | 6 |  |  |  |  | 14 |  | 
| 239 |  |  |  |  |  |  | } | 
| 240 | 2 |  |  |  |  | 4 | $sgf .= ")\n"; | 
| 241 |  |  |  |  |  |  |  | 
| 242 | 2 |  |  |  |  | 6 | return $sgf; | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | #pod =head2 as_text | 
| 246 |  |  |  |  |  |  | #pod | 
| 247 |  |  |  |  |  |  | #pod     print $board->as_text(coords => 1) | 
| 248 |  |  |  |  |  |  | #pod | 
| 249 |  |  |  |  |  |  | #pod Returns a printable text picture of the board, similar to that printed | 
| 250 |  |  |  |  |  |  | #pod by C. Black pieces are represented by C, white pieces by C, | 
| 251 |  |  |  |  |  |  | #pod and the latest move is enclosed in parentheses. I points are in their | 
| 252 |  |  |  |  |  |  | #pod normal position for Go, and printed as an C<+>. Coordinates are not printed by | 
| 253 |  |  |  |  |  |  | #pod default, but can be enabled as suggested in the synopsis. | 
| 254 |  |  |  |  |  |  | #pod | 
| 255 |  |  |  |  |  |  | #pod =cut | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | sub as_text { | 
| 258 | 2 |  |  | 2 | 1 | 6 | my $board = shift; | 
| 259 | 2 |  |  |  |  | 5 | my %opts  = @_; | 
| 260 | 2 |  |  |  |  | 6 | my @hoshi = $board->hoshi; | 
| 261 | 2 |  |  |  |  | 4 | my $text; | 
| 262 | 2 |  |  |  |  | 4 | for (my $y = $board->size - 1; $y >= 0; $y--) { ## no critic For | 
| 263 |  |  |  |  |  |  | $text .= substr($board->_grid2pos(0, $y, $board->skip_i), 1, 1) . ': ' | 
| 264 | 28 | 50 |  |  |  | 55 | if $opts{coords}; | 
| 265 | 28 |  |  |  |  | 52 | for my $x (0 .. ($board->size - 1)) { | 
| 266 | 442 |  |  |  |  | 815 | my $pos = $board->_grid2pos($x, $y, $board->skip_i); | 
| 267 | 442 |  |  |  |  | 807 | my $p = $board->get($pos); | 
| 268 | 442 | 100 | 100 |  |  | 797 | if (  $p | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 269 |  |  |  |  |  |  | and $p->move == $board->{move} - 1 | 
| 270 |  |  |  |  |  |  | and $text | 
| 271 |  |  |  |  |  |  | and substr($text, -1, 1) ne "\n") | 
| 272 |  |  |  |  |  |  | { | 
| 273 | 1 |  |  |  |  | 4 | chop $text; | 
| 274 | 1 |  |  |  |  | 2 | $text .= "("; | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  | $text .= ( | 
| 277 | 442 | 100 |  |  |  | 803 | $p | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | ? ($p->color eq "b" ? "X" : "O") | 
| 279 |  |  |  |  |  |  | : ($board->is_hoshi($pos) ? q{+} : q{.}) | 
| 280 |  |  |  |  |  |  | ) . q{ }; | 
| 281 | 442 | 100 | 100 |  |  | 1267 | if ($p and $p->move == $board->{move} - 1) { chop $text; $text .= ")"; } | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 6 |  | 
| 282 |  |  |  |  |  |  | } | 
| 283 | 28 |  |  |  |  | 67 | $text .= "\n"; | 
| 284 |  |  |  |  |  |  | } | 
| 285 | 2 | 50 |  |  |  | 7 | if ($opts{coords}) { | 
| 286 | 0 |  |  |  |  | 0 | $text .= q{ } x 3; | 
| 287 | 0 |  |  |  |  | 0 | for (0 .. ($board->size - 1)) { | 
| 288 | 0 |  |  |  |  | 0 | $text .= substr($board->_grid2pos($_, 0, $board->skip_i), 0, 1) . q{ }; | 
| 289 |  |  |  |  |  |  | } | 
| 290 | 0 |  |  |  |  | 0 | $text .= "\n"; | 
| 291 |  |  |  |  |  |  | } | 
| 292 | 2 |  |  |  |  | 20 | return $text; | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | #pod =head2 register | 
| 296 |  |  |  |  |  |  | #pod | 
| 297 |  |  |  |  |  |  | #pod     my $key = $board->register(\&callback); | 
| 298 |  |  |  |  |  |  | #pod | 
| 299 |  |  |  |  |  |  | #pod Register a callback to be called after every move is made. This is useful for | 
| 300 |  |  |  |  |  |  | #pod analysis programs which wish to maintain statistics on the board state. The | 
| 301 |  |  |  |  |  |  | #pod C returned from this can be fed to... | 
| 302 |  |  |  |  |  |  | #pod | 
| 303 |  |  |  |  |  |  | #pod =cut | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | sub register { | 
| 306 | 1 |  |  | 1 | 1 | 10 | my ($board, $cb) = @_; | 
| 307 | 1 |  |  |  |  | 4 | my $key = ++$board->{magiccookie}; | 
| 308 | 1 |  |  |  |  | 2 | $board->{callbacks}{$key} = $cb; | 
| 309 | 1 |  |  |  |  | 3 | $board->{notes}->{$key} = {}; | 
| 310 | 1 |  |  |  |  | 3 | return $key; | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | #pod =head2 notes | 
| 314 |  |  |  |  |  |  | #pod | 
| 315 |  |  |  |  |  |  | #pod     $board->notes($key)->{score} += 5; | 
| 316 |  |  |  |  |  |  | #pod | 
| 317 |  |  |  |  |  |  | #pod C returns a hash reference which can be used by a callback to | 
| 318 |  |  |  |  |  |  | #pod store local state about the board. | 
| 319 |  |  |  |  |  |  | #pod | 
| 320 |  |  |  |  |  |  | #pod =cut | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | sub notes { | 
| 323 | 4 |  |  | 4 | 1 | 16 | my ($board, $key) = @_; | 
| 324 | 4 |  |  |  |  | 16 | return $board->{notes}->{$key}; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | #pod =head2 hash | 
| 328 |  |  |  |  |  |  | #pod | 
| 329 |  |  |  |  |  |  | #pod     $hash = $board->hash | 
| 330 |  |  |  |  |  |  | #pod | 
| 331 |  |  |  |  |  |  | #pod Provides a unique hash of the board position. If the phrase "positional | 
| 332 |  |  |  |  |  |  | #pod superko" means anything to you, you want to use this method. If not, | 
| 333 |  |  |  |  |  |  | #pod move along, nothing to see here. | 
| 334 |  |  |  |  |  |  | #pod | 
| 335 |  |  |  |  |  |  | #pod =cut | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | sub hash { | 
| 338 | 0 |  |  | 0 | 1 | 0 | my $board = shift; | 
| 339 | 0 |  |  |  |  | 0 | my $hash  = chr(0) x 91; | 
| 340 | 0 |  |  |  |  | 0 | my $bit   = 0; | 
| 341 |  |  |  |  |  |  | $board->_iterboard( | 
| 342 |  |  |  |  |  |  | sub { | 
| 343 | 0 |  |  | 0 |  | 0 | my $piece = shift; | 
| 344 | 0 | 0 |  |  |  | 0 | vec($hash, $bit, 2) = $piece->color eq "b" ? 1 : 2 if $piece; | 
|  |  | 0 |  |  |  |  |  | 
| 345 | 0 |  |  |  |  | 0 | $bit += 3; | 
| 346 |  |  |  |  |  |  | } | 
| 347 | 0 |  |  |  |  | 0 | ); | 
| 348 | 0 |  |  |  |  | 0 | return $hash; | 
| 349 |  |  |  |  |  |  | } | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | #pod =head2 skip_i | 
| 352 |  |  |  |  |  |  | #pod | 
| 353 |  |  |  |  |  |  | #pod This method returns true if the 'skip_i' argument to the constructor was true | 
| 354 |  |  |  |  |  |  | #pod and the 'i' coordinant should be skipped.  (Note that 'i' is never skipped when | 
| 355 |  |  |  |  |  |  | #pod producing SGF output.) | 
| 356 |  |  |  |  |  |  | #pod | 
| 357 |  |  |  |  |  |  | #pod =cut | 
| 358 |  |  |  |  |  |  |  | 
| 359 | 4555 |  |  | 4555 | 1 | 7234 | sub skip_i { return (shift)->{skip_i} } | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | # This method accepts a position string and checks whether it is a valid | 
| 362 |  |  |  |  |  |  | # position on the given board.  If it is, 1 is returned.  Otherwise, it carps | 
| 363 |  |  |  |  |  |  | # that the position is not on the board.  It does this by calling _check_grid, | 
| 364 |  |  |  |  |  |  | # also below. | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | sub _check_pos { | 
| 367 | 10 |  |  | 10 |  | 16 | my $self = shift; | 
| 368 | 10 |  |  |  |  | 14 | my $pos  = shift; | 
| 369 |  |  |  |  |  |  |  | 
| 370 | 10 |  |  |  |  | 24 | my ($x, $y) = $self->_pos2grid($pos, $self->skip_i); | 
| 371 |  |  |  |  |  |  |  | 
| 372 | 10 |  |  |  |  | 26 | return $self->_check_grid($x, $y); | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | sub _check_grid { | 
| 376 | 458 |  |  | 458 |  | 1005 | my $self = shift; | 
| 377 | 458 |  |  |  |  | 678 | my ($x, $y) = @_; | 
| 378 |  |  |  |  |  |  |  | 
| 379 | 458 | 100 | 66 |  |  | 658 | return 1 | 
| 380 |  |  |  |  |  |  | if (($x < $self->size) and ($y < $self->size)); | 
| 381 |  |  |  |  |  |  |  | 
| 382 | 1 |  |  |  |  | 4 | croak "position '" | 
| 383 |  |  |  |  |  |  | . $self->_grid2pos($x, $y, $self->skip_i) | 
| 384 |  |  |  |  |  |  | . "' not on board"; | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | # This method returns a list of the hoshi points that should be found on the | 
| 388 |  |  |  |  |  |  | # board, given its size. | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | sub _calc_hoshi { | 
| 391 | 9 |  |  | 9 |  | 19 | my $self = shift; | 
| 392 | 9 |  |  |  |  | 25 | my $size = $self->size; | 
| 393 | 9 |  |  |  |  | 30 | my $half = ($size - 1) / 2; | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 9 |  |  |  |  | 20 | my @hoshi = (); | 
| 396 |  |  |  |  |  |  |  | 
| 397 | 9 | 50 |  |  |  | 32 | if ($size % 2) { push @hoshi, [ $half, $half ]; }  # middle center | 
|  | 9 |  |  |  |  | 26 |  | 
| 398 |  |  |  |  |  |  |  | 
| 399 | 9 | 0 |  |  |  | 33 | my $margin = ($size > 11 ? 4 : ($size > 6 ? 3 : ($size > 4 ? 2 : undef))); | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 400 |  |  |  |  |  |  |  | 
| 401 | 9 | 50 |  |  |  | 56 | return \@hoshi unless $margin; | 
| 402 |  |  |  |  |  |  |  | 
| 403 | 9 |  |  |  |  | 46 | push @hoshi, ( | 
| 404 |  |  |  |  |  |  | [ $margin - 1, $margin - 1 ],                    # top left | 
| 405 |  |  |  |  |  |  | [ $size - $margin, $margin - 1 ],                # top right | 
| 406 |  |  |  |  |  |  | [ $margin - 1, $size - $margin ],                # bottom left | 
| 407 |  |  |  |  |  |  | [ $size - $margin, $size - $margin ]             # bottom right | 
| 408 |  |  |  |  |  |  | ); | 
| 409 |  |  |  |  |  |  |  | 
| 410 | 9 | 100 | 66 |  |  | 119 | if (($size % 2) && ($size > 9)) { | 
| 411 | 8 |  |  |  |  | 51 | push @hoshi, ( | 
| 412 |  |  |  |  |  |  | [ $half, $margin - 1 ],                        # top center | 
| 413 |  |  |  |  |  |  | [ $margin - 1, $half ],                        # middle left | 
| 414 |  |  |  |  |  |  | [ $size - $margin, $half ],                    # middle right | 
| 415 |  |  |  |  |  |  | [ $half, $size - $margin ]                     # bottom center | 
| 416 |  |  |  |  |  |  | ); | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 9 |  |  |  |  | 31 | return \@hoshi; | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | # This subroutine passes every findable square on the board to the supplied | 
| 423 |  |  |  |  |  |  | # subroutine reference. | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | sub _iterboard { | 
| 426 | 0 |  |  | 0 |  | 0 | my ($self, $sub) = @_; | 
| 427 | 0 |  |  |  |  | 0 | for my $x ('a' .. chr($self->size + ord("a") - 1)) { | 
| 428 | 0 |  |  |  |  | 0 | for my $y ('a' .. chr($self->size + ord("a") - 1)) { | 
| 429 | 0 |  |  |  |  | 0 | $sub->($self->get("$x$y")); | 
| 430 |  |  |  |  |  |  | } | 
| 431 |  |  |  |  |  |  | } | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | # This method accepts an (x,y) position, starting with (0,0) and returns the | 
| 436 |  |  |  |  |  |  | # 'xy' text representing it. | 
| 437 |  |  |  |  |  |  | # The third parameter, if true, indicates that 'i' should be skipped. | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | sub _grid2pos { | 
| 440 | 8901 |  |  | 8901 |  | 344233 | my $self = shift; | 
| 441 | 8901 |  |  |  |  | 13230 | my ($x, $y, $skip_i) = @_; | 
| 442 |  |  |  |  |  |  |  | 
| 443 | 8901 | 100 |  |  |  | 14541 | if ($skip_i) { | 
| 444 | 11 |  |  |  |  | 17 | for ($x, $y) { | 
| 445 | 22 | 100 |  |  |  | 47 | $_++ if ($_ >= 8); | 
| 446 |  |  |  |  |  |  | } | 
| 447 |  |  |  |  |  |  | } | 
| 448 |  |  |  |  |  |  |  | 
| 449 | 8901 |  |  |  |  | 23776 | return chr($ORIGIN + $x) . chr($ORIGIN + $y); | 
| 450 |  |  |  |  |  |  | } | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | # This method accepts an 'xy' position string and returns the (x,y) indexes | 
| 453 |  |  |  |  |  |  | # where that position falls in the board. | 
| 454 |  |  |  |  |  |  | # The second parameter, if true, indicates that 'i' should be skipped. | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | sub _pos2grid { | 
| 457 | 3676 |  |  | 3676 |  | 8653 | my $self = shift; | 
| 458 | 3676 |  |  |  |  | 5845 | my ($pos, $skip_i) = @_; | 
| 459 |  |  |  |  |  |  |  | 
| 460 | 3676 |  |  |  |  | 13706 | my ($xc, $yc) = (lc($pos) =~ /^([a-z])([a-z])$/); | 
| 461 | 3676 |  |  |  |  | 5798 | my ($x, $y); | 
| 462 |  |  |  |  |  |  |  | 
| 463 | 3676 |  |  |  |  | 4947 | $x = ord($xc) - $ORIGIN; | 
| 464 | 3676 | 100 | 66 |  |  | 6776 | $x-- if ($skip_i and ($x > 8)); | 
| 465 |  |  |  |  |  |  |  | 
| 466 | 3676 |  |  |  |  | 4585 | $y = ord($yc) - $ORIGIN; | 
| 467 | 3676 | 100 | 100 |  |  | 6134 | $y-- if ($skip_i and ($y > 8)); | 
| 468 |  |  |  |  |  |  |  | 
| 469 | 3676 |  |  |  |  | 8661 | return ($x, $y); | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | package Games::Goban::Piece 1.103; | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | #pod =head1 C methods | 
| 475 |  |  |  |  |  |  | #pod | 
| 476 |  |  |  |  |  |  | #pod Here are the methods which can be called on a C | 
| 477 |  |  |  |  |  |  | #pod object, representing a piece on the board. | 
| 478 |  |  |  |  |  |  | #pod | 
| 479 |  |  |  |  |  |  | #pod =cut | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | #pod =head1 color | 
| 482 |  |  |  |  |  |  | #pod | 
| 483 |  |  |  |  |  |  | #pod Returns "b" for a black piece and "w" for a white. C is also | 
| 484 |  |  |  |  |  |  | #pod provided for Anglophones. | 
| 485 |  |  |  |  |  |  | #pod | 
| 486 |  |  |  |  |  |  | #pod =cut | 
| 487 |  |  |  |  |  |  |  | 
| 488 | 5 |  |  | 5 |  | 14 | sub color  { $_[0]->{colour} } | 
| 489 | 0 |  |  | 0 |  | 0 | sub colour { $_[0]->{colour} } | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | #pod =head1 notes | 
| 492 |  |  |  |  |  |  | #pod | 
| 493 |  |  |  |  |  |  | #pod Similar to the C method on the board class, this provides a | 
| 494 |  |  |  |  |  |  | #pod private area for callbacks to scribble on. | 
| 495 |  |  |  |  |  |  | #pod | 
| 496 |  |  |  |  |  |  | #pod =cut | 
| 497 |  |  |  |  |  |  |  | 
| 498 | 0 |  |  | 0 |  | 0 | sub notes { $_[0]->{notes}->{ $_[1] } } | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | #pod =head1 position | 
| 501 |  |  |  |  |  |  | #pod | 
| 502 |  |  |  |  |  |  | #pod Returns the position of this piece, as a two-character string. | 
| 503 |  |  |  |  |  |  | #pod Incidentally, try to avoid taking references to C objects, since | 
| 504 |  |  |  |  |  |  | #pod this stops them being destroyed in a timely fashion. Use a C | 
| 505 |  |  |  |  |  |  | #pod and C if you can get away with it, or take a weak reference if | 
| 506 |  |  |  |  |  |  | #pod you're worried about the piece going away or being replaced by another | 
| 507 |  |  |  |  |  |  | #pod one in that position. | 
| 508 |  |  |  |  |  |  | #pod | 
| 509 |  |  |  |  |  |  | #pod =cut | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | sub position { | 
| 512 | 0 |  |  | 0 |  | 0 | my $piece = shift; | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | ## no critic Private | 
| 515 | 0 |  |  |  |  | 0 | $piece->board->_grid2pos(@{ $piece->_xy }, $piece->board->skip_i); | 
|  | 0 |  |  |  |  | 0 |  | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  |  | 
| 518 | 6 |  |  | 6 |  | 15 | sub _xy { $_[0]->{xy} } | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | #pod =head1 move | 
| 521 |  |  |  |  |  |  | #pod | 
| 522 |  |  |  |  |  |  | #pod Returns the move number on which this piece was played. | 
| 523 |  |  |  |  |  |  | #pod | 
| 524 |  |  |  |  |  |  | #pod =cut | 
| 525 |  |  |  |  |  |  |  | 
| 526 | 10 |  |  | 10 |  | 49 | sub move { $_[0]->{move} } | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | #pod =head1 board | 
| 529 |  |  |  |  |  |  | #pod | 
| 530 |  |  |  |  |  |  | #pod Returns the board object whence this piece came. | 
| 531 |  |  |  |  |  |  | #pod | 
| 532 |  |  |  |  |  |  | #pod =cut | 
| 533 |  |  |  |  |  |  |  | 
| 534 | 0 |  |  | 0 |  |  | sub board { $_[0]->{board} } | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | 1; | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | #pod =head1 TODO | 
| 539 |  |  |  |  |  |  | #pod | 
| 540 |  |  |  |  |  |  | #pod =over | 
| 541 |  |  |  |  |  |  | #pod | 
| 542 |  |  |  |  |  |  | #pod =item * | 
| 543 |  |  |  |  |  |  | #pod | 
| 544 |  |  |  |  |  |  | #pod use Games::Goban::Board for game board | 
| 545 |  |  |  |  |  |  | #pod | 
| 546 |  |  |  |  |  |  | #pod =item * | 
| 547 |  |  |  |  |  |  | #pod | 
| 548 |  |  |  |  |  |  | #pod add C<<$board->pass>> | 
| 549 |  |  |  |  |  |  | #pod | 
| 550 |  |  |  |  |  |  | #pod =item * | 
| 551 |  |  |  |  |  |  | #pod | 
| 552 |  |  |  |  |  |  | #pod possibly enable C<<$board->move('')>> to pass | 
| 553 |  |  |  |  |  |  | #pod | 
| 554 |  |  |  |  |  |  | #pod =item * | 
| 555 |  |  |  |  |  |  | #pod | 
| 556 |  |  |  |  |  |  | #pod produce example referee | 
| 557 |  |  |  |  |  |  | #pod | 
| 558 |  |  |  |  |  |  | #pod =item * | 
| 559 |  |  |  |  |  |  | #pod | 
| 560 |  |  |  |  |  |  | #pod produce sample method for removing captured stones | 
| 561 |  |  |  |  |  |  | #pod | 
| 562 |  |  |  |  |  |  | #pod =back | 
| 563 |  |  |  |  |  |  | #pod | 
| 564 |  |  |  |  |  |  | #pod =head1 SEE ALSO | 
| 565 |  |  |  |  |  |  | #pod | 
| 566 |  |  |  |  |  |  | #pod Smart Game Format: http://www.red-bean.com/sgf/ | 
| 567 |  |  |  |  |  |  | #pod | 
| 568 |  |  |  |  |  |  | #pod C | 
| 569 |  |  |  |  |  |  | #pod | 
| 570 |  |  |  |  |  |  | #pod The US Go Association: http://www.usgo.org/ | 
| 571 |  |  |  |  |  |  | #pod | 
| 572 |  |  |  |  |  |  |  | 
| 573 |  |  |  |  |  |  | __END__ |