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