… | |
… | |
34 | sub configure { |
34 | sub configure { |
35 | my ($self, $timesys, $main, $interval, $count) = @_; |
35 | my ($self, $timesys, $main, $interval, $count) = @_; |
36 | |
36 | |
37 | if ($timesys == TIMESYS_ABSOLUTE) { |
37 | if ($timesys == TIMESYS_ABSOLUTE) { |
38 | $self->{format} = sub { |
38 | $self->{format} = sub { |
39 | if ($_[0] <= 0) { |
39 | if ($_[0] < 0) { |
40 | "TIMEOUT"; |
40 | "TIMEOUT"; |
41 | } else { |
41 | } else { |
42 | util::format_time $_[0]; |
42 | util::format_time $_[0]; |
43 | } |
43 | } |
44 | }; |
44 | }; |
45 | |
45 | |
46 | } elsif ($timesys == TIMESYS_BYO_YOMI) { |
46 | } elsif ($timesys == TIMESYS_BYO_YOMI) { |
47 | my $low = $interval * $count; |
47 | my $low = $interval * $count; |
48 | |
48 | |
49 | $self->{format} = sub { |
49 | $self->{format} = sub { |
50 | if ($_[0] <= 0) { |
50 | if ($_[0] < 0) { |
51 | "TIMEOUT"; |
51 | "TIMEOUT"; |
52 | } elsif ($_[0] > $low) { |
52 | } elsif ($_[0] > $low) { |
53 | util::format_time $_[0] - $low; |
53 | util::format_time $_[0] - $low; |
54 | } else { |
54 | } else { |
55 | sprintf "%s (%d)", |
55 | sprintf "%s (%d)", |
… | |
… | |
58 | } |
58 | } |
59 | }; |
59 | }; |
60 | |
60 | |
61 | } elsif ($timesys == TIMESYS_CANADIAN) { |
61 | } elsif ($timesys == TIMESYS_CANADIAN) { |
62 | $self->{format} = sub { |
62 | $self->{format} = sub { |
63 | if ($_[0] <= 0) { |
63 | if ($_[0] < 0) { |
64 | "TIMEOUT"; |
64 | "TIMEOUT"; |
65 | } elsif (!$self->{moves}) { |
65 | } elsif (!$self->{moves}) { |
66 | util::format_time $_[0] - $low; |
66 | util::format_time $_[0] - $low; |
67 | } else { |
67 | } else { |
68 | my $time = int (($_[0] - 1) % $interval + 1); |
68 | my $time = int (($_[0] - 1) % $interval + 1); |
… | |
… | |
133 | |
133 | |
134 | ### USER PANEL ############################################################## |
134 | ### USER PANEL ############################################################## |
135 | |
135 | |
136 | package game::userpanel; |
136 | package game::userpanel; |
137 | |
137 | |
|
|
138 | use KGS::Constants; |
|
|
139 | |
138 | use Glib::Object::Subclass |
140 | use Glib::Object::Subclass |
139 | Gtk2::HBox, |
141 | Gtk2::Frame, |
140 | properties => [ |
142 | properties => [ |
141 | Glib::ParamSpec->IV ("colour", "colour", "User Colour", 0, 1, 0, [qw(construct-only writable)]), |
143 | Glib::ParamSpec->IV ("colour", "colour", "User Colour", |
|
|
144 | COLOUR_BLACK, COLOUR_WHITE, COLOUR_BLACK, |
|
|
145 | [qw(construct-only readable writable)]), |
142 | ]; |
146 | ]; |
143 | |
147 | |
144 | sub INIT_INSTANCE { |
148 | sub INIT_INSTANCE { |
145 | my ($self) = @_; |
149 | my ($self) = @_; |
146 | |
150 | |
147 | $self->add (my $vbox = new Gtk2::VBox); |
151 | $self->add (my $vbox = new Gtk2::VBox); |
148 | |
152 | |
149 | $vbox->add ($self->{name} = new Gtk2::Label $self->{name}); |
153 | $vbox->pack_start (($self->{name} = new Gtk2::Label "-"), 1, 1, 0); |
150 | $vbox->add ($self->{info} = new Gtk2::Label ""); |
154 | $vbox->pack_start (($self->{info} = new Gtk2::Label "-"), 1, 1, 0); |
151 | $vbox->add ($self->{clock} = new game::goclock); Scalar::Util::weaken $self->{clock}; |
155 | $vbox->pack_start (($self->{clock} = new game::goclock), 1, 1, 0); |
152 | |
156 | |
153 | $vbox->add ($self->{imagebox} = new Gtk2::VBox); |
157 | $vbox->add ($self->{imagebox} = new Gtk2::VBox); |
154 | |
158 | |
155 | $self; |
159 | $self; |
156 | } |
160 | } |
… | |
… | |
214 | |
218 | |
215 | use POSIX qw(ceil); |
219 | use POSIX qw(ceil); |
216 | |
220 | |
217 | sub new { |
221 | sub new { |
218 | my ($self, %arg) = @_; |
222 | my ($self, %arg) = @_; |
|
|
223 | |
219 | $self = $self->Glib::Object::new; |
224 | $self = $self->Glib::Object::new; |
220 | $self->{$_} = delete $arg{$_} for keys %arg; |
225 | $self->{$_} = delete $arg{$_} for keys %arg; |
221 | |
226 | |
222 | gtk::state $self, "game::window", undef, window_size => [600, 500]; |
227 | gtk::state $self, "game::window", undef, window_size => [620, 460]; |
223 | |
228 | |
224 | $self->signal_connect (destroy => sub { |
229 | $self->signal_connect (destroy => sub { |
225 | $self->unlisten; |
230 | $self->unlisten; |
226 | delete $self->{app}{game}{$self->{channel}}; |
231 | delete $self->{app}{game}{$self->{channel}}; |
227 | %{$_[0]} = (); |
232 | %{$_[0]} = (); |
228 | });#d# |
233 | });#d# |
229 | |
234 | |
230 | $self->add (my $hpane = new Gtk2::HPaned); |
235 | $self->add (my $hpane = new Gtk2::HPaned); |
231 | gtk::state $hpane, "game::hpane", undef, position => 500; |
236 | gtk::state $hpane, "game::hpane", undef, position => 420; |
232 | |
237 | |
233 | # LEFT PANE |
238 | # LEFT PANE |
234 | |
239 | |
235 | $hpane->pack1 (($self->{left} = new Gtk2::VBox), 1, 0); |
240 | $hpane->pack1 (($self->{left} = new Gtk2::VBox), 1, 0); |
236 | |
241 | |
237 | $self->{boardbox} = new Gtk2::VBox; |
|
|
238 | |
|
|
239 | $hpane->pack1((my $vbox = new Gtk2::VBox), 1, 1); |
242 | $hpane->pack1((my $vbox = new Gtk2::VBox), 1, 1); |
240 | |
243 | |
241 | # board box (aspect/canvas) |
244 | # board box (aspect/canvas) |
242 | |
245 | |
243 | #$self->{boardbox}->pack_start((my $frame = new Gtk2::Frame), 0, 1, 0); |
|
|
244 | |
|
|
245 | # RIGHT PANE |
246 | # RIGHT PANE |
246 | |
247 | |
247 | $hpane->pack2 ((my $vbox = new Gtk2::VBox), 1, 1); |
248 | $hpane->pack2 ((my $vbox = new Gtk2::VBox), 1, 1); |
248 | $hpane->set (position_set => 1); |
249 | $hpane->set (position_set => 1); |
249 | |
250 | |
250 | $vbox->pack_start ((my $frame = new Gtk2::Frame), 0, 1, 0); |
251 | $vbox->pack_start ((my $frame = new Gtk2::Frame), 0, 1, 0); |
251 | |
252 | |
252 | { |
253 | { |
253 | $frame->add (my $vbox = new Gtk2::VBox); |
254 | $frame->add (my $vbox = new Gtk2::VBox); |
254 | $vbox->add ($self->{title} = new Gtk2::Label $title); |
255 | $vbox->add ($self->{title} = new Gtk2::Label "-"); |
255 | |
256 | |
256 | $vbox->add (my $hbox = new Gtk2::HBox); |
257 | $vbox->add (my $hbox = new Gtk2::HBox); |
257 | |
258 | |
258 | $hbox->pack_start (($self->{board_label} = new Gtk2::Label), 0, 1, 0); |
259 | $hbox->pack_start (($self->{board_label} = new Gtk2::Label), 0, 0, 0); |
259 | |
260 | |
260 | $self->{moveadj} = new Gtk2::Adjustment 1, 1, 1, 1, 5, 0; |
261 | $self->{moveadj} = new Gtk2::Adjustment 1, 1, 1, 1, 5, 0; |
261 | |
262 | |
262 | $hbox->pack_start ((my $scale = new Gtk2::HScale $self->{moveadj}), 1, 1, 0); |
263 | $hbox->pack_start ((my $scale = new Gtk2::HScale $self->{moveadj}), 1, 1, 0); |
263 | $scale->set_draw_value (0); |
264 | $scale->set_draw_value (0); |
… | |
… | |
275 | for COLOUR_WHITE, COLOUR_BLACK; |
276 | for COLOUR_WHITE, COLOUR_BLACK; |
276 | |
277 | |
277 | $vbox->pack_start ((my $buttonbox = new Gtk2::HButtonBox), 0, 1, 0); |
278 | $vbox->pack_start ((my $buttonbox = new Gtk2::HButtonBox), 0, 1, 0); |
278 | |
279 | |
279 | $buttonbox->add ($self->{button_pass} = |
280 | $buttonbox->add ($self->{button_pass} = |
280 | Gtk2::Button->Glib::Object::new (label => "Pass", no_show_all => 1, visible => 0)); |
281 | Gtk2::Button->Glib::Object::new (label => "Pass", visible => 0)); |
281 | $self->{button_pass}->signal_connect (clicked => sub { |
282 | $self->{button_pass}->signal_connect (clicked => sub { |
282 | $self->{board_click}->(255, 255) if $self->{board_click}; |
283 | $self->{board_click}->(255, 255) if $self->{board_click}; |
283 | }); |
284 | }); |
|
|
285 | eval { $self->{button_pass}->set (no_show_all => 1) }; # workaround for gtk+-2.2 |
284 | $buttonbox->add ($self->{button_undo} = |
286 | $buttonbox->add ($self->{button_undo} = |
285 | Gtk2::Button->Glib::Object::new (label => "Undo", no_show_all => 1, visible => 0)); |
287 | Gtk2::Button->Glib::Object::new (label => "Undo", visible => 0)); |
286 | $self->{button_undo}->signal_connect (clicked => sub { |
288 | $self->{button_undo}->signal_connect (clicked => sub { |
287 | $self->send (req_undo => channel => $self->{channel}); |
289 | $self->send (req_undo => channel => $self->{channel}); |
288 | }); |
290 | }); |
|
|
291 | eval { $self->{button_undo}->set (no_show_all => 1) }; # workaround for gtk+-2.2 |
289 | $buttonbox->add ($self->{button_resign} = |
292 | $buttonbox->add ($self->{button_resign} = |
290 | Gtk2::Button->Glib::Object::new (label => "Resign", no_show_all => 1, visible => 0)); |
293 | Gtk2::Button->Glib::Object::new (label => "Resign", visible => 0)); |
291 | $self->{button_resign}->signal_connect (clicked => sub { |
294 | $self->{button_resign}->signal_connect (clicked => sub { |
292 | $self->send (resign_game => channel => $self->{channel}, player => $self->{colour}); |
295 | $self->send (resign_game => channel => $self->{channel}, player => $self->{colour}); |
293 | }); |
296 | }); |
|
|
297 | eval { $self->{button_resign}->set (no_show_all => 1) }; # workaround for gtk+-2.2 |
294 | |
298 | |
295 | $vbox->pack_start (($self->{chat} = new superchat), 1, 1, 0); |
299 | $vbox->pack_start (($self->{chat} = new chat), 1, 1, 0); |
296 | |
300 | |
297 | $self->set_channel ($self->{channel}); |
301 | $self->set_channel ($self->{channel}); |
298 | |
302 | |
299 | $self->show_all; |
303 | $self->show_all; |
300 | |
304 | |
… | |
… | |
458 | |
462 | |
459 | return unless $self->{joined}; |
463 | return unless $self->{joined}; |
460 | |
464 | |
461 | $self->{colour} = $self->player_colour ($self->{conn}{name}); |
465 | $self->{colour} = $self->player_colour ($self->{conn}{name}); |
462 | |
466 | |
463 | my $title = defined $self->{channel} |
|
|
464 | ? $self->owner->as_string . " " . $self->opponent_string |
|
|
465 | : "Game Window"; |
|
|
466 | $self->set_title ("KGS Game $title"); |
|
|
467 | $self->{title}->set_text ($title); |
|
|
468 | |
|
|
469 | $self->{user}[COLOUR_BLACK] = $self->{black}; |
467 | $self->{user}[COLOUR_BLACK] = $self->{black}; |
470 | $self->{user}[COLOUR_WHITE] = $self->{white}; |
468 | $self->{user}[COLOUR_WHITE] = $self->{white}; |
471 | |
469 | |
472 | # show board |
470 | # show board |
473 | if ($self->is_inprogress) { |
471 | if ($self->is_inprogress) { |
474 | if (!$self->{boardbox}->parent) { |
472 | if (!$self->{board}) { |
475 | $self->{boardbox}->add ($self->{board} = new Gtk2::GoBoard size => $self->{size}); |
473 | $self->{left}->add ($self->{board} = new Gtk2::GoBoard size => $self->{size}); |
476 | $self->{left}->add ($self->{boardbox}); |
|
|
477 | $self->{board}->signal_connect (button_release => sub { |
474 | $self->{board}->signal_connect (button_release => sub { |
|
|
475 | return unless $self->{cur_board}; |
478 | if ($_[1] == 1) { |
476 | if ($_[1] == 1) { |
479 | $self->{board_click}->($_[2], $_[3]) if $self->{board_click}; |
477 | $self->{board_click}->($_[2], $_[3]) if $self->{board_click}; |
480 | } |
478 | } |
481 | }); |
479 | }); |
|
|
480 | $self->{board}->show_all; |
482 | } |
481 | } |
483 | if (my $ch = delete $self->{challenge}) { |
482 | if (my $ch = delete $self->{challenge}) { |
484 | $_->{inlay}->destroy for values %$ch; |
483 | $_->{inlay}->destroy for values %$ch; |
485 | } |
484 | } |
486 | $self->update_cursor; |
485 | $self->update_cursor; |
487 | } |
486 | } |
488 | |
487 | |
489 | $self->{left}->show_all; |
488 | my $title = defined $self->{channel} |
|
|
489 | ? $self->owner->as_string . " " . $self->opponent_string |
|
|
490 | : "Game Window"; |
|
|
491 | $self->set_title ("KGS Game $title"); |
|
|
492 | $self->{title}->set_text ($title); # title gets redrawn wrongly |
490 | |
493 | |
491 | $self->{rules_inlay}->refresh; |
494 | $self->{rules_inlay}->refresh; |
|
|
495 | |
|
|
496 | if (exists $self->{teacher}) { |
|
|
497 | $self->{teacher_inlay} ||= $self->{chat}->new_inlay; |
|
|
498 | $self->{teacher_inlay}->clear; |
|
|
499 | $self->{teacher_inlay}->append_text ("\n<header>Teacher:</header> <user>" |
|
|
500 | . (util::toxml $self->{teacher}) . "</user>"); |
|
|
501 | } elsif ($self->{teacher_inlay}) { |
|
|
502 | (delete $self->{teacher_inlay})->clear; |
492 | |
503 | } |
|
|
504 | |
|
|
505 | $self->update_cursor; |
493 | } |
506 | } |
494 | |
507 | |
495 | sub event_update_rules { |
508 | sub event_update_rules { |
496 | my ($self, $rules) = @_; |
509 | my ($self, $rules) = @_; |
497 | |
510 | |
498 | $self->{rules} = $rules; |
511 | $self->{rules} = $rules; |
499 | |
512 | |
500 | if ($self->{user}) { |
513 | if ($self->{user}) { |
|
|
514 | # todo. gets drawn wrongly |
|
|
515 | |
501 | $self->{userpanel}[$_]->configure ($self->{app}, $self->{user}[$_], $rules) |
516 | $self->{userpanel}[$_]->configure ($self->{app}, $self->{user}[$_], $rules) |
502 | for COLOUR_BLACK, COLOUR_WHITE; |
517 | for COLOUR_BLACK, COLOUR_WHITE; |
503 | } |
518 | } |
504 | |
519 | |
505 | sound::play 3, "gamestart"; |
520 | sound::play 3, "gamestart"; |
… | |
… | |
518 | $self->{userpanel}[$colour]->set_timer ( |
533 | $self->{userpanel}[$colour]->set_timer ( |
519 | $running && $colour == $self->{whosemove} && $t->[0], |
534 | $running && $colour == $self->{whosemove} && $t->[0], |
520 | $t->[1] || $self->{rules}{time} |
535 | $t->[1] || $self->{rules}{time} |
521 | + ($self->{rules}{timesys} == TIMESYS_BYO_YOMI |
536 | + ($self->{rules}{timesys} == TIMESYS_BYO_YOMI |
522 | && $self->{rules}{interval} * $self->{rules}{count}), |
537 | && $self->{rules}{interval} * $self->{rules}{count}), |
523 | $t->[2] || $self->{rules}{count}); |
538 | $t->[2]); |
524 | } |
539 | } |
525 | } |
540 | } |
526 | |
541 | |
527 | sub inject_set_gametime { |
542 | sub inject_set_gametime { |
528 | my ($self, $msg) = @_; |
543 | my ($self, $msg) = @_; |
… | |
… | |
536 | if $self->{showmove} == @{$self->{path}}; |
551 | if $self->{showmove} == @{$self->{path}}; |
537 | } |
552 | } |
538 | |
553 | |
539 | sub update_cursor { |
554 | sub update_cursor { |
540 | my ($self) = @_; |
555 | my ($self) = @_; |
|
|
556 | |
|
|
557 | return unless $self->{cur_board}; |
541 | |
558 | |
542 | my $running = $self->{showmove} == @{$self->{path}} && $self->is_active; |
559 | my $running = $self->{showmove} == @{$self->{path}} && $self->is_active; |
543 | |
560 | |
544 | delete $self->{board_click}; |
561 | delete $self->{board_click}; |
545 | |
562 | |
… | |
… | |
579 | |
596 | |
580 | } elsif ($self->{colour} == $self->{whosemove}) { |
597 | } elsif ($self->{colour} == $self->{whosemove}) { |
581 | # normal move |
598 | # normal move |
582 | $self->{button_pass}->set (label => "Pass", visible => 1, sensitive => 1); |
599 | $self->{button_pass}->set (label => "Pass", visible => 1, sensitive => 1); |
583 | $self->{board}->set (cursor => sub { |
600 | $self->{board}->set (cursor => sub { |
584 | # if is_valid_move oder so#TODO# |
601 | $self->{cur_board} |
585 | $_[0] & (MARK_B | MARK_W) |
602 | && $self->{cur_board}->is_valid_move ($self->{colour}, $_[1], $_[2], |
586 | ? $_[0] |
603 | $self->{rules}{ruleset} == RULESET_NEW_ZEALAND) |
587 | : $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B); |
604 | ? $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B) |
|
|
605 | : $_[0]; |
588 | }); |
606 | }); |
589 | $self->{board_click} = sub { |
607 | $self->{board_click} = sub { |
|
|
608 | return unless |
|
|
609 | $self->{cur_board}->is_valid_move ($self->{colour}, $_[1], $_[2], |
|
|
610 | $self->{rules}{ruleset} == RULESET_NEW_ZEALAND); |
590 | $self->send (game_move => channel => $self->{channel}, x => $_[0], y => $_[1]); |
611 | $self->send (game_move => channel => $self->{channel}, x => $_[0], y => $_[1]); |
591 | $self->{board}->set (cursor => undef); |
612 | $self->{board}->set (cursor => undef); |
592 | delete $self->{board_click}; |
613 | delete $self->{board_click}; |
593 | $self->{button_pass}->sensitive (0); |
614 | $self->{button_pass}->sensitive (0); |
594 | }; |
615 | }; |
… | |
… | |
605 | } |
626 | } |
606 | } |
627 | } |
607 | |
628 | |
608 | sub update_board { |
629 | sub update_board { |
609 | my ($self) = @_; |
630 | my ($self) = @_; |
|
|
631 | |
610 | return unless $self->{path}; |
632 | return unless $self->{path}; |
611 | |
633 | |
612 | $self->{board_label}->set_text ("Move " . ($self->{showmove} - 1)); |
634 | $self->{board_label}->set_text ("Move " . ($self->{showmove} - 1)); |
613 | |
635 | |
614 | $self->{cur_board} = new KGS::Game::Board $self->{size}; |
636 | $self->{cur_board} = new KGS::Game::Board $self->{size}; |
615 | $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); |
637 | $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); |
616 | |
|
|
617 | $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) |
|
|
618 | for COLOUR_WHITE, COLOUR_BLACK; |
|
|
619 | |
638 | |
620 | if ($self->{rules}{ruleset} == RULESET_JAPANESE) { |
639 | if ($self->{rules}{ruleset} == RULESET_JAPANESE) { |
621 | if ($self->{curnode}{move} == 0) { |
640 | if ($self->{curnode}{move} == 0) { |
622 | $self->{whosemove} = $self->{handicap} ? COLOUR_WHITE : COLOUR_BLACK; |
641 | $self->{whosemove} = $self->{handicap} ? COLOUR_WHITE : COLOUR_BLACK; |
623 | } else { |
642 | } else { |
… | |
… | |
632 | } else { |
651 | } else { |
633 | $self->{whosemove} = 1 - $self->{cur_board}{last}; |
652 | $self->{whosemove} = 1 - $self->{cur_board}{last}; |
634 | } |
653 | } |
635 | } |
654 | } |
636 | |
655 | |
|
|
656 | $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) |
|
|
657 | for COLOUR_WHITE, COLOUR_BLACK; |
|
|
658 | |
637 | my $start_time = $self->{rules}{time}; |
659 | my $start_time = $self->{rules}{time}; |
638 | |
660 | |
639 | if ($self->{showmove} == @{$self->{path}}) { |
661 | if ($self->{showmove} == @{$self->{path}}) { |
640 | $self->{timers} = [ |
662 | $self->{timers} = [ |
641 | [$self->{lastmove_time}, @{$self->{cur_board}{timer}[0]}], |
663 | [$self->{lastmove_time}, @{$self->{cur_board}{timer}[0]}], |
… | |
… | |
662 | } elsif ($self->{score_inlay}) { |
684 | } elsif ($self->{score_inlay}) { |
663 | (delete $self->{score_inlay})->clear; |
685 | (delete $self->{score_inlay})->clear; |
664 | } |
686 | } |
665 | |
687 | |
666 | $self->update_cursor; |
688 | $self->update_cursor; |
|
|
689 | |
667 | } |
690 | } |
668 | |
691 | |
669 | sub event_update_tree { |
692 | sub event_update_tree { |
670 | my ($self) = @_; |
693 | my ($self) = @_; |
671 | |
694 | |
… | |
… | |
739 | |
762 | |
740 | sound::play 3, "resign"; |
763 | sound::play 3, "resign"; |
741 | $self->{chat}->append_text ("\n<infoblock><header>Resign</header>" |
764 | $self->{chat}->append_text ("\n<infoblock><header>Resign</header>" |
742 | . "\n<user>" |
765 | . "\n<user>" |
743 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
766 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
744 | . "</user> resigned.</infoblock>"); |
767 | . "</user> resigned." |
|
|
768 | . "\n<user>" |
|
|
769 | . (util::toxml $self->{user}[1 - $msg->{player}]->as_string) |
|
|
770 | . "</user> wins the game." |
|
|
771 | . "</infoblock>"); |
745 | } |
772 | } |
746 | |
773 | |
747 | sub event_out_of_time { |
774 | sub event_time_win { |
748 | my ($self, $player) = @_; |
775 | my ($self, $player) = @_; |
749 | |
776 | |
750 | sound::play 3, "timewin"; |
777 | sound::play 3, "timewin"; |
751 | $self->{chat}->append_text ("\n<infoblock><header>Out of Time</header>" |
778 | $self->{chat}->append_text ("\n<infoblock><header>Out of Time</header>" |
752 | . "\n<user>" |
779 | . "\n<user>" |
|
|
780 | . (util::toxml $self->{user}[1 - $msg->{player}]->as_string) |
|
|
781 | . "</user> ran out of time and lost." |
|
|
782 | . "\n<user>" |
753 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
783 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
|
|
784 | . "</user> wins the game." |
|
|
785 | . "</infoblock>"); |
|
|
786 | } |
|
|
787 | |
|
|
788 | sub event_owner_left { |
|
|
789 | my ($self) = @_; |
|
|
790 | |
|
|
791 | $self->{chat}->append_text ("\n<infoblock><header>Owner left</header>" |
754 | . "</user> ran out of time and lost.</infoblock>"); |
792 | . "\nThe owner of this game left.</infoblock>"); |
|
|
793 | } |
|
|
794 | |
|
|
795 | sub event_teacher_left { |
|
|
796 | my ($self) = @_; |
|
|
797 | |
|
|
798 | $self->{chat}->append_text ("\n<infoblock><header>Teacher left</header>" |
|
|
799 | . "\nThe teacher left the game.</infoblock>"); |
755 | } |
800 | } |
756 | |
801 | |
757 | sub event_done { |
802 | sub event_done { |
758 | my ($self) = @_; |
803 | my ($self) = @_; |
759 | |
804 | |