#!/opt/bin/perl package util; sub format_time($) { my ($time) = @_; $time > 60*60 ? sprintf "%d:%02d:%02d", $time / (60 * 60), $time / 60 % 60, $time % 60 : sprintf "%d:%02d", $time / 60 % 60, $time % 60; } package goclock; use Time::HiRes (); use Gtk2; use Glib::Object::Subclass Gtk2::Label; sub INIT_INSTANCE { my $self = shift; $self->signal_connect (destroy => sub { $_[0]->stop }); $self->{format} = sub { if ($_[0] < 0 || $self->{interval} < 0 || $self->{moves} < 0) { util::format_time $_[0]; } else { my $time = int $_[0]; if ($self->{interval} != 0) { $time = int (($_[0] - 1) % $self->{interval} + 1); } sprintf "%s/%d = %d [s/mv]", util::format_time $time, $self->{moves}, $self->{moves} >= 1 ? $time / $self->{moves} : $self->{interval}; } } } sub FINALIZE_INSTANCE { my $self = shift; $self->stop; } sub refresh { my ($self) = @_; my $timer = $self->{time}; print "TIM: $self->{time} $self->{moves} $self->{interval}\n"; $self->set_text ($self->{format}->($timer)); } sub set_time { my ($self, $start, $time, $moves, $interval) = @_; $self->{interval} = $interval; $self->{time} = $time; $self->{moves} = $moves; $self->refresh; if ($start) { $self->start; } else { $self->stop; } } sub start { my ($self, $interval) = @_; $self->stop; my $timeout; $timeout = sub { $self->{timeout} = add Glib::Timeout 1000, $timeout; $self->{time}--; $self->refresh; 0; }; $self->{time}++; $timeout->(); } sub stop { my ($self) = @_; remove Glib::Source delete $self->{timeout} if $self->{timeout}; } package main; use Glib; use Gtk2 -init; use Gtk2::Helper; use Gtk2::SimpleList; use Gtk2::GoBoard; use Games::Go::SimpleBoard; use IO::Socket::INET; use Net::IGS; use strict; require Data::Dumper; my %games; my @timers; my %alphmap = ( A => 1, B => 2, C => 3, D => 4, E => 5, F => 6, G => 7, H => 8, J => 9, K => 10, L => 11, M => 12, N => 13, O => 14, P => 15, Q => 16, R => 17, S => 18, T => 19, U => 20, V => 21, W => 22, ); sub dump($) { print Data::Dumper::Dumper ([$_[0]]); } sub con { my ($host, $port) = @_; my $socket = new IO::Socket::INET PeerAddr => $host, PeerPort => $port or die "cannot connect to $host:$port: $!"; $socket->autoflush (1); return $socket; } sub spawn_game_view { my ($igs, $gamenr) = @_; my $game = $games{$gamenr}; my $size = $game->{size}; return if not defined $game; my $w = Gtk2::Window->new; $w->set_default_size (600, 700); $w->add (my $v = new Gtk2::VBox); $v->pack_start ((new Gtk2::Label "Game $gamenr: White: $game->{white} [$game->{white_rank}] vs. Black: $game->{black} [$game->{black_rank}]"), 0, 1, 0); $v->pack_start ((new Gtk2::Label "Size: $game->{size} Handicap: $game->{handicap}"), 0, 1, 0); $v->pack_start ((my $clock1 = new goclock), 0, 1, 0); $v->pack_start ((my $clock2 = new goclock), 0, 1, 0); $v->pack_start ((my $msg = new Gtk2::Label), 0, 1, 0); $v->pack_start ((my $mv_lbl = new Gtk2::Label), 0, 1, 0); $v->pack_start ((my $board = new Gtk2::GoBoard size => $size), 1, 1, 0); $msg->set_text ($game->{title}); my $p3 = $igs->set_ev_cb ('game_title' => sub { my ($s, $gnr, $t) = @_; $gnr == $gamenr or return; $msg->set_text ($t); $game->{title} = $t; }); my $pos = $igs->set_ev_cb ('game_over' => sub { my ($self, $gnr, $result) = @_; $gnr == $gamenr or return; $clock1->stop; $clock2->stop; $msg->set_text ($game->{title} . ": Game Over: $result"); }); my $pos2 = $igs->set_ev_cb ('game_move' => sub { my $self = shift; my $gnr = shift; $gnr == $gamenr or return; my $move = shift; my $mnr = $move->{number}; $move->{coords} =~ m/^(.)(\d+)/; my $x = $alphmap{$1} - 1; my $y = $size - $2; my $white_mv = $move->{color} eq 'white'; $game->{moves}->[$mnr] = $move; my $mv = $game->{moves_struct}->[$mnr] = [ $x, $y, ~MARK_CIRCLE, MARK_CIRCLE | MARK_MOVE | ($white_mv ? MARK_W : MARK_B), '' ]; print "MOVE $gamenr | $mnr | $gnr : $x, $y $move->{color} = $move->{white_time} <=> $move->{black_time} | $move->{white_stones} <-> $move->{black_stones} | $game->{byo}\n"; $mv_lbl->set_text ("Move " . ($mnr + 1)); $clock1->set_time ((not $white_mv), $move->{white_time}, $move->{white_stones}, $game->{byo} * 60); $clock2->set_time ($white_mv, $move->{black_time}, $move->{black_stones}, $game->{byo} * 60); if ($move->{white_time} == 0 && $move->{white_stones} == -1 && $move->{black_time} == 0 && $move->{black_stones} == -1) { $clock1->stop (); $clock2->stop (); } my $board_state = new Games::Go::SimpleBoard $size; $board_state->update ($game->{moves_struct}); $board->set_board ($board_state); }); $w->signal_connect (destroy => sub { $igs->unset_ev_cb ('game_over', $pos); $igs->unset_ev_cb ('game_move', $pos2); $igs->unset_ev_cb ('game_title', $p3); $igs->feed_event ('req_unobserve', $gamenr); }); $w->show_all; } sub build_ui { my $igs = shift; my $win = Gtk2::Window->new; $win->set_default_size (600, 800); my $slist = Gtk2::SimpleList->new ( 'N' => 'int', # game number 'White' => 'text', 'W Rank' => 'text', 'Black' => 'text', 'B Rank' => 'text', 'Move' => 'int', 'Size' => 'int', 'Hand' => 'int', 'Komi' => 'double', 'Byo' => 'int', 'F' => 'text', 'R' => 'text', ); my $plist = Gtk2::SimpleList->new ( 'Name' => 'text', 'Rank' => 'text', 'Obs' => 'int', 'Pla' => 'int', 'Idle' => 'text', 'Flags' => 'text', ); $win->add (my $p = Gtk2::VPaned->new); $p->add1 (my $sw = Gtk2::ScrolledWindow->new); $sw->add ($slist); $p->add2 (my $hp = Gtk2::HPaned->new); $hp->add1 (my $sw = Gtk2::ScrolledWindow->new); $sw->add_with_viewport (my $v = Gtk2::VBox->new); $v->pack_start (my $txt = Gtk2::TextView->new, 1, 1, 0); $v->pack_start (my $b1 = Gtk2::Button->new ('sort'), 0, 1, 0); $hp->add2 (my $sw = Gtk2::ScrolledWindow->new); $sw->add ($plist); $b1->signal_connect (clicked => sub { @{$slist->{data}} = map { my %g = %{$games{$_}}; print "$g{number}\n"; [ @g{qw/number white white_rank black black_rank move size handicap komi byo Fflag Rflag/} ] } sort { $a <=> $b } keys %games; }); $slist->signal_connect (row_activated => sub { my ($sl, $path, $column) = @_; my $row_ref = $sl->get_row_data_from_path ($path); spawn_game_view ($igs, $row_ref->[0]); $igs->feed_event ("req_observe", $row_ref->[0]); $igs->feed_event ("req_moves", $row_ref->[0]); }); $p->set_position (400); $hp->set_position (300); $igs->set_ev_cb ('game_list' => sub { my %game = %{$_[1]}; $games{$game{number}} = { %game }; push @{$slist->{data}}, [ @game{qw/number white white_rank black black_rank move size handicap komi byo Fflag Rflag/} ]; }); $igs->set_ev_cb ('player_list' => sub { my %player = %{$_[1]}; push @{$plist->{data}}, [ @player{qw/name rank obs playing idle flags/} ]; # @{$plist->{data}} = sort { $a->[0] cmp $b->[0] } @{$plist->{data}}; }); $win->show_all (); } ################################################################################# ################################################################################# ################################################################################# my $igs = new Net::IGS; $igs->init ("elmex", "lolfe123"); my $sck = con ("igs.joyjoy.net", 6969); $igs->set_writer (sub { $sck->syswrite ($_[0]) }); $igs->set_ev_cb ('recv_file' => sub { print "FILE [ $_[1] ]\n"; }); $igs->sendline ('games'); $igs->sendline ('players'); my $buffer; my $pos = $igs->set_ev_cb ('game_over' => sub { my ($self, $gnr, $result) = @_; delete $games{$gnr}; }); Gtk2::Helper->add_watch (fileno($sck), 'in', sub { my $data; my $r = $sck->sysread (my $data, 4096); if (not $r) { # check $r == 0 or undef # FIXME die "eof from server"; } $igs->feed_data ($data); 1; }); build_ui ($igs); Gtk2->main;