line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Games::Go::Referee; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
73084
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
46
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
33
|
|
5
|
1
|
|
|
1
|
|
1345
|
use Games::Go::SGF; |
|
1
|
|
|
|
|
105755
|
|
|
1
|
|
|
|
|
70
|
|
6
|
1
|
|
|
1
|
|
1039
|
use Games::Go::Referee::Node; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
35
|
|
7
|
1
|
|
|
1
|
|
6
|
use English qw(-no_match_vars); # Avoids regex performance penalty |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
10
|
|
8
|
1
|
|
|
1
|
|
536
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
9670
|
|
9
|
|
|
|
|
|
|
our $VERSION = 0.10; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
sub new { |
12
|
1
|
|
|
1
|
0
|
17
|
my $this = shift; |
13
|
1
|
|
33
|
|
|
7
|
my $class = ref($this) || $this; |
14
|
1
|
|
|
|
|
3
|
my $self = {}; |
15
|
1
|
|
|
|
|
14
|
$self->{_const} = { # defaults |
16
|
|
|
|
|
|
|
size => 18, # default board size |
17
|
|
|
|
|
|
|
selfcapture => 0, # is self capture OK? |
18
|
|
|
|
|
|
|
ssk => 0, # situational super ko |
19
|
|
|
|
|
|
|
passes => 2, # number of consecutive passes required to finish play |
20
|
|
|
|
|
|
|
hfree => 0, # are handicap stones freely placed? |
21
|
|
|
|
|
|
|
handicap => 0, # the handicap number |
22
|
|
|
|
|
|
|
exitonerror => 0, # exit on (Go) error if set, or continue if not set |
23
|
|
|
|
|
|
|
alternation => 1, # flag alternation errors as errors? yes/on |
24
|
|
|
|
|
|
|
passcount => 1, # flag passcount errors as errors? yes/on |
25
|
|
|
|
|
|
|
pointformat => 'sgf' # can be sgf or gmp |
26
|
|
|
|
|
|
|
}; |
27
|
1
|
|
|
|
|
4
|
$self->{_node} = {}; # contains a Referee::Node object |
28
|
1
|
|
|
|
|
3
|
$self->{_boardstr} = {}; |
29
|
1
|
|
|
|
|
3
|
$self->{_nodecount} = 0; |
30
|
1
|
|
|
|
|
3
|
$self->{_movecount} = 0; |
31
|
1
|
|
|
|
|
3
|
$self->{_passcount} = 0; |
32
|
1
|
|
|
|
|
3
|
$self->{_colour} = 'None'; |
33
|
1
|
|
|
|
|
4
|
$self->{_cellfarm} = {}; # eg key = 0,12 value = 'o','x', or '.' |
34
|
1
|
|
|
|
|
4
|
$self->{_errors} = []; # eg [3][12] where 3 is an error code, 12 the node it happened |
35
|
1
|
|
|
|
|
3
|
$self->{_prisonersB} = 0; |
36
|
1
|
|
|
|
|
2
|
$self->{_prisonersW} = 0; |
37
|
1
|
|
|
|
|
4
|
$self->{_sgf} = {}; # refererence to sgf file |
38
|
1
|
|
|
|
|
3
|
$self->{_coderef} = undef; |
39
|
1
|
|
|
|
|
3
|
$self->{_cellfarm}{','} = ''; # pass is empty |
40
|
1
|
|
|
|
|
3
|
$self->{_debug} = 0; |
41
|
1
|
|
|
|
|
8
|
$self->{_logfile} = './refereelog.txt'; |
42
|
1
|
|
|
|
|
4
|
bless $self, $class; |
43
|
1
|
|
|
|
|
12
|
$self->{_node}{0} = makenode($self, $self->{_colour}); |
44
|
1
|
|
|
|
|
3
|
return $self; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub sgffile{ |
48
|
2
|
|
|
2
|
1
|
168674
|
my ($self, $sgf_file, $p1, $p2) = @_; |
49
|
2
|
|
|
|
|
5
|
my $sgf; |
50
|
2
|
100
|
|
|
|
9
|
if (ref($sgf_file) eq 'Games::Go::SGF') { |
51
|
1
|
|
|
|
|
3
|
$sgf = $sgf_file; |
52
|
|
|
|
|
|
|
} else { |
53
|
1
|
|
|
|
|
11
|
$sgf = new Games::Go::SGF($sgf_file, $p1, $p2); |
54
|
1
|
50
|
|
|
|
143902
|
defined $sgf or croak "Bad Go sgf"; |
55
|
|
|
|
|
|
|
} |
56
|
2
|
|
|
|
|
12
|
restart($self); |
57
|
2
|
|
|
|
|
22
|
size($self, $sgf->SZ); |
58
|
2
|
|
|
|
|
26
|
initrules($self, $sgf->RU); |
59
|
2
|
|
|
|
|
17
|
$self->{_sgf} = $sgf; |
60
|
2
|
50
|
|
|
|
26
|
$self->{_const}{handicap} = $sgf->HA if $sgf->HA; |
61
|
2
|
|
|
|
|
27
|
my $clicker = 0; |
62
|
2
|
|
|
|
|
2
|
my $movecount = 0; |
63
|
|
|
|
|
|
|
|
64
|
2
|
|
|
|
|
18
|
while (my $node = $sgf->move($clicker++)) { |
65
|
114
|
|
|
|
|
717
|
$movecount = donode($self, $node, $movecount); |
66
|
|
|
|
|
|
|
} |
67
|
2
|
|
|
|
|
18
|
return Games::Go::SGF::getsgf($sgf); |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub donode { |
71
|
114
|
|
|
114
|
0
|
180
|
my ($self, $node, $movecount) = @_; |
72
|
114
|
100
|
|
|
|
354
|
if (ref($node) eq 'Games::Go::SGF::Node'){ |
73
|
112
|
50
|
66
|
|
|
340
|
if (ismove($node) or issetup($node)){ |
74
|
112
|
|
|
|
|
246
|
processtags($self, $node); |
75
|
112
|
|
|
|
|
191
|
$movecount++; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
} else { |
78
|
2
|
50
|
|
|
|
10
|
if (ref($node) eq 'Games::Go::SGF::Variation'){ |
79
|
0
|
|
|
|
|
0
|
dovar($self, $node, $movecount); |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
} |
82
|
114
|
|
|
|
|
1016
|
return $movecount |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub dovar { |
86
|
0
|
|
|
0
|
0
|
0
|
my ($self, $startpoint, $base) = @_; |
87
|
0
|
|
|
|
|
0
|
my $v = 0; |
88
|
0
|
|
|
|
|
0
|
my @vars = $startpoint->variations; |
89
|
|
|
|
|
|
|
|
90
|
0
|
|
|
|
|
0
|
while (defined $vars[$v]){ |
91
|
0
|
|
|
|
|
0
|
my $basenumber = $base; |
92
|
0
|
0
|
|
|
|
0
|
restore($self, $base) unless $v == 0; |
93
|
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
0
|
for (@{$vars[$v++]}){ |
|
0
|
|
|
|
|
0
|
|
95
|
0
|
|
|
|
|
0
|
$basenumber = donode($self, $_, $basenumber); |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
sub _iterboard (&$) { |
103
|
122
|
|
|
122
|
|
172
|
my ($sub, $size) = @_; |
104
|
122
|
|
|
|
|
218
|
for my $y (0..$size){ |
105
|
2318
|
|
|
|
|
3704
|
for my $x (0..$size){ |
106
|
44042
|
|
|
|
|
73148
|
$sub->($x, $y); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub size { |
112
|
2
|
|
|
2
|
1
|
39
|
my ($self, $size) = @_; |
113
|
2
|
|
|
|
|
2
|
my $adjust = 1; |
114
|
2
|
|
50
|
|
|
9
|
$size ||= 19; |
115
|
2
|
|
|
|
|
9
|
$self->{_const}{size} = _numbersetting($self, $size, 'size', $adjust); |
116
|
2
|
|
|
|
|
8
|
clearboard($self); |
117
|
2
|
|
|
|
|
6
|
return $self->{_const}{size} |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
0
|
|
|
0
|
1
|
0
|
sub ruleset { &initrules } |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub debug { |
123
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
124
|
0
|
|
|
|
|
0
|
my $debug = shift; |
125
|
0
|
0
|
0
|
|
|
0
|
$self->{_debug} = $debug if defined $debug and $debug =~ /0|1/; |
126
|
0
|
|
|
|
|
0
|
return $self->{_debug} |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub logfile { |
130
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
131
|
0
|
|
|
|
|
0
|
my $logfile = shift; |
132
|
0
|
0
|
|
|
|
0
|
$self->{_logfile} = $logfile if defined $logfile; |
133
|
0
|
|
|
|
|
0
|
return $self->{_logfile} |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub ssk { |
137
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
138
|
0
|
|
|
|
|
0
|
$self->{_const}{ssk} = _rulesetting($self, 'ssk', @_); |
139
|
0
|
|
|
|
|
0
|
return $self->{_const}{ssk} |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub alternation { |
143
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
144
|
0
|
|
|
|
|
0
|
$self->{_const}{alternation} = _rulesetting($self, 'alternation', @_); |
145
|
0
|
|
|
|
|
0
|
return $self->{_const}{alternation} |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub selfcapture { |
149
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
150
|
0
|
|
|
|
|
0
|
$self->{_const}{selfcapture} = _rulesetting($self, 'selfcapture', @_); |
151
|
0
|
|
|
|
|
0
|
return $self->{_const}{selfcapture} |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub exitonerror { |
155
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
156
|
0
|
|
|
|
|
0
|
$self->{_const}{exitonerror} = _rulesetting($self, 'exitonerror', @_); |
157
|
0
|
|
|
|
|
0
|
return $self->{_const}{exitonerror} |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub passes { |
161
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
162
|
0
|
|
|
|
|
0
|
$self->{_const}{passes} = _numbersetting($self, @_, 'passes', 0); |
163
|
0
|
|
|
|
|
0
|
return $self->{_const}{passes} |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub pointformat { |
167
|
1
|
|
|
1
|
0
|
1117
|
my $self = shift; |
168
|
1
|
50
|
|
|
|
6
|
if (@_) { |
169
|
1
|
|
|
|
|
2
|
my $format = shift ; |
170
|
1
|
50
|
33
|
|
|
7
|
if ($format eq 'sgf' or $format eq 'gtp') { |
171
|
1
|
|
|
|
|
4
|
$self->{_const}{pointformat} = $format; |
172
|
|
|
|
|
|
|
} else { |
173
|
0
|
0
|
|
|
|
0
|
croak 'Illegal value ', $format if defined $format; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
} |
176
|
1
|
|
|
|
|
6
|
return $self->{_const}{pointformat} |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
sub _numbersetting { |
180
|
2
|
|
|
2
|
|
5
|
my ($self, $value, $rule, $adjust) = @_; |
181
|
2
|
50
|
33
|
|
|
24
|
if ($value =~ /\d+/o and $value > 0) { |
182
|
2
|
|
|
|
|
9
|
$self->{_const}{$rule} = $value - $adjust; |
183
|
|
|
|
|
|
|
} else { |
184
|
0
|
|
|
|
|
0
|
croak 'Illegal value ', $value |
185
|
|
|
|
|
|
|
} |
186
|
2
|
|
|
|
|
8
|
return $self->{_const}{$rule} |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
sub _rulesetting { |
190
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
191
|
0
|
|
|
|
|
0
|
my $rule = shift; |
192
|
|
|
|
|
|
|
|
193
|
0
|
0
|
|
|
|
0
|
if (@_) { |
194
|
0
|
|
|
|
|
0
|
my $switch = shift; |
195
|
0
|
|
|
|
|
0
|
for ($switch) { |
196
|
0
|
0
|
|
|
|
0
|
if ($switch eq 'on') { |
197
|
0
|
|
|
|
|
0
|
$self->{_const}{$rule} = 1; |
198
|
0
|
|
|
|
|
0
|
last; |
199
|
|
|
|
|
|
|
} |
200
|
0
|
0
|
|
|
|
0
|
if ($switch eq 'off') { |
201
|
0
|
|
|
|
|
0
|
$self->{_const}{$rule} = 0; |
202
|
0
|
|
|
|
|
0
|
last; |
203
|
|
|
|
|
|
|
} |
204
|
0
|
|
|
|
|
0
|
croak 'Unknown setting'; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
} |
207
|
0
|
|
|
|
|
0
|
return $self->{_const}{$rule} |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub play { |
211
|
0
|
|
|
0
|
1
|
0
|
my ($self, $colour, $ab) = @_; |
212
|
0
|
0
|
|
|
|
0
|
croak 'Illegal move format' unless checkmove($self, $ab); |
213
|
0
|
0
|
0
|
|
|
0
|
if (($colour eq 'B') or ($colour eq 'W')) { |
214
|
0
|
|
|
|
|
0
|
$self->{_errors} = []; |
215
|
0
|
|
|
|
|
0
|
$self->{_node}{++$self->{_nodecount}} = makenode($self, $colour, $ab); |
216
|
0
|
|
|
|
|
0
|
move($self, $colour, $ab); |
217
|
|
|
|
|
|
|
} else { |
218
|
0
|
|
|
|
|
0
|
croak 'Colour not recognised'; |
219
|
|
|
|
|
|
|
} |
220
|
0
|
|
|
|
|
0
|
return errorcode($self); |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub setup { |
224
|
0
|
|
|
0
|
1
|
0
|
my ($self, $type, $ablist) = @_; |
225
|
0
|
|
|
|
|
0
|
for ($type) { |
226
|
0
|
0
|
|
|
|
0
|
if (',AB,AW,AE,' =~ /,($_),/) { |
227
|
0
|
|
|
|
|
0
|
$self->{_errors} = []; |
228
|
0
|
|
|
|
|
0
|
$self->{_node}{++$self->{_nodecount}} = makenode($self, 'None'); |
229
|
0
|
|
|
|
|
0
|
for (split (',', $ablist)){ changecell($self, $1, $_) } |
|
0
|
|
|
|
|
0
|
|
230
|
0
|
|
|
|
|
0
|
last; |
231
|
|
|
|
|
|
|
} |
232
|
0
|
|
|
|
|
0
|
croak 'Setup type not recognised'; |
233
|
|
|
|
|
|
|
} |
234
|
0
|
|
|
|
|
0
|
return errorcode($self); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub handicap { |
238
|
0
|
|
|
0
|
1
|
0
|
my ($self, $number) = @_; |
239
|
0
|
0
|
|
|
|
0
|
if ($number =~ /[2-9]/o){ |
240
|
0
|
0
|
|
|
|
0
|
if ($self->{_const}{hfree}){ |
241
|
0
|
|
|
|
|
0
|
$self->{_const}{handicap} = $number; |
242
|
|
|
|
|
|
|
} else { |
243
|
0
|
0
|
|
|
|
0
|
if ($self->{_const}{size} == 18){ |
244
|
0
|
|
|
|
|
0
|
my @hpoints = ('dp','pd','pp','dd','jj','dj','pj','jd','jp'); |
245
|
0
|
0
|
|
|
|
0
|
splice @hpoints, 4, 1 if $number % 2 == 0; |
246
|
0
|
|
|
|
|
0
|
splice @hpoints, $number; |
247
|
0
|
|
|
|
|
0
|
setup($self, 'AB', join ',', @hpoints); |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
} else { |
251
|
0
|
|
|
|
|
0
|
croak 'Handicap not allowed'; |
252
|
|
|
|
|
|
|
} |
253
|
0
|
|
|
|
|
0
|
return errorcode($self); |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# return true if a co-ordinate pair is a legal move |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
sub islegal { |
259
|
0
|
|
|
0
|
0
|
0
|
my ($self, $colour, $point) = @_; |
260
|
0
|
|
|
|
|
0
|
my $res = play($self, $colour, $point); |
261
|
0
|
0
|
|
|
|
0
|
myprint ($self, $colour, $point, 'has legality:', $res) if $self->{_debug}; |
262
|
0
|
|
|
|
|
0
|
restore($self, -1); |
263
|
0
|
0
|
|
|
|
0
|
return $res?0:1 |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# return a list of the co-ordinates of all legal moves |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
sub legal { |
269
|
0
|
|
|
0
|
1
|
0
|
my ($self, $colour) = @_; |
270
|
0
|
|
|
|
|
0
|
my @legallist; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
_iterboard { |
273
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
274
|
0
|
0
|
|
|
|
0
|
if ($self->{_cellfarm}{$x.','.$y} eq '.') { |
275
|
0
|
|
|
|
|
0
|
my $point = insertpoints($self, $x, $y); |
276
|
0
|
0
|
|
|
|
0
|
push @legallist, $point unless play($self, $colour, $point); |
277
|
0
|
|
|
|
|
0
|
restore($self, -1); |
278
|
|
|
|
|
|
|
} |
279
|
0
|
|
|
|
|
0
|
} $self->{_const}{size}; |
280
|
|
|
|
|
|
|
|
281
|
0
|
|
|
|
|
0
|
return @legallist; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# return a list of the co-ordinates of all illegal moves |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub illegal { |
287
|
0
|
|
|
0
|
0
|
0
|
my ($self, $colour) = @_; |
288
|
0
|
|
|
|
|
0
|
my @illegallist; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
_iterboard { |
291
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
292
|
0
|
0
|
|
|
|
0
|
if ($self->{_cellfarm}{$x.','.$y} eq '.') { |
293
|
0
|
|
|
|
|
0
|
my $point = insertpoints($self, $x, $y); |
294
|
0
|
0
|
|
|
|
0
|
push @illegallist, $point if play($self, $colour, $point); |
295
|
0
|
|
|
|
|
0
|
restore($self, -1); |
296
|
|
|
|
|
|
|
} |
297
|
0
|
|
|
|
|
0
|
} $self->{_const}{size}; |
298
|
|
|
|
|
|
|
|
299
|
0
|
|
|
|
|
0
|
return @illegallist; |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# return true if $colour (ie 'B' or 'W') has a legal move, otherwise return false |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
sub haslegal { |
305
|
0
|
|
|
0
|
1
|
0
|
my ($self, $colour) = @_; |
306
|
0
|
|
|
|
|
0
|
my $exit = 0; |
307
|
0
|
|
|
|
|
0
|
my $size = $self->{_const}{size}; |
308
|
0
|
|
|
|
|
0
|
for my $y (0..$size){ |
309
|
0
|
|
|
|
|
0
|
for my $x (0..$size){ |
310
|
0
|
0
|
|
|
|
0
|
if ($self->{_cellfarm}{$x.','.$y} eq '.') { |
311
|
0
|
0
|
|
|
|
0
|
$exit = 1 unless play($self, $colour, insertpoints($self, $x, $y)); |
312
|
0
|
|
|
|
|
0
|
restore($self, -1); |
313
|
0
|
0
|
|
|
|
0
|
return 1 if $exit; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
} |
317
|
0
|
|
|
|
|
0
|
return 0; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# return a ':' seperated list of the co-ordinates of any captured stones |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
sub captures { |
323
|
0
|
|
|
0
|
0
|
0
|
my ($self, $id) = @_; |
324
|
0
|
|
0
|
|
|
0
|
$id ||= $self->{_nodecount}; |
325
|
0
|
|
|
|
|
0
|
my $s = ''; |
326
|
0
|
|
|
|
|
0
|
my $capsref = $self->{_node}{$id}->captures; |
327
|
0
|
0
|
|
|
|
0
|
if ($capsref) { |
328
|
0
|
|
|
|
|
0
|
my @delstones = @{$capsref}; |
|
0
|
|
|
|
|
0
|
|
329
|
0
|
|
|
|
|
0
|
my $seperator = ':'; |
330
|
0
|
|
|
|
|
0
|
for my $i (0..$#delstones) { |
331
|
0
|
0
|
|
|
|
0
|
$seperator = '' if $i == $#delstones; |
332
|
0
|
|
|
|
|
0
|
$s .= insertpoints($self, ($delstones[$i][0]), ($delstones[$i][1])).$seperator; |
333
|
|
|
|
|
|
|
} |
334
|
|
|
|
|
|
|
} |
335
|
0
|
|
|
|
|
0
|
return $s |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
# restore the game to that at move $howmany |
339
|
|
|
|
|
|
|
# if $howmany is negative, go back that number of moves. |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub restore{ |
342
|
0
|
|
|
0
|
0
|
0
|
my ($self, $howmany) = @_; |
343
|
0
|
0
|
|
|
|
0
|
croak 'Cannot restore to ', $howmany if (abs($howmany) > $self->{_nodecount}); |
344
|
0
|
0
|
|
|
|
0
|
$howmany += $self->{_nodecount} if ($howmany < 0); |
345
|
0
|
|
|
|
|
0
|
boardrestore($self, $howmany); |
346
|
0
|
|
|
|
|
0
|
deletenodes($self, $howmany); |
347
|
0
|
|
|
|
|
0
|
$self->{_nodecount} = $howmany; |
348
|
0
|
|
|
|
|
0
|
my $node = $self->{_node}{$howmany}; |
349
|
0
|
|
|
|
|
0
|
$self->{_movecount} = $node->movecount; |
350
|
0
|
|
|
|
|
0
|
$self->{_colour} = $node->colour; |
351
|
0
|
|
|
|
|
0
|
$self->{_passcount} = $node->passcount; |
352
|
|
|
|
|
|
|
return |
353
|
0
|
|
|
|
|
0
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# return the board as a string |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub showboard{ |
358
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
359
|
0
|
|
|
|
|
0
|
my $h; |
360
|
0
|
|
|
|
|
0
|
my $size = $self->{_const}{size}; |
361
|
|
|
|
|
|
|
_iterboard { |
362
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
363
|
0
|
|
|
|
|
0
|
$h .= $self->{_cellfarm}{$x.','.$y}; |
364
|
0
|
0
|
|
|
|
0
|
$h .= "\n" if $x == $size; |
365
|
0
|
|
|
|
|
0
|
} $size; |
366
|
0
|
|
|
|
|
0
|
$h .= "\n"; |
367
|
0
|
|
|
|
|
0
|
return $h; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# return a section of the board as a string |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
sub getboardsection{ |
373
|
0
|
|
|
0
|
0
|
0
|
my ($self, $ox, $oy, $size) = @_; |
374
|
0
|
|
|
|
|
0
|
my $h; |
375
|
|
|
|
|
|
|
_iterboard { |
376
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
377
|
0
|
|
|
|
|
0
|
my $xnew = $x + $ox; |
378
|
0
|
|
|
|
|
0
|
my $ynew = $y + $oy; |
379
|
0
|
|
0
|
|
|
0
|
$h .= $self->{_cellfarm}{$xnew.','.$ynew} || '-'; |
380
|
0
|
|
|
|
|
0
|
} $size; |
381
|
0
|
|
|
|
|
0
|
return $h; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# get contents of a point |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub point{ |
387
|
0
|
|
|
0
|
0
|
0
|
my ($self, $ab, $y) = @_; |
388
|
0
|
0
|
|
|
|
0
|
($ab, $y) = extractpoints($self, $ab) unless defined($y); |
389
|
0
|
|
|
|
|
0
|
return $self->{_cellfarm}{$ab.','.$y}; |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
# get contents of a point at a particular move |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub nodepoint{ |
395
|
0
|
|
|
0
|
0
|
0
|
my ($self, $id, $x, $y) = @_; |
396
|
0
|
|
|
|
|
0
|
my $positionref = $self->{_node}{$id}->board; |
397
|
0
|
|
|
|
|
0
|
return substr($$positionref, ($y * ($self->{_const}{size} + 1)) + $x, 1) |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# get the co-ordinates of move number '$counter' |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
sub getmove { |
403
|
0
|
|
|
0
|
0
|
0
|
my ($self, $counter) = @_; |
404
|
0
|
|
|
|
|
0
|
my $node = $self->{_node}{$counter}; |
405
|
0
|
0
|
|
|
|
0
|
return $node->colour, $node->point if defined $node; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
#restore the board position to that of move number $id |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
sub boardrestore{ |
411
|
0
|
|
|
0
|
0
|
0
|
my ($self, $id) = @_; |
412
|
0
|
0
|
|
|
|
0
|
myprint ($self, 'Restoring to', $id) if $self->{_debug}; |
413
|
0
|
|
|
|
|
0
|
my $positionref = $self->{_node}{$id}->board; |
414
|
0
|
|
|
|
|
0
|
my $size = $self->{_const}{size}; |
415
|
|
|
|
|
|
|
_iterboard { |
416
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
417
|
0
|
|
|
|
|
0
|
$self->{_cellfarm}{$x.','.$y} = substr($$positionref, ($y*($size+1))+ $x, 1); |
418
|
0
|
|
|
|
|
0
|
} $size; |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
sub deletenodes { |
422
|
0
|
|
|
0
|
0
|
0
|
my ($self, $upperB) = @_; |
423
|
0
|
|
|
|
|
0
|
for (keys %{$self->{_node}}) { |
|
0
|
|
|
|
|
0
|
|
424
|
0
|
0
|
|
|
|
0
|
if ($_ > $upperB) { |
425
|
0
|
|
|
|
|
0
|
my $board = $self->{_node}{$_}->board; |
426
|
0
|
0
|
|
|
|
0
|
delete $self->{_boardstr}{$$board} if defined $board; |
427
|
0
|
|
|
|
|
0
|
delete $self->{_node}{$_}; |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
#save the board position as a reference to a string |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub store{ |
435
|
120
|
|
|
120
|
0
|
191
|
my $self = shift; |
436
|
120
|
|
|
|
|
168
|
my $h = ''; |
437
|
|
|
|
|
|
|
_iterboard { |
438
|
43320
|
|
|
43320
|
|
70305
|
my ($x, $y) = @_; |
439
|
43320
|
50
|
|
|
|
112108
|
die 'Undefined Value'."$!\n" unless defined $self->{_cellfarm}{$x.','.$y}; |
440
|
43320
|
|
|
|
|
89878
|
$h .= $self->{_cellfarm}{$x.','.$y}; |
441
|
120
|
|
|
|
|
798
|
} $self->{_const}{size}; |
442
|
120
|
|
|
|
|
950
|
return \$h; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
# Change the value of a cell |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
sub put_cell{ |
448
|
148
|
|
|
148
|
0
|
248
|
my ($self, $where, $what) = @_; |
449
|
148
|
50
|
66
|
|
|
696
|
if ($what ne '.' and $self->{_cellfarm}{$where} ne '.'){ |
450
|
0
|
|
|
|
|
0
|
return 1 |
451
|
|
|
|
|
|
|
} else { |
452
|
148
|
|
|
|
|
283
|
$self->{_cellfarm}{$where} = $what; |
453
|
148
|
|
|
|
|
426
|
return 0 |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
sub delete_group{ |
458
|
32
|
|
|
32
|
0
|
63
|
my ($self, @mygroup) = @_; |
459
|
32
|
|
|
|
|
72
|
for (0..$#mygroup) { |
460
|
36
|
|
|
|
|
148
|
put_cell($self, $mygroup[$_][0].','.$mygroup[$_][1], '.'); |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
# return a list of the points solidly connected to x,y |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
sub block{ |
467
|
0
|
|
|
0
|
0
|
0
|
my ($self, $x, $y, $c, $group) = @_; |
468
|
0
|
0
|
|
|
|
0
|
unless (offboard($self->{_const}{size}, $x, $y)) { |
469
|
0
|
|
|
|
|
0
|
my $key = "$x,$y"; |
470
|
0
|
0
|
|
|
|
0
|
if ($self->{_cellfarm}{$key} eq $c) { |
471
|
0
|
|
|
|
|
0
|
$group->{$key} = undef; # create a hash key |
472
|
0
|
|
|
|
|
0
|
my @directions = ([1,0],[0,1],[-1,0],[0,-1]); |
473
|
|
|
|
|
|
|
|
474
|
0
|
|
|
|
|
0
|
for (0..3) { |
475
|
0
|
|
|
|
|
0
|
my $xx = $directions[$_][0] + $x; |
476
|
0
|
|
|
|
|
0
|
my $yy = $directions[$_][1] + $y; |
477
|
0
|
0
|
|
|
|
0
|
unless (exists($group->{"$xx,$yy"})) { |
478
|
0
|
|
|
|
|
0
|
$group = block($self, $xx, $yy, $c, $group); |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
} |
484
|
0
|
|
|
|
|
0
|
return $group; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
sub libertycheck{ |
488
|
1568
|
|
|
1568
|
0
|
2585
|
my ($self, $x, $y, $c, $haslibs, $group) = @_; |
489
|
1568
|
100
|
100
|
|
|
4745
|
unless ($haslibs or offboard($self->{_const}{size}, $x, $y)) { |
490
|
984
|
|
|
|
|
1619
|
my $key = "$x,$y"; |
491
|
984
|
|
|
|
|
1775
|
my $cellcontents = $self->{_cellfarm}{$key}; |
492
|
984
|
100
|
|
|
|
1692
|
if ($cellcontents eq $c) { |
493
|
268
|
|
|
|
|
630
|
$group->{$key} = undef; |
494
|
268
|
|
|
|
|
1006
|
my @directions = ([1,0],[0,1],[-1,0],[0,-1]); |
495
|
|
|
|
|
|
|
|
496
|
268
|
|
|
|
|
501
|
for (0..3) { |
497
|
1072
|
|
|
|
|
1682
|
my $xx = $directions[$_][0] + $x; |
498
|
1072
|
|
|
|
|
1315
|
my $yy = $directions[$_][1] + $y; |
499
|
1072
|
100
|
|
|
|
2577
|
unless (exists($group->{"$xx,$yy"})) { |
500
|
1036
|
|
|
|
|
1843
|
($haslibs, $group) = libertycheck($self, $xx, $yy, $c, $haslibs, $group); |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
} else { |
505
|
716
|
|
|
|
|
1145
|
$haslibs = $cellcontents eq '.'; |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
} |
508
|
1568
|
|
|
|
|
5106
|
return $haslibs, $group; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
sub checkforcaptures{ |
512
|
196
|
|
|
196
|
0
|
303
|
my ($self, $x, $y, $colour, $type) = @_; |
513
|
196
|
|
|
|
|
223
|
my $capturedSomething = 0; |
514
|
196
|
100
|
|
|
|
865
|
my @directions = ($type eq 'self') ? ([0,0]) : ([1,0],[0,1],[-1,0],[0,-1]); |
515
|
196
|
|
|
|
|
310
|
my @deletedstones; |
516
|
|
|
|
|
|
|
|
517
|
196
|
|
|
|
|
451
|
for (0..$#directions) { |
518
|
532
|
|
|
|
|
861
|
my $xdir = $directions[$_][0]+$x; |
519
|
532
|
|
|
|
|
670
|
my $ydir = $directions[$_][1]+$y; |
520
|
532
|
|
|
|
|
1158
|
my ($haslibs, $points) = libertycheck($self, $xdir, $ydir, $colour, 0, {}); |
521
|
532
|
100
|
100
|
|
|
636
|
if (keys(%{$points}) and not $haslibs) { |
|
532
|
|
|
|
|
2643
|
|
522
|
32
|
|
|
|
|
84
|
my $pointsref = getpoints($points); |
523
|
32
|
|
|
|
|
48
|
delete_group($self, @{$pointsref}); |
|
32
|
|
|
|
|
95
|
|
524
|
32
|
|
|
|
|
52
|
push @deletedstones, @{$pointsref}; |
|
32
|
|
|
|
|
57
|
|
525
|
32
|
|
|
|
|
102
|
$capturedSomething = 1; |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
196
|
|
|
|
|
734
|
return $capturedSomething, \@deletedstones |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
# main move handler and error detector |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
sub processmove{ |
535
|
110
|
|
|
110
|
0
|
185
|
my ($self, $colour, $ab) = @_; |
536
|
110
|
|
|
|
|
177
|
my $id = $self->{_nodecount}; |
537
|
110
|
100
|
|
|
|
273
|
my $c = ($colour eq 'W')?'o':'x'; |
538
|
110
|
|
|
|
|
213
|
my $noderef = \$self->{_node}{$id}; |
539
|
110
|
|
|
|
|
197
|
my $move = $self->{_movecount}; |
540
|
110
|
50
|
|
|
|
295
|
if (defined $self->{_coderef}) { |
541
|
0
|
|
|
|
|
0
|
my $rank = $colour.'R'; |
542
|
0
|
0
|
|
|
|
0
|
myprint ($self, 'learning from move', $id) if $self->{_debug}; |
543
|
0
|
|
|
|
|
0
|
$self->{_coderef}->learn($colour, $ab, $self, $move, $self->{_sgf}->$rank); |
544
|
|
|
|
|
|
|
} |
545
|
110
|
100
|
66
|
|
|
361
|
if ($colour eq $self->{_colour} and $self->{_const}{alternation}){ |
546
|
4
|
50
|
33
|
|
|
23
|
unless ($id <= $self->{_const}{handicap} and $self->{_const}{hfree}) { |
547
|
4
|
|
|
|
|
16
|
adderror($self, 7, $move); |
548
|
4
|
50
|
|
|
|
27
|
return if $self->{_const}{exitonerror} |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
} |
551
|
110
|
|
|
|
|
157
|
$self->{_colour} = $colour; |
552
|
110
|
|
|
|
|
215
|
my $size = $self->{_const}{size}; |
553
|
110
|
100
|
|
|
|
248
|
if (ispass($self, $ab)) { |
554
|
6
|
|
|
|
|
30
|
$$noderef->passcount(++$self->{_passcount}); |
555
|
6
|
|
|
|
|
18
|
$$noderef->board(store($self)); |
556
|
|
|
|
|
|
|
} else { |
557
|
104
|
100
|
66
|
|
|
368
|
if ($self->{_passcount} >= $self->{_const}{passes} and $self->{_const}{passcount}) { |
558
|
2
|
|
|
|
|
9
|
adderror($self, 8, $move); |
559
|
2
|
50
|
|
|
|
10
|
return if $self->{_const}{exitonerror}; |
560
|
|
|
|
|
|
|
} |
561
|
104
|
|
|
|
|
149
|
$self->{_passcount} = 0; |
562
|
104
|
|
|
|
|
368
|
$$noderef->passcount(0); |
563
|
104
|
|
|
|
|
272
|
my ($x, $y) = extractpoints($self, $ab); |
564
|
104
|
50
|
|
|
|
247
|
if (offboard($size, $x, $y)) { |
565
|
0
|
|
|
|
|
0
|
adderror($self, 1, $move); |
566
|
0
|
0
|
|
|
|
0
|
return if $self->{_const}{exitonerror}; |
567
|
|
|
|
|
|
|
} else { |
568
|
104
|
50
|
|
|
|
371
|
if (put_cell($self, "$x,$y", $c)) { |
569
|
0
|
|
|
|
|
0
|
adderror($self, 2, $move); |
570
|
0
|
0
|
|
|
|
0
|
return if $self->{_const}{exitonerror}; |
571
|
|
|
|
|
|
|
} |
572
|
104
|
|
|
|
|
262
|
my ($captured, $delstonesref, $error) = checkbothcaptures($self, $x, $y, $c, 1); |
573
|
104
|
|
|
|
|
193
|
my $ctype = '_prisoners'.$colour; |
574
|
104
|
|
|
|
|
226
|
$self->{$ctype} += @$delstonesref; |
575
|
104
|
100
|
|
|
|
219
|
if ($error) { |
576
|
4
|
|
|
|
|
13
|
adderror($self, 5, $move); |
577
|
4
|
50
|
|
|
|
13
|
return if $self->{_const}{exitonerror}; |
578
|
|
|
|
|
|
|
} |
579
|
104
|
100
|
|
|
|
315
|
$$noderef->captures($delstonesref) if $captured; |
580
|
104
|
|
|
|
|
199
|
my $board = store($self); |
581
|
104
|
100
|
|
|
|
572
|
if (exists $self->{_boardstr}{$$board}) { |
582
|
6
|
50
|
|
|
|
24
|
if ($self->{_const}{ssk}) { |
583
|
0
|
|
|
|
|
0
|
adderror($self, 6, $move); |
584
|
0
|
0
|
|
|
|
0
|
return if $self->{_const}{exitonerror}; |
585
|
|
|
|
|
|
|
} else { |
586
|
6
|
|
|
|
|
21
|
adderror($self, 6, $move); |
587
|
6
|
50
|
|
|
|
25
|
return if $self->{_const}{exitonerror}; |
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
} else { |
590
|
98
|
|
|
|
|
587
|
$self->{_boardstr}{$$board} = $colour; |
591
|
|
|
|
|
|
|
} |
592
|
104
|
|
|
|
|
501
|
$$noderef->board($board); # store the board in a Node as a string |
593
|
104
|
50
|
|
|
|
258
|
myprint ($self, 'Node id', $id) if $self->{_debug}; |
594
|
104
|
50
|
|
|
|
323
|
myprint ($self, showboard($self)) if $self->{_debug}; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
} |
597
|
110
|
|
|
|
|
483
|
return 1 |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# change a value in cellfarm |
601
|
|
|
|
|
|
|
# used when AB, AW, and AE tags found |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
sub changecell{ |
604
|
8
|
|
|
8
|
0
|
15
|
my ($self, $colour, $point) = @_; |
605
|
8
|
|
|
|
|
10
|
my $c; |
606
|
8
|
|
|
|
|
16
|
SWITCH:for ($colour) { |
607
|
8
|
100
|
|
|
|
22
|
if ($_ eq 'AW') {$c = 'o'; last} |
|
6
|
|
|
|
|
9
|
|
|
6
|
|
|
|
|
12
|
|
608
|
2
|
50
|
|
|
|
8
|
if ($_ eq 'AB') {$c = 'x'; last} |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
5
|
|
609
|
0
|
|
|
|
|
0
|
$c = '.'; |
610
|
|
|
|
|
|
|
} |
611
|
8
|
|
|
|
|
14
|
my $id = $self->{_nodecount}; |
612
|
8
|
|
|
|
|
24
|
my ($x, $y) = extractpoints($self, $point); |
613
|
8
|
|
|
|
|
15
|
my $size = $self->{_const}{size}; |
614
|
8
|
50
|
|
|
|
25
|
if (offboard($size, $x, $y)) { |
615
|
0
|
|
|
|
|
0
|
adderror($self, 9, $id); |
616
|
|
|
|
|
|
|
} else { |
617
|
8
|
50
|
|
|
|
31
|
adderror($self, 4, $id) if (put_cell($self, "$x,$y", $c)); |
618
|
8
|
50
|
|
|
|
119
|
unless ($c eq '.'){ |
619
|
8
|
|
|
|
|
22
|
my ($capturedSomething, undef) = checkbothcaptures($self, $x, $y, $c, 0); |
620
|
8
|
100
|
|
|
|
22
|
if ($capturedSomething) { |
621
|
2
|
|
|
|
|
8
|
adderror($self, 5, $id); |
622
|
2
|
50
|
|
|
|
9
|
return if $self->{_const}{exitonerror}; |
623
|
|
|
|
|
|
|
} |
624
|
|
|
|
|
|
|
} |
625
|
8
|
|
|
|
|
26
|
$self->{_node}{$id}->board(store($self)); |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
sub checkbothcaptures { |
630
|
112
|
|
|
112
|
0
|
192
|
my ($self, $x, $y, $c, $movetype) = @_; |
631
|
112
|
|
|
|
|
136
|
my $myerror = 0; |
632
|
112
|
100
|
|
|
|
345
|
my $reversec = ($c eq 'o')?'x':'o'; # reverse colours |
633
|
112
|
|
|
|
|
258
|
my ($capturedsomething, $delstonesref) = checkforcaptures($self, $x, $y, $reversec, 'opponents'); |
634
|
112
|
100
|
|
|
|
265
|
unless ($capturedsomething){ |
635
|
84
|
|
|
|
|
172
|
($capturedsomething, $delstonesref) = checkforcaptures($self, $x, $y, $c, 'self'); |
636
|
84
|
100
|
66
|
|
|
251
|
$myerror = 1 if ($capturedsomething and not $self->{_const}{selfcapture}); |
637
|
|
|
|
|
|
|
} |
638
|
112
|
|
|
|
|
272
|
return $capturedsomething, $delstonesref, $myerror; |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
sub move { |
642
|
110
|
|
|
110
|
0
|
1023
|
my $self = shift; |
643
|
110
|
|
|
|
|
184
|
$self->{_movecount}++; |
644
|
110
|
|
|
|
|
226
|
return processmove($self, @_); |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
sub processtags { |
648
|
112
|
|
|
112
|
0
|
185
|
my ($self, $sgfnode) = @_; |
649
|
112
|
|
|
|
|
413
|
$self->{_node}{++$self->{_nodecount}} = makenode($self, $sgfnode->colour, $sgfnode->move); |
650
|
|
|
|
|
|
|
|
651
|
112
|
|
|
|
|
376
|
for (split (',',$sgfnode->tags)){ |
652
|
246
|
100
|
100
|
|
|
1480
|
if (($_ eq 'B') or ($_ eq 'W')) { |
653
|
110
|
50
|
|
|
|
307
|
return unless move($self, $sgfnode->colour, $sgfnode->move); |
654
|
110
|
|
|
|
|
447
|
next; |
655
|
|
|
|
|
|
|
} |
656
|
136
|
100
|
|
|
|
783
|
if (',AB,AW,AE,' =~ /,($_),/) { |
657
|
4
|
|
|
|
|
13
|
my $tag = $1; |
658
|
4
|
|
|
|
|
43
|
for (split (',', $sgfnode->$tag)) { |
659
|
8
|
50
|
|
|
|
68
|
if ( $_ =~ /(..):(..)/) { |
660
|
0
|
|
|
|
|
0
|
my $arrayref = generaterectangle($self, $1, $2); |
661
|
0
|
|
|
|
|
0
|
for (@$arrayref) {changecell($self, $tag, $_)}; |
|
0
|
|
|
|
|
0
|
|
662
|
|
|
|
|
|
|
} else { |
663
|
8
|
|
|
|
|
23
|
changecell($self, $tag, $_); |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
} |
666
|
4
|
|
|
|
|
18
|
next; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
} |
669
|
|
|
|
|
|
|
|
670
|
112
|
|
|
|
|
303
|
return 1 |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
sub generaterectangle { |
674
|
0
|
|
|
0
|
0
|
0
|
my ($self, $topleft, $bottomright) = @_; |
675
|
0
|
|
|
|
|
0
|
my @pointlist; |
676
|
0
|
|
|
|
|
0
|
my ($tx, $ty) = extractpoints($self, $topleft); |
677
|
0
|
|
|
|
|
0
|
my ($bx, $by) = extractpoints($self, $bottomright); |
678
|
0
|
|
|
|
|
0
|
for my $x ($tx..$bx) { |
679
|
0
|
|
|
|
|
0
|
for my $y ($ty..$by) { |
680
|
0
|
|
|
|
|
0
|
push @pointlist, insertpoints($self, $x, $y); |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
} |
683
|
0
|
|
|
|
|
0
|
return \@pointlist; |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
# list all the stones of a particular colour |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
sub liststones { |
689
|
0
|
|
|
0
|
0
|
0
|
my ($self, $colour) = @_; |
690
|
0
|
0
|
|
|
|
0
|
my $stone = ($colour eq 'B') ? 'x' : 'o'; |
691
|
0
|
|
|
|
|
0
|
my %hash; |
692
|
|
|
|
|
|
|
_iterboard { |
693
|
0
|
|
|
0
|
|
0
|
my ($x, $y) = @_; |
694
|
0
|
0
|
|
|
|
0
|
if ($self->{_cellfarm}{$x.','.$y} eq $stone) { |
695
|
0
|
|
|
|
|
0
|
$hash{$x.','.$y} = undef; |
696
|
|
|
|
|
|
|
} |
697
|
0
|
|
|
|
|
0
|
} $self->{_const}{size}; |
698
|
0
|
|
|
|
|
0
|
return \%hash |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
# list all the live stones of a particular colour |
702
|
|
|
|
|
|
|
# (as the set of all blocks adjacent to their opponent's illegal moves) |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
sub listalive { |
705
|
0
|
|
|
0
|
0
|
0
|
my ($self, $colour) = @_; |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# turn off alternation and passcount errors temporarily |
708
|
0
|
|
|
|
|
0
|
$self->{_const}{passcount} = 0; |
709
|
0
|
|
|
|
|
0
|
$self->{_const}{alternation} = 0; |
710
|
|
|
|
|
|
|
# first get the list of illegal moves for the other player |
711
|
0
|
|
|
|
|
0
|
my @illegallist = illegal($self, swapcolour($self, $colour)); |
712
|
0
|
|
|
|
|
0
|
my $points = {}; |
713
|
0
|
0
|
|
|
|
0
|
my $stone = ($colour eq 'B') ? 'x' : 'o'; |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
# now get the blocks attached to those illegal points |
716
|
0
|
|
|
|
|
0
|
for (@illegallist) { |
717
|
0
|
|
|
|
|
0
|
my ($x, $y) = extractpoints($self, $_); |
718
|
0
|
|
|
|
|
0
|
my @directions = ([1,0],[0,1],[-1,0],[0,-1]); |
719
|
0
|
|
|
|
|
0
|
for (0..3) { |
720
|
0
|
|
|
|
|
0
|
my $xdir = $directions[$_][0]+$x; |
721
|
0
|
|
|
|
|
0
|
my $ydir = $directions[$_][1]+$y; |
722
|
0
|
|
|
|
|
0
|
$points = block($self, $xdir, $ydir, $stone, $points); |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
} |
725
|
0
|
|
|
|
|
0
|
$self->{_const}{passcount} = 1; |
726
|
0
|
|
|
|
|
0
|
$self->{_const}{alternation} = 1; |
727
|
0
|
|
|
|
|
0
|
return $points |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
# list the dead stones of a particular colour |
731
|
|
|
|
|
|
|
# (as the difference between their alive list |
732
|
|
|
|
|
|
|
# and their total list) |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
sub listdead { |
735
|
0
|
|
|
0
|
0
|
0
|
my ($self, $colour) = @_; |
736
|
0
|
|
|
|
|
0
|
my $allref = liststones($self, $colour); |
737
|
0
|
|
|
|
|
0
|
my $aliveref = listalive($self, $colour); |
738
|
0
|
|
|
|
|
0
|
my @dead = (); |
739
|
0
|
|
|
|
|
0
|
for (keys %$allref) { |
740
|
0
|
0
|
|
|
|
0
|
push @dead, $_ unless exists $aliveref->{$_}; |
741
|
|
|
|
|
|
|
} |
742
|
0
|
|
|
|
|
0
|
@dead = map { |
743
|
0
|
|
|
|
|
0
|
/(.*),(.*)/; |
744
|
0
|
|
|
|
|
0
|
insertpoints($self, $1, $2) |
745
|
|
|
|
|
|
|
} @dead; |
746
|
|
|
|
|
|
|
return \@dead |
747
|
0
|
|
|
|
|
0
|
} |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
# list all the dead stones on the board |
750
|
|
|
|
|
|
|
# (as the union of the Black and White |
751
|
|
|
|
|
|
|
# dead stone list) |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
sub listalldead { |
754
|
0
|
|
|
0
|
0
|
0
|
my ($self) = @_; |
755
|
0
|
|
|
|
|
0
|
my $bdead = listdead($self, 'B'); |
756
|
0
|
|
|
|
|
0
|
my $wdead = listdead($self, 'W'); |
757
|
0
|
|
|
|
|
0
|
my @dead = (@$bdead, @$wdead); |
758
|
|
|
|
|
|
|
return \@dead |
759
|
0
|
|
|
|
|
0
|
} |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
sub ismove { |
762
|
112
|
100
|
|
112
|
0
|
278
|
testnode(shift, ',B,W,') ? return 1 : return 0 |
763
|
|
|
|
|
|
|
} |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
sub issetup { |
766
|
2
|
50
|
|
2
|
0
|
5
|
testnode(shift, ',AB,AW,AE,') ? return 1 : return 0 |
767
|
|
|
|
|
|
|
} |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
sub testnode{ |
770
|
114
|
|
|
114
|
0
|
269
|
my ($sgfnode, $type) = @_; |
771
|
114
|
50
|
|
|
|
359
|
if ($sgfnode->tags){ |
772
|
114
|
|
|
|
|
865
|
for (split (',',$sgfnode->tags)){ |
773
|
270
|
100
|
|
|
|
4161
|
if ($type =~ /,$_,/) { |
774
|
112
|
|
|
|
|
631
|
return 1; |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
} |
778
|
2
|
|
|
|
|
23
|
return 0 |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub restart { |
782
|
2
|
|
|
2
|
0
|
4
|
my $self = shift; |
783
|
2
|
|
|
|
|
9
|
$self->{_node} = {}; |
784
|
2
|
|
|
|
|
235
|
$self->{_boardstr} = {}; |
785
|
2
|
|
|
|
|
26
|
$self->{_nodecount} = 0; |
786
|
2
|
|
|
|
|
5
|
$self->{_movecount} = 0; |
787
|
2
|
|
|
|
|
6
|
$self->{_passcount} = 0; |
788
|
2
|
|
|
|
|
4
|
$self->{_colour} = 'None'; |
789
|
2
|
|
|
|
|
6
|
$self->{_cellfarm} = {}; |
790
|
2
|
|
|
|
|
167
|
$self->{_errors} = []; |
791
|
2
|
|
|
|
|
14
|
$self->{_prisonersB} = 0; |
792
|
2
|
|
|
|
|
4
|
$self->{_prisonersW} = 0; |
793
|
2
|
|
|
|
|
4
|
$self->{_sgf} = {}; |
794
|
2
|
|
|
|
|
81
|
$self->{_node}{0} = makenode($self, $self->{_colour}); |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
sub initrules { |
798
|
2
|
|
|
2
|
0
|
37
|
my $self = shift; |
799
|
2
|
|
|
|
|
7
|
my $rules = uc(shift); |
800
|
|
|
|
|
|
|
|
801
|
2
|
50
|
|
|
|
9
|
$rules = ($rules) ? $rules : 'Japanese'; |
802
|
2
|
50
|
|
|
|
13
|
$self->{_const}{selfcapture} = 1 if ($rules =~ /^NZ|^NEW ZEALAND|^ING|^GOE/); |
803
|
2
|
50
|
|
|
|
8
|
$self->{_const}{ssk} = 1 if ($rules =~ /^AGA/); |
804
|
2
|
50
|
|
|
|
9
|
$self->{_const}{passes} = 4 if ($rules =~ /^ING|^GOE/); |
805
|
2
|
50
|
|
|
|
15
|
$self->{_const}{hfree} = 1 if ($rules =~ /^NZ|^NEW ZEALAND|^ING|^GOE|^CHINESE/); |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
sub makenode { |
809
|
115
|
|
|
115
|
0
|
1486
|
my ($self, $colour, $point) = @_; |
810
|
115
|
|
|
|
|
674
|
return new Games::Go::Referee::Node($self->{_movecount}+1, $self->{_passcount}, $colour, $point); |
811
|
|
|
|
|
|
|
} |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
sub errors { |
814
|
2
|
|
|
2
|
1
|
2355
|
my ($self) = @_; |
815
|
2
|
|
|
|
|
30
|
my $errorhash = { |
816
|
|
|
|
|
|
|
1 => 'Not a board co-ordinate at move ', |
817
|
|
|
|
|
|
|
2 => 'Point already occupied at move ', |
818
|
|
|
|
|
|
|
3 => 'Illegal setup at node ', |
819
|
|
|
|
|
|
|
4 => 'Point already occupied at node ', |
820
|
|
|
|
|
|
|
5 => 'Illegal self-capture at move ', |
821
|
|
|
|
|
|
|
6 => 'Board repetition at move ', |
822
|
|
|
|
|
|
|
7 => 'Alternation error at move ', |
823
|
|
|
|
|
|
|
8 => 'Play over at move ', |
824
|
|
|
|
|
|
|
9 => 'Not a board co-ordinate at node ', |
825
|
|
|
|
|
|
|
10 => 'Board repetition at node ', |
826
|
|
|
|
|
|
|
}; |
827
|
2
|
|
|
|
|
6
|
my @array = @{$self->{_errors}}; |
|
2
|
|
|
|
|
11
|
|
828
|
2
|
|
|
|
|
4
|
my @return; |
829
|
2
|
|
|
|
|
8
|
for (0..$#array){ |
830
|
18
|
|
|
|
|
31
|
my $ecode = $self->{_errors}[$_][0]; |
831
|
18
|
|
|
|
|
61
|
push @return, join '', $errorhash->{$ecode}, $self->{_errors}[$_][1], "\n"; |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
return @return |
834
|
2
|
|
|
|
|
21
|
} |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
sub errorcode { |
837
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
838
|
0
|
|
|
|
|
0
|
my @array = @{$self->{_errors}}; |
|
0
|
|
|
|
|
0
|
|
839
|
0
|
|
|
|
|
0
|
my $ecode = undef; |
840
|
0
|
|
|
|
|
0
|
for (0..$#array){ |
841
|
0
|
|
|
|
|
0
|
$ecode = $self->{_errors}[$_][0]; |
842
|
0
|
|
|
|
|
0
|
last; |
843
|
|
|
|
|
|
|
} |
844
|
0
|
0
|
|
|
|
0
|
return defined($ecode)? $ecode: 0; |
845
|
|
|
|
|
|
|
} |
846
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
sub adderror { |
848
|
18
|
|
|
18
|
0
|
34
|
my ($self, $ecode, $place) = @_; |
849
|
18
|
|
|
|
|
30
|
push @{$self->{_errors}}, [$ecode, $place]; |
|
18
|
|
|
|
|
78
|
|
850
|
|
|
|
|
|
|
} |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
# empty board |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
sub clearboard{ |
855
|
2
|
|
|
2
|
0
|
3
|
my $self = shift; |
856
|
2
|
|
|
|
|
6
|
$self->{_cellfarm} = {}; |
857
|
|
|
|
|
|
|
_iterboard { |
858
|
722
|
|
|
722
|
|
855
|
my ($x, $y) = @_; |
859
|
722
|
|
|
|
|
2077
|
$self->{_cellfarm}{$x.','.$y} = '.'; |
860
|
2
|
|
|
|
|
16
|
} $self->{_const}{size}; |
861
|
2
|
|
|
|
|
18
|
$self->{_node}{0}->board(store($self)); |
862
|
|
|
|
|
|
|
return |
863
|
2
|
|
|
|
|
4
|
} |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
sub checkmove { # check move is OK according to format |
866
|
0
|
|
|
0
|
0
|
0
|
my ($self, $string) = @_; |
867
|
0
|
0
|
|
|
|
0
|
myprint ($self, 'Checking move', $string) if $self->{_debug}; |
868
|
0
|
0
|
|
|
|
0
|
return 1 if ispass($self, $string); |
869
|
0
|
0
|
|
|
|
0
|
if ($self->{_const}{pointformat} eq 'sgf') { |
870
|
0
|
|
|
|
|
0
|
return issgf($string) |
871
|
|
|
|
|
|
|
} else { |
872
|
0
|
|
|
|
|
0
|
return isgmp($string) |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
} |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
sub ispass { |
877
|
222
|
|
|
222
|
0
|
314
|
my ($self, $move) = @_; |
878
|
222
|
50
|
|
|
|
553
|
if ($self->{_const}{pointformat} eq 'sgf') { |
879
|
222
|
100
|
|
|
|
449
|
return 1 if not defined $move; |
880
|
218
|
100
|
33
|
|
|
1216
|
if (($move eq '') or ($move eq 'tt' and $self->{_const}{size} < 19)) { |
|
|
|
66
|
|
|
|
|
881
|
2
|
|
|
|
|
7
|
return 1 |
882
|
|
|
|
|
|
|
} |
883
|
|
|
|
|
|
|
} else { |
884
|
0
|
0
|
|
|
|
0
|
if ('pass' eq lc $move) { |
885
|
0
|
|
|
|
|
0
|
return 1 |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
} |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
sub issgf { # assuming not a pass |
891
|
0
|
|
|
0
|
0
|
0
|
shift =~ /^[a-z]{2}$/i; |
892
|
|
|
|
|
|
|
} |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
sub isgmp { # assuming not a pass |
895
|
0
|
0
|
0
|
0
|
0
|
0
|
shift =~ /^[a-z]([1-9]\d?)$/i and 1 <= $1 and $1 <= 25; |
896
|
|
|
|
|
|
|
} |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
sub getpoints { # extract points from a hash key eg '10,1' |
899
|
32
|
|
|
32
|
0
|
46
|
my $pointsref = shift; |
900
|
32
|
|
|
|
|
38
|
my @points; |
901
|
32
|
|
|
|
|
45
|
for (keys(%{$pointsref})) { |
|
32
|
|
|
|
|
89
|
|
902
|
36
|
|
|
|
|
203
|
/(.*),(.*)/; |
903
|
36
|
|
|
|
|
208
|
push @points, [$1,$2]; |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
return \@points |
906
|
32
|
|
|
|
|
90
|
} |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
sub extractpoints { # convert points from an sgf or gmp string to a pair of numbers |
909
|
112
|
|
|
112
|
0
|
228
|
my ($self, $string) = @_; |
910
|
112
|
|
|
|
|
190
|
my $pass = ispass($self, $string); |
911
|
112
|
50
|
|
|
|
237
|
return '','' if $pass; |
912
|
112
|
50
|
|
|
|
303
|
if ($self->{_const}{pointformat} eq 'sgf') { |
913
|
112
|
|
|
|
|
240
|
return fromsgf($string, $pass) |
914
|
|
|
|
|
|
|
} else { |
915
|
0
|
|
|
|
|
0
|
return fromgtp($self, $string) |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
sub insertpoints { # convert a pair of numbers to an sgf or gmp string |
920
|
0
|
|
|
0
|
0
|
0
|
my ($self, $x, $y) = @_; |
921
|
0
|
0
|
|
|
|
0
|
if ($self->{_const}{pointformat} eq 'sgf') { |
922
|
0
|
|
|
|
|
0
|
return tosgf($x, $y) |
923
|
|
|
|
|
|
|
} else { |
924
|
0
|
|
|
|
|
0
|
return togtp($self, $x, $y) |
925
|
|
|
|
|
|
|
} |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
sub fromsgf { |
929
|
112
|
|
|
112
|
0
|
157
|
my ($string) = @_; |
930
|
112
|
|
|
|
|
229
|
my $x = index(aZ(), substr($string,0,1)); |
931
|
112
|
|
|
|
|
206
|
my $y = index(aZ(), substr($string,1,1)); |
932
|
112
|
|
|
|
|
283
|
return $x,$y; |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
sub fromgtp { |
936
|
0
|
|
|
0
|
0
|
0
|
my ($self, $string) = @_; |
937
|
0
|
|
|
|
|
0
|
my $a = index aZnoi(), lc substr $string, 0, 1; |
938
|
0
|
|
|
|
|
0
|
my $y = substr $string, 1; |
939
|
0
|
|
|
|
|
0
|
return $a, $self->{_const}{size} - $y + 1; |
940
|
|
|
|
|
|
|
} |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
sub togtp { |
943
|
0
|
|
|
0
|
0
|
0
|
my ($self, $x, $y) = @_; |
944
|
0
|
0
|
0
|
|
|
0
|
return 'pass' if $x eq '' and $y eq ''; |
945
|
0
|
|
|
|
|
0
|
join '', uc(substr(aZnoi(), $x, 1)), $self->{_const}{size} - $y + 1 |
946
|
|
|
|
|
|
|
} |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
sub tosgf { |
949
|
0
|
0
|
0
|
0
|
0
|
0
|
return '' if $_[0] eq '' and $_[1] eq ''; |
950
|
0
|
|
|
|
|
0
|
join '', substr(aZ(), $_[0], 1), substr(aZ(), $_[1], 1) |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
sub offboard { |
954
|
1226
|
100
|
100
|
1226
|
0
|
19963
|
0 > $_[1] or $_[1] > $_[0] or 0 > $_[2] or $_[2] > $_[0]; |
|
|
|
100
|
|
|
|
|
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
sub swapcolour { |
958
|
0
|
0
|
|
0
|
0
|
0
|
return ($_[1] eq 'B') ? 'W' : 'B' |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
|
961
|
224
|
|
|
224
|
0
|
568
|
sub aZ { 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' } |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
sub aZnoi { |
964
|
0
|
|
|
0
|
0
|
|
my $str = aZ(); |
965
|
0
|
|
|
|
|
|
$str =~ s/i//; |
966
|
0
|
|
|
|
|
|
return $str |
967
|
|
|
|
|
|
|
} |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
sub myprint { |
970
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
971
|
0
|
|
|
|
|
|
my @messages = @_; |
972
|
0
|
0
|
|
|
|
|
if (exists $messages[0]) { |
973
|
0
|
0
|
|
|
|
|
open(LOG, ">>", $self->{_logfile}) or die 'Can\'t open'.$self->{_logfile}."\n"; |
974
|
0
|
|
|
|
|
|
print LOG (join ' ', @messages, "\n"); |
975
|
0
|
|
|
|
|
|
close(LOG); |
976
|
|
|
|
|
|
|
} |
977
|
|
|
|
|
|
|
} |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
1; |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
=head1 NAME |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
Games::Go::Referee - Check the moves of a game of Go for rule violations. |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
=head1 SYNOPSIS |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
Analyse a file: |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
use Games::Go::Referee; |
990
|
|
|
|
|
|
|
my $referee = new Games::Go::Referee(); |
991
|
|
|
|
|
|
|
$referee->sgffile('file.sgf'); |
992
|
|
|
|
|
|
|
print $referee->errors; |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
or |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
Analyse move by move: |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
use Games::Go::Referee; |
999
|
|
|
|
|
|
|
my $referee = new Games::Go::Referee(); |
1000
|
|
|
|
|
|
|
$referee->size(19); |
1001
|
|
|
|
|
|
|
$referee->ruleset('AGA'); |
1002
|
|
|
|
|
|
|
$referee->play('B','ab'); |
1003
|
|
|
|
|
|
|
$referee->restore(-1) if $referee->errors; |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
=head1 DESCRIPTION |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
Check a game of Go for rules violations, against a specific rule set. |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=head2 General use |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
Games::Go::Referee can be used in two ways; to analyse an sgf file, or to check plays |
1013
|
|
|
|
|
|
|
move by move. |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
If checking a file, the file will be completely read, and any errors found can be displayed |
1016
|
|
|
|
|
|
|
later using the errors method. Any illegal plays found are 'allowed' (ie play is assumed to |
1017
|
|
|
|
|
|
|
continue as if they were legal). The rule set to be used will be read from the RU sgf |
1018
|
|
|
|
|
|
|
property in the file, alternatively various rules can be set manually. |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
If checking move by move, it may be necessary to specify the size and rule set to be |
1021
|
|
|
|
|
|
|
used before starting. |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
There are basically two rules that can be set: self-capture allowed/disallowed and |
1024
|
|
|
|
|
|
|
situational superko (ssk) on/off. If ssk is off, positional superko is assumed. |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
The following errors are reported: |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
Not a board co-ordinate |
1029
|
|
|
|
|
|
|
Point already occupied |
1030
|
|
|
|
|
|
|
Illegal setup (if the setup caused a capture to occur) |
1031
|
|
|
|
|
|
|
Illegal self-capture |
1032
|
|
|
|
|
|
|
Board repetition |
1033
|
|
|
|
|
|
|
Alternation error (two Black moves in a row for example) |
1034
|
|
|
|
|
|
|
Play over (play continues when the game is over) |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
=head1 METHODS |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
=head2 ruleset |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
The ruleset method sets the rule set to be used. If a file is being checked, |
1041
|
|
|
|
|
|
|
the value of the sgf property RU will be used. If that is not found, Japanese rules |
1042
|
|
|
|
|
|
|
are assumed. |
1043
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
$referee->ruleset('AGA'); |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
=head2 size |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
The size method sets the size of the board to be used. If a file is being checked, |
1049
|
|
|
|
|
|
|
the value of the sgf property SZ will be used. If that is not found, the board is |
1050
|
|
|
|
|
|
|
assumed to be 19 x 19. |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
$referee->size(19); |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
=head2 ssk |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
The ssk method sets or unsets whether the situational superko rule is being used. |
1058
|
|
|
|
|
|
|
ssk can be turned on only by using this method, or by specifying 'AGA' via the |
1059
|
|
|
|
|
|
|
ruleset method. |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
$referee->ssk('on'); |
1062
|
|
|
|
|
|
|
$referee->ssk('off'); |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
=head2 selfcapture |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
The selfcapture method sets or unsets whether self-capture (aka suicide) is |
1067
|
|
|
|
|
|
|
allowed or not. selfcapture can be turned on only by using this method, or by |
1068
|
|
|
|
|
|
|
specifying New Zealand or Ing via the rulset method. |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
$referee->selfcapture('on'); |
1071
|
|
|
|
|
|
|
$referee->selfcapture('off'); |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
=head2 passes |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
The passes method sets the number of consecutive passes required to end the game. |
1076
|
|
|
|
|
|
|
The default value is 2. If the Ing ruleset is being used, this value becomes 4. |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
$referee->passes(3); |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
=head2 setup |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
For move by move analysis, the following two methods are availale. |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
The setup method is used to place preliminary stones on the board. |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
Setup types (the first argument) are 'AB', 'AW' and 'AE'. Each use of setup can |
1087
|
|
|
|
|
|
|
only use one of these types. |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
Setup points (the second argument) are a list of sgf style board co-ordinates. |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
$referee->setup('AW','ii,jj,gh'); |
1092
|
|
|
|
|
|
|
$referee->setup('AB','aa,bb'); |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
If the setup creates group with no liberties, an error is reported. The method |
1095
|
|
|
|
|
|
|
returns true if an error was found, otherwise false. |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
=head2 handicap |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
The handicap method takes as its argument a number from 2 to 9 |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
$referee->handicap(3); |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
This method can be used as a convenient way of placing handicap stones, provided |
1104
|
|
|
|
|
|
|
the board size is 19, and the rules indicate that handicap placement is fixed |
1105
|
|
|
|
|
|
|
(ie neither Ing, AGA nor Chinese). |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
If handicap placement is fixed, but the board size is not 19, use the setup method. |
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
If handicap placement is not fixed, the handicap method should still be used as then |
1110
|
|
|
|
|
|
|
the appropriate number of black consecutive plays will be allowed. |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
=head2 play |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
Play a move. |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
Play types (the first argument) are 'B' or 'W'. Each use of play can |
1117
|
|
|
|
|
|
|
only use one of these types. |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
The point played (the second argument) is a single sgf style co-ordinate (or '' for a pass.) |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
$referee->play('B','pd'); |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
The method returns true if an error was found, otherwise false. |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
=head2 haslegal |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
$referee->haslegal($colour); # $colour must be 'B' or 'W' |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
Returns true if $colour (ie 'B' or 'W') has a legal move, otherwise returns false. |
1130
|
|
|
|
|
|
|
Usage example - |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
while ($referee->haslegal($colour)){ |
1133
|
|
|
|
|
|
|
my $point = getmove(); |
1134
|
|
|
|
|
|
|
$referee->play($colour, $point); |
1135
|
|
|
|
|
|
|
if ($referee->errors) { |
1136
|
|
|
|
|
|
|
$referee->restore(-1); |
1137
|
|
|
|
|
|
|
} else { |
1138
|
|
|
|
|
|
|
$colour = ($colour eq 'B') ? 'W' : 'B'; |
1139
|
|
|
|
|
|
|
} |
1140
|
|
|
|
|
|
|
} |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
=head2 legal |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
my @points = $referee->legal($colour); # $colour must be 'B' or 'W' |
1145
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
Returns an array of a player's legal move co-ordinates. |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
Usage example - |
1149
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
my @legalpoints = $referee->legal($colour); |
1151
|
|
|
|
|
|
|
while ($#legalpoints >= 0){ |
1152
|
|
|
|
|
|
|
# play a random legal move |
1153
|
|
|
|
|
|
|
$referee->play($colour, @points[int(rand($#legalpoints))]); |
1154
|
|
|
|
|
|
|
$colour = ($colour eq 'B') ? 'W' : 'B'; |
1155
|
|
|
|
|
|
|
@legalpoints = $referee->legal($colour); |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
=head2 errors |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
print $referee->errors; |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
Lists any errors occurring either in the file analysed, or as a result of the previous |
1163
|
|
|
|
|
|
|
move/setup. |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
=head2 sgffile |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
$referee->sgffile('file.sgf'); |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
or |
1170
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
my $sgf = new Games::Go::SGF('file.sgf'); |
1172
|
|
|
|
|
|
|
$referee->sgffile($sgf); |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
Specify an sgf file to be analysed. |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
=head1 TODO |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
Score? |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
=head1 BUGS/CAVEATS |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
The move number of a reported error is one too large if it occurs in a variation. |
1183
|
|
|
|
|
|
|
Putting setup stones within a file (not just the first node) can cause problems. For example, |
1184
|
|
|
|
|
|
|
after some stones have been added like this, who is next to play? This needs to be known for |
1185
|
|
|
|
|
|
|
situational superko. Currently no look-ahead is done to see who, in fact, played next. |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
Natural Superko - if I understood the difference between this and SSK, I might put it in. |
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
Ko-pass moves, game resumption ... my head hurts. |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
=head1 AUTHOR (version 0.01) |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
DG |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
=cut |