Beginning of implementation of a ctypes-based interface to libboard, which is a much cleaner set of Go routines than I hacked together originally. Including a copy of gnugo 3.8 so we can build a dynamic version of libboard.
This commit is contained in:
549
gnugo/interface/gtp_examples/2ptkgo.pl
Normal file
549
gnugo/interface/gtp_examples/2ptkgo.pl
Normal file
@ -0,0 +1,549 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
|
||||
use Tk;
|
||||
use ttgo;
|
||||
use FileHandle;
|
||||
use IPC::Open2;
|
||||
|
||||
# use strict;
|
||||
|
||||
$| = 1;
|
||||
|
||||
my $boardsize = 11;
|
||||
|
||||
my $autoplay = 1;
|
||||
|
||||
my @program = ();
|
||||
my @cur_id = ();
|
||||
|
||||
|
||||
my $Aprg_in = new FileHandle;
|
||||
my $Aprg_out = new FileHandle;
|
||||
$program[0] = 'gnugo --mode gtp --quiet';
|
||||
$cur_id[0] = 1; # starting id
|
||||
|
||||
|
||||
my $Bprg_in = new FileHandle;
|
||||
my $Bprg_out = new FileHandle;
|
||||
$program[1] = 'gnugo --mode gtp --score aftermath --capture-all-dead --chinese-rules --quiet';
|
||||
$cur_id[1] = 1; # starting id
|
||||
|
||||
|
||||
my $state = 'start'; # first initialization state
|
||||
|
||||
open2($Aprg_out, $Aprg_in, $program[0]);
|
||||
$flags =
|
||||
fcntl( $Aprg_out, F_GETFL, 0)
|
||||
or die "Error with fcntl\n";
|
||||
$flags =
|
||||
fcntl( $Aprg_out, F_SETFL, $flags | O_NOBLOCK)
|
||||
or die "Error with fcntl\n";
|
||||
|
||||
|
||||
open2($Bprg_out, $Bprg_in, $program[1]);
|
||||
$flags =
|
||||
fcntl( $Bprg_out, F_GETFL, 0)
|
||||
or die "Error with fcntl\n";
|
||||
$flags =
|
||||
fcntl( $Bprg_out, F_SETFL, $flags | O_NOBLOCK)
|
||||
or die "Error with fcntl\n";
|
||||
|
||||
|
||||
|
||||
my $flags = 0;
|
||||
my $consecutive_passes = 0;
|
||||
my $ctm = 'B'; # who's turn to move?
|
||||
my $cc = 'W'; # computers color
|
||||
|
||||
|
||||
my $msgstr = '';
|
||||
|
||||
|
||||
# This handles up to 25 size boards
|
||||
# =================================
|
||||
my @letter = 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 );
|
||||
|
||||
|
||||
# color definitions
|
||||
# =================
|
||||
my %cstr = ( 'b' => '#604040', 'B' => '#604040',
|
||||
'w' => '#ffffff', 'W' => '#ffffff'
|
||||
);
|
||||
|
||||
my $bkc = '#eeeeee';
|
||||
|
||||
|
||||
|
||||
|
||||
# get command line arguments start with defaults
|
||||
# ==============================================
|
||||
my $sqwh = 26;
|
||||
my $sqwh2 = 12; # 1/2 of sqwh
|
||||
|
||||
|
||||
|
||||
|
||||
my %toix = ();
|
||||
foreach my $ix (0 .. $#letter) {
|
||||
$toix{ $letter[$ix] } = $ix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# initialize graphics and such
|
||||
# ----------------------------
|
||||
my $top = MainWindow->new;
|
||||
$top->title("ptkgo.pl");
|
||||
$top->resizable(0,0);
|
||||
my $geox = ($boardsize-1) * $sqwh + 80;
|
||||
my $geoy = ($boardsize-1) * $sqwh + 140;
|
||||
|
||||
$top->geometry( $geox . 'x' . $geoy );
|
||||
$top->configure( background => $bkc );
|
||||
|
||||
|
||||
|
||||
# build the background go board
|
||||
|
||||
my $backing = $top->Canvas(
|
||||
-width => $sqwh * $boardsize + 80,
|
||||
-height => $sqwh * $boardsize + 80,
|
||||
-background => $bkc
|
||||
)->place(
|
||||
-x => 0,
|
||||
-y => 0,
|
||||
);
|
||||
|
||||
|
||||
foreach my $x ( 0 .. $boardsize-1 ) {
|
||||
|
||||
$backing->createText( 40 + $x * $sqwh,
|
||||
25,
|
||||
-text => $letter[$x],
|
||||
-fill => 'black',
|
||||
-justify => 'center',
|
||||
-font => '-b&h-*-bold-r-*-*-11-*-*-*-*-*-*-*'
|
||||
);
|
||||
|
||||
$backing->createText( 40 + $x * $sqwh,
|
||||
($boardsize-1)*$sqwh + 55,
|
||||
-text => $letter[$x],
|
||||
-fill => 'black',
|
||||
-justify => 'center',
|
||||
-font => '-b&h-*-bold-r-*-*-11-*-*-*-*-*-*-*'
|
||||
);
|
||||
|
||||
|
||||
$backing->createLine( $x*$sqwh + 40,
|
||||
40,
|
||||
$x*$sqwh+40,
|
||||
($boardsize-1)*$sqwh + 40,
|
||||
-fill => 'black',
|
||||
-width => 1 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
foreach my $y ( 0 .. $boardsize-1 ) {
|
||||
|
||||
$backing->createText( 25,
|
||||
$y * $sqwh + 40,
|
||||
-text => $boardsize - $y,
|
||||
-fill => 'black',
|
||||
-justify => 'center',
|
||||
-font => '-b&h-*-bold-r-*-*-11-*-*-*-*-*-*-*'
|
||||
);
|
||||
|
||||
$backing->createText( ($boardsize-1) * $sqwh + 55,
|
||||
$y * $sqwh + 40,
|
||||
-text => $boardsize - $y,
|
||||
-fill => 'black',
|
||||
-justify => 'center',
|
||||
-font => '-b&h-*-bold-r-*-*-11-*-*-*-*-*-*-*'
|
||||
);
|
||||
|
||||
|
||||
$backing->createLine( 40,
|
||||
$y*$sqwh+40,
|
||||
($boardsize-1)*$sqwh+40,
|
||||
$y*$sqwh + 40,
|
||||
-fill => 'black',
|
||||
-width => 1 );
|
||||
}
|
||||
|
||||
|
||||
ttNewGame($boardsize);
|
||||
ttShowBoard();
|
||||
|
||||
|
||||
|
||||
|
||||
# pass button
|
||||
# -----------
|
||||
my $pass = $top->Button(
|
||||
-text => 'Pass',
|
||||
-command => sub { },
|
||||
-width => 2,
|
||||
-height => 1,
|
||||
-font => '5x7',
|
||||
-borderwidth => 1,
|
||||
-highlightcolor => 'black',
|
||||
-highlightthickness => 1,
|
||||
-highlightbackground => 'black',
|
||||
-relief => 'flat'
|
||||
)->place(
|
||||
-x => 40 + 0 * 40,
|
||||
-y => ($boardsize + 2) * $sqwh,
|
||||
);
|
||||
|
||||
|
||||
|
||||
# undo button
|
||||
# -----------
|
||||
my $undo = $top->Button(
|
||||
-text => 'Undo',
|
||||
-command => sub { },
|
||||
-width => 2,
|
||||
-height => 1,
|
||||
-font => '5x7',
|
||||
-borderwidth => 1,
|
||||
-highlightcolor => 'black',
|
||||
-highlightthickness => 1,
|
||||
-highlightbackground => 'black',
|
||||
-relief => 'flat'
|
||||
)->place(
|
||||
-x => 40 + 1 * 40,
|
||||
-y => ($boardsize + 2) * $sqwh,
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
$top->bind( "<Button-1>", [ \&drop_stone, Ev('x'), Ev('y') ] );
|
||||
|
||||
|
||||
$top->fileevent( $Aprg_out, 'readable', [ \&getmessage, 0] );
|
||||
$top->fileevent( $Bprg_out, 'readable', [ \&getmessage, 1] );
|
||||
|
||||
|
||||
$state = 'start'; # first initialization state
|
||||
control();
|
||||
|
||||
|
||||
MainLoop();
|
||||
|
||||
|
||||
|
||||
|
||||
my $tmpstr;
|
||||
|
||||
sub getmessage
|
||||
{
|
||||
my ($pi) = @_;
|
||||
|
||||
|
||||
if ($pi == 0) {
|
||||
$tmpstr = <$Aprg_out>;
|
||||
} else {
|
||||
$tmpstr = <$Bprg_out>;
|
||||
}
|
||||
|
||||
if (defined $tmpstr) {
|
||||
chomp($tmpstr);
|
||||
|
||||
if ($tmpstr eq '') { # eat the line, update id
|
||||
$cur_id[$pi] ++;
|
||||
control( $msgstr );
|
||||
} else {
|
||||
$msgstr = $tmpstr;
|
||||
print "Came up with $msgstr\n";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sub xputstone
|
||||
{
|
||||
my ($color, $x, $y) = @_;
|
||||
|
||||
|
||||
my $xx = $x * $sqwh + 40;
|
||||
my $yy = $y * $sqwh + 40;
|
||||
|
||||
$backing->createOval( $xx-$sqwh2, $yy-$sqwh2,
|
||||
$xx+$sqwh2, $yy+$sqwh2,
|
||||
-tags => $x . '_' . $y,
|
||||
-outline => 'black',
|
||||
-fill => $cstr{$color} );
|
||||
}
|
||||
|
||||
|
||||
# This routine clears all empty squares, it does
|
||||
# not actually draw board
|
||||
sub xfixboard
|
||||
{
|
||||
my @vis = ttGetBoard();
|
||||
my $st;
|
||||
|
||||
foreach my $y (0 .. $boardsize -1) {
|
||||
foreach my $x (0 .. $boardsize -1) {
|
||||
|
||||
$st = shift @vis;
|
||||
|
||||
if ($st eq '+') {
|
||||
$backing->delete( $x . '_' . $y );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub pass
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub drop_stone
|
||||
{
|
||||
my ( $w, $x, $y) = @_;
|
||||
|
||||
$x = -1 + int(($x-3) / 26);
|
||||
$y = -1 + int(($y-3) / 26);
|
||||
|
||||
if ($x < 0) { return 1; }
|
||||
if ($y < 0) { return 1; }
|
||||
if ($x >= $boardsize) { return 1; }
|
||||
if ($y >= $boardsize) { return 1; }
|
||||
|
||||
|
||||
my $gn = $letter[$x] . ($boardsize - $y);
|
||||
|
||||
if ( !ttPlaceStone( $ctm, $gn ) ) {
|
||||
xputstone( $ctm, $x, $y );
|
||||
xfixboard();
|
||||
ttShowBoard();
|
||||
} else { return 1; }
|
||||
|
||||
|
||||
if ($ctm eq 'W') {
|
||||
$state = 'white';
|
||||
} else {
|
||||
$state = 'black';
|
||||
}
|
||||
|
||||
swap_ctm();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# This routine is called after each message is recieved
|
||||
# -----------------------------------------------------
|
||||
|
||||
# How the control loop works:
|
||||
#
|
||||
# the '$state' variable determines where to jump in.
|
||||
# control is called when a program responds to a message
|
||||
|
||||
sub control
|
||||
{
|
||||
my ($msg) = @_;
|
||||
|
||||
|
||||
# send boardsize 0 (prgA)
|
||||
# send boardsize 1 (prgB)
|
||||
# xxx
|
||||
# send genmove_black (prgA);
|
||||
# send black (prgB);
|
||||
# send genmove_white (prgB);
|
||||
# white (prgA)
|
||||
# goto xxx
|
||||
|
||||
|
||||
if (defined $msg) {
|
||||
print STDERR "state/msg = $state $msg\n";
|
||||
} else { print STDERR "state/msg = $state NULL\n"; }
|
||||
|
||||
if ($state eq 'start') {
|
||||
snd( 0, "$cur_id[0] boardsize $boardsize" );
|
||||
$state = 'startb';
|
||||
return;
|
||||
}
|
||||
|
||||
if ($state eq 'startb') {
|
||||
snd( 1, "$cur_id[1] boardsize $boardsize" );
|
||||
$state = 'genmove_black';
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $state eq 'genmove_black' ) {
|
||||
snd( 0, "$cur_id[0] genmove_black" );
|
||||
$state = 'black';
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $state eq 'black' ) {
|
||||
my $y;
|
||||
my $x;
|
||||
my $gn;
|
||||
|
||||
print "msg ---> $msg\n";
|
||||
|
||||
$msg =~ /^=\d+\s+(.)(.*)/; # parse out move components
|
||||
|
||||
if ( $msg =~ /PASS/ ) {
|
||||
$consecutive_passes++;
|
||||
$gn = 'PASS';
|
||||
} else {
|
||||
$consecutive_passes = 0;
|
||||
$y = $boardsize - $2;
|
||||
$x = $toix{$1};
|
||||
$gn = $letter[$x] . ($boardsize - $y);
|
||||
}
|
||||
|
||||
|
||||
# show blacks move to the interface
|
||||
# ---------------------------------
|
||||
if ( !ttPlaceStone( $ctm, $gn ) ) {
|
||||
xputstone( $ctm, $x, $y ) if $gn ne 'PASS';
|
||||
xfixboard();
|
||||
ttShowBoard();
|
||||
swap_ctm();
|
||||
} else { return 1; }
|
||||
|
||||
# send the move along to WHITE
|
||||
# ----------------------------
|
||||
snd( 1, "$cur_id[1] black $gn" );
|
||||
$state = 'genmove_white';
|
||||
|
||||
if ($consecutive_passes == 2) {
|
||||
$state = 'gameover';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ( $state eq 'genmove_white' ) {
|
||||
snd( 1, "$cur_id[1] genmove_white" );
|
||||
$state = 'white';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ( $state eq 'white' ) {
|
||||
my $y;
|
||||
my $x;
|
||||
my $gn;
|
||||
|
||||
print "msg ---> $msg\n";
|
||||
|
||||
$msg =~ /^=\d+\s+(.)(.*)/; # parse out move components
|
||||
|
||||
if ( $msg =~ /PASS/ ) {
|
||||
$consecutive_passes++;
|
||||
$gn = 'PASS';
|
||||
} else {
|
||||
$consecutive_passes = 0;
|
||||
$y = $boardsize - $2;
|
||||
$x = $toix{$1};
|
||||
$gn = $letter[$x] . ($boardsize - $y);
|
||||
}
|
||||
|
||||
|
||||
# show blacks move to the interface
|
||||
# ---------------------------------
|
||||
if ( !ttPlaceStone( $ctm, $gn ) ) {
|
||||
xputstone( $ctm, $x, $y ) if $gn ne 'PASS';
|
||||
xfixboard();
|
||||
ttShowBoard();
|
||||
swap_ctm();
|
||||
} else { return 1; }
|
||||
|
||||
# send the move along to BLACK
|
||||
# ----------------------------
|
||||
snd( 0, "$cur_id[0] white $gn" );
|
||||
$state = 'genmove_black';
|
||||
|
||||
if ($consecutive_passes == 2) {
|
||||
$state = 'gameover';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ( $state eq 'gameover' ) {
|
||||
print "Game Over\n";
|
||||
ttScore();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
sub snd
|
||||
{
|
||||
my ($who, $str) = @_;
|
||||
|
||||
if ($who == 0) {
|
||||
print $Aprg_in "$str\n";
|
||||
} else {
|
||||
print $Bprg_in "$str\n";
|
||||
}
|
||||
|
||||
print STDERR "----> $str\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub swap_ctm
|
||||
{
|
||||
if ( $ctm eq 'B' ) {
|
||||
$ctm = 'W';
|
||||
} else {
|
||||
$ctm = 'B';
|
||||
}
|
||||
|
||||
}
|
39
gnugo/interface/gtp_examples/README
Normal file
39
gnugo/interface/gtp_examples/README
Normal file
@ -0,0 +1,39 @@
|
||||
GTP Demonstration Programs
|
||||
|
||||
twogtp : a very useful perl script for playing two gtp processes together.
|
||||
|
||||
twogtp-a : alternative implementation
|
||||
|
||||
twogtp.py : Python version of twogtp with additional features.
|
||||
|
||||
twogtp.pike : Pike version of twogtp with even more features.
|
||||
|
||||
2ptkgo.pl : Requires perltk and the next program. Plays two gtp processes
|
||||
together with graphical display
|
||||
|
||||
ttgo.pm : required by 2ptkgo.pl
|
||||
|
||||
gtp2_6 : patch to GNU Go 2.6 allowing use of the gtp with a
|
||||
limited command set sufficient to use twogtp.
|
||||
|
||||
vanilla.c : Not useful by itself, this program shows how to spawn a gtp
|
||||
process using fork() and exec(). It behaves just like GNU Go
|
||||
and could be modified into any number of useful programs such
|
||||
as a gtp/gmp mediator.
|
||||
|
||||
metamachine.c : This program sits between a gtp controller and an
|
||||
engine intercepting gtp commands and processing
|
||||
them. When the gtp engine is gnugo, it gives an
|
||||
alternative move generation scheme (though a weaker
|
||||
engine).
|
||||
|
||||
sgf2tst : A perl script that produces a gtp test script from an sgf file
|
||||
|
||||
matcher_check : A perl script that plays a gnugo process against itself,
|
||||
watching for inconsistencies in the dragon_status field.
|
||||
It flags these for further analysis by a person, and attempts
|
||||
to generate regression tests from them.
|
||||
|
||||
gnugo.el : This alternative to interface/gnugo.el can use XPM's to
|
||||
display the board, but it is not finished. Use
|
||||
interface/gnugo.el instead.
|
663
gnugo/interface/gtp_examples/gnugo.el
Normal file
663
gnugo/interface/gtp_examples/gnugo.el
Normal file
@ -0,0 +1,663 @@
|
||||
;;; ID: $Id: gnugo.el,v 1.1.1.1 2008/12/21 18:47:58 bump Exp $
|
||||
;;;
|
||||
;;; This is GNU Go, a Go program. Contact gnugo@gnu.org, or see
|
||||
;;; http://www.gnu.org/software/gnugo/ for more information.
|
||||
;;;
|
||||
;;; Copyright 1999, 2000, 2001 by the Free Software Foundation.
|
||||
;;;
|
||||
;;; This program is free software; you can redistribute it and/
|
||||
;;; modify it under the terms of the GNU General Public License
|
||||
;;; as published by the Free Software Foundation - version 3,
|
||||
;;; or (at your option) any later version.
|
||||
;;;
|
||||
;;; This program is distributed in the hope that it will be
|
||||
;;; useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
;;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
;;; PURPOSE. See the GNU General Public License in file COPYIN
|
||||
;;; for more details.
|
||||
;;;
|
||||
;;; You should have received a copy of the GNU General Public
|
||||
;;; License along with this program; if not, write to the Free
|
||||
;;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
;;; Boston, MA 02111, USA.
|
||||
|
||||
;;; Description: Run GNU Go in a buffer.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This is an interface to GNU Go using the Go Text Protocol. Interaction
|
||||
;; with the gnugo subprocess is synchronous except for `gnugo-get-move'. This
|
||||
;; means you can use Emacs to do other things while gnugo is thinking about
|
||||
;; its move. (Actually, all interaction with the subprocess is inhibited
|
||||
;; during thinking time -- really, trying to distract your opponent is poor
|
||||
;; sportsmanship. :-)
|
||||
;;
|
||||
;; Customization is presently limited to `gnugo-animation-string', q.v.
|
||||
;;
|
||||
;; This code was tested with Emacs 20.7 on a monochrome 80x24 terminal.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl) ; use the source luke!
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Variables
|
||||
|
||||
(defvar gnugo-board-mode-map nil
|
||||
"Keymap for GNU Go Board mode.")
|
||||
|
||||
(defvar gnugo-option-history nil
|
||||
"History of additional GNU Go command-line options.")
|
||||
|
||||
(defvar gnugo-animation-string
|
||||
(let ((jam "*#") (blink " #") (spin "-\\|/") (yada "*-*!"))
|
||||
(concat jam jam jam jam jam
|
||||
;; "SECRET MESSAGE HERE"
|
||||
blink blink blink blink blink blink blink blink
|
||||
;; Playing go is like fighting ignorance: when you think you have
|
||||
;; surrounded something by knowing it very well it often turns
|
||||
;; out that in the time you spent deepening this understanding,
|
||||
;; other areas of ignorance have surrounded you.
|
||||
spin spin spin spin spin spin spin spin spin
|
||||
;; Playing go is not like fighting ignorance: what one person
|
||||
;; knows many people may come to know; knowledge does not build
|
||||
;; solely move by move. Wisdom, on the other hand...
|
||||
yada yada yada))
|
||||
"*String whose individual characters are used for animation.
|
||||
Specifically, the `gnugo-worm-stones' and `gnugo-dragon-stones' commands
|
||||
render the stones in their respective (computed) groups as the first
|
||||
character in the string, then the next, and so on until the string (and/or
|
||||
the viewer) is exhausted.")
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Support functions
|
||||
|
||||
(defun gnugo-other (color)
|
||||
(if (string= "black" color) "white" "black"))
|
||||
|
||||
(defun gnugo-gate ()
|
||||
(unless (eq (current-buffer) (get 'gnugo 'bbuf))
|
||||
(error "Wrong buffer -- try M-x gnugo"))
|
||||
(when (eq 'waiting (get 'gnugo 'get-move-state))
|
||||
(error "Not your turn yet -- please wait"))
|
||||
(when (eq 'game-over (get 'gnugo 'last-move))
|
||||
(error "Sorry, game over")))
|
||||
|
||||
(defun gnugo-sentinel (proc string)
|
||||
(let ((status (process-status proc)))
|
||||
(when (or (eq status 'exit)
|
||||
(eq status 'signal))
|
||||
(switch-to-buffer (get 'gnugo 'bbuf))
|
||||
(delete-other-windows)
|
||||
(delete-process proc)
|
||||
(put 'gnugo 'proc nil))))
|
||||
|
||||
(defun gnugo-send-line (line)
|
||||
(process-send-string (get 'gnugo 'proc) (concat line "\n")))
|
||||
|
||||
(defun gnugo-synchronous-send/return (message)
|
||||
"Return (TIME . STRING) where TIME is that returned by `current-time' and
|
||||
STRING omits the two trailing newlines. See also `gnugo-query'."
|
||||
(when (eq 'waiting (get 'gnugo 'get-move-state))
|
||||
(error "sorry, still waiting for %s to play" (get 'gnugo 'gnugo-color)))
|
||||
(put 'gnugo 'sync-return "")
|
||||
(let ((proc (get 'gnugo 'proc)))
|
||||
(set-process-filter
|
||||
proc #'(lambda (proc string)
|
||||
(let* ((so-far (get 'gnugo 'sync-return))
|
||||
(start (max 0 (- (length so-far) 2))) ; backtrack a little
|
||||
(full (put 'gnugo 'sync-return (concat so-far string))))
|
||||
(when (string-match "\n\n" full start)
|
||||
(put 'gnugo 'sync-return
|
||||
(cons (current-time) (substring full 0 -2)))))))
|
||||
(gnugo-send-line message)
|
||||
(let (rv)
|
||||
;; type change => break
|
||||
(while (stringp (setq rv (get 'gnugo 'sync-return)))
|
||||
(accept-process-output proc))
|
||||
(put 'gnugo 'sync-return "")
|
||||
rv)))
|
||||
|
||||
(defun gnugo-query (message)
|
||||
"Return cleaned-up value of a call to `gnugo-synchronous-send/return', q.v.
|
||||
The TIME portion is omitted as well as the first two characters of the STRING
|
||||
portion (corresponding to the status indicator in the Go Text Protocol). Use
|
||||
this function when you are sure the command cannot fail."
|
||||
(substring (cdr (gnugo-synchronous-send/return message)) 2))
|
||||
|
||||
(defun gnugo-goto-pos (pos)
|
||||
"Move point to board position POS, a letter-number string."
|
||||
(goto-char (point-min))
|
||||
(search-forward (substring pos 0 1))
|
||||
(let ((col (1- (current-column))))
|
||||
(re-search-forward (concat "^\\s-*" (substring pos 1) "\\s-"))
|
||||
(move-to-column col)))
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Game play actions
|
||||
|
||||
(defun gnugo-showboard ()
|
||||
(interactive)
|
||||
(let ((board (cdr (gnugo-synchronous-send/return "showboard")))
|
||||
white-captures black-captures)
|
||||
(with-current-buffer (get 'gnugo 'bbuf)
|
||||
(delete-region (point-min) (point-max))
|
||||
(insert (substring board 3)) ; omit "= \n"
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\\s-*\\(WH\\|BL\\).*capt.*\\([0-9]+\\).*$"
|
||||
(point-max) t)
|
||||
(if (string= "WH" (match-string 1))
|
||||
(setq white-captures (match-string 2))
|
||||
(setq black-captures (match-string 2)))
|
||||
(replace-match ""))
|
||||
(goto-char (point-max))
|
||||
(move-to-column-force (get 'gnugo 'board-cols))
|
||||
(delete-region (point) (point-max))
|
||||
(let (pos)
|
||||
(insert
|
||||
(case (get 'gnugo 'last-move)
|
||||
((nil) "(black to play)")
|
||||
((game-over) "(t toggle, ! score, q quit)")
|
||||
(t (let* ((last-move (get 'gnugo 'last-move))
|
||||
(color (car last-move))
|
||||
(move (cdr last-move)))
|
||||
(setq pos (and (not (string= "PASS" move)) move))
|
||||
(format "%s: %s (%s to play)\n%scaptures: black %s white %s"
|
||||
color move (gnugo-other color)
|
||||
(make-string (get 'gnugo 'board-cols) 32) ; space
|
||||
black-captures white-captures)))))
|
||||
(when pos
|
||||
(gnugo-goto-pos pos)
|
||||
(delete-char -1) (insert "(")
|
||||
(forward-char 1) (delete-char 1) (insert ")")))
|
||||
(goto-char (get 'gnugo 'last)))))
|
||||
|
||||
(defun gnugo-get-move-insertion-filter (proc string)
|
||||
(let* ((so-far (get 'gnugo 'get-move-string))
|
||||
(full (put 'gnugo 'get-move-string (concat so-far string))))
|
||||
(when (string-match "^= \\(.+\\)\n\n" full)
|
||||
(let ((pos (match-string 1 full)))
|
||||
(put 'gnugo 'get-move-string nil)
|
||||
(put 'gnugo 'get-move-state nil)
|
||||
(put 'gnugo 'last-move (cons (get 'gnugo 'gnugo-color) pos))
|
||||
(gnugo-showboard)
|
||||
(put 'gnugo 'passes
|
||||
(if (string= "PASS" pos)
|
||||
(1+ (get 'gnugo 'passes))
|
||||
0))
|
||||
(when (= 2 (get 'gnugo 'passes))
|
||||
(put 'gnugo 'last-move 'game-over))))))
|
||||
|
||||
(defun gnugo-get-move (color)
|
||||
(put 'gnugo 'get-move-state 'waiting)
|
||||
(set-process-filter (get 'gnugo 'proc) 'gnugo-get-move-insertion-filter)
|
||||
(gnugo-send-line (concat "genmove " color))
|
||||
(accept-process-output))
|
||||
|
||||
(defun gnugo-cleanup (&optional quietly)
|
||||
"Kill gnugo process and *gnugo board* buffer. Reset internal state."
|
||||
(interactive)
|
||||
(let ((proc (get 'gnugo 'proc)))
|
||||
(when proc
|
||||
(delete-process proc)))
|
||||
(let ((bbuf (get 'gnugo 'bbuf)))
|
||||
(when (and bbuf (get-buffer bbuf))
|
||||
(kill-buffer bbuf)))
|
||||
(unless quietly
|
||||
(message "Thank you for playing GNU Go."))
|
||||
(setplist 'gnugo nil))
|
||||
|
||||
(defun gnugo-position ()
|
||||
(let* ((letter (ignore-errors
|
||||
(save-excursion
|
||||
(let ((col (current-column)))
|
||||
(re-search-forward "^\\s-+A B C")
|
||||
(move-to-column col)
|
||||
(buffer-substring (point) (1+ (point)))))))
|
||||
(number (save-excursion
|
||||
(beginning-of-line)
|
||||
(looking-at "\\s-*\\([0-9]+\\)")
|
||||
(match-string 1)))
|
||||
(pos (concat letter number)))
|
||||
(if (string-match "^[A-T][1-9][0-9]*$" pos)
|
||||
pos
|
||||
(error "Not a proper position point"))))
|
||||
|
||||
(defun gnugo-move ()
|
||||
"Make a move on the *gnugo board* buffer.
|
||||
The position is computed from current point.
|
||||
Signal error if done out-of-turn or if game-over.
|
||||
To start a game try M-x gnugo."
|
||||
(interactive)
|
||||
(gnugo-gate)
|
||||
(let* ((pos (gnugo-position))
|
||||
(move (format "play %s %s" (get 'gnugo 'user-color) pos))
|
||||
(accept (cdr (gnugo-synchronous-send/return move)))
|
||||
(status (substring accept 0 1)))
|
||||
(cond ((string= "=" status)
|
||||
(put 'gnugo 'last (point))
|
||||
(put 'gnugo 'last-move (cons (get 'gnugo 'user-color) pos))
|
||||
(put 'gnugo 'passes 0)
|
||||
(gnugo-showboard))
|
||||
(t (error accept)))
|
||||
(gnugo-get-move (get 'gnugo 'gnugo-color))))
|
||||
|
||||
(defun gnugo-mouse-move (e)
|
||||
"Do `gnugo-move' at mouse location."
|
||||
(interactive "@e")
|
||||
(mouse-set-point e)
|
||||
(when (looking-at "[.+]")
|
||||
(gnugo-move)))
|
||||
|
||||
(defun gnugo-pass ()
|
||||
"Make a pass on the *gnugo board* buffer.
|
||||
Signal error if done out-of-turn or if game-over.
|
||||
To start a game try M-x gnugo."
|
||||
(interactive)
|
||||
(gnugo-gate)
|
||||
(let ((passes (1+ (get 'gnugo 'passes))))
|
||||
(put 'gnugo 'passes passes)
|
||||
(put 'gnugo 'last-move
|
||||
(if (= 2 passes)
|
||||
'game-over
|
||||
(cons (get 'gnugo 'user-color) "PASS")))
|
||||
(gnugo-showboard)
|
||||
(unless (= 2 passes)
|
||||
(gnugo-get-move (get 'gnugo 'gnugo-color)))))
|
||||
|
||||
(defun gnugo-mouse-pass (e)
|
||||
"Do `gnugo-pass' at mouse location."
|
||||
(interactive "@e")
|
||||
(mouse-set-point e)
|
||||
(gnugo-pass))
|
||||
|
||||
(defun gnugo-refresh ()
|
||||
"Display *gnugo board* buffer and update it with the current board state.
|
||||
During normal play, parenthesize the last-played stone (no parens for pass),
|
||||
and display at bottom-right corner a message describing the last-played
|
||||
position, who played it (and who is to play), and the number of stones
|
||||
captured thus far by each player."
|
||||
(interactive)
|
||||
(switch-to-buffer (get 'gnugo 'bbuf))
|
||||
(gnugo-showboard))
|
||||
|
||||
(defun gnugo-animate-group (command)
|
||||
(message "Computing %s ..." command)
|
||||
(let ((stones (cdr (gnugo-synchronous-send/return
|
||||
(format "%s %s" command (gnugo-position))))))
|
||||
(if (not (string= "=" (substring stones 0 1)))
|
||||
(error stones)
|
||||
(setq stones (split-string (substring stones 1)))
|
||||
(message "Computing %s ... %s in group." command (length stones))
|
||||
(dolist (c (string-to-list gnugo-animation-string))
|
||||
(save-excursion
|
||||
(dolist (pos stones)
|
||||
(gnugo-goto-pos pos)
|
||||
(delete-char 1)
|
||||
(insert c)))
|
||||
(sit-for 0.08675309)) ; jenny jenny i got your number...
|
||||
(sit-for 5)
|
||||
(let ((p (point)))
|
||||
(gnugo-showboard)
|
||||
(goto-char p)))))
|
||||
|
||||
(defun gnugo-display-group-data (command buffer-name)
|
||||
(message "Computing %s ..." command)
|
||||
(let ((data (cdr (gnugo-synchronous-send/return
|
||||
(format "%s %s" command (gnugo-position))))))
|
||||
(switch-to-buffer buffer-name)
|
||||
(erase-buffer)
|
||||
(insert data))
|
||||
(message "Computing %s ... done." command))
|
||||
|
||||
(defun gnugo-worm-stones ()
|
||||
"In the *gnugo board* buffer, animate \"worm\" at current position.
|
||||
Signal error if done out-of-turn or if game-over.
|
||||
See variable `gnugo-animation-string' for customization."
|
||||
(interactive)
|
||||
(gnugo-gate)
|
||||
(gnugo-animate-group "worm_stones"))
|
||||
|
||||
(defun gnugo-worm-data ()
|
||||
"Display in another buffer data from \"worm\" at current position.
|
||||
Signal error if done out-of-turn or if game-over."
|
||||
(interactive)
|
||||
(gnugo-gate)
|
||||
(gnugo-display-group-data "worm_data" "*gnugo worm data*"))
|
||||
|
||||
(defun gnugo-dragon-stones ()
|
||||
"In the *gnugo board* buffer, animate \"dragon\" at current position.
|
||||
Signal error if done out-of-turn or if game-over.
|
||||
See variable `gnugo-animation-string' for customization."
|
||||
(interactive)
|
||||
(gnugo-gate)
|
||||
(gnugo-animate-group "dragon_stones"))
|
||||
|
||||
(defun gnugo-dragon-data ()
|
||||
"Display in another buffer data from \"dragon\" at current position.
|
||||
Signal error if done out-of-turn or if game-over."
|
||||
(interactive)
|
||||
(gnugo-gate)
|
||||
(gnugo-display-group-data "dragon_data" "*gnugo dragon data*"))
|
||||
|
||||
(defun gnugo-snap ()
|
||||
(save-excursion
|
||||
(let ((letters (progn
|
||||
(goto-char (point-min))
|
||||
(end-of-line)
|
||||
(split-string (buffer-substring (point-min) (point)))))
|
||||
(maxnum (read (current-buffer)))
|
||||
snap)
|
||||
(dolist (letter letters)
|
||||
(do ((number maxnum (1- number)))
|
||||
((= 0 number))
|
||||
(let* ((pos (format "%s%d" letter number))
|
||||
(color (gnugo-query (format "color %s" pos))))
|
||||
(unless (string= "empty" color)
|
||||
(setq snap (cons (cons pos color) snap))))))
|
||||
snap)))
|
||||
|
||||
(defun gnugo-toggle-dead-group ()
|
||||
"In a *gnugo board* buffer, during game-over, toggle a group as dead.
|
||||
The group is selected from current position (point).
|
||||
Signal error if not in game-over or if there is no group at that position."
|
||||
(interactive)
|
||||
(unless (eq 'game-over (get 'gnugo 'last-move))
|
||||
(error "Sorry, game still in play"))
|
||||
(let* ((snap (or (get 'gnugo 'snap) (put 'gnugo 'snap (gnugo-snap))))
|
||||
(pos (gnugo-position))
|
||||
(color (gnugo-query (format "color %s" pos)))
|
||||
(morgue (get 'gnugo 'morgue)))
|
||||
(if (string= "empty" color)
|
||||
(let ((already-dead (find-if '(lambda (group)
|
||||
(member pos (cdr group)))
|
||||
morgue)))
|
||||
(unless already-dead
|
||||
(error "No group at that position"))
|
||||
(put 'gnugo 'morgue (delete already-dead morgue))
|
||||
(setq color (car already-dead))
|
||||
(save-excursion
|
||||
(let ((c (if (string= color "black") "X" "O")))
|
||||
(dolist (stone (cdr already-dead))
|
||||
(gnugo-synchronous-send/return
|
||||
(format "play %s %s" color stone))
|
||||
(gnugo-goto-pos stone) (delete-char 1) (insert c)))))
|
||||
(let ((stones (sort (split-string
|
||||
(gnugo-query (format "worm_stones %s" pos)))
|
||||
'string<)))
|
||||
(let ((newly-dead (cons color stones)))
|
||||
(unless (member newly-dead morgue)
|
||||
(setq morgue (put 'gnugo 'morgue (cons newly-dead morgue)))))
|
||||
;; clear and add back everything except the dead -- yuk!
|
||||
(gnugo-synchronous-send/return "clear_board")
|
||||
(let ((all-dead (apply 'append (mapcar 'cdr morgue))))
|
||||
(dolist (pos-color snap)
|
||||
(unless (member (car pos-color) all-dead)
|
||||
(gnugo-synchronous-send/return
|
||||
(format "play %s %s" (cdr pos-color) (car pos-color))))))
|
||||
(let ((p (point)))
|
||||
;;(gnugo-showboard)
|
||||
(dolist (worm morgue)
|
||||
(let ((c (if (string= "black" (car worm)) "x" "o")))
|
||||
(dolist (stone (cdr worm))
|
||||
(gnugo-goto-pos stone)
|
||||
(delete-char 1) (insert c))))
|
||||
(goto-char p))))))
|
||||
|
||||
(defun gnugo-estimate-score ()
|
||||
"Display estimated score of a game of GNU Go.
|
||||
Output includes number of stones on the board and number of stones
|
||||
captured by each player, and the estimate of who has the advantage (and
|
||||
by how many stones)."
|
||||
(interactive)
|
||||
(message "Est.score ...")
|
||||
(let ((black (length (split-string (gnugo-query "list_stones black"))))
|
||||
(white (length (split-string (gnugo-query "list_stones white"))))
|
||||
(black-captures (gnugo-query "captures black"))
|
||||
(white-captures (gnugo-query "captures white"))
|
||||
(est (gnugo-query "estimate_score")))
|
||||
(message "Est.score ... B %s %s | W %s %s | %s"
|
||||
black black-captures white white-captures est)))
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Command properties and gnugo-command
|
||||
|
||||
;; A direct gtp command can easily confuse gnugo.el, so we allow for
|
||||
;; interpretation of any command (and still become confused when the
|
||||
;; heuristics fail ;-). Both control and data paths are are influenced by
|
||||
;; these properties:
|
||||
;;
|
||||
;; gnugo-full -- completely interpret the command string; the value is a
|
||||
;; func that takes the list of words derived from splitting the
|
||||
;; command string (minus the command) and handles everything.
|
||||
;;
|
||||
;; gnugo-rinse -- function taking raw output string and returning a
|
||||
;; (possibly filtered) replacement, the only one able
|
||||
;; to set the `gnugo-post-function' property (below).
|
||||
;; value may also be a list of such functions.
|
||||
;;
|
||||
;; gnugo-output -- symbol specifying the preferred output method.
|
||||
;; message -- show output in minibuffer
|
||||
;; discard -- sometimes you just don't care
|
||||
;; default is to switch to buffer "*gnugo command output*"
|
||||
;; if the output has a newline, otherwise use `message'.
|
||||
;;
|
||||
;; gnugo-post-function -- function or list of functions to call after the
|
||||
;; command (also after all output processing); only
|
||||
;; settable by a `gnugo-rinse' function.
|
||||
|
||||
(defun gnugo-command (command)
|
||||
"During a GNU Go game, send Go Text Protocol COMMAND to the subprocess."
|
||||
(interactive "sCommand: ")
|
||||
(if (string= "" command)
|
||||
(message "(no command given)")
|
||||
(let* ((split (split-string command))
|
||||
(cmd (intern (car split)))
|
||||
(full (get cmd 'gnugo-full))
|
||||
(last-message nil))
|
||||
(if full
|
||||
(funcall full (cdr split))
|
||||
(message "Doing %s ..." command)
|
||||
(let* ((ans (cdr (gnugo-synchronous-send/return command)))
|
||||
(rinse (get cmd 'gnugo-rinse))
|
||||
(where (get cmd 'gnugo-output)))
|
||||
(put cmd 'gnugo-post-function nil)
|
||||
(when rinse
|
||||
(cond ((functionp rinse) (setq ans (funcall rinse ans)))
|
||||
((listp rinse) (while rinse
|
||||
(setq ans (funcall (car rinse) ans)
|
||||
rinse (cdr rinse))))
|
||||
(t (error "bad gnugo-rinse property: %s" rinse))))
|
||||
(if (string-match "unknown.command" ans)
|
||||
(message ans)
|
||||
(cond ((eq 'discard where) (message ""))
|
||||
((or (eq 'message where)
|
||||
(not (string-match "\n" ans)))
|
||||
(message ans))
|
||||
(t (switch-to-buffer "*gnugo command output*")
|
||||
(erase-buffer)
|
||||
(insert ans)
|
||||
(message "Doing %s ... done." command)))
|
||||
(let ((pf (get cmd 'gnugo-post-function)))
|
||||
(when pf
|
||||
(cond ((functionp pf) (funcall pf))
|
||||
((listp pf) (while pf
|
||||
(progn (funcall (car pf))
|
||||
(setq pf (cdr pf)))))
|
||||
(t (error "bad gnugo-post-function property: %s"
|
||||
pf)))
|
||||
(put cmd 'gnugo-post-function nil)))))))))
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Major mode for interacting with a GNU Go subprocess
|
||||
|
||||
(defun gnugo-board-mode ()
|
||||
"In this mode, keys do not self insert.
|
||||
Here are the default keybindings:
|
||||
|
||||
? View this help.
|
||||
|
||||
RET or SPC Select point as the next move.
|
||||
An error is signalled for invalid locations.
|
||||
|
||||
q or Q Quit (the latter without confirmation).
|
||||
|
||||
R Resign.
|
||||
|
||||
C-l Refresh board.
|
||||
|
||||
_ or M-_ Bury the Board buffer (when the boss is near).
|
||||
|
||||
P Pass; i.e., select no location for your move.
|
||||
|
||||
w Animate current position's worm stones.
|
||||
d Animate current position's dragon stones.
|
||||
See variable `gnugo-animation-string'.
|
||||
|
||||
W Display current position's worm data in another buffer.
|
||||
D Display current position's dragon data in another buffer.
|
||||
|
||||
t Toggle dead groups (when the game is over).
|
||||
|
||||
! Estimate score (at any time).
|
||||
|
||||
: or ; Extended command. Type in a string to be passed (quite
|
||||
indirectly) to the GNU Go subprocess. Output and emacs
|
||||
behavior depend on which command is given. Try `help'
|
||||
to get a list of all commands. Note that some commands
|
||||
may confuse gnugo.el."
|
||||
(kill-all-local-variables)
|
||||
(use-local-map gnugo-board-mode-map)
|
||||
(setq major-mode 'gnugo-board-mode)
|
||||
(setq mode-name "GNU Go Board"))
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Entry point
|
||||
|
||||
;;;###autoload
|
||||
(defun gnugo ()
|
||||
"Run gnugo in a buffer, or resume a game in progress.
|
||||
You are queried for additional command-line options (Emacs supplies
|
||||
\"--mode gtp --quiet\" automatically). Here is a list of options
|
||||
that gnugo.el understands and handles specially:
|
||||
|
||||
--boardsize num Set the board size to use (5--19)
|
||||
--color <color> Choose your color ('black' or 'white')
|
||||
--handicap <num> Set the number of handicap stones (0--9)
|
||||
|
||||
If there is already a game in progress you may resume it instead of
|
||||
starting a new one. See `gnugo-board-mode' documentation for more info.
|
||||
See also variable `gnugo-option-history'."
|
||||
(interactive)
|
||||
(if (and (get 'gnugo 'proc)
|
||||
(y-or-n-p "GNU Go game in progress, resume play? "))
|
||||
(progn
|
||||
(switch-to-buffer (get 'gnugo 'bbuf))
|
||||
(gnugo-refresh))
|
||||
(gnugo-cleanup t)
|
||||
(put 'gnugo 'last 1)
|
||||
(let* ((name "gnugo")
|
||||
(args (read-string "GNU Go options: "
|
||||
(car gnugo-option-history)
|
||||
'gnugo-option-history))
|
||||
(proc (apply 'start-process name nil name
|
||||
"--mode" "gtp" "--quiet"
|
||||
(split-string args)))
|
||||
(bbuf (generate-new-buffer "*gnugo board*"))
|
||||
(board-cols (+ 8 (* 2 (if (string-match "--boardsize" args)
|
||||
(let ((start (match-end 0)))
|
||||
(string-match "[1-9]+" args start)
|
||||
(string-to-number (match-string 0 args)))
|
||||
19))))
|
||||
(user-color (if (string-match "--color" args)
|
||||
(let ((start (match-end 0)))
|
||||
(string-match "\\(black\\|white\\)" args start)
|
||||
(match-string 0 args))
|
||||
"black"))
|
||||
(gnugo-color (gnugo-other user-color))
|
||||
(handicap (if (string-match "--handicap" args)
|
||||
(let ((start (match-end 0)))
|
||||
(string-match "[0-9]+" args start)
|
||||
(string-to-number (match-string 0 args)))
|
||||
0))
|
||||
(passes 0)
|
||||
snap morgue)
|
||||
(mapcar '(lambda (sym)
|
||||
(put 'gnugo sym (eval sym)))
|
||||
'(proc bbuf board-cols user-color gnugo-color handicap passes
|
||||
snap morgue))
|
||||
(unless (= 0 handicap)
|
||||
(gnugo-synchronous-send/return (concat "fixed_handicap " handicap)))
|
||||
(set-process-sentinel proc 'gnugo-sentinel)
|
||||
(gnugo-refresh))
|
||||
;; set it all up
|
||||
(gnugo-board-mode)
|
||||
;; first move
|
||||
(when (or (and (string= "black" (get 'gnugo 'user-color))
|
||||
(< 1 (get 'gnugo 'handicap)))
|
||||
(and (string= "black" (get 'gnugo 'gnugo-color))
|
||||
(< (get 'gnugo 'handicap) 2)))
|
||||
(gnugo-get-move (get 'gnugo 'gnugo-color)))))
|
||||
|
||||
;;;---------------------------------------------------------------------------
|
||||
;;; Load-time actions
|
||||
|
||||
(unless gnugo-board-mode-map
|
||||
(setq gnugo-board-mode-map (make-sparse-keymap))
|
||||
(suppress-keymap gnugo-board-mode-map)
|
||||
(mapcar '(lambda (pair)
|
||||
(define-key gnugo-board-mode-map (car pair) (cdr pair)))
|
||||
'(("?" . describe-mode)
|
||||
("\C-m" . gnugo-move)
|
||||
(" " . gnugo-move)
|
||||
("P" . gnugo-pass)
|
||||
("R" . (lambda () (interactive)
|
||||
(if (y-or-n-p "Resign? ")
|
||||
(gnugo-cleanup)
|
||||
(message "(not resigning)"))))
|
||||
("q" . (lambda () (interactive)
|
||||
(if (y-or-n-p "Quit? ")
|
||||
(gnugo-cleanup)
|
||||
(message "(not quitting)"))))
|
||||
("Q" . gnugo-cleanup)
|
||||
("\C-l" . gnugo-refresh)
|
||||
("\M-_" . bury-buffer)
|
||||
("_" . bury-buffer)
|
||||
("w" . gnugo-worm-stones)
|
||||
("W" . gnugo-worm-data)
|
||||
("d" . gnugo-dragon-stones)
|
||||
("D" . gnugo-dragon-data)
|
||||
("t" . gnugo-toggle-dead-group)
|
||||
("!" . gnugo-estimate-score)
|
||||
(":" . gnugo-command)
|
||||
(";" . gnugo-command)
|
||||
;; mouse
|
||||
([(down-mouse-1)] . gnugo-mouse-move)
|
||||
([(down-mouse-3)] . gnugo-mouse-pass))))
|
||||
|
||||
(put 'help 'gnugo-full
|
||||
'(lambda (sel)
|
||||
(info "(gnugo)GTP command reference")
|
||||
(if (not sel)
|
||||
(message "(you can also try \"help COMMAND\" next time)")
|
||||
(let ((topic (intern (car sel))))
|
||||
(goto-char (point-min))
|
||||
(when (search-forward (concat "* " (car sel) "\n") (point-max) t)
|
||||
(let (buffer-read-only)
|
||||
(when (get topic 'gnugo-full)
|
||||
(insert "[NOTE: fully handled by gnugo.el]\n"))
|
||||
(when (get topic 'gnugo-rinse)
|
||||
(insert "[NOTE: output rinsed by gnugo.el]\n"))))))))
|
||||
|
||||
(mapc '(lambda (command)
|
||||
(put command 'gnugo-output 'discard)
|
||||
(put command 'gnugo-rinse
|
||||
'(lambda (ans)
|
||||
(put cmd 'gnugo-post-function 'gnugo-refresh)
|
||||
ans)))
|
||||
'(clear_board
|
||||
fixed_handicap))
|
||||
|
||||
(provide 'gnugo)
|
||||
|
||||
;;; $RCSfile: gnugo.el,v $$Revision: 1.1.1.1 $ ends here
|
1167
gnugo/interface/gtp_examples/gtp2_6
Normal file
1167
gnugo/interface/gtp_examples/gtp2_6
Normal file
File diff suppressed because it is too large
Load Diff
729
gnugo/interface/gtp_examples/matcher_check
Normal file
729
gnugo/interface/gtp_examples/matcher_check
Normal file
@ -0,0 +1,729 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 #
|
||||
# 2008 and 2009 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
#
|
||||
# matcher_check info:
|
||||
#
|
||||
# Plays one gtp program against itself or lets it analzye a saved .sgf-file,
|
||||
# and watches for bad status transitions.
|
||||
#
|
||||
# FIXME: if the vertex by which a dragon is named ever changes,
|
||||
# the hash table used will consider it new. therefore, if the
|
||||
# vertex changes at the same time an illegal state change occurs,
|
||||
# it will get missed. Also, it is possible that a dragon would
|
||||
# be captured, and that vertex go unused until a new piece was
|
||||
# played in that spot, resulting in a false positive. However,
|
||||
# this should be rare (?).
|
||||
|
||||
package TWOGTP_A;
|
||||
|
||||
use IPC::Open2;
|
||||
use Getopt::Long;
|
||||
use FileHandle;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Carp;
|
||||
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
#following added globally to allow "use strict" :
|
||||
my $vertex;
|
||||
my $first;
|
||||
my $sgfmove;
|
||||
my $sgffilename;
|
||||
my $pidp;
|
||||
my $sgffile;
|
||||
my $handicap_stones;
|
||||
my $result;
|
||||
my @vertices;
|
||||
my $second;
|
||||
my %game_list;
|
||||
#end of "use strict" repairs
|
||||
|
||||
my $program;
|
||||
my $size = 19;
|
||||
my $verbose = 0;
|
||||
my $komi = 5.5;
|
||||
my $handicap = 0;
|
||||
my $games = 1;
|
||||
my $wanthelp;
|
||||
|
||||
#added for matcher_check
|
||||
my %match_hist;
|
||||
my $loadfile;
|
||||
my $movenum;
|
||||
my $movecount;
|
||||
my $move;
|
||||
my $toplay;
|
||||
my $randseed;
|
||||
my $stable;
|
||||
my $pids;
|
||||
my $stable_move = "";
|
||||
my $noilcheck;
|
||||
my $color;
|
||||
|
||||
my $helpstring = "
|
||||
|
||||
Run with:
|
||||
|
||||
matchercheck --program \'<path to program> --mode gtp [program options]\' \\
|
||||
[matcher_check options]
|
||||
|
||||
Possible matcher_check options:
|
||||
|
||||
--verbose 1 (to list moves) or --verbose 2 (to draw board)
|
||||
--komi <amount>
|
||||
--handicap <amount>
|
||||
--size <board size> (default 19)
|
||||
--games <number of games to play> (-1 to play forever)
|
||||
--sgffile <filename> (file to save games as)
|
||||
--loadsgf <filename> (file to analyze)
|
||||
--movecount <number of moves to check>
|
||||
--randseed <number> (sets the random seed)
|
||||
--stable \'<path to stable version> --mode gtp [program options]\'
|
||||
--noilcheck (turns off illegal transition checks)
|
||||
--color <color> (only replay for color; has no effect
|
||||
without --noilcheck and --loadsgf)
|
||||
--help (show this)
|
||||
|
||||
|
||||
";
|
||||
|
||||
GetOptions(
|
||||
"program|p=s" => \$program,
|
||||
"verbose|v=i" => \$verbose,
|
||||
"komi|k=f" => \$komi,
|
||||
"handicap|h=i" => \$handicap,
|
||||
"size|boardsize|s=i" => \$size,
|
||||
"sgffile|o=s" => \$sgffilename,
|
||||
"loadsgf|l=s" => \$loadfile,
|
||||
"games=i" => \$games,
|
||||
"movecount=i" => \$movecount,
|
||||
"randseed=i" => \$randseed,
|
||||
"stable=s" => \$stable,
|
||||
"noilcheck" => \$noilcheck,
|
||||
"color=s" => \$color,
|
||||
"help" => \$wanthelp,
|
||||
);
|
||||
|
||||
if ($wanthelp) {
|
||||
print $helpstring;
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
if (!$program) {
|
||||
$program = '../gnugo --mode gtp --quiet';
|
||||
warn "Defaulting program to: $program\n";
|
||||
}
|
||||
|
||||
if (defined($color) and (!defined($noilcheck) or !defined($loadfile))) {
|
||||
print "Error: --color requires --noilcheck and --loadsgf";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
# create FileHandles
|
||||
my $prog_in = new FileHandle; # stdin of program
|
||||
my $prog_out = new FileHandle; # stdout of program
|
||||
my $stable_in = new FileHandle; # stdin of stable version
|
||||
my $stable_out = new FileHandle; # stdout of stable version
|
||||
|
||||
|
||||
if ($loadfile)
|
||||
{
|
||||
#we need to analyze an sgf file
|
||||
if (not defined $movecount) {
|
||||
print "Error: When analyzing an sgf file with --loadsgf <filename>, you also need to
|
||||
specify the number of moves to check with --movecount <n>.
|
||||
";
|
||||
exit;
|
||||
}
|
||||
|
||||
$pidp = open2($prog_out, $prog_in, $program);
|
||||
$pids = open2($stable_out, $stable_in, $stable) if defined($stable);
|
||||
print "program pid: $pidp\n" if $verbose;
|
||||
print "stable pid: $pids\n" if (defined($stable) and $verbose);
|
||||
|
||||
if (defined($randseed)) {
|
||||
print $prog_in "set_random_seed $randseed\n";
|
||||
eat_no_response($prog_out);
|
||||
} else {
|
||||
print $prog_in "get_random_seed\n";
|
||||
$randseed = eat_one_line($prog_out);
|
||||
print "random seed $randseed\n";
|
||||
}
|
||||
|
||||
if (defined($stable)) {
|
||||
$randseed =~ s/^= //smg;
|
||||
print $stable_in "set_random_seed $randseed\n";
|
||||
eat_no_response($stable_out);
|
||||
}
|
||||
|
||||
for ($movenum = 1; $movenum <= $movecount + 1; $movenum++)
|
||||
{
|
||||
#load the file, check the statuses, next move.
|
||||
my $lmove = $movenum + 1;#number to load up to
|
||||
print "loading move $movenum\n" if $verbose;
|
||||
print $prog_in "loadsgf $loadfile $lmove\n";
|
||||
eat_no_response($prog_out);
|
||||
if (!defined($noilcheck)) {
|
||||
check_matcher($prog_in, $prog_out);
|
||||
print "done checking status.\n" if ($verbose);
|
||||
}
|
||||
|
||||
#do stable checks
|
||||
if (defined($stable)) {
|
||||
print $stable_in "loadsgf $loadfile $lmove\n";
|
||||
$toplay = eat_one_line($stable_out);
|
||||
$toplay =~ s/^=//smg;
|
||||
$toplay =~ s/ //smg;
|
||||
if (!defined($color) or ($color eq $toplay)) {
|
||||
print $prog_in "genmove_$toplay\n";
|
||||
print $stable_in "genmove_$toplay\n";
|
||||
$move = eat_move($prog_out);
|
||||
$stable_move = eat_move($stable_out);
|
||||
if ($move ne $stable_move and defined ($stable)) {
|
||||
print "At move $movenum, $toplay\:\n";
|
||||
print "Test version played $move\n";
|
||||
print "Stable version played $stable_move\n";
|
||||
if ($verbose eq 2) {
|
||||
print $prog_in "showboard\n";
|
||||
print eat_response($prog_out);
|
||||
}
|
||||
} else {
|
||||
print "$toplay plays $move\n" if $verbose;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "done reading sgf file\n" if ($verbose);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
while ($games > 0) {
|
||||
%match_hist = ();
|
||||
$pidp = open2($prog_out, $prog_in, $program);
|
||||
print "program pid: $pidp\n" if $verbose;
|
||||
|
||||
if (defined($stable)) {
|
||||
$pids = open2($stable_out, $stable_in, $stable);
|
||||
print "stable pid: $pids\n" if $verbose;
|
||||
}
|
||||
|
||||
$sgffile = rename_sgffile($games, $sgffilename) if defined $sgffilename;
|
||||
|
||||
if ((defined $sgffilename) && !open(SGFFILEHANDLE, ">$sgffile")) {
|
||||
printf("can't open $sgffile\n");
|
||||
undef($sgffilename);
|
||||
}
|
||||
|
||||
#set autoflushing for sgf file
|
||||
SGFFILEHANDLE->autoflush(1);
|
||||
|
||||
if (!defined $komi) {
|
||||
if ($handicap > 0) {
|
||||
$komi = 0.5;
|
||||
}
|
||||
else {
|
||||
$komi = 5.5;
|
||||
}
|
||||
}
|
||||
|
||||
print $prog_in "boardsize $size\n";
|
||||
eat_no_response($prog_out);
|
||||
print $prog_in "komi $komi\n";
|
||||
eat_no_response($prog_out);
|
||||
|
||||
if (defined($stable)) {
|
||||
print $stable_in "komi $komi\n";
|
||||
eat_no_response($stable_out);
|
||||
print $stable_in "boardsize $size\n";
|
||||
eat_no_response($stable_out);
|
||||
}
|
||||
|
||||
if (defined($randseed)) {
|
||||
print $prog_in "set_random_seed $randseed\n";
|
||||
eat_no_response($prog_out);
|
||||
} else {
|
||||
print $prog_in "get_random_seed\n";
|
||||
$randseed = eat_one_line($prog_out);
|
||||
$randseed =~ s/^= //smg;
|
||||
print "random seed $randseed\n";
|
||||
}
|
||||
|
||||
if (defined($stable)) {
|
||||
print $stable_in "set_random_seed $randseed\n";
|
||||
eat_no_response($stable_out);
|
||||
}
|
||||
|
||||
undef $randseed; #if more than one game, get a new seed next time.
|
||||
|
||||
print SGFFILEHANDLE "(;GM[1]FF[4]RU[Japanese]SZ[$size]HA[$handicap]KM[$komi]"
|
||||
if defined $sgffilename;
|
||||
|
||||
my $pass = 0;
|
||||
$move = "";
|
||||
|
||||
if ($handicap < 2) {
|
||||
$toplay = "black";
|
||||
}
|
||||
else {
|
||||
$toplay = "white";
|
||||
print $prog_in "fixed_handicap $handicap\n";
|
||||
|
||||
$handicap_stones = eat_handicap($prog_out);
|
||||
my $stable_stones = $handicap_stones;
|
||||
|
||||
if (defined($stable)) {
|
||||
print $stable_in "fixed_handicap $handicap\n";
|
||||
$stable_stones = eat_handicap($stable_out);
|
||||
}
|
||||
|
||||
if ($stable_stones ne $handicap_stones) {
|
||||
print "Handicap discrepancy:\n";
|
||||
print "Test: $handicap_stones\n";
|
||||
print "Stable: $stable_stones\n";
|
||||
}
|
||||
|
||||
if (defined $sgffilename) {
|
||||
print SGFFILEHANDLE $handicap_stones;
|
||||
}
|
||||
}
|
||||
|
||||
$movenum = 1;
|
||||
while ($pass < 2) {
|
||||
print $prog_in "genmove_$toplay\n";
|
||||
$move = eat_move($prog_out);
|
||||
|
||||
if (defined($stable)) {
|
||||
print $stable_in "genmove_$toplay\n" if defined($stable);
|
||||
$stable_move = eat_move($stable_out);
|
||||
print $stable_in "undo\n";
|
||||
eat_no_response($stable_out);
|
||||
}
|
||||
|
||||
if ($move ne $stable_move and defined ($stable)) {
|
||||
print "At move $movenum, $toplay\:\n";
|
||||
print "Test version played $move\n";
|
||||
print "Stable version played $stable_move\n";
|
||||
if ($verbose eq 2) {
|
||||
print $prog_in "showboard\n";
|
||||
print eat_response($prog_out);
|
||||
}
|
||||
} else {
|
||||
print "$toplay plays $move\n" if $verbose;
|
||||
}
|
||||
|
||||
$sgfmove = standard_to_sgf($move);
|
||||
my $tpc = "B"; #toplay char
|
||||
$tpc = "W" if ($toplay eq "white");
|
||||
print SGFFILEHANDLE ";$tpc\[$sgfmove\]\n" if defined $sgffilename;
|
||||
|
||||
print $stable_in "$toplay $move\n" if defined($stable);
|
||||
eat_no_response($stable_out) if defined($stable);
|
||||
|
||||
if ($toplay eq "black") {
|
||||
$toplay = "white";
|
||||
} else {
|
||||
$toplay = "black";
|
||||
}
|
||||
|
||||
if ($move =~ /PASS/i) {
|
||||
$pass++;
|
||||
} else {
|
||||
$pass = 0;
|
||||
}
|
||||
|
||||
if ($verbose > 2) {
|
||||
print $prog_in "showboard\n";
|
||||
eat_no_response($prog_out);
|
||||
if (defined($stable)) {
|
||||
print $stable_in "showboard\n";
|
||||
eat_no_response($stable_out);
|
||||
}
|
||||
}
|
||||
|
||||
check_matcher($prog_in, $prog_out) if !defined($noilcheck);
|
||||
$movenum++;
|
||||
}
|
||||
print $prog_in "estimate_score\n";
|
||||
$result = eat_score($prog_out);
|
||||
if (defined($stable)) {
|
||||
print $stable_in "estimate_score\n";
|
||||
my $stable_result = eat_score($stable_out);
|
||||
print "scoring discrepancy. Stable score: $stable_result.\n" if ($stable_result ne $result);
|
||||
}
|
||||
|
||||
print "Result: $result\n";
|
||||
print $prog_in "quit\n";
|
||||
print $stable_in "quit\n" if defined($stable);
|
||||
|
||||
if (defined $sgffilename) {
|
||||
print "sgf file: $sgffile\n";
|
||||
print SGFFILEHANDLE ")";
|
||||
close SGFFILEHANDLE;
|
||||
$game_list{$sgffile} = $result;
|
||||
}
|
||||
$games-- if $games > 0;
|
||||
|
||||
#make sure gnugo dies correctly.
|
||||
close $prog_in;
|
||||
close $prog_out;
|
||||
close $stable_in if defined($stable);
|
||||
close $stable_out if defined($stable);
|
||||
waitpid $pidp, 0;
|
||||
waitpid $pids, 0;
|
||||
|
||||
print "games remaining: $games\n";
|
||||
}
|
||||
|
||||
if (defined $sgffilename) {
|
||||
my $index_out = new FileHandle;
|
||||
open ($index_out, "> " . index_name($sgffilename));
|
||||
print $index_out
|
||||
"<HTML><HEAD><TITLE>game results</TITLE></HEAD>
|
||||
<BODY><H3>Game Results</H3>
|
||||
<H4>White: ".html_encode($program)."</H4>
|
||||
<H4>Black: ".html_encode($program)."</H4>
|
||||
<TABLE border=1>
|
||||
<TR>
|
||||
<TD>SGF file</TD>
|
||||
<TD>Result</TD>
|
||||
</TR>
|
||||
";
|
||||
foreach (sort by_result keys(%game_list)) {
|
||||
print $index_out "<TR><TD><A href=\"$_\">$_</A></TD>" .
|
||||
"<TD>".html_encode(game_result($_))."</TD></TR>\n";
|
||||
}
|
||||
print $index_out "</TABLE></BODY></HTML>\n";
|
||||
}
|
||||
|
||||
exit;
|
||||
#all done here.
|
||||
|
||||
sub game_result {
|
||||
$_ = shift;
|
||||
$_ = $game_list{$_};
|
||||
#i.e.: B+13.5 (upper bound: -13.5, lower: -13.5)|B+13.5 (upper bound: -13.5, lower: -13.5)
|
||||
#Make sure that all 4 values are the same. I've not seen them different yet.
|
||||
#If they are ever different, need to improve the HTML output (now just -999) -
|
||||
# an explanation of the score mismatch problem would be appropriate.
|
||||
$_ =~ /^.*upper bound..([0-9+.\-]*)..lower..\1.\|.*upper bound..\1..lower..\1./;
|
||||
if (defined($1)) {
|
||||
return $1;
|
||||
} else {
|
||||
return -999;
|
||||
}
|
||||
}
|
||||
|
||||
sub by_result {
|
||||
game_result($a) <=> game_result($b) || $a cmp $b;
|
||||
}
|
||||
|
||||
sub html_encode {
|
||||
#print shift;
|
||||
my $r = shift;
|
||||
$r =~ s/&/&/g;
|
||||
$r =~ s/</</g;
|
||||
$r =~ s/>/>/g;
|
||||
return $r;
|
||||
}
|
||||
|
||||
sub eat_no_response {
|
||||
my $h = shift;
|
||||
|
||||
# ignore empty lines
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
}
|
||||
|
||||
sub eat_response {
|
||||
my $h = shift;
|
||||
my $response = "";
|
||||
# ignore empty lines
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
while ($line ne "") {
|
||||
$response = "$response$line\n";
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
sub eat_one_line {
|
||||
my $h = shift;
|
||||
# ignore empty lines
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
return $line;
|
||||
}
|
||||
|
||||
sub eat_move {
|
||||
my $h = shift;
|
||||
# ignore empty lines
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
if (!defined($line = <$h>)) {
|
||||
print SGFFILEHANDLE ")";
|
||||
close SGFFILEHANDLE;
|
||||
die "Engine crashed!\n";
|
||||
}
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
my ($equals, $move) = split(' ', $line, 2);
|
||||
$line = <$h>;
|
||||
defined($move) or confess "no move found: line was: '$line'";
|
||||
return $move;
|
||||
}
|
||||
|
||||
sub eat_handicap {
|
||||
my $h = shift;
|
||||
my $sgf_handicap = "AB";
|
||||
# ignore empty lines, die if process is gone
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
}
|
||||
@vertices = split(" ", $line);
|
||||
foreach $vertex (@vertices) {
|
||||
if (!($vertex eq "=")) {
|
||||
$vertex = standard_to_sgf($vertex);
|
||||
$sgf_handicap = "$sgf_handicap\[$vertex\]";
|
||||
}
|
||||
}
|
||||
return "$sgf_handicap;";
|
||||
}
|
||||
|
||||
sub eat_score {
|
||||
my $h = shift;
|
||||
# ignore empty lines, die if process is gone
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/^\s*//msg;
|
||||
$line =~ s/\s*$//msg;
|
||||
}
|
||||
$line =~ s/\s*$//;
|
||||
my ($equals, $result) = split(' ', $line, 2);
|
||||
$line = <$h>;
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub standard_to_sgf {
|
||||
for (@_) { confess "Yikes!" if !defined($_); }
|
||||
for (@_) { tr/A-Z/a-z/ };
|
||||
$_ = shift(@_);
|
||||
/([a-z])([0-9]+)/;
|
||||
return "tt" if $_ eq "pass";
|
||||
|
||||
$first = ord $1;
|
||||
if ($first > 104) {
|
||||
$first = $first - 1;
|
||||
}
|
||||
$first = chr($first);
|
||||
$second = chr($size+1-$2+96);
|
||||
return "$first$second";
|
||||
}
|
||||
|
||||
sub rename_sgffile {
|
||||
my $nogames = int shift(@_);
|
||||
$_ = shift(@_);
|
||||
s/\.sgf$//;
|
||||
# Annoying to loose _001 on game #1 in multi-game set.
|
||||
# Could record as an additional parameter.
|
||||
# return "$_.sgf" if ($nogames == 1);
|
||||
return sprintf("$_" . "_%03d.sgf", $nogames);
|
||||
}
|
||||
|
||||
sub index_name {
|
||||
$_ = shift;
|
||||
s/\.sgf$//;
|
||||
return $_ . "_index.html";
|
||||
}
|
||||
|
||||
sub check_matcher {
|
||||
#check for illegal transitions, and print things if they happen
|
||||
my $in = shift;
|
||||
my $out = shift;
|
||||
my $line = "";
|
||||
my $legality = "illegal";
|
||||
my $vertex = " ";
|
||||
my $new_status = " ";
|
||||
my $old_status;
|
||||
my $il_vertex = "";
|
||||
my $il_move = "";
|
||||
|
||||
#send command
|
||||
print $in "dragon_status\n";
|
||||
|
||||
while ($line eq "") {
|
||||
chop($line = <$out>);
|
||||
$line =~ s/^\s*//smg;
|
||||
$line =~ s/\s*$//smg;
|
||||
}
|
||||
|
||||
while ($line ne "")
|
||||
{
|
||||
print "parsing a line\n" if ($verbose);
|
||||
$line =~ s/= //g; #zap the "= " at the front of the response
|
||||
$line =~ s/\n//g; #zap newlines...
|
||||
$line =~ s/://g; #zap the :
|
||||
print $line . "\n" if ($verbose);
|
||||
($vertex, $new_status) = split(" ", $line); #and split on spaces
|
||||
#extra get trashed
|
||||
$old_status = $match_hist{$vertex} if (exists($match_hist{$vertex}));
|
||||
|
||||
#debug output
|
||||
if ($verbose > 1)
|
||||
{
|
||||
print "Vertex: $vertex\n";
|
||||
print "Old Status: $old_status\n" if (exists($match_hist{$vertex}));
|
||||
print "New Status: $new_status\n";
|
||||
}
|
||||
|
||||
#if it's new, we don't care
|
||||
if (!exists($match_hist{$vertex})) {
|
||||
print "$vertex is new.\n" if ($verbose > 0);
|
||||
$match_hist{$vertex} = $new_status;
|
||||
next;
|
||||
}
|
||||
|
||||
#ok, so it's old
|
||||
|
||||
$legality = "illegal";
|
||||
if ($old_status eq "critical") {$legality = "legal"};
|
||||
if ($new_status eq "critical") {$legality = "legal"};
|
||||
if ($new_status eq "unknown") {$legality = "legal"};
|
||||
if ($old_status eq "unknown") {
|
||||
if ($new_status eq "alive") {$legality = "legal";}
|
||||
if ($new_status eq "critical") {$legality = "legal";}
|
||||
}
|
||||
if ($old_status eq "alive" and $new_status eq "dead") {
|
||||
$legality = "killed";
|
||||
}
|
||||
|
||||
if ($match_hist{$vertex} eq $new_status)
|
||||
{
|
||||
#state didn't change -- valid result
|
||||
print "$vertex remained unchanged.\n" if ($verbose > 0);
|
||||
} else
|
||||
{
|
||||
#state changed
|
||||
if ($legality eq "legal")
|
||||
{
|
||||
#legal state change
|
||||
if ($verbose > 1)
|
||||
{
|
||||
print "Legal state change:\n";
|
||||
print "Games remaining: $games\n";
|
||||
print "Move: $movenum\n";
|
||||
print "Vertex: $vertex\n";
|
||||
print "Old Status: $old_status\n";
|
||||
print "New Status: $new_status\n";
|
||||
print "\n";
|
||||
}
|
||||
} else
|
||||
{
|
||||
#illegal state change -- alive to dead or vice versa
|
||||
print "Illegal state change:\n";
|
||||
print "Games remaining: $games\n";
|
||||
print "Move: $movenum\n";
|
||||
print "Vertex: $vertex\n";
|
||||
print "Old Status: $old_status\n";
|
||||
print "New Status: $new_status\n";
|
||||
print "\n";
|
||||
|
||||
#now print gtp output
|
||||
#FIXME: doesn't work with --loadsgf because we don't have
|
||||
#the move list available (it's hidden by using GTP loadsgf).
|
||||
#FIXME: currently, only produces GTP output for one transition
|
||||
#per move. This is because we have to finish parsing the
|
||||
#entire output of dragon_status before dealing with finding
|
||||
#missed attacks. Using arrays instead would fix it.
|
||||
if ($legality eq "killed" and !defined($loadfile)) {
|
||||
#The type we deal with now.
|
||||
#FIXME: check for defensive errors too.
|
||||
$il_move = $move;
|
||||
$il_vertex = $vertex;
|
||||
}
|
||||
}
|
||||
$match_hist{$vertex} = $new_status;
|
||||
}
|
||||
} continue {
|
||||
chop($line = <$out>);
|
||||
}
|
||||
|
||||
if ($il_move ne "") {
|
||||
print "attempting gtp output.\n";
|
||||
#undo the move, check owl_does_attack
|
||||
#and owl_attack, if they disagree,
|
||||
#output a regression test.
|
||||
print $in "undo\n";
|
||||
eat_no_response($out);
|
||||
my $oa_result = "";
|
||||
my $oda_result = "";
|
||||
print $in "owl_attack $il_vertex\n";
|
||||
$oa_result = eat_one_line($out);
|
||||
print "owl_attack $il_vertex\: $oa_result\n";
|
||||
print $in "owl_does_attack $il_move $il_vertex\n";
|
||||
$oda_result = eat_one_line($out);
|
||||
print "owl_does_attack $il_move $il_vertex\: $oda_result\n";
|
||||
|
||||
#now try to do something with it
|
||||
if ($oa_result eq "= 0" and $oda_result ne "= 0") {
|
||||
print "found a missed attack.\n\n";
|
||||
print "loadsgf $sgffile $movenum\n";
|
||||
print "owl_attack $il_vertex\n";
|
||||
print "#$oa_result\n";
|
||||
print "#? [1 $move]*\n\n";
|
||||
} else {
|
||||
print "no missed attack found.\n\n";
|
||||
}
|
||||
|
||||
#cancel the undo
|
||||
my $last_played = "black";
|
||||
if ($toplay eq "B") { $last_played = "white"; }
|
||||
print $in "genmove_$last_played\n";
|
||||
eat_move($out);
|
||||
}
|
||||
|
||||
print "\n" if ($verbose > 0);
|
||||
}
|
||||
|
435
gnugo/interface/gtp_examples/metamachine.c
Normal file
435
gnugo/interface/gtp_examples/metamachine.c
Normal file
@ -0,0 +1,435 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
|
||||
* http://www.gnu.org/software/gnugo/ for more information. *
|
||||
* *
|
||||
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
|
||||
* 2008 and 2009 by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation - version 3 *
|
||||
* or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License in file COPYING for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public *
|
||||
* License along with this program; if not, write to the Free *
|
||||
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
|
||||
* Boston, MA 02111, USA. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/*
|
||||
* This program sits between a GTP client and a GTP engine,
|
||||
* passing commands back and forth and modifying them in
|
||||
* some cases.
|
||||
*
|
||||
* To the client it appears to be a GTP engine.
|
||||
*
|
||||
* stdin pipe a
|
||||
* GTP client ----> metamachine -----> GTP engine
|
||||
* <---- <-----
|
||||
* stdout pipe b
|
||||
*
|
||||
* Most commands are passed verbatim to the engine. The
|
||||
* exception is gg_genmove, which is intercepted then
|
||||
* processed differently. The top two moves are both
|
||||
* tried, the position evaluated by estimate_score,
|
||||
* and the move yielding the higher score is selected.
|
||||
*
|
||||
* Usage: no arguments gives normal GTP behavior.
|
||||
* 'metamachine --debug' sends diagnostics to stderr. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
void error(const char *msg);
|
||||
void gprintf(FILE *outputfile, const char *fmt, ...);
|
||||
void trace(const char *fmt, ...);
|
||||
void tell_gnugo(char *gnugo_line, const char *msg);
|
||||
|
||||
int boardsize = 19;
|
||||
char delimiters[] = " \t\r\n";
|
||||
char gnugo_line[128], client_line[128];
|
||||
FILE *to_gnugo_stream, *from_gnugo_stream;
|
||||
|
||||
int debug = 0;
|
||||
|
||||
#define EMPTY 0
|
||||
#define WHITE 1
|
||||
#define BLACK 2
|
||||
|
||||
#define GTP_BUFSIZE 1000
|
||||
|
||||
void ask_gnugo(char *line, int verbose, const char *msg);
|
||||
|
||||
int
|
||||
main(int argc, char *const *argv)
|
||||
{
|
||||
int pfd_a[2];
|
||||
int pfd_b[2];
|
||||
int id;
|
||||
int k;
|
||||
char command[GTP_BUFSIZE];
|
||||
|
||||
for (k = 1; k < argc; k++)
|
||||
if (argc > 1 && strstr(argv[k], "debug"))
|
||||
debug = 1;
|
||||
if (pipe(pfd_a) == -1)
|
||||
error("can't open pipe a");
|
||||
if (pipe(pfd_b) == -1)
|
||||
error("can't open pipe b");
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
error("fork failed (try chopsticks)");
|
||||
case 0:
|
||||
/* Attach pipe a to stdin */
|
||||
if (dup2(pfd_a[0], 0) == -1)
|
||||
error("dup pfd_a[0] failed");
|
||||
/* attach pipe b to stdout" */
|
||||
if (dup2(pfd_b[1], 1) == -1)
|
||||
error("dup pfd_b[1] failed");
|
||||
execlp("gnugo", "gnugo", "--mode", "gtp", "--quiet", NULL);
|
||||
error("execlp failed");
|
||||
}
|
||||
/* Attach pipe a to to_gnugo_stream */
|
||||
to_gnugo_stream = fdopen(pfd_a[1], "w");
|
||||
/* Attach pipe b to from_gnugo_stream */
|
||||
from_gnugo_stream = fdopen(pfd_b[0], "r");
|
||||
|
||||
while (1) {
|
||||
char *p;
|
||||
int n;
|
||||
|
||||
if (!fgets(client_line, GTP_BUFSIZE, stdin)
|
||||
|| (strstr(client_line, "quit") == client_line)) {
|
||||
tell_gnugo("quit\n", "a");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* remove comments */
|
||||
if ((p = strchr(client_line, '#')) != NULL)
|
||||
*p = 0;
|
||||
|
||||
p = client_line;
|
||||
|
||||
/* Look for an identification number. */
|
||||
if (sscanf(p, "%d%n", &id, &n) == 1)
|
||||
p += n;
|
||||
else
|
||||
id = -1; /* No identification number. */
|
||||
trace("id = %d\n", id);
|
||||
|
||||
/* Look for command name. */
|
||||
if (sscanf(p, " %s %n", command, &n) < 1)
|
||||
continue; /* Whitespace only on this line, ignore. */
|
||||
p += n;
|
||||
trace("command: %s\n", command);
|
||||
|
||||
if (!strncmp(command, "boardsize", 9)) {
|
||||
char *token;
|
||||
tell_gnugo(client_line, "b");
|
||||
ask_gnugo(gnugo_line, 1, "1");
|
||||
|
||||
token = strtok(client_line, delimiters);
|
||||
token = strtok(NULL, delimiters);
|
||||
boardsize = atoi(token);
|
||||
}
|
||||
else if (!strncmp(command, "gg_genmove", 10)) {
|
||||
int move_i[10], move_j[10];
|
||||
float move_value[10], position_value[10];
|
||||
int moves_considered;
|
||||
int k;
|
||||
char *token;
|
||||
int line_length = 0;
|
||||
int color;
|
||||
|
||||
if (strstr(client_line, "black"))
|
||||
color = BLACK;
|
||||
else if (strstr(client_line, "white"))
|
||||
color = WHITE;
|
||||
else {
|
||||
color = EMPTY;
|
||||
printf("?\n\n");
|
||||
}
|
||||
|
||||
if (color == BLACK)
|
||||
tell_gnugo("top_moves_black\n", "c");
|
||||
else
|
||||
tell_gnugo("top_moves_white\n", "d");
|
||||
|
||||
ask_gnugo(gnugo_line, 0, "2");
|
||||
token = strtok(gnugo_line, delimiters);
|
||||
for (k = 0; k < 10; k++) {
|
||||
move_i[k] = -1;
|
||||
move_j[k] = -1;
|
||||
move_value[k] = 0.0;
|
||||
}
|
||||
for (k = 0; k < 10; k++) {
|
||||
token = strtok(NULL, delimiters);
|
||||
if (!token)
|
||||
break;
|
||||
string_to_location(boardsize, token, move_i+k, move_j+k);
|
||||
token = strtok(NULL, delimiters);
|
||||
if (!token)
|
||||
break;
|
||||
sscanf(token, "%f", move_value+k);
|
||||
trace("move %d: %m valued %f\n", k,
|
||||
move_i[k], move_j[k], move_value[k]);
|
||||
}
|
||||
moves_considered = k;
|
||||
if (debug)
|
||||
fprintf(stderr, "moves considered: %d\n",
|
||||
moves_considered);
|
||||
for (k = 0; k < 2 && k < moves_considered; k++) {
|
||||
float upper, lower;
|
||||
int n;
|
||||
|
||||
trace("%s %m\n", color == BLACK ? "black" : "white",
|
||||
move_i[k], move_j[k]);
|
||||
gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
|
||||
move_i[k], move_j[k]);
|
||||
fflush(to_gnugo_stream);
|
||||
ask_gnugo(gnugo_line, 0, "3");
|
||||
tell_gnugo("estimate_score\n", "e");
|
||||
ask_gnugo(gnugo_line, 0, "4");
|
||||
strtok(gnugo_line, "()\n");
|
||||
token = strtok(NULL, "()\n");
|
||||
trace("%s\n", token);
|
||||
sscanf(token, "upper bound: %f, lower: %f%n",
|
||||
&upper, &lower, &n);
|
||||
if (n < 2)
|
||||
error("can't read territory");
|
||||
trace("upper %f, lower %f\n", upper, lower);
|
||||
tell_gnugo("undo\n", "f");
|
||||
ask_gnugo(gnugo_line, 0, "5");
|
||||
fflush(stdout);
|
||||
if (color == BLACK)
|
||||
position_value[k] = - upper;
|
||||
else
|
||||
position_value[k] = lower;
|
||||
trace("position value %f\n", position_value[k]);
|
||||
}
|
||||
if (moves_considered == 0) {
|
||||
if (id == -1)
|
||||
gprintf(stdout, "= PASS\n\n");
|
||||
else
|
||||
gprintf(stdout, "=%d PASS\n\n", id);
|
||||
fflush(stdout);
|
||||
}
|
||||
else if (moves_considered == 1
|
||||
|| position_value[0] > position_value[1]) {
|
||||
gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
|
||||
move_i[0], move_j[0]);
|
||||
ask_gnugo(gnugo_line, 0, "6");
|
||||
if (id == -1)
|
||||
gprintf(stdout, "= %m\n\n", move_i[0], move_j[0]);
|
||||
else
|
||||
gprintf(stdout, "=%d %m\n\n", id, move_i[0], move_j[0]);
|
||||
fflush(stdout);
|
||||
}
|
||||
else {
|
||||
gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
|
||||
move_i[1], move_j[1]);
|
||||
ask_gnugo(gnugo_line, 0, "7");
|
||||
if (id == -1)
|
||||
gprintf(stdout, "= %m\n\n", move_i[1], move_j[1]);
|
||||
else
|
||||
gprintf(stdout, "=%d %m\n\n", id, move_i[1], move_j[1]);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tell_gnugo(client_line, "g");
|
||||
ask_gnugo(gnugo_line, 1, "8");
|
||||
}
|
||||
/* loadsgf commands could change the boardsize, so we get
|
||||
* it from the engine, after the command is run. */
|
||||
if (!strncmp(command, "loadsgf", 7)) {
|
||||
tell_gnugo("query_boardsize\n", "i");
|
||||
ask_gnugo(gnugo_line, 0, "10");
|
||||
if (!sscanf(gnugo_line, "= %d", &boardsize))
|
||||
error("can't get boardsize");
|
||||
trace("setting boardsize %d\n", boardsize);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* bark and barf */
|
||||
|
||||
void
|
||||
error(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "metamachine: %s\n", msg);
|
||||
tell_gnugo("quit\n", msg);
|
||||
abort();
|
||||
}
|
||||
|
||||
/* Send a GTP command to the engine. */
|
||||
|
||||
void
|
||||
tell_gnugo(char *gnugo_line, const char *msg)
|
||||
{
|
||||
gprintf(to_gnugo_stream, gnugo_line);
|
||||
fflush(to_gnugo_stream);
|
||||
if (debug) {
|
||||
fprintf(stderr, "%s: %s", msg, gnugo_line);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Obtains the engine's response to a GTP command. If verbose is true,
|
||||
* the reply is echoed to stdout.
|
||||
*/
|
||||
|
||||
void
|
||||
ask_gnugo(char *gnugo_line, int verbose, const char *msg)
|
||||
{
|
||||
int line_length = 0;
|
||||
char line[GTP_BUFSIZE];
|
||||
|
||||
while (line_length != 1) {
|
||||
if (!fgets(line, 128, from_gnugo_stream))
|
||||
error("can't get response");
|
||||
line_length = strlen(line);
|
||||
if (line_length > 1
|
||||
&& (line[0] == '=' || line[0] == '?'))
|
||||
strncpy(gnugo_line, line, 128);
|
||||
if (verbose)
|
||||
printf(line);
|
||||
if (debug)
|
||||
fprintf(stderr, "%s: %s\n", msg, gnugo_line);
|
||||
}
|
||||
if (verbose)
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
|
||||
/* Adapted from GNU Go. Formatted output with %m format. */
|
||||
|
||||
void
|
||||
gprintf(FILE *outputfile, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vgprintf(outputfile, fmt, ap);
|
||||
fflush(outputfile);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* print diagnostic */
|
||||
|
||||
void
|
||||
trace(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
if (debug) {
|
||||
va_start(ap, fmt);
|
||||
vgprintf(stderr, fmt, ap);
|
||||
fflush(stderr);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
vgprintf(FILE *outputfile, const char *fmt, va_list ap)
|
||||
{
|
||||
for ( ; *fmt; ++fmt) {
|
||||
if (*fmt == '%') {
|
||||
switch (*++fmt) {
|
||||
case 'c':
|
||||
{
|
||||
/* rules of promotion => passed as int, not char */
|
||||
|
||||
int c = va_arg(ap, int);
|
||||
putc(c, outputfile);
|
||||
break;
|
||||
}
|
||||
case 'd':
|
||||
{
|
||||
int d = va_arg(ap, int);
|
||||
fprintf(outputfile, "%d", d);
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
{
|
||||
double f = va_arg(ap, double); /* passed as double, not float */
|
||||
fprintf(outputfile, "%.2f", f);
|
||||
break;
|
||||
}
|
||||
case 's':
|
||||
{
|
||||
char *s = va_arg(ap, char *);
|
||||
fputs(s, outputfile);
|
||||
break;
|
||||
}
|
||||
case 'm':
|
||||
case 'M':
|
||||
{
|
||||
char movename[4];
|
||||
int m = va_arg(ap, int);
|
||||
int n = va_arg(ap, int);
|
||||
if (m == -1 && n == -1)
|
||||
fputs("PASS", outputfile);
|
||||
else if (m < 0 || n < 0 || m >= 19 || n >= 19)
|
||||
fprintf(outputfile, "[%d,%d]", m, n);
|
||||
else {
|
||||
/* Generate the move name. */
|
||||
if (n < 8)
|
||||
movename[0] = n + 65;
|
||||
else
|
||||
movename[0] = n + 66;
|
||||
if (*fmt == 'm')
|
||||
sprintf(movename+1, "%d", boardsize-m);
|
||||
else
|
||||
sprintf(movename+1, "%-2d", boardsize-m);
|
||||
fputs(movename, outputfile);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
fprintf(outputfile, "\n\nUnknown format character: '%c'\n", *fmt);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
putc(*fmt, outputfile);
|
||||
}
|
||||
fflush(outputfile);
|
||||
}
|
||||
|
||||
/* Extracts coordinates from a location in algebraic notation */
|
||||
|
||||
int
|
||||
string_to_location(int boardsize, char *str, int *m, int *n)
|
||||
{
|
||||
if (*str == '\0')
|
||||
return 0;
|
||||
|
||||
if (!isalpha((int) *str))
|
||||
return 0;
|
||||
*n = tolower((int) *str) - 'a';
|
||||
if (tolower((int) *str) >= 'i')
|
||||
--*n;
|
||||
if (*n < 0 || *n > boardsize - 1)
|
||||
return 0;
|
||||
|
||||
if (!isdigit((int) *(str+1)))
|
||||
return 0;
|
||||
*m = boardsize - atoi(str + 1);
|
||||
if (*m < 0 || *m > boardsize - 1)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
119
gnugo/interface/gtp_examples/sgf2tst
Normal file
119
gnugo/interface/gtp_examples/sgf2tst
Normal file
@ -0,0 +1,119 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
|
||||
|
||||
|
||||
# This is a script which converts a game record annotated with test cases to
|
||||
# the *.tst format.
|
||||
#
|
||||
# The script currently only processes SGF files without variations, but that's
|
||||
# quite useful enough when annotating games.
|
||||
#
|
||||
# All GTP commands are put into the SGF comment field. The comment field is
|
||||
# interpretted in order:
|
||||
#
|
||||
# 1) Lines starting with "#" are copied into the tst file.
|
||||
# 2) owl_attack, owl_defend, attack, defend, and eval_eye commands can
|
||||
# be put in such as:
|
||||
# owl_attack A1
|
||||
# 1 G2
|
||||
# 3) Otherwise, a single line is interpreted as the correct answer, with
|
||||
# the appropriate "gg_genmove" directive added automatically.
|
||||
#
|
||||
# See regression/trevora.tst for examples. The sgf files for this test
|
||||
# are in regression/games/trevor/auto/a??.sgf
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
local $/;
|
||||
undef $/;
|
||||
|
||||
my $autoprobnum = 100;
|
||||
my $increment = 10;
|
||||
|
||||
while (<>) {
|
||||
my $content = $_;
|
||||
if ($content !~ /C\[/) {
|
||||
print STDERR "Warning : $ARGV : No comments.\n";
|
||||
next;
|
||||
}
|
||||
|
||||
print "\n\n# $ARGV problems:\n\n";
|
||||
|
||||
$content =~ s/^\(;//;
|
||||
$content .= ';';
|
||||
|
||||
my $DEBUG = 0;
|
||||
|
||||
my $i=0;
|
||||
my $done=0;
|
||||
while ($content =~ /(.*?);/sg && ($done=1)) { # for each node.
|
||||
$i++;
|
||||
my $node = $1;
|
||||
print "CONTENT:'$content':CONTENT\n\n" if $DEBUG;
|
||||
print "NODE:'$node':NODE\n\n" if $DEBUG;
|
||||
next if $node !~ /C\[(.*)\]/s ;
|
||||
my $comments = "";
|
||||
my $command = "";
|
||||
my $comment = $1;
|
||||
my ($othercolor) = $node =~ /(W|B)\[/ or die "No W or B move here: $ARGV($i): $node";
|
||||
while ($comment =~ /^(.*)$/mg) { # i.e. for each line of comment
|
||||
my $line = $1;
|
||||
$line =~ s/\s*$//;
|
||||
$line =~ s/^\s*//;
|
||||
if ($line =~ /^#/) {
|
||||
$comments .= "$line\n";
|
||||
next;
|
||||
}
|
||||
$command .= "loadsgf $ARGV $i\n";
|
||||
my $probnum = $autoprobnum;
|
||||
if ($line =~ /^([0-9]*)\s*((?:owl_attack|attack|owl_defend|defend|eval_eye).*)$/) {
|
||||
if ($1 eq "") {
|
||||
$probnum = $autoprobnum;
|
||||
} else {
|
||||
$probnum = $1;
|
||||
$autoprobnum -= $increment;
|
||||
}
|
||||
|
||||
$command .= "$probnum $2\n"; #ah, this line is a specific gtp command.
|
||||
$comment =~ /^(.*)$/mg; #read next line for answer.
|
||||
$line = $1;
|
||||
$line =~ s/\s*$//;
|
||||
$line =~ s/^\s*//;
|
||||
} else {
|
||||
$command .= "$probnum gg_genmove " . ($othercolor eq 'B' ? 'white' : 'black') . "\n";
|
||||
}
|
||||
$autoprobnum += $increment;
|
||||
$command .= "#? [$line]*\n";
|
||||
print $command if $DEBUG;
|
||||
}
|
||||
print "$comments$command\n\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
289
gnugo/interface/gtp_examples/ttgo.pm
Normal file
289
gnugo/interface/gtp_examples/ttgo.pm
Normal file
@ -0,0 +1,289 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
|
||||
package ttgo;
|
||||
require Exporter;
|
||||
|
||||
use strict;
|
||||
|
||||
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT = qw(ttNewGame ttShowBoard ttPlaceStone ttGetBoard ttScore);
|
||||
|
||||
my @bd = ();
|
||||
my @sbd = (); # working board
|
||||
my $white = 1;
|
||||
my $black = 2;
|
||||
my $bs;
|
||||
my @letter = 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 );
|
||||
my %tocol = ( 'b' => 2, 'B' => 2, 'w' => 1, 'W' => 1 );
|
||||
my %toix = ();
|
||||
foreach my $ix (0 .. $#letter) {
|
||||
$toix{ $letter[$ix] } = $ix;
|
||||
}
|
||||
|
||||
my %hashed_boards = (); # for convenient rep testing
|
||||
my @all_boards = (); # for move takebacks
|
||||
|
||||
|
||||
my %tovisual = ( 2 => 'W', 1 => 'B', 0 => '+' );
|
||||
|
||||
my @dir = ();
|
||||
|
||||
|
||||
|
||||
sub ttNewGame
|
||||
{
|
||||
($bs) = @_;
|
||||
|
||||
my $s = ($bs+2) * ($bs+1);
|
||||
|
||||
foreach my $i (0 .. $s-1) {
|
||||
$bd[$i] = 3; # w+b
|
||||
}
|
||||
|
||||
foreach my $x (0 .. $bs - 1) {
|
||||
foreach my $y (0 .. $bs - 1) {
|
||||
$bd[ ($y+1) * ($bs+1) + $x ] = 0; # empty
|
||||
}
|
||||
}
|
||||
|
||||
@dir = ();
|
||||
|
||||
$dir[0] = -1;
|
||||
$dir[1] = 1;
|
||||
$dir[2] = $bs + 1;
|
||||
$dir[3] = -($bs + 1);
|
||||
|
||||
push( @all_boards, join(',', @bd) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub ttPlaceStone
|
||||
{
|
||||
my ($c, $loc) = @_;
|
||||
|
||||
my @prev_board = @bd; # to take back if needed
|
||||
$hashed_boards{join(',',@prev_board)} = 1; # hash previous board
|
||||
|
||||
if ($loc eq 'PASS') {
|
||||
return(0);
|
||||
}
|
||||
|
||||
$loc =~ /^(.)(.*)/;
|
||||
my $y = $bs - $2;
|
||||
my $x = $toix{$1};
|
||||
|
||||
my $sq = ($y+1) * ($bs+1) + $x;
|
||||
|
||||
|
||||
# occupied?
|
||||
# =========
|
||||
if ($bd[ ($y+1) * ($bs+1) + $x ] != 0) {
|
||||
print "Illegal move, square occupied\n";
|
||||
return(1);
|
||||
}
|
||||
|
||||
# Make move
|
||||
# =========
|
||||
$bd[$sq] = $tocol{$c};
|
||||
|
||||
# did we capture anything?
|
||||
# ========================
|
||||
my $cc = $tocol{$c}; # current color
|
||||
my $cap = 0;
|
||||
foreach my $d (@dir) {
|
||||
if ($bd[$sq+$d] == (3 ^ $cc)) {
|
||||
@sbd = @bd;
|
||||
my $lc = lib_count( 3 ^ $cc, $sq + $d );
|
||||
if ($lc == 0) {
|
||||
$cap = 1;
|
||||
print "Capture possible\n";
|
||||
capture( 3 ^ $cc, $sq+$d );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if capture not possible, it might be suicide
|
||||
# ============================================
|
||||
|
||||
if (!$cap) {
|
||||
$bd[$sq] = 0; # make it empty again
|
||||
@sbd = @bd;
|
||||
$sbd[$sq] = $tocol{$c};
|
||||
my $lc = lib_count($tocol{$c}, $sq );
|
||||
print "liberty count = $lc\n";
|
||||
if ($lc == 0) {
|
||||
print "Illegal move, suicide!\n";
|
||||
return(2);
|
||||
}
|
||||
# Make move
|
||||
# =========
|
||||
$bd[$sq] = $tocol{$c};
|
||||
}
|
||||
|
||||
|
||||
if ( defined( $hashed_boards{ join(',',@bd) } ) ) {
|
||||
print "Illegal move, repeated positions\n";
|
||||
# @bd = @prev_board;
|
||||
# return(0);
|
||||
}
|
||||
|
||||
push( @all_boards, join(',', @bd) );
|
||||
|
||||
ttScore();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sub lib_count
|
||||
{
|
||||
my ($c, $sq) = @_;
|
||||
my $count = 0;
|
||||
|
||||
foreach my $d (@dir) {
|
||||
if ($sbd[ $sq + $d ] == 0) {
|
||||
$count++;
|
||||
$sbd[$sq + $d ] = 9;
|
||||
next;
|
||||
}
|
||||
if ($sbd[ $sq + $d ] == 3) { next; }
|
||||
if ($sbd[ $sq + $d ] == $c) {
|
||||
$sbd[$sq + $d ] = 9;
|
||||
$count += lib_count( $c, $sq + $d );
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
|
||||
sub capture
|
||||
{
|
||||
my ($c, $sq) = @_;
|
||||
|
||||
$bd[$sq] = 0;
|
||||
foreach my $d (@dir) {
|
||||
if ( $bd[ $sq + $d ] == $c ) {
|
||||
capture( $c, $sq + $d );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub ttShowBoard
|
||||
{
|
||||
foreach my $y (0 .. $bs + 1) {
|
||||
foreach my $x (0 .. $bs) {
|
||||
printf ( "%2d", $bd[ $y * ($bs+1) + $x ] );
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
print "\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub ttGetBoard
|
||||
{
|
||||
my @tbd = ();
|
||||
|
||||
foreach my $y (0 .. $bs-1) {
|
||||
foreach my $x (0 .. $bs-1) {
|
||||
push @tbd, $tovisual{ $bd[ ($y+1) * ($bs+1) + $x ] };
|
||||
}
|
||||
}
|
||||
return @tbd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub ttScore
|
||||
{
|
||||
@sbd = @bd;
|
||||
|
||||
my $who = 0;
|
||||
my @ter = (0, 0, 0);
|
||||
my @stc = (0, 0, 0);
|
||||
|
||||
foreach my $sq (0 .. (($bs+2) * ($bs+1))-1 ) {
|
||||
if ( $bd[$sq]==1 || $bd[$sq]==2 ) { $stc[$bd[$sq]] ++; }
|
||||
if ($sbd[$sq] == 0) {
|
||||
my ($cnt, $who) = count_space($sq);
|
||||
if ($who == 1 || $who == 2) {
|
||||
$ter[$who] += $cnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print "white stones=$stc[$white] territory=$ter[$white]\n";
|
||||
print "black stones=$stc[$black] territory=$ter[$black]\n";
|
||||
|
||||
return( ($stc[$black] + $ter[$black])-($stc[$white] + $ter[$white]) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# return count
|
||||
# ------------
|
||||
sub count_space
|
||||
{
|
||||
my ($sq) = @_;
|
||||
my $count = 0;
|
||||
my $who = 0;
|
||||
|
||||
if ( $sbd[$sq] == 9 || $sbd[$sq] == 3) {
|
||||
return (0,0);
|
||||
} elsif ( $sbd[$sq] != 0 ) {
|
||||
$who |= $sbd[$sq];
|
||||
return( 0, $who);
|
||||
} else { # must be zero
|
||||
$count++;
|
||||
$sbd[$sq] = 9; # mark it
|
||||
foreach my $d (@dir) {
|
||||
my ($c, $w) = count_space( $sq + $d );
|
||||
$count += $c;
|
||||
$who |= $w;
|
||||
}
|
||||
}
|
||||
return ( $count, $who );
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
||||
|
||||
|
||||
|
||||
|
574
gnugo/interface/gtp_examples/twogtp
Normal file
574
gnugo/interface/gtp_examples/twogtp
Normal file
@ -0,0 +1,574 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 #
|
||||
# 2008 and 2009 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
|
||||
package GTP;
|
||||
|
||||
use strict;
|
||||
|
||||
my $debug = 0;
|
||||
|
||||
sub exec_cmd {
|
||||
my $hin = shift;
|
||||
my $hout = shift;
|
||||
my $cmd = shift;
|
||||
|
||||
# send the command to the GTP program
|
||||
|
||||
print $hin "$cmd\n";
|
||||
|
||||
# parse the response of the GTP program
|
||||
|
||||
my $line;
|
||||
my $repchar;
|
||||
my $result = "ERROR";
|
||||
$line = <$hout>;
|
||||
print STDERR "$hin 1:$line" if ($debug);
|
||||
return "ERROR" unless (defined $line);
|
||||
$line =~ s/\s*$//;
|
||||
($repchar, $result) = split(/\s*/, $line, 2);
|
||||
print STDERR "$hin 2:repchar $repchar\n" if ($debug);
|
||||
print STDERR "$hin 3:result $result\n" if ($debug);
|
||||
|
||||
$line = <$hout>;
|
||||
while (!($line =~ /^\s*$/)) {
|
||||
$result .= $line;
|
||||
$line = <$hout>;
|
||||
}
|
||||
print STDERR "$hin 4:$line" if ($debug);
|
||||
if ($repchar eq '?') {
|
||||
return "ERROR";
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub standard_to_sgf {
|
||||
|
||||
my $size = shift;
|
||||
my $board_coords = shift;
|
||||
$board_coords =~ tr/A-Z/a-z/;
|
||||
return "" if ($board_coords eq "pass");
|
||||
my $first = substr($board_coords, 0, 1);
|
||||
my $number = substr($board_coords, 1);
|
||||
my $sgffirst;
|
||||
if ($first gt 'i') {
|
||||
$sgffirst = chr(ord($first) - 1);
|
||||
} else {
|
||||
$sgffirst = $first;
|
||||
}
|
||||
my $sgfsecond = chr(ord('a') + $size - $number);
|
||||
# print "$board_coords, $sgffirst, $number, $sgfsecond\n";
|
||||
return $sgffirst . $sgfsecond;
|
||||
}
|
||||
|
||||
package GTP::Player;
|
||||
|
||||
use strict;
|
||||
use Class::Struct;
|
||||
use FileHandle;
|
||||
use IPC::Open2;
|
||||
|
||||
struct('GTP::Player' => {
|
||||
'in' => 'FileHandle',
|
||||
'out' => 'FileHandle',
|
||||
'gtp_version' => '$',
|
||||
}
|
||||
);
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub initialize {
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
|
||||
my $pid = open2($self->{out}, $self->{in}, $cmd);
|
||||
$self->{gtp_version} = GTP::exec_cmd($self->{in},
|
||||
$self->{out}, "protocol_version");
|
||||
$self->{gtp_version} eq 1 or $self->{gtp_version} eq 2 or
|
||||
die "Unsupported gtp version $self->{gtp_version}\n";
|
||||
return $pid;
|
||||
}
|
||||
|
||||
sub genmove {
|
||||
my $self = shift;
|
||||
my $color = shift;
|
||||
|
||||
my $cmd;
|
||||
if ($self->{gtp_version} eq 1) {
|
||||
$cmd = "genmove_";
|
||||
} else {
|
||||
$cmd = "genmove ";
|
||||
}
|
||||
if ($color =~ /^b/i) {
|
||||
$cmd .= "black";
|
||||
} elsif ($color =~ /^w/i) {
|
||||
$cmd .= "white";
|
||||
} else {
|
||||
die "Illegal color $color\n";
|
||||
}
|
||||
my $move = GTP::exec_cmd($self->{in}, $self->{out}, $cmd);
|
||||
}
|
||||
|
||||
sub black {
|
||||
my $self = shift;
|
||||
my $move = shift;
|
||||
my $cmd;
|
||||
if ($self->{gtp_version} eq 1) {
|
||||
$cmd = "black ";
|
||||
} else {
|
||||
$cmd = "play black ";
|
||||
}
|
||||
|
||||
GTP::exec_cmd($self->{in}, $self->{out}, $cmd . $move);
|
||||
}
|
||||
|
||||
sub white {
|
||||
my $self = shift;
|
||||
my $move = shift;
|
||||
my $cmd;
|
||||
if ($self->{gtp_version} eq 1) {
|
||||
$cmd = "white ";
|
||||
} else {
|
||||
$cmd = "play white ";
|
||||
}
|
||||
|
||||
GTP::exec_cmd($self->{in}, $self->{out}, $cmd . $move);
|
||||
}
|
||||
|
||||
sub komi {
|
||||
my $self = shift;
|
||||
my $komi = shift;
|
||||
|
||||
GTP::exec_cmd($self->{in}, $self->{out}, "komi $komi");
|
||||
}
|
||||
|
||||
sub boardsize {
|
||||
my $self = shift;
|
||||
my $size = shift;
|
||||
|
||||
GTP::exec_cmd($self->{in}, $self->{out}, "boardsize $size");
|
||||
}
|
||||
|
||||
sub clear_board {
|
||||
my $self = shift;
|
||||
|
||||
GTP::exec_cmd($self->{in}, $self->{out}, "clear_board");
|
||||
}
|
||||
|
||||
sub handicap {
|
||||
my $self = shift;
|
||||
my $handicap = shift;
|
||||
|
||||
my $stones;
|
||||
$stones = GTP::exec_cmd($self->{in}, $self->{out}, "handicap $handicap");
|
||||
return split(' ', $stones);
|
||||
}
|
||||
|
||||
sub fixed_handicap {
|
||||
my $self = shift;
|
||||
my $handicap = shift;
|
||||
|
||||
my $stones;
|
||||
$stones = GTP::exec_cmd($self->{in}, $self->{out}, "fixed_handicap $handicap");
|
||||
return split(' ', $stones);
|
||||
}
|
||||
|
||||
sub quit {
|
||||
my $self = shift;
|
||||
|
||||
$self->{in}->print("quit\n");
|
||||
}
|
||||
|
||||
sub showboard {
|
||||
my $self = shift;
|
||||
my $board;
|
||||
|
||||
$board = GTP::exec_cmd($self->{in}, $self->{out}, "showboard");
|
||||
|
||||
if ($self->{gtp_version} eq 2) {
|
||||
print $board;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_random_seed {
|
||||
my $self = shift;
|
||||
|
||||
my $ret = GTP::exec_cmd($self->{in}, $self->{out}, "get_random_seed");
|
||||
if ($ret eq "ERROR") {
|
||||
return "unknown";
|
||||
}
|
||||
my ($result, $rest) = split(' ', $ret, 2);
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub get_program_name {
|
||||
my $self = shift;
|
||||
|
||||
my $name = GTP::exec_cmd($self->{in}, $self->{out}, "name");
|
||||
my $version = GTP::exec_cmd($self->{in}, $self->{out}, "version");
|
||||
return "$name $version";
|
||||
}
|
||||
|
||||
sub score {
|
||||
my $self = shift;
|
||||
|
||||
return GTP::exec_cmd($self->{in}, $self->{out}, "score");
|
||||
}
|
||||
|
||||
sub final_score {
|
||||
my $self = shift;
|
||||
|
||||
my $ret = GTP::exec_cmd($self->{in}, $self->{out}, "final_score");
|
||||
my ($result, $rest) = split(' ', $ret, 2);
|
||||
return $result;
|
||||
}
|
||||
|
||||
package GTP::Game::Result;
|
||||
|
||||
use strict;
|
||||
use Class::Struct;
|
||||
use FileHandle;
|
||||
|
||||
struct('GTP::Game::Result' => {
|
||||
'resultw' => '$',
|
||||
'resultb' => '$'
|
||||
}
|
||||
);
|
||||
|
||||
package GTP::Game;
|
||||
|
||||
use strict;
|
||||
use Class::Struct;
|
||||
use FileHandle;
|
||||
|
||||
struct('GTP::Game' => {
|
||||
'black' => 'GTP::Player',
|
||||
'white' => 'GTP::Player',
|
||||
'size' => '$',
|
||||
'komi' => '$',
|
||||
'handicap' => '$',
|
||||
'handicap_stones' => '@',
|
||||
'moves' => '@',
|
||||
'result' => 'GTP::Game::Result'
|
||||
}
|
||||
);
|
||||
|
||||
my $verbose = 0;
|
||||
|
||||
sub verbose {
|
||||
my $self = shift;
|
||||
my $verbose_arg = shift;
|
||||
|
||||
$verbose = $verbose_arg;
|
||||
}
|
||||
|
||||
sub writesgf {
|
||||
my $self = shift;
|
||||
my $sgffile = shift;
|
||||
|
||||
my $size = $self->size;
|
||||
|
||||
my $handle = new FileHandle;
|
||||
$handle->open(">$sgffile") or
|
||||
die "Can't write to $sgffile\n";
|
||||
my $black_name = $self->black->get_program_name;
|
||||
my $white_name = $self->white->get_program_name;
|
||||
my $black_seed = $self->black->get_random_seed;
|
||||
my $white_seed = $self->white->get_random_seed;
|
||||
my $handicap = $self->handicap;
|
||||
my $komi = $self->komi;
|
||||
my $result = $self->{result}->resultw;
|
||||
|
||||
print $handle "(;GM[1]FF[4]RU[Japanese]SZ[$size]HA[$handicap]KM[$komi]RE[$result]\n";
|
||||
print $handle "PW[$white_name (random seed $white_seed)]PB[$black_name (random seed $black_seed)]\n";
|
||||
if ($handicap > 1) {
|
||||
for my $stone (@{$self->handicap_stones}) {
|
||||
printf $handle "AB[%s]", GTP::standard_to_sgf($self->size, $stone);
|
||||
}
|
||||
print $handle "\n";
|
||||
}
|
||||
my $toplay = $self->handicap < 2 ? 'B' : 'W';
|
||||
for my $move (@{$self->moves}) {
|
||||
my $sgfmove = GTP::standard_to_sgf($size, $move);
|
||||
print $handle ";$toplay" . "[$sgfmove]\n";
|
||||
$toplay = $toplay eq 'B' ? 'W' : 'B';
|
||||
}
|
||||
print $handle ")\n";
|
||||
$handle->close;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sub play {
|
||||
|
||||
my $self = shift;
|
||||
my $sgffile = shift;
|
||||
|
||||
my $size = $self->size;
|
||||
my $handicap = $self->handicap;
|
||||
my $komi = $self->komi;
|
||||
|
||||
print "Setting boardsize and komi for black\n" if $verbose;
|
||||
$self->black->boardsize($size);
|
||||
$self->black->clear_board();
|
||||
$self->black->komi($komi);
|
||||
|
||||
print "Setting boardsize and komi for white\n" if $verbose;
|
||||
$self->white->boardsize($size);
|
||||
$self->white->clear_board();
|
||||
$self->white->komi($komi);
|
||||
|
||||
my $pass = 0;
|
||||
my $resign = 0;
|
||||
my ($move, $toplay, $sgfmove);
|
||||
|
||||
$pass = 0;
|
||||
$#{$self->handicap_stones} = -1;
|
||||
if ($handicap < 2) {
|
||||
|
||||
$toplay = 'B';
|
||||
|
||||
} else {
|
||||
|
||||
@{$self->handicap_stones} = $self->white->fixed_handicap($handicap);
|
||||
for my $stone (@{$self->handicap_stones}) {
|
||||
$self->black->black($stone);
|
||||
}
|
||||
$toplay = 'W';
|
||||
|
||||
}
|
||||
|
||||
$#{$self->moves} = -1;
|
||||
while ($pass < 2 and $resign eq 0) {
|
||||
|
||||
if ($toplay eq 'B') {
|
||||
|
||||
$move = $self->black->genmove("black");
|
||||
if ($move eq "ERROR") {
|
||||
$self->writesgf($sgffile) if defined $sgffile;
|
||||
die "No response!";
|
||||
}
|
||||
$resign = ($move =~ /resign/i) ? 1 : 0;
|
||||
if ($resign) {
|
||||
print "Black resigns\n" if $verbose;
|
||||
} else {
|
||||
push @{$self->moves}, $move;
|
||||
print "Black plays $move\n" if $verbose;
|
||||
$pass = ($move =~ /PASS/i) ? $pass + 1 : 0;
|
||||
$self->white->black($move);
|
||||
}
|
||||
if ($verbose == 3) {
|
||||
my $black_seed = $self->black->get_random_seed;
|
||||
printf "Black seed $black_seed\n";
|
||||
}
|
||||
if ($verbose == 2) {
|
||||
$self->white->showboard;
|
||||
}
|
||||
|
||||
$toplay = 'W';
|
||||
|
||||
} else {
|
||||
|
||||
$move = $self->white->genmove("white");
|
||||
if ($move eq "ERROR") {
|
||||
$self->writesgf($sgffile) if defined $sgffile;
|
||||
die "No response!";
|
||||
}
|
||||
$resign = ($move =~ /resign/i) ? 1 : 0;
|
||||
if ($resign) {
|
||||
print "White resigns\n" if $verbose;
|
||||
} else {
|
||||
push @{$self->moves}, $move;
|
||||
print "White plays $move\n" if $verbose;
|
||||
$pass = ($move =~ /PASS/i) ? $pass + 1 : 0;
|
||||
$self->black->white($move);
|
||||
}
|
||||
if ($verbose == 3) {
|
||||
my $white_seed = $self->white->get_random_seed;
|
||||
printf "White seed $white_seed\n";
|
||||
}
|
||||
if ($verbose == 2) {
|
||||
$self->black->showboard;
|
||||
}
|
||||
$toplay = 'B';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
my $resultb;
|
||||
my $resultw;
|
||||
if ($resign) {
|
||||
$resultb = $toplay eq 'B' ? 'B+R' : 'W+R';
|
||||
$resultw = $resultb;
|
||||
} else {
|
||||
$resultw = $self->white->final_score;
|
||||
$resultb = $self->black->final_score;
|
||||
}
|
||||
if ($resultb eq $resultw) {
|
||||
print "Result: $resultw\n";
|
||||
} else {
|
||||
print "Result according to W: $resultw\n";
|
||||
print "****** according to B: $resultb\n";
|
||||
}
|
||||
$self->{result} = new GTP::Game::Result;
|
||||
$self->{result}->resultw($resultw);
|
||||
$self->{result}->resultb($resultb);
|
||||
$self->writesgf($sgffile) if defined $sgffile;
|
||||
}
|
||||
|
||||
package GTP::Match;
|
||||
|
||||
use strict;
|
||||
use Class::Struct;
|
||||
use FileHandle;
|
||||
|
||||
struct('GTP::Match' => {
|
||||
'black' => 'GTP::Player',
|
||||
'white' => 'GTP::Player',
|
||||
'size' => '$',
|
||||
'komi' => '$',
|
||||
'handicap' => '$'
|
||||
}
|
||||
);
|
||||
|
||||
sub play {
|
||||
my $self = shift;
|
||||
my $games = shift;
|
||||
my $sgffile = shift;
|
||||
|
||||
my $game = new GTP::Game;
|
||||
$game->size($self->size);
|
||||
$game->komi($self->komi);
|
||||
$game->handicap($self->handicap);
|
||||
$game->black($self->black);
|
||||
$game->white($self->white);
|
||||
$game->komi($self->komi);
|
||||
my @results;
|
||||
(my $sgffile_base = $sgffile) =~ s/\.sgf$//;
|
||||
for my $i (1..$games) {
|
||||
my $sgffile_game = sprintf "%s%03d.sgf", $sgffile_base, $i;
|
||||
$game->play($sgffile_game);
|
||||
my $result = new GTP::Game::Result;
|
||||
$result->resultb($game->{result}->resultb);
|
||||
$result->resultw($game->{result}->resultw);
|
||||
push @results, $result;
|
||||
}
|
||||
return @results;
|
||||
}
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
use FileHandle;
|
||||
|
||||
my $white;
|
||||
my $black;
|
||||
my $size = 19;
|
||||
my $games = 1;
|
||||
my $komi;
|
||||
my $handicap = 0;
|
||||
my $sgffile = "twogtp.sgf";
|
||||
|
||||
GetOptions(
|
||||
"white|w=s" => \$white,
|
||||
"black|b=s" => \$black,
|
||||
"verbose|v=i" => \$verbose,
|
||||
"komi|km=f" => \$komi,
|
||||
"handicap|ha=i" => \$handicap,
|
||||
"games|g=i" => \$games,
|
||||
"sgffile|f=s" => \$sgffile,
|
||||
"boardsize|size|s=i" => \$size
|
||||
);
|
||||
|
||||
GTP::Game->verbose($verbose);
|
||||
|
||||
my $helpstring = "
|
||||
|
||||
Run with:
|
||||
|
||||
twogtp --white \'<path to program 1> --mode gtp [program options]\' \\
|
||||
--black \'<path to program 2> --mode gtp [program options]\' \\
|
||||
[twogtp options]
|
||||
|
||||
Possible twogtp options:
|
||||
|
||||
--verbose 1 (to list moves) or --verbose 2 (to draw board)
|
||||
--komi <amount>
|
||||
--handicap <amount>
|
||||
--size <board size> (default 19)
|
||||
--games <number of games to play> (-1 to play forever)
|
||||
--sgffile <filename>
|
||||
";
|
||||
|
||||
die $helpstring unless defined $white and defined $black;
|
||||
|
||||
if (!defined $komi) {
|
||||
if ($handicap > 0) {
|
||||
$komi = 0.5;
|
||||
} else {
|
||||
$komi = 5.5;
|
||||
}
|
||||
}
|
||||
|
||||
# create GTP players
|
||||
|
||||
my $black_pl = new GTP::Player;
|
||||
$black_pl->initialize($black);
|
||||
print "Created black GTP player\n" if $verbose;
|
||||
|
||||
my $white_pl = new GTP::Player;
|
||||
$white_pl->initialize($white);
|
||||
print "Created white GTP player\n" if $verbose;
|
||||
|
||||
my $match = new GTP::Match;
|
||||
$match->white($white_pl);
|
||||
$match->black($black_pl);
|
||||
$match->size($size);
|
||||
$match->komi($komi);
|
||||
$match->handicap($handicap);
|
||||
my @results = $match->play($games, $sgffile);
|
||||
|
||||
my $i=0;
|
||||
for my $r (@results) {
|
||||
$i++;
|
||||
if ($r->resultb eq $r->resultw) {
|
||||
printf "Game $i: %s\n", $r->resultw;
|
||||
}
|
||||
else {
|
||||
printf "Game $i: %s %s\n", $r->resultb, $r->resultw;
|
||||
}
|
||||
}
|
||||
|
||||
$white_pl->quit;
|
||||
$black_pl->quit;
|
||||
|
||||
|
472
gnugo/interface/gtp_examples/twogtp-a
Normal file
472
gnugo/interface/gtp_examples/twogtp-a
Normal file
@ -0,0 +1,472 @@
|
||||
#! /usr/bin/perl -w
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 #
|
||||
# 2008 and 2009 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
#
|
||||
# Here is a small perlscript twogtp. Its purpose is to run
|
||||
# two programs against each other. Both must support the Go
|
||||
# Text Protocol. For example GNU Go 2.7.241 or higher works.
|
||||
#
|
||||
# It is easier to implement this program in gtp than gmp.
|
||||
# The script is almost trivial. It also works with cygwin on
|
||||
# windows.
|
||||
#
|
||||
# Run with:
|
||||
#
|
||||
# twogtp --white '<path to program 1> --mode gtp <options>' \
|
||||
# --black '<path to program 2> --mode gtp <options>' \
|
||||
# [twogtp options]
|
||||
#
|
||||
# Possible twogtp options:
|
||||
#
|
||||
# --verbose 1 (to list moves) or --verbose 2 (to draw board)
|
||||
# --komi <amount>
|
||||
# --handicap <amount>
|
||||
# --size <board size> (default 19)
|
||||
# --games <number of games to play> (-1 to play forever)
|
||||
# --sgffile <filename>
|
||||
#
|
||||
#
|
||||
|
||||
package TWOGTP_A;
|
||||
|
||||
use IPC::Open2;
|
||||
use Getopt::Long;
|
||||
use FileHandle;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Carp;
|
||||
|
||||
STDOUT->autoflush(1);
|
||||
|
||||
#following added globally to allow "use strict" :
|
||||
my $vertex;
|
||||
my $first;
|
||||
my $sgfmove;
|
||||
my $sgffilename;
|
||||
my $pidw;
|
||||
my $pidb;
|
||||
my $sgffile;
|
||||
my $handicap_stones;
|
||||
my $resultw;
|
||||
my $resultb;
|
||||
my @vertices;
|
||||
my $second;
|
||||
my %game_list;
|
||||
#end of "use strict" repairs
|
||||
|
||||
|
||||
my $white;
|
||||
my $black;
|
||||
my $size = 19;
|
||||
my $verbose = 0;
|
||||
my $komi;
|
||||
my $handicap = 0;
|
||||
my $games = 1;
|
||||
my $wanthelp;
|
||||
|
||||
my $helpstring = "
|
||||
|
||||
Run with:
|
||||
|
||||
twogtp --white \'<path to program 1> --mode gtp [program options]\' \\
|
||||
--black \'<path to program 2> --mode gtp [program options]\' \\
|
||||
[twogtp options]
|
||||
|
||||
Possible twogtp options:
|
||||
|
||||
--verbose 1 (to list moves) or --verbose 2 (to draw board)
|
||||
--komi <amount>
|
||||
--handicap <amount>
|
||||
--size <board size> (default 19)
|
||||
--games <number of games to play> (-1 to play forever)
|
||||
--sgffile <filename>
|
||||
--help (show this)
|
||||
|
||||
";
|
||||
|
||||
GetOptions(
|
||||
"white|w=s" => \$white,
|
||||
"black|b=s" => \$black,
|
||||
"verbose|v=i" => \$verbose,
|
||||
"komi|k=f" => \$komi,
|
||||
"handicap|h=i" => \$handicap,
|
||||
"size|boardsize|s=i" => \$size,
|
||||
"sgffile|o=s" => \$sgffilename,
|
||||
"games=i" => \$games,
|
||||
"help" => \$wanthelp,
|
||||
);
|
||||
|
||||
if ($wanthelp) {
|
||||
print $helpstring;
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
if (!$white) {
|
||||
$white = '../gnugo.exe --mode gtp --quiet';
|
||||
warn "Defaulting white to: $white";
|
||||
}
|
||||
if (!$black) {
|
||||
$black = '../gnugo.exe --mode gtp --quiet';
|
||||
warn "Defaulting black to: $black";
|
||||
}
|
||||
|
||||
die $helpstring unless defined $white and defined $black;
|
||||
|
||||
# create FileHandles
|
||||
#my $black_in;
|
||||
my $black_in = new FileHandle; # stdin of black player
|
||||
my $black_out = new FileHandle; # stdout of black player
|
||||
my $white_in = new FileHandle; # stdin of white player
|
||||
my $white_out = new FileHandle; # stdout of white player
|
||||
my $b_gtp_ver; # gtp version of black player
|
||||
my $w_gtp_ver; # gtp version of white player
|
||||
|
||||
while ($games > 0) {
|
||||
$pidb = open2($black_out, $black_in, $black);
|
||||
print "black pid: $pidb\n" if $verbose;
|
||||
$pidw = open2($white_out, $white_in, $white);
|
||||
print "white pid: $pidw\n" if $verbose;
|
||||
|
||||
$sgffile = rename_sgffile($games, $sgffilename) if defined $sgffilename;
|
||||
|
||||
if ((defined $sgffilename) && !open(SGFFILEHANDLE, ">$sgffile")) {
|
||||
printf("can't open $sgffile\n");
|
||||
undef($sgffilename);
|
||||
}
|
||||
|
||||
if (!defined $komi) {
|
||||
if ($handicap eq 0) {
|
||||
$komi = 5.5;
|
||||
}
|
||||
else {
|
||||
$komi = 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
print $black_in "protocol_version\n";
|
||||
$b_gtp_ver = eat_gtp_ver($black_out);
|
||||
print $black_in "boardsize $size\n";
|
||||
eat_no_response($black_out);
|
||||
print $black_in "clear_board\n";
|
||||
eat_no_response($black_out);
|
||||
print $black_in "komi $komi\n";
|
||||
eat_no_response($black_out);
|
||||
|
||||
print $white_in "protocol_version\n";
|
||||
$w_gtp_ver = eat_gtp_ver($white_out);
|
||||
print $white_in "boardsize $size\n";
|
||||
eat_no_response($white_out);
|
||||
print $white_in "clear_board\n";
|
||||
eat_no_response($white_out);
|
||||
print $white_in "komi $komi\n";
|
||||
eat_no_response($white_out);
|
||||
|
||||
print SGFFILEHANDLE "(;GM[1]FF[4]RU[Japanese]SZ[$size]HA[$handicap]KM[$komi]"
|
||||
if defined $sgffilename;
|
||||
|
||||
my $pass = 0;
|
||||
my ($move, $toplay);
|
||||
|
||||
if ($handicap < 2) {
|
||||
$toplay = 'B';
|
||||
}
|
||||
else {
|
||||
$toplay = 'W';
|
||||
print $black_in "fixed_handicap $handicap\n";
|
||||
$handicap_stones = eat_handicap($black_out);
|
||||
if (defined $sgffilename) {
|
||||
print SGFFILEHANDLE $handicap_stones;
|
||||
}
|
||||
print $white_in "fixed_handicap $handicap\n";
|
||||
$handicap_stones = eat_handicap($white_out);
|
||||
}
|
||||
while ($pass < 2) {
|
||||
if ($toplay eq 'B') {
|
||||
if ($b_gtp_ver eq 1) {
|
||||
print $black_in "genmove_black\n";
|
||||
} else {
|
||||
print $black_in "genmove black\n";
|
||||
}
|
||||
$move = eat_move($black_out);
|
||||
$sgfmove = standard_to_sgf($move);
|
||||
print SGFFILEHANDLE ";B[$sgfmove]\n" if defined $sgffilename;
|
||||
print "Black plays $move\n" if $verbose;
|
||||
if ($move =~ /PASS/i) {
|
||||
$pass++;
|
||||
} else {
|
||||
$pass = 0;
|
||||
}
|
||||
if ($w_gtp_ver eq 1) {
|
||||
print $white_in "black $move\n";
|
||||
} else {
|
||||
print $white_in "play black $move\n";
|
||||
}
|
||||
eat_no_response($white_out);
|
||||
if ($verbose > 1) {
|
||||
print $white_in "showboard\n";
|
||||
if ($w_gtp_ver eq 2) {
|
||||
eat_showboard($white_out);
|
||||
} else {
|
||||
eat_no_response($white_out);
|
||||
}
|
||||
}
|
||||
$toplay = 'W';
|
||||
} else {
|
||||
if ($w_gtp_ver eq 1) {
|
||||
print $white_in "genmove_white\n";
|
||||
} else {
|
||||
print $white_in "genmove white\n";
|
||||
}
|
||||
$move = eat_move($white_out);
|
||||
$sgfmove = standard_to_sgf($move);
|
||||
print SGFFILEHANDLE ";W[$sgfmove]\n" if defined $sgffilename;
|
||||
print "White plays $move\n" if $verbose;
|
||||
if ($move =~ /PASS/i) {
|
||||
$pass++;
|
||||
} else {
|
||||
$pass = 0;
|
||||
}
|
||||
if ($b_gtp_ver eq 1) {
|
||||
print $black_in "white $move\n";
|
||||
} else {
|
||||
print $black_in "play white $move\n";
|
||||
}
|
||||
eat_no_response($black_out);
|
||||
if ($verbose > 1) {
|
||||
print $black_in "showboard\n";
|
||||
if ($b_gtp_ver eq 2) {
|
||||
eat_showboard($black_out);
|
||||
} else {
|
||||
eat_no_response($black_out);
|
||||
}
|
||||
}
|
||||
$toplay = 'B';
|
||||
}
|
||||
}
|
||||
print $white_in "final_score\n";
|
||||
$resultw = eat_score($white_out);
|
||||
print "Result according to W: $resultw\n";
|
||||
print $black_in "final_score\n";
|
||||
$resultb = eat_score($black_out);
|
||||
print "Result according to B: $resultb\n";
|
||||
print $white_in "quit\n";
|
||||
print $black_in "quit\n";
|
||||
if (defined $sgffilename) {
|
||||
print "sgf file: $sgffile\n";
|
||||
print SGFFILEHANDLE ")";
|
||||
close SGFFILEHANDLE;
|
||||
$game_list{$sgffile} = $resultw . "|" . $resultb
|
||||
}
|
||||
$games-- if $games > 0;
|
||||
close $black_in;
|
||||
close $black_out;
|
||||
close $white_in;
|
||||
close $white_out;
|
||||
waitpid $pidb, 0;
|
||||
waitpid $pidw, 0;
|
||||
print "games remaining: $games\n";
|
||||
}
|
||||
|
||||
if (defined $sgffilename) {
|
||||
my $index_out = new FileHandle;
|
||||
open ($index_out, "> " . index_name($sgffilename));
|
||||
print $index_out
|
||||
"<HTML><HEAD><TITLE>game results</TITLE></HEAD>
|
||||
<BODY><H3>Game Results</H3>
|
||||
<H4>White: ".html_encode($white)."</H4>
|
||||
<H4>Black: ".html_encode($black)."</H4>
|
||||
<TABLE border=1>
|
||||
<TR>
|
||||
<TD>SGF file</TD>
|
||||
<TD>Result</TD>
|
||||
</TR>
|
||||
";
|
||||
foreach (sort by_result keys(%game_list)) {
|
||||
print $index_out "<TR><TD><A href=\"$_\">$_</A></TD>" .
|
||||
"<TD>".html_encode(game_result($_))."</TD></TR>\n";
|
||||
}
|
||||
print $index_out "</TABLE></BODY></HTML>\n";
|
||||
}
|
||||
|
||||
sub game_result {
|
||||
$_ = shift;
|
||||
$_ = $game_list{$_};
|
||||
#i.e.: B+13.5 (upper bound: -13.5, lower: -13.5)|B+13.5 (upper bound: -13.5, lower: -13.5)
|
||||
#Make sure that all 4 values are the same. I've not seen them different yet.
|
||||
#If they are ever different, need to improve the HTML output (now just -999) -
|
||||
# an explanation of the score mismatch problem would be appropriate.
|
||||
$_ =~ /^.*upper bound..([0-9+.\-]*)..lower..\1.\|.*upper bound..\1..lower..\1./;
|
||||
if (defined($1)) {
|
||||
return $1;
|
||||
} else {
|
||||
return -999;
|
||||
}
|
||||
}
|
||||
|
||||
sub by_result {
|
||||
game_result($a) <=> game_result($b) || $a cmp $b;
|
||||
}
|
||||
|
||||
sub html_encode {
|
||||
#print shift;
|
||||
my $r = shift;
|
||||
$r =~ s/&/&/g;
|
||||
$r =~ s/</</g;
|
||||
$r =~ s/>/>/g;
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub eat_no_response {
|
||||
my $h = shift;
|
||||
|
||||
# ignore empty lines
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
}
|
||||
|
||||
sub eat_move {
|
||||
my $h = shift;
|
||||
# ignore empty lines
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
if (!defined($line = <$h>)) {
|
||||
print SGFFILEHANDLE ")";
|
||||
close SGFFILEHANDLE;
|
||||
die "Engine crashed!\n";
|
||||
}
|
||||
$line =~ s/(\s|\n)*$//smg;
|
||||
}
|
||||
my ($equals, $move) = split(' ', $line, 2);
|
||||
$line = <$h>;
|
||||
defined($move) or confess "no move found: line was: '$line'";
|
||||
return $move;
|
||||
}
|
||||
|
||||
sub eat_handicap {
|
||||
my $h = shift;
|
||||
my $sgf_handicap = "AB";
|
||||
# ignore empty lines, die if process is gone
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
}
|
||||
@vertices = split(" ", $line);
|
||||
foreach $vertex (@vertices) {
|
||||
if (!($vertex eq "=")) {
|
||||
$vertex = standard_to_sgf($vertex);
|
||||
$sgf_handicap = "$sgf_handicap\[$vertex\]";
|
||||
}
|
||||
}
|
||||
return "$sgf_handicap;";
|
||||
}
|
||||
|
||||
sub eat_score {
|
||||
my $h = shift;
|
||||
# ignore empty lines, die if process is gone
|
||||
my $line = "";
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/^\s*//msg;
|
||||
$line =~ s/\s*$//msg;
|
||||
}
|
||||
$line =~ s/\s*$//;
|
||||
my ($equals, $result) = split(' ', $line, 2);
|
||||
$line = <$h>;
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub eat_gtp_ver {
|
||||
my $h = shift;
|
||||
my $line = "";
|
||||
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/^\s*//msg;
|
||||
$line =~ s/\s*$//msg;
|
||||
}
|
||||
$line =~ s/\s*$//;
|
||||
my ($equals, $result) = split(' ', $line, 2);
|
||||
$line = <$h>;
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub eat_showboard {
|
||||
my $h = shift;
|
||||
my $line = "";
|
||||
|
||||
while ($line eq "") {
|
||||
chop($line = <$h>) or die "No response!";
|
||||
$line =~ s/^\s*//msg;
|
||||
$line =~ s/\s*$//msg;
|
||||
}
|
||||
$line =~ s/\s*$//;
|
||||
my ($equals, $result) = split(' ', $line, 2);
|
||||
|
||||
while (!($line =~ /^\s*$/)) {
|
||||
$result .= $line;
|
||||
$line = <$h>;
|
||||
}
|
||||
print STDERR $result;
|
||||
}
|
||||
|
||||
sub standard_to_sgf {
|
||||
for (@_) { confess "Yikes!" if !defined($_); }
|
||||
for (@_) { tr/A-Z/a-z/ };
|
||||
$_ = shift(@_);
|
||||
/([a-z])([0-9]+)/;
|
||||
return "tt" if $_ eq "pass";
|
||||
|
||||
$first = ord $1;
|
||||
if ($first > 104) {
|
||||
$first = $first - 1;
|
||||
}
|
||||
$first = chr($first);
|
||||
$second = chr($size+1-$2+96);
|
||||
return "$first$second";
|
||||
}
|
||||
|
||||
sub rename_sgffile {
|
||||
my $nogames = int shift(@_);
|
||||
$_ = shift(@_);
|
||||
s/\.sgf$//;
|
||||
# Annoying to loose _001 on game #1 in multi-game set.
|
||||
# Could record as an additional parameter.
|
||||
# return "$_.sgf" if ($nogames == 1);
|
||||
return sprintf("$_" . "_%03d.sgf", $nogames);
|
||||
}
|
||||
|
||||
sub index_name {
|
||||
$_ = shift;
|
||||
s/\.sgf$//;
|
||||
return $_ . "_index.html";
|
||||
}
|
1420
gnugo/interface/gtp_examples/twogtp.pike
Executable file
1420
gnugo/interface/gtp_examples/twogtp.pike
Executable file
File diff suppressed because it is too large
Load Diff
702
gnugo/interface/gtp_examples/twogtp.py
Normal file
702
gnugo/interface/gtp_examples/twogtp.py
Normal file
@ -0,0 +1,702 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# This program is distributed with GNU Go, a Go program. #
|
||||
# #
|
||||
# Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
|
||||
# for more information. #
|
||||
# #
|
||||
# Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, #
|
||||
# 2008 and 2009 by the Free Software Foundation. #
|
||||
# #
|
||||
# This program is free software; you can redistribute it and/or #
|
||||
# modify it under the terms of the GNU General Public License #
|
||||
# as published by the Free Software Foundation - version 3, #
|
||||
# or (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be #
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied #
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
|
||||
# PURPOSE. See the GNU General Public License in file COPYING #
|
||||
# for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public #
|
||||
# License along with this program; if not, write to the Free #
|
||||
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
|
||||
# Boston, MA 02111, USA. #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
|
||||
from getopt import *
|
||||
import popen2
|
||||
import sys
|
||||
import string
|
||||
import re
|
||||
|
||||
|
||||
debug = 0
|
||||
|
||||
|
||||
def coords_to_sgf(size, board_coords):
|
||||
global debug
|
||||
|
||||
board_coords = string.lower(board_coords)
|
||||
if board_coords == "pass":
|
||||
return ""
|
||||
if debug:
|
||||
print "Coords: <" + board_coords + ">"
|
||||
letter = board_coords[0]
|
||||
digits = board_coords[1:]
|
||||
if letter > "i":
|
||||
sgffirst = chr(ord(letter) - 1)
|
||||
else:
|
||||
sgffirst = letter
|
||||
sgfsecond = chr(ord("a") + int(size) - int(digits))
|
||||
return sgffirst + sgfsecond
|
||||
|
||||
|
||||
|
||||
class GTP_connection:
|
||||
|
||||
#
|
||||
# Class members:
|
||||
# outfile File to write to
|
||||
# infile File to read from
|
||||
|
||||
def __init__(self, command):
|
||||
try:
|
||||
infile, outfile = popen2.popen2(command)
|
||||
except:
|
||||
print "popen2 failed"
|
||||
sys.exit(1)
|
||||
self.infile = infile
|
||||
self.outfile = outfile
|
||||
|
||||
def exec_cmd(self, cmd):
|
||||
global debug
|
||||
|
||||
if debug:
|
||||
sys.stderr.write("GTP command: " + cmd + "\n")
|
||||
self.outfile.write(cmd + "\n\n")
|
||||
self.outfile.flush()
|
||||
result = ""
|
||||
line = self.infile.readline()
|
||||
while line != "\n":
|
||||
result = result + line
|
||||
line = self.infile.readline()
|
||||
if debug:
|
||||
sys.stderr.write("Reply: " + line + "\n")
|
||||
|
||||
# Remove trailing newline from the result
|
||||
if result[-1] == "\n":
|
||||
result = result[:-1]
|
||||
|
||||
if len(result) == 0:
|
||||
return "ERROR: len = 0"
|
||||
if (result[0] == "?"):
|
||||
return "ERROR: GTP Command failed: " + result[2:]
|
||||
if (result[0] == "="):
|
||||
return result[2:]
|
||||
return "ERROR: Unrecognized answer: " + result
|
||||
|
||||
|
||||
class GTP_player:
|
||||
|
||||
# Class members:
|
||||
# connection GTP_connection
|
||||
|
||||
def __init__(self, command):
|
||||
self.connection = GTP_connection(command)
|
||||
protocol_version = self.connection.exec_cmd("protocol_version")
|
||||
if protocol_version[:5] != "ERROR":
|
||||
self.protocol_version = protocol_version
|
||||
else:
|
||||
self.protocol_version = "1"
|
||||
|
||||
def is_known_command(self, command):
|
||||
return self.connection.exec_cmd("known_command " + command) == "true"
|
||||
|
||||
def genmove(self, color):
|
||||
if color[0] in ["b", "B"]:
|
||||
command = "black"
|
||||
elif color[0] in ["w", "W"]:
|
||||
command = "white"
|
||||
if self.protocol_version == "1":
|
||||
command = "genmove_" + command
|
||||
else:
|
||||
command = "genmove " + command
|
||||
|
||||
return self.connection.exec_cmd(command)
|
||||
|
||||
def black(self, move):
|
||||
if self.protocol_version == "1":
|
||||
self.connection.exec_cmd("black " + move)
|
||||
else:
|
||||
self.connection.exec_cmd("play black " + move)
|
||||
|
||||
def white(self, move):
|
||||
if self.protocol_version == "1":
|
||||
self.connection.exec_cmd("white " + move)
|
||||
else:
|
||||
self.connection.exec_cmd("play white " + move)
|
||||
|
||||
def komi(self, komi):
|
||||
self.connection.exec_cmd("komi " + komi)
|
||||
|
||||
def boardsize(self, size):
|
||||
self.connection.exec_cmd("boardsize " + size)
|
||||
if self.protocol_version != "1":
|
||||
self.connection.exec_cmd("clear_board")
|
||||
|
||||
def handicap(self, handicap, handicap_type):
|
||||
if handicap_type == "fixed":
|
||||
result = self.connection.exec_cmd("fixed_handicap %d" % (handicap))
|
||||
else:
|
||||
result = self.connection.exec_cmd("place_free_handicap %d"
|
||||
% (handicap))
|
||||
|
||||
return string.split(result, " ")
|
||||
|
||||
def loadsgf(self, endgamefile, move_number):
|
||||
self.connection.exec_cmd(string.join(["loadsgf", endgamefile,
|
||||
str(move_number)]))
|
||||
|
||||
def list_stones(self, color):
|
||||
return string.split(self.connection.exec_cmd("list_stones " + color), " ")
|
||||
|
||||
def quit(self):
|
||||
return self.connection.exec_cmd("quit")
|
||||
|
||||
def showboard(self):
|
||||
board = self.connection.exec_cmd("showboard")
|
||||
if board and (board[0] == "\n"):
|
||||
board = board[1:]
|
||||
return board
|
||||
|
||||
def get_random_seed(self):
|
||||
result = self.connection.exec_cmd("get_random_seed")
|
||||
if result[:5] == "ERROR":
|
||||
return "unknown"
|
||||
return result
|
||||
|
||||
def set_random_seed(self, seed):
|
||||
self.connection.exec_cmd("set_random_seed " + seed)
|
||||
|
||||
def get_program_name(self):
|
||||
return self.connection.exec_cmd("name") + " " + \
|
||||
self.connection.exec_cmd("version")
|
||||
|
||||
def final_score(self):
|
||||
return self.connection.exec_cmd("final_score")
|
||||
|
||||
def score(self):
|
||||
return self.final_score(self)
|
||||
|
||||
def cputime(self):
|
||||
if (self.is_known_command("cputime")):
|
||||
return self.connection.exec_cmd("cputime")
|
||||
else:
|
||||
return "0"
|
||||
|
||||
|
||||
class GTP_game:
|
||||
|
||||
# Class members:
|
||||
# whiteplayer GTP_player
|
||||
# blackplayer GTP_player
|
||||
# size int
|
||||
# komi float
|
||||
# handicap int
|
||||
# handicap_type string
|
||||
# handicap_stones int
|
||||
# moves list of string
|
||||
# resultw
|
||||
# resultb
|
||||
|
||||
def __init__(self, whitecommand, blackcommand, size, komi, handicap,
|
||||
handicap_type, endgamefile):
|
||||
self.whiteplayer = GTP_player(whitecommand)
|
||||
self.blackplayer = GTP_player(blackcommand)
|
||||
self.size = size
|
||||
self.komi = komi
|
||||
self.handicap = handicap
|
||||
self.handicap_type = handicap_type
|
||||
self.endgamefile = endgamefile
|
||||
self.sgffilestart = ""
|
||||
if endgamefile != "":
|
||||
self.init_endgame_contest_game()
|
||||
else:
|
||||
self.sgffilestart = ""
|
||||
|
||||
def init_endgame_contest_game(self):
|
||||
infile = open(self.endgamefile)
|
||||
if not infile:
|
||||
print "Couldn't read " + self.endgamefile
|
||||
sys.exit(2)
|
||||
sgflines = infile.readlines()
|
||||
infile.close
|
||||
size = re.compile("SZ\[[0-9]+\]")
|
||||
move = re.compile(";[BW]\[[a-z]{0,2}\]")
|
||||
sgf_start = []
|
||||
for line in sgflines:
|
||||
match = size.search(line)
|
||||
if match:
|
||||
self.size = match.group()[3:-1]
|
||||
match = move.search(line)
|
||||
while match:
|
||||
sgf_start.append("A" + match.group()[1:])
|
||||
line = line[match.end():]
|
||||
match = move.search(line)
|
||||
self.endgame_start = len(sgf_start) - endgame_start_at
|
||||
self.sgffilestart = ";" + string.join(
|
||||
sgf_start[:self.endgame_start-1], "") + "\n"
|
||||
if self.endgame_start % 2 == 0:
|
||||
self.first_to_play = "W"
|
||||
else:
|
||||
self.first_to_play = "B"
|
||||
|
||||
def get_position_from_engine(self, engine):
|
||||
black_stones = engine.list_stones("black")
|
||||
white_stones = engine.list_stones("white")
|
||||
self.sgffilestart = ";"
|
||||
if len(black_stones) > 0:
|
||||
self.sgffilestart += "AB"
|
||||
for stone in black_stones:
|
||||
self.sgffilestart += "[%s]" % coords_to_sgf(self.size, stone)
|
||||
self.sgffilestart += "\n"
|
||||
if len(white_stones) > 0:
|
||||
self.sgffilestart += "AW"
|
||||
for stone in white_stones:
|
||||
self.sgffilestart += "[%s]" % coords_to_sgf(self.size, stone)
|
||||
self.sgffilestart += "\n"
|
||||
|
||||
def writesgf(self, sgffilename):
|
||||
"Write the game to an SGF file after a game"
|
||||
|
||||
size = self.size
|
||||
outfile = open(sgffilename, "w")
|
||||
if not outfile:
|
||||
print "Couldn't create " + sgffilename
|
||||
return
|
||||
black_name = self.blackplayer.get_program_name()
|
||||
white_name = self.whiteplayer.get_program_name()
|
||||
black_seed = self.blackplayer.get_random_seed()
|
||||
white_seed = self.whiteplayer.get_random_seed()
|
||||
handicap = self.handicap
|
||||
komi = self.komi
|
||||
result = self.resultw
|
||||
|
||||
outfile.write("(;GM[1]FF[4]RU[Japanese]SZ[%s]HA[%s]KM[%s]RE[%s]\n" %
|
||||
(size, handicap, komi, result))
|
||||
outfile.write("PW[%s (random seed %s)]PB[%s (random seed %s)]\n" %
|
||||
(white_name, white_seed, black_name, black_seed))
|
||||
outfile.write(self.sgffilestart)
|
||||
|
||||
if handicap > 1:
|
||||
outfile.write("AB");
|
||||
for stone in self.handicap_stones:
|
||||
outfile.write("[%s]" %(coords_to_sgf(size, stone)))
|
||||
outfile.write("PL[W]\n")
|
||||
|
||||
to_play = self.first_to_play
|
||||
|
||||
for move in self.moves:
|
||||
sgfmove = coords_to_sgf(size, move)
|
||||
outfile.write(";%s[%s]\n" % (to_play, sgfmove))
|
||||
if to_play == "B":
|
||||
to_play = "W"
|
||||
else:
|
||||
to_play = "B"
|
||||
outfile.write(")\n")
|
||||
outfile.close
|
||||
|
||||
def set_handicap(self, handicap):
|
||||
self.handicap = handicap
|
||||
|
||||
def swap_players(self):
|
||||
temp = self.whiteplayer
|
||||
self.whiteplayer = self.blackplayer
|
||||
self.blackplayer = temp
|
||||
|
||||
def play(self, sgffile):
|
||||
"Play a game"
|
||||
global verbose
|
||||
|
||||
if verbose >= 1:
|
||||
print "Setting boardsize and komi for black\n"
|
||||
self.blackplayer.boardsize(self.size)
|
||||
self.blackplayer.komi(self.komi)
|
||||
|
||||
if verbose >= 1:
|
||||
print "Setting boardsize and komi for white\n"
|
||||
self.whiteplayer.boardsize(self.size)
|
||||
self.whiteplayer.komi(self.komi)
|
||||
|
||||
self.handicap_stones = []
|
||||
|
||||
if self.endgamefile == "":
|
||||
if self.handicap < 2:
|
||||
self.first_to_play = "B"
|
||||
else:
|
||||
self.handicap_stones = self.blackplayer.handicap(self.handicap, self.handicap_type)
|
||||
for stone in self.handicap_stones:
|
||||
self.whiteplayer.black(stone)
|
||||
self.first_to_play = "W"
|
||||
else:
|
||||
self.blackplayer.loadsgf(self.endgamefile, self.endgame_start)
|
||||
self.blackplayer.set_random_seed("0")
|
||||
self.whiteplayer.loadsgf(self.endgamefile, self.endgame_start)
|
||||
self.whiteplayer.set_random_seed("0")
|
||||
if self.blackplayer.is_known_command("list_stones"):
|
||||
self.get_position_from_engine(self.blackplayer)
|
||||
elif self.whiteplayer.is_known_command("list_stones"):
|
||||
self.get_position_from_engine(self.whiteplayer)
|
||||
|
||||
to_play = self.first_to_play
|
||||
|
||||
self.moves = []
|
||||
passes = 0
|
||||
won_by_resignation = ""
|
||||
while passes < 2:
|
||||
if to_play == "B":
|
||||
move = self.blackplayer.genmove("black")
|
||||
if move[:5] == "ERROR":
|
||||
# FIXME: write_sgf
|
||||
sys.exit(1)
|
||||
|
||||
if move[:6] == "resign":
|
||||
if verbose >= 1:
|
||||
print "Black resigns"
|
||||
won_by_resignation = "W+Resign"
|
||||
break
|
||||
else:
|
||||
self.moves.append(move)
|
||||
if string.lower(move[:4]) == "pass":
|
||||
passes = passes + 1
|
||||
if verbose >= 1:
|
||||
print "Black passes"
|
||||
else:
|
||||
passes = 0
|
||||
self.whiteplayer.black(move)
|
||||
if verbose >= 1:
|
||||
print "Black plays " + move
|
||||
to_play = "W"
|
||||
else:
|
||||
move = self.whiteplayer.genmove("white")
|
||||
if move[:5] == "ERROR":
|
||||
# FIXME: write_sgf
|
||||
sys.exit(1)
|
||||
|
||||
if move[:6] == "resign":
|
||||
if verbose >= 1:
|
||||
print "White resigns"
|
||||
won_by_resignation = "B+Resign"
|
||||
break
|
||||
else:
|
||||
self.moves.append(move)
|
||||
if string.lower(move[:4]) == "pass":
|
||||
passes = passes + 1
|
||||
if verbose >= 1:
|
||||
print "White passes"
|
||||
else:
|
||||
passes = 0
|
||||
self.blackplayer.white(move)
|
||||
if verbose >= 1:
|
||||
print "White plays " + move
|
||||
to_play = "B"
|
||||
|
||||
if verbose >= 2:
|
||||
print self.whiteplayer.showboard() + "\n"
|
||||
|
||||
if won_by_resignation == "":
|
||||
self.resultw = self.whiteplayer.final_score()
|
||||
self.resultb = self.blackplayer.final_score()
|
||||
else:
|
||||
self.resultw = won_by_resignation;
|
||||
self.resultb = won_by_resignation;
|
||||
# if self.resultb == self.resultw:
|
||||
# print "Result: ", self.resultw
|
||||
# else:
|
||||
# print "Result according to W: ", self.resultw
|
||||
# print "Result according to B: ", self.resultb
|
||||
# FIXME: $self->writesgf($sgffile) if defined $sgffile;
|
||||
if sgffile != "":
|
||||
self.writesgf(sgffile)
|
||||
|
||||
def result(self):
|
||||
return (self.resultw, self.resultb)
|
||||
|
||||
def cputime(self):
|
||||
cputime = {}
|
||||
cputime["white"] = self.whiteplayer.cputime()
|
||||
cputime["black"] = self.blackplayer.cputime()
|
||||
return cputime
|
||||
|
||||
def quit(self):
|
||||
self.blackplayer.quit()
|
||||
self.whiteplayer.quit()
|
||||
|
||||
|
||||
class GTP_match:
|
||||
|
||||
# Class members:
|
||||
# black
|
||||
# white
|
||||
# size
|
||||
# komi
|
||||
# handicap
|
||||
# handicap_type
|
||||
|
||||
def __init__(self, whitecommand, blackcommand, size, komi, handicap,
|
||||
handicap_type, streak_length, endgamefilelist):
|
||||
self.white = whitecommand
|
||||
self.black = blackcommand
|
||||
self.size = size
|
||||
self.komi = komi
|
||||
self.handicap = handicap
|
||||
self.handicap_type = handicap_type
|
||||
self.streak_length = streak_length
|
||||
self.endgamefilelist = endgamefilelist
|
||||
|
||||
def endgame_contest(self, sgfbase):
|
||||
results = []
|
||||
i = 1
|
||||
for endgamefile in self.endgamefilelist:
|
||||
game1 = GTP_game(self.white, self.black, self.size, self.komi,
|
||||
0, "", endgamefile)
|
||||
game2 = GTP_game(self.black, self.white, self.size, self.komi,
|
||||
0, "", endgamefile)
|
||||
if verbose:
|
||||
print "Replaying", endgamefile
|
||||
print "Black:", self.black
|
||||
print "White:", self.white
|
||||
game1.play("")
|
||||
result1 = game1.result()[0]
|
||||
if result1 != "0":
|
||||
plain_result1 = re.search(r"([BW]\+)([0-9]*\.[0-9]*)", result1)
|
||||
result1_float = float(plain_result1.group(2))
|
||||
else:
|
||||
plain_result1 = re.search(r"(0)", "0")
|
||||
result1_float = 0.0
|
||||
if result1[0] == "B":
|
||||
result1_float *= -1
|
||||
if verbose:
|
||||
print "Result:", result1
|
||||
print "Replaying", endgamefile
|
||||
print "Black:", self.white
|
||||
print "White:", self.black
|
||||
game2.play("")
|
||||
result2 = game2.result()[1]
|
||||
if verbose:
|
||||
print "Result:", result2
|
||||
if result2 != "0":
|
||||
plain_result2 = re.search(r"([BW]\+)([0-9]*\.[0-9]*)", result2)
|
||||
result2_float = float(plain_result2.group(2))
|
||||
else:
|
||||
plain_result2 = re.search(r"(0)", "0")
|
||||
result2_float = 0.0
|
||||
|
||||
if result2[0] == "B":
|
||||
result2_float *= -1
|
||||
results.append(result1_float - result2_float)
|
||||
if (result1 != result2):
|
||||
print endgamefile+ ":", plain_result1.group(), \
|
||||
plain_result2.group(), "Difference:",
|
||||
print result1_float - result2_float
|
||||
else:
|
||||
print endgamefile+": Same result:", plain_result1.group()
|
||||
sgffilename = "%s%03d" % (sgfbase, i)
|
||||
game1.writesgf(sgffilename + "_1.sgf")
|
||||
game2.writesgf(sgffilename + "_2.sgf")
|
||||
game1.quit()
|
||||
game2.quit()
|
||||
i += 1
|
||||
return results
|
||||
|
||||
def play(self, games, sgfbase):
|
||||
last_color = ""
|
||||
last_streak = 0
|
||||
game = GTP_game(self.white, self.black,
|
||||
self.size, self.komi, self.handicap,
|
||||
self.handicap_type, "")
|
||||
results = []
|
||||
for i in range(games):
|
||||
sgffilename = "%s%03d.sgf" % (sgfbase, i + 1)
|
||||
game.play(sgffilename)
|
||||
result = game.result()
|
||||
if result[0] == result[1]:
|
||||
print "Game %d: %s" % (i + 1, result[0])
|
||||
else:
|
||||
print "Game %d: %s %s" % (i + 1, result[0], result[1])
|
||||
|
||||
if result[0][0] == last_color:
|
||||
last_streak += 1
|
||||
elif result[0][0] != "0":
|
||||
last_color = result[0][0]
|
||||
last_streak = 1
|
||||
|
||||
if last_streak == self.streak_length:
|
||||
if last_color == "W":
|
||||
self.handicap += 1
|
||||
if self.handicap == 1:
|
||||
self.handicap = 2
|
||||
print "White wins too often. Increasing handicap to %d" \
|
||||
% (self.handicap)
|
||||
else:
|
||||
if self.handicap > 0:
|
||||
self.handicap -= 1
|
||||
if self.handicap == 1:
|
||||
self.handicap = 0
|
||||
print "Black wins too often. Decreasing handicap to %d" \
|
||||
% (self.handicap)
|
||||
else:
|
||||
self.handicap = 2
|
||||
game.swap_players()
|
||||
print "Black looks stronger than white. Swapping colors and setting handicap to 2"
|
||||
game.set_handicap(self.handicap)
|
||||
last_color = ""
|
||||
last_streak = 0
|
||||
results.append(result)
|
||||
cputime = game.cputime()
|
||||
game.quit()
|
||||
return results, cputime
|
||||
|
||||
|
||||
# ================================================================
|
||||
# Main program
|
||||
#
|
||||
|
||||
|
||||
# Default values
|
||||
#
|
||||
|
||||
white = ""
|
||||
black = ""
|
||||
komi = ""
|
||||
size = "19"
|
||||
handicap = 0
|
||||
handicap_type = "fixed"
|
||||
streak_length = -1
|
||||
endgame_start_at = 0
|
||||
|
||||
games = 1
|
||||
sgfbase = "twogtp"
|
||||
|
||||
verbose = 0
|
||||
|
||||
helpstring = """
|
||||
|
||||
Run with:
|
||||
|
||||
twogtp --white \'<path to program 1> --mode gtp [program options]\' \\
|
||||
--black \'<path to program 2> --mode gtp [program options]\' \\
|
||||
[twogtp options]
|
||||
|
||||
Possible twogtp options:
|
||||
|
||||
--verbose 1 (to list moves) or --verbose 2 (to draw board)
|
||||
--komi <amount>
|
||||
--handicap <amount>
|
||||
--free-handicap <amount>
|
||||
--adjust-handicap <length> (change handicap by 1 after <length> wins
|
||||
in a row)
|
||||
--size <board size> (default 19)
|
||||
--games <number of games to play> (default 1)
|
||||
--sgfbase <filename> (create sgf files with sgfbase as basename)
|
||||
--endgame <moves before end> (endgame contest - add filenames of
|
||||
games to be replayed after last option)
|
||||
"""
|
||||
|
||||
def usage():
|
||||
print helpstring
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
(opts, params) = getopt(sys.argv[1:], "",
|
||||
["black=",
|
||||
"white=",
|
||||
"verbose=",
|
||||
"komi=",
|
||||
"boardsize=",
|
||||
"size=",
|
||||
"handicap=",
|
||||
"free-handicap=",
|
||||
"adjust-handicap=",
|
||||
"games=",
|
||||
"sgfbase=",
|
||||
"endgame=",
|
||||
])
|
||||
except:
|
||||
usage();
|
||||
|
||||
for opt, value in opts:
|
||||
if opt == "--black":
|
||||
black = value
|
||||
elif opt == "--white":
|
||||
white = value
|
||||
elif opt == "--verbose":
|
||||
verbose = int(value)
|
||||
elif opt == "--komi":
|
||||
komi = value
|
||||
elif opt == "--boardsize" or opt == "--size":
|
||||
size = value
|
||||
elif opt == "--handicap":
|
||||
handicap = int(value)
|
||||
handicap_type = "fixed"
|
||||
elif opt == "--free-handicap":
|
||||
handicap = int(value)
|
||||
handicap_type = "free"
|
||||
elif opt == "--adjust-handicap":
|
||||
streak_length = int(value)
|
||||
elif opt == "--games":
|
||||
games = int(value)
|
||||
elif opt == "--sgfbase":
|
||||
sgfbase = value
|
||||
elif opt == "--endgame":
|
||||
endgame_start_at = int(value)
|
||||
|
||||
if endgame_start_at != 0:
|
||||
endgame_filelist = params
|
||||
else:
|
||||
endgame_filelist = []
|
||||
if params != []:
|
||||
usage()
|
||||
|
||||
|
||||
if black == "" or white == "":
|
||||
usage()
|
||||
|
||||
if komi == "":
|
||||
if handicap > 1 and streak_length == -1:
|
||||
komi = "0.5"
|
||||
else:
|
||||
komi = "5.5"
|
||||
|
||||
match = GTP_match(white, black, size, komi, handicap, handicap_type,
|
||||
streak_length, endgame_filelist)
|
||||
if endgame_filelist != []:
|
||||
results = match.endgame_contest(sgfbase)
|
||||
win_black = 0
|
||||
win_white = 0
|
||||
for res in results:
|
||||
print res
|
||||
if res > 0.0:
|
||||
win_white += 1
|
||||
elif res < 0.0:
|
||||
win_black += 1
|
||||
print "%d games, %d wins for black, %d wins for white." \
|
||||
% (len(results), win_black, win_white)
|
||||
|
||||
else:
|
||||
results, cputimes = match.play(games, sgfbase)
|
||||
|
||||
i = 0
|
||||
for resw, resb in results:
|
||||
i = i + 1
|
||||
if resw == resb:
|
||||
print "Game %d: %s" % (i, resw)
|
||||
else:
|
||||
print "Game %d: %s %s" % (i, resb, resw)
|
||||
if (cputimes["white"] != "0"):
|
||||
print "White: %ss CPU time" % cputimes["white"]
|
||||
if (cputimes["black"] != "0"):
|
||||
print "Black: %ss CPU time" % cputimes["black"]
|
134
gnugo/interface/gtp_examples/vanilla.c
Normal file
134
gnugo/interface/gtp_examples/vanilla.c
Normal file
@ -0,0 +1,134 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
|
||||
* http://www.gnu.org/software/gnugo/ for more information. *
|
||||
* *
|
||||
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
|
||||
* 2008 and 2009 by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation - version 3 *
|
||||
* or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License in file COPYING for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public *
|
||||
* License along with this program; if not, write to the Free *
|
||||
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
|
||||
* Boston, MA 02111, USA. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* This program uses two pipes to communicate with the
|
||||
* GNU Go client. To an external client it appears to
|
||||
* be a gtp engine. It accomplishes this by intercepting
|
||||
* gtp commands and passing them on to GNU Go.
|
||||
*
|
||||
* This program in and of itself is not useful but it
|
||||
* can be the basis of useful programs. Example: hack
|
||||
* in gmp.c and get a gtp / gmp mediator.
|
||||
*
|
||||
* Pipe a: client --> gnugo
|
||||
* Pipe b: gnugo --> client
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
|
||||
void error(const char *msg);
|
||||
#define TELL_GNUGO(x) if (verbose) fprintf(stderr, "%s", x); \
|
||||
if (fprintf(to_gnugo_stream, "%s", x) < 0) \
|
||||
error ("can't write command in to_gnugo_stream"); \
|
||||
fflush(to_gnugo_stream);
|
||||
#define ASK_GNUGO(x) if (!fgets(x, 128, from_gnugo_stream)) \
|
||||
error("can't get response");
|
||||
int
|
||||
main()
|
||||
{
|
||||
int pfd_a[2];
|
||||
int pfd_b[2];
|
||||
char gnugo_line[128], client_line[128];
|
||||
int length = 0;
|
||||
int verbose = 1;
|
||||
FILE *to_gnugo_stream, *from_gnugo_stream;
|
||||
|
||||
if (pipe(pfd_a) == -1)
|
||||
error("can't open pipe a");
|
||||
if (pipe(pfd_b) == -1)
|
||||
error("can't open pipe b");
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
error("fork failed (try chopsticks)");
|
||||
case 0:
|
||||
/* Attach pipe a to stdin */
|
||||
if (dup2(pfd_a[0], 0) == -1)
|
||||
error("dup pfd_a[0] failed");
|
||||
/* attach pipe b to stdout" */
|
||||
if (dup2(pfd_b[1], 1) == -1)
|
||||
error("dup pfd_b[1] failed");
|
||||
execlp("gnugo", "gnugo", "--mode", "gtp", "--quiet", NULL);
|
||||
error("execlp failed");
|
||||
}
|
||||
/* We use stderr to communicate with the client since stdout is needed. */
|
||||
/* Attach pipe a to to_gnugo_stream */
|
||||
to_gnugo_stream = fdopen(pfd_a[1], "w");
|
||||
/* Attach pipe b to from_gnugo_stream */
|
||||
from_gnugo_stream = fdopen(pfd_b[0], "r");
|
||||
|
||||
while (1) {
|
||||
int length = 0;
|
||||
if (!fgets(client_line, 128, stdin)
|
||||
|| !strncmp(client_line, "quit", 4)) {
|
||||
TELL_GNUGO("quit\n");
|
||||
return 1;
|
||||
}
|
||||
if (!strncmp(client_line, "genmove_black", 13)) {
|
||||
char *token;
|
||||
const char delimiters[] = " \t\r\n";
|
||||
float value1, value2;
|
||||
|
||||
TELL_GNUGO("top_moves_black\n");
|
||||
ASK_GNUGO(gnugo_line);
|
||||
token = strtok(gnugo_line, delimiters);
|
||||
token = strtok(NULL, delimiters);
|
||||
TELL_GNUGO("black ");
|
||||
TELL_GNUGO(token);
|
||||
TELL_GNUGO("\n");
|
||||
ASK_GNUGO(gnugo_line);
|
||||
while (length != 1) {
|
||||
ASK_GNUGO(gnugo_line);
|
||||
length = strlen(gnugo_line);
|
||||
printf(gnugo_line);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
else {
|
||||
TELL_GNUGO(client_line);
|
||||
while (length != 1) {
|
||||
ASK_GNUGO(gnugo_line);
|
||||
length = strlen(gnugo_line);
|
||||
printf(gnugo_line);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
error(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "vanilla: %s\n", msg);
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
Reference in New Issue
Block a user