#!/opt/bin/perl # lots of things have been hardcoded. search for #d# to find the places require 5.008; use KGS::Protocol; use KGS::Listener::Debug; use IO::Socket::INET; use Event; use Getopt::Long; use Carp; our $VERSION = '0.0'; # be more confident.... $SIG{QUIT} = sub { Carp::confess "SIGQUIT" }; my $conn = new KGS::Protocol; my $kgs; my $igs; my $verbose = 1; my $user; my $pass; $Event::DIED = sub { Event::verbose_exception_handler (@_); Event::unloop_all; }; sub rank($) { return "NR" if !$_[0]->is_ranked || !$_[0]->rank; return $_[0]->rank . ($_[0]->is_reliable ? "*" : ""); } sub format_user($) { my $format = sprintf "%s|%s|%s", $_[0]{name}, $_[0]->flags_string, $_[0]->rank_string; $format =~ y/ //d; $format; } sub coord($$) { qw(A B C D E F G H J K L M N O P Q R S T U V W X Y Z)[$_[0]] . $_[1]; } ############################################################################# package kgs; use base KGS::Listener; sub new { my $class = shift; my $self = bless { @_ }, $class; print STDERR "$0 version $VERSION connecting...\n" if $verbose; my $sock = new IO::Socket::INET PeerHost => KGS::Protocol::KGSHOST, PeerPort => KGS::Protocol::KGSPORT or die "connect: $!"; $sock->blocking (1); $conn->handshake ($sock); $self->listen ($conn, "any"); # Listener for kgs data $self->{w} = Event->io (fd => $sock, poll => 'r', cb => sub { my $len = sysread $sock, my $buf, 16384; if ($len) { $conn->feed_data ($buf); } elsif (defined $len || (!$!{EINTR} and !$!{EAGAIN})) { print STDERR "disconnected\n" if $verbose; Event::unloop; } }); $conn->login ("kgs-igs $VERSION", $self->{user}, delete $self->{password}); $self; } sub inject_login { my ($self, $msg) = @_; print STDERR "login: $msg->{message}\n" if $verbose >= 2; if ($msg->{success}) { $igs->login_ok ($msg->{message}); } else { $igs->login_failed ($msg->{message}); } } sub inject_msg_room { my ($self, $msg) = @_; #use PApp::Util; warn PApp::Util::dumpval $msg;#d# $igs->send ("21 !$msg->{name}!: $msg->{message}"); } sub inject_any { my ($self, $msg) = @_; if ($verbose >= 2) { print STDERR "DEBUG: $msg->{type}#$msg->{channel}"; for (sort keys %$msg) { print STDERR" $_<$msg->{$_}>"; } print STDERR "\n"; } } sub inject_upd_rooms { my ($self, $msg) = @_; for (@{$msg->{rooms}}) { # $gtp->send ("kgs-room-update $_->{channel} $_->{name}"); } } sub inject_msg_chat { my ($self, $msg) = @_; return unless (lc $self->{conn}{name}) eq (lc $msg->{name2}); # $gtp->send ("kgs-user-chat $msg->{name} $msg->{message}"); } sub inject_new_game { my ($self, $msg) = @_; $::lastnew = $msg->{channel};#d# # $gtp->send ("kgs-game-new $msg->{id} $msg->{channel}"); } ############################################################################# package room; use base KGS::Listener::Room; sub new { my $class = shift; my $self = $class->SUPER::new (conn => $conn, @_); $self->listen ($self->{conn}); $self->join; $self; } sub event_join { my $self = shift; $self->SUPER::join (@_); $self->{timer} = Event->timer (after => 0, interval => 60, cb => sub { $self->req_games; }); } sub event_update_games { my ($self, $add, $upd, $del) = @_; # for (@$add, @$upd) { # $gtp->send (sprintf "kgs-game-update %d %d %s %s %s %s %d %d %f %d %d %d %d %s", # $self->{channel}, $_->{channel}, # $_->type_char, # ::format_user $_->{user1}, # ::format_user $_->{user2}, # ::format_user $_->{user3}, # $_->size, # $_->{handicap}, # $_->{komi}, # $_->moves, # $_->{flags}, # $_->{observers}, # $_->{saved}, # $_->{notes}, # ); # } # # for (@$del) { # $gtp->send ("kgs-game-delete $self->{channel} $_->{channel}"); # } } sub event_update_users { my ($self, $add, $upd, $del) = @_; # for (@$add, @$upd) { # $gtp->send (sprintf "kgs-user-update %s", ::format_user $_); # } # # for (@$del) { # $gtp->send (sprintf "kgs-user-remove %s", ::format_user $_); # } } sub DESTROY { my $self = shift; $self->{timer}->cancel if $self->{timer}; $self->SUPER::DESTROY; } ############################################################################# package game; use Gtk2::GoBoard::Constants; use base KGS::Listener::Game; sub new { my $class = shift; my $self = $class->SUPER::new (conn => $conn, @_); $self->listen ($self->{conn}); $self->join; $self; } sub event_update_users { return; my ($self, $add, $upd, $del) = @_; for (@$add, @$upd) { $gtp->send (sprintf "kgs-user-update %s", ::format_user $_); } for (@$del) { $gtp->send (sprintf "kgs-user-remove %s", ::format_user $_); } } sub inject_resign_game { my ($self, $msg) = @_; $gtp->set_gid ($self->{channel}); $gtp->send ("play " . (qw(b w))[$msg->{player}] . " resign"); } sub inject_final_result { my ($self, $msg) = @_; $gtp->send (sprintf "kgs-game-score %f %f %f %f %f %f", $_->{whitescore}{territory}, $_->{whitescore}{captures}, $_->{whitescore}{komi}, $_->{blackscore}{territory}, $_->{blackscore}{captures}, $_->{blackscore}{komi}); } sub inject_set_teacher { my ($self, $msg) = @_; } sub event_update_game { my ($self) = @_; $gtp->set_gid ($self->{channel}); # timesettings etc. } sub event_update_tree { my ($self) = @_; $gtp->set_gid ($self->{channel}); my $path = $self->get_path; my $prev = $self->{prevpath}; $self->{prevpath} = [ @$path ]; if (@$prev > 1 and @$path > @$prev and (join ":", @$prev) eq (join ":", @$path[0 .. $#$prev])) { splice @$path, @prev, $#path, (); } else { $gtp->send ("boardsize $path->[0]{rules}{size}"); $gtp->send ("komi $path->[0]{rules}{komi}"); $gtp->send ("clear_board"); my $setup = shift @$path; my $handi; while (my ($k, $v) = each %$setup) { if ($k =~ /^(\d+),(\d+)$/) { $handi .= " " . ::coord $1, $2; } } $gtp->send ("set_free_handicap$handi"); } for (@$path) { while (my ($k, $v) = each %$_) { if ($k =~ /^(\d+),(\d+)$/) { if ($v->[0] & MARK_MOVE) { if ($v->[0] & MARK_B) { $gtp->send ("play b ". ::coord $1, $2); } else { $gtp->send ("play w ". ::coord $1, $2); } } } } } } sub DESTROY { my $self = shift; $self->SUPER::DESTROY; } ############################################################################# package igs; use Gtk2::GoBoard::Constants; use KGS::Constants; use Fcntl; sub new { my $class = shift; my $self = bless { @_ }, $class; my $buf; $self->{w} = Event->io (fd => $self->{fh}, poll => 'r', cb => sub { 0 < sysread $self->{fh}, $buf, 4096, length $buf or Event::unloop -1; #$buf =~ y/\x00-\x09\x0b-\x0c\x0e-\x1f\x80-\xff//d; while ($buf =~ s/^([^\015\012]*)\015?\012//) { $self->{feed}->($1); } }); syswrite $self->{fh}, "##########################################################\r\n" for 1..1; sleep 1; # required for amny clients :/ syswrite $self->{fh}, "Login: "; $self->{feed} = sub { my $user = $_[0]; print "login<$user>\n"; #$self->send ("\377\373\1"); $self->send ("1 1"); $self->{feed} = sub { warn "pass <$_[0]>\n";#d# $kgs = new kgs user => $user, password => $_[0] eq "guest" ? "" : $_[0]; $self->{feed} = sub { }; } }; $self; } sub send { my ($self, $cmd) = @_; print "SEND<$cmd>\n";#d# syswrite $self->{fh}, "$cmd\015\012"; } sub feed { my ($self, $line) = @_; warn "GOT<$line>\n";#d# if ($line =~ /^ga/) { # gamelist $self->send ("7 [##] white name [ rk ] black name [ rk ] (Move size H Komi BY FR) (###)"); if ($self->{room}) { for (values %{$self->{room}{games} || {}}) { next if $_->{size} <= 0 || $_->{moves} <= 0 || $_->{handicap} < 0 || $_->is_scored; $self->send (sprintf "7 [%d] %11s [%4s] vs. %11s [%4s] (%3d %4d %2d %3.1f %2d %s%s) (%3d)", $_->{channel}, $_->{user2}{name}, ::rank $_->{user2}, $_->{user1}{name}, ::rank $_->{user1}, $_->{moves}, $_->{size}, $_->{handicap}, $_->{komi}, 0, "F", "I", $_->{observers}); } } } elsif ($line =~ /^chan/) { # channels $self->send ("9 #13 Title: English Game Room -- Open"); } elsif ($line =~ /^(y\S*|;)\s*/g) { # yell my $channel = $self->{channel}; $channel = $1 if $line =~ /\\(\d+)\s*/gc; if ($self->{channel} != $channel) { $self->{channel} = $channel; (delete $self->{room})->part if $self->{room}; if ($channel > 0) { $self->{room} = new room channel => $channel; $self->send ("32 Changing into channel $channel."); $self->send ("32 Welcome to cyberspace.");#maybe title##d# } } if ($channel > 0) { # msg_chat to room } } else { $self->send ("5 Unknown command."); } $self->send ("1 5"); } sub login_ok { my ($self, $msg) = @_; $self->{feed} = sub { $self->feed ($_[0]) }; #$self->send ("39 IGS entry on 05 - 26 - 2004"); #$self->send ("9 File"); # motd $self->send ("9 File"); $self->send (" $msg"); $self->send ("9 File"); $self->send ("1 5"); $self->{room} = new room channel => 13; # auto-join english room #d# } sub login_failed { my ($self, $msg) = @_; $self->send ("5 $msg"); Event::unloop -1; } package main; sub usage { print STDERR < sub { $verbose++ }, "q" => sub { $verbose-- }, "h" => sub { usage(0) }, ) or die usage(1); my $port = $ARGV[0] || 6969; my $socket = new IO::Socket::INET LocalPort => $port, Listen => 1, ReuseAddr => 1, or die "cannot create listening socket on port $port: $!"; print "Listening on 127.0.0.1:$port, please connect to it using your igs client.\n"; while (my $fh = $socket->accept) { if (fork == 0) { $igs = new igs fh => $fh; Event::loop; exit 0; } } 1;