1 |
elmex |
1.1 |
#!/opt/bin/perl |
2 |
|
|
package util; |
3 |
|
|
|
4 |
|
|
sub format_time($) { |
5 |
|
|
my ($time) = @_; |
6 |
|
|
|
7 |
|
|
$time > 60*60 |
8 |
|
|
? sprintf "%d:%02d:%02d", $time / (60 * 60), $time / 60 % 60, $time % 60 |
9 |
|
|
: sprintf "%d:%02d", $time / 60 % 60, $time % 60; |
10 |
|
|
} |
11 |
|
|
|
12 |
|
|
|
13 |
|
|
package goclock; |
14 |
|
|
|
15 |
|
|
use Time::HiRes (); |
16 |
|
|
|
17 |
|
|
use Gtk2; |
18 |
|
|
use Glib::Object::Subclass Gtk2::Label; |
19 |
|
|
|
20 |
|
|
sub INIT_INSTANCE { |
21 |
|
|
my $self = shift; |
22 |
|
|
|
23 |
|
|
$self->signal_connect (destroy => sub { $_[0]->stop }); |
24 |
|
|
|
25 |
|
|
$self->{format} = sub { |
26 |
elmex |
1.2 |
|
27 |
|
|
if ($_[0] < 0 || $self->{interval} < 0 || $self->{moves} < 0) { |
28 |
elmex |
1.1 |
util::format_time $_[0]; |
29 |
elmex |
1.2 |
|
30 |
elmex |
1.1 |
} else { |
31 |
elmex |
1.2 |
my $time = int $_[0]; |
32 |
|
|
|
33 |
|
|
if ($self->{interval} != 0) { |
34 |
|
|
$time = int (($_[0] - 1) % $self->{interval} + 1); |
35 |
|
|
} |
36 |
elmex |
1.1 |
|
37 |
elmex |
1.2 |
sprintf "%s/%d = %d [s/mv]", |
38 |
elmex |
1.1 |
util::format_time $time, |
39 |
|
|
$self->{moves}, |
40 |
elmex |
1.2 |
$self->{moves} >= 1 ? $time / $self->{moves} : $self->{interval}; |
41 |
elmex |
1.1 |
} |
42 |
|
|
} |
43 |
|
|
|
44 |
|
|
} |
45 |
|
|
|
46 |
|
|
sub FINALIZE_INSTANCE { |
47 |
|
|
my $self = shift; |
48 |
|
|
|
49 |
|
|
$self->stop; |
50 |
|
|
} |
51 |
|
|
|
52 |
|
|
sub refresh { |
53 |
|
|
my ($self) = @_; |
54 |
|
|
|
55 |
|
|
my $timer = $self->{time}; |
56 |
|
|
print "TIM: $self->{time} $self->{moves} $self->{interval}\n"; |
57 |
|
|
|
58 |
|
|
$self->set_text ($self->{format}->($timer)); |
59 |
|
|
} |
60 |
|
|
|
61 |
|
|
sub set_time { |
62 |
|
|
my ($self, $start, $time, $moves, $interval) = @_; |
63 |
|
|
|
64 |
|
|
$self->{interval} = $interval; |
65 |
|
|
$self->{time} = $time; |
66 |
|
|
$self->{moves} = $moves; |
67 |
|
|
|
68 |
|
|
$self->refresh; |
69 |
|
|
if ($start) { |
70 |
|
|
$self->start; |
71 |
|
|
} else { |
72 |
|
|
$self->stop; |
73 |
|
|
} |
74 |
|
|
} |
75 |
|
|
|
76 |
|
|
sub start { |
77 |
|
|
my ($self, $interval) = @_; |
78 |
|
|
|
79 |
|
|
$self->stop; |
80 |
|
|
|
81 |
|
|
my $timeout; $timeout = sub { |
82 |
|
|
$self->{timeout} = add Glib::Timeout 1000, $timeout; |
83 |
|
|
$self->{time}--; |
84 |
|
|
$self->refresh; |
85 |
|
|
0; |
86 |
|
|
}; |
87 |
|
|
|
88 |
|
|
$self->{time}++; |
89 |
|
|
$timeout->(); |
90 |
|
|
} |
91 |
|
|
|
92 |
|
|
sub stop { |
93 |
|
|
my ($self) = @_; |
94 |
|
|
|
95 |
|
|
remove Glib::Source delete $self->{timeout} if $self->{timeout}; |
96 |
|
|
} |
97 |
|
|
|
98 |
|
|
|
99 |
|
|
package main; |
100 |
root |
1.3 |
|
101 |
elmex |
1.1 |
use Glib; |
102 |
|
|
use Gtk2 -init; |
103 |
|
|
use Gtk2::Helper; |
104 |
|
|
use Gtk2::SimpleList; |
105 |
|
|
use Gtk2::GoBoard; |
106 |
|
|
use Games::Go::SimpleBoard; |
107 |
|
|
use IO::Socket::INET; |
108 |
root |
1.3 |
use Net::IGS; |
109 |
elmex |
1.1 |
use strict; |
110 |
root |
1.3 |
|
111 |
elmex |
1.1 |
require Data::Dumper; |
112 |
|
|
|
113 |
|
|
my %games; |
114 |
|
|
|
115 |
|
|
my @timers; |
116 |
|
|
|
117 |
|
|
my %alphmap = ( |
118 |
|
|
A => 1, |
119 |
|
|
B => 2, |
120 |
|
|
C => 3, |
121 |
|
|
D => 4, |
122 |
|
|
E => 5, |
123 |
|
|
F => 6, |
124 |
|
|
G => 7, |
125 |
|
|
H => 8, |
126 |
|
|
J => 9, |
127 |
|
|
K => 10, |
128 |
|
|
L => 11, |
129 |
|
|
M => 12, |
130 |
|
|
N => 13, |
131 |
|
|
O => 14, |
132 |
|
|
P => 15, |
133 |
|
|
Q => 16, |
134 |
|
|
R => 17, |
135 |
|
|
S => 18, |
136 |
|
|
T => 19, |
137 |
|
|
U => 20, |
138 |
|
|
V => 21, |
139 |
|
|
W => 22, |
140 |
|
|
); |
141 |
|
|
|
142 |
|
|
sub dump($) { |
143 |
|
|
print Data::Dumper::Dumper ([$_[0]]); |
144 |
|
|
} |
145 |
|
|
|
146 |
|
|
sub con { |
147 |
|
|
my ($host, $port) = @_; |
148 |
|
|
|
149 |
|
|
my $socket = new IO::Socket::INET PeerAddr => $host, PeerPort => $port |
150 |
|
|
or die "cannot connect to $host:$port: $!"; |
151 |
|
|
|
152 |
|
|
$socket->autoflush (1); |
153 |
|
|
return $socket; |
154 |
|
|
} |
155 |
|
|
|
156 |
|
|
sub spawn_game_view { |
157 |
|
|
my ($igs, $gamenr) = @_; |
158 |
|
|
|
159 |
|
|
my $game = $games{$gamenr}; |
160 |
|
|
my $size = $game->{size}; |
161 |
|
|
|
162 |
|
|
return if not defined $game; |
163 |
|
|
|
164 |
|
|
my $w = Gtk2::Window->new; |
165 |
|
|
$w->set_default_size (600, 700); |
166 |
|
|
|
167 |
|
|
$w->add (my $v = new Gtk2::VBox); |
168 |
|
|
$v->pack_start ((new Gtk2::Label |
169 |
|
|
"Game $gamenr: White: $game->{white} [$game->{white_rank}] vs. Black: $game->{black} [$game->{black_rank}]"), 0, 1, 0); |
170 |
|
|
$v->pack_start ((new Gtk2::Label |
171 |
|
|
"Size: $game->{size} Handicap: $game->{handicap}"), 0, 1, 0); |
172 |
|
|
$v->pack_start ((my $clock1 = new goclock), 0, 1, 0); |
173 |
|
|
$v->pack_start ((my $clock2 = new goclock), 0, 1, 0); |
174 |
|
|
$v->pack_start ((my $msg = new Gtk2::Label), 0, 1, 0); |
175 |
|
|
$v->pack_start ((my $mv_lbl = new Gtk2::Label), 0, 1, 0); |
176 |
|
|
|
177 |
|
|
$v->pack_start ((my $board = new Gtk2::GoBoard size => $size), 1, 1, 0); |
178 |
|
|
|
179 |
|
|
|
180 |
|
|
$msg->set_text ($game->{title}); |
181 |
|
|
|
182 |
|
|
my $p3 = $igs->set_ev_cb ('game_title' => sub { |
183 |
|
|
my ($s, $gnr, $t) = @_; |
184 |
|
|
$gnr == $gamenr or return; |
185 |
|
|
$msg->set_text ($t); |
186 |
|
|
$game->{title} = $t; |
187 |
|
|
}); |
188 |
|
|
|
189 |
|
|
my $pos = $igs->set_ev_cb ('game_over' => sub { |
190 |
|
|
my ($self, $gnr, $result) = @_; |
191 |
|
|
$gnr == $gamenr or return; |
192 |
|
|
$clock1->stop; |
193 |
|
|
$clock2->stop; |
194 |
|
|
$msg->set_text ($game->{title} . ": Game Over: $result"); |
195 |
|
|
}); |
196 |
|
|
|
197 |
|
|
my $pos2 = $igs->set_ev_cb ('game_move' => sub { |
198 |
|
|
my $self = shift; |
199 |
|
|
my $gnr = shift; |
200 |
|
|
|
201 |
|
|
$gnr == $gamenr or return; |
202 |
|
|
|
203 |
|
|
my $move = shift; |
204 |
|
|
|
205 |
|
|
my $mnr = $move->{number}; |
206 |
|
|
$move->{coords} =~ m/^(.)(\d+)/; |
207 |
|
|
my $x = $alphmap{$1} - 1; |
208 |
|
|
my $y = $size - $2; |
209 |
|
|
|
210 |
|
|
my $white_mv = $move->{color} eq 'white'; |
211 |
|
|
|
212 |
|
|
$game->{moves}->[$mnr] = $move; |
213 |
|
|
my $mv = $game->{moves_struct}->[$mnr] |
214 |
|
|
= [ $x, $y, ~MARK_CIRCLE, MARK_CIRCLE | MARK_MOVE | ($white_mv ? MARK_W : MARK_B), '' ]; |
215 |
|
|
|
216 |
|
|
print "MOVE $gamenr | $mnr | $gnr : $x, $y $move->{color} = $move->{white_time} <=> $move->{black_time} | $move->{white_stones} <-> $move->{black_stones} | $game->{byo}\n"; |
217 |
|
|
|
218 |
|
|
$mv_lbl->set_text ("Move " . ($mnr + 1)); |
219 |
|
|
$clock1->set_time ((not $white_mv), $move->{white_time}, $move->{white_stones}, $game->{byo} * 60); |
220 |
|
|
$clock2->set_time ($white_mv, $move->{black_time}, $move->{black_stones}, $game->{byo} * 60); |
221 |
|
|
if ($move->{white_time} == 0 && $move->{white_stones} == -1 && $move->{black_time} == 0 && $move->{black_stones} == -1) { |
222 |
|
|
$clock1->stop (); |
223 |
|
|
$clock2->stop (); |
224 |
|
|
} |
225 |
|
|
|
226 |
|
|
my $board_state = new Games::Go::SimpleBoard $size; |
227 |
|
|
$board_state->update ($game->{moves_struct}); |
228 |
|
|
$board->set_board ($board_state); |
229 |
|
|
}); |
230 |
|
|
|
231 |
|
|
$w->signal_connect (destroy => sub { |
232 |
|
|
$igs->unset_ev_cb ('game_over', $pos); |
233 |
|
|
$igs->unset_ev_cb ('game_move', $pos2); |
234 |
|
|
$igs->unset_ev_cb ('game_title', $p3); |
235 |
|
|
$igs->feed_event ('req_unobserve', $gamenr); |
236 |
|
|
}); |
237 |
|
|
|
238 |
|
|
$w->show_all; |
239 |
|
|
} |
240 |
|
|
|
241 |
|
|
sub build_ui { |
242 |
|
|
my $igs = shift; |
243 |
|
|
|
244 |
|
|
my $win = Gtk2::Window->new; |
245 |
|
|
$win->set_default_size (600, 800); |
246 |
|
|
|
247 |
|
|
my $slist = Gtk2::SimpleList->new ( |
248 |
|
|
'N' => 'int', # game number |
249 |
|
|
'White' => 'text', |
250 |
|
|
'W Rank' => 'text', |
251 |
|
|
'Black' => 'text', |
252 |
|
|
'B Rank' => 'text', |
253 |
|
|
'Move' => 'int', |
254 |
|
|
'Size' => 'int', |
255 |
|
|
'Hand' => 'int', |
256 |
|
|
'Komi' => 'double', |
257 |
|
|
'Byo' => 'int', |
258 |
|
|
'F' => 'text', |
259 |
|
|
'R' => 'text', |
260 |
|
|
); |
261 |
|
|
|
262 |
|
|
my $plist = Gtk2::SimpleList->new ( |
263 |
|
|
'Name' => 'text', |
264 |
|
|
'Rank' => 'text', |
265 |
|
|
'Obs' => 'int', |
266 |
|
|
'Pla' => 'int', |
267 |
|
|
'Idle' => 'text', |
268 |
|
|
'Flags' => 'text', |
269 |
|
|
); |
270 |
|
|
|
271 |
|
|
$win->add (my $p = Gtk2::VPaned->new); |
272 |
|
|
$p->add1 (my $sw = Gtk2::ScrolledWindow->new); |
273 |
|
|
$sw->add ($slist); |
274 |
|
|
$p->add2 (my $hp = Gtk2::HPaned->new); |
275 |
|
|
$hp->add1 (my $sw = Gtk2::ScrolledWindow->new); |
276 |
|
|
$sw->add_with_viewport (my $v = Gtk2::VBox->new); |
277 |
|
|
$v->pack_start (my $txt = Gtk2::TextView->new, 1, 1, 0); |
278 |
|
|
$v->pack_start (my $b1 = Gtk2::Button->new ('sort'), 0, 1, 0); |
279 |
|
|
$hp->add2 (my $sw = Gtk2::ScrolledWindow->new); |
280 |
|
|
$sw->add ($plist); |
281 |
|
|
|
282 |
|
|
$b1->signal_connect (clicked => sub { |
283 |
|
|
@{$slist->{data}} = |
284 |
|
|
map { my %g = %{$games{$_}}; print "$g{number}\n"; [ @g{qw/number white white_rank black black_rank move size handicap komi byo Fflag Rflag/} ] } |
285 |
|
|
sort { $a <=> $b } keys %games; |
286 |
|
|
}); |
287 |
|
|
$slist->signal_connect (row_activated => sub { |
288 |
|
|
my ($sl, $path, $column) = @_; |
289 |
|
|
my $row_ref = $sl->get_row_data_from_path ($path); |
290 |
|
|
|
291 |
|
|
spawn_game_view ($igs, $row_ref->[0]); |
292 |
|
|
|
293 |
|
|
$igs->feed_event ("req_observe", $row_ref->[0]); |
294 |
|
|
$igs->feed_event ("req_moves", $row_ref->[0]); |
295 |
|
|
}); |
296 |
|
|
|
297 |
|
|
$p->set_position (400); |
298 |
|
|
$hp->set_position (300); |
299 |
|
|
|
300 |
|
|
$igs->set_ev_cb ('game_list' => sub { |
301 |
|
|
my %game = %{$_[1]}; |
302 |
|
|
$games{$game{number}} = { %game }; |
303 |
|
|
push @{$slist->{data}}, [ @game{qw/number white white_rank black black_rank move size handicap komi byo Fflag Rflag/} ]; |
304 |
|
|
}); |
305 |
|
|
|
306 |
|
|
$igs->set_ev_cb ('player_list' => sub { |
307 |
|
|
my %player = %{$_[1]}; |
308 |
|
|
push @{$plist->{data}}, [ @player{qw/name rank obs playing idle flags/} ]; |
309 |
|
|
# @{$plist->{data}} = sort { $a->[0] cmp $b->[0] } @{$plist->{data}}; |
310 |
|
|
}); |
311 |
|
|
|
312 |
|
|
$win->show_all (); |
313 |
|
|
} |
314 |
|
|
|
315 |
|
|
################################################################################# |
316 |
|
|
################################################################################# |
317 |
|
|
################################################################################# |
318 |
|
|
|
319 |
root |
1.3 |
my $igs = new Net::IGS; |
320 |
elmex |
1.1 |
|
321 |
|
|
$igs->init ("elmex", "lolfe123"); |
322 |
|
|
|
323 |
|
|
my $sck = con ("igs.joyjoy.net", 6969); |
324 |
|
|
|
325 |
|
|
$igs->set_writer (sub { $sck->syswrite ($_[0]) }); |
326 |
|
|
|
327 |
|
|
$igs->set_ev_cb ('recv_file' => sub { |
328 |
|
|
print "FILE [ $_[1] ]\n"; |
329 |
|
|
}); |
330 |
|
|
|
331 |
|
|
$igs->sendline ('games'); |
332 |
|
|
$igs->sendline ('players'); |
333 |
|
|
my $buffer; |
334 |
|
|
|
335 |
|
|
my $pos = $igs->set_ev_cb ('game_over' => sub { |
336 |
|
|
my ($self, $gnr, $result) = @_; |
337 |
|
|
delete $games{$gnr}; |
338 |
|
|
}); |
339 |
|
|
|
340 |
|
|
Gtk2::Helper->add_watch (fileno($sck), 'in', sub { |
341 |
|
|
my $data; |
342 |
|
|
|
343 |
|
|
my $r = $sck->sysread ($data, 4096); |
344 |
|
|
|
345 |
|
|
if (not $r) { |
346 |
|
|
# check $r == 0 or undef # FIXME |
347 |
|
|
die "eof from server"; |
348 |
|
|
} |
349 |
|
|
|
350 |
|
|
$buffer .= $data; |
351 |
|
|
$igs->feed_data (\$buffer); |
352 |
|
|
|
353 |
|
|
1; |
354 |
|
|
}); |
355 |
|
|
|
356 |
|
|
build_ui ($igs); |
357 |
|
|
|
358 |
|
|
Gtk2->main; |