… | |
… | |
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->I# |
272 | $buttonbox->add ($self->{button_pass} = |
|
|
273 | Gtk2::Button->Glib::Object::new (label => "Pass", no_show_all => 1, visible => 0)); |
|
|
274 | $self->{button_pass}->signal_connect (clicked => sub { |
|
|
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}); |
|
|
286 | }); |
260 | |
287 | |
261 | $vbox->pack_start (($self->{chat} = new superchat), 1, 1, 0); |
288 | $vbox->pack_start (($self->{chat} = new superchat), 1, 1, 0); |
262 | |
289 | |
263 | $self->set_channel ($self->{channel}); |
290 | $self->set_channel ($self->{channel}); |
264 | |
291 | |
… | |
… | |
323 | } |
350 | } |
324 | |
351 | |
325 | sub update_cursor { |
352 | sub update_cursor { |
326 | my ($self) = @_; |
353 | my ($self) = @_; |
327 | |
354 | |
328 | my $move = int $self->{moveadj}->get_value; |
|
|
329 | my $cb; |
|
|
330 | |
|
|
331 | warn sprintf "move %d spath %d\n", $move, scalar @{$self->{path}};#d# |
|
|
332 | warn "isact " . $self->is_active;#d# |
|
|
333 | warn "flags $self->{flags}\n";#d# |
|
|
334 | warn "handi $self->{handicap}\n";#d# |
|
|
335 | |
|
|
336 | my $running = $move == @{$self->{path}}; # && $self->is_active; |
355 | my $running = $self->{showmove} == @{$self->{path}} && $self->is_active; |
337 | |
356 | |
338 | delete $self->{board_click}; |
357 | delete $self->{board_click}; |
339 | |
358 | |
340 | warn "plaiyng $self->{playing} running $running last $self->{cur_board}{last}\n";#d# |
|
|
341 | if ($self->{teacher} eq $self->{app}{conn}) { |
359 | if ($self->{teacher} eq $self->{app}{conn}) { |
342 | #TODO# # teaching mode not implemented |
360 | #TODO# # teaching mode not implemented |
|
|
361 | $self->{button_pass}->set (label => "Pass", sensitive => 1, visible => 1); |
|
|
362 | $self->{button_undo}->hide; |
|
|
363 | $self->{button_resign}->hide; |
343 | $self->{board}->set (cursor_mask => 0, cursor_value => 0); |
364 | $self->{board}->set (cursor => undef); |
344 | } elsif (!$self->{playing} || !$running) { |
365 | |
|
|
366 | } elsif ($running && $self->{colour} != COLOUR_NONE) { |
|
|
367 | # during game |
|
|
368 | $self->{button_undo}->show; |
|
|
369 | $self->{button_resign}->show; |
|
|
370 | |
|
|
371 | if ($self->{cur_board}{score}) { |
|
|
372 | # during scoring |
|
|
373 | $self->{button_pass}->set (label => "Done", sensitive => 1, visible => 1); |
|
|
374 | $self->{board}->set (cursor => sub { |
|
|
375 | $_[0] & (MARK_B | MARK_W) |
|
|
376 | ? $_[0] ^ MARK_GRAYED |
|
|
377 | : $_[0]; |
|
|
378 | }); |
|
|
379 | $self->{board_click} = sub { |
|
|
380 | if ($_[0] == 255) { |
|
|
381 | $self->{button_pass}->sensitive (0); |
|
|
382 | $self->done; |
|
|
383 | } else { |
|
|
384 | $self->send (mark_dead => |
|
|
385 | channel => $self->{channel}, |
|
|
386 | x => $_[0], |
|
|
387 | y => $_[1], |
|
|
388 | dead => !($self->{cur_board}{board}[$_[0]][$_[1]] & MARK_GRAYED), |
|
|
389 | ); |
|
|
390 | } |
|
|
391 | }; |
|
|
392 | |
|
|
393 | } elsif (1 - $self->{colour} == $self->{cur_board}{last}) { |
|
|
394 | # normal move |
|
|
395 | $self->{button_pass}->set (label => "Pass", sensitive => 1, visible => 1); |
|
|
396 | $self->{board}->set (cursor => sub { |
|
|
397 | # if is_valid_move oder so#TODO# |
|
|
398 | $_[0] & (MARK_B | MARK_W) |
|
|
399 | ? $_[0] |
|
|
400 | : $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B); |
|
|
401 | }); |
|
|
402 | $self->{board_click} = sub { |
|
|
403 | $self->send (game_move => channel => $self->{channel}, x => $_[0], y => $_[1]); |
|
|
404 | $self->{board}->set (cursor => undef); |
|
|
405 | delete $self->{board_click}; |
|
|
406 | $self->{button_pass}->sensitive (0); |
|
|
407 | }; |
|
|
408 | } else { |
|
|
409 | $self->{button_pass}->set (label => "Pass", sensitive => 0, visible => 1); |
|
|
410 | } |
|
|
411 | } else { |
|
|
412 | $self->{button_undo}->hide; |
|
|
413 | $self->{button_resign}->hide; |
|
|
414 | $self->{button_pass}->hide; |
345 | $self->{board}->set (cursor_mask => 0, cursor_value => 0); |
415 | $self->{board}->set (cursor => undef); |
346 | #TODO# # implement coordinate-grabbing |
416 | #TODO# # implement coordinate-grabbing |
347 | } elsif ($self->{black}{name} eq $self->{conn}{name} && $self->{cur_board}{last} == COLOUR_WHITE) { |
417 | } |
348 | $self->{board}->set (cursor_mask => MARK_B | MARK_W, cursor_value => MARK_B | MARK_GRAYED); |
418 | } |
349 | $self->{board_click} = sub { |
419 | |
350 | $self->send (game_move => channel => $self->{channel}, x => $_[0], y => $_[1]); |
420 | sub update_timers { |
351 | $self->{board}->set (cursor_mask => 0, cursor_value => 0); |
421 | my ($self, $timers) = @_; |
352 | delete $self->{board_click}; |
422 | |
353 | }; |
423 | my $running = $self->{showmove} == @{$self->{path}} && !$self->{teacher}; |
354 | } elsif ($self->{white}{name} eq $self->{conn}{name} && $self->{cur_board}{last} == COLOUR_BLACK) { |
424 | |
355 | $self->{board}->set (cursor_mask => MARK_B | MARK_W, cursor_value => MARK_W | MARK_GRAYED); |
425 | for my $colour (COLOUR_BLACK, COLOUR_WHITE) { |
356 | $self->{board_click} = sub { |
426 | my $t = $timers->[$colour]; |
357 | $self->send (game_move => channel => $self->{channel}, x => $_[0], y => $_[1]); |
427 | $self->{userpanel}[$colour]->set_timer ( |
358 | $self->{board}->set (cursor_mask => 0, cursor_value => 0); |
428 | $running && $self->{lastmove_colour} == 1 - $colour && $t->[0], |
359 | delete $self->{board_click}; |
429 | $t->[1], $t->[2]); |
360 | }; |
|
|
361 | } |
430 | } |
362 | } |
431 | } |
363 | |
432 | |
364 | sub update_board { |
433 | sub update_board { |
365 | my ($self) = @_; |
434 | my ($self) = @_; |
366 | return unless $self->{path}; |
435 | return unless $self->{path}; |
367 | |
436 | |
368 | my $move = int $self->{moveadj}->get_value; |
|
|
369 | |
|
|
370 | my $running = $move == @{$self->{path}} && !$self->{teacher}; |
|
|
371 | |
|
|
372 | $self->{board_label}->set_text ("Move " . ($move - 1)); |
437 | $self->{board_label}->set_text ("Move " . ($self->{showmove} - 1)); |
373 | |
438 | |
374 | $self->{cur_board} = new KGS::Game::Board $self->{size}; |
439 | $self->{cur_board} = new KGS::Game::Board $self->{size}; |
375 | $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $move - 1]]); |
440 | $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); |
376 | |
441 | |
|
|
442 | $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) |
377 | for my $colour (COLOUR_WHITE, COLOUR_BLACK) { |
443 | for COLOUR_WHITE, COLOUR_BLACK; |
378 | $self->{userpanel}[$colour]->set_state ( |
444 | |
379 | $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 ([ |
380 | $self->{cur_board}{timer}[$colour], |
453 | [0, @{$self->{cur_board}{timer}[0]}], |
381 | ($running && $self->{lastmove_colour} == !$colour) |
454 | [0, @{$self->{cur_board}{timer}[1]}], |
382 | ? $self->{lastmove_time} : 0 |
|
|
383 | ); |
455 | ]); |
384 | } |
456 | } |
385 | |
457 | |
386 | $self->{board}->set_board ($self->{cur_board}); |
458 | $self->{board}->set_board ($self->{cur_board}); |
387 | |
459 | |
388 | $self->update_cursor; |
460 | $self->update_cursor; |
… | |
… | |
474 | |
546 | |
475 | $self->SUPER::event_update_game; |
547 | $self->SUPER::event_update_game; |
476 | |
548 | |
477 | return unless $self->{joined}; |
549 | return unless $self->{joined}; |
478 | |
550 | |
479 | $self->{playing} = $self->{teacher} eq $self->{conn}{name} |
551 | $self->{colour} = $self->player_colour ($self->{conn}{name}); |
480 | || $self->is_playing ($self->{conn}{name}); |
|
|
481 | |
552 | |
482 | my $title = defined $self->{channel} |
553 | my $title = defined $self->{channel} |
483 | ? $self->owner->as_string . " " . $self->opponent_string |
554 | ? $self->owner->as_string . " " . $self->opponent_string |
484 | : "Game Window"; |
555 | : "Game Window"; |
485 | $self->set_title("KGS Game $title"); |
556 | $self->set_title ("KGS Game $title"); |
486 | $self->{title}->set_text ($title); |
557 | $self->{title}->set_text ($title); |
487 | |
558 | |
488 | $self->{user}[COLOUR_BLACK] = $self->{black}; |
559 | $self->{user}[COLOUR_BLACK] = $self->{black}; |
489 | $self->{user}[COLOUR_WHITE] = $self->{white}; |
560 | $self->{user}[COLOUR_WHITE] = $self->{white}; |
490 | |
561 | |
… | |
… | |
597 | |
668 | |
598 | sub event_out_of_time { |
669 | sub event_out_of_time { |
599 | my ($self, $player) = @_; |
670 | my ($self, $player) = @_; |
600 | |
671 | |
601 | sound::play 3, "timewin"; |
672 | sound::play 3, "timewin"; |
602 | $self->{chat}->append_text ("\n<infoblock><header>Time Over</header>" |
673 | $self->{chat}->append_text ("\n<infoblock><header>Out of Time</header>" |
603 | . "\n<user>" |
674 | . "\n<user>" |
604 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
675 | . (util::toxml $self->{user}[$msg->{player}]->as_string) |
605 | . "</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 | } |
606 | } |
686 | } |
607 | |
687 | |
608 | sub inject_final_result { |
688 | sub inject_final_result { |
609 | my ($self, $msg) = @_; |
689 | my ($self, $msg) = @_; |
610 | |
690 | |
… | |
… | |
613 | . "\nBlack Score " . (util::toxml $msg->{blackscore}->as_string) |
693 | . "\nBlack Score " . (util::toxml $msg->{blackscore}->as_string) |
614 | . "</infoblock>" |
694 | . "</infoblock>" |
615 | ); |
695 | ); |
616 | } |
696 | } |
617 | |
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}}; |
|
|
709 | } |
|
|
710 | |
618 | sub inject_req_undo { |
711 | sub inject_req_undo { |
619 | my ($self, $msg) = @_; |
712 | my ($self, $msg) = @_; |
620 | |
713 | |
621 | my $inlay = $self->{undo_inlay} ||= $self->{chat}->new_inlay; |
714 | my $inlay = $self->{undo_inlay} ||= $self->{chat}->new_inlay; |
622 | return if $inlay->{ignore}; |
715 | return if $inlay->{ignore}; |
623 | |
716 | |
|
|
717 | sound::play 3, "warning" unless $inlay->{count}; |
624 | $inlay->{count}++; |
718 | $inlay->{count}++; |
|
|
719 | |
625 | $inlay->clear; |
720 | $inlay->clear; |
626 | $inlay->append_text ("\n<undo>Undo requested ($inlay->{count} times)</undo>\n"); |
721 | $inlay->append_text ("\n<undo>Undo requested ($inlay->{count} times)</undo>\n"); |
627 | $inlay->append_button ("Grant", sub { |
722 | $inlay->append_button ("Grant", sub { |
628 | $inlay->clear; |
723 | $inlay->clear; |
629 | $self->send (grant_undo => channel => $self->{channel}); |
724 | $self->send (grant_undo => channel => $self->{channel}); |
… | |
… | |
632 | $inlay->clear; |
727 | $inlay->clear; |
633 | $inlay->{ignore} = 1; |
728 | $inlay->{ignore} = 1; |
634 | # but leave inlay, so further undo requests get counted |
729 | # but leave inlay, so further undo requests get counted |
635 | }); |
730 | }); |
636 | |
731 | |
|
|
732 | $self->{chat}->set_end; |
637 | } |
733 | } |
638 | |
734 | |
639 | sub inject_new_game { |
735 | sub inject_new_game { |
640 | my ($self, $msg) = @_; |
736 | my ($self, $msg) = @_; |
641 | |
737 | |
… | |
… | |
647 | my ($self, $id) = @_; |
743 | my ($self, $id) = @_; |
648 | |
744 | |
649 | my $info = $self->{challenge}{$id}; |
745 | my $info = $self->{challenge}{$id}; |
650 | my $inlay = $info->{inlay}; |
746 | my $inlay = $info->{inlay}; |
651 | my $rules = $info->{rules}; |
747 | my $rules = $info->{rules}; |
652 | warn "drawchal $id\n";#d# |
|
|
653 | # use PApp::Util; warn PApp::Util::dumpval $challenge;#d# |
|
|
654 | |
748 | |
655 | my $as_black = $info->{black}{name} eq $self->{conn}{name} ? 1 : 0;; |
749 | my $as_black = $info->{black}{name} eq $self->{conn}{name} ? 1 : 0;; |
656 | my $opponent = $as_black ? $info->{white} : $info->{black}; |
750 | my $opponent = $as_black ? $info->{white} : $info->{black}; |
657 | |
751 | |
658 | my ($size, $time, $interval, $count); |
752 | my ($size, $time, $interval, $count); |
… | |
… | |
818 | my $as_black = $info->{black}->{name} eq $self->{conn}{name}; |
912 | my $as_black = $info->{black}->{name} eq $self->{conn}{name}; |
819 | my $opponent = $as_black ? $info->{white} : $info->{black}; |
913 | my $opponent = $as_black ? $info->{white} : $info->{black}; |
820 | |
914 | |
821 | my $id = $opponent->{name}; |
915 | my $id = $opponent->{name}; |
822 | |
916 | |
823 | warn "challenge from $opponent->{name}\n";#d# |
|
|
824 | |
|
|
825 | $self->{challenge}{$id} = $info; |
917 | $self->{challenge}{$id} = $info; |
826 | $self->{challenge}{$id}{inlay} = $self->{chat}->new_switchable_inlay ( |
918 | $self->{challenge}{$id}{inlay} = $self->{chat}->new_switchable_inlay ( |
827 | exists $self->{challenge}{""} |
919 | exists $self->{challenge}{""} |
828 | ? "Challenge from $opponent->{name}" |
920 | ? "Challenge from $opponent->{name}" |
829 | : "Challenge to $opponent->{name}", |
921 | : "Challenge to $opponent->{name}", |
830 | sub { |
922 | sub { |
831 | $self->{challenge}{$id}{inlay} = $_[0]; |
923 | $self->{challenge}{$id}{inlay} = $_[0]; |
832 | $self->draw_challenge ($id); |
924 | $self->draw_challenge ($id); |
833 | }, |
925 | }, |
834 | 1 || !exists $self->{challenge}{""} # only open when not offerer |
926 | !exists $self->{challenge}{""} # only open when not offerer |
835 | ); |
927 | ); |
836 | } |
928 | } |
837 | |
929 | |
838 | 1; |
930 | 1; |
839 | |
931 | |