ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/kgsueme/kgsueme/game.pl
(Generate patch)

Comparing kgsueme/kgsueme/game.pl (file contents):
Revision 1.104 by pcg, Sun May 30 07:24:06 2004 UTC vs.
Revision 1.110 by root, Mon May 31 17:06:19 2004 UTC

21 21
22 $self->{set} = sub { }; 22 $self->{set} = sub { };
23 $self->{format} = sub { "???" }; 23 $self->{format} = sub { "???" };
24} 24}
25 25
26sub FINALIZE_INSTANCE {
27 my $self = shift;
28
29 $self->stop;
30}
31
26sub configure { 32sub configure {
27 my ($self, $timesys, $main, $interval, $count) = @_; 33 my ($self, $timesys, $main, $interval, $count) = @_;
28 34
29 if ($timesys == TIMESYS_ABSOLUTE) { 35 if ($timesys == TIMESYS_ABSOLUTE) {
30 $self->{set} = sub { $self->{time} = $_[0] }; 36 $self->{format} = sub {
31 $self->{format} = sub { util::format_time $_[0] }; 37 if ($_[0] <= 0) {
38 "TIMEOUT";
39 } else {
40 util::format_time $_[0];
41 }
42 };
32 43
33 } elsif ($timesys == TIMESYS_BYO_YOMI) { 44 } elsif ($timesys == TIMESYS_BYO_YOMI) {
34 my $low = $interval * $count; 45 my $low = $interval * $count;
35 46
36 $self->{set} = sub { $self->{time} = $_[0] };
37
38 $self->{format} = sub { 47 $self->{format} = sub {
48 if ($_[0] <= 0) {
49 "TIMEOUT";
39 if ($_[0] > $low) { 50 } elsif ($_[0] > $low) {
40 util::format_time $_[0] - $low; 51 util::format_time $_[0] - $low;
41 } else { 52 } else {
42 sprintf "%s (%d)", 53 sprintf "%s (%d)",
43 util::format_time int (($_[0] - 1) % $interval + 1), 54 util::format_time int (($_[0] - 1) % $interval + 1),
44 ($_[0] - 1) / $interval; 55 ($_[0] - 1) / $interval;
45 } 56 }
46 }; 57 };
47 58
48 } elsif ($timesys == TIMESYS_CANADIAN) { 59 } elsif ($timesys == TIMESYS_CANADIAN) {
49 $self->{set} = sub { $self->{time} = $_[0]; $self->{moves} = $_[1] };
50
51 $self->{format} = sub { 60 $self->{format} = sub {
61 if ($_[0] <= 0) {
62 "TIMEOUT";
52 if (!$self->{moves}) { 63 } elsif (!$self->{moves}) {
53 util::format_time $_[0] - $low; 64 util::format_time $_[0] - $low;
54 } else { 65 } else {
55 my $time = int (($_[0] - 1) % $interval + 1); 66 my $time = int (($_[0] - 1) % $interval + 1);
56 67
57 sprintf "%s/%d =%d", 68 sprintf "%s/%d =%d",
63 } 74 }
64 }; 75 };
65 76
66 } else { 77 } else {
67 # none, or unknown 78 # none, or unknown
68 $self->{set} = sub { };
69 $self->{format} = sub { "---" } 79 $self->{format} = sub { "-" }
70 } 80 }
71} 81}
72 82
73sub refresh { 83sub refresh {
74 my ($self, $timestamp) = @_; 84 my ($self, $timestamp) = @_;
76 86
77 # we round the timer value slightly... the protocol isn't exact anyways, 87 # we round the timer value slightly... the protocol isn't exact anyways,
78 # and this gives smoother timers ;) 88 # and this gives smoother timers ;)
79 my $timer2 = int $timer + 0.4; 89 my $timer2 = int $timer + 0.4;
80 90
81 if ($timer2 <= 0) { 91 $self->set_text ($self->{format}->($timer2));
82 $timer2 = 0 if $timer2 < 0; 92
83 $self->set_text ("TIME OUT"); 93 $timer - int $timer;
94}
95
96sub set_time {
97 my ($self, $start, $time, $moves) = @_;
98
99 $self->{time} = $time;
100 $self->{moves} = $moves;
101
102 if ($start) {
103 $self->{start} = $start;
104 $self->start;
84 } else { 105 } else {
85 $self->set_text ($self->{format}->($timer2)); 106 $self->stop;
86 }
87
88 $timer - int $timer;
89}
90
91sub set_time {
92 my ($self, $time, $moves) = @_;
93
94 # we ignore requests to re-set the time of a running clock.
95 # this is the easiest way to ensure that commentary etc.
96 # doesn't re-set the clock. yes, this is frickle design,
97 # but I think the protocol is to blame here, which gives
98 # very little time information. (cgoban2 also has had quite
99 # a lot of small time update problems...)
100 unless ($self->{timeout}) {
101 $self->{set}->($time, $moves);
102 $self->refresh ($self->{start}); 107 $self->refresh ($self->{start});
103 } 108 }
104} 109}
105 110
106sub start { 111sub start {
107 my ($self, $when) = @_; 112 my ($self) = @_;
108 113
109 $self->stop; 114 $self->stop;
110
111 $self->{start} = $when;
112 115
113 my $timeout; $timeout = sub { 116 my $timeout; $timeout = sub {
114 my $next = $self->refresh (Time::HiRes::time) * 1000; 117 my $next = $self->refresh (Time::HiRes::time) * 1000;
115 $next += 1000 if $next < 0; 118 $next += 1000 if $next < 0;
116 $self->{timeout} = add Glib::Timeout $next, $timeout; 119 $self->{timeout} = add Glib::Timeout $next, $timeout;
180 183
181 $self->{info}->set_text ("$captures pris."); 184 $self->{info}->set_text ("$captures pris.");
182} 185}
183 186
184sub set_timer { 187sub set_timer {
185 my ($self, $when, $time, $moves) = @_; 188 my ($self, $start, $time, $moves) = @_;
186 189
187 $self->{clock}->stop unless $when;
188 $self->{clock}->set_time ($time, $moves); 190 $self->{clock}->set_time ($start, $time, $moves);
189 $self->{clock}->start ($when) if $when;
190} 191}
191 192
192package game; 193package game;
193 194
194use Scalar::Util qw(weaken); 195use Scalar::Util qw(weaken);
356 357
357 delete $self->{board_click}; 358 delete $self->{board_click};
358 359
359 if ($self->{teacher} eq $self->{app}{conn}) { 360 if ($self->{teacher} eq $self->{app}{conn}) {
360 #TODO# # teaching mode not implemented 361 #TODO# # teaching mode not implemented
361 $self->{button_pass}->set (label => "Pass", sensitive => 1, visible => 1); 362 $self->{button_pass}->set (label => "Pass", visible => 1, sensitive => 1);
362 $self->{button_undo}->hide; 363 $self->{button_undo}->hide;
363 $self->{button_resign}->hide; 364 $self->{button_resign}->hide;
364 $self->{board}->set (cursor => undef); 365 $self->{board}->set (cursor => undef);
365 366
366 } elsif ($running && $self->{colour} != COLOUR_NONE) { 367 } elsif ($running && $self->{colour} != COLOUR_NONE) {
368 $self->{button_undo}->show; 369 $self->{button_undo}->show;
369 $self->{button_resign}->show; 370 $self->{button_resign}->show;
370 371
371 if ($self->{cur_board}{score}) { 372 if ($self->{cur_board}{score}) {
372 # during scoring 373 # during scoring
373 $self->{button_pass}->set (label => "Done", sensitive => 1, visible => 1); 374 $self->{button_pass}->set (label => "Done", visible => 1, sensitive => 1);
374 $self->{board}->set (cursor => sub { 375 $self->{board}->set (cursor => sub {
375 $_[0] & (MARK_B | MARK_W) 376 $_[0] & (MARK_B | MARK_W)
376 ? $_[0] ^ MARK_GRAYED 377 ? $_[0] ^ MARK_GRAYED
377 : $_[0]; 378 : $_[0];
378 }); 379 });
388 dead => !($self->{cur_board}{board}[$_[0]][$_[1]] & MARK_GRAYED), 389 dead => !($self->{cur_board}{board}[$_[0]][$_[1]] & MARK_GRAYED),
389 ); 390 );
390 } 391 }
391 }; 392 };
392 393
393 } elsif (1 - $self->{colour} == $self->{cur_board}{last}) { 394 } elsif ($self->{colour} == $self->{whosemove}) {
394 # normal move 395 # normal move
395 $self->{button_pass}->set (label => "Pass", sensitive => 1, visible => 1); 396 $self->{button_pass}->set (label => "Pass", visible => 1, sensitive => 1);
396 $self->{board}->set (cursor => sub { 397 $self->{board}->set (cursor => sub {
397 # if is_valid_move oder so#TODO# 398 # if is_valid_move oder so#TODO#
398 $_[0] & (MARK_B | MARK_W) 399 $_[0] & (MARK_B | MARK_W)
399 ? $_[0] 400 ? $_[0]
400 : $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B); 401 : $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B);
405 delete $self->{board_click}; 406 delete $self->{board_click};
406 $self->{button_pass}->sensitive (0); 407 $self->{button_pass}->sensitive (0);
407 }; 408 };
408 } else { 409 } else {
409 $self->{button_pass}->set (label => "Pass", sensitive => 0, visible => 1); 410 $self->{button_pass}->set (label => "Pass", sensitive => 0, visible => 1);
411 $self->{board}->set (cursor => undef);
410 } 412 }
411 } else { 413 } else {
412 $self->{button_undo}->hide; 414 $self->{button_undo}->hide;
413 $self->{button_resign}->hide; 415 $self->{button_resign}->hide;
414 $self->{button_pass}->hide; 416 $self->{button_pass}->hide;
423 my $running = $self->{showmove} == @{$self->{path}} && !$self->{teacher}; 425 my $running = $self->{showmove} == @{$self->{path}} && !$self->{teacher};
424 426
425 for my $colour (COLOUR_BLACK, COLOUR_WHITE) { 427 for my $colour (COLOUR_BLACK, COLOUR_WHITE) {
426 my $t = $timers->[$colour]; 428 my $t = $timers->[$colour];
427 $self->{userpanel}[$colour]->set_timer ( 429 $self->{userpanel}[$colour]->set_timer (
428 $running && $self->{lastmove_colour} == 1 - $colour && $t->[0], 430 $running && $colour == $self->{whosemove} && $t->[0],
429 $t->[1], $t->[2]); 431 $t->[1] || $self->{rules}{time}
432 + ($self->{rules}{timesys} == TIMESYS_BYO_YOMI
433 && $self->{rules}{interval} * $self->{rules}{count}),
434 $t->[2] || $self->{rules}{count});
430 } 435 }
431} 436}
432 437
433sub update_board { 438sub update_board {
434 my ($self) = @_; 439 my ($self) = @_;
439 $self->{cur_board} = new KGS::Game::Board $self->{size}; 444 $self->{cur_board} = new KGS::Game::Board $self->{size};
440 $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); 445 $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]);
441 446
442 $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) 447 $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_])
443 for COLOUR_WHITE, COLOUR_BLACK; 448 for COLOUR_WHITE, COLOUR_BLACK;
449
450 if ($self->{rules}{ruleset} == RULESET_JAPANESE) {
451 if ($self->{curnode}{move} == 0) {
452 $self->{whosemove} = $self->{handicap} ? COLOUR_WHITE : COLOUR_BLACK;
453 } else {
454 $self->{whosemove} = 1 - $self->{cur_board}{last};
455 }
456 } else {
457 # Chinese, Aga, NZ all have manual placement
458 if ($self->{curnode}{move} < $self->{handicap}) {
459 $self->{whosemove} = COLOUR_BLACK;
460 } elsif ($self->{curnode}{move} == $self->{handicap}) {
461 $self->{whosemove} = $self->{handicap} ? COLOUR_WHITE : COLOUR_BLACK;
462 } else {
463 $self->{whosemove} = 1 - $self->{cur_board}{last};
464 }
465 }
466
467 my $start_time = $self->{rules}{time};
444 468
445 if ($self->{showmove} == @{$self->{path}}) { 469 if ($self->{showmove} == @{$self->{path}}) {
446 $self->{timers} = [ 470 $self->{timers} = [
447 [$self->{lastmove_time}, @{$self->{cur_board}{timer}[0]}], 471 [$self->{lastmove_time}, @{$self->{cur_board}{timer}[0]}],
448 [$self->{lastmove_time}, @{$self->{cur_board}{timer}[1]}], 472 [$self->{lastmove_time}, @{$self->{cur_board}{timer}[1]}],
455 ]); 479 ]);
456 } 480 }
457 481
458 $self->{board}->set_board ($self->{cur_board}); 482 $self->{board}->set_board ($self->{cur_board});
459 483
484 if ($self->{cur_board}{score}) {
485 $self->{score_inlay} ||= $self->{chat}->new_inlay;
486 $self->{score_inlay}->clear;
487 $self->{score_inlay}->append_text ("\n<header>Scoring</header>"
488 . "\n<score>"
489 . "White: $self->{cur_board}{score}[COLOUR_WHITE], "
490 . "Black: $self->{cur_board}{score}[COLOUR_BLACK]"
491 . "</score>");
492 } elsif ($self->{score_inlay}) {
493 (delete $self->{score_inlay})->clear;
494 }
495
460 $self->update_cursor; 496 $self->update_cursor;
461} 497}
462 498
463sub event_update_tree { 499sub event_update_tree {
464 my ($self) = @_; 500 my ($self) = @_;
501
502 (delete $self->{undo_inlay})->clear
503 if $self->{undo_inlay};
465 504
466 $self->{path} = $self->get_path; 505 $self->{path} = $self->get_path;
467 506
468 if ($self->{moveadj}) { 507 if ($self->{moveadj}) {
469 my $upper = $self->{moveadj}->upper; 508 my $upper = $self->{moveadj}->upper;
532 $self->destroy; 571 $self->destroy;
533} 572}
534 573
535sub event_move { 574sub event_move {
536 my ($self, $pass) = @_; 575 my ($self, $pass) = @_;
576
537 sound::play 1, $pass ? "pass" : "move"; 577 sound::play 1, $pass ? "pass" : "move";
538
539 if ($self->{undo_inlay}) {
540 (delete $self->{undo_inlay})->clear;
541 }
542} 578}
543 579
544sub event_update_game { 580sub event_update_game {
545 my ($self) = @_; 581 my ($self) = @_;
546 582
650 $self->{userpanel}[$_]->configure ($self->{app}, $self->{user}[$_], $rules) 686 $self->{userpanel}[$_]->configure ($self->{app}, $self->{user}[$_], $rules)
651 for COLOUR_BLACK, COLOUR_WHITE; 687 for COLOUR_BLACK, COLOUR_WHITE;
652 } 688 }
653 689
654 sound::play 3, "gamestart"; 690 sound::play 3, "gamestart";
655
656 $self->{rules_inlay}->refresh; 691 $self->{rules_inlay}->refresh;
657} 692}
658 693
659sub event_resign_game { 694sub event_resign_game {
660 my ($self, $player) = @_; 695 my ($self, $player) = @_;
678 713
679sub event_done { 714sub event_done {
680 my ($self) = @_; 715 my ($self) = @_;
681 716
682 if ($self->{done}[1 - $self->{colour}] && !$self->{done}[$self->{colour}]) { 717 if ($self->{done}[1 - $self->{colour}] && !$self->{done}[$self->{colour}]) {
718 sound::play 2, "info" unless $inlay->{count};
683 $self->{chat}->append_text ("\n<infoblock><header>Done</header>" 719 $self->{chat}->append_text ("\n<infoblock><header>Press Done</header>"
684 . "\nYour opponent pressed done."); 720 . "\nYour opponent pressed done. Now it's up to you.");
685 } 721 }
722 if ($self->{doneid} & 0x80000000) {
723 sound::play 2, "info" unless $inlay->{count};
724 $self->{chat}->append_text ("\n<infoblock><header>Press Done Again</header>"
725 . "\nThe board has changed.");
726 }
727
728 $self->{button_pass}->sensitive (!$self->{done}[$self->{colour}]);
729
730 $self->{chat}->set_end;
686} 731}
687 732
688sub inject_final_result { 733sub inject_final_result {
689 my ($self, $msg) = @_; 734 my ($self, $msg) = @_;
690 735
701 $self->{timers} = [ 746 $self->{timers} = [
702 [$msg->{NOW}, $msg->{black_time}, $msg->{black_moves}], 747 [$msg->{NOW}, $msg->{black_time}, $msg->{black_moves}],
703 [$msg->{NOW}, $msg->{white_time}, $msg->{white_moves}], 748 [$msg->{NOW}, $msg->{white_time}, $msg->{white_moves}],
704 ]; 749 ];
705 750
706 print "SGT\n";#d#
707 $self->update_timers ($self->{timers}) 751 $self->update_timers ($self->{timers})
708 if $self->{showmove} == @{$self->{path}}; 752 if $self->{showmove} == @{$self->{path}};
709} 753}
710 754
711sub inject_req_undo { 755sub inject_req_undo {
712 my ($self, $msg) = @_; 756 my ($self, $msg) = @_;
713 757
714 my $inlay = $self->{undo_inlay} ||= $self->{chat}->new_inlay; 758 my $inlay = $self->{undo_inlay} ||= $self->{chat}->new_inlay;
715 return if $inlay->{ignore}; 759 return if $inlay->{ignore};
716 760
717 sound::play 3, "warning" unless $inlay->{count}; 761 sound::play 2, "warning" unless $inlay->{count};
718 $inlay->{count}++; 762 $inlay->{count}++;
719 763
720 $inlay->clear; 764 $inlay->clear;
721 $inlay->append_text ("\n<undo>Undo requested ($inlay->{count} times)</undo>\n"); 765 $inlay->append_text ("\n<undo>Undo requested ($inlay->{count} times)</undo>\n");
722 $inlay->append_button ("Grant", sub { 766 $inlay->append_button ("Grant", sub {
723 $inlay->clear; 767 (delete $self->{undo_inlay})->clear;
724 $self->send (grant_undo => channel => $self->{channel}); 768 $self->send (grant_undo => channel => $self->{channel});
725 }); 769 });
726 $inlay->append_button ("Ignore", sub { 770 $inlay->append_button ("Ignore", sub {
727 $inlay->clear; 771 $inlay->clear;
728 $inlay->{ignore} = 1; 772 $inlay->{ignore} = 1;
733} 777}
734 778
735sub inject_new_game { 779sub inject_new_game {
736 my ($self, $msg) = @_; 780 my ($self, $msg) = @_;
737 781
738 $self->{chat}->append_text ("\n<header>ACK from server ($msg->{cid} == $self->{cid})</header>"); 782 if ($msg->{cid} != $self->{cid}) {
783 $self->part;
784 warn "ERROR: challenge id mismatch, PLEASE REPORT, especially the circumstances (many games open? etc..)\n";#d#
785 }
786
787 $self->{chat}->append_text ("\n<header>Game successfully created on server.</header>");
739 delete $self->{cid}; 788 delete $self->{cid};
740} 789}
741 790
742sub draw_challenge { 791sub draw_challenge {
743 my ($self, $id) = @_; 792 my ($self, $id) = @_;
747 my $rules = $info->{rules}; 796 my $rules = $info->{rules};
748 797
749 my $as_black = $info->{black}{name} eq $self->{conn}{name} ? 1 : 0;; 798 my $as_black = $info->{black}{name} eq $self->{conn}{name} ? 1 : 0;;
750 my $opponent = $as_black ? $info->{white} : $info->{black}; 799 my $opponent = $as_black ? $info->{white} : $info->{black};
751 800
752 my ($size, $time, $interval, $count); 801 my ($size, $time, $interval, $count, $type);
753 802
754 if (!$self->{channel}) { 803 if (!$self->{channel}) {
755 $inlay->append_text ("\nNotes: "); 804 $inlay->append_text ("\nNotes: ");
756 $inlay->append_entry (\$info->{notes}, 20, ""); 805 $inlay->append_entry (\$info->{notes}, 20, "");
757 $inlay->append_text ("\nGlobal Offer: "); 806 $inlay->append_text ("\nGlobal Offer: ");
762 } else { 811 } else {
763 $inlay->append_text ("\nNotes: " . util::toxml $info->{notes}); 812 $inlay->append_text ("\nNotes: " . util::toxml $info->{notes});
764 } 813 }
765 814
766 $inlay->append_text ("\nType: "); 815 $inlay->append_text ("\nType: ");
767 $inlay->append_optionmenu ( 816 $type = $inlay->append_optionmenu (
768 \$info->{gametype}, 817 \$info->{gametype},
769 GAMETYPE_DEMONSTRATION , "Demonstration", 818 GAMETYPE_DEMONSTRATION , "Demonstration (not yet)",
770 GAMETYPE_DEMONSTRATION | GAMETYPE_PRIVATE, "Demonstration (P)", 819 GAMETYPE_DEMONSTRATION | GAMETYPE_PRIVATE, "Demonstration (P) (not yet)",
771 GAMETYPE_TEACHING , "Teaching", 820 GAMETYPE_TEACHING , "Teaching (not yet)",
772 GAMETYPE_TEACHING | GAMETYPE_PRIVATE, "Teaching (P)", 821 GAMETYPE_TEACHING | GAMETYPE_PRIVATE, "Teaching (P) (not yet)",
773 GAMETYPE_SIMUL , "Simul (not yet!)", 822 GAMETYPE_SIMUL , "Simul (not yet!)",
774 GAMETYPE_FREE , "Free", 823 GAMETYPE_FREE , "Free",
775 GAMETYPE_RATED , "Rated", 824 GAMETYPE_RATED , "Rated",
776 sub { 825 sub {
777 $size->set_history (2) if $_[0] eq GAMETYPE_RATED; 826 $size->set_history (2) if $_[0] eq GAMETYPE_RATED;
801 RULESET_NEW_ZEALAND, "New Zealand", 850 RULESET_NEW_ZEALAND, "New Zealand",
802 ); 851 );
803 852
804 $inlay->append_text ("\nSize: "); 853 $inlay->append_text ("\nSize: ");
805 $size = $inlay->append_optionmenu ( 854 $size = $inlay->append_optionmenu (
855 \$info->{rules}{size},
806 \$info->{rules}{size}, 9 => 9, 13 => 13, 19 => 19, map +($_, $_), 2..38 856 (9 => 9, 13 => 13, 19 => 19, map +($_, $_), 2..38),
857 sub {
858 $type->set_history (5) # reset to free
859 if $_[0] != 19 && $info->{gametype} == GAMETYPE_RATED;
860 },
807 ); 861 );
808 862
809 if ($self->{channel}) { 863 if ($self->{channel}) {
810 $inlay->append_text ("\nHandicap: "); 864 $inlay->append_text ("\nHandicap: ");
811 $inlay->append_optionmenu (\$info->{rules}{handicap}, map +($_, $_), 0..9); 865 $inlay->append_optionmenu (\$info->{rules}{handicap}, map +($_, $_), 0..9);
912 my $as_black = $info->{black}->{name} eq $self->{conn}{name}; 966 my $as_black = $info->{black}->{name} eq $self->{conn}{name};
913 my $opponent = $as_black ? $info->{white} : $info->{black}; 967 my $opponent = $as_black ? $info->{white} : $info->{black};
914 968
915 my $id = $opponent->{name}; 969 my $id = $opponent->{name};
916 970
971 sound::play 2, "info";
972
917 $self->{challenge}{$id} = $info; 973 $self->{challenge}{$id} = $info;
918 $self->{challenge}{$id}{inlay} = $self->{chat}->new_switchable_inlay ( 974 $self->{challenge}{$id}{inlay} = $self->{chat}->new_switchable_inlay (
919 exists $self->{challenge}{""} 975 exists $self->{challenge}{""}
920 ? "Challenge from $opponent->{name}" 976 ? "Challenge from " . $opponent->as_string
921 : "Challenge to $opponent->{name}", 977 : "Challenge to " . $opponent->as_string,
922 sub { 978 sub {
923 $self->{challenge}{$id}{inlay} = $_[0]; 979 $self->{challenge}{$id}{inlay} = $_[0];
924 $self->draw_challenge ($id); 980 $self->draw_challenge ($id);
925 }, 981 },
926 !exists $self->{challenge}{""} # only open when not offerer 982 !exists $self->{challenge}{""} # only open when not offerer

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines