Included gomill framework for SGF and GTP support, and sketched out SGF game-loading code.

This commit is contained in:
2012-04-21 04:27:05 -04:00
parent 700a6a2f32
commit 692dc294d6
119 changed files with 27458 additions and 3 deletions

View File

@ -0,0 +1,78 @@
# The following settings are supported:
#
# - all _common settings_
#
# - all _game settings_
#
# - tuning event settings (cf mcts_tuner):
# - candidate_colour
# - opponent
# - parameters
# - make_candidate
#
# - settings for experiment control
# - parallel -- number of games to run in parallel
# - stop_on_error -- boolean
#
# - regression parameters:
# - clop_H -- float
# - correlations -- 'all' (default) or 'none'
#
## <<
# clop_H: 3 is recommended (it is the default value)
# correlations:
# Even if variables are not correlated "all" should work well. The problem is
# that the regression might become very costly if the number of variables is
# high. So use "correlations none" only if you are certain parameters are
# independent or you have so many variables that "all" is too costly.
## >>
#
# The available parameter types are:
# LinearParameter
# IntegerParameter
# GammaParameter
# IntegerGammaParameter
# For GammaParameter, quadratic regression is performed on log(x)
competition_type = "clop_tuner"
description = """\
Sample control file for CLOP integration.
"""
def gnugo(level):
return Player("gnugo --mode=gtp --chinese-rules --capture-all-dead "
"--level=%d" % level)
def pachi(playouts, policy):
return Player(
"~/src/pachi/pachi "
"-d 0 " # silence stderr
"-t =%d "
"threads=1,max_tree_size=2048 "
"policy=%s "
% (playouts, policy))
players = {
'gnugo-l7' : gnugo(7),
}
parameters = [
Parameter('equiv_rave',
type = "GammaParameter",
min = 40,
max = 32000),
]
def make_candidate(equiv_rave):
return pachi(2000, policy="ucb1amaf:equiv_rave=%f" % equiv_rave)
board_size = 19
komi = 7.5
opponent = 'gnugo-l7'
candidate_colour = 'w'
parallel = 2

View File

@ -0,0 +1,54 @@
"""Find forfeited games in tournament results.
This demonstrates retrieving and processing results from a tournament.
"""
import sys
from optparse import OptionParser
from gomill.common import opponent_of
from gomill.ringmasters import Ringmaster, RingmasterError
def show_result(matchup, result, filename):
print "%s: %s forfeited game %s" % (
matchup.name, result.losing_player, filename)
def find_forfeits(ringmaster):
ringmaster.load_status()
tournament_results = ringmaster.get_tournament_results()
matchup_ids = tournament_results.get_matchup_ids()
for matchup_id in matchup_ids:
matchup = tournament_results.get_matchup(matchup_id)
results = tournament_results.get_matchup_results(matchup_id)
for result in results:
if result.is_forfeit:
filename = ringmaster.get_sgf_filename(result.game_id)
show_result(matchup, result, filename)
_description = """\
Read results of a tournament and show all forfeited games.
"""
def main(argv):
parser = OptionParser(usage="%prog <filename.ctl>",
description=_description)
opts, args = parser.parse_args(argv)
if not args:
parser.error("not enough arguments")
if len(args) > 1:
parser.error("too many arguments")
ctl_pathname = args[0]
try:
ringmaster = Ringmaster(ctl_pathname)
find_forfeits(ringmaster)
except RingmasterError, e:
print >>sys.stderr, "ringmaster:"
print >>sys.stderr, e
sys.exit(1)
if __name__ == "__main__":
main(sys.argv[1:])

470
gomill/examples/gomill-clop Executable file
View File

@ -0,0 +1,470 @@
#!/usr/bin/env python
"""Gomill integration for CLOP.
Designed for use with CLOP 0.0.8, available from
http://remi.coulom.free.fr/CLOP/
"""
# The 'connection script' interface is as follows:
# - the command-line arguments are:
# - any arguments specified in the Script line
# - processor
# - seed
# - then pairs of arguments (parameter name, parameter value)
# - the connection script prints a single character to stdout:
# 'W' for candidate win, 'D' for draw, 'L' for loss
# any further output is ignored
# - if the output doesn't start with 'W', 'D', or 'L', it's treated as an error,
# and the complete stdout and stderr are reported
# (by convention, print "Error:" with a description)
# - the connection script's exit status is ignored
import os
import sys
from optparse import OptionParser
from gomill import compact_tracebacks
from gomill import competitions
from gomill import game_jobs
from gomill.competitions import (
Competition, CompetitionError, ControlFileError, Player_config)
from gomill.job_manager import JobFailed
from gomill.ringmasters import (
Ringmaster, RingmasterError, RingmasterInternalError)
from gomill.settings import *
from gomill.common import opponent_of
PARAMETER_TYPES = [
"LinearParameter",
"IntegerParameter",
"GammaParameter",
"IntegerGammaParameter",
]
parameter_settings = [
Setting('code', interpret_identifier),
Setting('type', interpret_enum(*PARAMETER_TYPES)),
Setting('min', interpret_float),
Setting('max', interpret_float),
]
class Parameter_config(Quiet_config):
"""Parameter (ie, dimension) description for use in control files."""
# positional or keyword
positional_arguments = ('code',)
# keyword-only
keyword_arguments = tuple(setting.name for setting in parameter_settings
if setting.name != 'code')
class Parameter_spec(object):
"""Internal description of a parameter spec from the configuration file.
Public attributes:
code -- identifier
"""
def format_for_clop(self):
"""Return the parameter configuration string for the .clop file."""
if self.is_integer:
fmt = "%s %s %d %d"
else:
fmt = "%s %s %f %f"
return fmt % (self.type, self.code, self.min, self.max)
def interpret_value(self, s):
"""Convert CLOP command-line parameter to an engine parameter.
Returns an int or float.
"""
if self.is_integer:
return int(s)
else:
return float(s)
def format_for_display(self, v):
return str(v)
class Clop_tuner(Competition):
def control_file_globals(self):
result = Competition.control_file_globals(self)
result.update({
'Parameter' : Parameter_config,
})
return result
global_settings = (Competition.global_settings +
competitions.game_settings + [
Setting('candidate_colour', interpret_colour),
Setting('parallel', interpret_int, default=1),
Setting('clop_H', interpret_float, default=3),
Setting('correlations', interpret_enum('all', 'none'), default='all'),
Setting('stop_on_error', interpret_bool, default=True),
])
special_settings = [
Setting('opponent', interpret_identifier),
Setting('parameters',
interpret_sequence_of_quiet_configs(Parameter_config)),
Setting('make_candidate', interpret_callable),
]
def parameter_spec_from_config(self, parameter_config):
"""Make a Parameter_spec from a Parameter_config.
Raises ControlFileError if there is an error in the configuration.
Returns a Parameter_spec with all attributes set.
"""
arguments = parameter_config.resolve_arguments()
interpreted = load_settings(parameter_settings, arguments)
pspec = Parameter_spec()
for name, value in interpreted.iteritems():
setattr(pspec, name, value)
pspec.is_integer = ("Integer" in pspec.type)
if pspec.is_integer:
if pspec.min != int(pspec.min):
raise ControlFileError("'min': should be an integer")
if pspec.max != int(pspec.max):
raise ControlFileError("'max': should be an integer")
return pspec
def initialise_from_control_file(self, config):
Competition.initialise_from_control_file(self, config)
competitions.validate_handicap(
self.handicap, self.handicap_style, self.board_size)
try:
specials = load_settings(self.special_settings, config)
except ValueError, e:
raise ControlFileError(str(e))
try:
self.opponent = self.players[specials['opponent']]
except KeyError:
raise ControlFileError(
"opponent: unknown player %s" % specials['opponent'])
self.parameter_specs = []
if not specials['parameters']:
raise ControlFileError("parameters: empty list")
seen_codes = set()
for i, parameter_spec in enumerate(specials['parameters']):
try:
pspec = self.parameter_spec_from_config(parameter_spec)
except StandardError, e:
code = parameter_spec.get_key()
if code is None:
code = i
raise ControlFileError("parameter %s: %s" % (code, e))
if pspec.code in seen_codes:
raise ControlFileError(
"duplicate parameter code: %s" % pspec.code)
seen_codes.add(pspec.code)
self.parameter_specs.append(pspec)
self.candidate_maker_fn = specials['make_candidate']
def get_clop_parameter_specs(self):
"""Describe the parameters in the format used in the .clop file.
Returns a list of strings.
"""
return [pspec.format_for_clop() for pspec in self.parameter_specs]
def interpret_clop_parameters(self, clop_parameters):
"""Convert the CLOP command-line parameters to engine parameters.
clop_parameters -- list of pairs of strings
(parameter name, parameter value)
Returns a list of engine parameters, suitable for passing to
make_candidate().
"""
engine_parameters = []
try:
if len(clop_parameters) != len(self.parameter_specs):
raise ValueError
for pspec, (name, value) in \
zip(self.parameter_specs, clop_parameters):
if name != pspec.code:
raise ValueError
engine_parameters.append(pspec.interpret_value(value))
return engine_parameters
except ValueError:
raise CompetitionError(
"couldn't interpret parameters: %s" % repr(clop_parameters))
def format_engine_parameters(self, engine_parameters):
return "; ".join(
"%s %s" % (pspec.code, pspec.format_for_display(v))
for pspec, v in zip(self.parameter_specs, engine_parameters))
def make_candidate(self, player_code, engine_parameters):
"""Make a player using the specified engine parameters.
Returns a game_jobs.Player.
"""
try:
candidate_config = self.candidate_maker_fn(*engine_parameters)
except Exception:
raise CompetitionError(
"error from make_candidate()\n%s" %
compact_tracebacks.format_traceback(skip=1))
if not isinstance(candidate_config, Player_config):
raise CompetitionError(
"make_candidate() returned %r, not Player" %
candidate_config)
try:
candidate = self.game_jobs_player_from_config(
player_code, candidate_config)
except Exception, e:
raise CompetitionError(
"bad player spec from make_candidate():\n"
"%s\nparameters were: %s" %
(e, self.format_engine_parameters(engine_parameters)))
return candidate
def get_game_for_parameters(self, clop_seed, clop_parameters):
"""Return the details of the next game to play.
clop_seed -- second command-line parameter passed by clop
clop_parameters -- remaining command-line parameters passed by clop.
This is like Competition.get_game(), but it never returns
NoGameAvailable.
"""
engine_parameters = self.interpret_clop_parameters(clop_parameters)
candidate = self.make_candidate("#%s" % clop_seed, engine_parameters)
job = game_jobs.Game_job()
job.game_id = clop_seed
if self.candidate_colour == 'b':
job.player_b = candidate
job.player_w = self.opponent
else:
job.player_b = self.opponent
job.player_w = candidate
job.board_size = self.board_size
job.komi = self.komi
job.move_limit = self.move_limit
job.handicap = self.handicap
job.handicap_is_free = (self.handicap_style == 'free')
job.use_internal_scorer = (self.scorer == 'internal')
job.internal_scorer_handicap_compensation = \
self.internal_scorer_handicap_compensation
job.sgf_event = self.competition_code
job.sgf_note = ("Candidate parameters: %s" %
self.format_engine_parameters(engine_parameters))
return job
class Clop_ringmaster(Ringmaster):
def __init__(self, *args, **kwargs):
Ringmaster.__init__(self, *args, **kwargs)
# clop uses .log, so we need something different
self.log_pathname = os.path.join(
self.base_directory, self.competition_code) + ".elog"
@staticmethod
def _get_competition_class(competition_type):
if competition_type == "clop_tuner":
return Clop_tuner
else:
raise ValueError
def ensure_output_directories(self):
if self.record_games:
try:
if not os.path.exists(self.sgf_dir_pathname):
os.mkdir(self.sgf_dir_pathname)
except EnvironmentError:
raise RingmasterError("failed to create SGF directory:\n%s" % e)
if self.write_gtp_logs:
try:
if not os.path.exists(self.gtplog_dir_pathname):
os.mkdir(self.gtplog_dir_pathname)
except EnvironmentError:
raise RingmasterError(
"failed to create GTP log directory:\n%s" % e)
def open_logfile(self):
try:
self.logfile = open(self.log_pathname, "a")
except EnvironmentError, e:
raise RingmasterError("failed to open log file:\n%s" % e)
def run_game_for_clop(self, seed, parameters):
"""Act as a CLOP connection script.
seed -- seed string passed by clop
parameters -- list of pairs of strings (parameter name, parameter value)
Returns the message to print.
"""
self._initialise_presenter()
try:
job = self.competition.get_game_for_parameters(seed, parameters)
self._prepare_job(job)
start_msg = "starting game %s: %s (b) vs %s (w)" % (
job.game_id, job.player_b.code, job.player_w.code)
self.log(start_msg)
response = job.run()
self.log("response from game %s" % response.game_id)
for warning in response.warnings:
self.warn(warning)
for log_entry in response.log_entries:
self.log(log_entry)
except (CompetitionError, JobFailed), e:
raise RingmasterError(e)
result = response.game_result
candidate_colour = self.competition.candidate_colour
if result.winning_colour == candidate_colour:
message = "W"
elif result.winning_colour == opponent_of(candidate_colour):
message = "L"
elif result.is_jigo:
message = "D"
else:
if self.competition.stop_on_error:
# Don't want the experiment to stop just because a single game
# failed (eg, went over the move limit), so treat it as a draw.
message = "D"
else:
raise RingmasterError("unexpected game result: %s" %
result.sgf_result)
return message
clop_template = """\
Name %(experiment_name)s
Script %(connection_pathname)s %(control_filename)s run-game
%(parameter_specs)s
%(processor_specs)s
Replications 1
DrawElo %(drawelo)s
H %(clop_H)s
Correlations %(correlations)s
StopOnError %(stop_on_error)s
"""
def do_setup(ringmaster, arguments, options):
"""Create the .clop file, and any needed directories."""
connection_pathname = os.path.abspath(__file__)
control_filename = os.path.basename(ringmaster.control_pathname)
clop_pathname = os.path.join(ringmaster.base_directory,
"%s.clop" % ringmaster.competition_code)
competition = ringmaster.competition
experiment_name = ringmaster.competition_code
parameter_specs = "\n".join(competition.get_clop_parameter_specs())
processor_specs = "\n".join(
"Processor par%d" % i for i in xrange(competition.parallel))
if competition.komi == int(competition.komi) or competition.stop_on_error:
drawelo = "100"
else:
drawelo = "0"
clop_H = competition.clop_H
correlations = competition.correlations
stop_on_error = "true" if competition.stop_on_error else "false"
with open(clop_pathname, "w") as f:
f.write(clop_template % locals())
ringmaster.ensure_output_directories()
def do_run_game(ringmaster, arguments, options):
"""Act as a CLOP connection script."""
try:
processor, seed = arguments[:2]
except ValueError:
raise RingmasterError("not enough connection script arguments")
parameter_args = arguments[2:]
parameters = []
i = 0
try:
while i < len(parameter_args):
parameters.append((parameter_args[i], parameter_args[i+1]))
i += 2
except LookupError:
raise RingmasterError("parameter without value: %s" % parameter_args[i])
ringmaster.set_display_mode('quiet')
ringmaster.open_logfile()
message = ringmaster.run_game_for_clop(seed, parameters)
print message
_actions = {
"setup" : (do_setup, False),
"run-game" : (do_run_game, True),
}
def main(argv):
usage = ("%prog <control file> <command> [connection script arguments]\n\n"
"commands: setup, run-game")
description = (
"`setup` generates a .clop file for use with clop-gui or clop-console. "
"Then `run-game` is used (behind the scenes) as the connection script.")
parser = OptionParser(usage=usage, description=description)
(options, args) = parser.parse_args(argv)
if len(args) == 0:
parser.error("no control file specified")
if len(args) == 1:
parser.error("no command specified")
command = args[1]
command_args = args[2:]
try:
action, takes_arguments = _actions[command]
except KeyError:
parser.error("no such command: %s" % command)
if command_args and not takes_arguments:
parser.error("too many arguments for %s" % command)
ctl_pathname = args[0]
try:
if not os.path.exists(ctl_pathname):
raise RingmasterError("control file %s not found" % ctl_pathname)
ringmaster = Clop_ringmaster(ctl_pathname)
action(ringmaster, command_args, options)
exit_status = 0
except RingmasterError, e:
print >>sys.stderr, "gomill-clop:", e
exit_status = 1
except KeyboardInterrupt:
exit_status = 3
except RingmasterInternalError, e:
print >>sys.stderr, "gomill-clop: internal error"
print >>sys.stderr, e
exit_status = 4
except:
print >>sys.stderr, "gomill-clop: internal error"
compact_tracebacks.log_traceback()
exit_status = 4
if exit_status != 0 and command == 'run-game':
# Make sure the problem runner sees an error response
print "Error"
sys.exit(exit_status)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -0,0 +1,118 @@
#!/usr/bin/env python
"""GTP engine which maintains the board position.
This provides an example of a GTP engine using the gtp_states module.
It plays (and resigns) randomly.
It supports the following GTP commands, mostly provided by gtp_states:
Standard
boardsize
clear_board
fixed_handicap
genmove
known_command
komi
list_commands
loadsgf
name
place_free_handicap
play
protocol_version
quit
reg_genmove
set_free_handicap
showboard
undo
version
Gomill extensions
gomill-explain_last_move
gomill-genmove_ex
gomill-savesgf
Examples
gomill_resign_p <float> -- resign in future with the specified probabiltiy
"""
import random
import sys
from gomill import gtp_engine
from gomill import gtp_states
class Player(object):
"""Player for use with gtp_state."""
def __init__(self):
self.resign_probability = 0.1
def genmove(self, game_state, player):
"""Move generator that chooses a random empty point.
game_state -- gtp_states.Game_state
player -- 'b' or 'w'
This may return a self-capture move.
"""
board = game_state.board
empties = []
for row, col in board.board_points:
if board.get(row, col) is None:
empties.append((row, col))
result = gtp_states.Move_generator_result()
if random.random() < self.resign_probability:
result.resign = True
else:
result.move = random.choice(empties)
# Used by gomill-explain_last_move and gomill-savesgf
result.comments = "chosen at random from %d choices" % len(empties)
return result
def handle_name(self, args):
return "GTP stateful player"
def handle_version(self, args):
return ""
def handle_resign_p(self, args):
try:
f = gtp_engine.interpret_float(args[0])
except IndexError:
gtp_engine.report_bad_arguments()
self.resign_probability = f
def get_handlers(self):
return {
'name' : self.handle_name,
'version' : self.handle_version,
'gomill-resign_p' : self.handle_resign_p,
}
def make_engine(player):
"""Return a Gtp_engine_protocol which runs the specified player."""
gtp_state = gtp_states.Gtp_state(
move_generator=player.genmove,
acceptable_sizes=(9, 13, 19))
engine = gtp_engine.Gtp_engine_protocol()
engine.add_protocol_commands()
engine.add_commands(gtp_state.get_handlers())
engine.add_commands(player.get_handlers())
return engine
def main():
try:
player = Player()
engine = make_engine(player)
gtp_engine.run_interactive_gtp_session(engine)
except (KeyboardInterrupt, gtp_engine.ControllerDisconnected):
sys.exit(1)
if __name__ == "__main__":
main()

144
gomill/examples/gtp_test_player Executable file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env python
"""GTP engine intended for testing GTP controllers.
This provides an example of a GTP engine which does not use the gtp_states
module.
It supports the following GTP commands:
Standard
boardsize
clear_board
genmove
known_command
komi
list_commands
name
play
protocol_version
quit
version
Extensions:
gomill-force_error [error_type]
gomill-delayed_error <move_number> [error_type]
gomill-force_error immediately causes an error. error_type can be any of the
following:
error -- return a GTP error response (this is the default)
exit -- return a GTP error response and end the GTP session
internal -- propagate a Python exception to the GTP engine code
kill -- abruptly terminate the engine process
protocol -- send an ill-formed GTP response
gomill-delayed_error causes a later genmove command to produce an error. This
will happen the first time genmove is called for the move 'move_number' or
later, counting from the start of the game.
"""
import os
import sys
from gomill import gtp_engine
from gomill.gtp_engine import GtpError, GtpFatalError
class Test_player(object):
"""GTP player used for testing controllers' error handling."""
def __init__(self):
self.delayed_error_move = None
self.delayed_error_args = None
self.move_count = 0
def handle_name(self, args):
return "GTP test player"
def handle_version(self, args):
return ""
def handle_genmove(self, args):
"""Handler for the genmove command.
This honours gomill-delayed_error, and otherwise passes.
"""
self.move_count += 1
if (self.delayed_error_move and
self.move_count >= self.delayed_error_move):
self.delayed_error_move = None
self.handle_force_error(self.delayed_error_args)
return "pass"
def handle_play(self, args):
self.move_count += 1
def handle_boardsize(self, args):
pass
def handle_clear_board(self, args):
pass
def handle_komi(self, args):
pass
def handle_force_error(self, args):
"""Handler for the gomill-force_error command."""
try:
arg = args[0]
except IndexError:
arg = "error"
if arg == "error":
raise GtpError("forced GTP error")
if arg == "exit":
raise GtpFatalError("forced GTP error; exiting")
if arg == "internal":
3 / 0
if arg == "kill":
os.kill(os.getpid(), 15)
if arg == "protocol":
sys.stdout.write("!! forced ill-formed GTP response\n")
sys.stdout.flush()
return
raise GtpError("unknown force_error argument")
def handle_delayed_error(self, args):
"""Handler for the gomill-delayed_error command."""
try:
move_number = gtp_engine.interpret_int(args[0])
except IndexError:
gtp_engine.report_bad_arguments()
self.delayed_error_move = move_number
self.delayed_error_args = args[1:]
def get_handlers(self):
return {
'name' : self.handle_name,
'version' : self.handle_version,
'genmove' : self.handle_genmove,
'play' : self.handle_play,
'boardsize' : self.handle_boardsize,
'clear_board' : self.handle_clear_board,
'komi' : self.handle_komi,
'gomill-force_error' : self.handle_force_error,
'gomill-delayed_error' : self.handle_delayed_error,
}
def make_engine(test_player):
"""Return a Gtp_engine_protocol which runs the specified Test_player."""
engine = gtp_engine.Gtp_engine_protocol()
engine.add_protocol_commands()
engine.add_commands(test_player.get_handlers())
return engine
def main():
try:
test_player = Test_player()
engine = make_engine(test_player)
gtp_engine.run_interactive_gtp_session(engine)
except (KeyboardInterrupt, gtp_engine.ControllerDisconnected):
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,146 @@
"""GTP proxy for use with kgsGtp.
This supports saving a game record after each game, if the underlying engine
supports gomill-savesgf.
"""
import os
import sys
from optparse import OptionParser
from gomill import gtp_engine
from gomill import gtp_proxy
from gomill.gtp_engine import GtpError
from gomill.gtp_controller import BadGtpResponse
class Kgs_proxy(object):
"""GTP proxy for use with kgsGtp.
Instantiate with command line arguments.
Calls sys.exit on fatal errors.
"""
def __init__(self, command_line_args):
parser = OptionParser(usage="%prog [options] <back end command> [args]")
parser.disable_interspersed_args()
parser.add_option("--sgf-dir", metavar="PATHNAME")
parser.add_option("--filename-template", metavar="TEMPLATE",
help="eg '%03d.sgf'")
opts, args = parser.parse_args(command_line_args)
if not args:
parser.error("must specify a command")
self.subprocess_command = args
self.filename_template = "%04d.sgf"
try:
opts.filename_template % 3
except Exception:
pass
else:
self.filename_template = opts.filename_template
self.sgf_dir = opts.sgf_dir
if self.sgf_dir:
self.check_sgf_dir()
self.do_savesgf = True
else:
self.do_savesgf = False
def log(self, s):
print >>sys.stderr, s
def run(self):
self.proxy = gtp_proxy.Gtp_proxy()
try:
self.proxy.set_back_end_subprocess(self.subprocess_command)
self.proxy.engine.add_commands(
{'genmove' : self.handle_genmove,
'kgs-game_over' : self.handle_game_over,
})
if (self.do_savesgf and
not self.proxy.back_end_has_command("gomill-savesgf")):
sys.exit("kgs_proxy: back end doesn't support gomill-savesgf")
# Colour that we appear to be playing
self.my_colour = None
self.initialise_name()
except gtp_proxy.BackEndError, e:
sys.exit("kgs_proxy: %s" % e)
try:
self.proxy.run()
except KeyboardInterrupt:
sys.exit(1)
def initialise_name(self):
def shorten_version(name, version):
"""Clean up redundant version strings."""
if version.lower().startswith(name.lower()):
version = version[len(name):].lstrip()
# For MoGo's stupidly long version string
a, b, c = version.partition(". Please read http:")
if b:
version = a
return version[:32].rstrip()
self.my_name = None
try:
self.my_name = self.proxy.pass_command("name", [])
version = self.proxy.pass_command("version", [])
version = shorten_version(self.my_name, version)
self.my_name += ":" + version
except BadGtpResponse:
pass
def handle_genmove(self, args):
try:
self.my_colour = gtp_engine.interpret_colour(args[0])
except IndexError:
gtp_engine.report_bad_arguments()
return self.proxy.pass_command("genmove", args)
def check_sgf_dir(self):
if not os.path.isdir(self.sgf_dir):
sys.exit("kgs_proxy: can't find save game directory %s" %
self.sgf_dir)
def choose_filename(self, existing):
existing = set(existing)
for i in xrange(10000):
filename = self.filename_template % i
if filename not in existing:
return filename
raise StandardError("too many sgf files")
def handle_game_over(self, args):
"""Handler for kgs-game_over.
kgsGtp doesn't send any arguments, so we don't know the result.
"""
def escape_for_savesgf(s):
return s.replace("\\", "\\\\").replace(" ", "\\ ")
if self.do_savesgf:
filename = self.choose_filename(os.listdir(self.sgf_dir))
pathname = os.path.join(self.sgf_dir, filename)
self.log("kgs_proxy: saving game record to %s" % pathname)
args = [pathname]
if self.my_colour is not None and self.my_name is not None:
args.append("P%s=%s" % (self.my_colour.upper(),
escape_for_savesgf(self.my_name)))
try:
self.proxy.handle_command("gomill-savesgf", args)
except GtpError, e:
# Hide error from kgsGtp, though I don't suppose it would care
self.log("error: %s" % e)
def main():
kgs_proxy = Kgs_proxy(sys.argv[1:])
kgs_proxy.run()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,46 @@
"""Proxy for making mogo a better-behaved GTP engine.
This means the controller sees gomill's GTP implementation, not mogo's.
This makes quarry willing to run mogo, for example.
"""
import sys
from gomill import gtp_engine
from gomill import gtp_proxy
def handle_version(args):
# Override remarkably verbose version response
return "2007 public release"
def main(executable):
try:
if sys.argv[1] not in ("--9", "--13", "--19"):
raise ValueError
size = sys.argv[1][2:]
except Exception:
sys.exit("mogo_wrapper: first parameter must be --9, --13, or --19")
def handle_boardsize(args):
# No need to pass this down to mogo.
try:
if args[0] != size:
raise gtp_engine.GtpError("board size %s only please" % size)
except IndexError:
gtp_engine.report_bad_arguments()
proxy = gtp_proxy.Gtp_proxy()
proxy.set_back_end_subprocess([executable] + sys.argv[1:])
proxy.engine.add_command("version", handle_version)
proxy.engine.add_command("boardsize", handle_boardsize)
proxy.pass_command("boardsize", [size])
try:
proxy.run()
except KeyboardInterrupt:
sys.exit(1)
if __name__ == "__main__":
main("mogo")

View File

@ -0,0 +1,72 @@
"""Show the position from an SGF file.
This demonstrates the sgf and ascii_boards modules.
"""
import sys
from optparse import OptionParser
from gomill import ascii_boards
from gomill import sgf
from gomill import sgf_moves
def show_sgf_file(pathname, move_number):
f = open(pathname)
sgf_src = f.read()
f.close()
try:
sgf_game = sgf.Sgf_game.from_string(sgf_src)
except ValueError:
raise StandardError("bad sgf file")
try:
board, plays = sgf_moves.get_setup_and_moves(sgf_game)
except ValueError, e:
raise StandardError(str(e))
if move_number is not None:
move_number = max(0, move_number-1)
plays = plays[:move_number]
for colour, move in plays:
if move is None:
continue
row, col = move
try:
board.play(row, col, colour)
except ValueError:
raise StandardError("illegal move in sgf file")
print ascii_boards.render_board(board)
print
_description = """\
Show the position from an SGF file. If a move number is specified, the position
before that move is shown (this is to match the behaviour of GTP loadsgf).
"""
def main(argv):
parser = OptionParser(usage="%prog <filename> [move number]",
description=_description)
opts, args = parser.parse_args(argv)
if not args:
parser.error("not enough arguments")
pathname = args[0]
if len(args) > 2:
parser.error("too many arguments")
if len(args) == 2:
try:
move_number = int(args[1])
except ValueError:
parser.error("invalid integer value: %s" % args[1])
else:
move_number = None
try:
show_sgf_file(pathname, move_number)
except Exception, e:
print >>sys.stderr, "show_sgf:", str(e)
sys.exit(1)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -0,0 +1,54 @@
"""Split an SGF collection into separate files.
This demonstrates the parsing functions from the sgf_grammar module.
"""
import os
import sys
from optparse import OptionParser
from gomill import sgf_grammar
from gomill import sgf
def split_sgf_collection(pathname):
f = open(pathname)
sgf_src = f.read()
f.close()
dirname, basename = os.path.split(pathname)
root, ext = os.path.splitext(basename)
try:
coarse_games = sgf_grammar.parse_sgf_collection(sgf_src)
except ValueError, e:
raise StandardError("error parsing file: %s" % e)
for i, coarse_game in enumerate(coarse_games):
sgf_game = sgf.Sgf_game.from_coarse_game_tree(coarse_game)
sgf_game.get_root().add_comment_text(
"Split from %s (game %d)" % (basename, i+1))
split_pathname = os.path.join(dirname, "%s_%d%s" % (root, i+1, ext))
with open(split_pathname, "wb") as f:
f.write(sgf_game.serialise())
_description = """\
Split a file containing an SGF game collection into multiple files.
"""
def main(argv):
parser = OptionParser(usage="%prog <filename>",
description=_description)
opts, args = parser.parse_args(argv)
if not args:
parser.error("not enough arguments")
pathname = args[0]
if len(args) > 1:
parser.error("too many arguments")
try:
split_sgf_collection(pathname)
except Exception, e:
print >>sys.stderr, "sgf_splitter:", str(e)
sys.exit(1)
if __name__ == "__main__":
main(sys.argv[1:])

75
gomill/examples/twogtp Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env python
"""'Traditional' twogtp implementation.
This runs a single game between two local GTP engines and reports the result.
This demonstrates the gtp_games module.
"""
import shlex
import sys
from optparse import OptionParser, SUPPRESS_HELP
from gomill import ascii_boards
from gomill import gtp_games
from gomill.gtp_controller import GtpChannelError, BadGtpResponse
from gomill.common import format_vertex
def print_move(colour, move, board):
print colour.upper(), format_vertex(move)
def print_board(colour, move, board):
print colour.upper(), format_vertex(move)
print ascii_boards.render_board(board)
print
def main():
usage = "%prog [options] --black='<command>' --white='<command>'"
parser = OptionParser(usage=usage)
parser.add_option("--black", help=SUPPRESS_HELP)
parser.add_option("--white", help=SUPPRESS_HELP)
parser.add_option("--komi", type="float", default=7.5)
parser.add_option("--size", type="int", default=19)
parser.add_option("--verbose", type="choice", choices=('0','1','2'),
default=0, metavar="0|1|2")
(options, args) = parser.parse_args()
if args:
parser.error("too many arguments")
if not options.black or not options.white:
parser.error("players not specified")
black_command = shlex.split(options.black)
white_command = shlex.split(options.white)
game = gtp_games.Game(
board_size=options.size,
komi=options.komi,
move_limit=1000)
if black_command[0] != white_command[0]:
game.set_player_code('b', black_command[0])
game.set_player_code('w', white_command[0])
if options.verbose == '1':
game.set_move_callback(print_move)
elif options.verbose == '2':
game.set_move_callback(print_board)
game.allow_scorer('b')
game.allow_scorer('w')
try:
game.set_player_subprocess('b', black_command)
game.set_player_subprocess('w', white_command)
game.ready()
game.run()
except (GtpChannelError, BadGtpResponse), e:
sys.exit("aborting game due to error:\n%s\n" % e)
finally:
game.close_players()
print game.result.describe()
if game.late_errors:
sys.exit("\n".join(game.late_errors))
if __name__ == "__main__":
main()