File Coverage

blib/lib/Games/SGF/Go.pm
Criterion Covered Total %
statement 78 80 97.5
branch 23 28 82.1
condition 6 12 50.0
subroutine 16 16 100.0
pod 6 6 100.0
total 129 142 90.8


line stmt bran cond sub pod time code
1             package Games::SGF::Go;
2              
3 2     2   117023 use strict;
  2         6  
  2         146  
4 2     2   13 use warnings;
  2         5  
  2         92  
5             require Games::SGF;
6 2     2   11 no warnings 'redefine';
  2         4  
  2         3232  
7              
8             =head1 NAME
9              
10             Games::SGF::Go - A Go Specific SGF Parser
11              
12             =head1 VERSION
13              
14             Version 0.993
15              
16             =cut
17             our( @ISA ) = ('Games::SGF');
18             our $VERSION = 0.993;
19              
20             =head1 SYNOPSIS
21              
22             use Games::SGF::Go;
23              
24             my $sgf = new Games::SGF::Go;
25              
26             $sgf->readFile('somegame.sgf');
27              
28             # fetch Properties
29             my $komi = $sgf->property('KM');
30             my $handicap = $sgf->property('HA');
31              
32             # move to next node
33             $sgf->next;
34              
35             # get a move
36             my $move = $sgf->property('B');
37            
38             # add it to a board
39            
40             $board[ $move->[0] ][ $move->[1] ] = 'B';
41              
42             =head1 DISCRIPTION
43              
44             Games::SGF::Go Extends L for the game specifics of Go. These
45             include adding the tags 'TB', 'TW', 'HA', and 'KM'. It will also parse and
46             check the stone, move, and point types.
47              
48             The stone, move and point types will be returned as an array ref containing
49             the position on the board.
50              
51             You can set application specific tags using L. All the
52             callbacks to L have been set and thus can't be reset.
53              
54             All other methods from L can be used as you normally would.
55              
56             =head1 METHODS
57              
58             =head2 new
59              
60             my $sgf = new Games::SGF::Go;
61              
62             This will create the Games::SGF::Go object.
63              
64             =cut
65              
66             sub new {
67 3     3 1 3998 my $inv = shift;
68 3   66     21 my $class = ref $inv || $inv;
69 3         23 my $self = $class->SUPER::new(@_);
70              
71             # add Go Tags
72              
73             # Territory Black
74 3         67 $self->addTag('TB', $self->T_NONE, $self->V_POINT,
75             $self->VF_EMPTY | $self->VF_LIST | $self->VF_OPT_COMPOSE);
76              
77             # Territory White
78 3         31 $self->addTag('TW', $self->T_NONE, $self->V_POINT,
79             $self->VF_EMPTY | $self->VF_LIST | $self->VF_OPT_COMPOSE);
80              
81             # Handicap
82 3         27 $self->addTag('HA', $self->T_GAME_INFO, $self->V_NUMBER);
83              
84             # Komi
85 3         20 $self->addTag('KM', $self->T_GAME_INFO, $self->V_REAL);
86              
87            
88             # redefine tags so that stone becomes point
89 3         27 $self->redefineTag('AB', "", $self->V_POINT,
90             $self->VF_LIST | $self->VF_OPT_COMPOSE);
91 3         18 $self->redefineTag('AW', "", $self->V_POINT,
92             $self->VF_LIST | $self->VF_OPT_COMPOSE);
93              
94             # add Go CallBacks
95             # Read
96             $self->setPointRead( sub {
97 12     12   407 return $self->point( _readPoint($_[0]) );
98 3         24 });
99             $self->setMoveRead( sub {
100 18 100   18   49 if( $_[0] eq "" ) {
101 4         16 return $self->pass;
102             } else {
103 14         66 return $self->move( _readPoint($_[0]));
104             }
105 3         24 });
106              
107             # Check
108 3         18 $self->setPointCheck(\&_checkPoint);
109             # $self->setStoneCheck(\&_checkPoint);
110             $self->setMoveCheck( sub {
111 18 100   18   1604 if( $self->isPass($_[0]) ) {
112 4         50 return 1;
113             } else {
114 14         45 return &_checkPoint($_[0]);
115             }
116 3         23 });
117              
118             # Write
119 3         17 $self->setPointWrite( \&_writePoint );
120             # $self->setStoneWrite( \&_writePoint );
121             $self->setMoveWrite( sub {
122 9 100   9   27 if( $self->isPass( $_[0] ) ) {
123 2         9 return "";
124             } else {
125 7         15 _writePoint($_[0]);
126             }
127 3         24 });
128            
129              
130 3         13 return bless $self, $class; # reconsecrate
131             }
132              
133             # SGF -> internal
134             sub _readPoint {
135 26     26   44 my $text = shift;
136 26         111 my( @cord ) = split //, $text;
137            
138 26         59 foreach( @cord ) {
139 52 100 66     1030 if( $_ ge 'a' and $_ le 'z' ) {
    50 33        
140 44         171 $_ = ord($_) - ord('a'); # 0 - 25
141             } elsif( $_ ge 'A' and $_ le 'Z' ) {
142 8         26 $_ = ord($_) - ord('A') + 26; # 26 - 51
143             } else {
144             #error;
145             }
146             }
147 26         1120 return @cord;
148             }
149              
150             # checks internal
151             sub _checkPoint {
152 28     28   52 my $struct = shift;
153 28 50       419 return 0 if @$struct <= 0;
154 28         67 foreach( @$struct ) {
155 56 50       171 if( /\D/ ) {
156 0         0 return 0;
157             }
158 56 50 33     999 if( $_ < 0 or $_ > 52 ) {
159 0         0 return 0;
160             }
161             }
162 28         267 return 1;
163             }
164              
165             # internal -> SGF
166             sub _writePoint {
167 13     13   19 my $struct = shift;
168 13         20 my $text = "";
169 13         22 foreach(@$struct) {
170 26 100       42 if( $_ < 26 ) {
171 22         47 $text .= chr( ord('a') + $_ );
172             } else {
173 4         12 $text .= chr( ord('A') + $_ - 26 );
174             }
175             }
176 13         59 return $text;
177             }
178              
179             =head2 point
180              
181             =head2 stone
182              
183             =head2 move
184              
185             $struct = $self->move(@cord);
186             @cord = $self->move($struct);
187              
188             If a point, stone, or move is passed in, it will be broken into it's parts
189             and returned. If the parts are passed in it will construct the internal
190             structure which the parser uses.
191              
192             These override L, L, and
193             L.
194              
195             =cut
196              
197             # if passed @cord will return @cord again
198             sub point {
199 26     26 1 54 my $self = shift;
200 26 100       109 if( $self->isPoint($_[0]) ) {
201 1         3 return @{$_[0]};
  1         4  
202             } else {
203 25         166 return bless [@_], 'Games::SGF::Go::point';
204             }
205             }
206             sub move {
207 38     38 1 958 my $self = shift;
208 38 100       156 if( $self->isMove($_[0]) ) {
209 1         2 return @{$_[0]};
  1         5  
210             } else {
211 37         559 return bless [@_], 'Games::SGF::Go::move';
212             }
213             }
214             sub stone {
215 2     2 1 475 my $self = shift;
216 2 100       11 if( $self->isStone($_[0]) ) {
217 1         2 return @{$_[0]};
  1         5  
218             } else {
219 1         8 return bless [@_], 'Games::SGF::Go::stone';
220             }
221             }
222              
223             =head2 isPass
224              
225             $sgf->isPass($move);
226              
227             The method will return true if the move was a pass.
228              
229             This is represented in the SGF as an empty string:
230              
231             ;B[];W[]
232              
233             =cut
234              
235             sub isPass {
236 27     27 1 51 my $self = shift;
237 27         54 my $move = shift;
238              
239 27 50       108 if( $self->isMove($move) ) {
240 27 100       82 if( $move->[0] eq "" ) {
241 6         21 return 1;
242             }
243             }
244 21         67 return 0;
245             }
246              
247             =head2 pass
248              
249             $move = $sgf->pass;
250              
251             This will return a $move which is a pass.
252              
253             =cut
254              
255             sub pass {
256 8     8 1 14 my $self = shift;
257 8         23 return $self->move("");
258             }
259              
260             1;
261             __END__