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:
62
gnugo/engine/CMakeLists.txt
Normal file
62
gnugo/engine/CMakeLists.txt
Normal file
@ -0,0 +1,62 @@
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
INCLUDE_DIRECTORIES(${GNUGo_SOURCE_DIR}/patterns)
|
||||
INCLUDE_DIRECTORIES(${GNUGo_SOURCE_DIR}/sgf)
|
||||
INCLUDE_DIRECTORIES(${GNUGo_SOURCE_DIR}/utils)
|
||||
|
||||
|
||||
########### engine library ###############
|
||||
|
||||
SET(engine_STAT_SRCS
|
||||
aftermath.c
|
||||
board.c
|
||||
boardlib.c
|
||||
breakin.c
|
||||
cache.c
|
||||
clock.c
|
||||
combination.c
|
||||
dragon.c
|
||||
endgame.c
|
||||
filllib.c
|
||||
fuseki.c
|
||||
genmove.c
|
||||
globals.c
|
||||
handicap.c
|
||||
hash.c
|
||||
influence.c
|
||||
interface.c
|
||||
matchpat.c
|
||||
montecarlo.c
|
||||
move_reasons.c
|
||||
movelist.c
|
||||
optics.c
|
||||
oracle.c
|
||||
owl.c
|
||||
persistent.c
|
||||
printutils.c
|
||||
readconnect.c
|
||||
reading.c
|
||||
semeai.c
|
||||
sgfdecide.c
|
||||
sgffile.c
|
||||
shapes.c
|
||||
showbord.c
|
||||
surround.c
|
||||
unconditional.c
|
||||
utils.c
|
||||
value_moves.c
|
||||
worm.c
|
||||
)
|
||||
|
||||
ADD_LIBRARY(engine STATIC ${engine_STAT_SRCS})
|
||||
|
||||
|
||||
########### board library ###############
|
||||
|
||||
SET(board_STAT_SRCS
|
||||
board.c
|
||||
boardlib.c
|
||||
hash.c
|
||||
printutils.c
|
||||
)
|
||||
|
||||
ADD_LIBRARY(board STATIC ${board_STAT_SRCS})
|
63
gnugo/engine/Makefile.am
Normal file
63
gnugo/engine/Makefile.am
Normal file
@ -0,0 +1,63 @@
|
||||
EXTRA_DIST = engine.dsp board.dsp CMakeLists.txt
|
||||
|
||||
# Remove these files here... they are created locally
|
||||
DISTCLEANFILES = *~
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
$(GNU_GO_WARNINGS) \
|
||||
-I../patterns \
|
||||
-I$(top_srcdir)/patterns \
|
||||
-I$(top_srcdir)/sgf \
|
||||
-I$(top_srcdir)/utils
|
||||
|
||||
noinst_HEADERS = cache.h gnugo.h hash.h clock.h readconnect.h \
|
||||
influence.h liberty.h move_reasons.h board.h
|
||||
|
||||
# preconfigured settings for various configurations
|
||||
noinst_LIBRARIES = libengine.a libboard.a
|
||||
|
||||
libengine_a_SOURCES = \
|
||||
aftermath.c \
|
||||
board.c \
|
||||
boardlib.c \
|
||||
breakin.c \
|
||||
cache.c \
|
||||
clock.c \
|
||||
combination.c \
|
||||
dragon.c \
|
||||
endgame.c \
|
||||
filllib.c \
|
||||
fuseki.c \
|
||||
genmove.c \
|
||||
globals.c \
|
||||
handicap.c \
|
||||
hash.c \
|
||||
influence.c \
|
||||
interface.c \
|
||||
matchpat.c \
|
||||
montecarlo.c \
|
||||
move_reasons.c \
|
||||
movelist.c \
|
||||
optics.c \
|
||||
oracle.c \
|
||||
owl.c \
|
||||
persistent.c \
|
||||
printutils.c \
|
||||
readconnect.c \
|
||||
reading.c \
|
||||
semeai.c \
|
||||
sgfdecide.c \
|
||||
sgffile.c \
|
||||
shapes.c \
|
||||
showbord.c \
|
||||
surround.c \
|
||||
unconditional.c \
|
||||
utils.c \
|
||||
value_moves.c \
|
||||
worm.c
|
||||
|
||||
libboard_a_SOURCES = \
|
||||
board.c \
|
||||
boardlib.c \
|
||||
hash.c \
|
||||
printutils.c
|
509
gnugo/engine/Makefile.in
Normal file
509
gnugo/engine/Makefile.in
Normal file
@ -0,0 +1,509 @@
|
||||
# Makefile.in generated by automake 1.9.6 from Makefile.am.
|
||||
# @configure_input@
|
||||
|
||||
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
|
||||
# 2003, 2004, 2005 Free Software Foundation, Inc.
|
||||
# This Makefile.in is free software; the Free Software Foundation
|
||||
# gives unlimited permission to copy and/or distribute it,
|
||||
# with or without modifications, as long as this notice is preserved.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE.
|
||||
|
||||
@SET_MAKE@
|
||||
|
||||
|
||||
srcdir = @srcdir@
|
||||
top_srcdir = @top_srcdir@
|
||||
VPATH = @srcdir@
|
||||
pkgdatadir = $(datadir)/@PACKAGE@
|
||||
pkglibdir = $(libdir)/@PACKAGE@
|
||||
pkgincludedir = $(includedir)/@PACKAGE@
|
||||
top_builddir = ..
|
||||
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||
INSTALL = @INSTALL@
|
||||
install_sh_DATA = $(install_sh) -c -m 644
|
||||
install_sh_PROGRAM = $(install_sh) -c
|
||||
install_sh_SCRIPT = $(install_sh) -c
|
||||
INSTALL_HEADER = $(INSTALL_DATA)
|
||||
transform = $(program_transform_name)
|
||||
NORMAL_INSTALL = :
|
||||
PRE_INSTALL = :
|
||||
POST_INSTALL = :
|
||||
NORMAL_UNINSTALL = :
|
||||
PRE_UNINSTALL = :
|
||||
POST_UNINSTALL = :
|
||||
subdir = engine
|
||||
DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
|
||||
$(srcdir)/Makefile.in
|
||||
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||
am__aclocal_m4_deps = $(top_srcdir)/configure.in
|
||||
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
|
||||
$(ACLOCAL_M4)
|
||||
mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
|
||||
CONFIG_HEADER = $(top_builddir)/config.h
|
||||
CONFIG_CLEAN_FILES =
|
||||
LIBRARIES = $(noinst_LIBRARIES)
|
||||
AR = ar
|
||||
ARFLAGS = cru
|
||||
libboard_a_AR = $(AR) $(ARFLAGS)
|
||||
libboard_a_LIBADD =
|
||||
am_libboard_a_OBJECTS = board.$(OBJEXT) boardlib.$(OBJEXT) \
|
||||
hash.$(OBJEXT) printutils.$(OBJEXT)
|
||||
libboard_a_OBJECTS = $(am_libboard_a_OBJECTS)
|
||||
libengine_a_AR = $(AR) $(ARFLAGS)
|
||||
libengine_a_LIBADD =
|
||||
am_libengine_a_OBJECTS = aftermath.$(OBJEXT) board.$(OBJEXT) \
|
||||
boardlib.$(OBJEXT) breakin.$(OBJEXT) cache.$(OBJEXT) \
|
||||
clock.$(OBJEXT) combination.$(OBJEXT) dragon.$(OBJEXT) \
|
||||
endgame.$(OBJEXT) filllib.$(OBJEXT) fuseki.$(OBJEXT) \
|
||||
genmove.$(OBJEXT) globals.$(OBJEXT) handicap.$(OBJEXT) \
|
||||
hash.$(OBJEXT) influence.$(OBJEXT) interface.$(OBJEXT) \
|
||||
matchpat.$(OBJEXT) montecarlo.$(OBJEXT) move_reasons.$(OBJEXT) \
|
||||
movelist.$(OBJEXT) optics.$(OBJEXT) oracle.$(OBJEXT) \
|
||||
owl.$(OBJEXT) persistent.$(OBJEXT) printutils.$(OBJEXT) \
|
||||
readconnect.$(OBJEXT) reading.$(OBJEXT) semeai.$(OBJEXT) \
|
||||
sgfdecide.$(OBJEXT) sgffile.$(OBJEXT) shapes.$(OBJEXT) \
|
||||
showbord.$(OBJEXT) surround.$(OBJEXT) unconditional.$(OBJEXT) \
|
||||
utils.$(OBJEXT) value_moves.$(OBJEXT) worm.$(OBJEXT)
|
||||
libengine_a_OBJECTS = $(am_libengine_a_OBJECTS)
|
||||
DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
|
||||
depcomp = $(SHELL) $(top_srcdir)/depcomp
|
||||
am__depfiles_maybe = depfiles
|
||||
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
|
||||
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
|
||||
CCLD = $(CC)
|
||||
LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
|
||||
SOURCES = $(libboard_a_SOURCES) $(libengine_a_SOURCES)
|
||||
DIST_SOURCES = $(libboard_a_SOURCES) $(libengine_a_SOURCES)
|
||||
HEADERS = $(noinst_HEADERS)
|
||||
ETAGS = etags
|
||||
CTAGS = ctags
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
ACLOCAL = @ACLOCAL@
|
||||
AMDEP_FALSE = @AMDEP_FALSE@
|
||||
AMDEP_TRUE = @AMDEP_TRUE@
|
||||
AMTAR = @AMTAR@
|
||||
AUTOCONF = @AUTOCONF@
|
||||
AUTOHEADER = @AUTOHEADER@
|
||||
AUTOMAKE = @AUTOMAKE@
|
||||
AWK = @AWK@
|
||||
CC = @CC@
|
||||
CCDEPMODE = @CCDEPMODE@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPP = @CPP@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
CYGPATH_W = @CYGPATH_W@
|
||||
DEFS = @DEFS@
|
||||
DEPDIR = @DEPDIR@
|
||||
DFA_ENABLED_FALSE = @DFA_ENABLED_FALSE@
|
||||
DFA_ENABLED_TRUE = @DFA_ENABLED_TRUE@
|
||||
ECHO_C = @ECHO_C@
|
||||
ECHO_N = @ECHO_N@
|
||||
ECHO_T = @ECHO_T@
|
||||
EGREP = @EGREP@
|
||||
EXEEXT = @EXEEXT@
|
||||
GCC_MAJOR_VERSION = @GCC_MAJOR_VERSION@
|
||||
GCC_MINOR_VERSION = @GCC_MINOR_VERSION@
|
||||
GCC_ONLY_FALSE = @GCC_ONLY_FALSE@
|
||||
GCC_ONLY_TRUE = @GCC_ONLY_TRUE@
|
||||
GNU_GO_WARNINGS = @GNU_GO_WARNINGS@
|
||||
GREP = @GREP@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||
INSTALL_SCRIPT = @INSTALL_SCRIPT@
|
||||
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBOBJS = @LIBOBJS@
|
||||
LIBS = @LIBS@
|
||||
LTLIBOBJS = @LTLIBOBJS@
|
||||
MAINT = @MAINT@
|
||||
MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@
|
||||
MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@
|
||||
MAKEINFO = @MAKEINFO@
|
||||
OBJEXT = @OBJEXT@
|
||||
PACKAGE = @PACKAGE@
|
||||
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
|
||||
PACKAGE_NAME = @PACKAGE_NAME@
|
||||
PACKAGE_STRING = @PACKAGE_STRING@
|
||||
PACKAGE_TARNAME = @PACKAGE_TARNAME@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||
RANLIB = @RANLIB@
|
||||
SET_MAKE = @SET_MAKE@
|
||||
SHELL = @SHELL@
|
||||
STRIP = @STRIP@
|
||||
VERSION = @VERSION@
|
||||
ac_ct_CC = @ac_ct_CC@
|
||||
am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
|
||||
am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
|
||||
am__include = @am__include@
|
||||
am__leading_dot = @am__leading_dot@
|
||||
am__quote = @am__quote@
|
||||
am__tar = @am__tar@
|
||||
am__untar = @am__untar@
|
||||
bindir = @bindir@
|
||||
build_alias = @build_alias@
|
||||
datadir = @datadir@
|
||||
datarootdir = @datarootdir@
|
||||
docdir = @docdir@
|
||||
dvidir = @dvidir@
|
||||
exec_prefix = @exec_prefix@
|
||||
glibconfig = @glibconfig@
|
||||
host_alias = @host_alias@
|
||||
htmldir = @htmldir@
|
||||
includedir = @includedir@
|
||||
infodir = @infodir@
|
||||
install_sh = @install_sh@
|
||||
libdir = @libdir@
|
||||
libexecdir = @libexecdir@
|
||||
localedir = @localedir@
|
||||
localstatedir = @localstatedir@
|
||||
mandir = @mandir@
|
||||
mkdir_p = @mkdir_p@
|
||||
oldincludedir = @oldincludedir@
|
||||
pdfdir = @pdfdir@
|
||||
prefix = @prefix@
|
||||
program_transform_name = @program_transform_name@
|
||||
psdir = @psdir@
|
||||
sbindir = @sbindir@
|
||||
sharedstatedir = @sharedstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
target_alias = @target_alias@
|
||||
EXTRA_DIST = engine.dsp board.dsp CMakeLists.txt
|
||||
|
||||
# Remove these files here... they are created locally
|
||||
DISTCLEANFILES = *~
|
||||
AM_CPPFLAGS = \
|
||||
$(GNU_GO_WARNINGS) \
|
||||
-I../patterns \
|
||||
-I$(top_srcdir)/patterns \
|
||||
-I$(top_srcdir)/sgf \
|
||||
-I$(top_srcdir)/utils
|
||||
|
||||
noinst_HEADERS = cache.h gnugo.h hash.h clock.h readconnect.h \
|
||||
influence.h liberty.h move_reasons.h board.h
|
||||
|
||||
|
||||
# preconfigured settings for various configurations
|
||||
noinst_LIBRARIES = libengine.a libboard.a
|
||||
libengine_a_SOURCES = \
|
||||
aftermath.c \
|
||||
board.c \
|
||||
boardlib.c \
|
||||
breakin.c \
|
||||
cache.c \
|
||||
clock.c \
|
||||
combination.c \
|
||||
dragon.c \
|
||||
endgame.c \
|
||||
filllib.c \
|
||||
fuseki.c \
|
||||
genmove.c \
|
||||
globals.c \
|
||||
handicap.c \
|
||||
hash.c \
|
||||
influence.c \
|
||||
interface.c \
|
||||
matchpat.c \
|
||||
montecarlo.c \
|
||||
move_reasons.c \
|
||||
movelist.c \
|
||||
optics.c \
|
||||
oracle.c \
|
||||
owl.c \
|
||||
persistent.c \
|
||||
printutils.c \
|
||||
readconnect.c \
|
||||
reading.c \
|
||||
semeai.c \
|
||||
sgfdecide.c \
|
||||
sgffile.c \
|
||||
shapes.c \
|
||||
showbord.c \
|
||||
surround.c \
|
||||
unconditional.c \
|
||||
utils.c \
|
||||
value_moves.c \
|
||||
worm.c
|
||||
|
||||
libboard_a_SOURCES = \
|
||||
board.c \
|
||||
boardlib.c \
|
||||
hash.c \
|
||||
printutils.c
|
||||
|
||||
all: all-am
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .o .obj
|
||||
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
|
||||
@for dep in $?; do \
|
||||
case '$(am__configure_deps)' in \
|
||||
*$$dep*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
|
||||
&& exit 0; \
|
||||
exit 1;; \
|
||||
esac; \
|
||||
done; \
|
||||
echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu engine/Makefile'; \
|
||||
cd $(top_srcdir) && \
|
||||
$(AUTOMAKE) --gnu engine/Makefile
|
||||
.PRECIOUS: Makefile
|
||||
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||
@case '$?' in \
|
||||
*config.status*) \
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
|
||||
*) \
|
||||
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
|
||||
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
|
||||
esac;
|
||||
|
||||
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
|
||||
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
|
||||
|
||||
clean-noinstLIBRARIES:
|
||||
-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
|
||||
libboard.a: $(libboard_a_OBJECTS) $(libboard_a_DEPENDENCIES)
|
||||
-rm -f libboard.a
|
||||
$(libboard_a_AR) libboard.a $(libboard_a_OBJECTS) $(libboard_a_LIBADD)
|
||||
$(RANLIB) libboard.a
|
||||
libengine.a: $(libengine_a_OBJECTS) $(libengine_a_DEPENDENCIES)
|
||||
-rm -f libengine.a
|
||||
$(libengine_a_AR) libengine.a $(libengine_a_OBJECTS) $(libengine_a_LIBADD)
|
||||
$(RANLIB) libengine.a
|
||||
|
||||
mostlyclean-compile:
|
||||
-rm -f *.$(OBJEXT)
|
||||
|
||||
distclean-compile:
|
||||
-rm -f *.tab.c
|
||||
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/aftermath.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/board.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boardlib.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/breakin.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cache.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/clock.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/combination.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dragon.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/endgame.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filllib.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fuseki.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/genmove.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/globals.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handicap.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/influence.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/interface.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matchpat.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/montecarlo.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/move_reasons.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/movelist.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/optics.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/oracle.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/owl.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/persistent.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printutils.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readconnect.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reading.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/semeai.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgfdecide.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgffile.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shapes.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/showbord.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/surround.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unconditional.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/value_moves.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/worm.Po@am__quote@
|
||||
|
||||
.c.o:
|
||||
@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
|
||||
@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
|
||||
@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCC_FALSE@ $(COMPILE) -c $<
|
||||
|
||||
.c.obj:
|
||||
@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
|
||||
@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
|
||||
@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
|
||||
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
|
||||
@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
|
||||
uninstall-info-am:
|
||||
|
||||
ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
|
||||
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | \
|
||||
$(AWK) ' { files[$$0] = 1; } \
|
||||
END { for (i in files) print i; }'`; \
|
||||
mkid -fID $$unique
|
||||
tags: TAGS
|
||||
|
||||
TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
|
||||
$(TAGS_FILES) $(LISP)
|
||||
tags=; \
|
||||
here=`pwd`; \
|
||||
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | \
|
||||
$(AWK) ' { files[$$0] = 1; } \
|
||||
END { for (i in files) print i; }'`; \
|
||||
if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
|
||||
test -n "$$unique" || unique=$$empty_fix; \
|
||||
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||
$$tags $$unique; \
|
||||
fi
|
||||
ctags: CTAGS
|
||||
CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
|
||||
$(TAGS_FILES) $(LISP)
|
||||
tags=; \
|
||||
here=`pwd`; \
|
||||
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
|
||||
unique=`for i in $$list; do \
|
||||
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||
done | \
|
||||
$(AWK) ' { files[$$0] = 1; } \
|
||||
END { for (i in files) print i; }'`; \
|
||||
test -z "$(CTAGS_ARGS)$$tags$$unique" \
|
||||
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
|
||||
$$tags $$unique
|
||||
|
||||
GTAGS:
|
||||
here=`$(am__cd) $(top_builddir) && pwd` \
|
||||
&& cd $(top_srcdir) \
|
||||
&& gtags -i $(GTAGS_ARGS) $$here
|
||||
|
||||
distclean-tags:
|
||||
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
|
||||
|
||||
distdir: $(DISTFILES)
|
||||
@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
|
||||
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
|
||||
list='$(DISTFILES)'; for file in $$list; do \
|
||||
case $$file in \
|
||||
$(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
|
||||
$(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
|
||||
esac; \
|
||||
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||
dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||
if test "$$dir" != "$$file" && test "$$dir" != "."; then \
|
||||
dir="/$$dir"; \
|
||||
$(mkdir_p) "$(distdir)$$dir"; \
|
||||
else \
|
||||
dir=''; \
|
||||
fi; \
|
||||
if test -d $$d/$$file; then \
|
||||
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||
cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
|
||||
fi; \
|
||||
cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
|
||||
else \
|
||||
test -f $(distdir)/$$file \
|
||||
|| cp -p $$d/$$file $(distdir)/$$file \
|
||||
|| exit 1; \
|
||||
fi; \
|
||||
done
|
||||
check-am: all-am
|
||||
check: check-am
|
||||
all-am: Makefile $(LIBRARIES) $(HEADERS)
|
||||
installdirs:
|
||||
install: install-am
|
||||
install-exec: install-exec-am
|
||||
install-data: install-data-am
|
||||
uninstall: uninstall-am
|
||||
|
||||
install-am: all-am
|
||||
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||
|
||||
installcheck: installcheck-am
|
||||
install-strip:
|
||||
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||
`test -z '$(STRIP)' || \
|
||||
echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
|
||||
mostlyclean-generic:
|
||||
|
||||
clean-generic:
|
||||
|
||||
distclean-generic:
|
||||
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
|
||||
-test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
|
||||
|
||||
maintainer-clean-generic:
|
||||
@echo "This command is intended for maintainers to use"
|
||||
@echo "it deletes files that may require special tools to rebuild."
|
||||
clean: clean-am
|
||||
|
||||
clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
|
||||
|
||||
distclean: distclean-am
|
||||
-rm -rf ./$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
distclean-am: clean-am distclean-compile distclean-generic \
|
||||
distclean-tags
|
||||
|
||||
dvi: dvi-am
|
||||
|
||||
dvi-am:
|
||||
|
||||
html: html-am
|
||||
|
||||
info: info-am
|
||||
|
||||
info-am:
|
||||
|
||||
install-data-am:
|
||||
|
||||
install-exec-am:
|
||||
|
||||
install-info: install-info-am
|
||||
|
||||
install-man:
|
||||
|
||||
installcheck-am:
|
||||
|
||||
maintainer-clean: maintainer-clean-am
|
||||
-rm -rf ./$(DEPDIR)
|
||||
-rm -f Makefile
|
||||
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||
|
||||
mostlyclean: mostlyclean-am
|
||||
|
||||
mostlyclean-am: mostlyclean-compile mostlyclean-generic
|
||||
|
||||
pdf: pdf-am
|
||||
|
||||
pdf-am:
|
||||
|
||||
ps: ps-am
|
||||
|
||||
ps-am:
|
||||
|
||||
uninstall-am: uninstall-info-am
|
||||
|
||||
.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
|
||||
clean-noinstLIBRARIES ctags distclean distclean-compile \
|
||||
distclean-generic distclean-tags distdir dvi dvi-am html \
|
||||
html-am info info-am install install-am install-data \
|
||||
install-data-am install-exec install-exec-am install-info \
|
||||
install-info-am install-man install-strip installcheck \
|
||||
installcheck-am installdirs maintainer-clean \
|
||||
maintainer-clean-generic mostlyclean mostlyclean-compile \
|
||||
mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
|
||||
uninstall-am uninstall-info-am
|
||||
|
||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||
.NOEXPORT:
|
1207
gnugo/engine/aftermath.c
Normal file
1207
gnugo/engine/aftermath.c
Normal file
File diff suppressed because it is too large
Load Diff
4331
gnugo/engine/board.c
Normal file
4331
gnugo/engine/board.c
Normal file
File diff suppressed because it is too large
Load Diff
133
gnugo/engine/board.dsp
Normal file
133
gnugo/engine/board.dsp
Normal file
@ -0,0 +1,133 @@
|
||||
# Microsoft Developer Studio Project File - Name="board" - Package Owner=<4>
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 6.00
|
||||
# ** DO NOT EDIT **
|
||||
|
||||
# TARGTYPE "Win32 (x86) Static Library" 0x0104
|
||||
|
||||
CFG=board - Win32 Debug
|
||||
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
|
||||
!MESSAGE use the Export Makefile command and run
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "board.mak".
|
||||
!MESSAGE
|
||||
!MESSAGE You can specify a configuration when running NMAKE
|
||||
!MESSAGE by defining the macro CFG on the command line. For example:
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "board.mak" CFG="board - Win32 Debug"
|
||||
!MESSAGE
|
||||
!MESSAGE Possible choices for configuration are:
|
||||
!MESSAGE
|
||||
!MESSAGE "board - Win32 Release" (based on "Win32 (x86) Static Library")
|
||||
!MESSAGE "board - Win32 Debug" (based on "Win32 (x86) Static Library")
|
||||
!MESSAGE
|
||||
|
||||
# Begin Project
|
||||
# PROP AllowPerConfigDependencies 0
|
||||
# PROP Scc_ProjName ""
|
||||
# PROP Scc_LocalPath ""
|
||||
CPP=cl.exe
|
||||
RSC=rc.exe
|
||||
|
||||
!IF "$(CFG)" == "board - Win32 Release"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 0
|
||||
# PROP BASE Output_Dir "Release"
|
||||
# PROP BASE Intermediate_Dir "Release"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 0
|
||||
# PROP Output_Dir "Release"
|
||||
# PROP Intermediate_Dir "Release"
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
|
||||
# ADD CPP /GX /Zi /O2 /I "." /I ".." /I "..\sgf" /I "..\interface" /I "..\patterns" /I "..\utils" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /YX"gnugo.h" /Fd"Release/board" /FD /c
|
||||
# ADD BASE RSC /l 0x409 /d "NDEBUG"
|
||||
# ADD RSC /l 0x409 /d "NDEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LIB32=link.exe -lib
|
||||
# ADD BASE LIB32 /nologo
|
||||
# ADD LIB32 /nologo
|
||||
|
||||
!ELSEIF "$(CFG)" == "board - Win32 Debug"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 1
|
||||
# PROP BASE Output_Dir "board___Win32_Debug"
|
||||
# PROP BASE Intermediate_Dir "board___Win32_Debug"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 1
|
||||
# PROP Output_Dir "Debug"
|
||||
# PROP Intermediate_Dir "Debug"
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
|
||||
# ADD CPP /W2 /Gm /GX /ZI /Od /I "." /I ".." /I "..\sgf" /I "..\interface" /I "..\patterns" /I "..\utils" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "HAVE_CONFIG_H" /D "_LIB" /FR /YX"gnugo.h" /Fd"Debug/board" /FD /GZ /c
|
||||
# ADD BASE RSC /l 0x409 /d "_DEBUG"
|
||||
# ADD RSC /l 0x409 /d "_DEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LIB32=link.exe -lib
|
||||
# ADD BASE LIB32 /nologo
|
||||
# ADD LIB32 /nologo
|
||||
|
||||
!ENDIF
|
||||
|
||||
# Begin Target
|
||||
|
||||
# Name "board - Win32 Release"
|
||||
# Name "board - Win32 Debug"
|
||||
# Begin Group "Source Files"
|
||||
|
||||
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\board.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\boardlib.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\cache.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\globals.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\hash.c
|
||||
|
||||
!IF "$(CFG)" == "board - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "board - Win32 Debug"
|
||||
|
||||
# PROP Intermediate_Dir "Debug"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\printutils.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\sgffile.c
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Header Files"
|
||||
|
||||
# PROP Default_Filter "h;hpp;hxx;hm;inl"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\board.h
|
||||
# End Source File
|
||||
# End Group
|
||||
# End Target
|
||||
# End Project
|
476
gnugo/engine/board.h
Normal file
476
gnugo/engine/board.h
Normal file
@ -0,0 +1,476 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef _BOARD_H_
|
||||
#define _BOARD_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "config.h"
|
||||
#include "sgftree.h"
|
||||
#include "winsocket.h"
|
||||
|
||||
/* This type is used to store each intersection on the board.
|
||||
*
|
||||
* On a 486, char is best, since the time taken to push and pop
|
||||
* becomes significant otherwise. On other platforms, an int may
|
||||
* be better, e.g. if memcpy() is particularly fast, or if
|
||||
* character access is very slow.
|
||||
*/
|
||||
|
||||
typedef unsigned char Intersection;
|
||||
|
||||
/* FIXME: This is very ugly but we can't include hash.h until we have
|
||||
* defined Intersection. And we do need to include it before using
|
||||
* Hash_data.
|
||||
*/
|
||||
#include "hash.h"
|
||||
|
||||
/* local versions of absolute value, min and max */
|
||||
|
||||
#define gg_abs(x) ((x) < 0 ? -(x) : (x))
|
||||
#define gg_min(a, b) ((a)<(b) ? (a) : (b))
|
||||
#define gg_max(a, b) ((a)<(b) ? (b) : (a))
|
||||
|
||||
/* Avoid compiler warnings with unused parameters */
|
||||
#define UNUSED(x) (void)x
|
||||
|
||||
|
||||
/* A string with n stones can have at most 2(n+1) liberties. From this
|
||||
* follows that an upper bound on the number of liberties of a string
|
||||
* on a board of size N^2 is 2/3 (N^2+1).
|
||||
*/
|
||||
#define MAXLIBS (2*(MAX_BOARD*MAX_BOARD + 1)/3)
|
||||
/* This is a smaller, practical number of liberties that we care to keep track of. */
|
||||
#define MAX_LIBERTIES 8
|
||||
|
||||
|
||||
/* This is an upper bound on the number of strings that can exist on
|
||||
* the board simultaneously. Since each string must have at least one
|
||||
* liberty and each empty point can provide a liberty to at most four
|
||||
* strings, at least one out of five board points must be empty.
|
||||
*
|
||||
* FIXME: This is not sufficiently large. Above stackp==0, the
|
||||
* incremental board code doesn't re-use the entries for
|
||||
* removed or merged strings, while new strings require new
|
||||
* entries. This is a problem only in very pathological cases,
|
||||
* and is extremely unlikely to occur in practice.
|
||||
*
|
||||
* Actually, in the not all that pathological case of a
|
||||
* repeated triple ko cycle, each move creates a new string and
|
||||
* thus makes use of one more string, which relatively quickly
|
||||
* will exhaust the available strings. For a safe upper bound
|
||||
* MAX_STRINGS should be set to
|
||||
* MAX_STACK + 4 * MAX_BOARD * MAX_BOARD / 5.
|
||||
* It's not clear that it's worth the extra memory, however.
|
||||
*/
|
||||
#define MAX_STRINGS (4 * MAX_BOARD * MAX_BOARD / 5)
|
||||
|
||||
/* Per gf: Unconditional_life() can get very close to filling the
|
||||
* entire board under certain circumstances. This was discussed in
|
||||
* the list around August 21, 2001, in a thread with the subject
|
||||
* "gnugo bug logs".
|
||||
*/
|
||||
#define MAXSTACK MAX_BOARD * MAX_BOARD
|
||||
#define MAXCHAIN 160
|
||||
|
||||
#define HASH_RANDOM_SEED 12345
|
||||
|
||||
/* ================================================================ *
|
||||
* One-dimensional board *
|
||||
* ================================================================ */
|
||||
|
||||
/* Board sizes */
|
||||
|
||||
|
||||
#define MIN_BOARD 1 /* Minimum supported board size. */
|
||||
#define MAX_BOARD 19 /* Maximum supported board size. */
|
||||
#define MAX_HANDICAP 9 /* Maximum supported handicap. */
|
||||
#define MAX_MOVE_HISTORY 500 /* Max number of moves remembered. */
|
||||
|
||||
#define DEFAULT_BOARD_SIZE MAX_BOARD
|
||||
|
||||
/* Colors and komaster states. */
|
||||
enum colors {
|
||||
EMPTY,
|
||||
WHITE,
|
||||
BLACK,
|
||||
GRAY,
|
||||
GRAY_WHITE,
|
||||
GRAY_BLACK,
|
||||
WEAK_KO,
|
||||
NUM_KOMASTER_STATES
|
||||
};
|
||||
|
||||
#define COLOR_NAMES \
|
||||
"empty", \
|
||||
"white", \
|
||||
"black", \
|
||||
"gray", \
|
||||
"gray_white", \
|
||||
"gray_black", \
|
||||
"weak_ko"
|
||||
|
||||
const char *color_to_string(int color);
|
||||
|
||||
#define OTHER_COLOR(color) (WHITE+BLACK-(color))
|
||||
#define IS_STONE(arg) ((arg) == WHITE || (arg) == BLACK)
|
||||
|
||||
/* Note that POS(-1, -1) == 0
|
||||
* DELTA() is defined so that POS(i+di, j+dj) = POS(i, j) + DELTA(di, dj).
|
||||
*/
|
||||
#define BOARDSIZE ((MAX_BOARD + 2) * (MAX_BOARD + 1) + 1)
|
||||
#define BOARDMIN (MAX_BOARD + 2)
|
||||
#define BOARDMAX (MAX_BOARD + 1) * (MAX_BOARD + 1)
|
||||
#define POS(i, j) ((MAX_BOARD + 2) + (i) * (MAX_BOARD + 1) + (j))
|
||||
#define DELTA(di, dj) ((di) * (MAX_BOARD + 1) + (dj))
|
||||
#define I(pos) ((pos) / (MAX_BOARD + 1) - 1)
|
||||
#define J(pos) ((pos) % (MAX_BOARD + 1) - 1)
|
||||
#define PASS_MOVE 0
|
||||
#define NO_MOVE PASS_MOVE
|
||||
#define NS (MAX_BOARD + 1)
|
||||
#define WE 1
|
||||
#define SOUTH(pos) ((pos) + NS)
|
||||
#define WEST(pos) ((pos) - 1)
|
||||
#define NORTH(pos) ((pos) - NS)
|
||||
#define EAST(pos) ((pos) + 1)
|
||||
#define SW(pos) ((pos) + NS - 1)
|
||||
#define NW(pos) ((pos) - NS - 1)
|
||||
#define NE(pos) ((pos) - NS + 1)
|
||||
#define SE(pos) ((pos) + NS + 1)
|
||||
#define SS(pos) ((pos) + 2 * NS)
|
||||
#define WW(pos) ((pos) - 2)
|
||||
#define NN(pos) ((pos) - 2 * NS)
|
||||
#define EE(pos) ((pos) + 2)
|
||||
|
||||
#define DIRECT_NEIGHBORS(pos1, pos2) \
|
||||
((pos1) == SOUTH(pos2) \
|
||||
|| (pos1) == WEST(pos2) \
|
||||
|| (pos1) == NORTH(pos2) \
|
||||
|| (pos1) == EAST(pos2))
|
||||
|
||||
#define DIAGONAL_NEIGHBORS(pos1, pos2) \
|
||||
((pos1) == SW(pos2) \
|
||||
|| (pos1) == NW(pos2) \
|
||||
|| (pos1) == NE(pos2) \
|
||||
|| (pos1) == SE(pos2))
|
||||
|
||||
#define BOARD(i, j) board[POS(i, j)]
|
||||
|
||||
|
||||
#define MIRROR_MOVE(pos) POS(board_size - 1 - I(pos), board_size - 1 - J(pos))
|
||||
|
||||
/* ================================================================ */
|
||||
/* global variables */
|
||||
/* ================================================================ */
|
||||
|
||||
/* The board and the other parameters deciding the current position. */
|
||||
extern int board_size; /* board size (usually 19) */
|
||||
extern Intersection board[BOARDSIZE]; /* go board */
|
||||
extern int board_ko_pos;
|
||||
extern int black_captured; /* num. of black stones captured */
|
||||
extern int white_captured;
|
||||
|
||||
extern Intersection initial_board[BOARDSIZE];
|
||||
extern int initial_board_ko_pos;
|
||||
extern int initial_white_captured;
|
||||
extern int initial_black_captured;
|
||||
extern int move_history_color[MAX_MOVE_HISTORY];
|
||||
extern int move_history_pos[MAX_MOVE_HISTORY];
|
||||
extern Hash_data move_history_hash[MAX_MOVE_HISTORY];
|
||||
extern int move_history_pointer;
|
||||
|
||||
extern float komi;
|
||||
extern int handicap; /* used internally in chinese scoring */
|
||||
extern int movenum; /* movenumber - used for debug output */
|
||||
|
||||
extern signed char shadow[BOARDMAX]; /* reading tree shadow */
|
||||
|
||||
enum suicide_rules {
|
||||
FORBIDDEN,
|
||||
ALLOWED,
|
||||
ALL_ALLOWED
|
||||
};
|
||||
extern enum suicide_rules suicide_rule;
|
||||
|
||||
enum ko_rules {
|
||||
SIMPLE,
|
||||
NONE,
|
||||
PSK,
|
||||
SSK
|
||||
};
|
||||
extern enum ko_rules ko_rule;
|
||||
|
||||
|
||||
extern int stackp; /* stack pointer */
|
||||
extern int count_variations; /* count (decidestring) */
|
||||
extern SGFTree *sgf_dumptree;
|
||||
|
||||
|
||||
/* This struct holds the internal board state. */
|
||||
struct board_state {
|
||||
int board_size;
|
||||
|
||||
Intersection board[BOARDSIZE];
|
||||
int board_ko_pos;
|
||||
int black_captured;
|
||||
int white_captured;
|
||||
|
||||
Intersection initial_board[BOARDSIZE];
|
||||
int initial_board_ko_pos;
|
||||
int initial_white_captured;
|
||||
int initial_black_captured;
|
||||
int move_history_color[MAX_MOVE_HISTORY];
|
||||
int move_history_pos[MAX_MOVE_HISTORY];
|
||||
Hash_data move_history_hash[MAX_MOVE_HISTORY];
|
||||
int move_history_pointer;
|
||||
|
||||
float komi;
|
||||
int handicap;
|
||||
int move_number;
|
||||
};
|
||||
|
||||
/* This is increased by one anytime a move is (permanently) played or
|
||||
* the board is cleared.
|
||||
*/
|
||||
extern int position_number;
|
||||
|
||||
/* ================================================================ */
|
||||
/* board.c functions */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* Functions handling the permanent board state. */
|
||||
void clear_board(void);
|
||||
int test_gray_border(void);
|
||||
void setup_board(Intersection new_board[MAX_BOARD][MAX_BOARD], int ko_pos,
|
||||
int *last, float new_komi, int w_captured, int b_captured);
|
||||
void add_stone(int pos, int color);
|
||||
void remove_stone(int pos);
|
||||
void play_move(int pos, int color);
|
||||
int undo_move(int n);
|
||||
|
||||
void store_board(struct board_state *state);
|
||||
void restore_board(struct board_state *state);
|
||||
|
||||
/* Information about the permanent board. */
|
||||
int get_last_move(void);
|
||||
int get_last_player(void);
|
||||
int get_last_opponent_move(int color);
|
||||
int stones_on_board(int color);
|
||||
|
||||
/* Functions handling the variable board state. */
|
||||
int trymove(int pos, int color, const char *message, int str);
|
||||
int tryko(int pos, int color, const char *message);
|
||||
void popgo(void);
|
||||
int komaster_trymove(int pos, int color,
|
||||
const char *message, int str,
|
||||
int *is_conditional_ko, int consider_conditional_ko);
|
||||
int get_komaster(void);
|
||||
int get_kom_pos(void);
|
||||
|
||||
int move_in_stack(int pos, int cutoff);
|
||||
void get_move_from_stack(int k, int *move, int *color);
|
||||
void dump_stack(void);
|
||||
void do_dump_stack(void);
|
||||
|
||||
void reset_trymove_counter(void);
|
||||
int get_trymove_counter(void);
|
||||
|
||||
/* move properties */
|
||||
int is_pass(int pos);
|
||||
int is_legal(int pos, int color);
|
||||
int is_suicide(int pos, int color);
|
||||
int is_illegal_ko_capture(int pos, int color);
|
||||
int is_allowed_move(int pos, int color);
|
||||
int is_ko(int pos, int color, int *ko_pos);
|
||||
int is_ko_point(int pos);
|
||||
int does_capture_something(int pos, int color);
|
||||
int is_self_atari(int pos, int color);
|
||||
|
||||
/* Purely geometric functions. */
|
||||
int is_edge_vertex(int pos);
|
||||
int is_corner_vertex(int pos);
|
||||
int edge_distance(int pos);
|
||||
int square_dist(int pos1, int pos2);
|
||||
int rotate1(int pos, int rot);
|
||||
|
||||
/* Basic string information. */
|
||||
int find_origin(int str);
|
||||
int chainlinks(int str, int adj[MAXCHAIN]);
|
||||
int chainlinks2(int str, int adj[MAXCHAIN], int lib);
|
||||
int chainlinks3(int str, int adj[MAXCHAIN], int lib);
|
||||
int extended_chainlinks(int str, int adj[MAXCHAIN], int both_colors);
|
||||
|
||||
int liberty_of_string(int pos, int str);
|
||||
int second_order_liberty_of_string(int pos, int str);
|
||||
int neighbor_of_string(int pos, int str);
|
||||
int has_neighbor(int pos, int color);
|
||||
int same_string(int str1, int str2);
|
||||
int adjacent_strings(int str1, int str2);
|
||||
void mark_string(int str, signed char mx[BOARDMAX], signed char mark);
|
||||
int are_neighbors(int pos1, int pos2);
|
||||
|
||||
/* Count and/or find liberties at (pos). */
|
||||
int countlib(int str);
|
||||
int findlib(int str, int maxlib, int *libs);
|
||||
int fastlib(int pos, int color, int ignore_captures);
|
||||
int approxlib(int pos, int color, int maxlib, int *libs);
|
||||
int accuratelib(int pos, int color, int maxlib, int *libs);
|
||||
int count_common_libs(int str1, int str2);
|
||||
int find_common_libs(int str1, int str2, int maxlib, int *libs);
|
||||
int have_common_lib(int str1, int str2, int *lib);
|
||||
|
||||
/* Count the number of stones in a string. */
|
||||
int countstones(int str);
|
||||
int findstones(int str, int maxstones, int *stones);
|
||||
int count_adjacent_stones(int str1, int str2, int maxstones);
|
||||
|
||||
/* Detect a special shape. */
|
||||
int send_two_return_one(int move, int color);
|
||||
|
||||
/* Special function for reading.c */
|
||||
void incremental_order_moves(int move, int color, int string,
|
||||
int *number_edges, int *number_same_string,
|
||||
int *number_own, int *number_opponent,
|
||||
int *captured_stones, int *threatened_stones,
|
||||
int *saved_stones, int *number_open);
|
||||
|
||||
/* Board caches initialization functions. */
|
||||
void clear_approxlib_cache(void);
|
||||
void clear_accuratelib_cache(void);
|
||||
|
||||
|
||||
/* Is this point inside the board? */
|
||||
#if 0
|
||||
#define ON_BOARD2(i, j) ((i)>=0 && (j)>=0 && (i)<board_size && (j)<board_size)
|
||||
#else
|
||||
/*
|
||||
* For the case when expr can only be slightly negative,
|
||||
* if (expr < 0 || expr > something)
|
||||
* is equivalent to
|
||||
* if ((unsigned) expr > something)
|
||||
*
|
||||
* (I think gcc knows this trick, but it does no harm to
|
||||
* encode it explicitly since it saves typing !)
|
||||
*/
|
||||
#define ON_BOARD2(i, j) ((unsigned) (i) < (unsigned) board_size &&\
|
||||
(unsigned) (j) < (unsigned) board_size)
|
||||
#endif
|
||||
|
||||
#define ASSERT_ON_BOARD2(i, j) ASSERT2(ON_BOARD2((i), (j)), (i), (j))
|
||||
|
||||
#define ON_BOARD1(pos) (((unsigned) (pos) < BOARDSIZE) && board[pos] != GRAY)
|
||||
#define ON_BOARD(pos) (board[pos] != GRAY)
|
||||
#define ASSERT_ON_BOARD1(pos) ASSERT1(ON_BOARD1(pos), (pos))
|
||||
|
||||
/* Coordinates for the eight directions, ordered
|
||||
* south, west, north, east, southwest, northwest, northeast, southeast.
|
||||
* Defined in board.c.
|
||||
*/
|
||||
extern int deltai[8]; /* = { 1, 0, -1, 0, 1, -1, -1, 1}; */
|
||||
extern int deltaj[8]; /* = { 0, -1, 0, 1, -1, -1, 1, 1}; */
|
||||
extern int delta[8]; /* = { NS, -1, -NS, 1, NS-1, -NS-1, -NS+1, NS+1}; */
|
||||
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* Other functions */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* SGF routines for debugging purposes in sgffile.c */
|
||||
void sgffile_begindump(struct SGFTree_t *tree);
|
||||
void sgffile_enddump(const char *filename);
|
||||
|
||||
|
||||
/* Hashing and Caching statistics. */
|
||||
struct stats_data {
|
||||
int nodes; /* Number of visited nodes while reading */
|
||||
int read_result_entered; /* Number of read results entered. */
|
||||
int read_result_hits; /* Number of hits of read results. */
|
||||
int trusted_read_result_hits; /* Number of hits of read results */
|
||||
/* with sufficient remaining depth. */
|
||||
};
|
||||
|
||||
extern struct stats_data stats;
|
||||
|
||||
|
||||
/* printutils.c */
|
||||
int gprintf(const char *fmt, ...);
|
||||
void vgprintf(FILE *outputfile, const char *fmt, va_list ap);
|
||||
void mprintf(const char *fmt, ...);
|
||||
void gfprintf(FILE *outfile, const char *fmt, ...);
|
||||
|
||||
const char *color_to_string(int color);
|
||||
const char *location_to_string(int pos);
|
||||
void location_to_buffer(int pos, char *buf);
|
||||
|
||||
int string_to_location(int boardsize, const char *str);
|
||||
|
||||
int is_hoshi_point(int m, int n);
|
||||
void draw_letter_coordinates(FILE *outfile);
|
||||
void simple_showboard(FILE *outfile);
|
||||
|
||||
void mark_goal_in_sgf(signed char goal[BOARDMAX]);
|
||||
|
||||
/* ================================================================ */
|
||||
/* assertions */
|
||||
/* ================================================================ */
|
||||
|
||||
/* Our own abort() which prints board state on the way out.
|
||||
* (pos) is a "relevant" board position for info.
|
||||
*/
|
||||
void abortgo(const char *file, int line, const char *msg, int pos)
|
||||
#ifdef __GNUC__
|
||||
__attribute__ ((noreturn))
|
||||
#endif
|
||||
;
|
||||
|
||||
#ifdef GG_TURN_OFF_ASSERTS
|
||||
#define ASSERT2(x, i, j)
|
||||
#define ASSERT1(x, pos)
|
||||
#else
|
||||
/* avoid dangling else */
|
||||
/* FIXME: Should probably re-write these using do {...} while (0) idiom. */
|
||||
#define ASSERT2(x, i, j) if (x) ; else abortgo(__FILE__, __LINE__, #x, POS(i, j))
|
||||
#define ASSERT1(x, pos) if (x) ; else abortgo(__FILE__, __LINE__, #x, pos)
|
||||
#endif
|
||||
|
||||
#define gg_assert(x) ASSERT1(x, NO_MOVE)
|
||||
|
||||
/* Are we using valgrind memory checking? */
|
||||
#if USE_VALGRIND
|
||||
#include <valgrind/memcheck.h>
|
||||
#else
|
||||
#define VALGRIND_MAKE_WRITABLE(a, b)
|
||||
#endif
|
||||
|
||||
#endif /* _BOARD_H_ */
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
66
gnugo/engine/boardlib.c
Normal file
66
gnugo/engine/boardlib.c
Normal file
@ -0,0 +1,66 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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 file contains the global functions of the board library libboard.a. */
|
||||
|
||||
#include "board.h"
|
||||
#include "hash.h"
|
||||
|
||||
/* The board state itself. */
|
||||
int board_size = DEFAULT_BOARD_SIZE; /* board size */
|
||||
Intersection board[BOARDSIZE];
|
||||
int board_ko_pos;
|
||||
int white_captured; /* number of black and white stones captured */
|
||||
int black_captured;
|
||||
|
||||
Intersection initial_board[BOARDSIZE];
|
||||
int initial_board_ko_pos;
|
||||
int initial_white_captured;
|
||||
int initial_black_captured;
|
||||
int move_history_color[MAX_MOVE_HISTORY];
|
||||
int move_history_pos[MAX_MOVE_HISTORY];
|
||||
Hash_data move_history_hash[MAX_MOVE_HISTORY];
|
||||
int move_history_pointer;
|
||||
|
||||
float komi = 0.0;
|
||||
int handicap = 0;
|
||||
int movenum;
|
||||
enum suicide_rules suicide_rule = FORBIDDEN;
|
||||
enum ko_rules ko_rule = SIMPLE;
|
||||
|
||||
|
||||
signed char shadow[BOARDMAX];
|
||||
|
||||
/* Hashing of positions. */
|
||||
Hash_data board_hash;
|
||||
|
||||
int stackp; /* stack pointer */
|
||||
int position_number; /* position number */
|
||||
|
||||
/* Some statistics gathered partly in board.c and hash.c */
|
||||
struct stats_data stats;
|
||||
|
||||
/* Variation tracking in SGF trees: */
|
||||
int count_variations = 0;
|
||||
SGFTree *sgf_dumptree = NULL;
|
464
gnugo/engine/breakin.c
Normal file
464
gnugo/engine/breakin.c
Normal file
@ -0,0 +1,464 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
#include "liberty.h"
|
||||
#include "readconnect.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* This module looks for break-ins into territories that require
|
||||
* deeper tactical reading and are thus impossible to detect for the
|
||||
* influence module. It gets run after the influence module and revises
|
||||
* its territory valuations.
|
||||
*
|
||||
* The procedure is as follows: We look at all big (>= 10) territory regions
|
||||
* as detected by the influence code. Using the computation of
|
||||
* connection distances from readconnect.c, we compute all nearby vertices
|
||||
* of this territory. We look for the closest safe stones belonging to
|
||||
* the opponent.
|
||||
* For each such string (str) we call
|
||||
* - break_in(str, territory) if the opponent is assumed to be next to move,
|
||||
* or
|
||||
* - block_off(str, territory) if the territory owner is next.
|
||||
* If the break in is successful resp. the blocking unsuccessful, we
|
||||
* shrink the territory, and see whether the opponent can still break in.
|
||||
* We repeat this until the territory is shrunk so much that the opponent
|
||||
* can no longer reach it.
|
||||
*/
|
||||
|
||||
|
||||
/* Store possible break-ins in initial position to generate move reasons
|
||||
* later.
|
||||
*/
|
||||
struct break_in_data {
|
||||
int str;
|
||||
int move;
|
||||
};
|
||||
|
||||
#define MAX_BREAK_INS 50
|
||||
static struct break_in_data break_in_list[MAX_BREAK_INS];
|
||||
static int num_break_ins;
|
||||
|
||||
|
||||
/* Adds all empty intersections that have two goal neighbors to the goal. */
|
||||
static void
|
||||
enlarge_goal(signed char goal[BOARDMAX])
|
||||
{
|
||||
int pos;
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] == EMPTY && !goal[pos]) {
|
||||
int k;
|
||||
int goal_neighbors = 0;
|
||||
for (k = 0; k < 4; k++)
|
||||
if (board[pos + delta[k]] == EMPTY && goal[pos + delta[k]] == 1)
|
||||
goal_neighbors++;
|
||||
if (goal_neighbors >= 2)
|
||||
goal[pos] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* The "smaller goal" is the intersection of the goal with what is
|
||||
* stored in the queue of the connection_data conn.
|
||||
* Plus we need a couple of extra careful modifications in the case
|
||||
* of "blocking off", i.e. when color_to_move == owner.
|
||||
*/
|
||||
static void
|
||||
compute_smaller_goal(int owner, int color_to_move,
|
||||
const struct connection_data *conn,
|
||||
const signed char goal[BOARDMAX],
|
||||
signed char smaller_goal[BOARDMAX])
|
||||
{
|
||||
int k, j;
|
||||
int own_stones_visited[BOARDMAX];
|
||||
memset(smaller_goal, 0, BOARDMAX);
|
||||
for (k = 0; k < conn->queue_end; k++) {
|
||||
int pos = conn->queue[k];
|
||||
int goal_neighbors = 0;
|
||||
/* If we are trying to block-off, we need to be extra careful: We only
|
||||
* can block intrusions coming directly from the string in question.
|
||||
* Therefore, we discard the area if we have traversed more than two
|
||||
* stones of the color breaking in on the way to the goal.
|
||||
*/
|
||||
if (owner == color_to_move) {
|
||||
int coming_from = conn->coming_from[pos];
|
||||
if (coming_from == NO_MOVE)
|
||||
own_stones_visited[pos] = 0;
|
||||
else {
|
||||
own_stones_visited[pos] = own_stones_visited[coming_from];
|
||||
/* How many stones have we used to jump from coming_from to pos?
|
||||
* Use Manhattan metric as a guess.
|
||||
*/
|
||||
if (!goal[pos] && board[pos] == OTHER_COLOR(owner)) {
|
||||
int i;
|
||||
int stones[MAX_BOARD * MAX_BOARD];
|
||||
int num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
|
||||
int smallest_distance = 3;
|
||||
|
||||
for (i = 0; i < num_stones; i++) {
|
||||
int distance = (gg_abs(I(stones[i]) - I(coming_from))
|
||||
+ gg_abs(J(stones[i]) - J(coming_from)));
|
||||
|
||||
if (distance < smallest_distance)
|
||||
smallest_distance = distance;
|
||||
}
|
||||
|
||||
own_stones_visited[pos] += smallest_distance;
|
||||
}
|
||||
|
||||
if (own_stones_visited[pos] > 2)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!goal[pos])
|
||||
continue;
|
||||
|
||||
/* We don't want vertices that are at the border of the territory, and
|
||||
* from which a break-in is unlikely; these often lead to false
|
||||
* positives.
|
||||
* So we throw out every vertex that has only one neighbor in the goal,
|
||||
* or that is on an edge and has only two goal neighbors.
|
||||
*/
|
||||
for (j = 0; j < 4; j++)
|
||||
if (ON_BOARD(pos + delta[j])
|
||||
&& goal[pos + delta[j]]
|
||||
&& (board[pos] == EMPTY || goal[pos] == OTHER_COLOR(owner)))
|
||||
goal_neighbors++;
|
||||
#if 0
|
||||
if (goal_neighbors > 2
|
||||
|| goal_neighbors == 2 && !is_edge_vertex(pos))
|
||||
#else
|
||||
if (goal_neighbors >= 2)
|
||||
smaller_goal[pos] = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Finally, in the case of blocking off, we only want one connected
|
||||
* component.
|
||||
*/
|
||||
if (owner == color_to_move) {
|
||||
signed char marked[BOARDMAX];
|
||||
int sizes[BOARDMAX / 2];
|
||||
signed char mark = 0;
|
||||
int biggest_region = 1;
|
||||
memset(marked, 0, BOARDMAX);
|
||||
for (k = 0; k < conn->queue_end; k++) {
|
||||
int pos = conn->queue[k];
|
||||
if (ON_BOARD(pos) && smaller_goal[pos] && !marked[pos]) {
|
||||
/* Floodfill the connected component of (pos) in the goal. */
|
||||
int queue_start = 0;
|
||||
int queue_end = 1;
|
||||
int queue[BOARDMAX];
|
||||
mark++;
|
||||
sizes[(int) mark] = 1;
|
||||
marked[pos] = mark;
|
||||
queue[0] = pos;
|
||||
while (queue_start < queue_end) {
|
||||
test_gray_border();
|
||||
for (j = 0; j < 4; j++) {
|
||||
int pos2 = queue[queue_start] + delta[j];
|
||||
if (!ON_BOARD(pos2))
|
||||
continue;
|
||||
ASSERT1(marked[pos2] == 0 || marked[pos2] == mark, pos2);
|
||||
if (smaller_goal[pos2]
|
||||
&& !marked[pos2]) {
|
||||
sizes[(int) mark]++;
|
||||
marked[pos2] = mark;
|
||||
queue[queue_end++] = pos2;
|
||||
}
|
||||
}
|
||||
queue_start++;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Now selected the biggest connected component. (In case of
|
||||
* equality, take the first one.
|
||||
*/
|
||||
for (k = 1; k <= mark; k++) {
|
||||
if (sizes[k] > sizes[biggest_region])
|
||||
biggest_region = k;
|
||||
}
|
||||
memset(smaller_goal, 0, BOARDMAX);
|
||||
for (k = 0; k < conn->queue_end; k++) {
|
||||
int pos = conn->queue[k];
|
||||
if (marked[pos] == biggest_region)
|
||||
smaller_goal[pos] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Try to intrude from str into goal. If successful, we shrink the goal,
|
||||
* store the non-territory fields in the non_territory array, and
|
||||
* try again.
|
||||
*/
|
||||
static int
|
||||
break_in_goal_from_str(int str, signed char goal[BOARDMAX],
|
||||
int *num_non_territory, int non_territory[BOARDMAX],
|
||||
int color_to_move, int info_pos)
|
||||
{
|
||||
int move = NO_MOVE;
|
||||
int saved_move = NO_MOVE;
|
||||
signed char smaller_goal[BOARDMAX];
|
||||
struct connection_data conn;
|
||||
|
||||
/* When blocking off, we use a somewhat smaller goal area. */
|
||||
if (color_to_move == board[str])
|
||||
compute_connection_distances(str, NO_MOVE, FP(3.01), &conn, 1);
|
||||
else
|
||||
compute_connection_distances(str, NO_MOVE, FP(2.81), &conn, 1);
|
||||
|
||||
sort_connection_queue_tail(&conn);
|
||||
expand_connection_queue(&conn);
|
||||
compute_smaller_goal(OTHER_COLOR(board[str]), color_to_move,
|
||||
&conn, goal, smaller_goal);
|
||||
if (0 && (debug & DEBUG_BREAKIN))
|
||||
print_connection_distances(&conn);
|
||||
DEBUG(DEBUG_BREAKIN, "Trying to break in from %1m to:\n", str);
|
||||
if (debug & DEBUG_BREAKIN)
|
||||
goaldump(smaller_goal);
|
||||
while ((color_to_move == board[str]
|
||||
&& break_in(str, smaller_goal, &move))
|
||||
|| (color_to_move == OTHER_COLOR(board[str])
|
||||
&& !block_off(str, smaller_goal, NULL))) {
|
||||
/* Successful break-in/unsuccessful block. Now where exactly can we
|
||||
* erase territory? This is difficult, and the method here is very
|
||||
* crude: Wherever we enter the territory when computing the closest
|
||||
* neighbors of (str). Plus at the location of the break-in move.
|
||||
* FIXME: This needs improvement.
|
||||
*/
|
||||
int k;
|
||||
int save_num = *num_non_territory;
|
||||
int affected_size = 0;
|
||||
int cut_off_distance = FP(3.5);
|
||||
if (ON_BOARD(move) && goal[move]) {
|
||||
non_territory[(*num_non_territory)++] = move;
|
||||
if (info_pos)
|
||||
DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
|
||||
"%1m: Erasing territory at %1m -a.\n", info_pos, move);
|
||||
else
|
||||
DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
|
||||
"Erasing territory at %1m -a.\n", move);
|
||||
}
|
||||
|
||||
for (k = 0; k < conn.queue_end; k++) {
|
||||
int pos = conn.queue[k];
|
||||
if (conn.distances[pos] > cut_off_distance + FP(0.31))
|
||||
break;
|
||||
if (goal[pos]
|
||||
&& (!ON_BOARD(conn.coming_from[pos])
|
||||
|| !goal[conn.coming_from[pos]])) {
|
||||
non_territory[(*num_non_territory)++] = pos;
|
||||
if (info_pos)
|
||||
DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
|
||||
"%1m: Erasing territory at %1m -b.\n", info_pos, pos);
|
||||
else
|
||||
DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
|
||||
"Erasing territory at %1m -b.\n", pos);
|
||||
if (conn.distances[pos] < cut_off_distance)
|
||||
cut_off_distance = conn.distances[pos];
|
||||
}
|
||||
if (*num_non_territory >= save_num + 4)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Shouldn't happen, but it does. */
|
||||
if (*num_non_territory == save_num)
|
||||
break;
|
||||
|
||||
for (k = save_num; k < *num_non_territory; k++) {
|
||||
int j;
|
||||
int pos = non_territory[k];
|
||||
if (goal[pos]) {
|
||||
affected_size++;
|
||||
goal[pos] = 0;
|
||||
}
|
||||
for (j = 0; j < 4; j++)
|
||||
if (ON_BOARD(pos + delta[j]) && goal[pos + delta[j]])
|
||||
affected_size++;
|
||||
/* Don't kill too much territory at a time. */
|
||||
if (affected_size >= 5) {
|
||||
*num_non_territory = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
compute_smaller_goal(OTHER_COLOR(board[str]), color_to_move,
|
||||
&conn, goal, smaller_goal);
|
||||
DEBUG(DEBUG_BREAKIN, "Now trying to break to smaller goal:\n", str);
|
||||
if (debug & DEBUG_BREAKIN)
|
||||
goaldump(smaller_goal);
|
||||
|
||||
if (saved_move == NO_MOVE)
|
||||
saved_move = move;
|
||||
}
|
||||
return saved_move;
|
||||
}
|
||||
|
||||
#define MAX_TRIES 10
|
||||
|
||||
static void
|
||||
break_in_goal(int color_to_move, int owner, signed char goal[BOARDMAX],
|
||||
struct influence_data *q, int store, int info_pos)
|
||||
{
|
||||
struct connection_data conn;
|
||||
int k;
|
||||
int intruder = OTHER_COLOR(owner);
|
||||
signed char used[BOARDMAX];
|
||||
int non_territory[BOARDMAX];
|
||||
int num_non_territory = 0;
|
||||
int candidate_strings[MAX_TRIES];
|
||||
int candidates = 0;
|
||||
int min_distance = FP(5.0);
|
||||
|
||||
DEBUG(DEBUG_BREAKIN,
|
||||
"Trying to break (%C to move) %C's territory ", color_to_move, owner);
|
||||
if (debug & DEBUG_BREAKIN)
|
||||
goaldump(goal);
|
||||
/* Compute nearby fields of goal. */
|
||||
init_connection_data(intruder, goal, NO_MOVE, FP(3.01), &conn, 1);
|
||||
k = conn.queue_end;
|
||||
spread_connection_distances(intruder, &conn);
|
||||
sort_connection_queue_tail(&conn);
|
||||
if (0 && (debug & DEBUG_BREAKIN))
|
||||
print_connection_distances(&conn);
|
||||
|
||||
/* Look for nearby stones. */
|
||||
memset(used, 0, BOARDMAX);
|
||||
for (; k < conn.queue_end; k++) {
|
||||
int pos = conn.queue[k];
|
||||
if (conn.distances[pos] > min_distance + FP(1.001))
|
||||
break;
|
||||
if (board[pos] == intruder
|
||||
&& influence_considered_lively(q, pos)) {
|
||||
/* Discard this string in case the shortest path goes via a string
|
||||
* that we have in the candidate list already.
|
||||
*/
|
||||
int pos2 = pos;
|
||||
while (ON_BOARD(pos2)) {
|
||||
pos2 = conn.coming_from[pos2];
|
||||
if (IS_STONE(board[pos2]))
|
||||
pos2 = find_origin(pos2);
|
||||
|
||||
if (used[pos2])
|
||||
break;
|
||||
}
|
||||
|
||||
used[pos] = 1;
|
||||
if (ON_BOARD(pos2))
|
||||
continue;
|
||||
if (candidates == 0)
|
||||
min_distance = conn.distances[pos];
|
||||
candidate_strings[candidates++] = pos;
|
||||
if (candidates == MAX_TRIES)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, try the break-ins. */
|
||||
memset(non_territory, 0, BOARDMAX);
|
||||
for (k = 0; k < candidates; k++) {
|
||||
int move = break_in_goal_from_str(candidate_strings[k], goal,
|
||||
&num_non_territory, non_territory,
|
||||
color_to_move, info_pos);
|
||||
if (store && ON_BOARD(move) && num_break_ins < MAX_BREAK_INS) {
|
||||
/* Remember the move as a possible move candidate for later. */
|
||||
break_in_list[num_break_ins].str = candidate_strings[k];
|
||||
break_in_list[num_break_ins].move = move;
|
||||
num_break_ins++;
|
||||
}
|
||||
}
|
||||
|
||||
for (k = 0; k < num_non_territory; k++)
|
||||
influence_erase_territory(q, non_territory[k], owner);
|
||||
if (0 && num_non_territory > 0 && (debug & DEBUG_BREAKIN))
|
||||
showboard(0);
|
||||
}
|
||||
|
||||
|
||||
/* The main function of this module. color_to_move is self-explanatory,
|
||||
* and the influence_data refers to the influence territory evaluation that
|
||||
* we are analyzing (and will be correcting). store indicates whether
|
||||
* the successful break-ins should be stored in the break_in_list[] (which
|
||||
* later gets used to generate move reasons).
|
||||
*/
|
||||
void
|
||||
break_territories(int color_to_move, struct influence_data *q, int store,
|
||||
int info_pos)
|
||||
{
|
||||
struct moyo_data territories;
|
||||
int k;
|
||||
|
||||
if (!experimental_break_in || get_level() < 10)
|
||||
return;
|
||||
|
||||
influence_get_territory_segmentation(q, &territories);
|
||||
for (k = 1; k <= territories.number; k++) {
|
||||
signed char goal[BOARDMAX];
|
||||
int pos;
|
||||
int size = 0;
|
||||
|
||||
memset(goal, 0, BOARDMAX);
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos) && territories.segmentation[pos] == k) {
|
||||
goal[pos] = 1;
|
||||
if (board[pos] != territories.owner[k])
|
||||
size++;
|
||||
}
|
||||
if (size < 10)
|
||||
continue;
|
||||
|
||||
if (color_to_move == OTHER_COLOR(territories.owner[k]))
|
||||
enlarge_goal(goal);
|
||||
break_in_goal(color_to_move, territories.owner[k], goal, q, store,
|
||||
info_pos);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
clear_break_in_list()
|
||||
{
|
||||
num_break_ins = 0;
|
||||
}
|
||||
|
||||
/* The blocking moves should usually already have a move reason.
|
||||
*
|
||||
* The EXPAND_TERRITORY move reason ensures a territory evaluation of
|
||||
* this move, without setting the move.safety field. (I.e. the move will
|
||||
* be treated as a sacrifice move unless another move reasons tells us
|
||||
* otherwise.)
|
||||
*/
|
||||
void
|
||||
break_in_move_reasons(int color)
|
||||
{
|
||||
int k;
|
||||
for (k = 0; k < num_break_ins; k++)
|
||||
if (board[break_in_list[k].str] == color)
|
||||
add_expand_territory_move(break_in_list[k].move);
|
||||
}
|
403
gnugo/engine/cache.c
Normal file
403
gnugo/engine/cache.c
Normal file
@ -0,0 +1,403 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
#include "random.h"
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "cache.h"
|
||||
#include "sgftree.h"
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* The transposition table */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
static void tt_init(Transposition_table *table, int memsize);
|
||||
static void tt_clear(Transposition_table *table);
|
||||
|
||||
/* The transposition table itself. */
|
||||
Transposition_table ttable;
|
||||
|
||||
|
||||
/* Arrays with random numbers for Zobrist hashing of input data (other
|
||||
* than the board position). If you add an array here, do not forget
|
||||
* to also initialize it in keyhash_init() below.
|
||||
*/
|
||||
static Hash_data target1_hash[BOARDMAX];
|
||||
static Hash_data target2_hash[BOARDMAX];
|
||||
static Hash_data routine_hash[NUM_CACHE_ROUTINES];
|
||||
|
||||
static void
|
||||
keyhash_init(void)
|
||||
{
|
||||
static int is_initialized = 0;
|
||||
|
||||
if (!is_initialized) {
|
||||
|
||||
INIT_ZOBRIST_ARRAY(target1_hash);
|
||||
INIT_ZOBRIST_ARRAY(target2_hash);
|
||||
INIT_ZOBRIST_ARRAY(routine_hash);
|
||||
|
||||
is_initialized = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
calculate_hashval_for_tt(Hash_data *hashdata, int routine, int target1,
|
||||
int target2, Hash_data *extra_hash)
|
||||
{
|
||||
*hashdata = board_hash; /* from globals.c */
|
||||
hashdata_xor(*hashdata, routine_hash[routine]);
|
||||
hashdata_xor(*hashdata, target1_hash[target1]);
|
||||
if (target2 != NO_MOVE)
|
||||
hashdata_xor(*hashdata, target2_hash[target2]);
|
||||
if (extra_hash)
|
||||
hashdata_xor(*hashdata, *extra_hash);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Initialize the transposition table. Non-positive memsize means use
|
||||
* the default size of DEFAULT_NUMBER_OF_CACHE_ENTRIES entries.
|
||||
*/
|
||||
|
||||
static void
|
||||
tt_init(Transposition_table *table, int memsize)
|
||||
{
|
||||
int num_entries;
|
||||
|
||||
/* Make sure the hash system is initialized. */
|
||||
hash_init();
|
||||
keyhash_init();
|
||||
|
||||
if (memsize > 0)
|
||||
num_entries = memsize / sizeof(table->entries[0]);
|
||||
else
|
||||
num_entries = DEFAULT_NUMBER_OF_CACHE_ENTRIES;
|
||||
|
||||
table->num_entries = num_entries;
|
||||
table->entries = malloc(num_entries * sizeof(table->entries[0]));
|
||||
|
||||
if (table->entries == NULL) {
|
||||
perror("Couldn't allocate memory for transposition table. \n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
table->is_clean = 0;
|
||||
tt_clear(table);
|
||||
}
|
||||
|
||||
|
||||
/* Clear the transposition table. */
|
||||
|
||||
static void
|
||||
tt_clear(Transposition_table *table)
|
||||
{
|
||||
if (!table->is_clean) {
|
||||
memset(table->entries, 0, table->num_entries * sizeof(table->entries[0]));
|
||||
table->is_clean = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Free the transposition table. */
|
||||
|
||||
void
|
||||
tt_free(Transposition_table *table)
|
||||
{
|
||||
free(table->entries);
|
||||
}
|
||||
|
||||
|
||||
/* Get result and move. Return value:
|
||||
* 0 if not found
|
||||
* 1 if found, but depth too small to be trusted. In this case the move
|
||||
* can be used for move ordering.
|
||||
* 2 if found and depth is enough so that the result can be trusted.
|
||||
*/
|
||||
|
||||
int
|
||||
tt_get(Transposition_table *table,
|
||||
enum routine_id routine,
|
||||
int target1, int target2, int remaining_depth,
|
||||
Hash_data *extra_hash,
|
||||
int *value1, int *value2, int *move)
|
||||
{
|
||||
Hash_data hashval;
|
||||
Hashentry *entry;
|
||||
Hashnode *node;
|
||||
|
||||
/* Sanity check. */
|
||||
if (remaining_depth < 0 || remaining_depth > HN_MAX_REMAINING_DEPTH)
|
||||
return 0;
|
||||
|
||||
/* Get the combined hash value. */
|
||||
calculate_hashval_for_tt(&hashval, routine, target1, target2, extra_hash);
|
||||
|
||||
/* Get the correct entry and node. */
|
||||
entry = &table->entries[hashdata_remainder(hashval, table->num_entries)];
|
||||
if (hashdata_is_equal(hashval, entry->deepest.key))
|
||||
node = &entry->deepest;
|
||||
else if (hashdata_is_equal(hashval, entry->newest.key))
|
||||
node = &entry->newest;
|
||||
else
|
||||
return 0;
|
||||
|
||||
stats.read_result_hits++;
|
||||
|
||||
/* Return data. Only set the result if remaining depth in the table
|
||||
* is big enough to be trusted. The move can always be used for move
|
||||
* ordering if nothing else.
|
||||
*/
|
||||
if (move)
|
||||
*move = hn_get_move(node->data);
|
||||
if (remaining_depth <= (int) hn_get_remaining_depth(node->data)) {
|
||||
if (value1)
|
||||
*value1 = hn_get_value1(node->data);
|
||||
if (value2)
|
||||
*value2 = hn_get_value2(node->data);
|
||||
stats.trusted_read_result_hits++;
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Update a transposition table entry.
|
||||
*/
|
||||
|
||||
void
|
||||
tt_update(Transposition_table *table,
|
||||
enum routine_id routine, int target1, int target2,
|
||||
int remaining_depth, Hash_data *extra_hash,
|
||||
int value1, int value2, int move)
|
||||
{
|
||||
Hash_data hashval;
|
||||
Hashentry *entry;
|
||||
Hashnode *deepest;
|
||||
Hashnode *newest;
|
||||
unsigned int data;
|
||||
/* Get routine costs definitions from liberty.h. */
|
||||
static const int routine_costs[] = { ROUTINE_COSTS };
|
||||
gg_assert(routine_costs[NUM_CACHE_ROUTINES] == -1);
|
||||
|
||||
/* Sanity check. */
|
||||
if (remaining_depth < 0 || remaining_depth > HN_MAX_REMAINING_DEPTH)
|
||||
return;
|
||||
|
||||
/* Get the combined hash value. */
|
||||
calculate_hashval_for_tt(&hashval, routine, target1, target2, extra_hash);
|
||||
|
||||
data = hn_create_data(remaining_depth, value1, value2, move,
|
||||
routine_costs[routine]);
|
||||
|
||||
/* Get the entry and nodes. */
|
||||
entry = &table->entries[hashdata_remainder(hashval, table->num_entries)];
|
||||
deepest = &entry->deepest;
|
||||
newest = &entry->newest;
|
||||
|
||||
/* See if we found an already existing node. */
|
||||
if (hashdata_is_equal(hashval, deepest->key)
|
||||
&& remaining_depth >= (int) hn_get_remaining_depth(deepest->data)) {
|
||||
|
||||
/* Found deepest */
|
||||
deepest->data = data;
|
||||
|
||||
}
|
||||
else if (hashdata_is_equal(hashval, newest->key)
|
||||
&& remaining_depth >= (int) hn_get_remaining_depth(newest->data)) {
|
||||
|
||||
/* Found newest */
|
||||
newest->data = data;
|
||||
|
||||
/* If newest has become deeper than deepest, then switch them. */
|
||||
if (hn_get_remaining_depth(newest->data)
|
||||
> hn_get_remaining_depth(deepest->data)) {
|
||||
Hashnode temp;
|
||||
|
||||
temp = *deepest;
|
||||
*deepest = *newest;
|
||||
*newest = temp;
|
||||
}
|
||||
|
||||
}
|
||||
else if (hn_get_total_cost(data) > hn_get_total_cost(deepest->data)) {
|
||||
if (hn_get_total_cost(newest->data) < hn_get_total_cost(deepest->data))
|
||||
*newest = *deepest;
|
||||
deepest->key = hashval;
|
||||
deepest->data = data;
|
||||
}
|
||||
else {
|
||||
/* Replace newest. */
|
||||
newest->key = hashval;
|
||||
newest->data = data;
|
||||
}
|
||||
|
||||
stats.read_result_entered++;
|
||||
table->is_clean = 0;
|
||||
}
|
||||
|
||||
|
||||
static const char *routine_names[] = {
|
||||
ROUTINE_NAMES
|
||||
};
|
||||
|
||||
/* Convert a routine as used in the cache table to a string. */
|
||||
const char *
|
||||
routine_id_to_string(enum routine_id routine)
|
||||
{
|
||||
return routine_names[(int) routine];
|
||||
}
|
||||
|
||||
|
||||
/* Initialize the cache for read results, using at most the given
|
||||
* number of bytes of memory. If the memory isn't sufficient to
|
||||
* allocate a single node or if the allocation fails, the caching is
|
||||
* disabled.
|
||||
*/
|
||||
void
|
||||
reading_cache_init(int bytes)
|
||||
{
|
||||
tt_init(&ttable, bytes);
|
||||
}
|
||||
|
||||
|
||||
/* Clear the cache for read results. */
|
||||
void
|
||||
reading_cache_clear()
|
||||
{
|
||||
tt_clear(&ttable);
|
||||
}
|
||||
|
||||
float
|
||||
reading_cache_default_size()
|
||||
{
|
||||
return DEFAULT_NUMBER_OF_CACHE_ENTRIES * sizeof(Hashentry) / 1024.0 / 1024.0;
|
||||
}
|
||||
|
||||
|
||||
/* Write reading trace data to an SGF file. Normally called through the
|
||||
* macro SGFTRACE in cache.h.
|
||||
*/
|
||||
|
||||
void
|
||||
sgf_trace(const char *func, int str, int move, int result,
|
||||
const char *message)
|
||||
{
|
||||
char buf[100];
|
||||
|
||||
sprintf(buf, "%s %c%d: ", func, J(str) + 'A' + (J(str) >= 8),
|
||||
board_size - I(str));
|
||||
|
||||
if (result == 0)
|
||||
sprintf(buf + strlen(buf), "0");
|
||||
else if (ON_BOARD(move))
|
||||
sprintf(buf + strlen(buf), "%s %c%d", result_to_string(result),
|
||||
J(move) + 'A' + (J(move) >= 8),
|
||||
board_size - I(move));
|
||||
else if (is_pass(move))
|
||||
sprintf(buf + strlen(buf), "%s PASS", result_to_string(result));
|
||||
else
|
||||
sprintf(buf + strlen(buf), "%s [%d]", result_to_string(result), move);
|
||||
|
||||
if (message)
|
||||
sprintf(buf + strlen(buf), " (%s)", message);
|
||||
|
||||
sgftreeAddComment(sgf_dumptree, buf);
|
||||
}
|
||||
|
||||
/* Write two group reading (connection) trace data to an SGF file.
|
||||
* Normally called through the macro SGFTRACE2 in cache.h.
|
||||
*/
|
||||
|
||||
void
|
||||
sgf_trace2(const char *func, int str1, int str2, int move,
|
||||
const char *result, const char *message)
|
||||
{
|
||||
char buf[100];
|
||||
|
||||
sprintf(buf, "%s %c%d %c%d: ", func,
|
||||
J(str1) + 'A' + (J(str1) >= 8), board_size - I(str1),
|
||||
J(str2) + 'A' + (J(str2) >= 8), board_size - I(str2));
|
||||
|
||||
if (ON_BOARD(move))
|
||||
sprintf(buf + strlen(buf), "%s %c%d", result,
|
||||
J(move) + 'A' + (J(move) >= 8),
|
||||
board_size - I(move));
|
||||
else if (is_pass(move))
|
||||
sprintf(buf + strlen(buf), "%s PASS", result);
|
||||
else
|
||||
sprintf(buf + strlen(buf), "%s [%d]", result, move);
|
||||
|
||||
if (message)
|
||||
sprintf(buf + strlen(buf), " (%s)", message);
|
||||
|
||||
sgftreeAddComment(sgf_dumptree, buf);
|
||||
}
|
||||
|
||||
/* Write semeai reading trace data to an SGF file. Normally called
|
||||
* through the macro SGFTRACE_SEMEAI in cache.h.
|
||||
*/
|
||||
|
||||
void
|
||||
sgf_trace_semeai(const char *func, int str1, int str2, int move,
|
||||
int result1, int result2, const char *message)
|
||||
{
|
||||
char buf[100];
|
||||
|
||||
sprintf(buf, "%s %c%d %c%d: ", func,
|
||||
J(str1) + 'A' + (J(str1) >= 8), board_size - I(str1),
|
||||
J(str2) + 'A' + (J(str2) >= 8), board_size - I(str2));
|
||||
|
||||
if (ON_BOARD(move))
|
||||
sprintf(buf + strlen(buf), "%s %s %c%d",
|
||||
result_to_string(result1), result_to_string(result2),
|
||||
J(move) + 'A' + (J(move) >= 8), board_size - I(move));
|
||||
else if (is_pass(move))
|
||||
sprintf(buf + strlen(buf), "%s %s PASS",
|
||||
result_to_string(result1), result_to_string(result2));
|
||||
else
|
||||
sprintf(buf + strlen(buf), "%s %s [%d]",
|
||||
result_to_string(result1), result_to_string(result2),
|
||||
move);
|
||||
|
||||
if (message)
|
||||
sprintf(buf + strlen(buf), " (%s)", message);
|
||||
|
||||
sgftreeAddComment(sgf_dumptree, buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
377
gnugo/engine/cache.h
Normal file
377
gnugo/engine/cache.h
Normal file
@ -0,0 +1,377 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef _CACHE_H_
|
||||
#define _CACHE_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include "hash.h"
|
||||
|
||||
/*
|
||||
* This file, together with engine/hash.c implements hashing of go positions
|
||||
* using a method known as Zobrist hashing. See the Texinfo documentation
|
||||
* (Reading/Hashing) for more information.
|
||||
*/
|
||||
|
||||
|
||||
/* Hashnode: a node stored in the transposition table.
|
||||
*
|
||||
* In addition to the position, the hash lock encodes the following data,
|
||||
* all hashed:
|
||||
* komaster
|
||||
* kom_pos
|
||||
* routine
|
||||
* str1
|
||||
* str2
|
||||
* extra hashvalue, optional (e.g. encoding a goal array)
|
||||
*
|
||||
* The data field packs into 32 bits the following
|
||||
* fields:
|
||||
*
|
||||
* RESERVED : 5 bits
|
||||
* value1 : 4 bits
|
||||
* value2 : 4 bits
|
||||
* move : 10 bits
|
||||
* cost : 4 bits
|
||||
* remaining_depth: 5 bits (depth - stackp) NOTE: HN_MAX_REMAINING_DEPTH
|
||||
*
|
||||
* The last 9 bits together give an index for the total costs.
|
||||
*/
|
||||
typedef struct {
|
||||
Hash_data key;
|
||||
unsigned int data; /* Should be 32 bits, but only wastes 25% if 64 bits. */
|
||||
} Hashnode;
|
||||
|
||||
#define HN_MAX_REMAINING_DEPTH 31
|
||||
|
||||
|
||||
/* Hashentry: an entry, with two nodes of the hash_table
|
||||
*/
|
||||
typedef struct {
|
||||
Hashnode deepest;
|
||||
Hashnode newest;
|
||||
} Hashentry;
|
||||
|
||||
/* Hn is for hash node. */
|
||||
#define hn_get_value1(hn) ((hn >> 23) & 0x0f)
|
||||
#define hn_get_value2(hn) ((hn >> 19) & 0x0f)
|
||||
#define hn_get_move(hn) ((hn >> 9) & 0x3ff)
|
||||
#define hn_get_cost(hn) ((hn >> 5) & 0x0f)
|
||||
#define hn_get_remaining_depth(hn) ((hn >> 0) & 0x1f)
|
||||
#define hn_get_total_cost(hn) ((hn >> 0) & 0x1ff)
|
||||
|
||||
#define hn_create_data(remaining_depth, value1, value2, move, cost) \
|
||||
((((value1) & 0x0f) << 23) \
|
||||
| (((value2) & 0x0f) << 19) \
|
||||
| (((move) & 0x3ff) << 9) \
|
||||
| (((cost) & 0x0f) << 5) \
|
||||
| (((remaining_depth & 0x1f) << 0)))
|
||||
|
||||
|
||||
/* Transposition_table: transposition table used for caching. */
|
||||
typedef struct {
|
||||
unsigned int num_entries;
|
||||
Hashentry *entries;
|
||||
int is_clean;
|
||||
} Transposition_table;
|
||||
|
||||
extern Transposition_table ttable;
|
||||
|
||||
/* Number of cache entries to use by default if no cache memory usage
|
||||
* has been set explicitly.
|
||||
*/
|
||||
#define DEFAULT_NUMBER_OF_CACHE_ENTRIES 350000
|
||||
|
||||
void tt_free(Transposition_table *table);
|
||||
int tt_get(Transposition_table *table, enum routine_id routine,
|
||||
int target1, int target2, int remaining_depth,
|
||||
Hash_data *extra_hash,
|
||||
int *value1, int *value2, int *move);
|
||||
void tt_update(Transposition_table *table, enum routine_id routine,
|
||||
int target, int target2, int remaining_depth,
|
||||
Hash_data *extra_hash,
|
||||
int value1, int value2, int move);
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* Macros used from reading.c, readconnect.c, and owl.c to store and
|
||||
* retrieve read results.
|
||||
*/
|
||||
|
||||
#if TRACE_READ_RESULTS
|
||||
|
||||
#define TRACE_CACHED_RESULT(result, move) \
|
||||
gprintf("%o%s %1m %d %d %1m (cached) ", read_function_name, \
|
||||
q, stackp, result, move); \
|
||||
dump_stack();
|
||||
|
||||
#define TRACE_CACHED_RESULT2(result1, result2, move) \
|
||||
gprintf("%o%s %1m %1m %d %d %d %1m (cached) ", read_function_name, \
|
||||
q1, q2, stackp, result1, result2, move); \
|
||||
dump_stack();
|
||||
|
||||
|
||||
#define SETUP_TRACE_INFO(name, str) \
|
||||
const char *read_function_name = name; \
|
||||
int q = find_origin(str);
|
||||
|
||||
#define SETUP_TRACE_INFO2(name, str1, str2) \
|
||||
const char *read_function_name = name; \
|
||||
int q1 = board[str1] == EMPTY ? str1 : find_origin(str1); \
|
||||
int q2 = board[str2] == EMPTY ? str2 : find_origin(str2);
|
||||
|
||||
#else
|
||||
|
||||
#define TRACE_CACHED_RESULT(result, move)
|
||||
#define TRACE_CACHED_RESULT2(result1, result2, move)
|
||||
|
||||
#define SETUP_TRACE_INFO(name, str) \
|
||||
const char *read_function_name = name; \
|
||||
int q = str;
|
||||
|
||||
#define SETUP_TRACE_INFO2(name, str1, str2) \
|
||||
const char *read_function_name = name; \
|
||||
int q1 = str1; \
|
||||
int q2 = str2;
|
||||
|
||||
#endif
|
||||
|
||||
/* Trace messages in decidestring/decidedragon sgf file. */
|
||||
void sgf_trace(const char *func, int str, int move, int result,
|
||||
const char *message);
|
||||
/* Trace messages in decideconnection sgf file. */
|
||||
void sgf_trace2(const char *func, int str1, int str2, int move,
|
||||
const char *result, const char *message);
|
||||
/* Trace messages in decidesemeai sgf file. */
|
||||
void sgf_trace_semeai(const char *func, int str1, int str2, int move,
|
||||
int result1, int result2, const char *message);
|
||||
|
||||
/* Macro to hide the call to sgf_trace(). Notice that a little black
|
||||
* magic is going on here. Before using this macro, SETUP_TRACE_INFO
|
||||
* must have been called to provide the variables read_function_name
|
||||
* and q. These must of course not be used for anything else in
|
||||
* the function.
|
||||
*/
|
||||
#define SGFTRACE(move, result, message) \
|
||||
if (sgf_dumptree) \
|
||||
sgf_trace(read_function_name, q, move, result, message)
|
||||
|
||||
/* Corresponding macro for use in connection or semeai reading, where
|
||||
* two groups are involved.
|
||||
*/
|
||||
#define SGFTRACE2(move, result, message) \
|
||||
if (sgf_dumptree) \
|
||||
sgf_trace2(read_function_name, q1, q2, move, \
|
||||
result_to_string(result), message)
|
||||
|
||||
#define SGFTRACE_SEMEAI(move, result1, result2, message) \
|
||||
if (sgf_dumptree) \
|
||||
sgf_trace_semeai(read_function_name, q1, q2, move, \
|
||||
result1, result2, message)
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
|
||||
/*
|
||||
* These macros should be used in all the places where we want to
|
||||
* return a result from a reading function and where we want to
|
||||
* store the result in the hash table at the same time.
|
||||
*/
|
||||
|
||||
#if !TRACE_READ_RESULTS
|
||||
|
||||
#define READ_RETURN0(routine, str, remaining_depth) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, NULL,\
|
||||
0, 0, NO_MOVE);\
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN(routine, str, remaining_depth, point, move, value) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, NULL,\
|
||||
value, 0, move);\
|
||||
if ((value) != 0 && (point) != 0) *(point) = (move); \
|
||||
return (value); \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN_SEMEAI(routine, str1, str2, remaining_depth, point, move, value1, value2) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str1, str2, remaining_depth, NULL, \
|
||||
value1, value2, move); \
|
||||
if ((value1) != 0 && (point) != 0) *(point) = (move); \
|
||||
return; \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN_CONN(routine, str1, str2, remaining_depth, point, move, value) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str1, str2, remaining_depth, NULL,\
|
||||
value, 0, move);\
|
||||
if ((value) != 0 && (point) != 0) *(point) = (move); \
|
||||
return (value); \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN_HASH(routine, str, remaining_depth, hash, point, move, value) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, hash,\
|
||||
value, 0, move);\
|
||||
if ((value) != 0 && (point) != 0) *(point) = (move); \
|
||||
return (value); \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN2(routine, str, remaining_depth, point, move, value1, value2) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, NULL,\
|
||||
value1, value2, move);\
|
||||
if ((value1) != 0 && (point) != 0) *(point) = (move); \
|
||||
return (value1); \
|
||||
} while (0)
|
||||
|
||||
#else /* !TRACE_READ_RESULTS */
|
||||
|
||||
#define READ_RETURN0(routine, str, remaining_depth) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, NULL,\
|
||||
0, 0, NO_MOVE);\
|
||||
gprintf("%o%s %1m %d 0 0 ", read_function_name, q, stackp); \
|
||||
dump_stack(); \
|
||||
return 0; \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN(routine, str, remaining_depth, point, move, value) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, NULL,\
|
||||
value, 0, move);\
|
||||
if ((value) != 0 && (point) != 0) *(point) = (move); \
|
||||
gprintf("%o%s %1m %d %d %1m ", read_function_name, q, stackp, \
|
||||
(value), (move)); \
|
||||
dump_stack(); \
|
||||
return (value); \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN_SEMEAI(routine, str1, str2, remaining_depth, point, move, value1, value2) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str1, str2, remaining_depth, NULL, \
|
||||
value1, value2, move); \
|
||||
if ((value1) != 0 && (point) != 0) *(point) = (move); \
|
||||
gprintf("%o%s %1m %1m %d %d %d %1m ", read_function_name, q1, q2, stackp, \
|
||||
(value1), (value2), (move)); \
|
||||
dump_stack(); \
|
||||
return; \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN_CONN(routine, str1, str2, remaining_depth, point, move, value) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str1, str2, remaining_depth, NULL,\
|
||||
value, 0, move);\
|
||||
if ((value) != 0 && (point) != 0) *(point) = (move); \
|
||||
gprintf("%o%s %1m %1m %d %d %1m ", read_function_name, q1, q2, stackp, \
|
||||
(value), (move)); \
|
||||
dump_stack(); \
|
||||
return (value); \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN_HASH(routine, str, remaining_depth, hash, point, move, value) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, hash,\
|
||||
value, 0, move);\
|
||||
if ((value) != 0 && (point) != 0) *(point) = (move); \
|
||||
gprintf("%o%s %1m %d %d %1m ", read_function_name, q, stackp, \
|
||||
(value), (move)); \
|
||||
dump_stack(); \
|
||||
return (value); \
|
||||
} while (0)
|
||||
|
||||
#define READ_RETURN2(routine, str, remaining_depth, point, move, value1, value2) \
|
||||
do { \
|
||||
tt_update(&ttable, routine, str, NO_MOVE, remaining_depth, NULL,\
|
||||
value1, value2, move);\
|
||||
if ((value1) != 0 && (point) != 0) *(point) = (move); \
|
||||
gprintf("%o%s %1m %d %d %1m ", read_function_name, q, stackp, \
|
||||
(value1), (move)); \
|
||||
dump_stack(); \
|
||||
return (value1); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* This has actually nothing to do with caching, but is useful in
|
||||
* the same places where the caching is.
|
||||
*/
|
||||
|
||||
/* Macro to use when saving ko results while continuing to look for an
|
||||
* unconditional result. It's assumed that we have tried the move at
|
||||
* (move) and then called an attack or defense function giving the
|
||||
* result passed in the code parameter.
|
||||
*
|
||||
* In general we prefer not to have to do the first ko threat. Thus a
|
||||
* savecode KO_A is always better than a savecode KO_B. Also we always
|
||||
* prefer to keep the old move if we get the same savecode once more,
|
||||
* on the assumption that the moves have been ordered with the
|
||||
* presumably best one first.
|
||||
*
|
||||
* Notice that the savecode may be either 0 (nothing found so far), KO_B
|
||||
* or KO_A. Occasionally savecode WIN is also used, indicating an effective
|
||||
* but not preferred move, typically because it's either a sacrifice
|
||||
* or a backfilling move. If possible, we prefer making non-sacrifice
|
||||
* and direct moves. Of course savecode WIN is better than KO_A or KO_B.
|
||||
*/
|
||||
|
||||
#define UPDATE_SAVED_KO_RESULT(savecode, save, code, move) \
|
||||
if (code != 0 && REVERSE_RESULT(code) > savecode) { \
|
||||
save = move; \
|
||||
savecode = REVERSE_RESULT(code); \
|
||||
} \
|
||||
|
||||
/* Same as above, except this should be used when there's no
|
||||
* intervening trymove(). Thus we shouldn't reverse the save code.
|
||||
*/
|
||||
#define UPDATE_SAVED_KO_RESULT_UNREVERSED(savecode, save, code, move) \
|
||||
if (code != WIN && code > savecode) { \
|
||||
save = move; \
|
||||
savecode = code; \
|
||||
}
|
||||
|
||||
|
||||
/* This too isn't really related to caching but is convenient to have here.
|
||||
* (Needs to be available in reading.c and persistent.c.)
|
||||
*
|
||||
* Minimum number of nodes for which DEBUG_READING_PERFORMANCE reports
|
||||
* anything.
|
||||
*/
|
||||
#define MIN_READING_NODES_TO_REPORT 1000
|
||||
|
||||
|
||||
#endif /* _CACHE_H_ */
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
414
gnugo/engine/clock.c
Normal file
414
gnugo/engine/clock.c
Normal file
@ -0,0 +1,414 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* ============================================================= *\
|
||||
* Time handling *
|
||||
* for GNU Go *
|
||||
* __ __ *
|
||||
* < > < > *
|
||||
* +--++-------++--+ *
|
||||
* | .'11 12 1'. | *
|
||||
* | :10 \ 2: | *
|
||||
* | :9 @-> 3: | *
|
||||
* | :8 4; | *
|
||||
* | '..7 6 5..' | *
|
||||
* |_______________| *
|
||||
* *
|
||||
\* ============================================================= */
|
||||
|
||||
#include "clock.h"
|
||||
#include "gg_utils.h"
|
||||
#include "board.h"
|
||||
|
||||
/* Level data */
|
||||
static int level = DEFAULT_LEVEL; /* current level */
|
||||
static int level_offset = 0;
|
||||
static int min_level = 0;
|
||||
static int max_level = gg_max(DEFAULT_LEVEL, 10);
|
||||
|
||||
|
||||
/*************************/
|
||||
/* Datas and other stuff */
|
||||
/*************************/
|
||||
|
||||
/* clock parameters */
|
||||
static int main_time = -1;
|
||||
static int byoyomi_time = -1;
|
||||
static int byoyomi_stones = -1; /* <= 0 if no byo-yomi */
|
||||
|
||||
/* Keep track of the remaining time left.
|
||||
* If stones_left is zero, .._time_left is the remaining main time.
|
||||
* Otherwise, the remaining time for this byoyomi period.
|
||||
*/
|
||||
struct remaining_time_data {
|
||||
double time_left;
|
||||
double time_for_last_move;
|
||||
int stones;
|
||||
int movenum;
|
||||
int in_byoyomi;
|
||||
};
|
||||
|
||||
struct timer_data {
|
||||
struct remaining_time_data official;
|
||||
struct remaining_time_data estimated;
|
||||
int time_out;
|
||||
};
|
||||
|
||||
static struct timer_data black_time_data;
|
||||
static struct timer_data white_time_data;
|
||||
|
||||
|
||||
/* Echo a time value in STANDARD format */
|
||||
static void
|
||||
timeval_print(FILE *outfile, double tv)
|
||||
{
|
||||
int min;
|
||||
double sec;
|
||||
|
||||
min = (int) tv / 60;
|
||||
sec = tv - min*60;
|
||||
|
||||
fprintf(outfile, "%3dmin %.2fsec ", min, sec);
|
||||
}
|
||||
|
||||
|
||||
/* Print the clock status for one side. */
|
||||
void
|
||||
clock_print(int color)
|
||||
{
|
||||
struct timer_data *const td
|
||||
= (color == BLACK) ? &black_time_data : &white_time_data;
|
||||
|
||||
fprintf(stderr, "clock: ");
|
||||
fprintf(stderr, "%s ", color_to_string(color));
|
||||
|
||||
if (td->time_out)
|
||||
fprintf(stderr, "TIME OUT! ");
|
||||
else {
|
||||
if (td->estimated.in_byoyomi) {
|
||||
fprintf(stderr, "byoyomi");
|
||||
timeval_print(stderr, td->estimated.time_left);
|
||||
fprintf(stderr, "for %d stones.", td->estimated.stones);
|
||||
}
|
||||
else
|
||||
timeval_print(stderr, td->estimated.time_left);
|
||||
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
|
||||
/******************************/
|
||||
/* Initialization functions */
|
||||
/******************************/
|
||||
|
||||
/*
|
||||
* Initialize the time settings for this game.
|
||||
* -1 means "do not modify this value".
|
||||
*
|
||||
* byo_time > 0 and byo_stones == 0 means no time settings.
|
||||
*/
|
||||
void
|
||||
clock_settings(int time, int byo_time, int byo_stones)
|
||||
{
|
||||
if (time >= 0)
|
||||
main_time = time;
|
||||
if (byo_time >= 0)
|
||||
byoyomi_time = byo_time;
|
||||
if (byo_stones >= 0)
|
||||
byoyomi_stones = byo_stones;
|
||||
init_timers();
|
||||
}
|
||||
|
||||
/* Get time settings. Returns 1 if any time settings have been made,
|
||||
* 0 otherwise.
|
||||
*/
|
||||
int
|
||||
have_time_settings(void)
|
||||
{
|
||||
/* According to the semantics of the GTP command 'time_settings', the
|
||||
* following signifies no time limits.
|
||||
*/
|
||||
if (byoyomi_time > 0 && byoyomi_stones == 0)
|
||||
return 0;
|
||||
else
|
||||
return (main_time >= 0 || byoyomi_time >= 0);
|
||||
}
|
||||
|
||||
|
||||
/* Initialize all timers. */
|
||||
void
|
||||
init_timers()
|
||||
{
|
||||
white_time_data.official.time_left = main_time;
|
||||
white_time_data.official.time_for_last_move = -1.0;
|
||||
white_time_data.official.stones = 0;
|
||||
white_time_data.official.movenum = 0;
|
||||
white_time_data.official.in_byoyomi = 0;
|
||||
white_time_data.estimated = white_time_data.official;
|
||||
white_time_data.time_out = 0;
|
||||
black_time_data = white_time_data;
|
||||
|
||||
level_offset = 0;
|
||||
}
|
||||
|
||||
|
||||
/*****************************/
|
||||
/* Clock access functions. */
|
||||
/*****************************/
|
||||
|
||||
|
||||
void
|
||||
update_time_left(int color, int time_left, int stones)
|
||||
{
|
||||
struct timer_data *const td
|
||||
= ((color == BLACK) ? &black_time_data : &white_time_data);
|
||||
int time_used = td->official.time_left - time_left;
|
||||
|
||||
if (time_left > 0)
|
||||
td->time_out = 0;
|
||||
else
|
||||
td->time_out = 1;
|
||||
|
||||
/* Did our estimate for time usage go wrong? */
|
||||
if (time_used > 0
|
||||
&& gg_abs(time_used - td->estimated.time_for_last_move) >= 1.0)
|
||||
td->estimated.time_for_last_move = time_used;
|
||||
td->estimated.stones = stones;
|
||||
td->estimated.movenum = movenum;
|
||||
/* Did our clock go wrong? */
|
||||
if (gg_abs(td->estimated.time_left - time_left) >= 1.0)
|
||||
td->estimated.time_left = time_left;
|
||||
if (stones > 0)
|
||||
td->estimated.in_byoyomi = 1;
|
||||
else
|
||||
td->estimated.in_byoyomi = 0;
|
||||
|
||||
td->official.stones = stones;
|
||||
td->official.movenum = movenum;
|
||||
td->official.time_for_last_move = td->official.time_for_last_move - time_left;
|
||||
td->official.time_left = time_left;
|
||||
td->official.in_byoyomi = td->estimated.in_byoyomi;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the estimated timer after a move has been made.
|
||||
*/
|
||||
void
|
||||
clock_push_button(int color)
|
||||
{
|
||||
static double last_time = -1.0;
|
||||
static int last_movenum = -1;
|
||||
struct timer_data *const td
|
||||
= (color == BLACK) ? &black_time_data : &white_time_data;
|
||||
double now = gg_gettimeofday();
|
||||
|
||||
if (!have_time_settings())
|
||||
return;
|
||||
|
||||
if (last_movenum >= 0
|
||||
&& movenum == last_movenum + 1
|
||||
&& movenum > td->estimated.movenum) {
|
||||
double time_used = now - last_time;
|
||||
td->estimated.time_left -= time_used;
|
||||
td->estimated.movenum = movenum;
|
||||
td->estimated.time_for_last_move = time_used;
|
||||
if (td->estimated.time_left < 0) {
|
||||
if (td->estimated.in_byoyomi || byoyomi_stones == 0) {
|
||||
DEBUG(DEBUG_TIME, "%s ran out of time.\n", color_to_string(color));
|
||||
if (debug & DEBUG_TIME)
|
||||
clock_print(color);
|
||||
td->time_out = 1;
|
||||
}
|
||||
else {
|
||||
/* Entering byoyomi. */
|
||||
gg_assert(!(td->estimated.in_byoyomi));
|
||||
td->estimated.in_byoyomi = 1;
|
||||
td->estimated.stones = byoyomi_stones - 1;
|
||||
td->estimated.time_left += byoyomi_time;
|
||||
if (td->estimated.time_left < 0)
|
||||
td->time_out = 1;
|
||||
}
|
||||
}
|
||||
else if (td->estimated.stones > 0) {
|
||||
gg_assert(td->estimated.in_byoyomi);
|
||||
td->estimated.stones = td->estimated.stones - 1;
|
||||
if (td->estimated.stones == 0) {
|
||||
td->estimated.time_left = byoyomi_time;
|
||||
td->estimated.stones = byoyomi_stones;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_movenum = movenum;
|
||||
last_time = now;
|
||||
|
||||
/* Update main timer. */
|
||||
if (debug & DEBUG_TIME)
|
||||
clock_print(color);
|
||||
}
|
||||
|
||||
|
||||
/**********************/
|
||||
/* Autolevel system */
|
||||
/**********************/
|
||||
|
||||
|
||||
/* Analyze the two most recent time reports and determine the time
|
||||
* spent on the last moves, the (effective) number of stones left and
|
||||
* the (effective) remaining time.
|
||||
*/
|
||||
static int
|
||||
analyze_time_data(int color, double *time_for_last_move, double *time_left,
|
||||
int *stones_left)
|
||||
{
|
||||
struct remaining_time_data *const timer
|
||||
= (color == BLACK) ? &black_time_data.estimated
|
||||
: &white_time_data.estimated;
|
||||
|
||||
/* Do we have any time limits. */
|
||||
if (!have_time_settings())
|
||||
return 0;
|
||||
|
||||
/* If we don't have consistent time information yet, just return. */
|
||||
if (timer->time_for_last_move < 0.0)
|
||||
return 0;
|
||||
|
||||
*time_for_last_move = timer->time_for_last_move;
|
||||
|
||||
if (timer->stones == 0) {
|
||||
/* Main time running. */
|
||||
*time_left = timer->time_left + byoyomi_time;
|
||||
if (byoyomi_time > 0)
|
||||
*stones_left = byoyomi_stones;
|
||||
else {
|
||||
/* Absolute time. Here we aim to be able to play at least X more
|
||||
* moves or a total of Y moves. We choose Y as a third of the
|
||||
* number of vertices and X as 40% of Y. For 19x19 this means
|
||||
* that we aim to play at least a total of 120 moves
|
||||
* (corresponding to a 240 move game) or another 24 moves.
|
||||
*
|
||||
* FIXME: Maybe we should use the game_status of
|
||||
* influence_evaluate_position() here to guess how many moves
|
||||
* are remaining.
|
||||
*/
|
||||
int nominal_moves = board_size * board_size / 3;
|
||||
*stones_left = gg_max(nominal_moves - movenum / 2,
|
||||
2 * nominal_moves / 5);
|
||||
}
|
||||
}
|
||||
else {
|
||||
*time_left = timer->time_left;
|
||||
*stones_left = timer->stones;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Adjust the level offset given information of current playing speed
|
||||
* and remaining time and stones.
|
||||
*/
|
||||
void
|
||||
adjust_level_offset(int color)
|
||||
{
|
||||
double time_for_last_move;
|
||||
double time_left;
|
||||
int stones_left;
|
||||
|
||||
if (!analyze_time_data(color, &time_for_last_move, &time_left, &stones_left))
|
||||
return;
|
||||
|
||||
|
||||
/* These rules are both crude and ad hoc.
|
||||
*
|
||||
* FIXME: Use rules with at least some theoretical basis.
|
||||
*/
|
||||
if (time_left < time_for_last_move * (stones_left + 3))
|
||||
level_offset--;
|
||||
if (time_left < time_for_last_move * stones_left)
|
||||
level_offset--;
|
||||
if (3 * time_left < 2 * time_for_last_move * stones_left)
|
||||
level_offset--;
|
||||
if (2 * time_left < time_for_last_move * stones_left)
|
||||
level_offset--;
|
||||
if (3 * time_left < time_for_last_move * stones_left)
|
||||
level_offset--;
|
||||
|
||||
if (time_for_last_move == 0)
|
||||
time_for_last_move = 1;
|
||||
if (time_left > time_for_last_move * (stones_left + 6))
|
||||
level_offset++;
|
||||
if (time_left > 2 * time_for_last_move * (stones_left + 6))
|
||||
level_offset++;
|
||||
|
||||
if (level + level_offset < min_level)
|
||||
level_offset = min_level - level;
|
||||
|
||||
if (level + level_offset > max_level)
|
||||
level_offset = max_level - level;
|
||||
|
||||
DEBUG(DEBUG_TIME, "New level %d (%d %C %f %f %d)\n", level + level_offset,
|
||||
movenum / 2, color, time_for_last_move, time_left, stones_left);
|
||||
}
|
||||
|
||||
|
||||
/********************************/
|
||||
/* Interface to level settings. */
|
||||
/********************************/
|
||||
|
||||
int
|
||||
get_level()
|
||||
{
|
||||
return level + level_offset;
|
||||
}
|
||||
|
||||
void
|
||||
set_level(int new_level)
|
||||
{
|
||||
level = new_level;
|
||||
level_offset = 0;
|
||||
if (level > max_level)
|
||||
max_level = level;
|
||||
if (level < min_level)
|
||||
min_level = level;
|
||||
}
|
||||
|
||||
void
|
||||
set_max_level(int new_max)
|
||||
{
|
||||
max_level = new_max;
|
||||
}
|
||||
|
||||
void
|
||||
set_min_level(int new_min)
|
||||
{
|
||||
min_level = new_min;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
50
gnugo/engine/clock.h
Normal file
50
gnugo/engine/clock.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef _CLOCK_H_
|
||||
#define _CLOCK_H_
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
/* initialization and activation */
|
||||
void clock_settings(int maintime, int byotime, int byostones);
|
||||
void init_timers(void);
|
||||
|
||||
/* main access */
|
||||
void clock_push_button(int color);
|
||||
void update_time_left(int color, int time_left, int stones);
|
||||
void clock_print(int color);
|
||||
int have_time_settings(void);
|
||||
|
||||
void adjust_level_offset(int color);
|
||||
|
||||
/* Access to level settings. */
|
||||
int get_level(void);
|
||||
void set_level(int new_level);
|
||||
void set_max_level(int new_max);
|
||||
void set_min_level(int new_min);
|
||||
|
||||
|
||||
#endif /* _CLOCK_H_ */
|
||||
|
||||
|
1591
gnugo/engine/combination.c
Normal file
1591
gnugo/engine/combination.c
Normal file
File diff suppressed because it is too large
Load Diff
2605
gnugo/engine/dragon.c
Normal file
2605
gnugo/engine/dragon.c
Normal file
File diff suppressed because it is too large
Load Diff
527
gnugo/engine/endgame.c
Normal file
527
gnugo/engine/endgame.c
Normal file
@ -0,0 +1,527 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
#include "gnugo.h"
|
||||
#include "liberty.h"
|
||||
|
||||
|
||||
static void endgame_analyze_worm_liberties(int pos, int color);
|
||||
static void endgame_find_backfilling_dame(int str, int color);
|
||||
static int endgame_find_liberties(int str, int *essential_liberties,
|
||||
int essential_libs[MAXLIBS],
|
||||
int *inessential_liberties,
|
||||
int inessential_libs[MAXLIBS],
|
||||
int *false_eye_liberties,
|
||||
int false_eye_libs[MAXLIBS]);
|
||||
|
||||
|
||||
/* Generate endgame moves. These are typically moves in settled positions,
|
||||
* they aren't worth many points. Currently, we generate such moves using
|
||||
* patterns in endgames.db and this algorithmic move generator. It is only
|
||||
* called when no move of value higher than 6.0 has been found on board.
|
||||
*/
|
||||
void
|
||||
endgame(int color)
|
||||
{
|
||||
int pos;
|
||||
|
||||
TRACE("\nEndgame move generator tries to look for additional moves...\n");
|
||||
|
||||
/* Try to generate some moves using endgame_analyze_worm_liberties(). See
|
||||
* the description of that function to find what moves it generates.
|
||||
*/
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
/* We are only interested in alive, but not invincible worms which are
|
||||
* parts of alive dragons. That is, the position must be stable.
|
||||
*/
|
||||
if (IS_STONE(board[pos])
|
||||
&& worm[pos].origin == pos
|
||||
&& dragon[pos].status == ALIVE
|
||||
&& !worm[pos].invincible
|
||||
&& !worm[pos].inessential
|
||||
&& worm[pos].attack_codes[0] == 0) {
|
||||
endgame_analyze_worm_liberties(pos, color);
|
||||
endgame_find_backfilling_dame(pos, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This function handles two cases of endgame moves. Consider these two
|
||||
* positions (from endgame:301,302 and endgame:801,802 respectively):
|
||||
*
|
||||
* OOOOOOO XXXXXO.|
|
||||
* O.XXX.O X.O.O*.|
|
||||
* OOX.XXO X.OOOX.|
|
||||
* .O*X.OX XXXXOX.|
|
||||
* .OXX..X X..XOOO|
|
||||
* .OOOXX. XXXXXXX|
|
||||
*
|
||||
* The two marked with `*' moves are worth one point in gote each (for
|
||||
* both colors). The first one is obvious - once black runs short on
|
||||
* liberties, he'll have to defend in his own eyespace, wasting one
|
||||
* point. In the second position, although black sacrifices one point
|
||||
* by playing in white's territory, he forces white to eventually
|
||||
* capture the black string, losing three points. However, white has
|
||||
* to play at `*' sooner or later if black doesn't take that vertex, so
|
||||
* the move is worth 3 - 1 - 1 = 1 point only, not two.
|
||||
*
|
||||
* This function is able to find such moves. The algorithm is based on
|
||||
* finding so called "inessential liberties". These are defined as
|
||||
* liberties, which satisfy five conditions:
|
||||
*
|
||||
* 1) they are not within an eye (not in someone's territory),
|
||||
* 2) all their adjacent worms and dragons are alive,
|
||||
* 3) they have adjacent worms of both colors,
|
||||
* 4) they have no other adjacent worms of the same color as the worm
|
||||
* under consideration,
|
||||
* 5) they are safe to fill with stones of other than the worm's color.
|
||||
*
|
||||
* Such liberties are supposed to never become territory (they can't become
|
||||
* an additional eye for the worm under consideration), the worm cannot
|
||||
* connect to something via such a liberty and they will (or at least can)
|
||||
* eventually be filled by either of the players.
|
||||
*
|
||||
* FIXME: This function can probably be improved to handle more cases.
|
||||
*/
|
||||
static void
|
||||
endgame_analyze_worm_liberties(int pos, int color)
|
||||
{
|
||||
int k;
|
||||
int worm_color = board[pos];
|
||||
int other = OTHER_COLOR(worm_color);
|
||||
int essential_liberties;
|
||||
int essential_libs[MAXLIBS];
|
||||
int inessential_liberties;
|
||||
int inessential_libs[MAXLIBS];
|
||||
int false_eye_liberties;
|
||||
int false_eye_libs[MAXLIBS];
|
||||
int num_attacks;
|
||||
int num_attacks2;
|
||||
int attacks[MAXLIBS];
|
||||
int defenses[MAXLIBS];
|
||||
int apos;
|
||||
int value;
|
||||
|
||||
if (!endgame_find_liberties(pos, &essential_liberties, essential_libs,
|
||||
&inessential_liberties, inessential_libs,
|
||||
&false_eye_liberties, false_eye_libs))
|
||||
return;
|
||||
|
||||
apos = NO_MOVE;
|
||||
num_attacks = 0;
|
||||
|
||||
/* Now, try to predict the final state of the position. We fill all
|
||||
* inessential liberties by stones of other than the current worm's
|
||||
* color. This is just a guess, we'll have to check the results later.
|
||||
*/
|
||||
for (k = 0; k < inessential_liberties; k++) {
|
||||
if (!safe_move(inessential_libs[k], other)
|
||||
|| !trymove(inessential_libs[k], other, "endgame", pos))
|
||||
break;
|
||||
}
|
||||
|
||||
/* If we haven't eaten the worm accidentally, look if any attacks on the
|
||||
* worm have appeared.
|
||||
*/
|
||||
if (k == inessential_liberties && board[pos] != EMPTY) {
|
||||
/* Try to look for moves as in position 1. If the worm still has
|
||||
* more than one liberty, try to play on every essential liberty
|
||||
* and see if an attack appears.
|
||||
*/
|
||||
if (countlib(pos) > 1) {
|
||||
for (k = 0; k < essential_liberties; k++) {
|
||||
int lib = essential_libs[k];
|
||||
|
||||
if (safe_move(lib, worm_color) && safe_move(lib, other)
|
||||
&& trymove(lib, other, "endgame", pos)) {
|
||||
if (attack(pos, NULL) != 0) {
|
||||
int dpos;
|
||||
|
||||
if (find_defense(pos, &dpos) && is_proper_eye_space(dpos)) {
|
||||
int i;
|
||||
|
||||
/* If the attack cannot be defended against by playing on
|
||||
* another essential liberty, filling a pure false eye (an
|
||||
* eye which can't become territory) or capturing an opponent
|
||||
* string in atari, keep it for now.
|
||||
*/
|
||||
for (i = 0; i < essential_liberties; i++) {
|
||||
if (i != k && essential_libs[i] != dpos
|
||||
&& does_defend(essential_libs[i], pos))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == essential_liberties) {
|
||||
for (i = 0; i < false_eye_liberties; i++) {
|
||||
if (does_defend(false_eye_libs[i], pos))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == false_eye_liberties) {
|
||||
int adj[MAXCHAIN];
|
||||
int adjs;
|
||||
|
||||
adjs = chainlinks2(pos, adj, 1);
|
||||
for (i = 0; i < adjs; i++) {
|
||||
int lib2;
|
||||
findlib(adj[i], 1, &lib2);
|
||||
if (lib2 != dpos && !is_proper_eye_space(lib2)
|
||||
&& does_defend(lib2, pos))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == adjs) {
|
||||
attacks[num_attacks] = lib;
|
||||
defenses[num_attacks] = dpos;
|
||||
num_attacks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popgo();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (essential_liberties > 0) {
|
||||
/* If the only remaining liberty is essential, it is an attack. */
|
||||
attacks[num_attacks] = essential_libs[0];
|
||||
defenses[num_attacks] = NO_MOVE;
|
||||
num_attacks++;
|
||||
}
|
||||
|
||||
/* Try to find moves as in position 2. */
|
||||
if (attack(pos, &apos) != 0) {
|
||||
if (is_proper_eye_space(apos)) {
|
||||
/* The attack point is in someone's eye (must be an eye which the worm
|
||||
* bounds). This looks promising. If this attack cannot be averted by
|
||||
* playing on an essential liberty, keep it for further analyzis.
|
||||
*/
|
||||
for (k = 0; k < essential_liberties; k++) {
|
||||
if (does_defend(essential_libs[k], pos)) {
|
||||
apos = NO_MOVE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (apos != NO_MOVE && worm_color == color && !does_defend(apos, pos))
|
||||
apos = NO_MOVE;
|
||||
}
|
||||
else
|
||||
apos = NO_MOVE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* We were unable to fill all the liberties. Modify
|
||||
* `inessential_liberties' in order to undo the right number of
|
||||
* moves.
|
||||
*/
|
||||
inessential_liberties = k;
|
||||
}
|
||||
|
||||
/* Undo all the moves made to fill inessential liberties. */
|
||||
for (k = 0; k < inessential_liberties; k++)
|
||||
popgo();
|
||||
ASSERT1(stackp == 0, pos);
|
||||
|
||||
num_attacks2 = 0;
|
||||
for (k = 0; k < num_attacks; k++) {
|
||||
/* These moves must be safe for the other color, otherwise they are
|
||||
* pointless. Note that checks for safety on previous step were not
|
||||
* sufficient since we had additional stones on board then.
|
||||
*/
|
||||
if (safe_move(attacks[k], other)) {
|
||||
if (defenses[k] != NO_MOVE) {
|
||||
int i;
|
||||
|
||||
/* Consider this position:
|
||||
*
|
||||
* .X...OO The move at `*' satisfies the conditions above.
|
||||
* .X*OO.O However, it is pointless, since black has a miai
|
||||
* X.OX..O move at `a' to force white to play `b'. That is,
|
||||
* XXObOOO no matter if white plays `*' or `a', black takes
|
||||
* .XXaOXO the other point and white has to fill `b'. So, if
|
||||
* ...XXXX there is a point, adjacent to defense point, safe
|
||||
* for "other" color, we discard the attack.
|
||||
*
|
||||
* Also, in some positions, defense point is adjacent to worm
|
||||
* inessential liberty. In such cases we discard the attack too.
|
||||
*/
|
||||
for (i = 0; i < 4; i++) {
|
||||
int pos2 = defenses[k] + delta[i];
|
||||
|
||||
if (board[pos2] == EMPTY) {
|
||||
int m;
|
||||
|
||||
if (!is_proper_eye_space(pos2) && safe_move(pos2, other))
|
||||
break;
|
||||
|
||||
for (m = 0; m < inessential_liberties; m++) {
|
||||
if (inessential_libs[m] == pos2)
|
||||
break;
|
||||
}
|
||||
|
||||
if (m < inessential_liberties)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this is not the case, the attack is kept for the final trial. */
|
||||
if (i == 4)
|
||||
attacks[num_attacks2++] = attacks[k];
|
||||
}
|
||||
else {
|
||||
/* This must be the only attack (filling all inessential liberties
|
||||
* gives an atari).
|
||||
*/
|
||||
ASSERT1(num_attacks == 1, pos);
|
||||
attacks[num_attacks2++] = attacks[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = 0;
|
||||
if (apos != NO_MOVE) {
|
||||
/* We use the number of string's liberties minus 2 as the value of
|
||||
* the move. Minus 2 is explained in the comment before the
|
||||
* function. In some rare cases the value may differ, but this
|
||||
* should be a good guess.
|
||||
*/
|
||||
value = accuratelib(apos, other, MAXLIBS, NULL) - 2;
|
||||
}
|
||||
|
||||
/* If we haven't found anything interesting or have already dropped it,
|
||||
* there is no point trying more moves, so we return now.
|
||||
*/
|
||||
if (value <= 0 && num_attacks2 == 0)
|
||||
return;
|
||||
|
||||
/* We filled the liberties with stones of "other" color. That could lead to
|
||||
* some strange attacks, since inessential liberties are not always really
|
||||
* inessential (see trevorb:320 and trevorb:940 for examples where this step
|
||||
* is necessary). Now we fill the liberties with stones of the same color as
|
||||
* the current worm. If the results remain unchanged, then we can probably
|
||||
* trust them.
|
||||
*/
|
||||
for (k = 0; k < inessential_liberties; k++) {
|
||||
if (!trymove(inessential_libs[k], worm_color, "endgame", pos))
|
||||
break;
|
||||
}
|
||||
|
||||
/* GNU Go currently doesn't allow suicide, but let's assume it does. */
|
||||
if (k == inessential_liberties && board[pos] != EMPTY) {
|
||||
if (countlib(pos) > 1) {
|
||||
for (k = 0; k < num_attacks2; k++) {
|
||||
if (trymove(attacks[k], other, "endgame", pos)) {
|
||||
if (attack(pos, NULL) != 0) {
|
||||
TRACE(" endgame move with territorial value %d.0 found at %1m\n",
|
||||
1, attacks[k]);
|
||||
add_expand_territory_move(attacks[k]);
|
||||
/* FIXME: We just guess the value here. Find a way to calculate it
|
||||
* (more) precisely.
|
||||
*/
|
||||
set_minimum_territorial_value(attacks[k], 1.0);
|
||||
}
|
||||
|
||||
popgo();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (essential_liberties > 0 && essential_libs[0] == attacks[0]) {
|
||||
TRACE(" endgame move with territorial value %d.0 found at %1m\n",
|
||||
1, attacks[k]);
|
||||
add_expand_territory_move(attacks[0]);
|
||||
/* FIXME: We just guess the value here. Find a way to calculate it
|
||||
* (more) precisely.
|
||||
*/
|
||||
set_minimum_territorial_value(attacks[0], 1.0);
|
||||
}
|
||||
|
||||
if (value > 0 && does_attack(apos, pos)) {
|
||||
TRACE(" endgame move with territorial value %d.0 found at %1m\n",
|
||||
value, apos);
|
||||
add_expand_territory_move(apos);
|
||||
set_minimum_territorial_value(apos, (float) value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Don't undo moves we didn't play. */
|
||||
inessential_liberties = k;
|
||||
}
|
||||
|
||||
/* Undo all the moves made at the third step. */
|
||||
for (k = 0; k < inessential_liberties; k++)
|
||||
popgo();
|
||||
ASSERT1(stackp == 0, pos);
|
||||
}
|
||||
|
||||
/* A backfilling dame is a defense move, usually within potential own
|
||||
* territory, which does not have to be played immediately but after
|
||||
* outer liberties of some string have been filled. If those outer
|
||||
* liberties are dame points (here inessential liberties), it is
|
||||
* usually better to play the backfilling moves before filling the
|
||||
* dame points. If nothing else it reduces the risk for making stupid
|
||||
* blunders while filling dame.
|
||||
*/
|
||||
static void
|
||||
endgame_find_backfilling_dame(int str, int color_to_move)
|
||||
{
|
||||
int k;
|
||||
int color = board[str];
|
||||
int other = OTHER_COLOR(color);
|
||||
int essential_liberties;
|
||||
int essential_libs[MAXLIBS];
|
||||
int inessential_liberties;
|
||||
int inessential_libs[MAXLIBS];
|
||||
int false_eye_liberties;
|
||||
int false_eye_libs[MAXLIBS];
|
||||
int dpos;
|
||||
int loop_again = 1;
|
||||
int potential_moves[BOARDMAX];
|
||||
int num_potential_moves = 0;
|
||||
int move = NO_MOVE;
|
||||
|
||||
while (loop_again) {
|
||||
loop_again = 0;
|
||||
if (!endgame_find_liberties(str, &essential_liberties, essential_libs,
|
||||
&inessential_liberties, inessential_libs,
|
||||
&false_eye_liberties, false_eye_libs))
|
||||
break;
|
||||
for (k = 0; k < inessential_liberties; k++) {
|
||||
if (!safe_move(inessential_libs[k], other)
|
||||
|| !trymove(inessential_libs[k], other, "endgame", str))
|
||||
continue;
|
||||
increase_depth_values();
|
||||
if (board[str] == EMPTY)
|
||||
break;
|
||||
if (attack_and_defend(str, NULL, NULL, NULL, &dpos)) {
|
||||
if (worm[dpos].color == EMPTY) {
|
||||
potential_moves[num_potential_moves] = dpos;
|
||||
num_potential_moves++;
|
||||
}
|
||||
forced_backfilling_moves[dpos] = 1;
|
||||
if (trymove(dpos, color, "endgame", str))
|
||||
increase_depth_values();
|
||||
loop_again = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (stackp > 0) {
|
||||
popgo();
|
||||
decrease_depth_values();
|
||||
}
|
||||
|
||||
for (k = num_potential_moves - 1; k >= 0; k--)
|
||||
if (safe_move(potential_moves[k], color)) {
|
||||
move = potential_moves[k];
|
||||
TRACE(" backfilling dame found at %1m for string %1m\n", move, str);
|
||||
if (color == color_to_move) {
|
||||
add_expand_territory_move(move);
|
||||
set_minimum_territorial_value(move, 0.1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find liberties of the string str with various characteristics. See
|
||||
* the comments above endgame_analyze_worm_liberties() for more
|
||||
* information.
|
||||
*/
|
||||
static int
|
||||
endgame_find_liberties(int str,
|
||||
int *essential_liberties, int essential_libs[MAXLIBS],
|
||||
int *inessential_liberties,
|
||||
int inessential_libs[MAXLIBS],
|
||||
int *false_eye_liberties, int false_eye_libs[MAXLIBS])
|
||||
{
|
||||
int liberties;
|
||||
int libs[MAXLIBS];
|
||||
int k;
|
||||
|
||||
ASSERT1(IS_STONE(board[str]), str);
|
||||
|
||||
*essential_liberties = 0;
|
||||
*inessential_liberties = 0;
|
||||
*false_eye_liberties = 0;
|
||||
|
||||
/* Find all string liberties. */
|
||||
liberties = findlib(str, MAXLIBS, libs);
|
||||
|
||||
/* Loop over the liberties and find inessential and essential ones. The
|
||||
* latter are defined as those, which are not inside an eye space, but
|
||||
* don't otherwise qualify as inessential. If we find a non-alive (dead
|
||||
* or critical) worm or dragon around, we stop looking for liberties and
|
||||
* skip the current worm (position is unstable).
|
||||
*/
|
||||
for (k = 0; k < liberties; k++) {
|
||||
int lib = libs[k];
|
||||
|
||||
if (!is_proper_eye_space(lib)) {
|
||||
int i;
|
||||
int essential = 0;
|
||||
int found_other = 0;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
int pos = lib + delta[i];
|
||||
|
||||
if (!IS_STONE(board[pos]) || !IS_STONE(worm[pos].color))
|
||||
continue;
|
||||
|
||||
if (worm[pos].attack_codes[0] != 0 || dragon[pos].status != ALIVE)
|
||||
return 0;
|
||||
|
||||
if (board[pos] == board[str]) {
|
||||
if (find_origin(pos) != find_origin(str))
|
||||
essential = 1;
|
||||
}
|
||||
else
|
||||
found_other = 1;
|
||||
}
|
||||
|
||||
if (i < 4)
|
||||
break;
|
||||
|
||||
if (found_other) {
|
||||
if (essential)
|
||||
essential_libs[(*essential_liberties)++] = lib;
|
||||
else
|
||||
inessential_libs[(*inessential_liberties)++] = lib;
|
||||
}
|
||||
else if (is_false_eye(half_eye, lib) && !false_eye_territory[lib])
|
||||
false_eye_libs[(*false_eye_liberties)++] = lib;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
569
gnugo/engine/engine.dsp
Normal file
569
gnugo/engine/engine.dsp
Normal file
@ -0,0 +1,569 @@
|
||||
# Microsoft Developer Studio Project File - Name="engine" - Package Owner=<4>
|
||||
# Microsoft Developer Studio Generated Build File, Format Version 6.00
|
||||
# ** DO NOT EDIT **
|
||||
|
||||
# TARGTYPE "Win32 (x86) Static Library" 0x0104
|
||||
|
||||
CFG=engine - Win32 Debug
|
||||
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
|
||||
!MESSAGE use the Export Makefile command and run
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "engine.mak".
|
||||
!MESSAGE
|
||||
!MESSAGE You can specify a configuration when running NMAKE
|
||||
!MESSAGE by defining the macro CFG on the command line. For example:
|
||||
!MESSAGE
|
||||
!MESSAGE NMAKE /f "engine.mak" CFG="engine - Win32 Debug"
|
||||
!MESSAGE
|
||||
!MESSAGE Possible choices for configuration are:
|
||||
!MESSAGE
|
||||
!MESSAGE "engine - Win32 Release" (based on "Win32 (x86) Static Library")
|
||||
!MESSAGE "engine - Win32 Debug" (based on "Win32 (x86) Static Library")
|
||||
!MESSAGE
|
||||
|
||||
# Begin Project
|
||||
# PROP AllowPerConfigDependencies 0
|
||||
# PROP Scc_ProjName ""
|
||||
# PROP Scc_LocalPath ""
|
||||
CPP=cl.exe
|
||||
RSC=rc.exe
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 0
|
||||
# PROP BASE Output_Dir "Release"
|
||||
# PROP BASE Intermediate_Dir "Release"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 0
|
||||
# PROP Output_Dir "Release"
|
||||
# PROP Intermediate_Dir "Release"
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
|
||||
# ADD CPP /GX /Zi /O2 /I "." /I ".." /I "..\sgf" /I "..\interface" /I "..\patterns" /I "..\utils" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /YX"gnugo.h" /Fd"Release/engine" /FD /c
|
||||
# ADD BASE RSC /l 0x409 /d "NDEBUG"
|
||||
# ADD RSC /l 0x409 /d "NDEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LIB32=link.exe -lib
|
||||
# ADD BASE LIB32 /nologo
|
||||
# ADD LIB32 /nologo
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# PROP BASE Use_MFC 0
|
||||
# PROP BASE Use_Debug_Libraries 1
|
||||
# PROP BASE Output_Dir "engine___Win32_Debug"
|
||||
# PROP BASE Intermediate_Dir "engine___Win32_Debug"
|
||||
# PROP BASE Target_Dir ""
|
||||
# PROP Use_MFC 0
|
||||
# PROP Use_Debug_Libraries 1
|
||||
# PROP Output_Dir "Debug"
|
||||
# PROP Intermediate_Dir "Debug"
|
||||
# PROP Target_Dir ""
|
||||
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
|
||||
# ADD CPP /W2 /Gm /GX /ZI /Od /I "." /I ".." /I "..\sgf" /I "..\interface" /I "..\patterns" /I "..\utils" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /FR /YX"gnugo.h" /Fd"Debug/engine" /FD /GZ /c
|
||||
# ADD BASE RSC /l 0x409 /d "_DEBUG"
|
||||
# ADD RSC /l 0x409 /d "_DEBUG"
|
||||
BSC32=bscmake.exe
|
||||
# ADD BASE BSC32 /nologo
|
||||
# ADD BSC32 /nologo
|
||||
LIB32=link.exe -lib
|
||||
# ADD BASE LIB32 /nologo
|
||||
# ADD LIB32 /nologo
|
||||
|
||||
!ENDIF
|
||||
|
||||
# Begin Target
|
||||
|
||||
# Name "engine - Win32 Release"
|
||||
# Name "engine - Win32 Debug"
|
||||
# Begin Group "Source Files"
|
||||
|
||||
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\aftermath.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\board.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\boardlib.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\breakin.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\cache.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\clock.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\combination.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\dragon.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\endgame.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\filllib.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\fuseki.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\genmove.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\globals.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\handicap.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\hash.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\influence.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\interface.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\matchpat.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\montecarlo.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\move_reasons.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\movelist.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\optics.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\owl.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\persistent.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\printutils.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\readconnect.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\reading.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\semeai.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\sgfdecide.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\sgffile.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\shapes.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\showbord.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\surround.c
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\unconditional.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\utils.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\value_moves.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\worm.c
|
||||
|
||||
!IF "$(CFG)" == "engine - Win32 Release"
|
||||
|
||||
!ELSEIF "$(CFG)" == "engine - Win32 Debug"
|
||||
|
||||
# ADD CPP /YX"gnugo.h"
|
||||
|
||||
!ENDIF
|
||||
|
||||
# End Source File
|
||||
# End Group
|
||||
# Begin Group "Header Files"
|
||||
|
||||
# PROP Default_Filter ""
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\board.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\cache.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\clock.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\gnugo.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\hash.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\influence.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\liberty.h
|
||||
# End Source File
|
||||
# Begin Source File
|
||||
|
||||
SOURCE=.\move_reasons.h
|
||||
# End Source File
|
||||
# End Group
|
||||
# End Target
|
||||
# End Project
|
630
gnugo/engine/filllib.c
Normal file
630
gnugo/engine/filllib.c
Normal file
@ -0,0 +1,630 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "liberty.h"
|
||||
|
||||
static int find_backfilling_move(int move, int color, int *backfill_move,
|
||||
int forbidden_moves[BOARDMAX]);
|
||||
static int filllib_confirm_safety(int move, int color, int *defense_point);
|
||||
|
||||
/* Determine whether a point is adjacent to at least one own string which
|
||||
* isn't dead.
|
||||
*/
|
||||
static int
|
||||
living_neighbor(int pos, int color)
|
||||
{
|
||||
int k;
|
||||
for (k = 0; k < 4; k++) {
|
||||
if (board[pos + delta[k]] == color
|
||||
&& dragon[pos + delta[k]].status != DEAD)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Determine whether (pos) effectively is a black or white point.
|
||||
* The test for inessentiality is to avoid filling the liberties
|
||||
* around a killing nakade string.
|
||||
*/
|
||||
static void
|
||||
analyze_neighbor(int pos, int *found_black, int *found_white)
|
||||
{
|
||||
switch (board[pos]) {
|
||||
case EMPTY:
|
||||
if (!(*found_black)
|
||||
&& living_neighbor(pos, BLACK)
|
||||
&& safe_move(pos, WHITE) != WIN)
|
||||
*found_black = 1;
|
||||
|
||||
if (!(*found_white)
|
||||
&& living_neighbor(pos, WHITE)
|
||||
&& safe_move(pos, BLACK) != WIN)
|
||||
*found_white = 1;
|
||||
|
||||
break;
|
||||
|
||||
case BLACK:
|
||||
if (!worm[pos].inessential && DRAGON2(pos).safety != INESSENTIAL) {
|
||||
if (dragon[pos].status == ALIVE
|
||||
|| dragon[pos].status == UNKNOWN)
|
||||
*found_black = 1;
|
||||
else
|
||||
*found_white = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case WHITE:
|
||||
if (!worm[pos].inessential && DRAGON2(pos).safety != INESSENTIAL) {
|
||||
if (dragon[pos].status == ALIVE
|
||||
|| dragon[pos].status == UNKNOWN)
|
||||
*found_white = 1;
|
||||
else
|
||||
*found_black = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* If no move of value can be found to play, this seeks to fill a
|
||||
* common liberty, backfilling or back-capturing if necessary. When
|
||||
* backfilling we take care to start from the right end, in the case
|
||||
* that several backfilling moves are ultimately necessary.
|
||||
*
|
||||
* If a move for color is found, return 1, otherwise return 0.
|
||||
* The move is returned in (*move).
|
||||
*/
|
||||
|
||||
int
|
||||
fill_liberty(int *move, int color)
|
||||
{
|
||||
int k;
|
||||
int pos;
|
||||
int other = OTHER_COLOR(color);
|
||||
int defense_point;
|
||||
int potential_color[BOARDMAX];
|
||||
|
||||
/* We first make a fast scan for intersections which are potential
|
||||
* candidates for liberty filling. This is not very accurate, but it
|
||||
* does filter out intersections which could never pass the real
|
||||
* tests below but might still require a lot of tactical reading in
|
||||
* the process.
|
||||
*/
|
||||
memset(potential_color, 0, sizeof(potential_color));
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (!IS_STONE(board[pos]))
|
||||
continue;
|
||||
|
||||
if (worm[pos].inessential || DRAGON2(pos).safety == INESSENTIAL)
|
||||
continue;
|
||||
|
||||
if (dragon[pos].status != ALIVE) {
|
||||
for (k = 0; k < 4; k++) {
|
||||
int pos2 = pos + delta[k];
|
||||
if (board[pos2] == EMPTY)
|
||||
potential_color[pos2] |= OTHER_COLOR(board[pos]);
|
||||
}
|
||||
}
|
||||
|
||||
if (dragon[pos].status != DEAD) {
|
||||
for (k = 0; k < 12; k++) {
|
||||
int d = delta[k%8];
|
||||
|
||||
if (k >= 8) {
|
||||
if (board[pos + d] != EMPTY)
|
||||
continue;
|
||||
d *= 2;
|
||||
}
|
||||
if (board[pos + d] == EMPTY)
|
||||
potential_color[pos + d] |= board[pos];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
/* It seems we can't trust an empty liberty to be gray-colored
|
||||
* either as a cave or as a cavity. Instead we look for empty
|
||||
* intersections with at least one neighbor of each color, where
|
||||
* dead stones count as enemy stones. We also count empty
|
||||
* neighbors to either color if the opponent can't play there.
|
||||
*/
|
||||
int found_white = 0;
|
||||
int found_black = 0;
|
||||
|
||||
if (board[pos] != EMPTY)
|
||||
continue;
|
||||
|
||||
/* Quick rejection based on preliminary test above. */
|
||||
if (potential_color[pos] != GRAY)
|
||||
continue;
|
||||
|
||||
/* Loop over the neighbors. */
|
||||
for (k = 0; k < 4; k++) {
|
||||
int d = delta[k];
|
||||
if (ON_BOARD(pos + d))
|
||||
analyze_neighbor(pos + d, &found_black, &found_white);
|
||||
}
|
||||
|
||||
/* Do we have neighbors of both colors? */
|
||||
if (!(found_white && found_black))
|
||||
continue;
|
||||
|
||||
/* Ok, we wish to play here, but maybe we can't. The following
|
||||
* cases may occur:
|
||||
* 1. Move is legal and safe.
|
||||
* 2. Move is legal but not safe because it's in the middle of a seki.
|
||||
* 3. Move is legal but not safe, can be played after backfilling.
|
||||
* 4. Move is an illegal ko recapture.
|
||||
* 5. Move is illegal but can be played after back-captures.
|
||||
* 6. Move would violate confirm_safety.
|
||||
*/
|
||||
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Considering move at %1m.\n", pos);
|
||||
|
||||
/* Legal and tactically safe, play it if it passes
|
||||
* confirm_safety test, i.e. that it isn't a blunder which
|
||||
* causes problems for other strings.
|
||||
*/
|
||||
if (safe_move(pos, color) == WIN) {
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Tactically safe.\n");
|
||||
if (filllib_confirm_safety(pos, color, &defense_point)) {
|
||||
/* Safety confirmed. */
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Safety confirmed.\n");
|
||||
*move = pos;
|
||||
return 1;
|
||||
}
|
||||
else if (defense_point != NO_MOVE && is_legal(defense_point, color)) {
|
||||
/* Safety not confirmed because the move at (pos) would set
|
||||
* up a double threat. (defense_point) is assumed to defend
|
||||
* against this threat.
|
||||
*
|
||||
* FIXME: We should verify that (defense_point) really is effective.
|
||||
*/
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Safety not confirmed, but %1m defends.\n",
|
||||
defense_point);
|
||||
*move = defense_point;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
/* The move causes problems somewhere else on the board, so
|
||||
* we have to discard it. If everything works right this
|
||||
* should not happen at this time.
|
||||
*/
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Safety not confirmed, discarded.\n");
|
||||
TRACE("Warning: Blunder detected in fill_liberty().\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to play the move. */
|
||||
if (trymove(pos, color, "fill_liberty", NO_MOVE)) {
|
||||
int forbidden_moves[BOARDMAX];
|
||||
popgo();
|
||||
/* Legal, but not safe. Look for backfilling move. */
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Legal but not safe, looking for backfilling move.\n");
|
||||
|
||||
memset(forbidden_moves, 0, sizeof(forbidden_moves));
|
||||
while (find_backfilling_move(pos, color, move, forbidden_moves)) {
|
||||
/* Mark as forbidden in case we need another turn in the loop. */
|
||||
forbidden_moves[*move] = 1;
|
||||
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Backfilling move at %1m.\n", *move);
|
||||
/* In certain positions it may happen that an illegal move
|
||||
* is found. This probably only can happen if we try to play
|
||||
* a move inside a lost semeai. Anyway we should discard the
|
||||
* move.
|
||||
*/
|
||||
if (!is_legal(*move, color)) {
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Was illegal, discarded.\n");
|
||||
*move = NO_MOVE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If the move turns out to be strategically unsafe, or
|
||||
* setting up a double threat elsewhere, also discard it.
|
||||
*/
|
||||
if (!filllib_confirm_safety(*move, color, &defense_point)) {
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Safety not confirmed, discarded.\n");
|
||||
*move = NO_MOVE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Seems to be ok. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* No acceptable backfilling move found.
|
||||
* If we captured some stones, this move should be ok anyway.
|
||||
*/
|
||||
if (does_capture_something(pos, color)) {
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Not tactically safe, but captures stones.\n");
|
||||
if (!filllib_confirm_safety(pos, color, &defense_point)) {
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Safety not confirmed, discarded.\n");
|
||||
continue;
|
||||
}
|
||||
*move = pos;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Move is illegal. Look for an attack on one of the neighbor
|
||||
* worms. If found, return that move for back-capture.
|
||||
*/
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Illegal, looking for back-capture.\n");
|
||||
for (k = 0; k < 4; k++) {
|
||||
int d = delta[k];
|
||||
if (board[pos + d] == other
|
||||
&& worm[pos + d].attack_codes[0] == WIN) {
|
||||
*move = worm[pos + d].attack_points[0];
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Found at %1m.\n", *move);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Nothing found, looking for ko back-capture.\n");
|
||||
for (k = 0; k < 4; k++) {
|
||||
int d = delta[k];
|
||||
if (board[pos + d] == other
|
||||
&& worm[pos + d].attack_codes[0] != 0
|
||||
&& is_legal(worm[pos + d].attack_points[0], color)) {
|
||||
*move = worm[pos + d].attack_points[0];
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Found at %1m.\n", *move);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(DEBUG_FILLLIB,
|
||||
"Filllib: Nothing found, looking for threat to back-capture.\n");
|
||||
for (k = 0; k < 4; k++) {
|
||||
int d = delta[k];
|
||||
if (board[pos + d] == other
|
||||
&& worm[pos + d].attack_codes[0] != 0) {
|
||||
/* Just pick some other liberty. */
|
||||
/* FIXME: Something is odd about this code. */
|
||||
int libs[2];
|
||||
if (findlib(pos + d, 2, libs) > 1) {
|
||||
if (is_legal(libs[0], color))
|
||||
*move = libs[0];
|
||||
else if (is_legal(libs[1], color))
|
||||
*move = libs[1];
|
||||
else
|
||||
continue;
|
||||
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: Found at %1m.\n", *move);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Nothing found. */
|
||||
DEBUG(DEBUG_FILLLIB, "Filllib: No move found.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* The strategy for finding a backfilling move is to first identify
|
||||
* moves that
|
||||
*
|
||||
* 1. defends the position obtained after playing (move).
|
||||
* 2. captures a stone adjacent to our neighbors to (move), before
|
||||
* (move) is played.
|
||||
*
|
||||
* Then we check which of these are legal before (move) is played. If
|
||||
* there is at least one, we take one of these arbitrarily as a
|
||||
* backfilling move.
|
||||
*
|
||||
* Now it may happen that (move) still isn't a safe move. In that case
|
||||
* we recurse to find a new backfilling move. To do things really
|
||||
* correctly we should also give the opponent the opportunity to keep
|
||||
* up the balance of the position by letting him do a backfilling move
|
||||
* of his own. Maybe this could also be arranged by recursing this
|
||||
* function. Currently we only do a half-hearted attempt to find
|
||||
* opponent moves.
|
||||
*
|
||||
* The purpose of the forbidden_moves[] array is to get a new
|
||||
* backfilling move if the first one later was found to be unsafe,
|
||||
* like backfilling for J5 at F9 in filllib:45. With F9 marked as
|
||||
* forbidden the correct move at G9 is found.
|
||||
*/
|
||||
static int adjs[MAXCHAIN];
|
||||
static int libs[MAXLIBS];
|
||||
|
||||
static int
|
||||
find_backfilling_move(int move, int color, int *backfill_move,
|
||||
int forbidden_moves[BOARDMAX])
|
||||
{
|
||||
int k;
|
||||
int liberties;
|
||||
int neighbors;
|
||||
int found_one = 0;
|
||||
int apos = NO_MOVE;
|
||||
int bpos = NO_MOVE;
|
||||
int extra_pop = 0;
|
||||
int success = 0;
|
||||
int acode;
|
||||
int saved_move = NO_MOVE;
|
||||
int opponent_libs;
|
||||
|
||||
DEBUG(DEBUG_FILLLIB, "find_backfilling_move for %C %1m\n", color, move);
|
||||
if (debug & DEBUG_FILLLIB)
|
||||
dump_stack();
|
||||
|
||||
/* Play (move) and identify all liberties and adjacent strings. */
|
||||
if (!trymove(move, color, "find_backfilling_move", move))
|
||||
return 0; /* This shouldn't happen, I believe. */
|
||||
|
||||
/* The move wasn't safe, so there must be an attack for the
|
||||
* opponent. Save it for later use.
|
||||
*/
|
||||
acode = attack(move, &apos);
|
||||
gg_assert(acode != 0 && apos != NO_MOVE);
|
||||
|
||||
/* Find liberties. */
|
||||
liberties = findlib(move, MAXLIBS, libs);
|
||||
|
||||
/* Find neighbors. */
|
||||
neighbors = chainlinks(move, adjs);
|
||||
|
||||
/* Remove (move) again. */
|
||||
popgo();
|
||||
|
||||
/* It's most fun to capture stones. Start by trying to take some
|
||||
* neighbor off the board. If the attacking move does not directly
|
||||
* reduce the number of liberties of the attacked string we don't
|
||||
* trust it but keep it around if we don't find anything else. (See
|
||||
* filllib:17 for a position where this matters.)
|
||||
*
|
||||
* It is also necessary to take care to first attack the string with
|
||||
* the fewest liberties, which can probably be removed the fastest.
|
||||
* See filllib:37 for an example (J5 tactically attacks K7 but the
|
||||
* correct move is H5).
|
||||
*
|
||||
* FIXME: It seems we have to return immediately when we find an
|
||||
* attacking move, because recursing for further backfilling might
|
||||
* lead to moves which complete the capture but cannot be played
|
||||
* before the attacking move itself. This is not ideal but probably
|
||||
* good enough.
|
||||
*
|
||||
* In order to avoid losing unnecessary points while capturing dead
|
||||
* stones, we try first to capture stones in atari, second defending
|
||||
* at a liberty, and third capture stones with two or more
|
||||
* liberties. See filllib:43 for a position where capturing dead
|
||||
* stones (B10 or C8) loses a point compared to defending at a
|
||||
* liberty (C6).
|
||||
*/
|
||||
for (opponent_libs = 1; opponent_libs <= 1; opponent_libs++) {
|
||||
for (k = 0; k < neighbors; k++) {
|
||||
if (opponent_libs < 5 && countlib(adjs[k]) != opponent_libs)
|
||||
continue;
|
||||
if (attack(adjs[k], &bpos) == WIN) {
|
||||
if (forbidden_moves[bpos])
|
||||
continue;
|
||||
if (liberty_of_string(bpos, adjs[k])) {
|
||||
*backfill_move = bpos;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
saved_move = bpos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise look for a safe move at a liberty. */
|
||||
if (!found_one) {
|
||||
for (k = 0; k < liberties; k++) {
|
||||
if (!forbidden_moves[libs[k]] && safe_move(libs[k], color) == WIN) {
|
||||
*backfill_move = libs[k];
|
||||
found_one = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_one) {
|
||||
for (opponent_libs = 2; opponent_libs <= 5; opponent_libs++) {
|
||||
for (k = 0; k < neighbors; k++) {
|
||||
if (opponent_libs < 5 && countlib(adjs[k]) != opponent_libs)
|
||||
continue;
|
||||
if (attack(adjs[k], &bpos) == WIN) {
|
||||
if (forbidden_moves[bpos])
|
||||
continue;
|
||||
if (liberty_of_string(bpos, adjs[k])) {
|
||||
*backfill_move = bpos;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
saved_move = bpos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If no luck so far, try with superstring liberties. */
|
||||
if (!found_one) {
|
||||
trymove(move, color, "find_backfilling_move", move);
|
||||
find_proper_superstring_liberties(move, &liberties, libs, 0);
|
||||
popgo();
|
||||
for (k = 0; k < liberties; k++) {
|
||||
if (!forbidden_moves[libs[k]] && safe_move(libs[k], color) == WIN) {
|
||||
*backfill_move = libs[k];
|
||||
found_one = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If no luck so far, try attacking superstring neighbors. */
|
||||
if (!found_one) {
|
||||
trymove(move, color, "find_backfilling_move", move);
|
||||
superstring_chainlinks(move, &neighbors, adjs, 4);
|
||||
popgo();
|
||||
for (k = 0; k < neighbors; k++) {
|
||||
if (attack(adjs[k], &bpos) == WIN) {
|
||||
if (!forbidden_moves[bpos] && liberty_of_string(bpos, adjs[k])) {
|
||||
*backfill_move = bpos;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found_one) {
|
||||
ASSERT1(!forbidden_moves[*backfill_move], *backfill_move);
|
||||
|
||||
if (!trymove(*backfill_move, color, "find_backfilling_move", move))
|
||||
return 0; /* This really shouldn't happen. */
|
||||
|
||||
/* Allow opponent to get a move in here. */
|
||||
if (trymove(apos, OTHER_COLOR(color), "find_backfilling_move", move))
|
||||
extra_pop = 1;
|
||||
|
||||
/* If still not safe, recurse to find a new backfilling move. */
|
||||
if (safe_move(move, color) == WIN)
|
||||
success = 1;
|
||||
else
|
||||
success = find_backfilling_move(move, color, backfill_move,
|
||||
forbidden_moves);
|
||||
|
||||
/* Pop move(s) and return. */
|
||||
if (extra_pop)
|
||||
popgo();
|
||||
popgo();
|
||||
}
|
||||
|
||||
if (!success && saved_move != NO_MOVE) {
|
||||
ASSERT1(!forbidden_moves[saved_move], saved_move);
|
||||
*backfill_move = saved_move;
|
||||
success = 1;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
*backfill_move = NO_MOVE;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
/* Confirm that (move) is a safe move for color. In addition to
|
||||
* calling the global confirm_safety(), this function also calls the
|
||||
* owl code to verify the strategical viability of the move.
|
||||
*/
|
||||
static int
|
||||
filllib_confirm_safety(int move, int color, int *defense_point)
|
||||
{
|
||||
int k;
|
||||
int apos = NO_MOVE;
|
||||
int save_verbose;
|
||||
|
||||
gg_assert(stackp == 0);
|
||||
gg_assert(defense_point != NULL);
|
||||
*defense_point = NO_MOVE;
|
||||
|
||||
/* Before we can call the owl code, we need to find a neighbor of
|
||||
* our color.
|
||||
*/
|
||||
for (k = 0; k < 4; k++)
|
||||
if (board[move + delta[k]] == color) {
|
||||
apos = move + delta[k];
|
||||
break;
|
||||
}
|
||||
|
||||
/* If none found, look for a neighbor of an attacked adjacent string. */
|
||||
if (apos == NO_MOVE)
|
||||
for (k = 0; k < 4; k++) {
|
||||
int pos2 = move + delta[k];
|
||||
if (board[pos2] == OTHER_COLOR(color)
|
||||
&& !play_attack_defend_n(color, 0, 1, move, pos2)) {
|
||||
int adj;
|
||||
adj = chainlinks(pos2, adjs);
|
||||
/* It seems unlikely that we would ever get no adjacent strings
|
||||
* here, but if it should happen we simply give up and say the
|
||||
* move is unsafe.
|
||||
*/
|
||||
if (adj == 0)
|
||||
return 0;
|
||||
|
||||
apos = adjs[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Next attempt are diagonal neighbors. */
|
||||
if (apos == NO_MOVE) {
|
||||
for (k = 4; k < 8; k++)
|
||||
if (board[move + delta[k]] == color) {
|
||||
apos = move + delta[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* And two steps away. */
|
||||
if (apos == NO_MOVE) {
|
||||
for (k = 0; k < 4; k++)
|
||||
if (board[move + 2 * delta[k]] == color) {
|
||||
apos = move + 2 * delta[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We should have found something by now. If not something's
|
||||
* probably broken elsewhere. Declare the move unsafe if it happens.
|
||||
*/
|
||||
if (apos == NO_MOVE)
|
||||
return 0;
|
||||
|
||||
/* Ask the owl code whether this move is strategically viable. */
|
||||
|
||||
save_verbose = verbose;
|
||||
if (verbose > 0)
|
||||
verbose--;
|
||||
if (!owl_does_defend(move, apos, NULL))
|
||||
return 0;
|
||||
verbose = save_verbose;
|
||||
|
||||
return confirm_safety(move, color, defense_point, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
410
gnugo/engine/fuseki.c
Normal file
410
gnugo/engine/fuseki.c
Normal file
@ -0,0 +1,410 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "patterns.h"
|
||||
#include "random.h"
|
||||
|
||||
#include "sgftree.h"
|
||||
|
||||
|
||||
/* Pointless to do fuseki database pattern matching after this number
|
||||
* of stones have been placed on the board.
|
||||
*
|
||||
* Notice that we are not talking of the move number here but the
|
||||
* number of stones actually residing on the board. This does in
|
||||
* particular include handicap stones.
|
||||
*/
|
||||
#define MAX_FUSEKI_DATABASE_STONES 30
|
||||
|
||||
|
||||
#define UPPER_LEFT 0
|
||||
#define UPPER_RIGHT 1
|
||||
#define LOWER_LEFT 2
|
||||
#define LOWER_RIGHT 3
|
||||
|
||||
/* Global variables remembering which symmetries the position has. */
|
||||
static int horizontally_symmetric; /* symmetry with respect to K column */
|
||||
static int vertically_symmetric; /* symmetry with respect to 10 row */
|
||||
static int diagonally_symmetric; /* with respect to diagonal from UR to LL */
|
||||
|
||||
/* This value must be lower than the value for an ongoing joseki.
|
||||
* (Gets multiplied with board_size / 19.)
|
||||
*/
|
||||
#define EMPTY_CORNER_VALUE 25
|
||||
|
||||
/* check if region from i1, j1 to i2, j2 is open */
|
||||
|
||||
static int
|
||||
openregion(int i1, int i2, int j1, int j2)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
if (i1 > i2)
|
||||
return openregion(i2, i1, j1, j2);
|
||||
if (j1 > j2)
|
||||
return openregion(i1, i2, j2, j1);
|
||||
|
||||
/* Disregard parts of the region off the board. This is convenient
|
||||
* in order not to have to special-case tiny boards. It also secures
|
||||
* against potential reading outside the board[] array boundaries.
|
||||
*/
|
||||
if (i1 < 0)
|
||||
i1 = 0;
|
||||
if (j1 < 0)
|
||||
j1 = 0;
|
||||
if (i2 >= board_size)
|
||||
i2 = board_size - 1;
|
||||
if (j2 >= board_size)
|
||||
j2 = board_size - 1;
|
||||
|
||||
for (x = i1; x <= i2; x++)
|
||||
for (y = j1; y <= j2; y++)
|
||||
if (BOARD(x, y) != EMPTY)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* This function sets the global variables indicating symmetries of the
|
||||
* position. (Important for etiquette.)
|
||||
*/
|
||||
static void
|
||||
set_symmetries(void)
|
||||
{
|
||||
int i, j;
|
||||
horizontally_symmetric = 1;
|
||||
vertically_symmetric = 1;
|
||||
diagonally_symmetric = 1;
|
||||
for (i = 0; i < board_size
|
||||
&& (vertically_symmetric || horizontally_symmetric
|
||||
|| diagonally_symmetric); i++)
|
||||
for (j = 0; j < board_size; j++) {
|
||||
if (board[POS(i, j)] != board[POS(i, board_size - 1 - j)])
|
||||
horizontally_symmetric = 0;
|
||||
if (board[POS(i, j)] != board[POS(board_size - 1 - i, j)])
|
||||
vertically_symmetric = 0;
|
||||
if (board[POS(i, j)]
|
||||
!= board[POS(board_size - 1 - j, board_size - 1 - i)])
|
||||
diagonally_symmetric = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* The corner moves. */
|
||||
|
||||
static int corners[][2] =
|
||||
{
|
||||
{3, 3},
|
||||
{3, 4},
|
||||
{4, 3},
|
||||
{4, 4},
|
||||
{5, 3},
|
||||
{3, 5},
|
||||
{5, 4},
|
||||
{4, 5},
|
||||
};
|
||||
|
||||
/* Relative weights for different corner moves at different board
|
||||
sizes. */
|
||||
|
||||
/* up to 11x11 */
|
||||
static int small_board[] =
|
||||
{
|
||||
50, /* 3-3 */
|
||||
18, /* 3-4 */
|
||||
17, /* 4-3 */
|
||||
15, /* 4-4 */
|
||||
0, /* 5-3 */
|
||||
0, /* 3-5 */
|
||||
0, /* 5-4 */
|
||||
0, /* 4-5 */
|
||||
};
|
||||
|
||||
/* 12x12 to 15x15 */
|
||||
static int medium_board[] =
|
||||
{
|
||||
30, /* 3-3 */
|
||||
20, /* 3-4 */
|
||||
20, /* 4-3 */
|
||||
22, /* 4-4 */
|
||||
2, /* 5-3 */
|
||||
2, /* 3-5 */
|
||||
2, /* 5-4 */
|
||||
2, /* 4-5 */
|
||||
};
|
||||
|
||||
/* 16x16 and larger */
|
||||
static int large_board[] =
|
||||
{
|
||||
15, /* 3-3 */
|
||||
15, /* 3-4 */
|
||||
15, /* 4-3 */
|
||||
35, /* 4-4 */
|
||||
5, /* 5-3 */
|
||||
5, /* 3-5 */
|
||||
5, /* 5-4 */
|
||||
5, /* 4-5 */
|
||||
};
|
||||
|
||||
static void
|
||||
choose_corner_move(int corner, int *m, int *n)
|
||||
{
|
||||
int *table = 0;
|
||||
int sum_of_weights = 0;
|
||||
int i;
|
||||
int q;
|
||||
|
||||
if (board_size <= 11)
|
||||
table = small_board;
|
||||
else if (board_size <= 15)
|
||||
table = medium_board;
|
||||
else
|
||||
table = large_board;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
sum_of_weights += table[i];
|
||||
|
||||
q = gg_rand() % sum_of_weights;
|
||||
for (i = 0; i < 8; i++) {
|
||||
q -= table[i];
|
||||
if (q < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
*m = corners[i][0];
|
||||
*n = corners[i][1];
|
||||
|
||||
switch (corner) {
|
||||
case UPPER_LEFT:
|
||||
*m = *m - 1;
|
||||
*n = *n - 1;
|
||||
break;
|
||||
case UPPER_RIGHT:
|
||||
*m = *m - 1;
|
||||
*n = board_size - *n;
|
||||
break;
|
||||
case LOWER_LEFT:
|
||||
*m = board_size - *m;
|
||||
*n = *n - 1;
|
||||
break;
|
||||
case LOWER_RIGHT:
|
||||
*m = board_size - *m;
|
||||
*n = board_size - *n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Announce move, but check for politeness first. */
|
||||
static void
|
||||
announce_move(int move, int value, int color)
|
||||
{
|
||||
int i, j;
|
||||
/* This shouldn't happen. */
|
||||
if (board[move] != EMPTY)
|
||||
return;
|
||||
|
||||
/* Politeness: Black plays in lower right half of upper right corner first.
|
||||
* White plays in upper left half of lower left corner first.
|
||||
* (Not sure whether this is correct for handicap games. Is this an
|
||||
* urgent FIXME? :-) )
|
||||
*/
|
||||
if (horizontally_symmetric) {
|
||||
i = I(move);
|
||||
j = J(move);
|
||||
if ((2 * j < board_size - 1) ^ (color == WHITE))
|
||||
move = POS(i, board_size - 1 - j);
|
||||
}
|
||||
if (vertically_symmetric) {
|
||||
i = I(move);
|
||||
j = J(move);
|
||||
if ((2 * i > board_size - 1) ^ (color == WHITE))
|
||||
move = POS(board_size - 1 - i, j);
|
||||
}
|
||||
if (diagonally_symmetric) {
|
||||
i = I(move);
|
||||
j = J(move);
|
||||
if ((board_size - 1 - j > i) ^ (color == WHITE))
|
||||
move = POS(board_size - 1 - j, board_size - 1 - i);
|
||||
}
|
||||
|
||||
if (set_minimum_move_value(move, value))
|
||||
TRACE("Fuseki Player suggests %1m with value %d\n", move, value);
|
||||
}
|
||||
|
||||
|
||||
/* Storage for values collected during pattern matching. */
|
||||
static int fuseki_moves[MAX_BOARD * MAX_BOARD];
|
||||
static int fuseki_value[MAX_BOARD * MAX_BOARD];
|
||||
static int num_fuseki_moves;
|
||||
static int fuseki_total_value;
|
||||
|
||||
/* Callback for fuseki database pattern matching. */
|
||||
static void
|
||||
fuseki_callback(int move, struct fullboard_pattern *pattern, int ll)
|
||||
{
|
||||
TRACE("Fuseki database move at %1m with relative weight %d, pattern %s+%d\n",
|
||||
move, pattern->value, pattern->name, ll);
|
||||
|
||||
/* Store coordinates and relative weight for the found move. */
|
||||
fuseki_moves[num_fuseki_moves] = move;
|
||||
fuseki_value[num_fuseki_moves] = pattern->value;
|
||||
fuseki_total_value += pattern->value;
|
||||
num_fuseki_moves++;
|
||||
}
|
||||
|
||||
/* Full board matching in database for fuseki moves. Return 1 if any
|
||||
* pattern found.
|
||||
*/
|
||||
static int
|
||||
search_fuseki_database(int color)
|
||||
{
|
||||
struct fullboard_pattern *database;
|
||||
int q;
|
||||
int k;
|
||||
|
||||
/* Disable matching after a certain number of stones are placed on
|
||||
* the board.
|
||||
*/
|
||||
if (stones_on_board(BLACK | WHITE) > MAX_FUSEKI_DATABASE_STONES)
|
||||
return 0;
|
||||
|
||||
/* We only have databases for 9x9, 13x13 and 19x19. */
|
||||
if (board_size == 9)
|
||||
database = fuseki9;
|
||||
else if (board_size == 13)
|
||||
database = fuseki13;
|
||||
else if (board_size == 19)
|
||||
database = fuseki19;
|
||||
else
|
||||
return 0;
|
||||
|
||||
/* Do the matching. */
|
||||
num_fuseki_moves = 0;
|
||||
fuseki_total_value = 0;
|
||||
fullboard_matchpat(fuseki_callback, color, database);
|
||||
|
||||
/* No match. */
|
||||
if (num_fuseki_moves == 0)
|
||||
return 0;
|
||||
|
||||
/* Choose randomly with respect to relative weights for matched moves.
|
||||
*/
|
||||
q = gg_rand() % fuseki_total_value;
|
||||
for (k = 0; k < num_fuseki_moves; k++) {
|
||||
q -= fuseki_value[k];
|
||||
if (q < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
gg_assert(k < num_fuseki_moves);
|
||||
/* Give this move an arbitrary value of 75. The actual value doesn't
|
||||
* matter much since the intention is that we should play this move
|
||||
* whatever the rest of the analysis thinks.
|
||||
*/
|
||||
announce_move(fuseki_moves[k], 75, color);
|
||||
|
||||
/* Also make sure the other considered moves can be seen in the
|
||||
* traces and in the output file.
|
||||
*/
|
||||
for (k = 0; k < num_fuseki_moves; k++)
|
||||
set_minimum_move_value(fuseki_moves[k], 74);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Generate move in empty corner or in middle of small board.*/
|
||||
void
|
||||
fuseki(int color)
|
||||
{
|
||||
int i = -1;
|
||||
int j = -1;
|
||||
int width; /* Side of the open region required in the corner. */
|
||||
int empty_corner_value = EMPTY_CORNER_VALUE * board_size/19;
|
||||
|
||||
/* Return immediately if --disable_fuseki option used. */
|
||||
if (disable_fuseki)
|
||||
return;
|
||||
|
||||
set_symmetries();
|
||||
|
||||
/* Search in fuseki database unless disabled by --nofusekidb option. */
|
||||
if (fusekidb && search_fuseki_database(color))
|
||||
return;
|
||||
|
||||
/* On 9x9, only play open corners after the first move if nothing
|
||||
* else useful is found.
|
||||
*/
|
||||
if (board_size == 9 && stones_on_board(color) > 0)
|
||||
empty_corner_value = 5;
|
||||
|
||||
if (board_size <= 11) {
|
||||
/* For boards of size 11x11 or smaller we first go for the center point. */
|
||||
int middle = board_size/2;
|
||||
if (openregion(middle-2, middle+2, middle-2, middle+2)) {
|
||||
announce_move(POS(middle, middle), 45, color);
|
||||
}
|
||||
}
|
||||
|
||||
if (board_size < 9)
|
||||
return;
|
||||
|
||||
if (board_size >= 18)
|
||||
width = 8;
|
||||
else if (board_size == 9)
|
||||
width = 5;
|
||||
else
|
||||
width = board_size/2;
|
||||
|
||||
if (openregion(0, width-1, board_size-width, board_size-1)) {
|
||||
choose_corner_move(UPPER_RIGHT, &i, &j);
|
||||
announce_move(POS(i, j), empty_corner_value, color);
|
||||
}
|
||||
|
||||
if (openregion(board_size-width, board_size-1, 0, width-1)) {
|
||||
choose_corner_move(LOWER_LEFT, &i, &j);
|
||||
announce_move(POS(i, j), empty_corner_value, color);
|
||||
}
|
||||
if (openregion(board_size-width, board_size-1,
|
||||
board_size-width, board_size-1)) {
|
||||
choose_corner_move(LOWER_RIGHT, &i, &j);
|
||||
announce_move(POS(i, j), empty_corner_value, color);
|
||||
}
|
||||
|
||||
if (openregion(0, width-1, 0, width-1)) {
|
||||
choose_corner_move(UPPER_LEFT, &i, &j);
|
||||
announce_move(POS(i, j), empty_corner_value, color);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
||||
|
981
gnugo/engine/genmove.c
Normal file
981
gnugo/engine/genmove.c
Normal file
@ -0,0 +1,981 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "sgftree.h"
|
||||
#include "gg_utils.h"
|
||||
|
||||
/* Return one if x doesn't equal position_number and 0 otherwise.
|
||||
* After using this macro x will always have the value
|
||||
* position_number.
|
||||
*/
|
||||
#define NEEDS_UPDATE(x) (x != position_number ? (x = position_number, 1) : 0)
|
||||
|
||||
/* Mark a limited search area. If limit_search != 1, genmove
|
||||
* will only return moves within the area marked by the array
|
||||
* search_mask.
|
||||
*/
|
||||
static int limit_search = 0;
|
||||
static int search_mask[BOARDMAX];
|
||||
|
||||
static int do_genmove(int color, float pure_threat_value,
|
||||
int allowed_moves[BOARDMAX], float *value, int *resign);
|
||||
|
||||
/* Position numbers for which various examinations were last made. */
|
||||
static int worms_examined = -1;
|
||||
static int initial_influence_examined = -1;
|
||||
static int dragons_examined_without_owl = -1;
|
||||
static int dragons_examined = -1;
|
||||
static int initial_influence2_examined = -1;
|
||||
static int dragons_refinedly_examined = -1;
|
||||
|
||||
static int revise_semeai(int color);
|
||||
static int revise_thrashing_dragon(int color, float our_score,
|
||||
float advantage);
|
||||
|
||||
static void break_mirror_go(int color);
|
||||
static int find_mirror_move(int *move, int color);
|
||||
static int should_resign(int color, float optimistic_score, int move);
|
||||
static void compute_scores(int use_chinese_rules);
|
||||
|
||||
|
||||
/* Reset some things in the engine.
|
||||
*
|
||||
* This prepares the hash table for the reading code for use. It
|
||||
* should be called when we start examine a new position.
|
||||
*/
|
||||
|
||||
void
|
||||
reset_engine()
|
||||
{
|
||||
/* To improve the reproducability of games, we restart the random
|
||||
* number generator with the same seed for each move. Thus we don't
|
||||
* have to know how many previous moves have been played, nor
|
||||
* actually play through them, in order to get the right random
|
||||
* numbers.
|
||||
*/
|
||||
reuse_random_seed();
|
||||
|
||||
/* Initialize things for hashing of positions. */
|
||||
reading_cache_clear();
|
||||
|
||||
hashdata_recalc(&board_hash, board, board_ko_pos);
|
||||
|
||||
worms_examined = -1;
|
||||
initial_influence_examined = -1;
|
||||
dragons_examined_without_owl = -1;
|
||||
dragons_examined = -1;
|
||||
initial_influence2_examined = -1;
|
||||
dragons_refinedly_examined = -1;
|
||||
|
||||
/* Prepare our table of move reasons. */
|
||||
clear_move_reasons();
|
||||
clear_break_in_list();
|
||||
|
||||
/* Set up depth values (see comments there for details). */
|
||||
set_depth_values(get_level(), 0);
|
||||
|
||||
/* Initialize arrays of moves which are meaningless due to
|
||||
* static analysis of unconditional status.
|
||||
*/
|
||||
clear_unconditionally_meaningless_moves();
|
||||
}
|
||||
|
||||
/*
|
||||
* Examine the position and try to gather as much information as possible.
|
||||
* This is used mainly for move generation, but could also be called
|
||||
* for debugging purposes (decidestring, etc).
|
||||
*
|
||||
* The parameter how_much tells us how much of the work we have to do.
|
||||
* For move generation we have to do it all. For debugging we can
|
||||
* sometimes stop a little earlier.
|
||||
*
|
||||
* aftermath_play indicates we are in aftermath playout phase. It's only
|
||||
* effect is to always use chinese rules for the score estimates.
|
||||
*/
|
||||
|
||||
void
|
||||
examine_position(int how_much, int aftermath_play)
|
||||
{
|
||||
int save_verbose = verbose;
|
||||
|
||||
purge_persistent_caches();
|
||||
|
||||
/* Don't print reading traces during make_worms and make_dragons unless
|
||||
* the user really wants it (verbose == 3).
|
||||
*/
|
||||
if (verbose == 1 || verbose == 2)
|
||||
--verbose;
|
||||
|
||||
if (NEEDS_UPDATE(worms_examined)) {
|
||||
start_timer(0);
|
||||
make_worms();
|
||||
time_report(0, " make worms", NO_MOVE, 1.0);
|
||||
}
|
||||
|
||||
if (how_much == EXAMINE_WORMS) {
|
||||
verbose = save_verbose;
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stones_on_board(BLACK | WHITE) != 0) {
|
||||
if (NEEDS_UPDATE(initial_influence_examined))
|
||||
compute_worm_influence();
|
||||
if (how_much == EXAMINE_INITIAL_INFLUENCE) {
|
||||
verbose = save_verbose;
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (how_much == EXAMINE_DRAGONS_WITHOUT_OWL) {
|
||||
if (NEEDS_UPDATE(dragons_examined_without_owl))
|
||||
make_dragons(1);
|
||||
verbose = save_verbose;
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (NEEDS_UPDATE(dragons_examined)) {
|
||||
make_dragons(0);
|
||||
compute_scores(chinese_rules || aftermath_play);
|
||||
/* We have automatically done a partial dragon analysis as well. */
|
||||
dragons_examined_without_owl = position_number;
|
||||
}
|
||||
if (how_much == EXAMINE_DRAGONS) {
|
||||
verbose = save_verbose;
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (how_much == EXAMINE_INITIAL_INFLUENCE
|
||||
|| how_much == EXAMINE_DRAGONS
|
||||
|| how_much == EXAMINE_ALL) {
|
||||
initialize_dragon_data();
|
||||
compute_scores(chinese_rules || aftermath_play);
|
||||
verbose = save_verbose;
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
verbose = save_verbose;
|
||||
|
||||
if (NEEDS_UPDATE(initial_influence2_examined)) {
|
||||
compute_dragon_influence();
|
||||
}
|
||||
if (how_much == EXAMINE_INITIAL_INFLUENCE2) {
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (NEEDS_UPDATE(dragons_refinedly_examined)) {
|
||||
compute_refined_dragon_weaknesses();
|
||||
compute_strategic_sizes();
|
||||
}
|
||||
if (how_much == FULL_EXAMINE_DRAGONS) {
|
||||
gg_assert(test_gray_border() < 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (printworms)
|
||||
show_dragons();
|
||||
}
|
||||
|
||||
|
||||
/* The same as examine_position(), except that all traces, debug
|
||||
* output, and sgf traces are turned off.
|
||||
*/
|
||||
void
|
||||
silent_examine_position(int how_much)
|
||||
{
|
||||
int save_verbose = verbose;
|
||||
SGFTree *save_sgf_dumptree = sgf_dumptree;
|
||||
int save_count_variations = count_variations;
|
||||
int save_debug = debug;
|
||||
int save_printmoyo = printmoyo;
|
||||
|
||||
verbose = 0;
|
||||
sgf_dumptree = NULL;
|
||||
count_variations = 0;
|
||||
debug = 0;
|
||||
printmoyo = 0;
|
||||
|
||||
examine_position(how_much, 0);
|
||||
|
||||
verbose = save_verbose;
|
||||
sgf_dumptree = save_sgf_dumptree;
|
||||
count_variations = save_count_variations;
|
||||
debug = save_debug;
|
||||
printmoyo = save_printmoyo;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generate computer move for color.
|
||||
*
|
||||
* Return the generated move.
|
||||
*/
|
||||
|
||||
int
|
||||
genmove(int color, float *value, int *resign)
|
||||
{
|
||||
int move = PASS_MOVE;
|
||||
if (resign)
|
||||
*resign = 0;
|
||||
|
||||
#if ORACLE
|
||||
if (metamachine) {
|
||||
move = metamachine_genmove(color, value, limit_search);
|
||||
gg_assert(stackp == 0);
|
||||
if (move != PASS_MOVE)
|
||||
return move;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (limit_search)
|
||||
move = do_genmove(color, 0.4, search_mask, value, resign);
|
||||
else
|
||||
move = do_genmove(color, 0.4, NULL, value, resign);
|
||||
gg_assert(move == PASS_MOVE || ON_BOARD(move));
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Same as above but doesn't generate pure threat moves. Useful when
|
||||
* trying to score a game.
|
||||
*/
|
||||
|
||||
int
|
||||
genmove_conservative(int color, float *value)
|
||||
{
|
||||
return do_genmove(color, 0.0, NULL, value, NULL);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
genmove_restricted(int color, int allowed_moves[BOARDMAX])
|
||||
{
|
||||
return do_genmove(color, 0.0, allowed_moves, NULL, NULL);
|
||||
}
|
||||
|
||||
/* This function collects move reasons can be generated immediately from
|
||||
* the data gathered in the examine_position() phase.
|
||||
*/
|
||||
void
|
||||
collect_move_reasons(int color)
|
||||
{
|
||||
worm_reasons(color);
|
||||
semeai_move_reasons(color);
|
||||
owl_reasons(color);
|
||||
cut_reasons(color);
|
||||
break_in_move_reasons(color);
|
||||
unconditional_move_reasons(color);
|
||||
}
|
||||
|
||||
/* Call Monte Carlo module to generate a move. */
|
||||
static int
|
||||
monte_carlo_genmove(int color, int allowed_moves[BOARDMAX],
|
||||
float *value, int *resign)
|
||||
{
|
||||
int pos;
|
||||
int best_move = PASS_MOVE;
|
||||
int best_uct_move = PASS_MOVE;
|
||||
int unconditional_territory_black[BOARDMAX];
|
||||
int unconditional_territory_white[BOARDMAX];
|
||||
int forbidden_move[BOARDMAX];
|
||||
float move_values[BOARDMAX];
|
||||
int move_frequencies[BOARDMAX];
|
||||
float best_value;
|
||||
int frequency_cutoff;
|
||||
int frequency_cutoff2;
|
||||
int number_of_simulations;
|
||||
|
||||
memset(move_values, 0, sizeof(move_values));
|
||||
memset(move_frequencies, 0, sizeof(move_frequencies));
|
||||
|
||||
if (0) {
|
||||
simple_showboard(stderr);
|
||||
gprintf("\n");
|
||||
}
|
||||
|
||||
if (resign)
|
||||
*resign = 0;
|
||||
|
||||
unconditional_life(unconditional_territory_black, BLACK);
|
||||
unconditional_life(unconditional_territory_white, WHITE);
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (!ON_BOARD(pos))
|
||||
continue;
|
||||
else if (unconditional_territory_black[pos])
|
||||
forbidden_move[pos] = BLACK;
|
||||
else if (unconditional_territory_white[pos])
|
||||
forbidden_move[pos] = WHITE;
|
||||
else
|
||||
forbidden_move[pos] = 0;
|
||||
|
||||
number_of_simulations = mc_games_per_level * gg_max(get_level(), 1);
|
||||
|
||||
uct_genmove(color, &best_uct_move, forbidden_move, allowed_moves,
|
||||
number_of_simulations, move_values, move_frequencies);
|
||||
|
||||
best_move = best_uct_move;
|
||||
best_value = 0.0;
|
||||
frequency_cutoff = move_frequencies[best_uct_move] / 2;
|
||||
frequency_cutoff2 = move_frequencies[best_uct_move] / 10;
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (ON_BOARD(pos)
|
||||
&& (move_frequencies[pos] > frequency_cutoff
|
||||
|| (move_values[pos] > 0.9
|
||||
&& move_frequencies[pos] > frequency_cutoff2)
|
||||
|| move_values[best_uct_move] < 0.1)
|
||||
&& (!allowed_moves || allowed_moves[pos])
|
||||
&& potential_moves[pos] > best_value) {
|
||||
best_move = pos;
|
||||
best_value = potential_moves[pos];
|
||||
}
|
||||
}
|
||||
|
||||
unconditionally_meaningless_move(best_move, color, &best_move);
|
||||
|
||||
*value = 1.0;
|
||||
|
||||
return best_move;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Perform the actual move generation.
|
||||
*
|
||||
* The array allowed_moves restricts which moves may be considered. If
|
||||
* NULL any move is allowed. Pass is always allowed and will be chosen
|
||||
* if the move generation doesn't like any of the allowed moves (or
|
||||
* overlooks them).
|
||||
*/
|
||||
|
||||
static int
|
||||
do_genmove(int color, float pure_threat_value,
|
||||
int allowed_moves[BOARDMAX], float *value, int *resign)
|
||||
{
|
||||
float average_score, pessimistic_score, optimistic_score;
|
||||
int save_verbose;
|
||||
int save_depth;
|
||||
int move;
|
||||
float dummy_value;
|
||||
int use_thrashing_dragon_heuristics = 0;
|
||||
|
||||
if (!value)
|
||||
value = &dummy_value;
|
||||
|
||||
start_timer(0);
|
||||
clearstats();
|
||||
|
||||
/* Usually we would not recommend resignation. */
|
||||
if (resign)
|
||||
*resign = 0;
|
||||
|
||||
/* Prepare our table of moves considered. */
|
||||
memset(potential_moves, 0, sizeof(potential_moves));
|
||||
|
||||
/* no move is found yet. */
|
||||
move = PASS_MOVE;
|
||||
*value = 0.0;
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
/* Store the depth value so we can check that it hasn't changed when
|
||||
* we leave this function.
|
||||
*/
|
||||
save_depth = depth;
|
||||
|
||||
/* If in mirror mode, try to find a mirror move. */
|
||||
if (play_mirror_go
|
||||
&& (mirror_stones_limit < 0
|
||||
|| stones_on_board(WHITE | BLACK) <= mirror_stones_limit)
|
||||
&& find_mirror_move(&move, color)) {
|
||||
TRACE("genmove() recommends mirror move at %1m\n", move);
|
||||
*value = 1.0;
|
||||
return move;
|
||||
}
|
||||
|
||||
/* Find out information about the worms and dragons. */
|
||||
start_timer(1);
|
||||
examine_position(EXAMINE_ALL, 0);
|
||||
time_report(1, "examine position", NO_MOVE, 1.0);
|
||||
|
||||
|
||||
/* The score will be used to determine when we are safely
|
||||
* ahead. So we want the most conservative score.
|
||||
*
|
||||
* We always want to have the score from our point of view. So
|
||||
* negate it if we are black.
|
||||
*/
|
||||
if (color == WHITE) {
|
||||
pessimistic_score = black_score;
|
||||
optimistic_score = white_score;
|
||||
}
|
||||
else {
|
||||
pessimistic_score = -white_score;
|
||||
optimistic_score = -black_score;
|
||||
}
|
||||
|
||||
if (color == WHITE)
|
||||
average_score = (white_score + black_score)/2.0;
|
||||
else
|
||||
average_score = -(white_score + black_score)/2.0;
|
||||
choose_strategy(color, average_score, game_status(color));
|
||||
|
||||
if (printboard) {
|
||||
if (printboard == 1)
|
||||
fprintf(stderr, "\n dragon_status display:\n\n");
|
||||
if (printboard == 2)
|
||||
fprintf(stderr, "\n eye display:\n\n");
|
||||
showboard(printboard);
|
||||
if (printboard == 1) {
|
||||
fprintf(stderr, "\n owl_status display:\n\n");
|
||||
showboard(3);
|
||||
fprintf(stderr, "\n matcher_status display:\n\n");
|
||||
showboard(4);
|
||||
}
|
||||
}
|
||||
|
||||
gg_assert(stackp == 0);
|
||||
|
||||
/*
|
||||
* Ok, information gathering is complete. Now start to find some moves!
|
||||
*/
|
||||
|
||||
|
||||
/* Pick up moves that we know of already. */
|
||||
save_verbose = verbose;
|
||||
if (verbose > 0)
|
||||
verbose--;
|
||||
collect_move_reasons(color);
|
||||
verbose = save_verbose;
|
||||
time_report(1, "generate move reasons", NO_MOVE, 1.0);
|
||||
|
||||
/* Try to find empty corner moves. */
|
||||
fuseki(color);
|
||||
gg_assert(stackp == 0);
|
||||
|
||||
/* Look for moves to break mirror play by the opponent. */
|
||||
break_mirror_go(color);
|
||||
|
||||
/* If we are ahead by 5 points or more, consider a thrashing
|
||||
* dragon dangerous and change its status from DEAD to
|
||||
* UNKNOWN. Otherwise, pretend there is no thrashing dragon.
|
||||
*/
|
||||
if (!doing_scoring)
|
||||
use_thrashing_dragon_heuristics
|
||||
= revise_thrashing_dragon(color, pessimistic_score, 5.0);
|
||||
|
||||
/* The general pattern database. */
|
||||
shapes(color);
|
||||
time_report(1, "shapes", NO_MOVE, 1.0);
|
||||
gg_assert(stackp == 0);
|
||||
|
||||
/* Look for combination attacks and defenses against them. */
|
||||
combinations(color);
|
||||
time_report(1, "combinations", NO_MOVE, 1.0);
|
||||
gg_assert(stackp == 0);
|
||||
|
||||
/* Review the move reasons and estimate move values. */
|
||||
if (review_move_reasons(&move, value, color,
|
||||
pure_threat_value, pessimistic_score, allowed_moves,
|
||||
use_thrashing_dragon_heuristics))
|
||||
TRACE("Move generation likes %1m with value %f\n", move, *value);
|
||||
gg_assert(stackp == 0);
|
||||
time_report(1, "review move reasons", NO_MOVE, 1.0);
|
||||
|
||||
|
||||
/* If the move value is 6 or lower, we look for endgame patterns too. */
|
||||
if (*value <= 6.0 && !disable_endgame_patterns) {
|
||||
endgame_shapes(color);
|
||||
endgame(color);
|
||||
gg_assert(stackp == 0);
|
||||
if (review_move_reasons(&move, value, color, pure_threat_value,
|
||||
pessimistic_score, allowed_moves,
|
||||
use_thrashing_dragon_heuristics))
|
||||
TRACE("Move generation likes %1m with value %f\n", move, *value);
|
||||
gg_assert(stackp == 0);
|
||||
time_report(1, "endgame", NO_MOVE, 1.0);
|
||||
}
|
||||
|
||||
/* If no move found yet, revisit any semeai and change the
|
||||
* status of the opponent group from DEAD to UNKNOWN, then
|
||||
* run shapes and endgame_shapes again. This may turn up a move.
|
||||
*/
|
||||
if (move == PASS_MOVE) {
|
||||
if (revise_semeai(color)) {
|
||||
shapes(color);
|
||||
endgame_shapes(color);
|
||||
if (review_move_reasons(&move, value, color, pure_threat_value,
|
||||
pessimistic_score, allowed_moves,
|
||||
use_thrashing_dragon_heuristics)) {
|
||||
TRACE("Upon reconsideration move generation likes %1m with value %f\n",
|
||||
move, *value);
|
||||
}
|
||||
}
|
||||
time_report(1, "move reasons with revised semeai status",
|
||||
NO_MOVE, 1.0);
|
||||
}
|
||||
|
||||
/* If Monte Carlo move generation is enabled, call it now. Do not
|
||||
* override a fuseki move.
|
||||
*
|
||||
* FIXME: Identifying fuseki moves by checking the move value is
|
||||
* very ugly and fragile.
|
||||
*/
|
||||
if (use_monte_carlo_genmove && move != PASS_MOVE
|
||||
&& (*value < 75.0 || *value > 75.01) && !doing_scoring) {
|
||||
int allowed_moves2[BOARDMAX];
|
||||
int num_allowed_moves2 = 0;
|
||||
int pos;
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos)
|
||||
&& (!allowed_moves || allowed_moves[pos])
|
||||
&& is_allowed_move(pos, color)) {
|
||||
allowed_moves2[pos] = 1;
|
||||
num_allowed_moves2++;
|
||||
}
|
||||
else
|
||||
allowed_moves2[pos] = 0;
|
||||
|
||||
if (num_allowed_moves2 > 1)
|
||||
move = monte_carlo_genmove(color, allowed_moves2, value, resign);
|
||||
}
|
||||
|
||||
/* If still no move, fill a remaining liberty. This should pick up
|
||||
* all missing dame points.
|
||||
*/
|
||||
if (move == PASS_MOVE
|
||||
&& fill_liberty(&move, color)) {
|
||||
if (!allowed_moves || allowed_moves[move]) {
|
||||
*value = 1.0;
|
||||
TRACE("Filling a liberty at %1m\n", move);
|
||||
record_top_move(move, *value);
|
||||
move_considered(move, *value);
|
||||
time_report(1, "fill liberty", NO_MOVE, 1.0);
|
||||
}
|
||||
else
|
||||
move = PASS_MOVE;
|
||||
}
|
||||
|
||||
/* If we're instructed to play out the aftermath or capture all dead
|
||||
* opponent stones, or if the opponent is trying to live inside
|
||||
* our territory and we are clearly ahead, generate an aftermath move.
|
||||
*/
|
||||
if (move == PASS_MOVE) {
|
||||
if (play_out_aftermath
|
||||
|| capture_all_dead
|
||||
|| (!doing_scoring && thrashing_dragon && pessimistic_score > 15.0))
|
||||
move = aftermath_genmove(color, capture_all_dead, allowed_moves);
|
||||
|
||||
if (move != PASS_MOVE) {
|
||||
ASSERT1(is_legal(move, color), move);
|
||||
*value = 1.0;
|
||||
TRACE("Aftermath move at %1m\n", move);
|
||||
record_top_move(move, *value);
|
||||
move_considered(move, *value);
|
||||
time_report(1, "aftermath_genmove", NO_MOVE, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we somehow have managed to generate an illegal move, pass instead. */
|
||||
if (!is_allowed_move(move, color)) {
|
||||
TRACE("ILLEGAL MOVE GENERATED. Passing instead.\n");
|
||||
move = PASS_MOVE;
|
||||
*value = -1.0;
|
||||
}
|
||||
|
||||
/* If no move is found then pass. */
|
||||
if (move == PASS_MOVE) {
|
||||
TRACE("I pass.\n");
|
||||
}
|
||||
else {
|
||||
TRACE("genmove() recommends %1m with value %f\n", move, *value);
|
||||
}
|
||||
|
||||
/* Maybe time to resign...
|
||||
*/
|
||||
if (resign && resign_allowed
|
||||
&& *value < 10.0 && should_resign(color, optimistic_score, move)) {
|
||||
TRACE("... though, genmove() thinks the position is hopeless\n");
|
||||
*resign = 1;
|
||||
}
|
||||
|
||||
/* If statistics is turned on, this is the place to show it. */
|
||||
if (showstatistics)
|
||||
showstats();
|
||||
|
||||
if (showtime) {
|
||||
double spent = time_report(0, "TIME to generate move at ", move, 1.0);
|
||||
total_time += spent;
|
||||
if (spent > slowest_time) {
|
||||
slowest_time = spent;
|
||||
slowest_move = move;
|
||||
slowest_movenum = movenum + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Some consistency checks to verify that things are properly
|
||||
* restored and/or have not been corrupted.
|
||||
*/
|
||||
gg_assert(stackp == 0);
|
||||
gg_assert(test_gray_border() < 0);
|
||||
gg_assert(depth == save_depth);
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* This is called for each move which has been considered. For
|
||||
* debugging purposes, we keep a table of all the moves we
|
||||
* have considered.
|
||||
*/
|
||||
|
||||
void
|
||||
move_considered(int move, float value)
|
||||
{
|
||||
if (value > potential_moves[move])
|
||||
potential_moves[move] = value;
|
||||
}
|
||||
|
||||
|
||||
/* revise_semeai(color) changes the status of any DEAD dragon of
|
||||
* OPPOSITE_COLOR(color) which occurs in a semeai to UNKNOWN.
|
||||
* It returns true if such a dragon is found.
|
||||
*/
|
||||
|
||||
static int
|
||||
revise_semeai(int color)
|
||||
{
|
||||
int pos;
|
||||
int found_one = 0;
|
||||
int other = OTHER_COLOR(color);
|
||||
|
||||
if (stones_on_board(BLACK | WHITE) == 0)
|
||||
return 0;
|
||||
|
||||
if (doing_scoring)
|
||||
return 0;
|
||||
|
||||
gg_assert(dragon2 != NULL);
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (ON_BOARD(pos)
|
||||
&& dragon[pos].color == other
|
||||
&& DRAGON2(pos).semeais
|
||||
&& dragon[pos].status == DEAD) {
|
||||
found_one = 1;
|
||||
dragon[pos].status = UNKNOWN;
|
||||
if (dragon[pos].origin == pos)
|
||||
TRACE("revise_semeai: changed status of dragon %1m from DEAD to UNKNOWN\n",
|
||||
pos);
|
||||
}
|
||||
}
|
||||
|
||||
return found_one;
|
||||
}
|
||||
|
||||
|
||||
/* If the opponent's last move added a stone to a dead dragon,
|
||||
* revise it's status to UNKNOWN. This will cause genmove to
|
||||
* generate moves restraining the dragon. We only do this if
|
||||
* we are ahead by 'advantage', and no owl threat has been found.
|
||||
*/
|
||||
|
||||
static int
|
||||
revise_thrashing_dragon(int color, float our_score, float advantage)
|
||||
{
|
||||
int pos;
|
||||
signed char safe_stones[BOARDMAX];
|
||||
float strength[BOARDMAX];
|
||||
|
||||
/* Trust the owl code's opinion if we are behind. */
|
||||
if (our_score < advantage)
|
||||
return 0;
|
||||
|
||||
if (disable_threat_computation
|
||||
|| !thrashing_dragon
|
||||
|| dragon[thrashing_dragon].status != DEAD)
|
||||
return 0;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos) && thrashing_stone[pos]
|
||||
&& worm[pos].unconditional_status != DEAD) {
|
||||
dragon[pos].status = UNKNOWN;
|
||||
DRAGON2(pos).safety = ALIVE;
|
||||
}
|
||||
|
||||
set_strength_data(OTHER_COLOR(color), safe_stones, strength);
|
||||
compute_influence(OTHER_COLOR(color), safe_stones, strength,
|
||||
OPPOSITE_INFLUENCE(color),
|
||||
NO_MOVE, "revised thrashing dragon");
|
||||
compute_refined_dragon_weaknesses();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Look for a mirror move. First try the mirror of the last move. If
|
||||
* none is available, test all moves to see if one makes the board
|
||||
* symmetric.
|
||||
*
|
||||
* To be able to deal with handicap stones we use a somewhat weak
|
||||
* definition of symmetry.
|
||||
*/
|
||||
|
||||
static int
|
||||
find_mirror_move(int *move, int color)
|
||||
{
|
||||
int last_move = get_last_move();
|
||||
int mirror_move;
|
||||
if (last_move != NO_MOVE) {
|
||||
mirror_move = MIRROR_MOVE(last_move);
|
||||
if (test_symmetry_after_move(mirror_move, color, 0)) {
|
||||
*move = mirror_move;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (mirror_move = BOARDMIN; mirror_move < BOARDMAX; mirror_move++) {
|
||||
if (ON_BOARD(mirror_move)
|
||||
&& test_symmetry_after_move(mirror_move, color, 0)) {
|
||||
*move = mirror_move;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Computer two territory estimates: for *upper, the status of all
|
||||
* cricital stones gets resolved in White's favor; vice verso for
|
||||
* black.
|
||||
*/
|
||||
static void
|
||||
compute_scores(int use_chinese_rules)
|
||||
{
|
||||
signed char safe_stones[BOARDMAX];
|
||||
float strength[BOARDMAX];
|
||||
|
||||
set_strength_data(WHITE, safe_stones, strength);
|
||||
compute_influence(EMPTY, safe_stones, strength, &move_influence,
|
||||
NO_MOVE, "White territory estimate");
|
||||
white_score = influence_score(&move_influence, use_chinese_rules);
|
||||
set_strength_data(BLACK, safe_stones, strength);
|
||||
compute_influence(EMPTY, safe_stones, strength, &move_influence,
|
||||
NO_MOVE, "White territory estimate");
|
||||
black_score = influence_score(&move_influence, use_chinese_rules);
|
||||
|
||||
if (verbose || showscore) {
|
||||
if (white_score == black_score)
|
||||
gprintf("Score estimate: %s %f\n",
|
||||
black_score > 0 ? "W " : "B ", gg_abs(black_score));
|
||||
else
|
||||
gprintf("Score estimate: %s %f to %s %f\n",
|
||||
black_score > 0 ? "W " : "B ", gg_abs(black_score),
|
||||
white_score > 0 ? "W " : "B ", gg_abs(white_score));
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Detect if a white opponent has played mirror go for at least 10
|
||||
* moves and if so play on tengen.
|
||||
*
|
||||
* Mirror breaking moves in other situations are handled by patterns
|
||||
* in patterns.db.
|
||||
*/
|
||||
static void
|
||||
break_mirror_go(int color)
|
||||
{
|
||||
int tengen = POS((board_size - 1) / 2, (board_size - 1) / 2);
|
||||
if (board[tengen] == EMPTY
|
||||
&& color == BLACK
|
||||
&& stones_on_board(BLACK | WHITE) > 10
|
||||
&& test_symmetry_after_move(tengen, color, 1)) {
|
||||
set_minimum_move_value(tengen, 30.0);
|
||||
TRACE("Play %1m to break mirror go, value 30.\n", tengen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Helper to decide whether GG should resign a game
|
||||
*/
|
||||
static int
|
||||
should_resign(int color, float optimistic_score, int move)
|
||||
{
|
||||
float status;
|
||||
int d;
|
||||
/* We resign 19x19 games only, smaller board games are fast enough.
|
||||
* We resign only if the margin is bigger than 45 pts and if we are
|
||||
* behind (of course).
|
||||
*
|
||||
* As an exception to this rule, we resign on any board size if
|
||||
* it looks like all our dragons are dead and the generated move
|
||||
* is a pass.
|
||||
*/
|
||||
if (board_size > 2 && move == PASS_MOVE && !lively_dragon_exists(color))
|
||||
return 1;
|
||||
|
||||
if (move == PASS_MOVE
|
||||
|| board_size < 19
|
||||
|| optimistic_score > -45.0)
|
||||
return 0;
|
||||
/* Check dragon statuses. If a friendly dragon is critical, we are
|
||||
* possibly not that much behind after we save it. If some hostile
|
||||
* dragon is at least weak, we possibly have a chance to come back
|
||||
* if we can kill it.
|
||||
*/
|
||||
for (d = 0; d < number_of_dragons; d++) {
|
||||
/* any friendly critical dragon ? */
|
||||
if (board[dragon2[d].origin] == color
|
||||
&& DRAGON(d).status == CRITICAL)
|
||||
return 0;
|
||||
/* any weak opponent dragon ? */
|
||||
if (board[dragon2[d].origin] == OTHER_COLOR(color)
|
||||
&& DRAGON(d).status != DEAD
|
||||
&& DRAGON(d).effective_size >= 10
|
||||
&& dragon_weak(dragon2[d].origin))
|
||||
return 0;
|
||||
}
|
||||
/* Is it already too late to try something ? */
|
||||
status = game_status(color);
|
||||
if (status < 0.8)
|
||||
/* Still "too early".
|
||||
* Note: the 0.8 constant is very conservative, we actually could give
|
||||
* up a bit earlier.
|
||||
*/
|
||||
return 0;
|
||||
|
||||
/* well, it is probably reasonable and polite to give up this game */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************\
|
||||
* Mark a limited search area *
|
||||
\*********************************************************************/
|
||||
|
||||
/* Activate or deactivate search limit. */
|
||||
void
|
||||
set_limit_search(int value)
|
||||
{
|
||||
limit_search = value;
|
||||
}
|
||||
|
||||
/* The following function marks a diamond of radius 6 with center pos. */
|
||||
|
||||
void
|
||||
set_search_diamond(int pos)
|
||||
{
|
||||
int i = I(pos);
|
||||
int j = J(pos);
|
||||
int m, n;
|
||||
|
||||
for (m = 0; m < board_size; m++)
|
||||
for (n = 0; n < board_size; n++) {
|
||||
if (gg_abs(m - i) + gg_abs(n - j) <= 6)
|
||||
search_mask[POS(m, n)] = 1;
|
||||
else
|
||||
search_mask[POS(m, n)] = 0;
|
||||
}
|
||||
limit_search = pos;
|
||||
if (0)
|
||||
draw_search_area();
|
||||
}
|
||||
|
||||
/* unmarks the entire board */
|
||||
|
||||
void
|
||||
reset_search_mask()
|
||||
{
|
||||
memset(search_mask, 0, sizeof(search_mask));
|
||||
}
|
||||
|
||||
/* marks a single vertex */
|
||||
|
||||
void
|
||||
set_search_mask(int pos, int value)
|
||||
{
|
||||
search_mask[pos] = value;
|
||||
}
|
||||
|
||||
/* displays the search area */
|
||||
|
||||
void
|
||||
draw_search_area(void)
|
||||
{
|
||||
int m, n;
|
||||
|
||||
start_draw_board();
|
||||
for (m = 0; m < board_size; m++)
|
||||
for (n = 0; n < board_size; n++) {
|
||||
int col, c;
|
||||
|
||||
if (search_mask[POS(m, n)])
|
||||
col = GG_COLOR_RED;
|
||||
else
|
||||
col = GG_COLOR_BLACK;
|
||||
|
||||
if (board[POS(m, n)] == BLACK)
|
||||
c = 'X';
|
||||
else if (board[POS(m, n)] == WHITE)
|
||||
c = 'O';
|
||||
else if (search_mask[POS(m, n)])
|
||||
c = '*';
|
||||
else
|
||||
c = '.';
|
||||
draw_color_char(m, n, c, col);
|
||||
}
|
||||
end_draw_board();
|
||||
}
|
||||
|
||||
/* returns true if the position is within the search area */
|
||||
int
|
||||
within_search_area(int pos)
|
||||
{
|
||||
if (!limit_search)
|
||||
return 1;
|
||||
return search_mask[pos];
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
180
gnugo/engine/globals.c
Normal file
180
gnugo/engine/globals.c
Normal file
@ -0,0 +1,180 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sgftree.h"
|
||||
#include "liberty.h"
|
||||
#include "config.h"
|
||||
|
||||
/*
|
||||
* Define all global variables used within the engine.
|
||||
*/
|
||||
|
||||
int thrashing_dragon = NO_MOVE; /* Dead opponent's dragon trying to live. */
|
||||
signed char thrashing_stone[BOARDMAX]; /* All thrashing stones. */
|
||||
|
||||
float potential_moves[BOARDMAX];
|
||||
|
||||
/* Used by reading. */
|
||||
int depth; /* deep reading cut off */
|
||||
int backfill_depth; /* deep reading cut off */
|
||||
int backfill2_depth; /* deep reading cut off */
|
||||
int break_chain_depth; /* deep reading cut off */
|
||||
int superstring_depth; /* deep reading cut off */
|
||||
int fourlib_depth; /* deep reading cut off */
|
||||
int ko_depth; /* deep reading cut off */
|
||||
int branch_depth; /* deep reading cut off */
|
||||
int aa_depth;
|
||||
int depth_offset; /* keeps track of temporary depth changes */
|
||||
int owl_distrust_depth; /* below this owl trusts the optics code */
|
||||
int owl_branch_depth; /* below this owl tries only one variation */
|
||||
int owl_reading_depth; /* owl does not read below this depth */
|
||||
int owl_node_limit; /* maximum number of nodes considered */
|
||||
int semeai_branch_depth;
|
||||
int semeai_branch_depth2;
|
||||
int semeai_node_limit;
|
||||
int connect_depth; /* Used by Tristan Cazenave's connection reader. */
|
||||
int connect_depth2; /* Used by alternater connection reader. */
|
||||
int connection_node_limit;
|
||||
int breakin_node_limit; /* Reading limits for break_in/block_off reading */
|
||||
int breakin_depth;
|
||||
/* Mandated values for deep reading cutoffs. */
|
||||
int mandated_depth = -1;
|
||||
int mandated_backfill_depth = -1;
|
||||
int mandated_backfill2_depth = -1;
|
||||
int mandated_break_chain_depth = -1;
|
||||
int mandated_superstring_depth = -1;
|
||||
int mandated_fourlib_depth = -1;
|
||||
int mandated_ko_depth = -1;
|
||||
int mandated_branch_depth = -1;
|
||||
int mandated_aa_depth = -1;
|
||||
int mandated_owl_distrust_depth = -1;
|
||||
int mandated_owl_branch_depth = -1;
|
||||
int mandated_owl_reading_depth = -1;
|
||||
int mandated_owl_node_limit = -1;
|
||||
int mandated_semeai_node_limit = -1;
|
||||
|
||||
|
||||
/* Miscellaneous. */
|
||||
int quiet = 0; /* minimal output */
|
||||
int showstatistics = 0; /* print statistics */
|
||||
int profile_patterns = 0; /* print statistics of pattern usage */
|
||||
int allpats = 0; /* generate all patterns, even small ones */
|
||||
int printworms = 0; /* print full data on each string */
|
||||
int printmoyo = 0; /* print moyo board each move */
|
||||
int printboard = 0; /* print board each move */
|
||||
int fusekidb = 1; /* use fuseki database */
|
||||
int disable_fuseki = 0; /* do not generate fuseki moves */
|
||||
int josekidb = 1; /* use joseki database */
|
||||
int showtime = 0; /* print time to find move */
|
||||
int showscore = 0; /* print estimated score */
|
||||
int debug = 0; /* controls debug output */
|
||||
int verbose = 0; /* trace level */
|
||||
char outfilename[128] = ""; /* output file (-o option) */
|
||||
int output_flags = OUTPUT_DEFAULT; /* amount of output to outfile */
|
||||
int metamachine = 0; /* use metamachine_genmove */
|
||||
int oracle_exists = 0; /* oracle is available for consultation */
|
||||
int autolevel_on = 0; /* Adjust level in GMP or ASCII mode. */
|
||||
|
||||
int disable_threat_computation = 0;
|
||||
int disable_endgame_patterns = 0;
|
||||
int doing_scoring = 0;
|
||||
|
||||
int chinese_rules = CHINESE_RULES; /* ruleset choice for GMP connection */
|
||||
/* use experimental connection module */
|
||||
int experimental_connections = EXPERIMENTAL_CONNECTIONS;
|
||||
/* use alternate connection reading algorithm */
|
||||
int alternate_connections = ALTERNATE_CONNECTIONS;
|
||||
/* compute owl threats */
|
||||
int owl_threats = OWL_THREATS;
|
||||
/* use experimental owl extension (GAIN/LOSS) */
|
||||
int experimental_owl_ext = EXPERIMENTAL_OWL_EXT;
|
||||
/* use experimental territory break-in module */
|
||||
int experimental_break_in = USE_BREAK_IN;
|
||||
/* use central oriented influence */
|
||||
int cosmic_gnugo = COSMIC_GNUGO;
|
||||
/* search for large scale owl moves */
|
||||
int large_scale = LARGE_SCALE;
|
||||
|
||||
int capture_all_dead = 0; /* capture all dead opponent stones */
|
||||
int play_out_aftermath = 0; /* make everything unconditionally settled */
|
||||
int resign_allowed = RESIGNATION_ALLOWED; /* resign hopeless games */
|
||||
|
||||
int play_mirror_go = 0; /* try to play mirror go if possible */
|
||||
int mirror_stones_limit = -1; /* but stop at this number of stones */
|
||||
|
||||
int gtp_version = 2; /* Use GTP version 2 by default. */
|
||||
int use_monte_carlo_genmove = 0; /* Default is not to use Monte Carlo move
|
||||
* generation.
|
||||
*/
|
||||
int mc_games_per_level = 8000; /* By default, use 8000 times the current
|
||||
* level number of simulations
|
||||
* for each mmove when Monte Carlo
|
||||
* move generation is enabled.
|
||||
*/
|
||||
|
||||
float best_move_values[10];
|
||||
int best_moves[10];
|
||||
float white_score;
|
||||
float black_score;
|
||||
|
||||
int close_worms[BOARDMAX][4];
|
||||
int number_close_worms[BOARDMAX];
|
||||
int close_black_worms[BOARDMAX][4];
|
||||
int number_close_black_worms[BOARDMAX];
|
||||
int close_white_worms[BOARDMAX][4];
|
||||
int number_close_white_worms[BOARDMAX];
|
||||
|
||||
int false_eye_territory[BOARDMAX];
|
||||
int forced_backfilling_moves[BOARDMAX];
|
||||
|
||||
struct worm_data worm[BOARDMAX];
|
||||
struct dragon_data dragon[BOARDMAX];
|
||||
int number_of_dragons;
|
||||
struct dragon_data2 *dragon2 = NULL;
|
||||
struct half_eye_data half_eye[BOARDMAX];
|
||||
struct eye_data black_eye[BOARDMAX];
|
||||
struct eye_data white_eye[BOARDMAX];
|
||||
struct vital_eye_points black_vital_points[BOARDMAX];
|
||||
struct vital_eye_points white_vital_points[BOARDMAX];
|
||||
struct surround_data surroundings[MAX_SURROUND];
|
||||
int surround_pointer;
|
||||
|
||||
int cutting_points[BOARDMAX];
|
||||
|
||||
double slowest_time = 0.0;
|
||||
int slowest_move = NO_MOVE;
|
||||
int slowest_movenum = 0;
|
||||
double total_time = 0.0;
|
||||
|
||||
|
||||
float minimum_value_weight = 1.0;
|
||||
float maximum_value_weight = 1.0;
|
||||
float invasion_malus_weight = 1.0;
|
||||
float territorial_weight = 1.0;
|
||||
float strategical_weight = 1.0;
|
||||
float attack_dragon_weight = 1.0;
|
||||
float followup_weight = 1.0;
|
388
gnugo/engine/gnugo.h
Normal file
388
gnugo/engine/gnugo.h
Normal file
@ -0,0 +1,388 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* ---------------------------------------------------------------- *
|
||||
* gnugo.h
|
||||
* This file contains the public interface to the GNU Go engine.
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
|
||||
#ifndef _GNUGO_H_
|
||||
#define _GNUGO_H_
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CRTDBG_H
|
||||
#include <crtdbg.h>
|
||||
#endif
|
||||
|
||||
#include "sgftree.h"
|
||||
#include "clock.h"
|
||||
#include "winsocket.h"
|
||||
|
||||
/* interface.c */
|
||||
/* Initialize the whole thing. Should be called once. */
|
||||
void init_gnugo(float memory, unsigned int random_seed);
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* some public macros used everywhere */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* Used in matchpat.c. Have to be different from WHITE, BLACK. */
|
||||
#define ANCHOR_COLOR 6
|
||||
#define ANCHOR_OTHER 7
|
||||
|
||||
/* Return codes for reading functions */
|
||||
|
||||
#define WIN 5
|
||||
#define KO_A 4
|
||||
#define GAIN 3
|
||||
#define LOSS 2
|
||||
#define KO_B 1
|
||||
#define LOSE 0
|
||||
|
||||
const char *result_to_string(int result);
|
||||
|
||||
/* Used by break_through(). Must be different from 0 and WIN. */
|
||||
#define CUT 2
|
||||
|
||||
|
||||
/* Surrounded */
|
||||
|
||||
#define SURROUNDED 1
|
||||
#define WEAKLY_SURROUNDED 2
|
||||
|
||||
/* ================================================================ */
|
||||
/* Board manipulation */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
int check_boardsize(int boardsize, FILE *out);
|
||||
void gnugo_clear_board(int boardsize);
|
||||
void gnugo_play_move(int move, int color);
|
||||
int gnugo_play_sgfnode(SGFNode *node, int to_move);
|
||||
int gnugo_sethand(int desired_handicap, SGFNode *node);
|
||||
float gnugo_estimate_score(float *upper, float *lower);
|
||||
|
||||
/* ================================================================ */
|
||||
/* Game handling */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
typedef struct {
|
||||
int handicap;
|
||||
int to_move; /* whose move it currently is */
|
||||
SGFTree game_record; /* Game record in sgf format. */
|
||||
int computer_player; /* BLACK, WHITE, or EMPTY (used as BOTH) */
|
||||
} Gameinfo;
|
||||
|
||||
void gameinfo_clear(Gameinfo *ginfo);
|
||||
void gameinfo_print(Gameinfo *ginfo);
|
||||
int gameinfo_play_sgftree_rot(Gameinfo *gameinfo, SGFTree *tree,
|
||||
const char *untilstr, int orientation);
|
||||
int gameinfo_play_sgftree(Gameinfo *gameinfo, SGFTree *tree,
|
||||
const char *untilstr);
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* global variables */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* Miscellaneous debug options. */
|
||||
extern int quiet; /* Minimal output. */
|
||||
extern int verbose; /* Bore the opponent. */
|
||||
extern int allpats; /* generate all patterns, even small ones */
|
||||
extern int printworms; /* print full data on each string */
|
||||
extern int printmoyo; /* print moyo board each move */
|
||||
extern int printdragons; /* print full data on each dragon */
|
||||
extern int printboard; /* print board each move */
|
||||
extern int showstatistics; /* print statistics */
|
||||
extern int profile_patterns; /* print statistics of pattern usage */
|
||||
extern char outfilename[128]; /* output file (-o option) */
|
||||
extern int output_flags; /* amount of output to outfile */
|
||||
|
||||
/* output flag bits */
|
||||
#define OUTPUT_MARKDRAGONS 0x0001 /* mark dead and critical dragons */
|
||||
#define OUTPUT_MOVEVALUES 0x0002 /* output values of all moves in list */
|
||||
|
||||
#define OUTPUT_DEFAULT 0 /* no debug output by default */
|
||||
|
||||
/* debug flag bits */
|
||||
/* NOTE : can specify -d0x... */
|
||||
/* Please keep this list in sync with the DEBUG_FLAGS string below. */
|
||||
#define DEBUG_INFLUENCE 0x0001
|
||||
#define DEBUG_EYES 0x0002
|
||||
#define DEBUG_OWL 0x0004
|
||||
#define DEBUG_ESCAPE 0x0008
|
||||
#define DEBUG_MATCHER 0x0010
|
||||
#define DEBUG_DRAGONS 0x0020
|
||||
#define DEBUG_SEMEAI 0x0040
|
||||
#define DEBUG_LOADSGF 0x0080
|
||||
#define DEBUG_HELPER 0x0100
|
||||
#define DEBUG_READING 0x0200
|
||||
#define DEBUG_WORMS 0x0400
|
||||
#define DEBUG_MOVE_REASONS 0x0800
|
||||
#define DEBUG_OWL_PERFORMANCE 0x1000
|
||||
#define DEBUG_BREAKIN 0x2000
|
||||
#define DEBUG_FILLLIB 0x4000
|
||||
#define DEBUG_READING_PERFORMANCE 0x8000
|
||||
#define DEBUG_SCORING 0x010000
|
||||
#define DEBUG_AFTERMATH 0x020000
|
||||
#define DEBUG_ATARI_ATARI 0x040000
|
||||
#define DEBUG_READING_CACHE 0x080000
|
||||
#define DEBUG_TERRITORY 0x100000
|
||||
#define DEBUG_PERSISTENT_CACHE 0x200000
|
||||
#define DEBUG_TOP_MOVES 0x400000
|
||||
#define DEBUG_MISCELLANEOUS 0x800000
|
||||
#define DEBUG_ORACLE_STREAM 0x1000000
|
||||
#define DEBUG_LARGE_SCALE 0x1000000
|
||||
#define DEBUG_SPLIT_OWL 0x2000000
|
||||
#define DEBUG_TIME 0x4000000
|
||||
|
||||
|
||||
#define DEBUG_FLAGS "\
|
||||
DEBUG_INFLUENCE 0x0001\n\
|
||||
DEBUG_EYES 0x0002\n\
|
||||
DEBUG_OWL 0x0004\n\
|
||||
DEBUG_ESCAPE 0x0008\n\
|
||||
DEBUG_MATCHER 0x0010\n\
|
||||
DEBUG_DRAGONS 0x0020\n\
|
||||
DEBUG_SEMEAI 0x0040\n\
|
||||
DEBUG_LOADSGF 0x0080\n\
|
||||
DEBUG_HELPER 0x0100\n\
|
||||
DEBUG_READING 0x0200\n\
|
||||
DEBUG_WORMS 0x0400\n\
|
||||
DEBUG_MOVE_REASONS 0x0800\n\
|
||||
DEBUG_OWL_PERFORMANCE 0x1000\n\
|
||||
DEBUG_BREAKIN 0x2000\n\
|
||||
DEBUG_FILLLIB 0x4000\n\
|
||||
DEBUG_READING_PERFORMANCE 0x8000\n\
|
||||
DEBUG_SCORING 0x010000\n\
|
||||
DEBUG_AFTERMATH 0x020000\n\
|
||||
DEBUG_ATARI_ATARI 0x040000\n\
|
||||
DEBUG_READING_CACHE 0x080000\n\
|
||||
DEBUG_TERRITORY 0x100000\n\
|
||||
DEBUG_PERSISTENT_CACHE 0x200000\n\
|
||||
DEBUG_TOP_MOVES 0x400000\n\
|
||||
DEBUG_MISCELLANEOUS 0x800000\n\
|
||||
DEBUG_ORACLE_STREAM 0x1000000\n\
|
||||
DEBUG_LARGE_SCALE 0x1000000\n\
|
||||
DEBUG_SPLIT_OWL 0x2000000\n\
|
||||
DEBUG_TIME 0x4000000\n\
|
||||
"
|
||||
|
||||
|
||||
extern int debug; /* debug flags */
|
||||
extern int fusekidb; /* use fuseki database */
|
||||
extern int disable_fuseki; /* do not generate fuseki moves */
|
||||
extern int josekidb; /* use joseki database */
|
||||
extern int semeai_variations; /* max variations considered reading semeai */
|
||||
extern int showtime; /* print genmove time */
|
||||
extern int showscore; /* print score */
|
||||
extern int chinese_rules; /* use chinese (area) rules for counting */
|
||||
extern int experimental_owl_ext; /* use experimental owl (GAIN/LOSS) */
|
||||
extern int experimental_connections; /* use experimental connection module */
|
||||
extern int alternate_connections; /* use alternate connection module */
|
||||
extern int owl_threats; /* compute owl threats */
|
||||
extern int capture_all_dead; /* capture all dead opponent stones */
|
||||
extern int play_out_aftermath; /* make everything unconditionally settled */
|
||||
extern int resign_allowed; /* allows GG to resign hopeless games */
|
||||
extern int play_mirror_go; /* try to play mirror go if possible */
|
||||
extern int mirror_stones_limit; /* but stop at this number of stones */
|
||||
extern int gtp_version; /* version of Go Text Protocol */
|
||||
extern int use_monte_carlo_genmove; /* use Monte Carlo move generation */
|
||||
extern int mc_games_per_level; /* number of Monte Carlo simulations per level */
|
||||
|
||||
/* Mandatory values of reading parameters. Normally -1, if set
|
||||
* these override the values derived from the level. */
|
||||
extern int mandated_depth;
|
||||
extern int mandated_backfill_depth;
|
||||
extern int mandated_backfill2_depth;
|
||||
extern int mandated_break_chain_depth;
|
||||
extern int mandated_superstring_depth;
|
||||
extern int mandated_fourlib_depth;
|
||||
extern int mandated_ko_depth;
|
||||
extern int mandated_branch_depth;
|
||||
extern int mandated_aa_depth;
|
||||
extern int mandated_owl_distrust_depth;
|
||||
extern int mandated_owl_branch_depth;
|
||||
extern int mandated_owl_reading_depth;
|
||||
extern int mandated_owl_node_limit;
|
||||
extern int mandated_semeai_node_limit;
|
||||
|
||||
extern int autolevel_on;
|
||||
|
||||
extern float potential_moves[BOARDMAX];
|
||||
|
||||
extern int oracle_exists; /* oracle is available for consultation */
|
||||
extern int metamachine; /* use metamachine_genmove */
|
||||
|
||||
/* ================================================================ */
|
||||
/* tracing and debugging functions */
|
||||
/* ================================================================ */
|
||||
|
||||
/* Colors. */
|
||||
#define GG_COLOR_BLACK 0
|
||||
#define GG_COLOR_RED 1
|
||||
#define GG_COLOR_GREEN 2
|
||||
#define GG_COLOR_YELLOW 3
|
||||
#define GG_COLOR_BLUE 4
|
||||
#define GG_COLOR_MAGENTA 5
|
||||
#define GG_COLOR_CYAN 6
|
||||
#define GG_COLOR_WHITE 7
|
||||
|
||||
/* showbord.c */
|
||||
void start_draw_board(void);
|
||||
void draw_color_char(int m, int n, int c, int color);
|
||||
void draw_char(int m, int n, int c);
|
||||
void end_draw_board(void);
|
||||
void showboard(int xo); /* ascii rep. of board to stderr */
|
||||
|
||||
|
||||
/* influence.c */
|
||||
void debug_influence_move(int move);
|
||||
|
||||
|
||||
#define TRACE (!(verbose)) ? (void)0 : (void)gprintf
|
||||
|
||||
#ifdef HAVE_VARIADIC_DEFINE
|
||||
|
||||
/* gnuc allows variadic macros, so the tests can be done inline */
|
||||
#define DEBUG(level, fmt, args...) \
|
||||
do { if ((debug & (level))) gprintf(fmt, ##args); } while (0)
|
||||
|
||||
#else /*HAVE_VARIADIC_DEFINE*/
|
||||
|
||||
/* if debug == 0, then can skip the function call. */
|
||||
#define DEBUG (!(debug)) ? (void)0 : (void)DEBUG_func
|
||||
int DEBUG_func(int level, const char *fmt, ...);
|
||||
|
||||
#endif /*HAVE_VARIADIC_DEFINE*/
|
||||
|
||||
|
||||
/* genmove.c */
|
||||
#define EXAMINE_WORMS 1
|
||||
#define EXAMINE_INITIAL_INFLUENCE 2
|
||||
#define EXAMINE_DRAGONS_WITHOUT_OWL 3
|
||||
#define EXAMINE_DRAGONS 4
|
||||
#define EXAMINE_OWL_REASONS 5
|
||||
#define EXAMINE_INITIAL_INFLUENCE2 6
|
||||
#define FULL_EXAMINE_DRAGONS 7
|
||||
|
||||
#define EXAMINE_ALL 99
|
||||
|
||||
void reset_engine(void);
|
||||
void examine_position(int how_much, int aftermath_play);
|
||||
void silent_examine_position(int how_much);
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* statistics functions */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* These are mostly used for GTP examination. */
|
||||
void reset_owl_node_counter(void);
|
||||
int get_owl_node_counter(void);
|
||||
void reset_reading_node_counter(void);
|
||||
int get_reading_node_counter(void);
|
||||
void reset_connection_node_counter(void);
|
||||
int get_connection_node_counter(void);
|
||||
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* Low level functions */
|
||||
/* ================================================================ */
|
||||
|
||||
/* utils.c */
|
||||
void who_wins(int color, FILE *outfile);
|
||||
|
||||
/* high-level routine to generate the best move for the given color */
|
||||
int genmove(int color, float *value, int *resign);
|
||||
int genmove_conservative(int color, float *value);
|
||||
|
||||
/* Play through the aftermath. */
|
||||
float aftermath_compute_score(int color, SGFTree *tree);
|
||||
|
||||
/* Basic information gathering. */
|
||||
/* worm.c */
|
||||
void make_worms(void);
|
||||
void compute_worm_influence(void);
|
||||
|
||||
/* dragon.c */
|
||||
void make_dragons(int stop_before_owl);
|
||||
void initialize_dragon_data(void);
|
||||
void show_dragons(void);
|
||||
enum dragon_status crude_status(int pos);
|
||||
enum dragon_status dragon_status(int pos);
|
||||
int same_dragon(int dr1, int dr2);
|
||||
|
||||
/* debugging functions */
|
||||
void prepare_pattern_profiling(void);
|
||||
void report_pattern_profiling(void);
|
||||
|
||||
/* sgffile.c */
|
||||
void sgffile_add_debuginfo(SGFNode *node, float value);
|
||||
void sgffile_output(SGFTree *tree);
|
||||
|
||||
void sgffile_printsgf(int color_to_play, const char *filename);
|
||||
void sgffile_printboard(SGFTree *tree);
|
||||
void sgffile_recordboard(SGFNode *node);
|
||||
int get_sgfmove(SGFProperty *property);
|
||||
|
||||
/* sgfdecide.c */
|
||||
void decide_string(int pos);
|
||||
void decide_connection(int apos, int bpos);
|
||||
void decide_owl(int pos);
|
||||
void decide_dragon_data(int pos);
|
||||
void decide_semeai(int apos, int bpos);
|
||||
void decide_tactical_semeai(int apos, int bpos);
|
||||
void decide_position(void);
|
||||
void decide_eye(int pos);
|
||||
void decide_combination(int color);
|
||||
void decide_surrounded(int pos);
|
||||
void decide_oracle(Gameinfo *gameinfo, char *infilename, char *untilstring);
|
||||
|
||||
/*oracle.c*/
|
||||
void dismiss_oracle(void);
|
||||
void oracle_clear_board(int boardsize);
|
||||
|
||||
#endif /* _GNUGO_H_ */
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
446
gnugo/engine/handicap.c
Normal file
446
gnugo/engine/handicap.c
Normal file
@ -0,0 +1,446 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include "liberty.h"
|
||||
#include "patterns.h"
|
||||
#include "random.h"
|
||||
|
||||
/* ================================================================ */
|
||||
/* Set up fixed placement handicap stones for black side */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/* Handicap stones are set up according to the following diagrams:
|
||||
*
|
||||
* 2 stones: 3 stones:
|
||||
*
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
* 9 . . . . . . . . . 9 9 . . . . . . . . . 9
|
||||
* 8 . . . . . . . . . 8 8 . . . . . . . . . 8
|
||||
* 7 . . + . . . X . . 7 7 . . X . . . X . . 7
|
||||
* 6 . . . . . . . . . 6 6 . . . . . . . . . 6
|
||||
* 5 . . . . + . . . . 5 5 . . . . + . . . . 5
|
||||
* 4 . . . . . . . . . 4 4 . . . . . . . . . 4
|
||||
* 3 . . X . . . + . . 3 3 . . X . . . + . . 3
|
||||
* 2 . . . . . . . . . 2 2 . . . . . . . . . 2
|
||||
* 1 . . . . . . . . . 1 1 . . . . . . . . . 1
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
*
|
||||
* 4 stones: 5 stones:
|
||||
*
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
* 9 . . . . . . . . . 9 9 . . . . . . . . . 9
|
||||
* 8 . . . . . . . . . 8 8 . . . . . . . . . 8
|
||||
* 7 . . X . . . X . . 7 7 . . X . . . X . . 7
|
||||
* 6 . . . . . . . . . 6 6 . . . . . . . . . 6
|
||||
* 5 . . . . + . . . . 5 5 . . . . X . . . . 5
|
||||
* 4 . . . . . . . . . 4 4 . . . . . . . . . 4
|
||||
* 3 . . X . . . X . . 3 3 . . X . . . X . . 3
|
||||
* 2 . . . . . . . . . 2 2 . . . . . . . . . 2
|
||||
* 1 . . . . . . . . . 1 1 . . . . . . . . . 1
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
*
|
||||
* 6 stones: 7 stones:
|
||||
*
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
* 9 . . . . . . . . . 9 9 . . . . . . . . . 9
|
||||
* 8 . . . . . . . . . 8 8 . . . . . . . . . 8
|
||||
* 7 . . X . . . X . . 7 7 . . X . . . X . . 7
|
||||
* 6 . . . . . . . . . 6 6 . . . . . . . . . 6
|
||||
* 5 . . X . + . X . . 5 5 . . X . X . X . . 5
|
||||
* 4 . . . . . . . . . 4 4 . . . . . . . . . 4
|
||||
* 3 . . X . . . X . . 3 3 . . X . . . X . . 3
|
||||
* 2 . . . . . . . . . 2 2 . . . . . . . . . 2
|
||||
* 1 . . . . . . . . . 1 1 . . . . . . . . . 1
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
*
|
||||
* 8 stones: 9 stones:
|
||||
*
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
* 9 . . . . . . . . . 9 9 . . . . . . . . . 9
|
||||
* 8 . . . . . . . . . 8 8 . . . . . . . . . 8
|
||||
* 7 . . X . X . X . . 7 7 . . X . X . X . . 7
|
||||
* 6 . . . . . . . . . 6 6 . . . . . . . . . 6
|
||||
* 5 . . X . + . X . . 5 5 . . X . X . X . . 5
|
||||
* 4 . . . . . . . . . 4 4 . . . . . . . . . 4
|
||||
* 3 . . X . X . X . . 3 3 . . X . X . X . . 3
|
||||
* 2 . . . . . . . . . 2 2 . . . . . . . . . 2
|
||||
* 1 . . . . . . . . . 1 1 . . . . . . . . . 1
|
||||
* A B C D E F G H J A B C D E F G H J
|
||||
*
|
||||
* For odd-sized boards larger than 9x9, the same pattern is followed,
|
||||
* except that the edge stones are moved to the fourth line for 13x13
|
||||
* boards and larger.
|
||||
*
|
||||
* For even-sized boards at least 8x8, only the four first diagrams
|
||||
* are used, because there is no way to place the center stones
|
||||
* symmetrically. As for odd-sized boards, the edge stones are moved
|
||||
* to the fourth line for boards larger than 11x11.
|
||||
*
|
||||
* At most four stones are placed on 7x7 boards too (this size may or
|
||||
* may not be supported by the rest of the engine). No handicap stones
|
||||
* are ever placed on smaller boards.
|
||||
*
|
||||
* Notice that this function only deals with fixed handicap placement.
|
||||
* Larger handicaps can be added by free placement if the used
|
||||
* interface supports it.
|
||||
*/
|
||||
|
||||
|
||||
/* This table contains the (coded) positions of the stones.
|
||||
* 2 maps to 2 or 3, depending on board size
|
||||
* 0 maps to center
|
||||
* -ve numbers map to board_size - number
|
||||
*
|
||||
* The stones are placed in this order, *except* if there are
|
||||
* 5 or 7 stones, in which case center ({0, 0}) is placed, and
|
||||
* then as for 4 or 6.
|
||||
*/
|
||||
|
||||
static const int places[][2] = {
|
||||
|
||||
{2, -2}, {-2, 2}, {2, 2}, {-2, -2}, /* first 4 are easy */
|
||||
/* for 5, {0,0} is explicitly placed */
|
||||
|
||||
{0, 2}, {0, -2}, /* for 6 these two are placed */
|
||||
/* for 7, {0,0} is explicitly placed */
|
||||
|
||||
{2, 0}, {-2, 0}, /* for 8, these two are placed */
|
||||
|
||||
{0, 0}, /* finally tengen for 9 */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Sets up fixed handicap placement stones, returning the number of
|
||||
* placed handicap stones and also setting the global variable
|
||||
* handicap to the same value.
|
||||
*/
|
||||
|
||||
int
|
||||
place_fixed_handicap(int desired_handicap)
|
||||
{
|
||||
int r;
|
||||
int max_handicap;
|
||||
int remaining_stones;
|
||||
int three = board_size > 11 ? 3 : 2;
|
||||
int mid = board_size/2;
|
||||
|
||||
/* A handicap of 1 just means that B plays first, no komi.
|
||||
* Black is not told where to play the first stone so no handicap
|
||||
* is set.
|
||||
*/
|
||||
if (desired_handicap < 2) {
|
||||
handicap = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((board_size % 2 == 1) && (board_size >= 9))
|
||||
max_handicap = 9;
|
||||
else if (board_size >= 7)
|
||||
max_handicap = 4;
|
||||
else
|
||||
max_handicap = 0;
|
||||
|
||||
/* It's up to the caller of this function to notice if the handicap
|
||||
* was too large for fixed placement and act upon that.
|
||||
*/
|
||||
if (desired_handicap > max_handicap)
|
||||
handicap = max_handicap;
|
||||
else
|
||||
handicap = desired_handicap;
|
||||
|
||||
remaining_stones = handicap;
|
||||
/* special cases: 5 and 7 */
|
||||
if (desired_handicap == 5 || desired_handicap == 7) {
|
||||
add_stone(POS(mid, mid), BLACK);
|
||||
remaining_stones--;
|
||||
}
|
||||
|
||||
for (r = 0; r < remaining_stones; r++) {
|
||||
int i = places[r][0];
|
||||
int j = places[r][1];
|
||||
|
||||
/* Translate the encoded values to board co-ordinates. */
|
||||
if (i == 2)
|
||||
i = three; /* 2 or 3 */
|
||||
else if (i == 0)
|
||||
i = mid;
|
||||
else if (i == -2)
|
||||
i = board_size - 1 - three;
|
||||
|
||||
if (j == 2)
|
||||
j = three;
|
||||
else if (j == 0)
|
||||
j = mid;
|
||||
else if (j == -2)
|
||||
j = board_size - 1 - three;
|
||||
|
||||
add_stone(POS(i, j), BLACK);
|
||||
}
|
||||
|
||||
return handicap;
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* Set up free placement handicap stones for black side */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/*
|
||||
* Sets up free handicap placement stones, returning the number of
|
||||
* placed handicap stones.
|
||||
*/
|
||||
|
||||
static int remaining_handicap_stones = -1;
|
||||
static int total_handicap_stones = -1;
|
||||
|
||||
static int find_free_handicap_pattern(void);
|
||||
static void free_handicap_callback(int anchor, int color,
|
||||
struct pattern *pattern,
|
||||
int ll, void *data);
|
||||
|
||||
/*
|
||||
* Sets up free placement handicap stones, returning the number of
|
||||
* placed handicap stones and also setting the global variable
|
||||
* handicap to the same value.
|
||||
*/
|
||||
int
|
||||
place_free_handicap(int desired_handicap)
|
||||
{
|
||||
gg_assert(desired_handicap == 0 || desired_handicap >= 2);
|
||||
|
||||
if (desired_handicap == 0) {
|
||||
handicap = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
total_handicap_stones = desired_handicap;
|
||||
remaining_handicap_stones = desired_handicap;
|
||||
|
||||
/* First place black stones in the four corners to enable the
|
||||
* pattern matching scheme.
|
||||
*/
|
||||
add_stone(POS(0, 0), BLACK);
|
||||
add_stone(POS(0, board_size - 1), BLACK);
|
||||
add_stone(POS(board_size - 1, 0), BLACK);
|
||||
add_stone(POS(board_size - 1, board_size - 1), BLACK);
|
||||
|
||||
/* Find and place free handicap stones by pattern matching. */
|
||||
while (remaining_handicap_stones > 0) {
|
||||
if (!find_free_handicap_pattern())
|
||||
break;
|
||||
}
|
||||
|
||||
/* Remove the artificial corner stones. */
|
||||
remove_stone(POS(0, 0));
|
||||
remove_stone(POS(0, board_size - 1));
|
||||
remove_stone(POS(board_size - 1, 0));
|
||||
remove_stone(POS(board_size - 1, board_size - 1));
|
||||
|
||||
/* Find and place additional free handicap stones by the aftermath
|
||||
* algorithm.
|
||||
*/
|
||||
while (remaining_handicap_stones > 0) {
|
||||
int move;
|
||||
/* Call genmove_conservative() in order to prepare the engine for
|
||||
* an aftermath_genmove() call. We discard the genmove result.
|
||||
*/
|
||||
genmove_conservative(BLACK, NULL);
|
||||
move = aftermath_genmove(BLACK, 0, NULL);
|
||||
if (move != PASS_MOVE) {
|
||||
add_stone(move, BLACK);
|
||||
remaining_handicap_stones--;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set handicap to the number of actually placed stones. */
|
||||
handicap = desired_handicap - remaining_handicap_stones;
|
||||
|
||||
/* Reset these to invalid values, so that improper use of handicap
|
||||
* helper functions can be detected.
|
||||
*/
|
||||
total_handicap_stones = -1;
|
||||
remaining_handicap_stones = -1;
|
||||
|
||||
return handicap;
|
||||
}
|
||||
|
||||
struct handicap_match {
|
||||
int value;
|
||||
int anchor;
|
||||
struct pattern *pattern;
|
||||
int ll;
|
||||
};
|
||||
|
||||
#define MAX_HANDICAP_MATCHES 40
|
||||
|
||||
static struct handicap_match handicap_matches[MAX_HANDICAP_MATCHES];
|
||||
static int number_of_matches;
|
||||
|
||||
static int
|
||||
find_free_handicap_pattern()
|
||||
{
|
||||
int k;
|
||||
int highest_value = -1;
|
||||
int sum_values = 0;
|
||||
int r;
|
||||
int anchor;
|
||||
struct pattern *pattern;
|
||||
int ll;
|
||||
int move;
|
||||
|
||||
number_of_matches = 0;
|
||||
matchpat(free_handicap_callback, BLACK, &handipat_db, NULL, NULL);
|
||||
|
||||
if (number_of_matches == 0)
|
||||
return 0;
|
||||
|
||||
/* Find the highest value among the matched patterns. */
|
||||
for (k = 0; k < number_of_matches; k++)
|
||||
if (highest_value < handicap_matches[k].value)
|
||||
highest_value = handicap_matches[k].value;
|
||||
|
||||
/* Replace the values by 2^(value - highest_value + 10) and compute
|
||||
* the sum of these values. Fractional values are discarded.
|
||||
*/
|
||||
for (k = 0; k < number_of_matches; k++) {
|
||||
if (handicap_matches[k].value < highest_value - 10)
|
||||
handicap_matches[k].value = 0;
|
||||
else
|
||||
handicap_matches[k].value = 1 << (handicap_matches[k].value
|
||||
- highest_value + 10);
|
||||
sum_values += handicap_matches[k].value;
|
||||
}
|
||||
|
||||
/* Pick a random number between 0 and sum_values. Don't bother with
|
||||
* the fact that lower numbers will tend to be very slightly
|
||||
* overrepresented.
|
||||
*/
|
||||
r = gg_rand() % sum_values;
|
||||
|
||||
/* Find the chosen pattern. */
|
||||
for (k = 0; k < number_of_matches; k++) {
|
||||
r -= handicap_matches[k].value;
|
||||
if (r < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Place handicap stones according to pattern k. */
|
||||
anchor = handicap_matches[k].anchor;
|
||||
pattern = handicap_matches[k].pattern;
|
||||
ll = handicap_matches[k].ll;
|
||||
|
||||
/* Pick up the location of the move */
|
||||
move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
|
||||
add_stone(move, BLACK);
|
||||
remaining_handicap_stones--;
|
||||
|
||||
/* Add stones at all '!' in the pattern. */
|
||||
for (k = 0; k < pattern->patlen; k++) {
|
||||
if (pattern->patn[k].att == ATT_not) {
|
||||
int pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
|
||||
add_stone(pos, BLACK);
|
||||
remaining_handicap_stones--;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
free_handicap_callback(int anchor, int color, struct pattern *pattern,
|
||||
int ll, void *data)
|
||||
{
|
||||
int r = -1;
|
||||
int k;
|
||||
int number_of_stones = 1;
|
||||
|
||||
/* Pick up the location of the move */
|
||||
int move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
|
||||
|
||||
UNUSED(data);
|
||||
|
||||
/* Check how many stones are placed by the pattern. This must not be
|
||||
* larger than the number of remaining handicap stones.
|
||||
*/
|
||||
for (k = 0; k < pattern->patlen; k++) {
|
||||
if (pattern->patn[k].att == ATT_not)
|
||||
number_of_stones++;
|
||||
}
|
||||
if (number_of_stones > remaining_handicap_stones)
|
||||
return;
|
||||
|
||||
/* If the pattern has a constraint, call the autohelper to see
|
||||
* if the pattern must be rejected.
|
||||
*/
|
||||
if (pattern->autohelper_flag & HAVE_CONSTRAINT) {
|
||||
if (!pattern->autohelper(ll, move, color, 0))
|
||||
return;
|
||||
}
|
||||
|
||||
if (number_of_matches < MAX_HANDICAP_MATCHES) {
|
||||
r = number_of_matches;
|
||||
number_of_matches++;
|
||||
}
|
||||
else {
|
||||
int least_value = handicap_matches[0].value + 1;
|
||||
for (k = 0; k < number_of_matches; k++) {
|
||||
if (handicap_matches[k].value < least_value) {
|
||||
r = k;
|
||||
least_value = handicap_matches[k].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
gg_assert(r >= 0 && r < MAX_HANDICAP_MATCHES);
|
||||
handicap_matches[r].value = pattern->value;
|
||||
handicap_matches[r].anchor = anchor;
|
||||
handicap_matches[r].pattern = pattern;
|
||||
handicap_matches[r].ll = ll;
|
||||
}
|
||||
|
||||
int
|
||||
free_handicap_remaining_stones()
|
||||
{
|
||||
gg_assert(remaining_handicap_stones >= 0);
|
||||
return remaining_handicap_stones;
|
||||
}
|
||||
|
||||
int
|
||||
free_handicap_total_stones()
|
||||
{
|
||||
gg_assert(total_handicap_stones >= 0);
|
||||
return total_handicap_stones;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
257
gnugo/engine/hash.c
Normal file
257
gnugo/engine/hash.c
Normal file
@ -0,0 +1,257 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
#include "board.h"
|
||||
#include "hash.h"
|
||||
#include "random.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* This file, together with engine/hash.h implements hashing of go positions
|
||||
* using a method known as Zobrist hashing. See the Texinfo documentation
|
||||
* (Reading/Hashing) for more information.
|
||||
*/
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
|
||||
|
||||
/* Random values for the board hash function. For stones and ko position. */
|
||||
static Hash_data white_hash[BOARDMAX];
|
||||
static Hash_data black_hash[BOARDMAX];
|
||||
static Hash_data ko_hash[BOARDMAX];
|
||||
static Hash_data komaster_hash[NUM_KOMASTER_STATES];
|
||||
static Hash_data kom_pos_hash[BOARDMAX];
|
||||
static Hash_data goal_hash[BOARDMAX];
|
||||
|
||||
|
||||
/* Get a random Hashvalue, where all bits are used. */
|
||||
static Hashvalue
|
||||
hash_rand(void)
|
||||
{
|
||||
int i;
|
||||
Hashvalue h = 0;
|
||||
|
||||
for (i = 0; 32*i < (int) (CHAR_BIT*sizeof(Hashvalue)); i++)
|
||||
h |= (Hashvalue) gg_urand() << 32*i;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Fill an array with random numbers for Zobrist hashing. */
|
||||
void
|
||||
hash_init_zobrist_array(Hash_data *array, int size)
|
||||
{
|
||||
int i, j;
|
||||
for (i = 0; i < size; i++)
|
||||
for (j = 0; j < NUM_HASHVALUES; j++)
|
||||
array[i].hashval[j] = hash_rand();
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the board hash system.
|
||||
*/
|
||||
|
||||
void
|
||||
hash_init(void)
|
||||
{
|
||||
static int is_initialized = 0;
|
||||
if (is_initialized)
|
||||
return;
|
||||
|
||||
INIT_ZOBRIST_ARRAY(black_hash);
|
||||
INIT_ZOBRIST_ARRAY(white_hash);
|
||||
INIT_ZOBRIST_ARRAY(ko_hash);
|
||||
INIT_ZOBRIST_ARRAY(komaster_hash);
|
||||
INIT_ZOBRIST_ARRAY(kom_pos_hash);
|
||||
INIT_ZOBRIST_ARRAY(goal_hash);
|
||||
|
||||
is_initialized = 1;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
/* Calculate the hashvalue from scratch. */
|
||||
void
|
||||
hashdata_recalc(Hash_data *hd, Intersection *p, int ko_pos)
|
||||
{
|
||||
int pos;
|
||||
|
||||
hashdata_clear(hd);
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (p[pos] == WHITE)
|
||||
hashdata_xor(*hd, white_hash[pos]);
|
||||
else if (p[pos] == BLACK)
|
||||
hashdata_xor(*hd, black_hash[pos]);
|
||||
}
|
||||
|
||||
if (ko_pos != 0)
|
||||
hashdata_xor(*hd, ko_hash[ko_pos]);
|
||||
}
|
||||
|
||||
/* Clear hashdata. */
|
||||
void
|
||||
hashdata_clear(Hash_data *hd)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < NUM_HASHVALUES; i++)
|
||||
hd->hashval[i] = 0;
|
||||
}
|
||||
|
||||
/* Set or remove ko in the hash value and hash position. */
|
||||
void
|
||||
hashdata_invert_ko(Hash_data *hd, int pos)
|
||||
{
|
||||
hashdata_xor(*hd, ko_hash[pos]);
|
||||
}
|
||||
|
||||
|
||||
/* Set or remove a stone of COLOR at pos in a Hash_data. */
|
||||
void
|
||||
hashdata_invert_stone(Hash_data *hd, int pos, int color)
|
||||
{
|
||||
if (color == BLACK)
|
||||
hashdata_xor(*hd, black_hash[pos]);
|
||||
else if (color == WHITE)
|
||||
hashdata_xor(*hd, white_hash[pos]);
|
||||
}
|
||||
|
||||
|
||||
/* Set or remove the komaster value in the hash data. */
|
||||
void
|
||||
hashdata_invert_komaster(Hash_data *hd, int komaster)
|
||||
{
|
||||
hashdata_xor(*hd, komaster_hash[komaster]);
|
||||
}
|
||||
|
||||
/* Set or remove the komaster position in the hash data. */
|
||||
void
|
||||
hashdata_invert_kom_pos(Hash_data *hd, int kom_pos)
|
||||
{
|
||||
hashdata_xor(*hd, kom_pos_hash[kom_pos]);
|
||||
}
|
||||
|
||||
/* Calculate a transformation invariant hashvalue. */
|
||||
void
|
||||
hashdata_calc_orientation_invariant(Hash_data *hd, Intersection *p, int ko_pos)
|
||||
{
|
||||
int pos;
|
||||
int rot;
|
||||
Hash_data hd_rot;
|
||||
|
||||
for (rot = 0; rot < 8; rot++) {
|
||||
hashdata_clear(&hd_rot);
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (p[pos] == WHITE)
|
||||
hashdata_xor(hd_rot, white_hash[rotate1(pos, rot)]);
|
||||
else if (p[pos] == BLACK)
|
||||
hashdata_xor(hd_rot, black_hash[rotate1(pos, rot)]);
|
||||
}
|
||||
|
||||
if (ko_pos != NO_MOVE)
|
||||
hashdata_xor(hd_rot, ko_hash[rotate1(ko_pos, rot)]);
|
||||
|
||||
if (rot == 0 || hashdata_is_smaller(hd_rot, *hd))
|
||||
*hd = hd_rot;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute hash value to identify the goal area. */
|
||||
Hash_data
|
||||
goal_to_hashvalue(const signed char *goal)
|
||||
{
|
||||
int pos;
|
||||
Hash_data return_value;
|
||||
|
||||
hashdata_clear(&return_value);
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos) && goal[pos])
|
||||
hashdata_xor(return_value, goal_hash[pos]);
|
||||
|
||||
return return_value;
|
||||
}
|
||||
|
||||
|
||||
#define HASHVALUE_NUM_DIGITS (1 + (CHAR_BIT * SIZEOF_HASHVALUE - 1) / 4)
|
||||
#define BUFFER_SIZE (1 + NUM_HASHVALUES * HASHVALUE_NUM_DIGITS)
|
||||
char *
|
||||
hashdata_to_string(Hash_data *hashdata)
|
||||
{
|
||||
static char buffer[BUFFER_SIZE];
|
||||
int n = 0;
|
||||
int k;
|
||||
|
||||
/* Loop backwards for consistency between 32 and 64 bit platforms. */
|
||||
for (k = NUM_HASHVALUES - 1; k >= 0; k--) {
|
||||
n += sprintf(buffer + n, HASHVALUE_PRINT_FORMAT,
|
||||
HASHVALUE_NUM_DIGITS, hashdata->hashval[k]);
|
||||
gg_assert(n < BUFFER_SIZE);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#if NUM_HASHVALUES > 2
|
||||
int
|
||||
hashdata_is_equal_func(Hash_data *hd1, Hash_data *hd2)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < NUM_HASHVALUES; i++)
|
||||
if (hd1->hashval[i] != hd2->hashval[i])
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
hashdata_is_smaller_func(Hash_data *hd1, Hash_data *hd2)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < NUM_HASHVALUES; i++)
|
||||
if (hd1->hashval[i] < hd2->hashval[i])
|
||||
return 1;
|
||||
else if (hd1->hashval[i] > hd2->hashval[i])
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
171
gnugo/engine/hash.h
Normal file
171
gnugo/engine/hash.h
Normal file
@ -0,0 +1,171 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef _HASH_H_
|
||||
#define _HASH_H_
|
||||
|
||||
#include "config.h"
|
||||
#include <limits.h>
|
||||
|
||||
/*
|
||||
* This file, together with engine/hash.c implements hashing of go positions
|
||||
* using a method known as Zobrist hashing. See the Texinfo documentation
|
||||
* (Reading/Hashing) for more information.
|
||||
*/
|
||||
|
||||
/* Hash values and the compact board representation should use the
|
||||
* longest integer type that the platform can handle efficiently.
|
||||
* Typically this would be a 32 bit integer on a 32 bit platform and a
|
||||
* 64 bit integer on a 64 bit platform.
|
||||
*
|
||||
* Our current assumption is that unsigned long has this
|
||||
* characteristic. Should it turn out to be false for some platform
|
||||
* we'll add conditional code to choose some other type.
|
||||
*
|
||||
* At the few places in the code where the actual size of these types
|
||||
* matter, the code should use sizeof(type) to test for this. Notice
|
||||
* that ISO C guarantees a long to be at least 32 bits.
|
||||
*
|
||||
* On (future) platforms with word length 128 bits or more, it might
|
||||
* be a waste to use more than 64 bit hashvalues, since the decreased
|
||||
* risk for hash collisions probably isn't worth the increased storage
|
||||
* cost.
|
||||
*/
|
||||
typedef unsigned long Hashvalue;
|
||||
#define SIZEOF_HASHVALUE SIZEOF_LONG
|
||||
#define HASHVALUE_PRINT_FORMAT "%0*lx"
|
||||
|
||||
/* for testing: Enables a lot of checks. */
|
||||
#define CHECK_HASHING 0
|
||||
|
||||
/* Dump (almost) all read results. */
|
||||
#define TRACE_READ_RESULTS 0
|
||||
|
||||
/* How many bits should be used at least for hashing? Set this to 32 for
|
||||
* some memory save and speedup, at the cost of occasional irreproducable
|
||||
* mistakes (and possibly assertion failures).
|
||||
* With 64 bits, there should be less than one such mistake in 10^9 games.
|
||||
* Set this to 96 if this is not safe enough for you.
|
||||
*/
|
||||
#define MIN_HASHBITS 64
|
||||
|
||||
#define NUM_HASHVALUES (1 + (MIN_HASHBITS - 1) / (CHAR_BIT * SIZEOF_HASHVALUE))
|
||||
|
||||
/* This struct is maintained by the machinery that updates the board
|
||||
* to provide incremental hashing. Examples: trymove(), play_move(), ...
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
Hashvalue hashval[NUM_HASHVALUES];
|
||||
} Hash_data;
|
||||
|
||||
extern Hash_data board_hash;
|
||||
|
||||
Hash_data goal_to_hashvalue(const signed char *goal);
|
||||
|
||||
void hash_init_zobrist_array(Hash_data *array, int size);
|
||||
void hash_init(void);
|
||||
#define INIT_ZOBRIST_ARRAY(a) \
|
||||
hash_init_zobrist_array(a, (int) (sizeof(a) / sizeof(a[0])))
|
||||
|
||||
void hashdata_clear(Hash_data *hd);
|
||||
void hashdata_recalc(Hash_data *hd, Intersection *board, int ko_pos);
|
||||
void hashdata_invert_ko(Hash_data *hd, int pos);
|
||||
void hashdata_invert_stone(Hash_data *hd, int pos, int color);
|
||||
void hashdata_invert_komaster(Hash_data *hd, int komaster);
|
||||
void hashdata_invert_kom_pos(Hash_data *hd, int kom_pos);
|
||||
void hashdata_calc_orientation_invariant(Hash_data *hd, Intersection *board,
|
||||
int ko_pos);
|
||||
|
||||
char *hashdata_to_string(Hash_data *hashdata);
|
||||
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
/* There is no need to involve all bits in the remainder computation
|
||||
* as long as we only use it to compute a key into a hash table. 32
|
||||
* random bits are sufficient to get an even distribution within any
|
||||
* hashtable of reasonable size. By never using more than 32 bits we
|
||||
* also reduce the platform dependency of the GNU Go engine.
|
||||
*/
|
||||
#define hashdata_remainder(hd, num) \
|
||||
(((hd).hashval[0] & 0xffffffffU) % (num))
|
||||
|
||||
#if NUM_HASHVALUES == 1
|
||||
|
||||
#define hashdata_is_equal(hd1, hd2) \
|
||||
((hd1).hashval[0] == (hd2).hashval[0])
|
||||
|
||||
#define hashdata_is_smaller(hd1, hd2) \
|
||||
((hd1).hashval[0] < (hd2).hashval[0])
|
||||
|
||||
#define hashdata_xor(hd1, hd2) \
|
||||
(hd1).hashval[0] ^= (hd2).hashval[0]
|
||||
|
||||
#elif NUM_HASHVALUES == 2
|
||||
|
||||
#define hashdata_is_equal(hd1, hd2) \
|
||||
((hd1).hashval[0] == (hd2).hashval[0] \
|
||||
&& (hd1).hashval[1] == (hd2).hashval[1])
|
||||
|
||||
#define hashdata_is_smaller(hd1, hd2) \
|
||||
((hd1).hashval[0] < (hd2).hashval[0] \
|
||||
|| ((hd1).hashval[0] == (hd2).hashval[0] \
|
||||
&& (hd1).hashval[1] < (hd2).hashval[1]))
|
||||
|
||||
#define hashdata_xor(hd1, hd2) \
|
||||
do { \
|
||||
(hd1).hashval[0] ^= (hd2).hashval[0]; \
|
||||
(hd1).hashval[1] ^= (hd2).hashval[1]; \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
|
||||
int hashdata_is_equal_func(Hash_data *hd1, Hash_data *hd2);
|
||||
int hashdata_is_smaller_func(Hash_data *hd1, Hash_data *hd2);
|
||||
|
||||
#define hashdata_is_equal(hd1, hd2) \
|
||||
hashdata_is_equal_func(&(hd1), &(hd2))
|
||||
|
||||
#define hashdata_is_smaller(hd1, hd2) \
|
||||
hashdata_is_smaller_func(&(hd1), &(hd2))
|
||||
|
||||
#define hashdata_xor(hd1, hd2) \
|
||||
do { \
|
||||
int i; \
|
||||
for (i = 0; i < NUM_HASHVALUES; i++) \
|
||||
(hd1).hashval[i] ^= (hd2).hashval[i]; \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
2122
gnugo/engine/influence.c
Normal file
2122
gnugo/engine/influence.c
Normal file
File diff suppressed because it is too large
Load Diff
148
gnugo/engine/influence.h
Normal file
148
gnugo/engine/influence.h
Normal file
@ -0,0 +1,148 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "winsocket.h"
|
||||
|
||||
|
||||
/* The cosmic style uses more influence than the defaults attenuation
|
||||
* coefficients !
|
||||
* The "TERR_.."-values are used in the influence computations used
|
||||
* for territory evaluation. (initial_influence with dragons_known,
|
||||
* move_influence)
|
||||
*/
|
||||
#define DEFAULT_ATTENUATION \
|
||||
(cosmic_importance * 2.7 + (1.0 - cosmic_importance) * 3.0)
|
||||
#define TERR_DEFAULT_ATTENUATION \
|
||||
(cosmic_importance * 2.15 + (1.0 - cosmic_importance) * 2.4)
|
||||
|
||||
/* Extra damping coefficient for spreading influence diagonally. */
|
||||
#define DIAGONAL_DAMPING \
|
||||
(cosmic_importance * 2.5 + (1.0 - cosmic_importance) * 2.0)
|
||||
#define TERR_DIAGONAL_DAMPING \
|
||||
(cosmic_importance * 2.5 + (1.0 - cosmic_importance) * 1.7)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Smallest amount of influence that we care about distributing. */
|
||||
#define INFLUENCE_CUTOFF 0.02
|
||||
|
||||
/* Value in delta_territory_cache indicating that the value has not
|
||||
* been computed. Arbitrary but unattainable.
|
||||
*/
|
||||
#define NOT_COMPUTED (-2.0 * MAX_BOARD * MAX_BOARD)
|
||||
|
||||
/* Maximum number of regions allowed between territory, moyo, and area.
|
||||
* FIXME: This number is vastly exaggerated. Should be possible to
|
||||
* come up with a much better upper bound.
|
||||
*/
|
||||
#define MAX_REGIONS (3*MAX_BOARD*MAX_BOARD + 1)
|
||||
|
||||
#define MAX_INTRUSIONS (2 * MAX_BOARD * MAX_BOARD)
|
||||
|
||||
struct intrusion_data
|
||||
{
|
||||
int source_pos; /* Stone from which intrusion originates.*/
|
||||
int strength_pos; /* Position of the intrusion influence soure. */
|
||||
float strength;
|
||||
float attenuation;
|
||||
};
|
||||
|
||||
struct influence_data
|
||||
{
|
||||
signed char safe[BOARDMAX];
|
||||
|
||||
float white_influence[BOARDMAX]; /* Accumulated influence. */
|
||||
float black_influence[BOARDMAX]; /* Accumulated influence. */
|
||||
float white_strength[BOARDMAX]; /* Strength of influence source. */
|
||||
float black_strength[BOARDMAX]; /* Strength of influence source. */
|
||||
float white_attenuation[BOARDMAX];
|
||||
float black_attenuation[BOARDMAX];
|
||||
float white_permeability[BOARDMAX];
|
||||
float black_permeability[BOARDMAX];
|
||||
|
||||
int is_territorial_influence; /* 0 only if computing escape_influence.*/
|
||||
|
||||
float territory_value[BOARDMAX];
|
||||
int non_territory[BOARDMAX];
|
||||
int captured;
|
||||
|
||||
int color_to_move; /* Which color is in turn to move. */
|
||||
|
||||
int queue[MAX_BOARD * MAX_BOARD]; /* Points receiving influence. */
|
||||
|
||||
int intrusion_counter;
|
||||
struct intrusion_data intrusions[MAX_INTRUSIONS];
|
||||
|
||||
int id;
|
||||
};
|
||||
|
||||
/* Typedef for pointer to either of the functions whose_territory(),
|
||||
* whose_loose_territory(), whose_moyo(), and whose_area().
|
||||
*/
|
||||
typedef int (*owner_function_ptr)(const struct influence_data *q, int pos);
|
||||
|
||||
/* Used for tuning game advancement algorythm */
|
||||
#define WEIGHT_TERRITORY 10
|
||||
#define WEIGHT_MOYO 3
|
||||
#define WEIGHT_AREA 1
|
||||
|
||||
|
||||
|
||||
/* cosmic_importance is a number between 0.0 and 1.0 ;
|
||||
* when cosmic_importance is 0.0, the default influence
|
||||
* values are used; when cosmic_importance is 1.0, GNU Go
|
||||
* will try to play an influence-oriented fuseki by
|
||||
* over-estimatingthe potential territory values of moyos.
|
||||
* In the current implementation, cosmic_importance decreases
|
||||
* slowly for 19*19 games from 1.0 at move 4 to 0.0 at move 120.
|
||||
*/
|
||||
float cosmic_importance;
|
||||
|
||||
|
||||
/* Used in the whose_moyo() function */
|
||||
struct moyo_determination_data
|
||||
{
|
||||
float influence_balance;
|
||||
float my_influence_minimum;
|
||||
float opp_influence_maximum;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
||||
|
430
gnugo/engine/interface.c
Normal file
430
gnugo/engine/interface.c
Normal file
@ -0,0 +1,430 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sgftree.h"
|
||||
#include "liberty.h"
|
||||
#include "clock.h"
|
||||
|
||||
#include "gg_utils.h"
|
||||
|
||||
/*
|
||||
* Initialize the gnugo engine. This needs to be called
|
||||
* once only.
|
||||
*/
|
||||
|
||||
void
|
||||
init_gnugo(float memory, unsigned int seed)
|
||||
{
|
||||
/* We need a fixed seed when initializing the Zobrist hashing to get
|
||||
* reproducable results.
|
||||
* FIXME: Test the quality of the seed.
|
||||
*/
|
||||
set_random_seed(HASH_RANDOM_SEED);
|
||||
reading_cache_init(memory * 1024 * 1024);
|
||||
set_random_seed(seed);
|
||||
persistent_cache_init();
|
||||
clear_board();
|
||||
|
||||
transformation_init();
|
||||
dfa_match_init();
|
||||
choose_mc_patterns(NULL);
|
||||
|
||||
clear_approxlib_cache();
|
||||
clear_accuratelib_cache();
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
/* Check whether we can accept a certain boardsize. Set out to NULL to
|
||||
* suppress informative messages. Return 1 for an acceptable
|
||||
* boardsize, 0 otherwise.
|
||||
*/
|
||||
int check_boardsize(int boardsize, FILE *out)
|
||||
{
|
||||
int max_board = MAX_BOARD;
|
||||
if (use_monte_carlo_genmove && max_board > 9)
|
||||
max_board = 9;
|
||||
|
||||
if (boardsize < MIN_BOARD || boardsize > max_board) {
|
||||
if (out) {
|
||||
fprintf(out, "Unsupported board size: %d. ", boardsize);
|
||||
if (boardsize < MIN_BOARD)
|
||||
fprintf(out, "Min size is %d.\n", MIN_BOARD);
|
||||
else {
|
||||
fprintf(out, "Max size is %d", max_board);
|
||||
if (max_board < MAX_BOARD)
|
||||
fprintf(out, " (%d without --monte-carlo)", MAX_BOARD);
|
||||
fprintf(out, ".\n");
|
||||
}
|
||||
fprintf(out, "Try `gnugo --help' for more information.\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the board.
|
||||
*/
|
||||
void
|
||||
gnugo_clear_board(int boardsize)
|
||||
{
|
||||
board_size = boardsize;
|
||||
clear_board();
|
||||
init_timers();
|
||||
#if 0
|
||||
if (metamachine && oracle_exists)
|
||||
oracle_clear_board(boardsize);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Play a move and start the clock */
|
||||
|
||||
void
|
||||
gnugo_play_move(int move, int color)
|
||||
{
|
||||
#if ORACLE
|
||||
if (oracle_exists)
|
||||
oracle_play_move(move, color);
|
||||
else
|
||||
play_move(move, color);
|
||||
#else
|
||||
play_move(move, color);
|
||||
#endif
|
||||
clock_push_button(color);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Perform the moves and place the stones from the SGF node on the
|
||||
* board. Return the color of the player whose turn it is to move.
|
||||
*/
|
||||
|
||||
int
|
||||
gnugo_play_sgfnode(SGFNode *node, int to_move)
|
||||
{
|
||||
SGFProperty *prop;
|
||||
|
||||
for (prop = node->props; prop; prop = prop->next) {
|
||||
switch (prop->name) {
|
||||
case SGFAB:
|
||||
/* A black stone. */
|
||||
add_stone(get_sgfmove(prop), BLACK);
|
||||
break;
|
||||
|
||||
case SGFAW:
|
||||
/* A white stone. */
|
||||
add_stone(get_sgfmove(prop), WHITE);
|
||||
break;
|
||||
|
||||
case SGFPL:
|
||||
/* Player property - who is next to move? */
|
||||
if (prop->value[0] == 'w' || prop->value[0] == 'W')
|
||||
to_move = WHITE;
|
||||
else
|
||||
to_move = BLACK;
|
||||
break;
|
||||
|
||||
case SGFW:
|
||||
case SGFB:
|
||||
/* An ordinary move. */
|
||||
to_move = (prop->name == SGFW) ? WHITE : BLACK;
|
||||
gnugo_play_move(get_sgfmove(prop), to_move);
|
||||
to_move = OTHER_COLOR(to_move);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return to_move;
|
||||
}
|
||||
|
||||
|
||||
/* Interface to place_fixed_handicap. Sets up handicap stones and
|
||||
* updates the sgf file.
|
||||
*/
|
||||
int
|
||||
gnugo_sethand(int desired_handicap, SGFNode *node)
|
||||
{
|
||||
place_fixed_handicap(desired_handicap);
|
||||
sgffile_recordboard(node);
|
||||
return handicap;
|
||||
}
|
||||
|
||||
|
||||
/* Put upper and lower score estimates into *upper, *lower and
|
||||
* return the average. A positive score favors white. In computing
|
||||
* the upper bound, CRITICAL dragons are awarded to white; in
|
||||
* computing the lower bound, they are awarded to black.
|
||||
*/
|
||||
|
||||
float
|
||||
gnugo_estimate_score(float *upper, float *lower)
|
||||
{
|
||||
silent_examine_position(EXAMINE_DRAGONS);
|
||||
if (upper != NULL)
|
||||
*upper = white_score;
|
||||
if (lower != NULL)
|
||||
*lower = black_score;
|
||||
return ((white_score + black_score) / 2.0);
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================ */
|
||||
/* Gameinfo */
|
||||
/* ================================================================ */
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the structure.
|
||||
*/
|
||||
|
||||
void
|
||||
gameinfo_clear(Gameinfo *gameinfo)
|
||||
{
|
||||
gnugo_clear_board(board_size);
|
||||
gameinfo->handicap = 0;
|
||||
gameinfo->to_move = BLACK;
|
||||
sgftree_clear(&gameinfo->game_record);
|
||||
|
||||
/* Info relevant to the computer player. */
|
||||
gameinfo->computer_player = WHITE; /* Make an assumption. */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Print a gameinfo.
|
||||
*/
|
||||
|
||||
void
|
||||
gameinfo_print(Gameinfo *gameinfo)
|
||||
{
|
||||
printf("Board Size: %d\n", board_size);
|
||||
printf("Handicap %d\n", gameinfo->handicap);
|
||||
printf("Komi: %.1f\n", komi);
|
||||
printf("Move Number: %d\n", movenum);
|
||||
printf("To Move: %s\n", color_to_string(gameinfo->to_move));
|
||||
|
||||
printf("Computer player: ");
|
||||
if (gameinfo->computer_player == WHITE)
|
||||
printf("White\n");
|
||||
else if (gameinfo->computer_player == BLACK)
|
||||
printf("Black\n");
|
||||
else if (gameinfo->computer_player == EMPTY)
|
||||
printf("Both (solo)\n");
|
||||
else
|
||||
printf("Nobody\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Play the moves in an SGF tree. Walk the main variation, actioning
|
||||
* the properties into the playing board.
|
||||
*
|
||||
* Returns the color of the next move to be made. The returned color
|
||||
* being EMPTY signals a failure to load the file.
|
||||
*
|
||||
* Head is an sgf tree.
|
||||
* Untilstr is an optional string of the form either 'L12' or '120'
|
||||
* which tells it to stop playing at that move or move-number.
|
||||
* When debugging, this is the location of the move being examined.
|
||||
*/
|
||||
|
||||
int
|
||||
gameinfo_play_sgftree_rot(Gameinfo *gameinfo, SGFTree *tree,
|
||||
const char *untilstr, int orientation)
|
||||
{
|
||||
int bs;
|
||||
int next = BLACK;
|
||||
int untilmove = -1; /* Neither a valid move nor pass. */
|
||||
int until = 9999;
|
||||
|
||||
if (!sgfGetIntProperty(tree->root, "SZ", &bs))
|
||||
bs = 19;
|
||||
|
||||
if (!check_boardsize(bs, stderr))
|
||||
return EMPTY;
|
||||
|
||||
handicap = 0;
|
||||
if (sgfGetIntProperty(tree->root, "HA", &handicap) && handicap > 1)
|
||||
next = WHITE;
|
||||
gameinfo->handicap = handicap;
|
||||
|
||||
if (handicap > bs * bs - 1 || handicap < 0) {
|
||||
gprintf(" Handicap HA[%d] is unreasonable.\n Modify SGF file.\n",
|
||||
handicap);
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
gnugo_clear_board(bs);
|
||||
|
||||
if (!sgfGetFloatProperty(tree->root, "KM", &komi)) {
|
||||
if (gameinfo->handicap == 0)
|
||||
komi = 5.5;
|
||||
else
|
||||
komi = 0.5;
|
||||
}
|
||||
|
||||
/* Now we can safely parse the until string (which depends on board size). */
|
||||
if (untilstr) {
|
||||
if (*untilstr > '0' && *untilstr <= '9') {
|
||||
until = atoi(untilstr);
|
||||
DEBUG(DEBUG_LOADSGF, "Loading until move %d\n", until);
|
||||
}
|
||||
else {
|
||||
untilmove = string_to_location(board_size, untilstr);
|
||||
DEBUG(DEBUG_LOADSGF, "Loading until move at %1m\n", untilmove);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, we iterate over all the properties of all the
|
||||
* nodes, actioning them. We follow only the 'child' pointers,
|
||||
* as we have no interest in variations.
|
||||
*
|
||||
* The sgf routines map AB[aa][bb][cc] into AB[aa]AB[bb]AB[cc]
|
||||
*/
|
||||
for (tree->lastnode = NULL; sgftreeForward(tree);) {
|
||||
SGFProperty *prop;
|
||||
int move;
|
||||
|
||||
for (prop = tree->lastnode->props; prop; prop = prop->next) {
|
||||
DEBUG(DEBUG_LOADSGF, "%c%c[%s]\n",
|
||||
prop->name & 0xff, (prop->name >> 8), prop->value);
|
||||
switch (prop->name) {
|
||||
case SGFAB:
|
||||
case SGFAW:
|
||||
/* Generally the last move is unknown when the AB or AW
|
||||
* properties are encountered. These are used to set up
|
||||
* a board position (diagram) or to place handicap stones
|
||||
* without reference to the order in which the stones are
|
||||
* placed on the board.
|
||||
*/
|
||||
move = rotate1(get_sgfmove(prop), orientation);
|
||||
if (board[move] != EMPTY)
|
||||
gprintf("Illegal SGF! attempt to add a stone at occupied point %1m\n",
|
||||
move);
|
||||
else
|
||||
add_stone(move, prop->name == SGFAB ? BLACK : WHITE);
|
||||
break;
|
||||
|
||||
case SGFPL:
|
||||
/* Due to a bad comment in the SGF FF3 definition (in the
|
||||
* "Alphabetical list of properties" section) some
|
||||
* applications encode the colors with 1 for black and 2 for
|
||||
* white.
|
||||
*/
|
||||
if (prop->value[0] == 'w'
|
||||
|| prop->value[0] == 'W'
|
||||
|| prop->value[0] == '2')
|
||||
next = WHITE;
|
||||
else
|
||||
next = BLACK;
|
||||
/* following really should not be needed for proper sgf file */
|
||||
if (stones_on_board(GRAY) == 0 && next == WHITE) {
|
||||
place_fixed_handicap(gameinfo->handicap);
|
||||
sgfOverwritePropertyInt(tree->root, "HA", handicap);
|
||||
}
|
||||
break;
|
||||
|
||||
case SGFW:
|
||||
case SGFB:
|
||||
next = prop->name == SGFW ? WHITE : BLACK;
|
||||
/* following really should not be needed for proper sgf file */
|
||||
if (stones_on_board(GRAY) == 0 && next == WHITE) {
|
||||
place_fixed_handicap(gameinfo->handicap);
|
||||
sgfOverwritePropertyInt(tree->root, "HA", handicap);
|
||||
}
|
||||
|
||||
move = get_sgfmove(prop);
|
||||
if (move == untilmove || movenum == until - 1) {
|
||||
gameinfo->to_move = next;
|
||||
/* go back so that variant will be added to the proper node */
|
||||
sgftreeBack(tree);
|
||||
return next;
|
||||
}
|
||||
|
||||
move = rotate1(move, orientation);
|
||||
if (move == PASS_MOVE || board[move] == EMPTY) {
|
||||
gnugo_play_move(move, next);
|
||||
next = OTHER_COLOR(next);
|
||||
}
|
||||
else {
|
||||
gprintf("WARNING: Move off board or on occupied position found in sgf-file.\n");
|
||||
gprintf("Move at %1m ignored, trying to proceed.\n", move);
|
||||
gameinfo->to_move = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SGFIL:
|
||||
/* The IL property is not a standard SGF property but
|
||||
* is used by GNU Go to mark illegal moves. If a move
|
||||
* is found marked with the IL property which is a ko
|
||||
* capture then that ko capture is deemed illegal and
|
||||
* (board_ko_i, board_ko_j) is set to the location of
|
||||
* the ko.
|
||||
*/
|
||||
move = rotate1(get_sgfmove(prop), orientation);
|
||||
|
||||
if (board_size > 1)
|
||||
{
|
||||
int move_color;
|
||||
|
||||
if (ON_BOARD(NORTH(move)))
|
||||
move_color = OTHER_COLOR(board[NORTH(move)]);
|
||||
else
|
||||
move_color = OTHER_COLOR(board[SOUTH(move)]);
|
||||
if (is_ko(move, move_color, NULL))
|
||||
board_ko_pos = move;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameinfo->to_move = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
/* Same as previous function, using standard orientation */
|
||||
|
||||
int
|
||||
gameinfo_play_sgftree(Gameinfo *gameinfo, SGFTree *tree, const char *untilstr)
|
||||
{
|
||||
return gameinfo_play_sgftree_rot(gameinfo, tree, untilstr, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
1065
gnugo/engine/liberty.h
Normal file
1065
gnugo/engine/liberty.h
Normal file
File diff suppressed because it is too large
Load Diff
1137
gnugo/engine/matchpat.c
Normal file
1137
gnugo/engine/matchpat.c
Normal file
File diff suppressed because it is too large
Load Diff
2262
gnugo/engine/montecarlo.c
Normal file
2262
gnugo/engine/montecarlo.c
Normal file
File diff suppressed because it is too large
Load Diff
2070
gnugo/engine/move_reasons.c
Normal file
2070
gnugo/engine/move_reasons.c
Normal file
File diff suppressed because it is too large
Load Diff
220
gnugo/engine/move_reasons.h
Normal file
220
gnugo/engine/move_reasons.h
Normal file
@ -0,0 +1,220 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* values for move_reason.type */
|
||||
#define THREAT_BIT 1
|
||||
|
||||
/* Only use even values for non-threat move reasons! */
|
||||
#define ATTACK_MOVE 2
|
||||
#define ATTACK_MOVE_GOOD_KO 4
|
||||
#define ATTACK_MOVE_BAD_KO 6
|
||||
#define ATTACK_THREAT (ATTACK_MOVE | THREAT_BIT)
|
||||
#define DEFEND_MOVE 8
|
||||
#define DEFEND_MOVE_GOOD_KO 10
|
||||
#define DEFEND_MOVE_BAD_KO 12
|
||||
#define DEFEND_THREAT (DEFEND_MOVE | THREAT_BIT)
|
||||
|
||||
#define CONNECT_MOVE 14
|
||||
#define CUT_MOVE 16
|
||||
|
||||
#define SEMEAI_MOVE 18
|
||||
#define SEMEAI_THREAT (SEMEAI_MOVE | THREAT_BIT)
|
||||
|
||||
#define EXPAND_TERRITORY_MOVE 20
|
||||
#define EXPAND_MOYO_MOVE 22
|
||||
#define INVASION_MOVE 24
|
||||
|
||||
#define OWL_ATTACK_MOVE 26
|
||||
#define OWL_ATTACK_MOVE_GOOD_KO 28
|
||||
#define OWL_ATTACK_MOVE_BAD_KO 30
|
||||
#define OWL_ATTACK_THREAT (OWL_ATTACK_MOVE | THREAT_BIT)
|
||||
#define OWL_DEFEND_MOVE 32
|
||||
#define OWL_DEFEND_MOVE_GOOD_KO 34
|
||||
#define OWL_DEFEND_MOVE_BAD_KO 36
|
||||
#define OWL_DEFEND_THREAT (OWL_DEFEND_MOVE | THREAT_BIT)
|
||||
#define OWL_PREVENT_THREAT 38
|
||||
#define UNCERTAIN_OWL_ATTACK 40
|
||||
#define UNCERTAIN_OWL_DEFENSE 42
|
||||
#define STRATEGIC_ATTACK_MOVE 44
|
||||
#define STRATEGIC_DEFEND_MOVE 46
|
||||
|
||||
#define MY_ATARI_ATARI_MOVE 50
|
||||
#define YOUR_ATARI_ATARI_MOVE 52
|
||||
#define VITAL_EYE_MOVE 54
|
||||
|
||||
#define OWL_ATTACK_MOVE_GAIN 60
|
||||
#define OWL_DEFEND_MOVE_LOSS 62
|
||||
#define POTENTIAL_SEMEAI_ATTACK 64
|
||||
#define POTENTIAL_SEMEAI_DEFENSE 66
|
||||
|
||||
#define ANTISUJI_MOVE 70
|
||||
|
||||
#define EITHER_MOVE 100
|
||||
#define ALL_MOVE 102
|
||||
|
||||
|
||||
/* Bitmap values for move_reason.status */
|
||||
#define ACTIVE 0
|
||||
#define TERRITORY_REDUNDANT 1
|
||||
#define STRATEGICALLY_REDUNDANT 2
|
||||
#define REDUNDANT (TERRITORY_REDUNDANT | STRATEGICALLY_REDUNDANT)
|
||||
#define SECONDARY 4
|
||||
|
||||
#define MAX_REASONS 120
|
||||
|
||||
#define MAX_TRACE_LENGTH 160
|
||||
|
||||
#define HUGE_MOVE_VALUE 10.0*MAX_BOARD*MAX_BOARD
|
||||
|
||||
struct move_reason {
|
||||
int type; /* e.g. attack, defend, or connect */
|
||||
int what; /* pointer into list of strings, list of pair of dragons,
|
||||
or similar */
|
||||
int status; /* This is a bitmap to mark redundant or secondary
|
||||
move reasons. */
|
||||
};
|
||||
|
||||
struct move_data {
|
||||
float value; /* total comparison value, computed at the very end */
|
||||
float final_value; /* value after point redistribution. */
|
||||
float additional_ko_value; /* Additional threat value if ko fight going on.*/
|
||||
|
||||
float territorial_value; /* Value in terms of actual profit. */
|
||||
float strategical_value; /* Value with respect to strength, weakness, and
|
||||
safety of all groups on the board. */
|
||||
|
||||
float maxpos_shape; /* Maximal positive contribution to shape */
|
||||
float maxneg_shape; /* Maximal negative contribution to shape */
|
||||
int numpos_shape; /* Number of positive contributions to shape */
|
||||
int numneg_shape; /* Number of negative contributions to shape */
|
||||
|
||||
float followup_value; /* Value of followup move (our sente). */
|
||||
float influence_followup_value; /* Followup value of move as reported by
|
||||
experimental influence. */
|
||||
float reverse_followup_value; /* Value of opponents followup move
|
||||
(reverse sente). */
|
||||
float secondary_value; /* Secondary move value. */
|
||||
float min_value; /* Minimum allowed value for the move. */
|
||||
float max_value; /* Maximum allowed value for the move. */
|
||||
float min_territory; /* Minimum territorial value. */
|
||||
float max_territory; /* Maximum territorial value. */
|
||||
float randomness_scaling; /* Increase to randomize this move. */
|
||||
|
||||
int reason[MAX_REASONS]; /* List of reasons for a move. */
|
||||
int move_safety; /* Whether the move seems safe. */
|
||||
int worthwhile_threat; /* Play this move as a pure threat. */
|
||||
float random_number; /* Random number connected to this move. */
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Some sizes.
|
||||
*
|
||||
* FIXME: Many of these could be optimized more for size (e.g. MAX_EYES)
|
||||
*/
|
||||
|
||||
#define MAX_MOVE_REASONS 1000
|
||||
#define MAX_WORMS 2*MAX_BOARD*MAX_BOARD/3
|
||||
#define MAX_DRAGONS MAX_WORMS
|
||||
#define MAX_CONNECTIONS 4*MAX_WORMS
|
||||
#define MAX_POTENTIAL_SEMEAI 50
|
||||
#define MAX_EYES MAX_BOARD*MAX_BOARD/2
|
||||
#define MAX_LUNCHES MAX_WORMS
|
||||
#define MAX_EITHER 100
|
||||
#define MAX_ALL 100
|
||||
#define MAX_ATTACK_THREATS 6
|
||||
|
||||
|
||||
extern struct move_data move[BOARDMAX];
|
||||
extern struct move_reason move_reasons[MAX_MOVE_REASONS];
|
||||
extern int next_reason;
|
||||
|
||||
/* Connections */
|
||||
extern int conn_worm1[MAX_CONNECTIONS];
|
||||
extern int conn_worm2[MAX_CONNECTIONS];
|
||||
extern int next_connection;
|
||||
|
||||
extern int semeai_target1[MAX_POTENTIAL_SEMEAI];
|
||||
extern int semeai_target2[MAX_POTENTIAL_SEMEAI];
|
||||
|
||||
/* Unordered sets (currently pairs) of move reasons / targets */
|
||||
typedef struct {
|
||||
int reason1;
|
||||
int what1;
|
||||
int reason2;
|
||||
int what2;
|
||||
} Reason_set;
|
||||
extern Reason_set either_data[MAX_EITHER];
|
||||
extern int next_either;
|
||||
extern Reason_set all_data[MAX_ALL];
|
||||
extern int next_all;
|
||||
|
||||
/* Eye shapes */
|
||||
extern int eyes[MAX_EYES];
|
||||
extern int eyecolor[MAX_EYES];
|
||||
extern int next_eye;
|
||||
|
||||
/* Lunches */
|
||||
extern int lunch_dragon[MAX_LUNCHES]; /* eater */
|
||||
extern int lunch_worm[MAX_LUNCHES]; /* food */
|
||||
extern int next_lunch;
|
||||
|
||||
/* Point redistribution */
|
||||
extern int replacement_map[BOARDMAX];
|
||||
|
||||
/* The color for which we are evaluating moves. */
|
||||
extern int current_color;
|
||||
|
||||
int find_worm(int str);
|
||||
int find_dragon(int str);
|
||||
|
||||
int move_reason_known(int pos, int type, int what);
|
||||
int attack_move_reason_known(int pos, int what);
|
||||
int defense_move_reason_known(int pos, int what);
|
||||
int owl_attack_move_reason_known(int pos, int what);
|
||||
int owl_defense_move_reason_known(int pos, int what);
|
||||
int owl_move_reason_known(int pos, int what);
|
||||
int semeai_move_reason_known(int pos, int what);
|
||||
int get_biggest_owl_target(int pos);
|
||||
int is_antisuji_move(int pos);
|
||||
|
||||
void discard_redundant_move_reasons(int pos);
|
||||
|
||||
void mark_changed_dragon(int pos, int color, int affected, int affected2,
|
||||
int move_reason_type,
|
||||
signed char safe_stones[BOARDMAX],
|
||||
float strength[BOARDMAX], float *effective_size);
|
||||
void mark_changed_string(int affected, signed char changed_stones[BOARDMAX],
|
||||
float strength[BOARDMAX], signed char new_status);
|
||||
int adjacent_to_nondead_stone(int pos, int color);
|
||||
|
||||
int find_connection(int worm1, int worm2);
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
||||
|
143
gnugo/engine/movelist.c
Normal file
143
gnugo/engine/movelist.c
Normal file
@ -0,0 +1,143 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
|
||||
|
||||
static void movelist_sort_points(int max_points, int points[], int codes[]);
|
||||
static void swap_points_and_codes(int points[], int codes[], int m, int n);
|
||||
|
||||
|
||||
/* Return the code for the move if it is known.
|
||||
*/
|
||||
int
|
||||
movelist_move_known(int move, int max_points, int points[], int codes[])
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0; k < max_points; k++) {
|
||||
if (codes[k] == 0)
|
||||
return 0;
|
||||
if (points[k] == move)
|
||||
return codes[k];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function does the real work for change_attack(),
|
||||
* change_defense(), change_attack_threat(), and
|
||||
* change_defense_threat().
|
||||
*/
|
||||
|
||||
void
|
||||
movelist_change_point(int move, int code, int max_points,
|
||||
int points[], int codes[])
|
||||
{
|
||||
int k;
|
||||
|
||||
/* First see if we already know about this point. */
|
||||
for (k = 0; k < max_points; k++)
|
||||
if (points[k] == move)
|
||||
break;
|
||||
|
||||
/* Yes, we do. */
|
||||
if (k < max_points) {
|
||||
if (codes[k] <= code)
|
||||
return; /* Old news. */
|
||||
|
||||
codes[k] = code;
|
||||
movelist_sort_points(max_points, points, codes);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This tactical point is new to us. */
|
||||
if (code > codes[max_points - 1]) {
|
||||
points[max_points - 1] = move;
|
||||
codes[max_points - 1] = code;
|
||||
movelist_sort_points(max_points, points, codes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Sort the tactical points so we have it sorted in falling order on
|
||||
* the code values.
|
||||
*
|
||||
* We use shaker sort because we prefer a stable sort and in all use
|
||||
* cases we can expect it to suffice with one turn through the outer
|
||||
* loop.
|
||||
*/
|
||||
|
||||
static void
|
||||
movelist_sort_points(int max_points, int points[], int codes[])
|
||||
{
|
||||
int start = 0;
|
||||
int end = max_points - 1;
|
||||
int new_start;
|
||||
int new_end;
|
||||
int k;
|
||||
|
||||
while (start < end) {
|
||||
new_start = end;
|
||||
for (k = end; k > start; k--)
|
||||
if (codes[k] > codes[k-1]) {
|
||||
swap_points_and_codes(points, codes, k, k-1);
|
||||
new_start = k;
|
||||
}
|
||||
start = new_start;
|
||||
new_end = start;
|
||||
for (k = start; k < end - 1; k++)
|
||||
if (codes[k] < codes[k+1]) {
|
||||
swap_points_and_codes(points, codes, k, k+1);
|
||||
new_end = k;
|
||||
}
|
||||
end = new_end;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
swap_points_and_codes(int points[], int codes[], int m, int n)
|
||||
{
|
||||
int tmp = points[m];
|
||||
points[m] = points[n];
|
||||
points[n] = tmp;
|
||||
tmp = codes[m];
|
||||
codes[m] = codes[n];
|
||||
codes[n] = tmp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
3761
gnugo/engine/optics.c
Normal file
3761
gnugo/engine/optics.c
Normal file
File diff suppressed because it is too large
Load Diff
474
gnugo/engine/oracle.c
Normal file
474
gnugo/engine/oracle.c
Normal file
@ -0,0 +1,474 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
/* The functions in this file implement a mechanism whereby
|
||||
* GNU Go can fork a second gnugo process, called the oracle.
|
||||
* The two processes communicate by means of the GTP.
|
||||
* The functions oracle_trymove() and oracle_popgo() call
|
||||
* trymove and popgo in the primary gnugo processes but
|
||||
* actually play and undo the move in the oracle. This
|
||||
* the oracle can be queried for information which is
|
||||
* normally only available at the top level.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#if ORACLE
|
||||
|
||||
#include "gnugo.h"
|
||||
#include "liberty.h"
|
||||
#include "patterns.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define USE_POSIX 1
|
||||
|
||||
FILE *to_gnugo_stream, *from_gnugo_stream;
|
||||
char gnugo_line[128];
|
||||
int gnugo_line_length;
|
||||
|
||||
int pfd_a[2];
|
||||
int pfd_b[2];
|
||||
|
||||
#define TELL_ORACLE(x, args...) do { \
|
||||
if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, x, ##args); \
|
||||
if (fprintf(to_gnugo_stream, x, ##args) < 0) \
|
||||
error("can't write command in to_gnugo_stream"); \
|
||||
fflush(to_gnugo_stream); \
|
||||
} while (0)
|
||||
|
||||
#define ASK_ORACLE do { \
|
||||
gnugo_line_length = 0; \
|
||||
while (gnugo_line_length != 1) { \
|
||||
if (!fgets(gnugo_line, 128, from_gnugo_stream)) \
|
||||
error("can't get response"); \
|
||||
gnugo_line_length = strlen(gnugo_line); \
|
||||
if (debug & DEBUG_ORACLE_STREAM) \
|
||||
fprintf(stderr, gnugo_line); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define MAX_ORACLE_MOVES 10
|
||||
|
||||
struct oracle_move_data {
|
||||
int pos; /* move coordinate */
|
||||
int color; /* color to play */
|
||||
int value; /* value */
|
||||
int ab_value; /* alpha-beta value */
|
||||
const char *reason; /* why this move */
|
||||
};
|
||||
|
||||
static void oracle_callback(int anchor, int color, struct pattern *pattern,
|
||||
int ll, void *data);
|
||||
static void oracle_add_move(struct oracle_move_data *moves,
|
||||
int this_move, int this_value,
|
||||
const char *this_reason);
|
||||
void do_consult_oracle(int color);
|
||||
void error(const char *msg);
|
||||
static int oracle_trymove(int pos, int color, const char *message, int str,
|
||||
int komaster, int kom_pos);
|
||||
static void oracle_popgo(void);
|
||||
static void tell_oracle(const char *fmt, ...);
|
||||
static void ask_oracle(void);
|
||||
static int search_width(void);
|
||||
|
||||
|
||||
/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
|
||||
* Primary Oracle Functions *
|
||||
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
|
||||
|
||||
/* Forks and attaches pipes to a new GNU Go process in gtp mode.
|
||||
* Loads the sgf file
|
||||
*/
|
||||
|
||||
void
|
||||
summon_oracle(void)
|
||||
{
|
||||
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");
|
||||
}
|
||||
oracle_exists = 1;
|
||||
/* Attach pipe a to to_gnugo_stream */
|
||||
to_gnugo_stream = (FILE *) fdopen(pfd_a[1], "w");
|
||||
/* Attach pipe b to from_gnugo_stream */
|
||||
from_gnugo_stream = (FILE *) fdopen(pfd_b[0], "r");
|
||||
}
|
||||
|
||||
/* load an sgf file */
|
||||
|
||||
void
|
||||
oracle_loadsgf(char *infilename, char *untilstring)
|
||||
{
|
||||
if (untilstring)
|
||||
TELL_ORACLE("loadsgf %s %s\n", infilename, untilstring);
|
||||
else
|
||||
TELL_ORACLE("loadsgf %s\n", infilename);
|
||||
ASK_ORACLE;
|
||||
fflush(to_gnugo_stream);
|
||||
gnugo_line_length = 0;
|
||||
}
|
||||
|
||||
/* Tell the oracle to go away. */
|
||||
|
||||
void
|
||||
dismiss_oracle(void)
|
||||
{
|
||||
if (oracle_exists)
|
||||
TELL_ORACLE("quit\n");
|
||||
oracle_exists = 0;
|
||||
}
|
||||
|
||||
/* complain and die! */
|
||||
|
||||
void
|
||||
error(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "oracle: %s\n", msg);
|
||||
abort();
|
||||
}
|
||||
|
||||
/* Call trymove in the primary process, and have the oracle actually
|
||||
* play the move.
|
||||
*/
|
||||
static int
|
||||
oracle_trymove(int pos, int color, const char *message, int str,
|
||||
int komaster, int kom_pos)
|
||||
{
|
||||
if (!trymove(pos, color, message, str))
|
||||
return 0;
|
||||
if (debug & DEBUG_ORACLE_STREAM)
|
||||
gfprintf(stderr, "%o%s %1m\n",
|
||||
color == BLACK ? "black" : "white", pos);
|
||||
gfprintf(to_gnugo_stream, "%o%s %1m\n",
|
||||
color == BLACK ? "black" : "white", pos);
|
||||
fflush(to_gnugo_stream);
|
||||
ASK_ORACLE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Undo the move.
|
||||
*/
|
||||
static void
|
||||
oracle_popgo(void)
|
||||
{
|
||||
popgo();
|
||||
TELL_ORACLE("undo\n");
|
||||
ASK_ORACLE;
|
||||
}
|
||||
|
||||
/* Play the move.
|
||||
*/
|
||||
|
||||
int
|
||||
oracle_play_move(int pos, int color)
|
||||
{
|
||||
play_move(pos, color);
|
||||
|
||||
if (debug & DEBUG_ORACLE_STREAM)
|
||||
gfprintf(stderr, "%o%s %1m\n",
|
||||
color == BLACK ? "black" : "white", pos);
|
||||
gfprintf(to_gnugo_stream, "%o%s %1m\n",
|
||||
color == BLACK ? "black" : "white", pos);
|
||||
fflush(to_gnugo_stream);
|
||||
ASK_ORACLE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* FIXME: Debugging needed. This variadic function doesn't work right if we
|
||||
* try to pass a const *char argument, like the infilename in
|
||||
* oracle_loadsgf. So for the time being we stick with the variadic macro
|
||||
* TELL_ORACLE.
|
||||
*/
|
||||
static void
|
||||
tell_oracle(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, fmt, ap);
|
||||
if (fprintf(to_gnugo_stream, fmt, ap) < 0)
|
||||
error("can't write command in to_gnugo_stream");
|
||||
fflush(to_gnugo_stream);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* FIXME: Debugging needed. This variadic function seems a little more
|
||||
* reliable than the corresponding variadic macro ASK_ORACLE.
|
||||
*/
|
||||
|
||||
static void
|
||||
ask_oracle(void)
|
||||
{
|
||||
int line_length = 0;
|
||||
char line[128];
|
||||
|
||||
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 (debug & DEBUG_ORACLE_STREAM) {
|
||||
fprintf(stderr, line);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* clear the oracle's board and set the boardsize */
|
||||
|
||||
void
|
||||
oracle_clear_board(int boardsize)
|
||||
{
|
||||
TELL_ORACLE("boardsize %d\n", boardsize);
|
||||
ASK_ORACLE;
|
||||
}
|
||||
|
||||
/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
|
||||
* Demonstration: a pattern matcher *
|
||||
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
|
||||
|
||||
/* Call the pattern matcher */
|
||||
|
||||
void
|
||||
consult_oracle(int color)
|
||||
{
|
||||
do_consult_oracle(color);
|
||||
}
|
||||
|
||||
void
|
||||
do_consult_oracle(int color)
|
||||
{
|
||||
struct oracle_move_data oracle_moves[MAX_ORACLE_MOVES];
|
||||
int k;
|
||||
|
||||
for (k = 0; k < MAX_ORACLE_MOVES; k++)
|
||||
oracle_moves[k].value = -1;
|
||||
|
||||
matchpat(oracle_callback, color, &oracle_db, oracle_moves, NULL);
|
||||
for (k = 0; k < MAX_ORACLE_MOVES; k++)
|
||||
if (oracle_moves[k].value > -1) {
|
||||
oracle_trymove(oracle_moves[k].pos, color, oracle_moves[k].reason,
|
||||
0, 0, NO_MOVE);
|
||||
do_consult_oracle(OTHER_COLOR(color));
|
||||
oracle_popgo();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
oracle_callback(int anchor, int color, struct pattern *pattern,
|
||||
int ll, void *data)
|
||||
{
|
||||
int this_move;
|
||||
struct oracle_move_data *moves = data;
|
||||
UNUSED(color);
|
||||
|
||||
this_move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
|
||||
if (within_search_area(this_move))
|
||||
oracle_add_move(moves, this_move, pattern->value, pattern->name);
|
||||
else
|
||||
gprintf("outside the area\n");
|
||||
}
|
||||
|
||||
/* Add a move to a list */
|
||||
|
||||
static void
|
||||
oracle_add_move(struct oracle_move_data moves[MAX_ORACLE_MOVES],
|
||||
int this_move, int this_value, const char *this_reason)
|
||||
{
|
||||
int k, l;
|
||||
|
||||
for (k = 0; k < MAX_ORACLE_MOVES; k++)
|
||||
if (moves[k].value == -1
|
||||
|| this_value >= moves[k].value)
|
||||
break;
|
||||
for (l = MAX_ORACLE_MOVES-1; l > k; l--) {
|
||||
moves[l].pos = moves[l-1].pos;
|
||||
moves[l].value = moves[l-1].value;
|
||||
moves[l].reason = moves[l-1].reason;
|
||||
}
|
||||
moves[k].pos = this_move;
|
||||
moves[k].value = this_value;
|
||||
moves[k].reason = this_reason;
|
||||
}
|
||||
|
||||
/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
|
||||
* Demonstration: metamachine *
|
||||
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
|
||||
|
||||
#define FIRST_LEVEL_MOVES 3
|
||||
#define SECOND_LEVEL_MOVES 2
|
||||
|
||||
static int
|
||||
do_metamachine_genmove(int color, int width, float *value);
|
||||
|
||||
int
|
||||
metamachine_genmove(int color, float *value, int limit_search)
|
||||
{
|
||||
int move;
|
||||
int pos;
|
||||
|
||||
if (limit_search) {
|
||||
TELL_ORACLE("limit_search 1\n");
|
||||
ASK_ORACLE;
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (within_search_area(pos)) {
|
||||
if (debug & DEBUG_ORACLE_STREAM)
|
||||
gfprintf(stderr, "%oset_search_limit %1m\n", pos);
|
||||
gfprintf(to_gnugo_stream, "%oset_search_limit %1m\n", pos);
|
||||
fflush(to_gnugo_stream);
|
||||
ASK_ORACLE;
|
||||
}
|
||||
}
|
||||
count_variations = 1;
|
||||
move = do_metamachine_genmove(color, search_width(), value);
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
return move;
|
||||
}
|
||||
|
||||
static int
|
||||
do_metamachine_genmove(int color, int width, float *value)
|
||||
{
|
||||
int k, moves_considered;
|
||||
float move_value[10];
|
||||
float best_score = 0.;
|
||||
int best_move = -1;
|
||||
char *token;
|
||||
int moves[10];
|
||||
float score[10];
|
||||
char delimiters[] = " \t\r\n";
|
||||
char buf[100];
|
||||
int i, j;
|
||||
|
||||
if (color == BLACK)
|
||||
TELL_ORACLE("top_moves_black\n");
|
||||
else
|
||||
TELL_ORACLE("top_moves_white\n");
|
||||
ask_oracle();
|
||||
token = strtok(gnugo_line, delimiters);
|
||||
for (k = 0; k < 10; k++) {
|
||||
moves[k] = PASS_MOVE;
|
||||
move_value[k] = 0.0;
|
||||
}
|
||||
moves_considered = width;
|
||||
if (verbose)
|
||||
dump_stack();
|
||||
for (k = 0; k < moves_considered; k++) {
|
||||
token = strtok(NULL, delimiters);
|
||||
if (!token)
|
||||
break;
|
||||
moves[k] = string_to_location(board_size, token);
|
||||
token = strtok(NULL, delimiters);
|
||||
if (!token)
|
||||
break;
|
||||
sscanf(token, "%f", move_value + k);
|
||||
TRACE("move %d: %1m valued %f\n", k, moves[k], move_value[k]);
|
||||
}
|
||||
/* if we left the loop early, k is the number of valid moves */
|
||||
moves_considered = k;
|
||||
if (moves_considered == 0) {
|
||||
*value = 0.0;
|
||||
return PASS_MOVE;
|
||||
}
|
||||
if (moves_considered == 1) {
|
||||
*value = 1.0;
|
||||
return moves[k];
|
||||
}
|
||||
for (k = 0; k < moves_considered; k++) {
|
||||
if (oracle_trymove(moves[k], color, "", 0, 0, NO_MOVE)) {
|
||||
int new_width = search_width();
|
||||
|
||||
if (new_width == 0) {
|
||||
TELL_ORACLE("experimental_score %s\n",
|
||||
color == BLACK ? "black" : "white");
|
||||
ask_oracle();
|
||||
sscanf(gnugo_line, "= %f", score + k);
|
||||
}
|
||||
else {
|
||||
do_metamachine_genmove(OTHER_COLOR(color), new_width, &score[k]);
|
||||
}
|
||||
if (verbose)
|
||||
dump_stack();
|
||||
TRACE("score: %f\n", color == WHITE ? score[k] : -score[k]);
|
||||
sprintf(buf, "value %.2f", color == WHITE ? score[k] : -score[k]);
|
||||
if (sgf_dumptree)
|
||||
sgftreeAddComment(sgf_dumptree, buf);
|
||||
oracle_popgo();
|
||||
}
|
||||
if (best_move == -1
|
||||
|| (color == WHITE && score[k] > best_score)
|
||||
|| (color == BLACK && score[k] < best_score)) {
|
||||
best_move = k;
|
||||
best_score = score[k];
|
||||
}
|
||||
}
|
||||
TRACE("best: %f at %1m\n", best_score, moves[best_move]);
|
||||
*value = score[best_move];
|
||||
return moves[best_move];
|
||||
}
|
||||
|
||||
/* decide how wide to search */
|
||||
|
||||
static int
|
||||
search_width(void)
|
||||
{
|
||||
if (stackp == 0)
|
||||
return 3;
|
||||
else if (stackp == 1)
|
||||
return 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
7182
gnugo/engine/owl.c
Normal file
7182
gnugo/engine/owl.c
Normal file
File diff suppressed because it is too large
Load Diff
1462
gnugo/engine/persistent.c
Normal file
1462
gnugo/engine/persistent.c
Normal file
File diff suppressed because it is too large
Load Diff
539
gnugo/engine/printutils.c
Normal file
539
gnugo/engine/printutils.c
Normal file
@ -0,0 +1,539 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "board.h"
|
||||
#include "hash.h"
|
||||
#include "gg_utils.h"
|
||||
#include "sgftree.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/*
|
||||
* This function underpins all the TRACE and DEBUG stuff.
|
||||
* It accepts %c, %d, %f, %s, and %x as usual. But it
|
||||
* also accepts %m, which takes TWO integers and writes a move.
|
||||
* Other accepted formats are
|
||||
* %H: Print a hashvalue.
|
||||
* %C: Print a color as a string.
|
||||
* Nasty bodge: %o at the start means outdent, i.e. cancel indent.
|
||||
*/
|
||||
|
||||
void
|
||||
vgprintf(FILE *outputfile, const char *fmt, va_list ap)
|
||||
{
|
||||
if (fmt[0] == '%' && fmt[1] == 'o')
|
||||
fmt += 2; /* cancel indent */
|
||||
else if (stackp > 0)
|
||||
fprintf(outputfile, "%.*s", stackp*2, " ");
|
||||
|
||||
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 'x':
|
||||
{
|
||||
unsigned int d = va_arg(ap, unsigned int);
|
||||
fprintf(outputfile, "%x", 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 '2':
|
||||
fmt++;
|
||||
if (*fmt != 'm' && *fmt != 'M') {
|
||||
fprintf(outputfile, "\n\nUnknown format string '2%c'\n", *fmt);
|
||||
break;
|
||||
}
|
||||
/* else fall through - 2 modifier on %m is default. */
|
||||
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 (!ON_BOARD2(m, n))
|
||||
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", board_size - m);
|
||||
else
|
||||
sprintf(movename+1, "%-2d", board_size - m);
|
||||
fputs(movename, outputfile);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '1':
|
||||
fmt++;
|
||||
if (*fmt != 'm' && *fmt != 'M') {
|
||||
fprintf(outputfile, "\n\nUnknown format string '1%c'\n", *fmt);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
char movename[4];
|
||||
int pos = va_arg(ap, int);
|
||||
int m = I(pos);
|
||||
int n = J(pos);
|
||||
if (pos == NO_MOVE)
|
||||
fputs("PASS", outputfile);
|
||||
else if (!ON_BOARD1(pos))
|
||||
fprintf(outputfile, "[%d]", pos);
|
||||
else {
|
||||
/* Generate the move name. */
|
||||
if (n < 8)
|
||||
movename[0] = n + 65;
|
||||
else
|
||||
movename[0] = n + 66;
|
||||
if (*fmt == 'm')
|
||||
sprintf(movename + 1, "%d", board_size - m);
|
||||
else
|
||||
sprintf(movename + 1, "%-2d", board_size - m);
|
||||
fputs(movename, outputfile);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'H':
|
||||
{
|
||||
unsigned long h = va_arg(ap, unsigned long);
|
||||
fprintf(outputfile, "%lx", h);
|
||||
break;
|
||||
}
|
||||
case 'C':
|
||||
{
|
||||
int color = va_arg(ap, int);
|
||||
fputs(color_to_string(color), outputfile);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
fprintf(outputfile, "\n\nUnknown format character '%c'\n", *fmt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
putc(*fmt, outputfile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* required wrapper around vgprintf, writes to outfile.
|
||||
*/
|
||||
|
||||
void
|
||||
gfprintf(FILE *outfile, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vgprintf(outfile, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* required wrapper around vgprintf, writes to stderr.
|
||||
* Always returns 1 to allow use in short-circuit logical expressions.
|
||||
*/
|
||||
|
||||
int
|
||||
gprintf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vgprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* required wrapper around vgprintf, in contrast to gprintf this one
|
||||
* writes to stdout.
|
||||
*/
|
||||
|
||||
void
|
||||
mprintf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vgprintf(stdout, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* This writes the move history information in sgf format to stderr.
|
||||
* This is only intended as a stand-alone debug tool for use in
|
||||
* abortgo(). Anywhere else you should use the normal sgf library.
|
||||
*/
|
||||
static void
|
||||
dump_board_sgf(void)
|
||||
{
|
||||
int pos;
|
||||
int initial_colors_found = EMPTY;
|
||||
int color;
|
||||
int k;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos))
|
||||
initial_colors_found |= initial_board[pos];
|
||||
|
||||
fprintf(stderr, "(;GM[1]FF[4]SZ[%d]KM[%.1f]HA[%d]GN[GNU Go %s stepped on a bug]\n",
|
||||
board_size, komi, handicap, gg_version());
|
||||
|
||||
for (color = WHITE; color <= BLACK; color++) {
|
||||
if (initial_colors_found & color) {
|
||||
fprintf(stderr, "A%s", color == WHITE ? "W" : "B");
|
||||
for (k = 0, pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (ON_BOARD(pos) && initial_board[pos] == color) {
|
||||
fprintf(stderr, "[%c%c]", 'a' + J(pos), 'a' + I(pos));
|
||||
k++;
|
||||
if (k % 16 == 0)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
if (k % 16 != 0)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (move_history_pointer > 0) {
|
||||
for (k = 0; k < move_history_pointer; k++) {
|
||||
fprintf(stderr, ";%s", move_history_color[k] == WHITE ? "W" : "B");
|
||||
if (move_history_pos[k] == PASS_MOVE)
|
||||
fprintf(stderr, "[]");
|
||||
else
|
||||
fprintf(stderr, "[%c%c]", 'a' + J(move_history_pos[k]),
|
||||
'a' + I(move_history_pos[k]));
|
||||
|
||||
if (k % 12 == 11)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
if (k % 12 != 0)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
fprintf(stderr, ")\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* A wrapper around abort() which shows the state variables at the time
|
||||
* of the problem. (pos) is typically a related move, or NO_MOVE.
|
||||
*/
|
||||
|
||||
void
|
||||
abortgo(const char *file, int line, const char *msg, int pos)
|
||||
{
|
||||
gprintf("%o\n\n***assertion failure:\n%s:%d - %s near %1m***\n\n",
|
||||
file, line, msg, pos);
|
||||
dump_stack();
|
||||
|
||||
/* Print the board at the top of the stack. */
|
||||
simple_showboard(stderr);
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
dump_board_sgf();
|
||||
|
||||
fprintf(stderr, "gnugo %s (seed %d): You stepped on a bug.\n",
|
||||
gg_version(), get_random_seed());
|
||||
if (board_size >= 9 && board_size <= 19) {
|
||||
fprintf(stderr, "\
|
||||
Please mail this message, including the debug output above, \
|
||||
to gnugo@gnu.org\n");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
fflush(stderr);
|
||||
fflush(stdout);
|
||||
|
||||
abort(); /* cause core dump */
|
||||
}
|
||||
|
||||
static const char *color_names[] = {
|
||||
COLOR_NAMES
|
||||
};
|
||||
|
||||
/* Convert a color value to a string. */
|
||||
const char *
|
||||
color_to_string(int color)
|
||||
{
|
||||
gg_assert(color < NUM_KOMASTER_STATES);
|
||||
return color_names[color];
|
||||
}
|
||||
|
||||
/* Convert a location to a string. */
|
||||
const char *
|
||||
location_to_string(int pos)
|
||||
{
|
||||
static int init = 0;
|
||||
static char buf[BOARDSIZE][5];
|
||||
if (!init) {
|
||||
int pos;
|
||||
for (pos = 0; pos < BOARDSIZE; pos++)
|
||||
location_to_buffer(pos, buf[pos]);
|
||||
init = 1;
|
||||
}
|
||||
ASSERT1(pos >= 0 && pos < BOARDSIZE, pos);
|
||||
return buf[pos];
|
||||
}
|
||||
|
||||
/* Convert a location to a string, writing to a buffer. */
|
||||
|
||||
void
|
||||
location_to_buffer(int pos, char *buf)
|
||||
{
|
||||
char *bufp = buf;
|
||||
int i = I(pos);
|
||||
int j = J(pos);
|
||||
|
||||
if (pos == NO_MOVE) {
|
||||
strcpy(buf, "Pass");
|
||||
return;
|
||||
}
|
||||
|
||||
*bufp = 'A'+j;
|
||||
if (*bufp >= 'I')
|
||||
(*bufp)++;
|
||||
bufp++;
|
||||
|
||||
i = board_size - i;
|
||||
if (i > 9)
|
||||
*bufp++ = '0' + i/10;
|
||||
*bufp++ = '0' + i%10;
|
||||
|
||||
*bufp = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Convert the string str to a 1D coordinate. Return NO_MOVE if invalid
|
||||
* string.
|
||||
*/
|
||||
|
||||
int
|
||||
string_to_location(int boardsize, const char *str)
|
||||
{
|
||||
int m, n;
|
||||
|
||||
if (*str == '\0')
|
||||
return NO_MOVE;
|
||||
|
||||
if (!isalpha((int) *str))
|
||||
return NO_MOVE;
|
||||
|
||||
n = tolower((int) *str) - 'a';
|
||||
if (tolower((int) *str) >= 'i')
|
||||
--n;
|
||||
if (n < 0 || n > boardsize - 1)
|
||||
return NO_MOVE;
|
||||
|
||||
if (!isdigit((int) *(str + 1)))
|
||||
return NO_MOVE;
|
||||
|
||||
m = boardsize - atoi(str + 1);
|
||||
if (m < 0 || m > boardsize - 1)
|
||||
return NO_MOVE;
|
||||
|
||||
return POS(m, n);
|
||||
}
|
||||
|
||||
|
||||
/* Some simple functions to draw an ASCII board. */
|
||||
|
||||
/* True if the coordinate is a hoshi point. */
|
||||
int
|
||||
is_hoshi_point(int m, int n)
|
||||
{
|
||||
int hoshi;
|
||||
int middle;
|
||||
|
||||
/* No hoshi points on these boards. */
|
||||
if (board_size == 2 || board_size == 4)
|
||||
return 0;
|
||||
|
||||
/* In the middle of a 3x3 board. */
|
||||
if (board_size == 3) {
|
||||
if (m == 1 && n == 1)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (board_size == 5) {
|
||||
if (m == 1 && (n == 1 || n == 3))
|
||||
return 1;
|
||||
if (m == 2 && n == 2)
|
||||
return 1;
|
||||
if (m == 3 && (n == 1 || n == 3))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 3-3 points are hoshi on sizes 7--11, 4-4 on larger. */
|
||||
if (board_size <= 11)
|
||||
hoshi = 2;
|
||||
else
|
||||
hoshi = 3;
|
||||
|
||||
/* Coordinate for midpoint. */
|
||||
middle = board_size/2;
|
||||
|
||||
/* Normalize the coordinates by mirroring to the lower numbers. */
|
||||
if (m >= middle)
|
||||
m = board_size - 1 - m;
|
||||
if (n >= middle)
|
||||
n = board_size - 1 - n;
|
||||
|
||||
/* Is this a corner hoshi? */
|
||||
if (m == hoshi && n == hoshi)
|
||||
return 1;
|
||||
|
||||
/* If even sized board, only hoshi points in the corner. */
|
||||
if (board_size%2 == 0)
|
||||
return 0;
|
||||
|
||||
/* Less than 12 in board size only middle point. */
|
||||
if (board_size < 12) {
|
||||
if (m == middle && n == middle)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Is this a midpoint hoshi? */
|
||||
if ((m == hoshi || m == middle)
|
||||
&& (n == hoshi || n == middle))
|
||||
return 1;
|
||||
|
||||
/* No more chances. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Print a line with coordinate letters above the board. */
|
||||
void
|
||||
draw_letter_coordinates(FILE *outfile)
|
||||
{
|
||||
int i;
|
||||
int ch;
|
||||
|
||||
fprintf(outfile, " ");
|
||||
for (i = 0, ch = 'A'; i < board_size; i++, ch++) {
|
||||
if (ch == 'I')
|
||||
ch++;
|
||||
fprintf(outfile, " %c", ch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Bare bones version of showboard(0). No fancy options, no hint of
|
||||
* color, and you can choose where to write it.
|
||||
*/
|
||||
void
|
||||
simple_showboard(FILE *outfile)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
draw_letter_coordinates(outfile);
|
||||
|
||||
for (i = 0; i < board_size; i++) {
|
||||
fprintf(outfile, "\n%2d", board_size - i);
|
||||
|
||||
for (j = 0; j < board_size; j++) {
|
||||
if (BOARD(i, j) == EMPTY)
|
||||
fprintf(outfile, " %c", is_hoshi_point(i, j) ? '+' : '.');
|
||||
else
|
||||
fprintf(outfile, " %c", BOARD(i, j) == BLACK ? 'X' : 'O');
|
||||
}
|
||||
|
||||
fprintf(outfile, " %d", board_size - i);
|
||||
|
||||
if ((board_size < 10 && i == board_size-2)
|
||||
|| (board_size >= 10 && i == 8))
|
||||
fprintf(outfile, " WHITE (O) has captured %d stones", black_captured);
|
||||
|
||||
if ((board_size < 10 && i == board_size-1)
|
||||
|| (board_size >= 10 && i == 9))
|
||||
fprintf(outfile, " BLACK (X) has captured %d stones", white_captured);
|
||||
}
|
||||
|
||||
fprintf(outfile, "\n");
|
||||
draw_letter_coordinates(outfile);
|
||||
}
|
||||
|
||||
|
||||
/* Adds square marks for each goal intersecion in the current sgf_dumptree.
|
||||
* This function cannot be in sgf/ as it has to understand the 1-D board.
|
||||
*/
|
||||
void
|
||||
mark_goal_in_sgf(signed char goal[BOARDMAX])
|
||||
{
|
||||
int pos;
|
||||
SGFNode *node;
|
||||
|
||||
if (!sgf_dumptree)
|
||||
return;
|
||||
node = sgftreeNodeCheck(sgf_dumptree);
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos) && goal[pos])
|
||||
sgfSquare(node, I(pos), J(pos));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
4350
gnugo/engine/readconnect.c
Normal file
4350
gnugo/engine/readconnect.c
Normal file
File diff suppressed because it is too large
Load Diff
91
gnugo/engine/readconnect.h
Normal file
91
gnugo/engine/readconnect.h
Normal file
@ -0,0 +1,91 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
struct heap_entry;
|
||||
struct connection_data;
|
||||
|
||||
/* Expensive functions that we try to evaluate as late as possible
|
||||
* when spreading connection distances.
|
||||
*/
|
||||
typedef void (*connection_helper_fn_ptr) (struct connection_data *conn,
|
||||
int color);
|
||||
|
||||
/* This heap contains a list of positions where we have delayed a
|
||||
* decision whether to "spread a connection distance". The function
|
||||
* helper() will be called when we finally need the decision. See
|
||||
* push_connection_heap_entry() for organization of the heap.
|
||||
*/
|
||||
struct heap_entry {
|
||||
int distance;
|
||||
int coming_from;
|
||||
int target;
|
||||
connection_helper_fn_ptr helper;
|
||||
};
|
||||
|
||||
/* Fixed-point arithmetic helper macros */
|
||||
#define FIXED_POINT_BASIS 10000
|
||||
#define FP(x) ((int) (0.5 + FIXED_POINT_BASIS * (x)))
|
||||
#define FIXED_TO_FLOAT(x) ((x) / (float) FIXED_POINT_BASIS)
|
||||
|
||||
#define HUGE_CONNECTION_DISTANCE FP(100.0)
|
||||
|
||||
struct connection_data {
|
||||
int distances[BOARDMAX];
|
||||
int deltas[BOARDMAX];
|
||||
int coming_from[BOARDMAX];
|
||||
int vulnerable1[BOARDMAX];
|
||||
int vulnerable2[BOARDMAX];
|
||||
int queue[BOARDMAX];
|
||||
int queue_start;
|
||||
int queue_end;
|
||||
|
||||
int heap_data_size;
|
||||
int heap_size;
|
||||
struct heap_entry heap_data[4 * BOARDMAX];
|
||||
struct heap_entry *heap[BOARDMAX];
|
||||
|
||||
int target;
|
||||
int cutoff_distance;
|
||||
int speculative;
|
||||
};
|
||||
|
||||
|
||||
void compute_connection_distances(int str, int target, int cutoff,
|
||||
struct connection_data *conn,
|
||||
int speculative);
|
||||
void init_connection_data(int color, const signed char goal[BOARDMAX],
|
||||
int target, int cutoff,
|
||||
struct connection_data *conn, int speculative);
|
||||
void spread_connection_distances(int color, struct connection_data *conn);
|
||||
void sort_connection_queue_tail(struct connection_data *conn);
|
||||
void expand_connection_queue(struct connection_data *conn);
|
||||
void print_connection_distances(struct connection_data *conn);
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
5739
gnugo/engine/reading.c
Normal file
5739
gnugo/engine/reading.c
Normal file
File diff suppressed because it is too large
Load Diff
617
gnugo/engine/semeai.c
Normal file
617
gnugo/engine/semeai.c
Normal file
@ -0,0 +1,617 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "liberty.h"
|
||||
|
||||
#define INFINITY 1000
|
||||
|
||||
static void find_moves_to_make_seki(void);
|
||||
static void update_status(int dr, enum dragon_status new_status,
|
||||
enum dragon_status new_safety);
|
||||
static int close_enough_for_proper_semeai(int apos, int bpos);
|
||||
|
||||
/* semeai() searches for pairs of dragons of opposite color which
|
||||
* have safety DEAD. If such a pair is found, owl_analyze_semeai is
|
||||
* called to read out which dragon will prevail in a semeai, and
|
||||
* whether a move now will make a difference in the outcome. The
|
||||
* dragon statuses are revised, and if a move now will make a
|
||||
* difference in the outcome this information is stored in
|
||||
* dragon_data2 and an owl reason is later generated by
|
||||
* semeai_move_reasons().
|
||||
*/
|
||||
|
||||
#define MAX_DRAGONS 50
|
||||
|
||||
void
|
||||
semeai()
|
||||
{
|
||||
int semeai_results_first[MAX_DRAGONS][MAX_DRAGONS];
|
||||
int semeai_results_second[MAX_DRAGONS][MAX_DRAGONS];
|
||||
int semeai_move[MAX_DRAGONS][MAX_DRAGONS];
|
||||
signed char semeai_certain[MAX_DRAGONS][MAX_DRAGONS];
|
||||
int d1, d2;
|
||||
int k;
|
||||
int num_dragons = number_of_dragons;
|
||||
|
||||
if (num_dragons > MAX_DRAGONS) {
|
||||
TRACE("Too many dragons!!! Semeai analysis disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (d1 = 0; d1 < num_dragons; d1++)
|
||||
for (d2 = 0; d2 < num_dragons; d2++) {
|
||||
semeai_results_first[d1][d2] = -1;
|
||||
semeai_results_second[d1][d2] = -1;
|
||||
}
|
||||
|
||||
for (d1 = 0; d1 < num_dragons; d1++)
|
||||
for (k = 0; k < dragon2[d1].neighbors; k++) {
|
||||
int apos = DRAGON(d1).origin;
|
||||
int bpos = DRAGON(dragon2[d1].adjacent[k]).origin;
|
||||
int result_certain;
|
||||
|
||||
d2 = dragon[bpos].id;
|
||||
|
||||
/* Look for semeais */
|
||||
|
||||
if (dragon[apos].color == dragon[bpos].color
|
||||
|| (dragon[apos].status != DEAD
|
||||
&& dragon[apos].status != CRITICAL)
|
||||
|| (dragon[bpos].status != DEAD
|
||||
&& dragon[bpos].status != CRITICAL))
|
||||
continue;
|
||||
|
||||
|
||||
/* Ignore inessential worms or dragons */
|
||||
|
||||
if (worm[apos].inessential
|
||||
|| DRAGON2(apos).safety == INESSENTIAL
|
||||
|| worm[bpos].inessential
|
||||
|| DRAGON2(bpos).safety == INESSENTIAL)
|
||||
continue;
|
||||
|
||||
/* Sometimes the dragons are considered neighbors but are too
|
||||
* distant to constitute a proper semeai, e.g. in nngs4:650, P2
|
||||
* vs. R3. Then the result of semeai reading may be meaningless
|
||||
* and can confuse the analysis. In order to avoid this we check
|
||||
* that the dragons either are directly adjacent or at least
|
||||
* have one common liberty.
|
||||
*/
|
||||
if (!close_enough_for_proper_semeai(apos, bpos))
|
||||
continue;
|
||||
|
||||
/* The array semeai_results_first[d1][d2] will contain the status
|
||||
* of d1 after the d1 d2 semeai, giving d1 the first move.
|
||||
* The array semeai_results_second[d1][d2] will contain the status
|
||||
* of d1 after the d1 d2 semeai, giving d2 the first move.
|
||||
*/
|
||||
|
||||
DEBUG(DEBUG_SEMEAI, "Considering semeai between %1m and %1m\n",
|
||||
apos, bpos);
|
||||
owl_analyze_semeai(apos, bpos,
|
||||
&(semeai_results_first[d1][d2]),
|
||||
&(semeai_results_second[d1][d2]),
|
||||
&(semeai_move[d1][d2]), 1, &result_certain);
|
||||
DEBUG(DEBUG_SEMEAI, "results if %s moves first: %s %s, %1m%s\n",
|
||||
board[apos] == BLACK ? "black" : "white",
|
||||
result_to_string(semeai_results_first[d1][d2]),
|
||||
result_to_string(semeai_results_second[d1][d2]),
|
||||
semeai_move[d1][d2], result_certain ? "" : " (uncertain)");
|
||||
semeai_certain[d1][d2] = result_certain;
|
||||
}
|
||||
|
||||
/* Look for dragons which lose all their semeais outright. The
|
||||
* winners in those semeais are considered safe and further semeais
|
||||
* they are involved in are disregarded. See semeai:81-86 and
|
||||
* nicklas5:1211 for examples of where this is useful.
|
||||
*
|
||||
* Note: To handle multiple simultaneous semeais properly we would
|
||||
* have to make simultaneous semeai reading. Lacking that we can
|
||||
* only get rough guesses of the correct status of the involved
|
||||
* dragons. This code is not guaranteed to be correct in all
|
||||
* situations but should usually be an improvement.
|
||||
*/
|
||||
for (d1 = 0; d1 < num_dragons; d1++) {
|
||||
int involved_in_semeai = 0;
|
||||
int all_lost = 1;
|
||||
for (d2 = 0; d2 < num_dragons; d2++) {
|
||||
if (semeai_results_first[d1][d2] != -1) {
|
||||
involved_in_semeai = 1;
|
||||
if (semeai_results_first[d1][d2] != 0) {
|
||||
all_lost = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (involved_in_semeai && all_lost) {
|
||||
/* Leave the status changes to the main loop below. Here we just
|
||||
* remove the presumably irrelevant semeai results.
|
||||
*/
|
||||
for (d2 = 0; d2 < num_dragons; d2++) {
|
||||
if (semeai_results_first[d1][d2] == 0) {
|
||||
int d3;
|
||||
for (d3 = 0; d3 < num_dragons; d3++) {
|
||||
if (semeai_results_second[d3][d2] > 0) {
|
||||
semeai_results_first[d3][d2] = -1;
|
||||
semeai_results_second[d3][d2] = -1;
|
||||
semeai_results_first[d2][d3] = -1;
|
||||
semeai_results_second[d2][d3] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (d1 = 0; d1 < num_dragons; d1++) {
|
||||
int semeais_found = 0;
|
||||
int best_defense = 0;
|
||||
int best_attack = 0;
|
||||
int defense_move = PASS_MOVE;
|
||||
int attack_move = PASS_MOVE;
|
||||
int defense_certain = -1;
|
||||
int attack_certain = -1;
|
||||
int semeai_attack_target = NO_MOVE;
|
||||
int semeai_defense_target = NO_MOVE;
|
||||
|
||||
for (d2 = 0; d2 < num_dragons; d2++) {
|
||||
if (semeai_results_first[d1][d2] == -1)
|
||||
continue;
|
||||
gg_assert(semeai_results_second[d1][d2] != -1);
|
||||
semeais_found++;
|
||||
|
||||
if (best_defense < semeai_results_first[d1][d2]
|
||||
|| (best_defense == semeai_results_first[d1][d2]
|
||||
&& defense_certain < semeai_certain[d1][d2])) {
|
||||
best_defense = semeai_results_first[d1][d2];
|
||||
defense_move = semeai_move[d1][d2];
|
||||
defense_certain = semeai_certain[d1][d2];
|
||||
gg_assert(board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d1].origin]));
|
||||
semeai_defense_target = dragon2[d2].origin;
|
||||
}
|
||||
if (best_attack < semeai_results_second[d2][d1]
|
||||
|| (best_attack == semeai_results_second[d2][d1]
|
||||
&& attack_certain < semeai_certain[d2][d1])) {
|
||||
best_attack = semeai_results_second[d2][d1];
|
||||
attack_move = semeai_move[d2][d1];
|
||||
attack_certain = semeai_certain[d2][d1];
|
||||
semeai_attack_target = dragon2[d2].origin;
|
||||
}
|
||||
}
|
||||
|
||||
if (semeais_found) {
|
||||
dragon2[d1].semeais = semeais_found;
|
||||
if (best_defense != 0 && best_attack != 0)
|
||||
update_status(DRAGON(d1).origin, CRITICAL, CRITICAL);
|
||||
else if (best_attack == 0 && attack_certain)
|
||||
update_status(DRAGON(d1).origin, ALIVE, ALIVE);
|
||||
dragon2[d1].semeai_defense_code = best_defense;
|
||||
dragon2[d1].semeai_defense_point = defense_move;
|
||||
dragon2[d1].semeai_defense_certain = defense_certain;
|
||||
ASSERT1(board[semeai_defense_target]
|
||||
== OTHER_COLOR(board[dragon2[d1].origin]),
|
||||
dragon2[d1].origin);
|
||||
dragon2[d1].semeai_defense_target = semeai_defense_target;
|
||||
dragon2[d1].semeai_attack_code = best_attack;
|
||||
dragon2[d1].semeai_attack_point = attack_move;
|
||||
dragon2[d1].semeai_attack_certain = attack_certain;
|
||||
dragon2[d1].semeai_attack_target = semeai_attack_target;
|
||||
}
|
||||
}
|
||||
find_moves_to_make_seki();
|
||||
}
|
||||
|
||||
/* Find moves turning supposed territory into seki. This is not
|
||||
* detected above since it either involves an ALIVE dragon adjacent to
|
||||
* a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded
|
||||
* and turned into a seki.
|
||||
*
|
||||
* Currently we only search for tactically critical strings with
|
||||
* dragon status dead, which are neighbors of only one opponent
|
||||
* dragon, which is alive. Through semeai analysis we then determine
|
||||
* whether such a string can in fact live in seki. Relevant testcases
|
||||
* include gunnar:42 and gifu03:2.
|
||||
*/
|
||||
static void
|
||||
find_moves_to_make_seki()
|
||||
{
|
||||
int str;
|
||||
int defend_move;
|
||||
int resulta, resultb;
|
||||
|
||||
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||||
if (IS_STONE(board[str]) && is_worm_origin(str, str)
|
||||
&& attack_and_defend(str, NULL, NULL, NULL, &defend_move)
|
||||
&& dragon[str].status == DEAD
|
||||
&& DRAGON2(str).hostile_neighbors == 1) {
|
||||
int k;
|
||||
int color = board[str];
|
||||
int opponent = NO_MOVE;
|
||||
int certain;
|
||||
struct eyevalue reduced_genus;
|
||||
|
||||
for (k = 0; k < DRAGON2(str).neighbors; k++) {
|
||||
opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
|
||||
if (board[opponent] != color)
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT1(opponent != NO_MOVE, opponent);
|
||||
|
||||
if (dragon[opponent].status != ALIVE)
|
||||
continue;
|
||||
|
||||
/* FIXME: These heuristics are used for optimization. We don't
|
||||
* want to call expensive semeai code if the opponent
|
||||
* dragon has more than one eye elsewhere. However, the
|
||||
* heuristics might still need improvement.
|
||||
*/
|
||||
compute_dragon_genus(opponent, &reduced_genus, str);
|
||||
|
||||
if (min_eyes(&reduced_genus) > 1
|
||||
|| DRAGON2(opponent).moyo_size > 10
|
||||
|| DRAGON2(opponent).moyo_territorial_value > 2.999
|
||||
|| DRAGON2(opponent).escape_route > 0
|
||||
|| DRAGON2(str).escape_route > 0)
|
||||
continue;
|
||||
|
||||
owl_analyze_semeai_after_move(defend_move, color, opponent, str,
|
||||
&resulta, &resultb, NULL, 1, &certain, 0);
|
||||
|
||||
if (resultb == WIN) {
|
||||
owl_analyze_semeai(str, opponent, &resultb, &resulta,
|
||||
&defend_move, 1, &certain);
|
||||
resulta = REVERSE_RESULT(resulta);
|
||||
resultb = REVERSE_RESULT(resultb);
|
||||
}
|
||||
|
||||
/* Do not trust uncertain results. In fact it should only take a
|
||||
* few nodes to determine the semeai result, if it is a proper
|
||||
* potential seki position.
|
||||
*/
|
||||
if (resultb != WIN && certain) {
|
||||
int d = dragon[str].id;
|
||||
DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n",
|
||||
defend_move, str, opponent);
|
||||
dragon2[d].semeais++;
|
||||
update_status(str, CRITICAL, CRITICAL);
|
||||
dragon2[d].semeai_defense_code = REVERSE_RESULT(resultb);
|
||||
dragon2[d].semeai_defense_point = defend_move;
|
||||
dragon2[d].semeai_defense_certain = certain;
|
||||
gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin]));
|
||||
dragon2[d].semeai_defense_target = opponent;
|
||||
|
||||
/* We need to determine a proper attack move (the one that
|
||||
* prevents seki). Currently we try the defense move first,
|
||||
* and if it doesn't work -- all liberties of the string.
|
||||
*/
|
||||
owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
|
||||
str, opponent, &resulta, NULL,
|
||||
NULL, 1, NULL, 0);
|
||||
if (resulta != WIN) {
|
||||
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||||
dragon2[d].semeai_attack_point = defend_move;
|
||||
}
|
||||
else {
|
||||
int k;
|
||||
int libs[MAXLIBS];
|
||||
int liberties = findlib(str, MAXLIBS, libs);
|
||||
|
||||
for (k = 0; k < liberties; k++) {
|
||||
owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
|
||||
str, opponent, &resulta, NULL,
|
||||
NULL, 1, NULL, 0);
|
||||
if (resulta != WIN) {
|
||||
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||||
dragon2[d].semeai_attack_point = libs[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (k == liberties) {
|
||||
DEBUG(DEBUG_SEMEAI,
|
||||
"No move to attack in semeai (%1m vs %1m), seki assumed.\n",
|
||||
str, opponent);
|
||||
dragon2[d].semeai_attack_code = 0;
|
||||
dragon2[d].semeai_attack_point = NO_MOVE;
|
||||
update_status(str, ALIVE, ALIVE_IN_SEKI);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
|
||||
dragon2[d].semeai_attack_point, opponent, str);
|
||||
|
||||
dragon2[d].semeai_attack_certain = certain;
|
||||
dragon2[d].semeai_attack_target = opponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now look for dead strings inside a single eyespace of a living dragon.
|
||||
*
|
||||
* FIXME: Clearly this loop should share most of its code with the
|
||||
* one above. It would also be good to reimplement so that
|
||||
* moves invading a previously empty single eyespace to make
|
||||
* seki can be found.
|
||||
*/
|
||||
for (str = BOARDMIN; str < BOARDMAX; str++) {
|
||||
if (IS_STONE(board[str]) && is_worm_origin(str, str)
|
||||
&& !find_defense(str, NULL)
|
||||
&& dragon[str].status == DEAD
|
||||
&& DRAGON2(str).hostile_neighbors == 1) {
|
||||
int k;
|
||||
int color = board[str];
|
||||
int opponent = NO_MOVE;
|
||||
int certain;
|
||||
struct eyevalue reduced_genus;
|
||||
|
||||
for (k = 0; k < DRAGON2(str).neighbors; k++) {
|
||||
opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
|
||||
if (board[opponent] != color)
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT1(opponent != NO_MOVE, opponent);
|
||||
|
||||
if (dragon[opponent].status != ALIVE)
|
||||
continue;
|
||||
|
||||
/* FIXME: These heuristics are used for optimization. We don't
|
||||
* want to call expensive semeai code if the opponent
|
||||
* dragon has more than one eye elsewhere. However, the
|
||||
* heuristics might still need improvement.
|
||||
*/
|
||||
compute_dragon_genus(opponent, &reduced_genus, str);
|
||||
if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1)
|
||||
continue;
|
||||
|
||||
owl_analyze_semeai(str, opponent, &resulta, &resultb,
|
||||
&defend_move, 1, &certain);
|
||||
|
||||
/* Do not trust uncertain results. In fact it should only take a
|
||||
* few nodes to determine the semeai result, if it is a proper
|
||||
* potential seki position.
|
||||
*/
|
||||
if (resulta != 0 && certain) {
|
||||
int d = dragon[str].id;
|
||||
DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n",
|
||||
defend_move, str, opponent);
|
||||
dragon2[d].semeais++;
|
||||
update_status(str, CRITICAL, CRITICAL);
|
||||
dragon2[d].semeai_defense_code = resulta;
|
||||
dragon2[d].semeai_defense_point = defend_move;
|
||||
dragon2[d].semeai_defense_certain = certain;
|
||||
gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin]));
|
||||
dragon2[d].semeai_defense_target = opponent;
|
||||
|
||||
/* We need to determine a proper attack move (the one that
|
||||
* prevents seki). Currently we try the defense move first,
|
||||
* and if it doesn't work -- all liberties of the string.
|
||||
*/
|
||||
owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
|
||||
str, opponent, &resulta, NULL,
|
||||
NULL, 1, NULL, 0);
|
||||
if (resulta != WIN) {
|
||||
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||||
dragon2[d].semeai_attack_point = defend_move;
|
||||
}
|
||||
else {
|
||||
int k;
|
||||
int libs[MAXLIBS];
|
||||
int liberties = findlib(str, MAXLIBS, libs);
|
||||
|
||||
for (k = 0; k < liberties; k++) {
|
||||
owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
|
||||
str, opponent, &resulta, NULL,
|
||||
NULL, 1, NULL, 0);
|
||||
if (resulta != WIN) {
|
||||
dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta);
|
||||
dragon2[d].semeai_attack_point = libs[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (k == liberties) {
|
||||
DEBUG(DEBUG_SEMEAI,
|
||||
"No move to attack in semeai (%1m vs %1m), seki assumed.\n",
|
||||
str, opponent);
|
||||
dragon2[d].semeai_attack_code = 0;
|
||||
dragon2[d].semeai_attack_point = NO_MOVE;
|
||||
update_status(str, ALIVE, ALIVE_IN_SEKI);
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
|
||||
dragon2[d].semeai_attack_point, opponent, str);
|
||||
|
||||
dragon2[d].semeai_attack_certain = certain;
|
||||
dragon2[d].semeai_attack_target = opponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* neighbor_of_dragon(pos, origin) returns true if the vertex at (pos) is a
|
||||
* neighbor of the dragon with origin at (origin).
|
||||
*/
|
||||
static int
|
||||
neighbor_of_dragon(int pos, int origin)
|
||||
{
|
||||
int k;
|
||||
if (pos == NO_MOVE)
|
||||
return 0;
|
||||
|
||||
for (k = 0; k < 4; k++)
|
||||
if (ON_BOARD(pos + delta[k]) && dragon[pos + delta[k]].origin == origin)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check whether two dragons are directly adjacent or have at least
|
||||
* one common liberty.
|
||||
*/
|
||||
static int
|
||||
close_enough_for_proper_semeai(int apos, int bpos)
|
||||
{
|
||||
int pos;
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] == EMPTY
|
||||
&& neighbor_of_dragon(pos, apos)
|
||||
&& neighbor_of_dragon(pos, bpos))
|
||||
return 1;
|
||||
else if (IS_STONE(board[pos])) {
|
||||
if (is_same_dragon(pos, apos) && neighbor_of_dragon(pos, bpos))
|
||||
return 1;
|
||||
if (is_same_dragon(pos, bpos) && neighbor_of_dragon(pos, apos))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This function adds the semeai related move reasons, using the information
|
||||
* stored in the dragon2 array.
|
||||
*
|
||||
* If the semeai had an uncertain result, and there is a owl move with
|
||||
* certain result doing the same, we don't trust the semeai move.
|
||||
*/
|
||||
void
|
||||
semeai_move_reasons(int color)
|
||||
{
|
||||
int other = OTHER_COLOR(color);
|
||||
int d;
|
||||
int liberties;
|
||||
int libs[MAXLIBS];
|
||||
int r;
|
||||
|
||||
for (d = 0; d < number_of_dragons; d++)
|
||||
if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) {
|
||||
if (DRAGON(d).color == color
|
||||
&& dragon2[d].semeai_defense_point
|
||||
&& (dragon2[d].owl_defense_point == NO_MOVE
|
||||
|| dragon2[d].semeai_defense_certain >=
|
||||
dragon2[d].owl_defense_certain)) {
|
||||
/* My dragon can be defended. */
|
||||
add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin);
|
||||
DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n",
|
||||
DRAGON(d).origin, dragon2[d].semeai_defense_point);
|
||||
if (neighbor_of_dragon(dragon2[d].semeai_defense_point,
|
||||
dragon2[d].semeai_defense_target)
|
||||
&& !neighbor_of_dragon(dragon2[d].semeai_defense_point,
|
||||
dragon2[d].origin)
|
||||
&& !is_self_atari(dragon2[d].semeai_defense_point, color)) {
|
||||
|
||||
/* If this is a move to fill the non-common liberties of the
|
||||
* target, and is not a ko or snap-back, then we mark all
|
||||
* non-common liberties of the target as potential semeai moves.
|
||||
*/
|
||||
|
||||
liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs);
|
||||
|
||||
for (r = 0; r < liberties; r++) {
|
||||
if (!neighbor_of_dragon(libs[r], dragon2[d].origin)
|
||||
&& !is_self_atari(libs[r], color)
|
||||
&& libs[r] != dragon2[d].semeai_defense_point)
|
||||
add_potential_semeai_defense(libs[r], dragon2[d].origin,
|
||||
dragon2[d].semeai_defense_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (DRAGON(d).color == other
|
||||
&& dragon2[d].semeai_attack_point
|
||||
&& (dragon2[d].owl_attack_point == NO_MOVE
|
||||
|| dragon2[d].owl_defense_point == NO_MOVE
|
||||
|| dragon2[d].semeai_attack_certain >=
|
||||
dragon2[d].owl_attack_certain)) {
|
||||
/* Your dragon can be attacked. */
|
||||
add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin);
|
||||
DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n",
|
||||
DRAGON(d).origin, dragon2[d].semeai_attack_point);
|
||||
if (neighbor_of_dragon(dragon2[d].semeai_attack_point,
|
||||
dragon2[d].origin)
|
||||
&& !neighbor_of_dragon(dragon2[d].semeai_attack_point,
|
||||
dragon2[d].semeai_attack_target)
|
||||
&& !is_self_atari(dragon2[d].semeai_attack_point, color)) {
|
||||
|
||||
liberties = findlib(dragon2[d].origin, MAXLIBS, libs);
|
||||
|
||||
for (r = 0; r < liberties; r++) {
|
||||
if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target)
|
||||
&& !is_self_atari(libs[r], color)
|
||||
&& libs[r] != dragon2[d].semeai_attack_point)
|
||||
add_potential_semeai_attack(libs[r], dragon2[d].origin,
|
||||
dragon2[d].semeai_attack_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Change the status and safety of a dragon. In addition, if the new
|
||||
* status is not DEAD, make all worms of the dragon essential, so that
|
||||
* results found by semeai code don't get ignored.
|
||||
*/
|
||||
static void
|
||||
update_status(int dr, enum dragon_status new_status,
|
||||
enum dragon_status new_safety)
|
||||
{
|
||||
int pos;
|
||||
|
||||
if (dragon[dr].status != new_status
|
||||
&& (dragon[dr].status != CRITICAL || new_status != DEAD)) {
|
||||
DEBUG(DEBUG_SEMEAI, "Changing status of %1m from %s to %s.\n", dr,
|
||||
status_to_string(dragon[dr].status),
|
||||
status_to_string(new_status));
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (IS_STONE(board[pos]) && is_same_dragon(dr, pos)) {
|
||||
dragon[pos].status = new_status;
|
||||
if (new_status != DEAD)
|
||||
worm[pos].inessential = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (DRAGON2(dr).safety != new_safety
|
||||
&& (DRAGON2(dr).safety != CRITICAL || new_safety != DEAD)) {
|
||||
DEBUG(DEBUG_SEMEAI, "Changing safety of %1m from %s to %s.\n", dr,
|
||||
status_to_string(DRAGON2(dr).safety), status_to_string(new_safety));
|
||||
DRAGON2(dr).safety = new_safety;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
675
gnugo/engine/sgfdecide.c
Normal file
675
gnugo/engine/sgfdecide.c
Normal file
@ -0,0 +1,675 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* ================================================================ */
|
||||
/* Show status for a string, a dragon, etc in an SGF file. */
|
||||
/* ================================================================ */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "sgftree.h"
|
||||
|
||||
|
||||
/*
|
||||
* decide_string tries to attack and defend the string at (pos),
|
||||
* and then writes the number of variations considered in the attack
|
||||
* and defence to the sgf file.
|
||||
*/
|
||||
|
||||
void
|
||||
decide_string(int pos)
|
||||
{
|
||||
int aa, dd;
|
||||
int acode, dcode;
|
||||
SGFTree tree;
|
||||
|
||||
if (board[pos] == EMPTY) {
|
||||
fprintf(stderr, "gnugo: --decide-string called on an empty vertex\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
count_variations = 1;
|
||||
acode = attack(pos, &aa);
|
||||
if (acode) {
|
||||
if (acode == WIN)
|
||||
gprintf("%1m can be attacked at %1m (%d variations)\n",
|
||||
pos, aa, count_variations);
|
||||
else if (acode == KO_A)
|
||||
gprintf("%1m can be attacked with ko (good) at %1m (%d variations)\n",
|
||||
pos, aa, count_variations);
|
||||
else if (acode == KO_B)
|
||||
gprintf("%1m can be attacked with ko (bad) at %1m (%d variations)\n",
|
||||
pos, aa, count_variations);
|
||||
|
||||
if (debug & DEBUG_READING_PERFORMANCE) {
|
||||
gprintf("Reading shadow: \n");
|
||||
draw_reading_shadow();
|
||||
}
|
||||
|
||||
count_variations = 1;
|
||||
dcode = find_defense(pos, &dd);
|
||||
if (dcode) {
|
||||
if (dcode == WIN)
|
||||
gprintf("%1m can be defended at %1m (%d variations)\n",
|
||||
pos, dd, count_variations);
|
||||
else if (dcode == KO_A)
|
||||
gprintf("%1m can be defended with ko (good) at %1m (%d variations)\n",
|
||||
pos, dd, count_variations);
|
||||
else if (dcode == KO_B)
|
||||
gprintf("%1m can be defended with ko (bad) at %1m (%d variations)\n",
|
||||
pos, dd, count_variations);
|
||||
}
|
||||
else
|
||||
gprintf("%1m cannot be defended (%d variations)\n",
|
||||
pos, count_variations);
|
||||
if (debug & DEBUG_READING_PERFORMANCE) {
|
||||
gprintf("Reading shadow: \n");
|
||||
draw_reading_shadow();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
gprintf("%1m cannot be attacked (%d variations)\n",
|
||||
pos, count_variations);
|
||||
if (debug & DEBUG_READING_PERFORMANCE) {
|
||||
gprintf("Reading shadow: \n");
|
||||
draw_reading_shadow();
|
||||
}
|
||||
}
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* decide_connection tries to connect and disconnect the strings at
|
||||
* (apos) and (bpos), and then writes the number of variations
|
||||
* considered in the attack and defence to the sgf file.
|
||||
*/
|
||||
|
||||
void
|
||||
decide_connection(int apos, int bpos)
|
||||
{
|
||||
int move;
|
||||
int result;
|
||||
SGFTree tree;
|
||||
|
||||
ASSERT_ON_BOARD1(apos);
|
||||
ASSERT_ON_BOARD1(bpos);
|
||||
|
||||
if (board[apos] == EMPTY || board[bpos] == EMPTY) {
|
||||
fprintf(stderr, "gnugo: --decide-connection called on an empty vertex\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (board[apos] != board[bpos]) {
|
||||
fprintf(stderr, "gnugo: --decide-connection called for strings of different colors\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
count_variations = 1;
|
||||
result = string_connect(apos, bpos, &move);
|
||||
if (result == WIN) {
|
||||
if (move == NO_MOVE)
|
||||
gprintf("%1m and %1m are connected as it stands (%d variations)\n",
|
||||
apos, bpos, count_variations);
|
||||
else
|
||||
gprintf("%1m and %1m can be connected at %1m (%d variations)\n",
|
||||
apos, bpos, move, count_variations);
|
||||
}
|
||||
else if (result == KO_A)
|
||||
gprintf("%1m and %1m can be connected with ko (good) at %1m (%d variations)\n",
|
||||
apos, bpos, move, count_variations);
|
||||
else if (result == KO_B)
|
||||
gprintf("%1m and %1m can be connected with ko (bad) at %1m (%d variations)\n",
|
||||
apos, bpos, move, count_variations);
|
||||
else
|
||||
gprintf("%1m and %1m cannot be connected (%d variations)\n",
|
||||
apos, bpos, count_variations);
|
||||
|
||||
count_variations = 1;
|
||||
result = disconnect(apos, bpos, &move);
|
||||
if (result == WIN) {
|
||||
if (move == NO_MOVE)
|
||||
gprintf("%1m and %1m are disconnected as it stands (%d variations)\n",
|
||||
apos, bpos, count_variations);
|
||||
else
|
||||
gprintf("%1m and %1m can be disconnected at %1m (%d variations)\n",
|
||||
apos, bpos, move, count_variations);
|
||||
}
|
||||
else if (result == KO_A)
|
||||
gprintf("%1m and %1m can be disconnected with ko (good) at %1m (%d variations)\n",
|
||||
apos, bpos, move, count_variations);
|
||||
else if (result == KO_B)
|
||||
gprintf("%1m and %1m can be disconnected with ko (bad) at %1m (%d variations)\n",
|
||||
apos, bpos, move, count_variations);
|
||||
else
|
||||
gprintf("%1m and %1m cannot be disconnected (%d variations)\n",
|
||||
apos, bpos, count_variations);
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* decide_owl (formerly called decide_dragon) tries to attack and defend
|
||||
* the dragon at (pos), and then writes the number of variations considered
|
||||
* in the attack and defence to the sgf file.
|
||||
*/
|
||||
|
||||
void
|
||||
decide_owl(int pos)
|
||||
{
|
||||
int move = NO_MOVE;
|
||||
int acode, dcode;
|
||||
SGFTree tree;
|
||||
int result_certain;
|
||||
int kworm;
|
||||
|
||||
if (board[pos] == EMPTY) {
|
||||
fprintf(stderr, "gnugo: --decide-dragon called on an empty vertex\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
|
||||
gprintf("finished examine_position\n");
|
||||
|
||||
/* We want to see the reading performed, not just a result picked
|
||||
* from the cache. Thus we clear the cache here.
|
||||
*/
|
||||
reading_cache_clear();
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
count_variations = 1;
|
||||
acode = owl_attack(pos, &move, &result_certain, &kworm);
|
||||
if (acode) {
|
||||
if (acode == WIN) {
|
||||
if (move == NO_MOVE)
|
||||
gprintf("%1m is dead as it stands", pos);
|
||||
else
|
||||
gprintf("%1m can be attacked at %1m (%d variations)",
|
||||
pos, move, count_variations);
|
||||
}
|
||||
else if (acode == KO_A)
|
||||
gprintf("%1m can be attacked with ko (good) at %1m (%d variations)",
|
||||
pos, move, count_variations);
|
||||
else if (acode == KO_B)
|
||||
gprintf("%1m can be attacked with ko (bad) at %1m (%d variations)",
|
||||
pos, move, count_variations);
|
||||
else if (acode == GAIN)
|
||||
gprintf("%1m can be attacked with gain (captures %1m) at %1m (%d variations)",
|
||||
pos, kworm, move, count_variations);
|
||||
}
|
||||
else
|
||||
gprintf("%1m cannot be attacked (%d variations)", pos, count_variations);
|
||||
|
||||
if (result_certain)
|
||||
gprintf("\n");
|
||||
else
|
||||
gprintf(" result uncertain\n");
|
||||
|
||||
reading_cache_clear();
|
||||
count_variations = 1;
|
||||
dcode = owl_defend(pos, &move, &result_certain, &kworm);
|
||||
|
||||
if (dcode) {
|
||||
if (dcode == WIN) {
|
||||
if (move == NO_MOVE)
|
||||
gprintf("%1m is alive as it stands", pos);
|
||||
else
|
||||
gprintf("%1m can be defended at %1m (%d variations)",
|
||||
pos, move, count_variations);
|
||||
}
|
||||
else if (dcode == KO_A)
|
||||
gprintf("%1m can be defended with ko (good) at %1m (%d variations)",
|
||||
pos, move, count_variations);
|
||||
else if (dcode == KO_B)
|
||||
gprintf("%1m can be defended with ko (bad) at %1m (%d variations)",
|
||||
pos, move, count_variations);
|
||||
else if (dcode == LOSS)
|
||||
gprintf("%1m can be defended with loss (loses %1m) at %1m (%d variations)",
|
||||
pos, kworm, move, count_variations);
|
||||
}
|
||||
else
|
||||
gprintf("%1m cannot be defended (%d variations)",
|
||||
pos, count_variations);
|
||||
|
||||
if (result_certain)
|
||||
gprintf("\n");
|
||||
else
|
||||
gprintf(" result uncertain\n");
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* decide_dragon_data prints the dragon data at (pos).
|
||||
*/
|
||||
|
||||
void
|
||||
decide_dragon_data(int pos)
|
||||
{
|
||||
if (board[pos] == EMPTY) {
|
||||
fprintf(stderr, "gnugo: --decide-dragon-data called on an empty vertex\n");
|
||||
return;
|
||||
}
|
||||
reset_engine();
|
||||
silent_examine_position(FULL_EXAMINE_DRAGONS);
|
||||
|
||||
gprintf("Dragon at %1m:\n", pos);
|
||||
report_dragon(stderr, pos);
|
||||
}
|
||||
|
||||
|
||||
/* Print the result of the semeai code on the semeai at apos/bpos,
|
||||
* optionally writing an sgf file.
|
||||
*/
|
||||
|
||||
void
|
||||
decide_semeai(int apos, int bpos)
|
||||
{
|
||||
SGFTree tree;
|
||||
int resulta, resultb, move, result_certain;
|
||||
int color = board[apos];
|
||||
|
||||
if (color == EMPTY || board[bpos] != OTHER_COLOR(color)) {
|
||||
gprintf("gnugo: --decide-semeai called on invalid data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
|
||||
gprintf("finished examine_position\n");
|
||||
count_variations = 1;
|
||||
|
||||
/* We want to see the reading performed, not just a result picked
|
||||
* from the cache. Thus we clear the cache here. */
|
||||
reading_cache_clear();
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
gprintf("Analyzing semeai between %1m and %1m, %C moves first\n",
|
||||
apos, bpos, board[apos]);
|
||||
owl_analyze_semeai(apos, bpos, &resulta, &resultb, &move, 1,
|
||||
&result_certain);
|
||||
gprintf("Semeai defense of %1m: result %s %1m\n",
|
||||
apos, result_to_string(resulta), move);
|
||||
gprintf("Semeai attack of %1m: result %s %1m\n",
|
||||
bpos, result_to_string(resultb), move);
|
||||
gprintf("%d nodes%s\n\n", count_variations,
|
||||
result_certain ? "" : ", uncertain result");
|
||||
|
||||
gprintf("Analyzing semeai between %1m and %1m, %C moves first\n",
|
||||
bpos, apos, board[bpos]);
|
||||
owl_analyze_semeai(bpos, apos, &resultb, &resulta, &move, 1,
|
||||
&result_certain);
|
||||
gprintf("Semeai defense of %1m: result %s %1m\n",
|
||||
bpos, result_to_string(resultb), move);
|
||||
gprintf("Semeai attack of %1m: result %s %1m\n",
|
||||
apos, result_to_string(resulta), move);
|
||||
gprintf("%d nodes%s\n", count_variations,
|
||||
result_certain ? "" : ", uncertain result");
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
decide_tactical_semeai(int apos, int bpos)
|
||||
{
|
||||
SGFTree tree;
|
||||
int resulta, resultb, move, dummy;
|
||||
int color = board[apos];
|
||||
|
||||
if (color == EMPTY || board[bpos] != OTHER_COLOR(color)) {
|
||||
gprintf("gnugo: --decide-semeai called on invalid data\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
|
||||
gprintf("finished examine_position\n");
|
||||
count_variations = 1;
|
||||
|
||||
/* We want to see the reading performed, not just a result picked
|
||||
* from the cache. Thus we clear the cache here. */
|
||||
reading_cache_clear();
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
/* FIXME: Calling status_to_string() with a result code as argument
|
||||
* doesn't make sense. It could be changed to result_to_string() but
|
||||
* the overall formatting needs change as well.
|
||||
*/
|
||||
owl_analyze_semeai(apos, bpos, &resulta, &resultb, &move, 0, &dummy);
|
||||
gprintf("After %s at %1m, %1m is %s, %1m is %s (%d nodes)\n",
|
||||
color_to_string(color),
|
||||
move,
|
||||
apos, status_to_string(resulta),
|
||||
bpos, status_to_string(resultb),
|
||||
count_variations);
|
||||
owl_analyze_semeai(bpos, apos, &resultb, &resulta, &move, 0, &dummy);
|
||||
gprintf("After %s at %1m, %1m is %s, %1m is %s (%d nodes)\n",
|
||||
color_to_string(color),
|
||||
move,
|
||||
apos, status_to_string(resulta),
|
||||
bpos, status_to_string(resultb),
|
||||
count_variations);
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* decide_position tries to attack and defend every dragon with
|
||||
* dragon.escape<6 and writes the variations to an sgf file.
|
||||
*/
|
||||
|
||||
void
|
||||
decide_position()
|
||||
{
|
||||
int pos;
|
||||
int move = NO_MOVE;
|
||||
int acode = 0, dcode = 0;
|
||||
int kworm;
|
||||
static const char *snames[] = {"dead", "alive", "critical", "unknown"};
|
||||
SGFTree tree;
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
|
||||
|
||||
/* We want to see the reading performed, not just a result picked
|
||||
* from the cache. Thus we clear the cache here. */
|
||||
reading_cache_clear();
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
count_variations = 1;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (!ON_BOARD(pos)
|
||||
|| dragon[pos].origin != pos
|
||||
|| board[pos] == EMPTY
|
||||
|| DRAGON2(pos).escape_route >= 6)
|
||||
continue;
|
||||
|
||||
gprintf("\nanalyzing %1m\n", pos);
|
||||
gprintf("status=%s, escape=%d\n",
|
||||
snames[dragon[pos].crude_status], DRAGON2(pos).escape_route);
|
||||
acode = owl_attack(pos, &move, NULL, &kworm);
|
||||
if (acode) {
|
||||
if (acode == WIN) {
|
||||
if (move == NO_MOVE)
|
||||
gprintf("%1m is dead as it stands\n", pos);
|
||||
else
|
||||
gprintf("%1m can be attacked at %1m (%d variations)\n",
|
||||
pos, move, count_variations);
|
||||
}
|
||||
else if (acode == KO_A)
|
||||
gprintf("%1m can be attacked with ko (good) at %1m (%d variations)\n",
|
||||
pos, move, count_variations);
|
||||
else if (acode == KO_B)
|
||||
gprintf("%1m can be attacked with ko (bad) at %1m (%d variations)\n",
|
||||
pos, move, count_variations);
|
||||
else if (acode == GAIN)
|
||||
gprintf("%1m can be attacked with gain (captures %1m) at %1m (%d variations)",
|
||||
pos, kworm, move, count_variations);
|
||||
|
||||
count_variations = 1;
|
||||
dcode = owl_defend(pos, &move, NULL, &kworm);
|
||||
if (dcode) {
|
||||
if (dcode == WIN) {
|
||||
if (move == NO_MOVE)
|
||||
gprintf("%1m is alive as it stands\n", pos);
|
||||
else
|
||||
gprintf("%1m can be defended at %1m (%d variations)\n",
|
||||
pos, move, count_variations);
|
||||
}
|
||||
else if (dcode == KO_A)
|
||||
gprintf("%1m can be defended with ko (good) at %1m (%d variations)\n",
|
||||
pos, move, count_variations);
|
||||
else if (dcode == KO_B)
|
||||
gprintf("%1m can be defended with ko (bad) at %1m (%d variations)\n",
|
||||
pos, move, count_variations);
|
||||
else if (dcode == LOSS)
|
||||
gprintf("%1m can be defended with loss (loses %1m) at %1m (%d variations)",
|
||||
pos, kworm, move, count_variations);
|
||||
}
|
||||
else
|
||||
gprintf("%1m cannot be defended (%d variations)\n",
|
||||
pos, count_variations);
|
||||
}
|
||||
else
|
||||
gprintf("%1m cannot be attacked (%d variations)\n",
|
||||
pos, count_variations);
|
||||
|
||||
if (acode) {
|
||||
if (dcode)
|
||||
gprintf("status of %1m revised to CRITICAL\n", pos);
|
||||
else
|
||||
gprintf("status of %1m revised to DEAD\n", pos);
|
||||
}
|
||||
else
|
||||
gprintf("status of %1m revised to ALIVE\n", pos);
|
||||
}
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Evaluates the eyespace at (pos) and prints a report. You can get
|
||||
* more information by adding -d0x02 to the command line.
|
||||
*/
|
||||
|
||||
void
|
||||
decide_eye(int pos)
|
||||
{
|
||||
int color;
|
||||
struct eyevalue value;
|
||||
int attack_point;
|
||||
int defense_point;
|
||||
int eyepos;
|
||||
SGFTree tree;
|
||||
|
||||
reset_engine();
|
||||
silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL);
|
||||
|
||||
color = black_eye[pos].color;
|
||||
if (!IS_STONE(color)) {
|
||||
gprintf("The eye at %1m is not of a single color.\n", pos);
|
||||
return;
|
||||
}
|
||||
|
||||
if (printboard)
|
||||
showboard(0);
|
||||
|
||||
/* Enable sgf output. */
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
count_variations = 1;
|
||||
|
||||
if (black_eye[pos].color == BLACK) {
|
||||
eyepos = black_eye[pos].origin;
|
||||
compute_eyes(eyepos, &value, &attack_point, &defense_point,
|
||||
black_eye, half_eye, 0);
|
||||
gprintf("Black eyespace at %1m: %s\n", eyepos, eyevalue_to_string(&value));
|
||||
if (eye_move_urgency(&value) > 0) {
|
||||
gprintf(" vital points: %1m (attack) %1m (defense)\n", attack_point,
|
||||
defense_point);
|
||||
}
|
||||
}
|
||||
|
||||
if (white_eye[pos].color == WHITE) {
|
||||
eyepos = white_eye[pos].origin;
|
||||
compute_eyes(eyepos, &value, &attack_point, &defense_point,
|
||||
white_eye, half_eye, 0);
|
||||
gprintf("White eyespace at %1m: %s\n", eyepos, eyevalue_to_string(&value));
|
||||
if (eye_move_urgency(&value) > 0) {
|
||||
gprintf(" vital points: %1m (attack) %1m (defense)\n", attack_point,
|
||||
defense_point);
|
||||
}
|
||||
}
|
||||
|
||||
/* Finish sgf output. */
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* decide_combination tries to find a combination attack for (color) by
|
||||
* calling atari_atari().
|
||||
*/
|
||||
|
||||
void
|
||||
decide_combination(int color)
|
||||
{
|
||||
int attack_move;
|
||||
signed char defense_moves[BOARDMAX];
|
||||
SGFTree tree;
|
||||
int first = 1;
|
||||
int pos;
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
silent_examine_position(EXAMINE_ALL);
|
||||
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
count_variations = 1;
|
||||
|
||||
if (atari_atari(color, &attack_move, defense_moves, verbose)) {
|
||||
gprintf("Combination attack for %C at %1m, defense at ", color,
|
||||
attack_move);
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (ON_BOARD(pos) && defense_moves[pos]) {
|
||||
if (first)
|
||||
first = 0;
|
||||
else
|
||||
gprintf(", ");
|
||||
gprintf("%1m", pos);
|
||||
}
|
||||
}
|
||||
gprintf("\n");
|
||||
}
|
||||
else
|
||||
gprintf("No Combination attack for %C\n", color);
|
||||
|
||||
sgffile_enddump(outfilename);
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
decide_surrounded(int pos)
|
||||
{
|
||||
int surround_status;
|
||||
|
||||
if (board[pos] == EMPTY) {
|
||||
fprintf(stderr, "location must not be empty!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Prepare pattern matcher and reading code. */
|
||||
reset_engine();
|
||||
|
||||
silent_examine_position(EXAMINE_ALL);
|
||||
surround_status = compute_surroundings(pos, NO_MOVE, 1, NULL);
|
||||
if (surround_status == 1)
|
||||
gprintf("the dragon at %1m is SURROUNDED!\n", pos);
|
||||
else if (surround_status == 2)
|
||||
gprintf("the dragon at %1m is WEAKLY SURROUNDED!\n", pos);
|
||||
else
|
||||
gprintf("the dragon at %1m is not surrounded.\n", pos);
|
||||
}
|
||||
|
||||
|
||||
#if ORACLE
|
||||
|
||||
void
|
||||
decide_oracle(Gameinfo *gameinfo, char *infilename, char *untilstring)
|
||||
{
|
||||
SGFTree tree;
|
||||
|
||||
reset_engine();
|
||||
if (*outfilename)
|
||||
sgffile_begindump(&tree);
|
||||
|
||||
count_variations = 1;
|
||||
summon_oracle();
|
||||
oracle_loadsgf(infilename, untilstring);
|
||||
consult_oracle(gameinfo->to_move);
|
||||
sgffile_enddump(outfilename);
|
||||
dismiss_oracle();
|
||||
count_variations = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
||||
|
249
gnugo/engine/sgffile.c
Normal file
249
gnugo/engine/sgffile.c
Normal file
@ -0,0 +1,249 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/*
|
||||
* sgffile.c
|
||||
*
|
||||
* This file used to contain functions that create an SGF file on the fly.
|
||||
*
|
||||
* Today it contains supporting code around the more general SGF library
|
||||
* found in the sgf/ directory.
|
||||
*/
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "sgftree.h"
|
||||
#include "gg_utils.h"
|
||||
|
||||
/*
|
||||
* Add debug information to a node if user requested it from command
|
||||
* line.
|
||||
*/
|
||||
|
||||
void
|
||||
sgffile_add_debuginfo(SGFNode *node, float value)
|
||||
{
|
||||
int pos;
|
||||
char comment[24];
|
||||
|
||||
if (!outfilename[0])
|
||||
return;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (!ON_BOARD(pos))
|
||||
continue;
|
||||
|
||||
if (IS_STONE(board[pos]) && (output_flags & OUTPUT_MARKDRAGONS)) {
|
||||
if (dragon[pos].crude_status == DEAD)
|
||||
sgfLabel(node, "X", I(pos), J(pos));
|
||||
else if (dragon[pos].crude_status == CRITICAL)
|
||||
sgfLabel(node, "!", I(pos), J(pos));
|
||||
}
|
||||
|
||||
if (potential_moves[pos] > 0.0 && (output_flags & OUTPUT_MOVEVALUES)) {
|
||||
if (potential_moves[pos] < 1.0)
|
||||
sgfLabel(node, "<1", I(pos), J(pos));
|
||||
else
|
||||
sgfLabelInt(node, (int) potential_moves[pos], I(pos), J(pos));
|
||||
}
|
||||
}
|
||||
|
||||
if (value > 0.0 && (output_flags & OUTPUT_MOVEVALUES)) {
|
||||
sprintf(comment, "Value of move: %.2f", value);
|
||||
sgfAddComment(node, comment);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write sgf tree to output file specified with -o option.
|
||||
* This can safely be done multiple times.
|
||||
*/
|
||||
|
||||
void
|
||||
sgffile_output(SGFTree *tree)
|
||||
{
|
||||
if (outfilename[0])
|
||||
writesgf(tree->root, outfilename);
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* Dumping of information about a position into an sgftree.
|
||||
* Used by sgffile_decideposition, etc.
|
||||
* ================================================================ */
|
||||
|
||||
|
||||
/*
|
||||
* sgffile_begindump begins storing all moves considered by
|
||||
* trymove and tryko in an sgf tree in memory.
|
||||
*
|
||||
* The caller only has to provide an own SGFTree pointer if he wants
|
||||
* to do something more with the tree than writing it to file as done
|
||||
* by sgffile_enddump().
|
||||
*/
|
||||
|
||||
void
|
||||
sgffile_begindump(SGFTree *tree)
|
||||
{
|
||||
static SGFTree local_tree;
|
||||
gg_assert(sgf_dumptree == NULL);
|
||||
|
||||
if (tree == NULL)
|
||||
sgf_dumptree = &local_tree;
|
||||
else
|
||||
sgf_dumptree = tree;
|
||||
|
||||
sgftree_clear(sgf_dumptree);
|
||||
sgftreeCreateHeaderNode(sgf_dumptree, board_size, komi, handicap);
|
||||
sgffile_printboard(sgf_dumptree);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* sgffile_enddump ends the dump and writes the sgf tree to file.
|
||||
*/
|
||||
|
||||
void
|
||||
sgffile_enddump(const char *filename)
|
||||
{
|
||||
/* Check if we have a valid filename and a tree. */
|
||||
if (filename && *filename && sgf_dumptree) {
|
||||
if (writesgf(sgf_dumptree->root, filename)) {
|
||||
/* Only delete the tree if writesgf() succeeds. If it doesn't, one
|
||||
* will most likely wish to save into another (writable) file.
|
||||
*/
|
||||
sgfFreeNode(sgf_dumptree->root);
|
||||
sgf_dumptree = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* sgffile_printsgf creates an sgf of the current board position
|
||||
* (without any move history). It also adds information about who is
|
||||
* to play and marks illegal moves with the private sgf property IL.
|
||||
*/
|
||||
|
||||
void
|
||||
sgffile_printsgf(int color_to_play, const char *filename)
|
||||
{
|
||||
SGFTree sgftree;
|
||||
int m, n;
|
||||
char pos[3];
|
||||
char str[128];
|
||||
float relative_komi;
|
||||
|
||||
relative_komi = komi + black_captured - white_captured;
|
||||
|
||||
sgftree_clear(&sgftree);
|
||||
sgftreeCreateHeaderNode(&sgftree, board_size, relative_komi, handicap);
|
||||
sgf_write_header(sgftree.root, 1, get_random_seed(), relative_komi,
|
||||
handicap, get_level(), chinese_rules);
|
||||
gg_snprintf(str, 128, "GNU Go %s load and print", gg_version());
|
||||
sgfOverwriteProperty(sgftree.root, "GN", str);
|
||||
|
||||
sgffile_printboard(&sgftree);
|
||||
|
||||
if (color_to_play != EMPTY) {
|
||||
sgfAddProperty(sgftree.lastnode, "PL",
|
||||
(color_to_play == WHITE ? "W" : "B"));
|
||||
|
||||
for (m = 0; m < board_size; ++m)
|
||||
for (n = 0; n < board_size; ++n)
|
||||
if (BOARD(m, n) == EMPTY && !is_legal(POS(m, n), color_to_play)) {
|
||||
gg_snprintf(pos, 3, "%c%c", 'a' + n, 'a' + m);
|
||||
sgfAddProperty(sgftree.lastnode, "IL", pos);
|
||||
}
|
||||
}
|
||||
|
||||
writesgf(sgftree.root, filename);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* sgffile_printboard adds the current board position to the tree.
|
||||
*/
|
||||
|
||||
void
|
||||
sgffile_printboard(SGFTree *tree)
|
||||
{
|
||||
int i, j;
|
||||
SGFNode *node;
|
||||
|
||||
gg_assert(tree);
|
||||
node = tree->lastnode;
|
||||
|
||||
/* Write the white stones to the file. */
|
||||
for (i = 0; i < board_size; i++) {
|
||||
for (j = 0; j < board_size; j++) {
|
||||
if (BOARD(i, j) == WHITE)
|
||||
sgfAddStone(node, WHITE, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the black stones to the file. */
|
||||
for (i = 0; i < board_size; i++) {
|
||||
for (j = 0; j < board_size; j++) {
|
||||
if (BOARD(i, j) == BLACK)
|
||||
sgfAddStone(node, BLACK, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
sgftreeSetLastNode(tree, node);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
sgffile_recordboard(SGFNode *node)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
if (node)
|
||||
for (i = 0; i < board_size; i++)
|
||||
for (j = 0; j < board_size; j++)
|
||||
if (BOARD(i, j) == BLACK)
|
||||
sgfAddStone(node, BLACK, i, j);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
get_sgfmove(SGFProperty *property)
|
||||
{
|
||||
return POS(get_moveX(property, board_size), get_moveY(property, board_size));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
656
gnugo/engine/shapes.c
Normal file
656
gnugo/engine/shapes.c
Normal file
@ -0,0 +1,656 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "patterns.h"
|
||||
|
||||
/* Maximum number of dragons considered by a, B, C, and d class patterns. */
|
||||
#define MAX_DRAGONS_PER_PATTERN 5
|
||||
#define MAX_STRINGS_PER_PATTERN 5
|
||||
|
||||
/* Values of joseki patterns. */
|
||||
#define U_VALUE 40.0
|
||||
#define J_VALUE 35.0
|
||||
#define j_VALUE 24.0
|
||||
#define t_VALUE 16.0
|
||||
|
||||
|
||||
/* Take care of joseki patterns. */
|
||||
static void
|
||||
handle_joseki_patterns(struct pattern_attribute *attributes,
|
||||
unsigned int class, int move,
|
||||
int my_dragons[MAX_DRAGONS_PER_PATTERN],
|
||||
int my_ndragons,
|
||||
int your_dragons[MAX_DRAGONS_PER_PATTERN],
|
||||
int your_ndragons)
|
||||
{
|
||||
struct pattern_attribute *attribute;
|
||||
|
||||
/* Pattern class J, joseki standard move. Add expand territory and
|
||||
* moyo, and require the value at least J_value.
|
||||
*/
|
||||
if (class & CLASS_J) {
|
||||
TRACE("...joseki standard move\n");
|
||||
add_expand_territory_move(move);
|
||||
TRACE("...expands territory\n");
|
||||
add_expand_moyo_move(move);
|
||||
TRACE("...expands moyo\n");
|
||||
set_minimum_move_value(move, J_VALUE);
|
||||
TRACE("... minimum move value %f\n", J_VALUE);
|
||||
}
|
||||
|
||||
/* Class `j' and `t' patterns are treated similarly. */
|
||||
if (class & (CLASS_j | CLASS_t)) {
|
||||
float min_value;
|
||||
float shape_value = 0.0;
|
||||
|
||||
if (class & CLASS_j) {
|
||||
min_value = j_VALUE;
|
||||
TRACE("...less urgent joseki move\n");
|
||||
add_expand_territory_move(move);
|
||||
TRACE("...expands territory\n");
|
||||
add_expand_moyo_move(move);
|
||||
TRACE("...expands moyo\n");
|
||||
}
|
||||
else {
|
||||
min_value = t_VALUE;
|
||||
TRACE("...minor joseki move\n");
|
||||
}
|
||||
|
||||
/* Board size modification. */
|
||||
min_value *= board_size / 19.0;
|
||||
|
||||
for (attribute = attributes; attribute->type != LAST_ATTRIBUTE;
|
||||
attribute++) {
|
||||
if (attribute->type == SHAPE) {
|
||||
shape_value = attribute->value;
|
||||
min_value *= (1 + 0.01 * shape_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((board_size >= 17) && (class & CLASS_F)) {
|
||||
/* Otherwise, `j' and `t' patterns not of CLASS_F would get
|
||||
* preferred in value_move_reasons().
|
||||
*/
|
||||
min_value *= 1.005;
|
||||
|
||||
set_maximum_move_value(move, min_value);
|
||||
scale_randomness(move, 5.0);
|
||||
TRACE("...move value %f (shape %f)\n", min_value, shape_value);
|
||||
}
|
||||
else
|
||||
TRACE("...minimum move value %f (shape %f)\n", min_value, shape_value);
|
||||
|
||||
set_minimum_move_value(move, min_value);
|
||||
}
|
||||
|
||||
/* Pattern class U, very urgent joseki move. Add strategical defense
|
||||
* and attack, plus a shape bonus of 15 and a minimum value of 40.
|
||||
*/
|
||||
if (class & CLASS_U) {
|
||||
int k;
|
||||
|
||||
TRACE("...joseki urgent move\n");
|
||||
for (k = 0; k < my_ndragons; k++) {
|
||||
add_strategical_defense_move(move, my_dragons[k]);
|
||||
TRACE("...strategical defense of %1m\n", my_dragons[k]);
|
||||
}
|
||||
for (k = 0; k < your_ndragons; k++) {
|
||||
add_strategical_attack_move(move, your_dragons[k]);
|
||||
TRACE("...strategical attack on %1m\n", your_dragons[k]);
|
||||
}
|
||||
add_shape_value(move, 15);
|
||||
TRACE("...shape value 15\n");
|
||||
set_minimum_move_value(move, U_VALUE);
|
||||
TRACE("...(min) move value %f\n", U_VALUE);
|
||||
}
|
||||
|
||||
/* Pattern class T, joseki trick move. For the moment we never play
|
||||
* these.
|
||||
*/
|
||||
if (class & CLASS_T) {
|
||||
TRACE("...joseki trick move\n");
|
||||
add_antisuji_move(move);
|
||||
TRACE("...antisuji\n");
|
||||
}
|
||||
|
||||
for (attribute = attributes; attribute->type != LAST_ATTRIBUTE;
|
||||
attribute++) {
|
||||
switch (attribute->type) {
|
||||
case MIN_VALUE:
|
||||
set_minimum_move_value(move, attribute->value);
|
||||
TRACE("...(min) move value %f\n", attribute->value);
|
||||
break;
|
||||
|
||||
case MAX_VALUE:
|
||||
set_maximum_move_value(move, attribute->value);
|
||||
TRACE("...max move value %f\n", attribute->value);
|
||||
break;
|
||||
|
||||
case MIN_TERRITORY:
|
||||
set_minimum_territorial_value(move, attribute->value);
|
||||
TRACE("...(min) territorial value %f\n", attribute->value);
|
||||
break;
|
||||
|
||||
case MAX_TERRITORY:
|
||||
set_maximum_territorial_value(move, attribute->value);
|
||||
TRACE("...max territorial value %f\n", attribute->value);
|
||||
break;
|
||||
|
||||
case SHAPE:
|
||||
/* For class `j' and `t' patterns shape value has been counted
|
||||
* already.
|
||||
*/
|
||||
if (!(class & (CLASS_j | CLASS_t))) {
|
||||
add_shape_value(move, attribute->value);
|
||||
TRACE("...shape value %f\n", attribute->value);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case FOLLOWUP:
|
||||
add_followup_value(move, attribute->value);
|
||||
TRACE("...followup value %f\n", attribute->value);
|
||||
break;
|
||||
|
||||
case REVERSE_FOLLOWUP:
|
||||
add_reverse_followup_value(move, attribute->value);
|
||||
TRACE("...reverse followup value %f\n", attribute->value);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Must not happen. */
|
||||
gg_assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This callback is invoked for each matched pattern.
|
||||
*/
|
||||
static void
|
||||
shapes_callback(int anchor, int color, struct pattern *pattern, int ll,
|
||||
void *data)
|
||||
{
|
||||
int other = OTHER_COLOR(color);
|
||||
|
||||
int k, l;
|
||||
int move;
|
||||
/* For restricted search, the pattern must intersect the search area */
|
||||
|
||||
/* Dragons of our color. */
|
||||
int my_dragons[MAX_DRAGONS_PER_PATTERN];
|
||||
int my_ndragons = 0;
|
||||
|
||||
/* Dragons of other color. */
|
||||
int your_dragons[MAX_DRAGONS_PER_PATTERN];
|
||||
int your_ndragons = 0;
|
||||
|
||||
/* Strings of our color. */
|
||||
int my_strings[MAX_STRINGS_PER_PATTERN];
|
||||
int my_nstrings = 0;
|
||||
|
||||
/* Strings of other color. */
|
||||
int your_strings[MAX_STRINGS_PER_PATTERN];
|
||||
int your_nstrings = 0;
|
||||
|
||||
/* Make a local copy of the classification that we may modify. */
|
||||
unsigned int class = pattern->class;
|
||||
|
||||
/* Don't accept fuseki marked patterns while scoring. */
|
||||
if (doing_scoring && (class & CLASS_F))
|
||||
return;
|
||||
|
||||
/* Don't need auxiliary data in this callback. */
|
||||
UNUSED(data);
|
||||
|
||||
/* Pick up the location of the move */
|
||||
move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
|
||||
|
||||
/* For some classes of patterns we need to find all dragons present
|
||||
* in the pattern.
|
||||
*/
|
||||
if ((class & (CLASS_B | CLASS_C | CLASS_c | CLASS_a | CLASS_d | CLASS_O
|
||||
| CLASS_J | CLASS_j | CLASS_U | CLASS_T | CLASS_t)) != 0) {
|
||||
/* Match each point. */
|
||||
for (k = 0; k < pattern->patlen; ++k) {
|
||||
int pos; /* absolute (board) co-ord of (transformed) pattern element */
|
||||
int origin; /* dragon origin */
|
||||
|
||||
/* all the following stuff (currently) applies only at occupied cells */
|
||||
if (pattern->patn[k].att == ATT_dot)
|
||||
continue;
|
||||
|
||||
/* transform pattern real coordinate */
|
||||
pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
|
||||
|
||||
/* Already, matchpat rejects O patterns containing a friendly stone with
|
||||
* DEAD or CRITICAL matcher_status. If the stone is tactically
|
||||
* CRITICAL it still could have matcher_status ALIVE since it might
|
||||
* be amalgamated into a live dragon. In this case we want to reject the
|
||||
* pattern if (move) does not rescue it. This is most easily tested
|
||||
* here within shapes_callback(), since the value of (move) is not
|
||||
* known by matchpat().
|
||||
*/
|
||||
if ((class & CLASS_O)
|
||||
&& board[pos] == color
|
||||
&& worm[pos].attack_points[0] != 0
|
||||
&& !does_defend(move, pos))
|
||||
return;
|
||||
|
||||
origin = dragon[pos].origin;
|
||||
if (board[pos] == color && my_ndragons < MAX_DRAGONS_PER_PATTERN) {
|
||||
for (l = 0; l < my_ndragons; l++) {
|
||||
if (my_dragons[l] == origin)
|
||||
break;
|
||||
}
|
||||
if (l == my_ndragons) {
|
||||
/* We found another dragon of our color. Check that it (or
|
||||
* rather the underlying worm) cannot be tactically
|
||||
* captured before adding it to the list of my_dragons.
|
||||
*/
|
||||
if (worm[pos].attack_codes[0] == 0
|
||||
|| does_defend(move, pos)) {
|
||||
/* Ok, add the dragon to the list. */
|
||||
my_dragons[l] = origin;
|
||||
my_ndragons++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (board[pos] == other && your_ndragons < MAX_DRAGONS_PER_PATTERN) {
|
||||
for (l = 0; l < your_ndragons; l++) {
|
||||
if (your_dragons[l] == origin)
|
||||
break;
|
||||
}
|
||||
if (l == your_ndragons) {
|
||||
/* We found another opponent dragon, add it to the list. */
|
||||
your_dragons[l] = origin;
|
||||
your_ndragons++;
|
||||
}
|
||||
}
|
||||
|
||||
if (pattern->patn[k].att == ATT_O || pattern->patn[k].att == ATT_X) {
|
||||
origin = find_origin(pos);
|
||||
if (board[pos] == color && my_nstrings < MAX_STRINGS_PER_PATTERN) {
|
||||
for (l = 0; l < my_nstrings; l++) {
|
||||
if (my_strings[l] == origin)
|
||||
break;
|
||||
}
|
||||
if (l == my_nstrings) {
|
||||
/* We found another string of our color. Check that it
|
||||
* cannot be tactically captured before adding it to the
|
||||
* list of my_strings.
|
||||
*/
|
||||
if (worm[pos].attack_codes[0] == 0
|
||||
|| does_defend(move, pos)) {
|
||||
/* Ok, add the string to the list. */
|
||||
my_strings[l] = origin;
|
||||
my_nstrings++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (board[pos] == other && your_nstrings < MAX_STRINGS_PER_PATTERN) {
|
||||
for (l = 0; l < your_nstrings; l++) {
|
||||
if (your_strings[l] == origin)
|
||||
break;
|
||||
}
|
||||
if (l == your_nstrings) {
|
||||
/* We found another opponent string, add it to the list. */
|
||||
your_strings[l] = origin;
|
||||
your_nstrings++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* loop over elements */
|
||||
} /* if we need to loop over the elements */
|
||||
|
||||
/* Nothing to connect. Remove C class bit. */
|
||||
if (my_nstrings < 2)
|
||||
class &= ~CLASS_C;
|
||||
|
||||
/* Nothing to cut. Remove B class bit. */
|
||||
if (your_nstrings < 2)
|
||||
class &= ~CLASS_B;
|
||||
|
||||
/*
|
||||
* If this pattern can't produce any effect (e.g. if it was a B or C
|
||||
* pattern with only one dragon of the appropriate color), don't
|
||||
* do any expensive checking but return immediately.
|
||||
* If it only has some move_values, these will be ignored.
|
||||
*/
|
||||
if (!pattern->helper
|
||||
&& !allpats
|
||||
&& !(pattern->autohelper_flag & HAVE_ACTION)
|
||||
&& !(class & CLASS_MOVE_REASONS)
|
||||
&& pattern->attributes->type == LAST_ATTRIBUTE)
|
||||
return;
|
||||
|
||||
/* For sacrifice patterns, the survival of the stone to be played is
|
||||
* not checked (but it still needs to be legal). Otherwise we
|
||||
* discard moves which can be captured.
|
||||
*/
|
||||
if (!(class & CLASS_s)) {
|
||||
/* Don't allow ko unsafety. */
|
||||
if (safe_move(move, color) != WIN) {
|
||||
if (0)
|
||||
TRACE(" move at %1m wasn't safe, discarded\n", move);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Allow illegal ko captures at this stage. */
|
||||
if (!is_ko(move, color, NULL) && !is_legal(move, color)) {
|
||||
if (0)
|
||||
TRACE(" move at %1m wasn't legal, discarded\n", move);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* For class n patterns, the pattern is contingent on an opponent
|
||||
* move at * not being captured.
|
||||
*/
|
||||
if (class & CLASS_n) {
|
||||
/* Allow ko unsafety. */
|
||||
if (safe_move(move, other) == 0) {
|
||||
if (0)
|
||||
TRACE(" opponent can't play safely at %1m, move discarded\n", move);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the pattern has a constraint, call the autohelper to see
|
||||
* if the pattern must be rejected.
|
||||
*/
|
||||
if (pattern->autohelper_flag & HAVE_CONSTRAINT) {
|
||||
if (!pattern->autohelper(ll, move, color, 0))
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ask helper for acceptance of pattern. */
|
||||
if (pattern->helper) {
|
||||
/* ask helper function to consider the move */
|
||||
int accepted;
|
||||
DEBUG(DEBUG_HELPER, " asking helper to consider '%s'+%d at %1m\n",
|
||||
pattern->name, ll, move);
|
||||
accepted = pattern->helper(pattern, ll, move, color);
|
||||
|
||||
if (accepted) {
|
||||
DEBUG(DEBUG_HELPER, "helper likes pattern '%s' at %1m\n",
|
||||
pattern->name, move);
|
||||
}
|
||||
else {
|
||||
DEBUG(DEBUG_HELPER, " helper does not like pattern '%s' at %1m\n",
|
||||
pattern->name, move);
|
||||
return; /* pattern matcher does not like it */
|
||||
}
|
||||
}
|
||||
|
||||
/* If using -a, want to see all matches even if not -v */
|
||||
if (allpats || verbose) {
|
||||
TRACE("pattern '%s'+%d matched at %1m\n", pattern->name, ll, move);
|
||||
}
|
||||
|
||||
/* does the pattern have an action? */
|
||||
if (pattern->autohelper_flag & HAVE_ACTION)
|
||||
pattern->autohelper(ll, move, color, 1);
|
||||
|
||||
/* Pattern class B, try to cut all combinations of opponent strings. */
|
||||
if (class & CLASS_B) {
|
||||
for (k = 0; k < your_nstrings; k++)
|
||||
for (l = k+1; l < your_nstrings; l++) {
|
||||
if (string_connect(your_strings[k], your_strings[l], NULL)
|
||||
&& !play_connect_n(color, 1, 1, move,
|
||||
your_strings[k], your_strings[l])) {
|
||||
add_cut_move(move, your_strings[k], your_strings[l]);
|
||||
TRACE("...cuts strings %1m, %1m\n",
|
||||
your_strings[k], your_strings[l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pattern class C, try to connect all combinations of our strings. */
|
||||
if (class & CLASS_C) {
|
||||
for (k = 0; k < my_nstrings; k++)
|
||||
for (l = k+1; l < my_nstrings; l++) {
|
||||
if (disconnect(my_strings[k], my_strings[l], NULL)
|
||||
&& !play_connect_n(color, 0, 1, move,
|
||||
my_strings[k], my_strings[l])) {
|
||||
add_connection_move(move, my_strings[k], my_strings[l]);
|
||||
TRACE("...connects strings %1m, %1m\n",
|
||||
my_strings[k], my_strings[l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pattern class c, add strategical defense move reason for all our
|
||||
* dragons and a small shape bonus.
|
||||
*
|
||||
* This is a preliminary effect of "weak connection" and may need to
|
||||
* be revised.
|
||||
*/
|
||||
if (class & CLASS_c) {
|
||||
for (k = 0; k < my_ndragons; k++) {
|
||||
add_strategical_defense_move(move, my_dragons[k]);
|
||||
TRACE("...strategical defense (weak connection) of %1m\n",
|
||||
my_dragons[k]);
|
||||
}
|
||||
add_shape_value(move, 1);
|
||||
TRACE("...shape value 1\n");
|
||||
}
|
||||
|
||||
/* Pattern class b is obsolete in the pattern databases handled here. */
|
||||
gg_assert(!(class & CLASS_b));
|
||||
|
||||
/* Pattern class e, expand to make territory. */
|
||||
if (class & CLASS_e) {
|
||||
add_expand_territory_move(move);
|
||||
TRACE("...expands territory\n");
|
||||
}
|
||||
|
||||
/* Pattern class E, expand to make moyo. */
|
||||
if (class & CLASS_E) {
|
||||
add_expand_moyo_move(move);
|
||||
TRACE("...expands moyo\n");
|
||||
}
|
||||
|
||||
/* Pattern class i, an invasion. */
|
||||
if (class & CLASS_I) {
|
||||
add_invasion_move(move);
|
||||
TRACE("...is an invasion\n");
|
||||
}
|
||||
|
||||
/* Pattern class a, strategical level attack on all opponent dragons. */
|
||||
if (class & CLASS_a) {
|
||||
for (k = 0; k < your_ndragons; k++) {
|
||||
add_strategical_attack_move(move, your_dragons[k]);
|
||||
TRACE("...strategical attack on %1m\n", your_dragons[k]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pattern class d, strategical level defense of all own dragons. */
|
||||
if (class & CLASS_d) {
|
||||
for (k = 0; k < my_ndragons; k++) {
|
||||
add_strategical_defense_move(move, my_dragons[k]);
|
||||
TRACE("...strategical defense of %1m\n", my_dragons[k]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pattern class W, worthwhile threat move. */
|
||||
if (class & CLASS_W) {
|
||||
TRACE("...worthwhile threat move\n");
|
||||
add_worthwhile_threat_move(move);
|
||||
}
|
||||
|
||||
handle_joseki_patterns(pattern->attributes, class, move,
|
||||
my_dragons, my_ndragons, your_dragons, your_ndragons);
|
||||
}
|
||||
|
||||
|
||||
/* This callback is invoked for each matched pattern from joseki
|
||||
* database. This function is just a copy of relevant parts of
|
||||
* shapes_callback(). However, most of the common code resides in
|
||||
* handle_joseki_patterns().
|
||||
*/
|
||||
static void
|
||||
joseki_callback(int move, int color, struct corner_pattern *pattern,
|
||||
int trans, int *stones, int num_stones)
|
||||
{
|
||||
int k, l;
|
||||
int class = pattern->class;
|
||||
|
||||
/* Dragons of our color. */
|
||||
int my_dragons[MAX_DRAGONS_PER_PATTERN];
|
||||
int my_ndragons = 0;
|
||||
|
||||
/* Dragons of other color. */
|
||||
int your_dragons[MAX_DRAGONS_PER_PATTERN];
|
||||
int your_ndragons = 0;
|
||||
|
||||
/* For urgent joseki patterns we need to find all dragons present in the
|
||||
* pattern since such patterns are assumed to have strategical effect on
|
||||
* them.
|
||||
*/
|
||||
if (class & CLASS_U) {
|
||||
/* Loop over all stones in the pattern. */
|
||||
for (k = 0; k < num_stones; k++) {
|
||||
int pos = stones[k];
|
||||
int origin = dragon[pos].origin;
|
||||
|
||||
if (board[pos] == color && my_ndragons < MAX_DRAGONS_PER_PATTERN) {
|
||||
for (l = 0; l < my_ndragons; l++) {
|
||||
if (my_dragons[l] == origin)
|
||||
break;
|
||||
}
|
||||
|
||||
if (l == my_ndragons) {
|
||||
/* We found another dragon of our color. Check that it (or
|
||||
* rather the underlying worm) cannot be tactically
|
||||
* captured before adding it to the list of my_dragons.
|
||||
*/
|
||||
if (worm[pos].attack_codes[0] == 0 || does_defend(move, pos)) {
|
||||
/* Ok, add the dragon to the list. */
|
||||
my_dragons[l] = origin;
|
||||
my_ndragons++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (board[pos] != color && your_ndragons < MAX_DRAGONS_PER_PATTERN) {
|
||||
for (l = 0; l < your_ndragons; l++) {
|
||||
if (your_dragons[l] == origin)
|
||||
break;
|
||||
}
|
||||
if (l == your_ndragons) {
|
||||
/* We found another opponent dragon, add it to the list. */
|
||||
your_dragons[l] = origin;
|
||||
your_ndragons++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* For joseki patterns we don't check if the proposed move is safe or legal.
|
||||
*/
|
||||
|
||||
/* If the pattern has a constraint, call the autohelper to see
|
||||
* if the pattern must be rejected.
|
||||
*/
|
||||
if (pattern->autohelper_flag & HAVE_CONSTRAINT) {
|
||||
if (!pattern->autohelper(trans, move, color, 0))
|
||||
return;
|
||||
}
|
||||
|
||||
/* If using -a, want to see all matches even if not -v. */
|
||||
if (allpats || verbose)
|
||||
TRACE("pattern '%s'+%d matched at %1m\n", pattern->name, trans, move);
|
||||
|
||||
/* Does the pattern have an action? */
|
||||
if (pattern->autohelper_flag & HAVE_ACTION)
|
||||
pattern->autohelper(trans, move, color, 1);
|
||||
|
||||
/* Pattern class N, antisuji move. */
|
||||
if (class & CLASS_N) {
|
||||
TRACE("...antisuji move\n");
|
||||
add_antisuji_move(move);
|
||||
}
|
||||
|
||||
handle_joseki_patterns(pattern->attributes, class, move,
|
||||
my_dragons, my_ndragons, your_dragons, your_ndragons);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Match all patterns in patterns.db and patterns2.db on all positions.
|
||||
*
|
||||
* This function is one of the basic generators of move reasons, called
|
||||
* by genmove().
|
||||
*/
|
||||
void
|
||||
shapes(int color)
|
||||
{
|
||||
TRACE("\nPattern matcher is looking for move reasons for %s!\n",
|
||||
color_to_string(color));
|
||||
|
||||
matchpat(shapes_callback, color, &pat_db, NULL, NULL);
|
||||
|
||||
/* Don't match joseki patterns while scoring. */
|
||||
if (josekidb && !doing_scoring)
|
||||
#if 1
|
||||
corner_matchpat(joseki_callback, color, &joseki_db);
|
||||
#else
|
||||
matchpat(shapes_callback, color, &joseki_db, NULL, NULL);
|
||||
#endif
|
||||
|
||||
if (!disable_fuseki && !doing_scoring)
|
||||
matchpat(shapes_callback, color, &fusekipat_db, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Match all patterns in endgame.db on all positions.
|
||||
*/
|
||||
void
|
||||
endgame_shapes(int color)
|
||||
{
|
||||
TRACE("\nEndgame pattern matcher is looking for move reasons for %s!\n",
|
||||
color_to_string(color));
|
||||
|
||||
matchpat(shapes_callback, color, &endpat_db, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
374
gnugo/engine/showbord.c
Normal file
374
gnugo/engine/showbord.c
Normal file
@ -0,0 +1,374 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/*-------------------------------------------------------------
|
||||
showbord.c -- Show current go board and playing information
|
||||
-------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* NOTE : this is no longer intended as the main user interface
|
||||
* as it was in GNU Go 1.2. It is now a debugging aid, showing
|
||||
* the internal state of dragons, and things. But with
|
||||
* color enabled, it should be easy enough to see the state
|
||||
* of play at a glance.
|
||||
*
|
||||
* Note : the dragons must have been calculated before this is called
|
||||
*/
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "gg_utils.h"
|
||||
|
||||
|
||||
/*
|
||||
* Stuff to enumerate the dragons
|
||||
*/
|
||||
|
||||
/* Element at origin of each worm stores allocated worm number. */
|
||||
static unsigned char dragon_num[BOARDMAX];
|
||||
|
||||
static int next_white; /* next worm number to allocate */
|
||||
static int next_black;
|
||||
|
||||
/* linux console :
|
||||
* 0=black
|
||||
* 1=red [critical]
|
||||
* 2=green [alive]
|
||||
* 3=yellow/brown [unknown]
|
||||
* 4=blue
|
||||
* 5=magenta
|
||||
* 6=cyan [dead]
|
||||
* 7=white [unchecked]
|
||||
*/
|
||||
|
||||
/* Both black and white are common background colors and should be
|
||||
* avoided.
|
||||
*/
|
||||
static const int colors[3][5] = {
|
||||
{0, 0, 0, 0, 0}, /*not used */
|
||||
{6, 2, 1, 3, 5}, /* WHITE : dead, alive, critical, unknown, unchecked */
|
||||
{6, 2, 1, 3, 5} /* BLACK : dead, alive, critical, unknown, unchecked */
|
||||
};
|
||||
|
||||
static const int domain_colors[4] = {5, 1, 2, 3}; /* gray, black, white, both */
|
||||
|
||||
|
||||
/* The following four functions define an API for drawing boards. The
|
||||
* typical use would be along the following lines:
|
||||
*
|
||||
* start_draw_board();
|
||||
* for (m = 0; m < board_size; m++)
|
||||
* for (n = 0; n < board_size; n++) {
|
||||
* int color = ...;
|
||||
* int c = ...;
|
||||
* draw_color_char(m, n, c, color);
|
||||
* }
|
||||
* end_draw_board();
|
||||
*
|
||||
* Coordinate system, hoshi points, and linefeeds are written
|
||||
* automatically by the board drawing functions. The coordinates m, n
|
||||
* must be ordered as in the full loops above.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Init color and print a line with coordinate letters above the board. */
|
||||
void
|
||||
start_draw_board()
|
||||
{
|
||||
gg_init_color();
|
||||
draw_letter_coordinates(stderr);
|
||||
}
|
||||
|
||||
/* Draw a colored character. If c has the value EMPTY, either a "." or
|
||||
* a "+" is drawn, depending on whether it is a hoshi stone. If this
|
||||
* is the first or last intersection on a line, the coordinate number
|
||||
* is also drawn.
|
||||
*/
|
||||
void
|
||||
draw_color_char(int m, int n, int c, int color)
|
||||
{
|
||||
/* Is this the first column? */
|
||||
if (n == 0)
|
||||
fprintf(stderr, "\n%2d", board_size - m);
|
||||
|
||||
/* Do we see a hoshi point? */
|
||||
if (c == EMPTY) {
|
||||
if (is_hoshi_point(m, n))
|
||||
c = '+';
|
||||
else
|
||||
c = '.';
|
||||
}
|
||||
|
||||
/* Use fprintf to draw black characters. This way they'll turn out
|
||||
* white on terminals with black background.
|
||||
*/
|
||||
if (color == GG_COLOR_BLACK)
|
||||
fprintf(stderr, " %c", c);
|
||||
else
|
||||
write_color_char(color, c);
|
||||
|
||||
/* Is this the last column? */
|
||||
if (n == board_size - 1)
|
||||
fprintf(stderr, " %-2d", board_size - m);
|
||||
}
|
||||
|
||||
/* Draw a black character as specified above. */
|
||||
void
|
||||
draw_char(int m, int n, int c)
|
||||
{
|
||||
draw_color_char(m, n, c, GG_COLOR_BLACK);
|
||||
}
|
||||
|
||||
/* Print a line with coordinate letters under the board. */
|
||||
void
|
||||
end_draw_board()
|
||||
{
|
||||
fprintf(stderr, "\n");
|
||||
draw_letter_coordinates(stderr);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write one stone. Use 'empty' if the board is empty ('-' or '+')
|
||||
* We use capital letters A,B,... for black, lower case a,b,... for white.
|
||||
* This allows us to indicate up to 26 dragons uniquely, and more with
|
||||
* low risk of ambiguity.
|
||||
*/
|
||||
|
||||
/* The variable xo=1 if running gnugo -T, 2 if running gnugo -E, or
|
||||
* 3 if displaying owl_status.
|
||||
*/
|
||||
|
||||
static void
|
||||
showchar(int i, int j, int empty, int xo)
|
||||
{
|
||||
struct dragon_data *d; /* dragon data at (i, j) */
|
||||
struct dragon_data2 *d2;
|
||||
int x;
|
||||
ASSERT_ON_BOARD2(i, j);
|
||||
x = BOARD(i, j);
|
||||
d = &(dragon[POS(i, j)]);
|
||||
d2 = &(dragon2[d->id]);
|
||||
|
||||
if (x == EMPTY) {
|
||||
if (xo != 2)
|
||||
fprintf(stderr, " %c", empty);
|
||||
else {
|
||||
int empty_color;
|
||||
char empty_char;
|
||||
|
||||
if (black_eye[POS(i, j)].color == BLACK) {
|
||||
if (white_eye[POS(i, j)].color == WHITE)
|
||||
empty_color = domain_colors[3];
|
||||
else
|
||||
empty_color = domain_colors[1];
|
||||
|
||||
if (black_eye[POS(i, j)].marginal)
|
||||
empty_char = '!';
|
||||
else
|
||||
empty_char = 'x';
|
||||
}
|
||||
else if (white_eye[POS(i, j)].color == WHITE) {
|
||||
empty_color = domain_colors[2];
|
||||
if (white_eye[POS(i, j)].marginal)
|
||||
empty_char = '!';
|
||||
else
|
||||
empty_char = 'o';
|
||||
}
|
||||
else {
|
||||
empty_color = domain_colors[0];
|
||||
empty_char = '.';
|
||||
}
|
||||
|
||||
write_color_char(empty_color, empty_char);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int w;
|
||||
|
||||
if (xo == 0 || ! ON_BOARD1(d->origin)) {
|
||||
fprintf(stderr, " %c", BOARD(i, j) == BLACK ? 'X' : 'O');
|
||||
return;
|
||||
}
|
||||
|
||||
/* Figure out ascii character for this dragon. This is the
|
||||
* dragon number allocated to the origin of this worm. */
|
||||
|
||||
w = dragon_num[d->origin];
|
||||
if (!w) {
|
||||
/* Not yet allocated - allocate next one. */
|
||||
/* Count upwards for black, downwards for white to reduce confusion. */
|
||||
if (BOARD(i, j) == BLACK)
|
||||
w = dragon_num[d->origin] = next_black++;
|
||||
else
|
||||
w = dragon_num[d->origin] = next_white--;
|
||||
}
|
||||
|
||||
w = w%26 + (BOARD(i, j) == BLACK ? 'A' : 'a');
|
||||
|
||||
/* Now draw it. */
|
||||
if (xo == 1)
|
||||
write_color_char(colors[BOARD(i, j)][d->crude_status], w);
|
||||
else if (xo == 2) {
|
||||
if (BOARD(i, j) == BLACK)
|
||||
write_color_char(domain_colors[1], 'X');
|
||||
else
|
||||
write_color_char(domain_colors[2], 'O');
|
||||
}
|
||||
else if (xo == 3)
|
||||
write_color_char(colors[BOARD(i, j)][d2->owl_status], w);
|
||||
else if (xo == 4)
|
||||
write_color_char(colors[BOARD(i, j)][d->status], w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Show go board.
|
||||
*
|
||||
* xo=0: black and white XO board for ascii game
|
||||
* xo=1: colored dragon display
|
||||
* xo=2: colored eye display
|
||||
* xo=3: colored owl display
|
||||
* xo=4: colored matcher status display
|
||||
*
|
||||
*/
|
||||
|
||||
void
|
||||
showboard(int xo)
|
||||
{
|
||||
int i, j, ii;
|
||||
gg_init_color();
|
||||
|
||||
/* Set all dragon numbers to 0. */
|
||||
memset(dragon_num, 0, sizeof(dragon_num));
|
||||
|
||||
next_white = (259 - 26);
|
||||
next_black = 26;
|
||||
|
||||
start_draw_board();
|
||||
|
||||
for (i = 0; i < board_size; i++) {
|
||||
ii = board_size - i;
|
||||
fprintf(stderr, "\n%2d", ii);
|
||||
|
||||
for (j = 0; j < board_size; j++)
|
||||
showchar(i, j, is_hoshi_point(i, j) ? '+' : '.', xo);
|
||||
|
||||
fprintf(stderr, " %d", ii);
|
||||
|
||||
if (xo == 0 && ((board_size < 10 && i == board_size-2)
|
||||
|| (board_size >= 10 && i == 8)))
|
||||
fprintf(stderr, " WHITE (O) has captured %d stones", black_captured);
|
||||
|
||||
if (xo == 0 && ((board_size < 10 && i == board_size-1)
|
||||
|| (board_size >= 10 && i == 9)))
|
||||
fprintf(stderr, " BLACK (X) has captured %d stones", white_captured);
|
||||
|
||||
if (xo == 3) {
|
||||
if (i == board_size-5)
|
||||
write_color_string(GG_COLOR_GREEN, " green=alive");
|
||||
if (i == board_size-4)
|
||||
write_color_string(GG_COLOR_CYAN, " cyan=dead");
|
||||
if (i == board_size-3)
|
||||
write_color_string(GG_COLOR_RED, " red=critical");
|
||||
if (i == board_size-2)
|
||||
write_color_string(GG_COLOR_YELLOW, " yellow=unknown");
|
||||
if (i == board_size-1)
|
||||
write_color_string(GG_COLOR_MAGENTA, " magenta=unchecked");
|
||||
}
|
||||
}
|
||||
|
||||
end_draw_board();
|
||||
}
|
||||
|
||||
|
||||
/* Some print utility function that don't really have a better place
|
||||
* in the engine code than here.
|
||||
*/
|
||||
|
||||
static const char *status_names[] = {
|
||||
DRAGON_STATUS_NAMES
|
||||
};
|
||||
|
||||
/* Convert a status value to a string. */
|
||||
const char *
|
||||
status_to_string(enum dragon_status status)
|
||||
{
|
||||
return status_names[(int) status];
|
||||
}
|
||||
|
||||
|
||||
/* Convert a read result to a string */
|
||||
const char *
|
||||
result_to_string(int result)
|
||||
{
|
||||
switch (result) {
|
||||
case 0: return "0";
|
||||
case KO_B: return "KO_B";
|
||||
case LOSS: return "LOSS";
|
||||
case GAIN: return "GAIN";
|
||||
case KO_A: return "KO_A";
|
||||
case WIN: return "WIN";
|
||||
|
||||
default: return "ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef HAVE_VARIADIC_DEFINE
|
||||
|
||||
/* See gnugo.h for related TRACE family macro definitions */
|
||||
|
||||
/* Always returns 1 to allow use in short-circuit logical expressions. */
|
||||
int
|
||||
DEBUG_func(int flag, const char *fmt, ...)
|
||||
{
|
||||
if (debug & flag) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vgprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /*HAVE_VARIADIC_DEFINE*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
689
gnugo/engine/surround.c
Normal file
689
gnugo/engine/surround.c
Normal file
@ -0,0 +1,689 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "liberty.h"
|
||||
#include "gg_utils.h"
|
||||
|
||||
/* Forward declarations */
|
||||
static int goal_dist(int pos, signed char goal[BOARDMAX]);
|
||||
static int compare_angles(const void *a, const void *b);
|
||||
static void show_surround_map(signed char mf[BOARDMAX],
|
||||
signed char mn[BOARDMAX]);
|
||||
|
||||
/* Globals */
|
||||
static int gg; /* stores the gravity center of the goal */
|
||||
|
||||
|
||||
/* Returns true if a dragon is enclosed within the convex hull of
|
||||
* its hostile neighbor dragons. This is an indication that the dragon is
|
||||
* in danger. Stones on the second and first lines are not tested.
|
||||
*
|
||||
* Normally NULL will be passed to the parameter apos. It can be
|
||||
* an empty board location. If apos is non NULL it is marked and
|
||||
* added to the the hull. Thus we can ask if adding a single stone
|
||||
* to the board surrounds the dragon.
|
||||
*
|
||||
* A CORNER is a vertex of the polygon which comprises this convex
|
||||
* hull. The algorithm proceeds by first finding the sequence of
|
||||
* corners on the left side of the polyhedron, then the sequence
|
||||
* of corners on the right side.
|
||||
*
|
||||
* The hull is marked in the array mn with the number 1. A slight
|
||||
* expansion is marked with the number 2. Return code is SURROUNDED if
|
||||
* the friendly dragon lies within the area marked 1,
|
||||
* WEAKLY_SURROUNDED if it lies in the slightly larger area marked 1
|
||||
* and 2, and 0 otherwise.
|
||||
*
|
||||
* The notion of weak surroundedness seems to be much less indicative
|
||||
* of a dragon's immanent danger than surroundedness.
|
||||
*
|
||||
* An exception: if the larger area contains any stone of a different
|
||||
* friendly dragon (which is not DEAD) the return code is 0, unless
|
||||
* that allied dragon is ENTIRELY contained within the hull.
|
||||
*
|
||||
* Another exception: an ikken tobi (one space jump) is generally not
|
||||
* a connection but in practice may be almost as good. If there is an
|
||||
* ikken tobi out of the hull, then the dragon is not surrounded.
|
||||
*
|
||||
* If the parameter showboard is 1, the figure is drawn. If showboard
|
||||
* is 2, the figure is only drawn if the region is surrounded.
|
||||
*
|
||||
* If (apos) is NULL, the result is saved in the surround_data cache.
|
||||
* The assumption is that the function will only be called once
|
||||
* with (apos) null, during make_dragons; thereafter the surroundedness
|
||||
* will be accessed using the function is_surrounded().
|
||||
*
|
||||
* If *surround_size is not a NULL pointer, then surround_size
|
||||
* returns the size of the surroundings.
|
||||
*/
|
||||
|
||||
int
|
||||
compute_surroundings(int pos, int apos, int showboard, int *surround_size)
|
||||
{
|
||||
int i, j;
|
||||
int m, n;
|
||||
int k;
|
||||
int dpos;
|
||||
int surrounded;
|
||||
|
||||
int left_corner[MAX_BOARD];
|
||||
int right_corner[MAX_BOARD];
|
||||
int corner[BOARDMAX];
|
||||
int left_corners = 0, right_corners = 0;
|
||||
int corners = 0;
|
||||
int top_row, bottom_row;
|
||||
int color = board[pos];
|
||||
int other = OTHER_COLOR(color);
|
||||
int gi = 0;
|
||||
int gj = 0;
|
||||
int stones = 0;
|
||||
int found_some;
|
||||
|
||||
signed char mf[BOARDMAX]; /* friendly dragon */
|
||||
signed char mn[BOARDMAX]; /* neighbor dragons */
|
||||
int sd[BOARDMAX]; /* distances to the goal */
|
||||
|
||||
if (DRAGON2(pos).hostile_neighbors == 0)
|
||||
return(0);
|
||||
|
||||
memset(mf, 0, sizeof(mf));
|
||||
memset(mn, 0, sizeof(mn));
|
||||
memset(sd, 0, sizeof(sd));
|
||||
|
||||
mark_dragon(pos, mf, 1);
|
||||
|
||||
/* mark hostile neighbors */
|
||||
|
||||
for (k = 0; k < DRAGON2(pos).neighbors; k++) {
|
||||
int nd = DRAGON(DRAGON2(pos).adjacent[k]).origin;
|
||||
|
||||
if (board[nd] != color) {
|
||||
if (0)
|
||||
gprintf("neighbor: %1m\n", nd);
|
||||
mark_dragon(nd, mn, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* descend markings from stones lying on the 2nd and third lines */
|
||||
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (ON_BOARD(dpos) && mn[dpos]) {
|
||||
for (k = 0; k < 4; k++) {
|
||||
int d = delta[k];
|
||||
if (!ON_BOARD(dpos + d))
|
||||
continue;
|
||||
if (!ON_BOARD(dpos + 2*d)) {
|
||||
if (board[dpos + d] == EMPTY)
|
||||
mn[dpos + d] = 1;
|
||||
}
|
||||
else if (!ON_BOARD(dpos + 3*d)) {
|
||||
if (board[dpos + d] == EMPTY
|
||||
&& board[dpos + 2*d] == EMPTY)
|
||||
mn[dpos + 2*d] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* compute minimum distances to the goal */
|
||||
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (ON_BOARD(dpos) && mn[dpos])
|
||||
sd[dpos] = goal_dist(dpos, mf);
|
||||
|
||||
/* revise markings */
|
||||
|
||||
do {
|
||||
found_some = 0;
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (ON_BOARD(dpos) && mn[dpos] && sd[dpos] > 8) {
|
||||
/* discard markings if we can find 2 stones
|
||||
* that verify :
|
||||
* - it is closer to the goal than we are
|
||||
* - it is closer to us than the goal is
|
||||
* - they are closer to each other than we are to the goal
|
||||
*/
|
||||
for (i = BOARDMIN; i < BOARDMAX; i++)
|
||||
if (ON_BOARD(i) && mn[i] && i != dpos
|
||||
&& sd[i] < sd[dpos]
|
||||
&& square_dist(i, dpos) < sd[dpos]) {
|
||||
for (j = i + 1; j < BOARDMAX; j++)
|
||||
if (ON_BOARD(j) && mn[j] && j != dpos
|
||||
&& sd[j] < sd[dpos]
|
||||
&& square_dist(j, dpos) < sd[dpos]
|
||||
&& square_dist(i, j) < sd[dpos]) {
|
||||
mn[dpos] = 0;
|
||||
found_some = 1;
|
||||
break;
|
||||
}
|
||||
if (mn[dpos] == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (found_some);
|
||||
|
||||
/* prepare corner array */
|
||||
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (ON_BOARD(dpos) && mn[dpos])
|
||||
corner[corners++] = dpos;
|
||||
|
||||
/* compute gravity center of the goal */
|
||||
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (ON_BOARD(dpos) && mf[dpos]) {
|
||||
gi += I(dpos);
|
||||
gj += J(dpos);
|
||||
stones++;
|
||||
}
|
||||
gi /= stones;
|
||||
gj /= stones;
|
||||
gg = POS(gi, gj);
|
||||
|
||||
/* sort the corner array */
|
||||
|
||||
gg_sort(corner, corners, sizeof(int), compare_angles);
|
||||
|
||||
/* if apos is not NO_MOVE, mark it. */
|
||||
|
||||
if (apos != NO_MOVE) {
|
||||
ASSERT_ON_BOARD1(apos);
|
||||
mn[apos] = 1;
|
||||
}
|
||||
|
||||
if (showboard == 1) {
|
||||
show_surround_map(mf, mn);
|
||||
}
|
||||
|
||||
/* find top row of surrounding polyhedron */
|
||||
|
||||
top_row = -1;
|
||||
for (m = 0; m < board_size; m++) {
|
||||
if (top_row != -1)
|
||||
break;
|
||||
for (n = 0; n < board_size; n++)
|
||||
if (mn[POS(m, n)]) {
|
||||
left_corner[0] = POS(m, n);
|
||||
top_row = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* find bottom row */
|
||||
|
||||
bottom_row = -1;
|
||||
for (m = board_size - 1; m >= 0; m--) {
|
||||
if (bottom_row != -1)
|
||||
break;
|
||||
for (n = 0; n < board_size; n++)
|
||||
if (mn[POS(m, n)]) {
|
||||
bottom_row = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* find the corners on the left side */
|
||||
|
||||
for (left_corners = 1; I(left_corner[left_corners-1]) < bottom_row;
|
||||
left_corners++) {
|
||||
int best_found = 0;
|
||||
float best_slope = 0.;
|
||||
int m = I(left_corner[left_corners-1]);
|
||||
int n = J(left_corner[left_corners-1]);
|
||||
|
||||
for (i = m + 1; i <= bottom_row; i++)
|
||||
for (j = 0; j < board_size; j++)
|
||||
if (mn[POS(i, j)]) {
|
||||
float slope = ((float) (j - n))/((float) (i - m));
|
||||
if (0)
|
||||
gprintf("(left) at %m, last %m, slope=%f\n", i, j, m, n, slope);
|
||||
|
||||
if (!best_found || slope < best_slope) {
|
||||
best_found = POS(i, j);
|
||||
best_slope = slope;
|
||||
}
|
||||
}
|
||||
ASSERT_ON_BOARD1(best_found);
|
||||
left_corner[left_corners] = best_found;
|
||||
}
|
||||
|
||||
for (n = board_size-1; n >= 0; n--)
|
||||
if (mn[POS(top_row, n)]) {
|
||||
right_corner[0] = POS(top_row, n);
|
||||
break;
|
||||
}
|
||||
|
||||
/* find the corners on the right side */
|
||||
|
||||
for (right_corners = 1; I(right_corner[right_corners-1]) < bottom_row;
|
||||
right_corners++) {
|
||||
int best_found = 0;
|
||||
float best_slope = 0.;
|
||||
int m = I(right_corner[right_corners-1]);
|
||||
int n = J(right_corner[right_corners-1]);
|
||||
|
||||
for (i = m + 1; i <= bottom_row; i++) {
|
||||
for (j = board_size - 1; j >= 0; j--) {
|
||||
if (mn[POS(i, j)]) {
|
||||
float slope = ((float) (j - n))/((float) (i - m));
|
||||
if (0)
|
||||
gprintf("(right) at %m, last %m, slope=%f\n", i, j, m, n, slope);
|
||||
if (!best_found || slope > best_slope) {
|
||||
best_found = POS(i, j);
|
||||
best_slope = slope;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_ON_BOARD1(best_found);
|
||||
right_corner[right_corners] = best_found;
|
||||
}
|
||||
|
||||
if (0) {
|
||||
for (k = 0; k < left_corners; k++)
|
||||
gprintf("left corner %d: %1m\n", k, left_corner[k]);
|
||||
|
||||
for (k = 0; k < right_corners; k++)
|
||||
gprintf("right corner %d: %1m\n", k, right_corner[k]);
|
||||
}
|
||||
|
||||
/* Now mark the interior of the convex hull */
|
||||
|
||||
for (n = J(left_corner[0]); n <= J(right_corner[0]); n++)
|
||||
mn[POS(top_row, n)] = 1;
|
||||
|
||||
for (n = J(left_corner[left_corners-1]);
|
||||
n <= J(right_corner[right_corners-1]); n++)
|
||||
mn[POS(bottom_row, n)] = 1;
|
||||
|
||||
for (m = top_row+1; m < bottom_row; m++) {
|
||||
int left_boundary = -1, right_boundary = -1;
|
||||
for (k = 1; k < left_corners; k++) {
|
||||
if (I(left_corner[k]) > m) {
|
||||
float ti = I(left_corner[k-1]);
|
||||
float tj = J(left_corner[k-1]);
|
||||
float bi = I(left_corner[k]);
|
||||
float bj = J(left_corner[k]);
|
||||
|
||||
if (0)
|
||||
gprintf("(left) %d: %1m %1m\n",
|
||||
m, left_corner[k-1], left_corner[k]);
|
||||
/* left edge in this row is on segment (ti,tj) -> (bi, bj) */
|
||||
|
||||
/* FIXME: Rewrite this to avoid floating point arithmetic */
|
||||
left_boundary = ceil(tj + (m - ti) * (bj - tj) / (bi - ti));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (k = 1; k < right_corners; k++) {
|
||||
if (I(right_corner[k]) > m) {
|
||||
float ti = I(right_corner[k-1]);
|
||||
float tj = J(right_corner[k-1]);
|
||||
float bi = I(right_corner[k]);
|
||||
float bj = J(right_corner[k]);
|
||||
|
||||
if (0)
|
||||
gprintf("(right) %d: %1m %1m\n",
|
||||
m, right_corner[k-1], right_corner[k]);
|
||||
|
||||
/* FIXME: Rewrite this to avoid floating point arithmetic */
|
||||
right_boundary = floor(tj + (m - ti) * (bj - tj) / (bi - ti));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (n = left_boundary; n <= right_boundary; n++)
|
||||
mn[POS(m, n)] = 1;
|
||||
}
|
||||
|
||||
/* mark the expanded region */
|
||||
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (ON_BOARD(dpos) && mn[dpos] == 1)
|
||||
for (k = 0; k < 4; k++)
|
||||
if (ON_BOARD(dpos + delta[k]) && !mn[dpos + delta[k]])
|
||||
mn[dpos + delta[k]] = 2;
|
||||
|
||||
/* Mark allied dragons that intersect the (unexpanded) hull.
|
||||
* These must all lie entirely within the hull for the
|
||||
* dragon to be considered surrounded.
|
||||
*
|
||||
* Only neighbor dragons are considered since dragons that
|
||||
* are not neighbors are less likely to be helpful.
|
||||
*/
|
||||
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) {
|
||||
int mpos;
|
||||
if (ON_BOARD(dpos)
|
||||
&& mn[dpos] == 1
|
||||
&& board[dpos] == color
|
||||
&& are_neighbor_dragons(pos, dpos)
|
||||
&& !mf[dpos]) {
|
||||
|
||||
for (mpos = BOARDMIN; mpos < BOARDMAX; mpos++)
|
||||
if (ON_BOARD(mpos) && is_same_dragon(mpos, dpos))
|
||||
mf[mpos] = 2;
|
||||
}
|
||||
/* A special case
|
||||
*
|
||||
* . X X .
|
||||
* X O . X
|
||||
* X . O O
|
||||
* . O . .
|
||||
*
|
||||
* The O stone hasn't been amalgamated and the surround computations
|
||||
* might think this single stone dragon is surrounded, which in turn
|
||||
* can generate overvaluation of moves around this stone.
|
||||
* Consequently, we allow inclusion of the stones at kosumi distance
|
||||
* in the mf (friendly) array.
|
||||
*/
|
||||
if (ON_BOARD(dpos)
|
||||
&& mn[dpos] == 2
|
||||
&& board[dpos] == color
|
||||
&& are_neighbor_dragons(pos, dpos)
|
||||
&& !mf[dpos]) {
|
||||
for (k = 4; k < 8; k++)
|
||||
if (ON_BOARD(dpos + delta[k]) && board[dpos + delta[k]] == color
|
||||
&& mn[dpos + delta[k]] == 1
|
||||
&& board[dpos + delta[k-4]] == EMPTY
|
||||
&& board[dpos + delta[(k-3)%4]] == EMPTY) {
|
||||
for (mpos = BOARDMIN; mpos < BOARDMAX; mpos++)
|
||||
if (ON_BOARD(mpos) && is_same_dragon(mpos, dpos))
|
||||
mf[mpos] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* determine the surround status of the dragon */
|
||||
|
||||
surrounded = SURROUNDED;
|
||||
|
||||
/* Compute the maximum surround status awarded
|
||||
* If distances between enclosing stones are large, reduce to
|
||||
* WEAKLY_SURROUNDED. If (really) too large, then reduce to 0
|
||||
* FIXME: constants chosen completely ad hoc. Possibly better tunings
|
||||
* can be found.
|
||||
*/
|
||||
|
||||
for (k = 0; k < corners - 1; k++) {
|
||||
if (is_edge_vertex(corner[k])
|
||||
&& is_edge_vertex(corner[k+1]))
|
||||
continue;
|
||||
if (square_dist(corner[k], corner[k+1]) > 60) {
|
||||
surrounded = 0;
|
||||
break;
|
||||
}
|
||||
else if (square_dist(corner[k], corner[k+1]) > 27)
|
||||
surrounded = WEAKLY_SURROUNDED;
|
||||
}
|
||||
if (surrounded
|
||||
&& (!is_edge_vertex(corner[0])
|
||||
|| !is_edge_vertex(corner[corners-1]))) {
|
||||
if (square_dist(corner[0], corner[corners-1]) > 60)
|
||||
surrounded = 0;
|
||||
else if (square_dist(corner[0], corner[corners-1]) > 27)
|
||||
surrounded = WEAKLY_SURROUNDED;
|
||||
}
|
||||
|
||||
if (surrounded)
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++)
|
||||
if (mf[dpos]) {
|
||||
if (mn[dpos] == 0) {
|
||||
surrounded = 0;
|
||||
break;
|
||||
}
|
||||
else if (mn[dpos] == 2)
|
||||
surrounded = WEAKLY_SURROUNDED;
|
||||
}
|
||||
|
||||
/* revise the status for single stone dragons. */
|
||||
|
||||
if (stones == 1
|
||||
&& surrounded == WEAKLY_SURROUNDED
|
||||
&& mn[pos] == 2)
|
||||
surrounded = 0;
|
||||
|
||||
/* revise the status if an ikken tobi jumps out. */
|
||||
|
||||
if (surrounded) {
|
||||
for (dpos = BOARDMIN; dpos < BOARDMAX && surrounded; dpos++) {
|
||||
if (!ON_BOARD(dpos) || !mf[dpos])
|
||||
continue;
|
||||
|
||||
for (k = 0; k < 4; k++) {
|
||||
int up = delta[k];
|
||||
int right = delta[(k + 1) % 4];
|
||||
if (board[dpos + up] == EMPTY
|
||||
&& board[dpos + 2*up] == color
|
||||
&& mn[dpos + 2*up] != 1
|
||||
&& ON_BOARD(dpos + up + right)
|
||||
&& board[dpos + up + right] != other
|
||||
&& ON_BOARD(dpos + up - right)
|
||||
&& board[dpos + up - right] != other) {
|
||||
surrounded = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showboard == 1 || (showboard == 2 && surrounded)) {
|
||||
show_surround_map(mf, mn);
|
||||
}
|
||||
|
||||
if (!apos && surrounded && surround_pointer < MAX_SURROUND) {
|
||||
memcpy(surroundings[surround_pointer].surround_map, mn, sizeof(mn));
|
||||
surroundings[surround_pointer].dragon_number = dragon[pos].id;
|
||||
surround_pointer++;
|
||||
}
|
||||
|
||||
if (surround_size) {
|
||||
int pos;
|
||||
|
||||
*surround_size = 0;
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos) && mn[pos] == 1)
|
||||
(*surround_size)++;
|
||||
}
|
||||
|
||||
return surrounded;
|
||||
}
|
||||
|
||||
|
||||
/* Computes the minimum distance to the goal
|
||||
*/
|
||||
|
||||
static int
|
||||
goal_dist(int pos, signed char goal[BOARDMAX])
|
||||
{
|
||||
int dist = 10000;
|
||||
int ii;
|
||||
|
||||
for (ii = BOARDMIN; ii < BOARDMAX; ii++)
|
||||
if (ON_BOARD(ii) && goal[ii])
|
||||
dist = gg_min(dist, square_dist(ii, pos));
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
/* Compares angles. Chosen convention:
|
||||
* - SOUTH is "lowest"
|
||||
* - ascending order is done clock-wise (WEST, NORTH, EAST)
|
||||
*/
|
||||
static int
|
||||
compare_angles(const void *a, const void *b)
|
||||
{
|
||||
int aa = *((const int *)a);
|
||||
int bb = *((const int *)b);
|
||||
|
||||
int di_a = I(aa) - I(gg);
|
||||
int dj_a = J(aa) - J(gg);
|
||||
int di_b = I(bb) - I(gg);
|
||||
int dj_b = J(bb) - J(gg);
|
||||
|
||||
float sin_a, sin_b;
|
||||
|
||||
if (aa == gg)
|
||||
return 1;
|
||||
if (bb == gg)
|
||||
return -1;
|
||||
|
||||
if (dj_a == 0) {
|
||||
if (di_a > 0) {
|
||||
if (dj_b != 0 || di_b <= 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
if (dj_b > 0)
|
||||
return -1;
|
||||
else if (dj_b < 0 || di_b > 0)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sin_a = (float)di_a / sqrt(di_a*di_a + dj_a*dj_a);
|
||||
sin_b = (float)di_b / sqrt(di_b*di_b + dj_b*dj_b);
|
||||
|
||||
if (dj_a > 0) {
|
||||
if (dj_b <= 0)
|
||||
return 1;
|
||||
if (sin_a > sin_b)
|
||||
return 1;
|
||||
else if (sin_a < sin_b)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else { /* if (dj_a < 0) */
|
||||
if (dj_b > 0)
|
||||
return -1;
|
||||
if (sin_a < sin_b)
|
||||
return 1;
|
||||
else if (sin_a > sin_b)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
show_surround_map(signed char mf[BOARDMAX], signed char mn[BOARDMAX])
|
||||
{
|
||||
int m, n;
|
||||
|
||||
start_draw_board();
|
||||
for (m = 0; m < board_size; m++)
|
||||
for (n = 0; n < board_size; n++) {
|
||||
int col, c;
|
||||
|
||||
if (mf[POS(m, n)]) {
|
||||
if (mn[POS(m, n)] == 1)
|
||||
col = GG_COLOR_RED;
|
||||
else if (mn[POS(m, n)] == 2)
|
||||
col = GG_COLOR_YELLOW;
|
||||
else
|
||||
col = GG_COLOR_GREEN;
|
||||
}
|
||||
else if (mn[POS(m, n)] == 1)
|
||||
col = GG_COLOR_BLUE;
|
||||
else if (mn[POS(m, n)] == 2)
|
||||
col = GG_COLOR_CYAN;
|
||||
else
|
||||
col = GG_COLOR_BLACK;
|
||||
if (board[POS(m, n)] == BLACK)
|
||||
c = 'X';
|
||||
else if (board[POS(m, n)] == WHITE)
|
||||
c = 'O';
|
||||
else if (mn[POS(m, n)])
|
||||
c = '*';
|
||||
else
|
||||
c = '.';
|
||||
draw_color_char(m, n, c, col);
|
||||
}
|
||||
end_draw_board();
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
is_surrounded(int dr)
|
||||
{
|
||||
return(DRAGON2(dr).surround_status);
|
||||
}
|
||||
|
||||
/* Returns true if (dragon) is not surrounded, but (move) surrounds it.
|
||||
*/
|
||||
|
||||
int
|
||||
does_surround(int move, int dr)
|
||||
{
|
||||
if (DRAGON2(dr).surround_status)
|
||||
return 0;
|
||||
return compute_surroundings(dr, move, 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
/* Should be run once per genmove, before make_dragons. */
|
||||
|
||||
void
|
||||
reset_surround_data(void)
|
||||
{
|
||||
surround_pointer = 0;
|
||||
}
|
||||
|
||||
|
||||
/* Returns 1 (respectively 2) if pos is in the convex hull
|
||||
* (respectively expanded hull boundary) of the surrounding
|
||||
* dragons. Returns -1 if the dragon is not found.
|
||||
*/
|
||||
int
|
||||
surround_map(int dr, int pos)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0; k < surround_pointer; k++)
|
||||
if (surroundings[k].dragon_number == dragon[dr].id)
|
||||
return surroundings[k].surround_map[pos];
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
705
gnugo/engine/unconditional.c
Normal file
705
gnugo/engine/unconditional.c
Normal file
@ -0,0 +1,705 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||||
* 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. *
|
||||
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "gnugo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "liberty.h"
|
||||
|
||||
/* Capture as many strings of the given color as we can. Played stones
|
||||
* are left on the board and the number of played stones is returned.
|
||||
* Strings marked in the exceptions array are excluded from capturing
|
||||
* attempts. If all non-excepted strings are successfully captured,
|
||||
* *none_invincible is set to one. Set none_invincible to NULL if you
|
||||
* don't need that information.
|
||||
*/
|
||||
static int
|
||||
capture_non_invincible_strings(int color, int exceptions[BOARDMAX],
|
||||
int *none_invincible)
|
||||
{
|
||||
int other = OTHER_COLOR(color);
|
||||
int something_captured = 1; /* To get into the first turn of the loop. */
|
||||
int string_found = 0;
|
||||
int moves_played = 0;
|
||||
int save_moves;
|
||||
int libs[MAXLIBS];
|
||||
int liberties;
|
||||
int pos;
|
||||
int k;
|
||||
|
||||
while (something_captured) {
|
||||
/* Nothing captured so far in this turn of the loop. */
|
||||
something_captured = 0;
|
||||
|
||||
/* Is there something left to try to capture? */
|
||||
string_found = 0;
|
||||
|
||||
/* Visit all friendly strings on the board. */
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] != color || find_origin(pos) != pos)
|
||||
continue;
|
||||
|
||||
if (exceptions && exceptions[pos])
|
||||
continue;
|
||||
|
||||
string_found = 1;
|
||||
|
||||
/* Try to capture the string at pos. */
|
||||
liberties = findlib(pos, MAXLIBS, libs);
|
||||
save_moves = moves_played;
|
||||
for (k = 0; k < liberties; k++) {
|
||||
if (trymove(libs[k], other, "unconditional_life", pos))
|
||||
moves_played++;
|
||||
}
|
||||
|
||||
/* Successful if already captured or a single liberty remains.
|
||||
* Otherwise we must rewind and take back the last batch of moves.
|
||||
*/
|
||||
if (board[pos] == EMPTY)
|
||||
something_captured = 1;
|
||||
else if (findlib(pos, 2, libs) == 1) {
|
||||
/* Need to use tryko as a defense against the extreme case
|
||||
* when the only opponent liberty that is not suicide is an
|
||||
* illegal ko capture, like in this 5x5 position:
|
||||
* +-----+
|
||||
* |.XO.O|
|
||||
* |XXOO.|
|
||||
* |X.XOO|
|
||||
* |XXOO.|
|
||||
* |.XO.O|
|
||||
* +-----+
|
||||
*/
|
||||
int success = tryko(libs[0], other, "unconditional_life");
|
||||
gg_assert(success);
|
||||
moves_played++;
|
||||
something_captured = 1;
|
||||
}
|
||||
else
|
||||
while (moves_played > save_moves) {
|
||||
popgo();
|
||||
moves_played--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (none_invincible)
|
||||
*none_invincible = !string_found;
|
||||
|
||||
return moves_played;
|
||||
}
|
||||
|
||||
/* Find those worms of the given color that can never be captured,
|
||||
* even if the opponent is allowed an arbitrary number of consecutive
|
||||
* moves. The coordinates of the origins of these worms are written to
|
||||
* the worm arrays and the number of non-capturable worms is
|
||||
* returned.
|
||||
*
|
||||
* The algorithm is to cycle through the worms until none remains or
|
||||
* no more can be captured. A worm is removed when it is found to be
|
||||
* capturable, by letting the opponent try to play on all its
|
||||
* liberties. If the attack fails, the moves are undone. When no more
|
||||
* worm can be removed in this way, the remaining ones are
|
||||
* unconditionally alive.
|
||||
*
|
||||
* After this, unconditionally dead opponent worms and unconditional
|
||||
* territory are identified. This is almost, but only almost,
|
||||
* straightforward. We first present a simple but only almost correct
|
||||
* solution, then show how to patch up its deficiencies.
|
||||
*
|
||||
* - - - - - - -
|
||||
*
|
||||
* Algorithm 1, simple but slightly incorrect.
|
||||
*
|
||||
* To find unconditionally dead opponent worms and unconditional
|
||||
* territory, we continue from the position obtained at the end of the
|
||||
* previous operation (only unconditionally alive strings remain for
|
||||
* color) with the following steps:
|
||||
*
|
||||
* 1. Play opponent stones on all liberties of the unconditionally
|
||||
* alive strings except where illegal. (That the move order may
|
||||
* determine exactly which liberties can be played legally is not
|
||||
* important. Just pick an arbitrary order).
|
||||
* 2. Recursively extend opponent strings in atari, except where this
|
||||
* would be suicide.
|
||||
* 3. Play an opponent stone anywhere it can get two empty
|
||||
* neighbors. (I.e. split big eyes into small ones).
|
||||
* 4. Play an opponent stone anywhere it can get one empty
|
||||
* neighbor. (I.e. reduce two space eyes to one space eyes.)
|
||||
*
|
||||
* Remaining opponent strings in atari and remaining liberties of the
|
||||
* unconditionally alive strings constitute the unconditional
|
||||
* territory.
|
||||
*
|
||||
* Opponent strings from the initial position placed on
|
||||
* unconditional territory are unconditionally dead.
|
||||
*
|
||||
* - - - - - - -
|
||||
*
|
||||
* The deficiency with this algorithm is that a certain class of sekis
|
||||
* are considered as dead, e.g. this position:
|
||||
*
|
||||
* .OOOOO.
|
||||
* OOXXXOO
|
||||
* OXX.XXO
|
||||
* OX.O.XO
|
||||
* OX.O.XO
|
||||
* OXX.XXO
|
||||
* OOXXXOO
|
||||
* .OOOOO.
|
||||
*
|
||||
* The problem is that while removing the two O stones, X is reduced
|
||||
* to a single small eye. Still O cannot capture these stones under
|
||||
* alternating play since the eyespace is too big.
|
||||
*
|
||||
* Before discussing this seki further we make a preliminary
|
||||
* modification of the algorithm.
|
||||
*
|
||||
* - - - - - - -
|
||||
*
|
||||
* Algorithm 2. More complex but still slightly incorrect algorithm:
|
||||
*
|
||||
* 1. Run algorithm 1.
|
||||
* 2. Return to the original position.
|
||||
* 3. Capture all capturable O strings which according to algorithm 1
|
||||
* do not belong to unconditional territory.
|
||||
* 4. Play opponent stones on all liberties of the unconditionally
|
||||
* alive strings except where illegal. (That the move order may
|
||||
* determine exactly which liberties can be played legally is not
|
||||
* important. Just pick an arbitrary order).
|
||||
* 5. Recursively extend opponent strings in atari, except where this
|
||||
* would be suicide.
|
||||
* 6. Capture all remaining capturable O strings.
|
||||
* 7. Repeat 4 and 5 once.
|
||||
* 8. Play an opponent stone anywhere it can get two empty
|
||||
* neighbors. (I.e. split big eyes into small ones).
|
||||
* 9. Play an opponent stone anywhere it can get one empty
|
||||
* neighbor. (I.e. reduce two space eyes to one space eyes.)
|
||||
*
|
||||
* Remaining opponent strings in atari and remaining liberties of the
|
||||
* unconditionally alive strings constitute the unconditional
|
||||
* territory.
|
||||
*
|
||||
* Opponent strings from the initial position placed on
|
||||
* unconditional territory are unconditionally dead.
|
||||
*
|
||||
* - - - - - - -
|
||||
*
|
||||
* We can observe that, after step 5, an X group with at least two
|
||||
* distinct eyespaces would not risk being reduced to a single small
|
||||
* eye. Similarly an X group with a capturable O string of size at
|
||||
* least three would allow the formation of two distinct small eyes
|
||||
* after being captured. Thus it is easy to see that the only X groups
|
||||
* which would live in seki but could not be transformed into
|
||||
* unconditionally alive groups would have a single eyespace with a
|
||||
* capturable O string of size at most 2. Furthermore the eyespace
|
||||
* would not be possible to subdivide. Then if the capturable string
|
||||
* would be of size 1 it would in all cases form a nakade and we would
|
||||
* not have a seki. The plausible seki positions would all be
|
||||
* reducable to the following eyeshape:
|
||||
*
|
||||
* .OOOOO.
|
||||
* OOXXXO.
|
||||
* OXX.XOO
|
||||
* OX.OXXO
|
||||
* OXXO.XO
|
||||
* OOX.XXO
|
||||
* .OXXXOO
|
||||
* .OOOOO.
|
||||
*
|
||||
* The remaining question is what effects cutting points in the X
|
||||
* group would have. For example these X groups are dead:
|
||||
*
|
||||
* .OOOOO. .OOOOO. .OOOOO. .OOOOO. ..OOOO. ..OOOO.
|
||||
* .OXXXO. .OXXXO. .OXXXO. .OXXXO. OOOXXO. OOOXXO.
|
||||
* OOX.XO. OOX.XOO OOX.XOO OOX.XOO OXX.XO. OXX.XOO
|
||||
* OX.OXOO OX.OXXO OX.OXXO OX.OXXO OX.OXOO OX.OXXO
|
||||
* OXXO.XO OXXO.XO OXXO.XO OXXO.XO OXXO.XO OXXO.XO
|
||||
* OOX.XXO OOX.XOO OOX.XXO OOX.XXO OOX.XXO OOX.XXO
|
||||
* .OXXXOO .OXXXO. .OXXOOO .OOXXOO .OXXXOO .OXXOOO
|
||||
* .OOOOO. .OOOOO. .OOOO.. ..OOOO. .OOOOO. .OOOO..
|
||||
*
|
||||
* while these are alive in seki
|
||||
*
|
||||
* ..OOOO. .OOOO.. .OOOO.. ..OOOO. ..OOOO.
|
||||
* OOOXXO. .OXXOO. OOXXOO. .OOXXO. OOOXXO.
|
||||
* OXX.XOO OOX.XOO OXX.XOO OOX.XOO OXX.XOO
|
||||
* OX.OXXO OX.OXXO OX.OXXO OX.OXXO OX.OXXO
|
||||
* OXXO.XO OXXO.XO OOXO.XO OXXO.XO OOXO.XO
|
||||
* OOX.XXO OOX.XXO .OX.XXO OOX.XXO .OX.XXO
|
||||
* .OXXXOO .OXXXOO .OXXOOO .OXXXOO .OXXXOO
|
||||
* .OOOOO. .OOOOO. .OOOO.. ..OOOO. .OOOOO.
|
||||
*
|
||||
* The critical distinction between the dead ones and the seki ones is
|
||||
* that the stones marked a and b below,
|
||||
*
|
||||
* .OOOOO.
|
||||
* OOXXXO.
|
||||
* OXX.XOO
|
||||
* OX.ObXO
|
||||
* OXaO.XO
|
||||
* OOX.XXO
|
||||
* .OXXXOO
|
||||
* .OOOOO.
|
||||
*
|
||||
* belong to different strings for the dead groups and to the same
|
||||
* string for the seki groups.
|
||||
*
|
||||
* The trick to avoid misclassifying areas where the opponent can form
|
||||
* a seki group but not an invincible group as unconditional territory
|
||||
* is thus to detect the formation above and add a third stone to the
|
||||
* O group before the capturing in step 6 above.
|
||||
*
|
||||
* This leads to the final algorithm.
|
||||
*
|
||||
* - - - - - - -
|
||||
*
|
||||
* Algorithm 3. Final and correct algorithm:
|
||||
*
|
||||
* 1. Run algorithm 1.
|
||||
* 2. Return to the original position.
|
||||
* 3. Capture all capturable O strings which according to algorithm 1
|
||||
* do not belong to unconditional territory.
|
||||
* 4. Play opponent stones on all liberties of the unconditionally
|
||||
* alive strings except where illegal. (That the move order may
|
||||
* determine exactly which liberties can be played legally is not
|
||||
* important. Just pick an arbitrary order).
|
||||
* 5. Recursively extend opponent strings in atari, except where this
|
||||
* would be suicide.
|
||||
* 6. Identify eyespaces of the kind described above and extend any
|
||||
* matching two-stone string with a third stone.
|
||||
* 7. Capture all remaining capturable O strings.
|
||||
* 8. Repeat 4 and 5 once.
|
||||
* 9. Play an opponent stone anywhere it can get two empty
|
||||
* neighbors. (I.e. split big eyes into small ones).
|
||||
* 10. Play an opponent stone anywhere it can get one empty
|
||||
* neighbor. (I.e. reduce two space eyes to one space eyes.)
|
||||
*
|
||||
* Remaining opponent strings in atari and remaining liberties of the
|
||||
* unconditionally alive strings constitute the unconditional
|
||||
* territory.
|
||||
*
|
||||
* Opponent strings from the initial position placed on
|
||||
* unconditional territory are unconditionally dead.
|
||||
*
|
||||
* - - - - - - -
|
||||
*
|
||||
* On return, unconditional_territory[][] is 1 where color has
|
||||
* unconditionally alive stones, 2 where it has unconditional
|
||||
* territory, and 0 otherwise.
|
||||
*/
|
||||
|
||||
void
|
||||
unconditional_life(int unconditional_territory[BOARDMAX], int color)
|
||||
{
|
||||
int found_one;
|
||||
int other = OTHER_COLOR(color);
|
||||
int libs[MAXLIBS];
|
||||
int liberties;
|
||||
int pos;
|
||||
int k, r;
|
||||
int moves_played;
|
||||
int potential_sekis[BOARDMAX];
|
||||
int none_invincible;
|
||||
|
||||
/* Initialize unconditional_territory array. */
|
||||
memset(unconditional_territory, 0,
|
||||
sizeof(unconditional_territory[0]) * BOARDMAX);
|
||||
|
||||
/* Find isolated two-stone strings which might be involved in the
|
||||
* kind of seki described in the comments.
|
||||
*/
|
||||
memset(potential_sekis, 0, sizeof(potential_sekis));
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
int isolated = 1;
|
||||
int stones[2];
|
||||
int pos2;
|
||||
|
||||
if (board[pos] != color
|
||||
|| find_origin(pos) != pos
|
||||
|| countstones(pos) != 2)
|
||||
continue;
|
||||
|
||||
findstones(pos, 2, stones);
|
||||
for (k = 0; k < 2 && isolated; k++) {
|
||||
for (r = 0; r < 8 && isolated; r++) {
|
||||
pos2 = stones[k] + delta[r];
|
||||
if (!ON_BOARD(pos2)
|
||||
|| (board[pos2] == color
|
||||
&& !same_string(pos, pos2)))
|
||||
isolated = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (isolated) {
|
||||
potential_sekis[stones[0]] = 1;
|
||||
potential_sekis[stones[1]] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
moves_played = capture_non_invincible_strings(color, potential_sekis,
|
||||
&none_invincible);
|
||||
|
||||
/* If there are no invincible strings, nothing can be unconditionally
|
||||
* settled.
|
||||
*/
|
||||
if (none_invincible) {
|
||||
/* Take back all moves. */
|
||||
while (moves_played > 0) {
|
||||
popgo();
|
||||
moves_played--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* The strings still remaining except those marked in
|
||||
* potential_sekis[] are uncapturable. Now see which opponent
|
||||
* strings can survive.
|
||||
*
|
||||
* 1. Play opponent stones on all liberties of the unconditionally
|
||||
* alive strings except where illegal.
|
||||
*/
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] != color || potential_sekis[pos] || find_origin(pos) != pos)
|
||||
continue;
|
||||
|
||||
/* Play as many liberties as we can. */
|
||||
liberties = findlib(pos, MAXLIBS, libs);
|
||||
for (k = 0; k < liberties; k++) {
|
||||
if (trymove(libs[k], other, "unconditional_life", pos))
|
||||
moves_played++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. Recursively extend opponent strings in atari, except where this
|
||||
* would be suicide.
|
||||
*/
|
||||
found_one = 1;
|
||||
while (found_one) {
|
||||
/* Nothing found so far in this turn of the loop. */
|
||||
found_one = 0;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] != other || countlib(pos) > 1)
|
||||
continue;
|
||||
|
||||
/* Try to extend the string at (m, n). */
|
||||
findlib(pos, 1, libs);
|
||||
if (trymove(libs[0], other, "unconditional_life", pos)) {
|
||||
moves_played++;
|
||||
found_one = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now see whether there are any significant sekis on the board. */
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (!potential_sekis[pos]
|
||||
|| board[pos] == EMPTY
|
||||
|| find_origin(pos) != pos)
|
||||
continue;
|
||||
for (r = 0; r < 4; r++) {
|
||||
int up = delta[r];
|
||||
int right = delta[(r + 1) % 4];
|
||||
int locally_played_moves = 0;
|
||||
if (board[pos + up] != color
|
||||
|| board[pos + up + up] != EMPTY
|
||||
|| board[pos - up] != EMPTY)
|
||||
continue;
|
||||
for (k = 0; k < 2; k++) {
|
||||
if (k == 1)
|
||||
right = -right;
|
||||
if (board[pos + right] != EMPTY || board[pos + up - right] != EMPTY)
|
||||
continue;
|
||||
if (board[pos - right] == EMPTY
|
||||
&& trymove(pos - right, other, "unconditional_life", pos))
|
||||
locally_played_moves++;
|
||||
if (board[pos + up + right] == EMPTY
|
||||
&& trymove(pos + up + right, other, "unconditional_life", pos))
|
||||
locally_played_moves++;
|
||||
if (board[pos - right] == other && board[pos + up + right] == other
|
||||
&& same_string(pos - right, pos + up + right)) {
|
||||
/* This is a critical seki. Extend the string with one stone
|
||||
* in an arbitrary direction to break the seki.
|
||||
*/
|
||||
while (locally_played_moves > 0) {
|
||||
popgo();
|
||||
locally_played_moves--;
|
||||
}
|
||||
trymove(pos - up, color, "unconditional_life", pos);
|
||||
moves_played++;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
while (locally_played_moves > 0) {
|
||||
popgo();
|
||||
locally_played_moves--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (countstones(pos) > 2)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Capture the strings involved in potential sekis. */
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (!potential_sekis[pos] || board[pos] == EMPTY)
|
||||
continue;
|
||||
/* Play as many liberties as we can. */
|
||||
liberties = findlib(pos, MAXLIBS, libs);
|
||||
for (k = 0; k < liberties; k++) {
|
||||
if (trymove(libs[k], other, "unconditional_life", pos))
|
||||
moves_played++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
int apos;
|
||||
int bpos;
|
||||
int aopen, bopen;
|
||||
int alib, blib;
|
||||
if (board[pos] != other || countlib(pos) != 2)
|
||||
continue;
|
||||
findlib(pos, 2, libs);
|
||||
apos = libs[0];
|
||||
bpos = libs[1];
|
||||
if (abs(I(apos) - I(bpos)) + abs(J(apos) - J(bpos)) != 1)
|
||||
continue;
|
||||
|
||||
/* Only two liberties and these are adjacent. Play one. We want
|
||||
* to maximize the number of open liberties. In this particular
|
||||
* situation we can count this with approxlib for the opposite
|
||||
* color. If the number of open liberties is the same, we
|
||||
* maximize the total number of obtained liberties.
|
||||
* Two relevant positions:
|
||||
*
|
||||
* |XXX.
|
||||
* |OOXX |XXXXXXX
|
||||
* |O.OX |OOXOOOX
|
||||
* |..OX |..OO.OX
|
||||
* +---- +-------
|
||||
*/
|
||||
aopen = approxlib(apos, color, 4, NULL);
|
||||
bopen = approxlib(bpos, color, 4, NULL);
|
||||
alib = approxlib(apos, other, 4, NULL);
|
||||
blib = approxlib(bpos, other, 4, NULL);
|
||||
|
||||
if (aopen > bopen || (aopen == bopen && alib >= blib)) {
|
||||
trymove(apos, other, "unconditional_life", pos);
|
||||
moves_played++;
|
||||
}
|
||||
else {
|
||||
trymove(bpos, other, "unconditional_life", pos);
|
||||
moves_played++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Identify unconditionally alive stones and unconditional territory. */
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] == color && !potential_sekis[pos]) {
|
||||
unconditional_territory[pos] = 1;
|
||||
if (find_origin(pos) == pos) {
|
||||
liberties = findlib(pos, MAXLIBS, libs);
|
||||
for (k = 0; k < liberties; k++)
|
||||
unconditional_territory[libs[k]] = 2;
|
||||
}
|
||||
}
|
||||
else if (board[pos] == other && countlib(pos) == 1) {
|
||||
unconditional_territory[pos] = 2;
|
||||
findlib(pos, 1, libs);
|
||||
unconditional_territory[libs[0]] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Take back all moves. */
|
||||
while (moves_played > 0) {
|
||||
popgo();
|
||||
moves_played--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* By unconditional status analysis we can statically find some moves
|
||||
* which there is never any need to play. Those belong to three
|
||||
* different categories:
|
||||
*
|
||||
* 1. A move on a vertex which is already unconditional territory for
|
||||
* either color.
|
||||
* 2. A move which after having been made ends up as unconditional
|
||||
* territory for the opponent.
|
||||
* 3. If a move at vertex A makes vertex B become unconditional
|
||||
* territory, there is no need to consider a move at B, since A has
|
||||
* all the positive effects that B would have.
|
||||
*
|
||||
* Moves in categories 1 and 2 are never any better than passing and
|
||||
* often worse (with territory scoring always worse). Moves in
|
||||
* category three can be either better or worse than passing, but it's
|
||||
* always true that a move at A is at least as good as a move at B.
|
||||
* Occasionally they are identically good (A makes B unconditional
|
||||
* territory and B makes A unconditional territory) but there is never
|
||||
* any need to analyze both.
|
||||
*
|
||||
* In meaningless_black_moves[] and meaningless_white_moves[] a value
|
||||
* of -1 means it is not meaningless, 0 (NO_MOVE) means it belongs to
|
||||
* category 1 or 2, and a value greater than zero points to the
|
||||
* preferred move in category 3.
|
||||
*
|
||||
* The parameter unconditional_territory should contain the result of
|
||||
* calling unconditional_life() in the original position. Meaningless
|
||||
* moves are computed for the given color.
|
||||
*/
|
||||
void
|
||||
find_unconditionally_meaningless_moves(int unconditional_territory[BOARDMAX],
|
||||
int color)
|
||||
{
|
||||
int *meaningless_moves;
|
||||
int other = OTHER_COLOR(color);
|
||||
int friendly_unconditional[BOARDMAX];
|
||||
int opponent_unconditional[BOARDMAX];
|
||||
int pos;
|
||||
int pos2;
|
||||
|
||||
gg_assert(color == BLACK || color == WHITE);
|
||||
|
||||
if (color == BLACK)
|
||||
meaningless_moves = meaningless_black_moves;
|
||||
else
|
||||
meaningless_moves = meaningless_white_moves;
|
||||
|
||||
/* Initialize meaningless_moves and detect moves of category 1, but
|
||||
* only for own unconditional territory.
|
||||
*
|
||||
* FIXME: We would save some time by detecting all category 1 moves
|
||||
* here but then we would need to have the initial unconditional
|
||||
* territory for the opponent as well. This can of course be done,
|
||||
* the question is how we get it in the nicest way.
|
||||
*/
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (board[pos] == EMPTY) {
|
||||
if (unconditional_territory[pos])
|
||||
meaningless_moves[pos] = NO_MOVE;
|
||||
else
|
||||
meaningless_moves[pos] = -1;
|
||||
}
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
||||
if (board[pos] != EMPTY || meaningless_moves[pos] != -1)
|
||||
continue;
|
||||
|
||||
if (!tryko(pos, color, "find_unconditionally_meaningless_moves"))
|
||||
continue;
|
||||
|
||||
unconditional_life(opponent_unconditional, other);
|
||||
if (opponent_unconditional[pos]) {
|
||||
/* Move of category 1 or 2. */
|
||||
meaningless_moves[pos] = NO_MOVE;
|
||||
}
|
||||
else {
|
||||
unconditional_life(friendly_unconditional, color);
|
||||
if (friendly_unconditional[pos])
|
||||
for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++)
|
||||
if (board[pos2] == EMPTY
|
||||
&& meaningless_moves[pos2] == -1
|
||||
&& friendly_unconditional[pos2]) {
|
||||
/* Move of category 3. */
|
||||
meaningless_moves[pos2] = pos;
|
||||
}
|
||||
}
|
||||
|
||||
popgo();
|
||||
}
|
||||
|
||||
/* Meaningless moves of category 3 may have been found in multiple
|
||||
* steps. Normalize to the final replacement move.
|
||||
*/
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (board[pos] == EMPTY && meaningless_moves[pos] > 0)
|
||||
while (meaningless_moves[meaningless_moves[pos]] > 0)
|
||||
meaningless_moves[pos] = meaningless_moves[meaningless_moves[pos]];
|
||||
}
|
||||
|
||||
/* Returns 1 if the move at pos by color is meaningless and 0
|
||||
* otherwise. When it is meaningless, *replacement_move will contain a
|
||||
* replacing move, which is NO_MOVE if passing is guaranteed to be no
|
||||
* worse than making the move.
|
||||
*/
|
||||
int
|
||||
unconditionally_meaningless_move(int pos, int color, int *replacement_move)
|
||||
{
|
||||
if (color == WHITE && meaningless_white_moves[pos] != -1) {
|
||||
*replacement_move = meaningless_white_moves[pos];
|
||||
return 1;
|
||||
}
|
||||
if (color == BLACK && meaningless_black_moves[pos] != -1) {
|
||||
*replacement_move = meaningless_black_moves[pos];
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
clear_unconditionally_meaningless_moves()
|
||||
{
|
||||
int pos;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (ON_BOARD(pos)) {
|
||||
meaningless_black_moves[pos] = -1;
|
||||
meaningless_white_moves[pos] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pick up antisuji and replacement move reasons found by analysis
|
||||
* of unconditional status.
|
||||
*/
|
||||
void
|
||||
unconditional_move_reasons(int color)
|
||||
{
|
||||
int replacement_move;
|
||||
int pos;
|
||||
|
||||
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
||||
if (board[pos] == EMPTY
|
||||
&& unconditionally_meaningless_move(pos, color, &replacement_move)) {
|
||||
if (replacement_move == NO_MOVE) {
|
||||
TRACE("%1m unconditional antisuji.\n", pos);
|
||||
add_antisuji_move(pos);
|
||||
}
|
||||
else {
|
||||
TRACE("%1m unconditionally replaced to %1m.\n", pos, replacement_move);
|
||||
add_replacement_move(pos, replacement_move, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 2
|
||||
* End:
|
||||
*/
|
2004
gnugo/engine/utils.c
Normal file
2004
gnugo/engine/utils.c
Normal file
File diff suppressed because it is too large
Load Diff
4043
gnugo/engine/value_moves.c
Normal file
4043
gnugo/engine/value_moves.c
Normal file
File diff suppressed because it is too large
Load Diff
1876
gnugo/engine/worm.c
Normal file
1876
gnugo/engine/worm.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user