… | |
… | |
71 | } |
71 | } |
72 | |
72 | |
73 | sub refresh { |
73 | sub refresh { |
74 | my ($self, $timestamp) = @_; |
74 | my ($self, $timestamp) = @_; |
75 | my $timer = $self->{time} + $self->{start} - $timestamp; |
75 | my $timer = $self->{time} + $self->{start} - $timestamp; |
76 | |
76 | |
77 | # we round the timer value slightly... the protocol isn't exact anyways, |
77 | # we round the timer value slightly... the protocol isn't exact anyways, |
78 | # and this gives smoother timers ;) |
78 | # and this gives smoother timers ;) |
79 | my @format = $self->{format}->(int ($timer + 0.4)); |
79 | my $timer2 = int $timer + 0.4; |
|
|
80 | |
|
|
81 | if ($timer2 <= 0) { |
|
|
82 | $timer2 = 0 if $timer2 < 0; |
|
|
83 | $self->set_text ("TIME OUT"); |
|
|
84 | } else { |
80 | $self->set_text ($self->{format}->(int ($timer + 0.4))); |
85 | $self->set_text ($self->{format}->($timer2)); |
|
|
86 | } |
81 | |
87 | |
82 | $timer - int $timer; |
88 | $timer - int $timer; |
83 | } |
89 | } |
84 | |
90 | |
85 | sub set_time { |
91 | sub set_time { |
86 | my ($self, $time) = @_; |
92 | my ($self, $time, $moves) = @_; |
87 | |
93 | |
88 | # we ignore requests to re-set the time of a running clock. |
94 | # we ignore requests to re-set the time of a running clock. |
89 | # this is the easiest way to ensure that commentary etc. |
95 | # this is the easiest way to ensure that commentary etc. |
90 | # doesn't re-set the clock. yes, this is frickle design, |
96 | # doesn't re-set the clock. yes, this is frickle design, |
91 | # but I think the protocol is to blame here, which gives |
97 | # but I think the protocol is to blame here, which gives |
92 | # very little time information. (cgoban2 also has had quite |
98 | # very little time information. (cgoban2 also has had quite |
93 | # a lot of small time update problems...) |
99 | # a lot of small time update problems...) |
94 | unless ($self->{timeout}) { |
100 | unless ($self->{timeout}) { |
95 | $self->{set}->($time->[0], $time->[1]); |
101 | $self->{set}->($time, $moves); |
96 | $self->refresh ($self->{start}); |
102 | $self->refresh ($self->{start}); |
97 | } |
103 | } |
98 | } |
104 | } |
99 | |
105 | |
100 | sub start { |
106 | sub start { |
… | |
… | |
167 | } |
173 | } |
168 | |
174 | |
169 | $self->{clock}->configure (@{$rules}{qw(timesys time interval count)}); |
175 | $self->{clock}->configure (@{$rules}{qw(timesys time interval count)}); |
170 | } |
176 | } |
171 | |
177 | |
172 | sub set_state { |
178 | sub set_captures { |
173 | my ($self, $captures, $timer, $when) = @_; |
179 | my ($self, $captures) = @_; |
|
|
180 | |
|
|
181 | $self->{info}->set_text ("$captures pris."); |
|
|
182 | } |
|
|
183 | |
|
|
184 | sub set_timer { |
|
|
185 | my ($self, $when, $time, $moves) = @_; |
174 | |
186 | |
175 | $self->{clock}->stop unless $when; |
187 | $self->{clock}->stop unless $when; |
176 | $self->{clock}->set_time ($timer); |
188 | $self->{clock}->set_time ($time, $moves); |
177 | $self->{clock}->start ($when) if $when; |
189 | $self->{clock}->start ($when) if $when; |
178 | |
|
|
179 | $self->{info}->set_text ("$captures pris."); |
|
|
180 | } |
190 | } |
181 | |
191 | |
182 | package game; |
192 | package game; |
183 | |
193 | |
184 | use Scalar::Util qw(weaken); |
194 | use Scalar::Util qw(weaken); |
… | |
… | |
187 | use KGS::Game::Board; |
197 | use KGS::Game::Board; |
188 | |
198 | |
189 | use Gtk2::GoBoard; |
199 | use Gtk2::GoBoard; |
190 | use Gtk2::GoBoard::Constants; |
200 | use Gtk2::GoBoard::Constants; |
191 | |
201 | |
|
|
202 | use base KGS::Game; |
|
|
203 | use base KGS::Listener::Game; |
|
|
204 | |
192 | use Glib::Object::Subclass |
205 | use Glib::Object::Subclass |
193 | Gtk2::Window; |
206 | Gtk2::Window; |
194 | |
|
|
195 | use base KGS::Listener::Game; |
|
|
196 | use base KGS::Game; |
|
|
197 | |
207 | |
198 | use POSIX qw(ceil); |
208 | use POSIX qw(ceil); |
199 | |
209 | |
200 | sub new { |
210 | sub new { |
201 | my ($self, %arg) = @_; |
211 | my ($self, %arg) = @_; |
… | |
… | |
244 | |
254 | |
245 | $hbox->pack_start ((my $scale = new Gtk2::HScale $self->{moveadj}), 1, 1, 0); |
255 | $hbox->pack_start ((my $scale = new Gtk2::HScale $self->{moveadj}), 1, 1, 0); |
246 | $scale->set_draw_value (0); |
256 | $scale->set_draw_value (0); |
247 | $scale->set_digits (0); |
257 | $scale->set_digits (0); |
248 | |
258 | |
249 | $self->{moveadj}->signal_connect (value_changed => sub { $self->update_board }); |
259 | $self->{moveadj}->signal_connect (value_changed => sub { |
|
|
260 | $self->{showmove} = int $self->{moveadj}->get_value; |
|
|
261 | $self->update_board; |
|
|
262 | }); |
250 | } |
263 | } |
251 | |
264 | |
252 | $vbox->pack_start ((my $hbox = new Gtk2::HBox 1), 0, 1, 0); |
265 | $vbox->pack_start ((my $hbox = new Gtk2::HBox 1), 0, 1, 0); |
253 | |
266 | |
254 | $hbox->add ($self->{userpanel}[$_] = new game::userpanel colour => $_) |
267 | $hbox->add ($self->{userpanel}[$_] = new game::userpanel colour => $_) |
255 | for COLOUR_WHITE, COLOUR_BLACK; |
268 | for COLOUR_WHITE, COLOUR_BLACK; |
256 | |
269 | |
257 | $vbox->pack_start ((my $buttonbox = new Gtk2::HButtonBox), 0, 1, 0); |
270 | $vbox->pack_start ((my $buttonbox = new Gtk2::HButtonBox), 0, 1, 0); |
258 | |
271 | |
259 | $buttonbox->add ($self->{button_pass} = |
272 | $buttonbox->add ($self->{button_pass} = |
260 | Gtk2::Button->Glib::Object::new (label => "Pass", sensitive => 0)); |
273 | Gtk2::Button->Glib::Object::new (label => "Pass", no_show_all => 1, visible => 0)); |
261 | $self->{button_pass}->signal_connect (clicked => sub { |
274 | $self->{button_pass}->signal_connect (clicked => sub { |
262 | $self->{board_click}->(255, 255) if $self->{board_click}; |
275 | $self->{board_click}->(255, 255) if $self->{board_click}; |
|
|
276 | }); |
|
|
277 | $buttonbox->add ($self->{button_undo} = |
|
|
278 | Gtk2::Button->Glib::Object::new (label => "Undo", no_show_all => 1, visible => 0)); |
|
|
279 | $self->{button_undo}->signal_connect (clicked => sub { |
|
|
280 | $self->send (req_undo => channel => $self->{channel}); |
|
|
281 | }); |
|
|
282 | $buttonbox->add ($self->{button_resign} = |
|
|
283 | Gtk2::Button->Glib::Object::new (label => "Resign", no_show_all => 1, visible => 0)); |
|
|
284 | $self->{button_resign}->signal_connect (clicked => sub { |
|
|
285 | $self->send (resign_game => channel => $self->{channel}, player => $self->{colour}); |
263 | }); |
286 | }); |
264 | |
287 | |
265 | $vbox->pack_start (($self->{chat} = new superchat), 1, 1, 0); |
288 | $vbox->pack_start (($self->{chat} = new superchat), 1, 1, 0); |
266 | |
289 | |
267 | $self->set_channel ($self->{channel}); |
290 | $self->set_channel ($self->{channel}); |
… | |
… | |
327 | } |
350 | } |
328 | |
351 | |
329 | sub update_cursor { |
352 | sub update_cursor { |
330 | my ($self) = @_; |
353 | my ($self) = @_; |
331 | |
354 | |
332 | my $move = int $self->{moveadj}->get_value; |
|
|
333 | my $cb; |
|
|
334 | |
|
|
335 | warn sprintf "move %d spath %d\n", $move, scalar @{$self->{path}};#d# |
|
|
336 | warn "isact " . $self->is_active;#d# |
|
|
337 | warn "flags $self->{flags}\n";#d# |
|
|
338 | warn "handi $self->{handicap}\n";#d# |
|
|
339 | |
|
|
340 | my $running = $move == @{$self->{path}}; # && $self->is_active; |
355 | my $running = $self->{showmove} == @{$self->{path}} && $self->is_active; |
341 | |
356 | |
342 | delete $self->{board_click}; |
357 | delete $self->{board_click}; |
343 | |
358 | |
344 | if ($self->{teacher} eq $self->{app}{conn}) { |
359 | if ($self->{teacher} eq $self->{app}{conn}) { |
345 | #TODO# # teaching mode not implemented |
360 | #TODO# # teaching mode not implemented |
346 | $self->{button_pass}->set (label => "Pass", sensitive => 1, visible => 1); |
361 | $self->{button_pass}->set (label => "Pass", sensitive => 1, visible => 1); |
|
|
362 | $self->{button_undo}->hide; |
347 | $self->{button_pass}->show; |
363 | $self->{button_resign}->hide; |
348 | $self->{board}->set (cursor => undef); |
364 | $self->{board}->set (cursor => undef); |
349 | |
365 | |
350 | } elsif ($running && $self->{colour} != COLOUR_NONE) { |
366 | } elsif ($running && $self->{colour} != COLOUR_NONE) { |
351 | # during game |
367 | # during game |
|
|
368 | $self->{button_undo}->show; |
|
|
369 | $self->{button_resign}->show; |
352 | |
370 | |
353 | if ($self->{cur_board}{score}) { |
371 | if ($self->{cur_board}{score}) { |
354 | # during scoring |
372 | # during scoring |
355 | $self->{button_pass}->set (label => "Done", sensitive => 1, visible => 1); |
373 | $self->{button_pass}->set (label => "Done", sensitive => 1, visible => 1); |
356 | $self->{board}->set (cursor => sub { |
374 | $self->{board}->set (cursor => sub { |
… | |
… | |
389 | }; |
407 | }; |
390 | } else { |
408 | } else { |
391 | $self->{button_pass}->set (label => "Pass", sensitive => 0, visible => 1); |
409 | $self->{button_pass}->set (label => "Pass", sensitive => 0, visible => 1); |
392 | } |
410 | } |
393 | } else { |
411 | } else { |
|
|
412 | $self->{button_undo}->hide; |
|
|
413 | $self->{button_resign}->hide; |
394 | $self->{button_pass}->hide; |
414 | $self->{button_pass}->hide; |
395 | $self->{board}->set (cursor => undef); |
415 | $self->{board}->set (cursor => undef); |
396 | #TODO# # implement coordinate-grabbing |
416 | #TODO# # implement coordinate-grabbing |
397 | } |
417 | } |
398 | } |
418 | } |
399 | |
419 | |
|
|
420 | sub update_timers { |
|
|
421 | my ($self, $timers) = @_; |
|
|
422 | |
|
|
423 | my $running = $self->{showmove} == @{$self->{path}} && !$self->{teacher}; |
|
|
424 | |
|
|
425 | for my $colour (COLOUR_BLACK, COLOUR_WHITE) { |
|
|
426 | my $t = $timers->[$colour]; |
|
|
427 | $self->{userpanel}[$colour]->set_timer ( |
|
|
428 | $running && $self->{lastmove_colour} == 1 - $colour && $t->[0], |
|
|
429 | $t->[1], $t->[2]); |
|
|
430 | } |
|
|
431 | } |
|
|
432 | |
400 | sub update_board { |
433 | sub update_board { |
401 | my ($self) = @_; |
434 | my ($self) = @_; |
402 | return unless $self->{path}; |
435 | return unless $self->{path}; |
403 | |
436 | |
404 | my $move = int $self->{moveadj}->get_value; |
|
|
405 | |
|
|
406 | my $running = $move == @{$self->{path}} && !$self->{teacher}; |
|
|
407 | |
|
|
408 | $self->{board_label}->set_text ("Move " . ($move - 1)); |
437 | $self->{board_label}->set_text ("Move " . ($self->{showmove} - 1)); |
409 | |
438 | |
410 | $self->{cur_board} = new KGS::Game::Board $self->{size}; |
439 | $self->{cur_board} = new KGS::Game::Board $self->{size}; |
411 | $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $move - 1]]); |
440 | $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); |
412 | |
441 | |
|
|
442 | $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) |
413 | for my $colour (COLOUR_WHITE, COLOUR_BLACK) { |
443 | for COLOUR_WHITE, COLOUR_BLACK; |
414 | $self->{userpanel}[$colour]->set_state ( |
444 | |
415 | $self->{cur_board}{captures}[$colour], |
445 | if ($self->{showmove} == @{$self->{path}}) { |
|
|
446 | $self->{timers} = [ |
|
|
447 | [$self->{lastmove_time}, @{$self->{cur_board}{timer}[0]}], |
|
|
448 | [$self->{lastmove_time}, @{$self->{cur_board}{timer}[1]}], |
|
|
449 | ]; |
|
|
450 | $self->update_timers ($self->{timers}); |
|
|
451 | } else { |
|
|
452 | $self->update_timers ([ |
416 | $self->{cur_board}{timer}[$colour], |
453 | [0, @{$self->{cur_board}{timer}[0]}], |
417 | ($running && $self->{lastmove_colour} == !$colour) |
454 | [0, @{$self->{cur_board}{timer}[1]}], |
418 | ? $self->{lastmove_time} : 0 |
|
|
419 | ); |
455 | ]); |
420 | } |
456 | } |
421 | |
457 | |
422 | $self->{board}->set_board ($self->{cur_board}); |
458 | $self->{board}->set_board ($self->{cur_board}); |
423 | |
459 | |
424 | $self->update_cursor; |
460 | $self->update_cursor; |
… | |
… | |
515 | $self->{colour} = $self->player_colour ($self->{conn}{name}); |
551 | $self->{colour} = $self->player_colour ($self->{conn}{name}); |
516 | |
552 | |
517 | my $title = defined $self->{channel} |
553 | my $title = defined $self->{channel} |
518 | ? $self->owner->as_string . " " . $self->opponent_string |
554 | ? $self->owner->as_string . " " . $self->opponent_string |
519 | : "Game Window"; |
555 | : "Game Window"; |
520 | $self->set_title("KGS Game $title"); |
556 | $self->set_title ("KGS Game $title"); |
521 | $self->{title}->set_text ($title); |
557 | $self->{title}->set_text ($title); |
522 | |
558 | |
523 | $self->{user}[COLOUR_BLACK] = $self->{black}; |
559 | $self->{user}[COLOUR_BLACK] = $self->{black}; |
524 | $self->{user}[COLOUR_WHITE] = $self->{white}; |
560 | $self->{user}[COLOUR_WHITE] = $self->{white}; |
525 | |
561 | |
… | |
… | |
632 | |
668 | |
633 | sub event_out_of_time { |
669 | sub event_out_of_time { |
634 | my ($self, $player) = @_; |
670 | my ($self, $player) = @_; |
635 | |
671 | |
636 | sound::play 3, "timewin"; |
672 | sound::play 3, "timewin"; |
637 | $self->{chat}->append_text ("\n<infoblock><header>Time Over</header>" |
673 | $self->{chat}->append_text ("\n<infoblock><header>Out of Time</header>" |
638 | . "\n<user>" |
674 | . "\n<user>" |
639 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
675 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
640 | . "</user> ran out of time.</infoblock>"); |
676 | . "</user> ran out of time and lost.</infoblock>"); |
|
|
677 | } |
|
|
678 | |
|
|
679 | sub event_done { |
|
|
680 | my ($self) = @_; |
|
|
681 | |
|
|
682 | if ($self->{done}[1 - $self->{colour}] && !$self->{done}[$self->{colour}]) { |
|
|
683 | $self->{chat}->append_text ("\n<infoblock><header>Done</header>" |
|
|
684 | . "\nYour opponent pressed done."); |
|
|
685 | } |
641 | } |
686 | } |
642 | |
687 | |
643 | sub inject_final_result { |
688 | sub inject_final_result { |
644 | my ($self, $msg) = @_; |
689 | my ($self, $msg) = @_; |
645 | |
690 | |
646 | $self->{chat}->append_text ("<infoblock>\n<header>Game Over</header>" |
691 | $self->{chat}->append_text ("<infoblock>\n<header>Game Over</header>" |
647 | . "\nWhite Score " . (util::toxml $msg->{whitescore}->as_string) |
692 | . "\nWhite Score " . (util::toxml $msg->{whitescore}->as_string) |
648 | . "\nBlack Score " . (util::toxml $msg->{blackscore}->as_string) |
693 | . "\nBlack Score " . (util::toxml $msg->{blackscore}->as_string) |
649 | . "</infoblock>" |
694 | . "</infoblock>" |
650 | ); |
695 | ); |
|
|
696 | } |
|
|
697 | |
|
|
698 | sub inject_set_gametime { |
|
|
699 | my ($self, $msg) = @_; |
|
|
700 | |
|
|
701 | $self->{timers} = [ |
|
|
702 | [$msg->{NOW}, $msg->{black_time}, $msg->{black_moves}], |
|
|
703 | [$msg->{NOW}, $msg->{white_time}, $msg->{white_moves}], |
|
|
704 | ]; |
|
|
705 | |
|
|
706 | print "SGT\n";#d# |
|
|
707 | $self->update_timers ($self->{timers}) |
|
|
708 | if $self->{showmove} == @{$self->{path}}; |
651 | } |
709 | } |
652 | |
710 | |
653 | sub inject_req_undo { |
711 | sub inject_req_undo { |
654 | my ($self, $msg) = @_; |
712 | my ($self, $msg) = @_; |
655 | |
713 | |
… | |
… | |
867 | }, |
925 | }, |
868 | !exists $self->{challenge}{""} # only open when not offerer |
926 | !exists $self->{challenge}{""} # only open when not offerer |
869 | ); |
927 | ); |
870 | } |
928 | } |
871 | |
929 | |
872 | sub event_done { |
|
|
873 | my ($self) = @_; |
|
|
874 | print "FDONE($self->{doneid} $self->{done}[0],$self->{done}[1]\n";#d# |
|
|
875 | } |
|
|
876 | |
|
|
877 | 1; |
930 | 1; |
878 | |
931 | |