--- kgsueme/kgsueme/game.pl 2004/05/31 02:04:40 1.107 +++ kgsueme/kgsueme/game.pl 2004/06/01 12:46:54 1.114 @@ -2,6 +2,8 @@ use Scalar::Util (); +### GO CLOCK WIDGET ######################################################### + package game::goclock; # Lo and Behold! I admit it! The rounding stuff etc.. in goclock @@ -34,7 +36,7 @@ if ($timesys == TIMESYS_ABSOLUTE) { $self->{format} = sub { - if ($_[0] <= 0) { + if ($_[0] < 0) { "TIMEOUT"; } else { util::format_time $_[0]; @@ -45,7 +47,7 @@ my $low = $interval * $count; $self->{format} = sub { - if ($_[0] <= 0) { + if ($_[0] < 0) { "TIMEOUT"; } elsif ($_[0] > $low) { util::format_time $_[0] - $low; @@ -58,7 +60,7 @@ } elsif ($timesys == TIMESYS_CANADIAN) { $self->{format} = sub { - if ($_[0] <= 0) { + if ($_[0] < 0) { "TIMEOUT"; } elsif (!$self->{moves}) { util::format_time $_[0] - $low; @@ -129,12 +131,18 @@ remove Glib::Source delete $self->{timeout} if $self->{timeout}; } +### USER PANEL ############################################################## + package game::userpanel; +use KGS::Constants; + use Glib::Object::Subclass - Gtk2::HBox, + Gtk2::Frame, properties => [ - Glib::ParamSpec->IV ("colour", "colour", "User Colour", 0, 1, 0, [qw(construct-only writable)]), + Glib::ParamSpec->IV ("colour", "colour", "User Colour", + COLOUR_BLACK, COLOUR_WHITE, COLOUR_BLACK, + [qw(construct-only readable writable)]), ]; sub INIT_INSTANCE { @@ -142,9 +150,9 @@ $self->add (my $vbox = new Gtk2::VBox); - $vbox->add ($self->{name} = new Gtk2::Label $self->{name}); - $vbox->add ($self->{info} = new Gtk2::Label ""); - $vbox->add ($self->{clock} = new game::goclock); Scalar::Util::weaken $self->{clock}; + $vbox->pack_start (($self->{name} = new Gtk2::Label "-"), 1, 1, 0); + $vbox->pack_start (($self->{info} = new Gtk2::Label "-"), 1, 1, 0); + $vbox->pack_start (($self->{clock} = new game::goclock), 1, 1, 0); $vbox->add ($self->{imagebox} = new Gtk2::VBox); @@ -190,6 +198,8 @@ $self->{clock}->set_time ($start, $time, $moves); } +### GAME WINDOW ############################################################# + package game; use Scalar::Util qw(weaken); @@ -228,14 +238,10 @@ $hpane->pack1 (($self->{left} = new Gtk2::VBox), 1, 0); - $self->{boardbox} = new Gtk2::VBox; - $hpane->pack1((my $vbox = new Gtk2::VBox), 1, 1); # board box (aspect/canvas) - #$self->{boardbox}->pack_start((my $frame = new Gtk2::Frame), 0, 1, 0); - # RIGHT PANE $hpane->pack2 ((my $vbox = new Gtk2::VBox), 1, 1); @@ -245,11 +251,11 @@ { $frame->add (my $vbox = new Gtk2::VBox); - $vbox->add ($self->{title} = new Gtk2::Label $title); + $vbox->add ($self->{title} = new Gtk2::Label "-"); $vbox->add (my $hbox = new Gtk2::HBox); - $hbox->pack_start (($self->{board_label} = new Gtk2::Label), 0, 1, 0); + $hbox->pack_start (($self->{board_label} = new Gtk2::Label), 0, 0, 0); $self->{moveadj} = new Gtk2::Adjustment 1, 1, 1, 1, 5, 0; @@ -318,6 +324,47 @@ } } +### JOIN/LEAVE ############################################################## + +sub join { + my ($self) = @_; + return if $self->{joined}; + + $self->SUPER::join; +} + +sub event_join { + my ($self) = @_; + + $self->SUPER::event_join (@_); + $self->init_tree; + $self->event_update_game; +} + +sub event_part { + my ($self) = @_; + + $self->SUPER::event_part; + $self->destroy; +} + +sub event_quit { + my ($self) = @_; + + $self->SUPER::event_quit; + $self->destroy; +} + +### USERS ################################################################### + +sub draw_users { + my ($self, $inlay) = @_; + + for (sort keys %{$self->{users}}) { + $inlay->append_text (" " . $self->{users}{$_}->as_string . ""); + } +} + sub event_update_users { my ($self, $add, $update, $remove) = @_; @@ -343,20 +390,174 @@ } } -sub join { +### GAME INFO ############################################################### + +sub draw_setup { + my ($self, $inlay) = @_; + + return unless $self->{joined}; + + my $rules = $self->{rules}; + + my $text = ""; + + $text .= "\nTeacher: " . (util::toxml $self->{teacher}) . "" + if $self->{teacher}; + + $text .= "\nOwner: " . (util::toxml $self->{owner}->as_string) . "" + if $self->{owner}->is_valid; + + if ($self->is_inprogress) { + $text .= "\nPlayers: " . (util::toxml $self->{white}->as_string) . "" + . " vs. " . (util::toxml $self->{black}->as_string) . ""; + } + $text .= "\nType: " . util::toxml $gametype{$self->type}; + + $text .= "\nRuleset: " . $ruleset{$rules->{ruleset}}; + + $text .= "\nTime: "; + + if ($rules->{timesys} == TIMESYS_NONE) { + $text .= "UNLIMITED"; + } elsif ($rules->{timesys} == TIMESYS_ABSOLUTE) { + $text .= util::format_time $rules->{time}; + $text .= " ABS"; + } elsif ($rules->{timesys} == TIMESYS_BYO_YOMI) { + $text .= util::format_time $rules->{time}; + $text .= sprintf " + %s (%d) BY", util::format_time $rules->{interval}, $rules->{count}; + } elsif ($rules->{timesys} == TIMESYS_CANADIAN) { + $text .= util::format_time $rules->{time}; + $text .= sprintf " + %s/%d CAN", util::format_time $rules->{interval}, $rules->{count}; + } + + $text .= "\nFlags:"; + $text .= " private" if $self->is_private; + $text .= " started" if $self->is_inprogress; + $text .= " adjourned" if $self->is_adjourned; + $text .= " scored" if $self->is_scored; + $text .= " saved" if $self->is_saved; + + if ($self->is_inprogress) { + $text .= "\nHandicap: " . $self->{handicap}; + $text .= "\nKomi: " . $self->{komi}; + $text .= "\nSize: " . $self->size_string; + } + + if ($self->is_scored) { + $text .= "\nResult: " . $self->score_string; + } + + $inlay->append_text ("$text"); + +} + +sub event_update_game { my ($self) = @_; - return if $self->{joined}; - $self->SUPER::join; + $self->SUPER::event_update_game; + + return unless $self->{joined}; + + $self->{colour} = $self->player_colour ($self->{conn}{name}); + + $self->{user}[COLOUR_BLACK] = $self->{black}; + $self->{user}[COLOUR_WHITE] = $self->{white}; + + # show board + if ($self->is_inprogress) { + if (!$self->{board}) { + $self->{left}->add ($self->{board} = new Gtk2::GoBoard size => $self->{size}); + $self->{board}->signal_connect (button_release => sub { + return unless $self->{cur_board}; + if ($_[1] == 1) { + $self->{board_click}->($_[2], $_[3]) if $self->{board_click}; + } + }); + $self->{board}->show_all; + } + if (my $ch = delete $self->{challenge}) { + $_->{inlay}->destroy for values %$ch; + } + $self->update_cursor; + } + + my $title = defined $self->{channel} + ? $self->owner->as_string . " " . $self->opponent_string + : "Game Window"; + $self->set_title ("KGS Game $title"); + $self->{title}->set_text ($title); # title gets redrawn wrongly + + $self->{rules_inlay}->refresh; + + if (exists $self->{teacher}) { + $self->{teacher_inlay} ||= $self->{chat}->new_inlay; + $self->{teacher_inlay}->clear; + $self->{teacher_inlay}->append_text ("\n
Teacher:
" + . (util::toxml $self->{teacher}) . ""); + } elsif ($self->{teacher_inlay}) { + (delete $self->{teacher_inlay})->clear; + } + + $self->update_cursor; +} + +sub event_update_rules { + my ($self, $rules) = @_; + + $self->{rules} = $rules; + + if ($self->{user}) { + # todo. gets drawn wrongly + + $self->{userpanel}[$_]->configure ($self->{app}, $self->{user}[$_], $rules) + for COLOUR_BLACK, COLOUR_WHITE; + } + + sound::play 3, "gamestart"; + $self->{rules_inlay}->refresh; +} + +### BOARD DISPLAY ########################################################### + +sub update_timers { + my ($self, $timers) = @_; + + my $running = $self->{showmove} == @{$self->{path}} && !$self->{teacher}; + + for my $colour (COLOUR_BLACK, COLOUR_WHITE) { + my $t = $timers->[$colour]; + $self->{userpanel}[$colour]->set_timer ( + $running && $colour == $self->{whosemove} && $t->[0], + $t->[1] || $self->{rules}{time} + + ($self->{rules}{timesys} == TIMESYS_BYO_YOMI + && $self->{rules}{interval} * $self->{rules}{count}), + $t->[2]); + } +} + +sub inject_set_gametime { + my ($self, $msg) = @_; + + $self->{timers} = [ + [$msg->{NOW}, $msg->{black_time}, $msg->{black_moves}], + [$msg->{NOW}, $msg->{white_time}, $msg->{white_moves}], + ]; + + $self->update_timers ($self->{timers}) + if $self->{showmove} == @{$self->{path}}; } sub update_cursor { my ($self) = @_; + return unless $self->{cur_board}; + my $running = $self->{showmove} == @{$self->{path}} && $self->is_active; delete $self->{board_click}; + $self->{colour} = COLOUR_BLACK;#d# + $self->{whosemove} = COLOUR_BLACK;#d# if ($self->{teacher} eq $self->{app}{conn}) { #TODO# # teaching mode not implemented $self->{button_pass}->set (label => "Pass", visible => 1, sensitive => 1); @@ -380,7 +581,6 @@ $self->{board_click} = sub { if ($_[0] == 255) { $self->{button_pass}->sensitive (0); - warn sprintf "SEND DONE @{$self->{done}} %x\n", $self->{doneid};#d# $self->done; } else { $self->send (mark_dead => @@ -396,12 +596,16 @@ # normal move $self->{button_pass}->set (label => "Pass", visible => 1, sensitive => 1); $self->{board}->set (cursor => sub { - # if is_valid_move oder so#TODO# - $_[0] & (MARK_B | MARK_W) - ? $_[0] - : $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B); + $self->{cur_board} + && $self->{cur_board}->is_valid_move ($self->{colour}, $_[1], $_[2], + $self->{rules}{ruleset} == RULESET_NEW_ZEALAND) + ? $_[0] | MARK_GRAYED | ($self->{colour} == COLOUR_WHITE ? MARK_W : MARK_B) + : $_[0]; }); $self->{board_click} = sub { + return unless + $self->{cur_board}->is_valid_move ($self->{colour}, $_[1], $_[2], + $self->{rules}{ruleset} == RULESET_NEW_ZEALAND); $self->send (game_move => channel => $self->{channel}, x => $_[0], y => $_[1]); $self->{board}->set (cursor => undef); delete $self->{board_click}; @@ -420,33 +624,10 @@ } } -sub update_timers { - my ($self, $timers) = @_; - - my $running = $self->{showmove} == @{$self->{path}} && !$self->{teacher}; - - for my $colour (COLOUR_BLACK, COLOUR_WHITE) { - my $t = $timers->[$colour]; - $self->{userpanel}[$colour]->set_timer ( - $running && $colour == $self->{whosemove} && $t->[0], - $t->[1] || $self->{rules}{time} - + ($self->{rules}{timesys} == TIMESYS_BYO_YOMI - && $self->{rules}{interval} * $self->{rules}{count}), - $t->[2] || $self->{rules}{count}); - } -} - sub update_board { my ($self) = @_; - return unless $self->{path}; - $self->{board_label}->set_text ("Move " . ($self->{showmove} - 1)); - - $self->{cur_board} = new KGS::Game::Board $self->{size}; - $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); - - $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) - for COLOUR_WHITE, COLOUR_BLACK; + return unless $self->{path}; if ($self->{rules}{ruleset} == RULESET_JAPANESE) { if ($self->{curnode}{move} == 0) { @@ -459,12 +640,22 @@ if ($self->{curnode}{move} < $self->{handicap}) { $self->{whosemove} = COLOUR_BLACK; } elsif ($self->{curnode}{move} == $self->{handicap}) { - $self->{whosemove} = COLOUR_WHITE; + $self->{whosemove} = $self->{handicap} ? COLOUR_WHITE : COLOUR_BLACK; } else { $self->{whosemove} = 1 - $self->{cur_board}{last}; } } + $self->{board_label}->set_text ("Move " . ($self->{showmove} - 1)); + + $self->{cur_board} = new KGS::Game::Board $self->{size}; + $self->{cur_board}->interpret_path ([@{$self->{path}}[0 .. $self->{showmove} - 1]]); + + $self->update_cursor; + + $self->{userpanel}[$_]->set_captures ($self->{cur_board}{captures}[$_]) + for COLOUR_WHITE, COLOUR_BLACK; + my $start_time = $self->{rules}{time}; if ($self->{showmove} == @{$self->{path}}) { @@ -493,8 +684,6 @@ } elsif ($self->{score_inlay}) { (delete $self->{score_inlay})->clear; } - - $self->update_cursor; } sub event_update_tree { @@ -557,140 +746,13 @@ $self->{chat}->append_text ($text); } -sub event_join { - my ($self) = @_; - - $self->SUPER::event_join (@_); - $self->init_tree; - $self->event_update_game; -} - -sub event_part { - my ($self) = @_; - - $self->SUPER::event_part; - $self->destroy; -} - sub event_move { my ($self, $pass) = @_; sound::play 1, $pass ? "pass" : "move"; } -sub event_update_game { - my ($self) = @_; - - $self->SUPER::event_update_game; - - return unless $self->{joined}; - - $self->{colour} = $self->player_colour ($self->{conn}{name}); - - my $title = defined $self->{channel} - ? $self->owner->as_string . " " . $self->opponent_string - : "Game Window"; - $self->set_title ("KGS Game $title"); - $self->{title}->set_text ($title); - - $self->{user}[COLOUR_BLACK] = $self->{black}; - $self->{user}[COLOUR_WHITE] = $self->{white}; - - # show board - if ($self->is_inprogress) { - if (!$self->{boardbox}->parent) { - $self->{boardbox}->add ($self->{board} = new Gtk2::GoBoard size => $self->{size}); - $self->{left}->add ($self->{boardbox}); - $self->{board}->signal_connect (button_release => sub { - if ($_[1] == 1) { - $self->{board_click}->($_[2], $_[3]) if $self->{board_click}; - } - }); - } - if (my $ch = delete $self->{challenge}) { - $_->{inlay}->destroy for values %$ch; - } - $self->update_cursor; - } - - $self->{left}->show_all; - - $self->{rules_inlay}->refresh; - -} - -sub draw_setup { - my ($self, $inlay) = @_; - - return unless $self->{joined}; - - my $rules = $self->{rules}; - - my $text = ""; - - $text .= "\nTeacher: " . (util::toxml $self->{teacher}) . "" - if $self->{teacher}; - - $text .= "\nOwner: " . (util::toxml $self->{owner}->as_string) . "" - if $self->{owner}->is_valid; - - if ($self->is_inprogress) { - $text .= "\nPlayers: " . (util::toxml $self->{white}->as_string) . "" - . " vs. " . (util::toxml $self->{black}->as_string) . ""; - } - $text .= "\nType: " . util::toxml $gametype{$self->type}; - - $text .= "\nRuleset: " . $ruleset{$rules->{ruleset}}; - - $text .= "\nTime: "; - - if ($rules->{timesys} == TIMESYS_NONE) { - $text .= "UNLIMITED"; - } elsif ($rules->{timesys} == TIMESYS_ABSOLUTE) { - $text .= util::format_time $rules->{time}; - $text .= " ABS"; - } elsif ($rules->{timesys} == TIMESYS_BYO_YOMI) { - $text .= util::format_time $rules->{time}; - $text .= sprintf " + %s (%d) BY", util::format_time $rules->{interval}, $rules->{count}; - } elsif ($rules->{timesys} == TIMESYS_CANADIAN) { - $text .= util::format_time $rules->{time}; - $text .= sprintf " + %s/%d CAN", util::format_time $rules->{interval}, $rules->{count}; - } - - $text .= "\nFlags:"; - $text .= " private" if $self->is_private; - $text .= " started" if $self->is_inprogress; - $text .= " adjourned" if $self->is_adjourned; - $text .= " scored" if $self->is_scored; - $text .= " saved" if $self->is_saved; - - if ($self->is_inprogress) { - $text .= "\nHandicap: " . $self->{handicap}; - $text .= "\nKomi: " . $self->{komi}; - $text .= "\nSize: " . $self->size_string; - } - - if ($self->is_scored) { - $text .= "\nResult: " . $self->score_string; - } - - $inlay->append_text ("$text"); - -} - -sub event_update_rules { - my ($self, $rules) = @_; - - $self->{rules} = $rules; - - if ($self->{user}) { - $self->{userpanel}[$_]->configure ($self->{app}, $self->{user}[$_], $rules) - for COLOUR_BLACK, COLOUR_WHITE; - } - - sound::play 3, "gamestart"; - $self->{rules_inlay}->refresh; -} +### GAMEPLAY EVENTS ######################################################### sub event_resign_game { my ($self, $player) = @_; @@ -699,33 +761,55 @@ $self->{chat}->append_text ("\n
Resign
" . "\n" . (util::toxml $self->{user}[$msg->{player}]->as_string) - . " resigned.
"); + . " resigned." + . "\n" + . (util::toxml $self->{user}[1 - $msg->{player}]->as_string) + . " wins the game." + . ""); } -sub event_out_of_time { +sub event_time_win { my ($self, $player) = @_; sound::play 3, "timewin"; $self->{chat}->append_text ("\n
Out of Time
" . "\n" + . (util::toxml $self->{user}[1 - $msg->{player}]->as_string) + . " ran out of time and lost." + . "\n" . (util::toxml $self->{user}[$msg->{player}]->as_string) - . " ran out of time and lost.
"); + . " wins the game." + . ""); } -sub event_done { +sub event_owner_left { + my ($self) = @_; + + sound::play 2, "info"; + $self->{chat}->append_text ("\n
Owner left
" + . "\nThe owner of this game left.
"); +} + +sub event_teacher_left { my ($self) = @_; - warn sprintf "EVENT DONE @{$self->{done}} %x\n", $self->{doneid};#d# + sound::play 2, "info"; + $self->{chat}->append_text ("\n
Teacher left
" + . "\nThe teacher left the game.
"); +} + +sub event_done { + my ($self) = @_; if ($self->{done}[1 - $self->{colour}] && !$self->{done}[$self->{colour}]) { - sound::play 2, "ring" unless $inlay->{count}; + sound::play 2, "info" unless $inlay->{count}; $self->{chat}->append_text ("\n
Press Done
" . "\nYour opponent pressed done. Now it's up to you."); } if ($self->{doneid} & 0x80000000) { - sound::play 2, "warning" unless $inlay->{count}; + sound::play 2, "info" unless $inlay->{count}; $self->{chat}->append_text ("\n
Press Done Again
" - . "\nYour opponent changed the board.."); + . "\nThe board has changed."); } $self->{button_pass}->sensitive (!$self->{done}[$self->{colour}]); @@ -743,18 +827,6 @@ ); } -sub inject_set_gametime { - my ($self, $msg) = @_; - - $self->{timers} = [ - [$msg->{NOW}, $msg->{black_time}, $msg->{black_moves}], - [$msg->{NOW}, $msg->{white_time}, $msg->{white_moves}], - ]; - - $self->update_timers ($self->{timers}) - if $self->{showmove} == @{$self->{path}}; -} - sub inject_req_undo { my ($self, $msg) = @_; @@ -791,6 +863,8 @@ delete $self->{cid}; } +### CHALLENGE HANDLING ###################################################### + sub draw_challenge { my ($self, $id) = @_; @@ -955,12 +1029,27 @@ } } -sub draw_users { - my ($self, $inlay) = @_; +sub new_game_challenge { + my ($self) = @_; - for (sort keys %{$self->{users}}) { - $inlay->append_text (" " . $self->{users}{$_}->as_string . ""); - } + my $d = $self->{app}{defaults}; + + $self->{challenge}{""} = { + gametype => $d->{gametype}, + flags => 0, + notes => $d->{stones}, + rules => { + ruleset => $d->{ruleset}, + size => $d->{size}, + timesys => $d->{timesys}, + time => $d->{time}, + interval => $d->{timesys} == TIMESYS_BYO_YOMI ? $d->{byo_time} : $d->{can_time}, + count => $d->{timesys} == TIMESYS_BYO_YOMI ? $d->{byo_periods} : $d->{can_stones}, + }, + + inlay => $self->{chat}->new_inlay, + }; + $self->draw_challenge (""); } sub event_challenge { @@ -971,11 +1060,13 @@ my $id = $opponent->{name}; + sound::play 2, "info"; + $self->{challenge}{$id} = $info; $self->{challenge}{$id}{inlay} = $self->{chat}->new_switchable_inlay ( exists $self->{challenge}{""} - ? "Challenge from $opponent->{name}" - : "Challenge to $opponent->{name}", + ? "Challenge from " . $opponent->as_string + : "Challenge to " . $opponent->as_string, sub { $self->{challenge}{$id}{inlay} = $_[0]; $self->draw_challenge ($id);