Beginning of implementation of a ctypes-based interface to libboard, which is a much cleaner set of Go routines than I hacked together originally. Including a copy of gnugo 3.8 so we can build a dynamic version of libboard.

This commit is contained in:
2012-04-12 13:46:27 -04:00
parent 55dbed09f5
commit 8b772255a1
2259 changed files with 388094 additions and 291 deletions

View File

@ -0,0 +1,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
View 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
View 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

File diff suppressed because it is too large Load Diff

4331
gnugo/engine/board.c Normal file

File diff suppressed because it is too large Load Diff

133
gnugo/engine/board.dsp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

2605
gnugo/engine/dragon.c Normal file

File diff suppressed because it is too large Load Diff

527
gnugo/engine/endgame.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

148
gnugo/engine/influence.h Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

1137
gnugo/engine/matchpat.c Normal file

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

220
gnugo/engine/move_reasons.h Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

474
gnugo/engine/oracle.c Normal file
View 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

File diff suppressed because it is too large Load Diff

1462
gnugo/engine/persistent.c Normal file

File diff suppressed because it is too large Load Diff

539
gnugo/engine/printutils.c Normal file
View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

617
gnugo/engine/semeai.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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:
*/

View 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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff