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:
2012-04-12 13:46:27 -04:00
parent 55dbed09f5
commit 8b772255a1
2259 changed files with 388094 additions and 291 deletions

View 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';
}
}

View 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.

View 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

File diff suppressed because it is too large Load Diff

View 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/&/&amp;/g;
$r =~ s/</&lt;/g;
$r =~ s/>/&gt;/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);
}

View 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;
}

View 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";
}
}

View 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;

View 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;

View 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/&/&amp;/g;
$r =~ s/</&lt;/g;
$r =~ s/>/&gt;/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";
}

File diff suppressed because it is too large Load Diff

View 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"]

View 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:
*/