--- a/Makefile Sun Jan 29 12:53:28 2017 +0000
+++ b/Makefile Sun Jan 29 22:07:19 2017 +0000
@@ -1,20 +1,20 @@
.PHONY: pubdocs
-sourcefiles = $(shell ffind --full-path --dir src --literal .lisp)
+sourcefiles = $(shell ffind --full-path --literal .lisp)
docfiles = $(shell ls docs/*.markdown)
-apidoc = docs/03-reference.markdown
+apidocs = $(shell ls docs/*reference*.markdown)
# src/utils.lisp: src/make-utilities.lisp
# cd src && sbcl --noinform --load make-utilities.lisp --eval '(quit)'
-$(apidoc): $(sourcefiles) docs/api.lisp package.lisp
+$(apidocs): $(sourcefiles)
sbcl --noinform --load docs/api.lisp --eval '(quit)'
+docs/build/index.html: $(docfiles) $(apidocs) docs/title
+ cd docs && ~/.virtualenvs/d/bin/d
+
docs: docs/build/index.html
-docs/build/index.html: $(docfiles) $(apidoc) docs/title
- cd docs && ~/.virtualenvs/d/bin/d
-
pubdocs: docs
hg -R ~/src/sjl.bitbucket.org pull -u
rsync --delete -a ./docs/build/ ~/src/sjl.bitbucket.org/cl-ggp
--- a/README.markdown Sun Jan 29 12:53:28 2017 +0000
+++ b/README.markdown Sun Jan 29 22:07:19 2017 +0000
@@ -1,17 +1,24 @@
-
+```
___ __ ___ ___ ____
/ __)( ) ___ / __) / __)( _ \
( (__ / (_/\(___)( (_ \( (_ \ ) __/
\___)\____/ \___/ \___/(__)
+```
-`cl-ggp` is a tiny framework for writing [GGP][] players in Common Lisp.
+`cl-ggp` is a tiny framework for writing [general game players][GGP] in Common
+Lisp.
-It handles the GGP protocol for you but *nothing else*. In particular you'll
-need to bring your own logic system to parse the games.
+The `cl-ggp` system handles the GGP protocol for you and *nothing else*. If you
+plan on doing your own GDL reasoning, this is all you need.
+
+The `cl-ggp.reasoner` system contains a simple Prolog-based reasoner using the
+[Temperance][] logic programming library. It's useful as a starting point for
+when writing players.
[GGP]: http://www.ggp.org/
+[Temperance]: https://sjl.bitbucket.io/temperance/
* **License:** MIT/X11
-* **Documentation:** <http://sjl.bitbucket.org/cl-ggp/>
-* **Code:** <http://bitbucket.org/sjl/cl-ggp/>
-* **Issues:** <http://bitbucket.org/sjl/cl-ggp/issues/>
+* **Documentation:** <https://sjl.bitbucket.io/cl-ggp/>
+* **Mercurial:** <https://bitbucket.org/sjl/cl-ggp/>
+* **Git:** <https://github.com/sjl/cl-ggp/>
--- a/docs/03-reference.markdown Sun Jan 29 12:53:28 2017 +0000
+++ b/docs/03-reference.markdown Sun Jan 29 22:07:19 2017 +0000
@@ -1,4 +1,4 @@
-# API Reference
+# Main API Reference
The following is a list of all user-facing parts of `cl-ggp`.
@@ -18,72 +18,6 @@
The base class for a GGP player. Custom players should extend this.
-#### Slot `NAME`
-
-* Allocation: `:INSTANCE`
-* Type: `STRING`
-* Initarg: `:NAME`
-* Initform: `"CL-GGP"`
-* Reader: `PLAYER-NAME`
-
-The name of the player.
-
-#### Slot `PORT`
-
-* Allocation: `:INSTANCE`
-* Type: `(INTEGER 0)`
-* Initarg: `:PORT`
-* Initform: `9999`
-* Reader: `PLAYER-PORT`
-
-The port the HTTP server should listen on.
-
-#### Slot `MATCH-ROLES`
-
-* Allocation: `:INSTANCE`
-* Type: `(OR NULL LIST)`
-* Initform: `NIL`
-* Reader: `PLAYER-MATCH-ROLES`
-
-A list of the roles for the current match. Feel free to read and use this if you like. **Do not modify this.**
-
-#### Slot `START-CLOCK`
-
-* Allocation: `:INSTANCE`
-* Type: `(OR NULL (INTEGER 1))`
-* Initform: `NIL`
-
-The start clock for the current game. **Do not touch this.** Use the `timeout` value passed to your methods instead.
-
-#### Slot `PLAY-CLOCK`
-
-* Allocation: `:INSTANCE`
-* Type: `(OR NULL (INTEGER 1))`
-* Initform: `NIL`
-
-The play clock for the current game. **Do not touch this.** Use the `timeout` value passed to your methods instead.
-
-#### Slot `MESSAGE-START`
-
-* Allocation: `:INSTANCE`
-* Type: `(OR NULL (INTEGER 0))`
-* Initform: `NIL`
-
-The (internal-real) timestamp of when the current GGP message was received. **Do not touch this.** Use the `timeout` value passed to your methods instead.
-
-#### Slot `CURRENT-MATCH`
-
-* Allocation: `:INSTANCE`
-* Initform: `NIL`
-
-The ID of the current match the player is playing, or `nil` if it is waiting. **Do not touch this.**
-
-#### Slot `SERVER`
-
-* Allocation: `:INSTANCE`
-
-The Clack server object of the player. **Do not touch this.** Use `start-player` and `kill-player` to start/stop the server safely.
-
### `KILL-PLAYER` (function)
(KILL-PLAYER PLAYER)
@@ -163,6 +97,12 @@
+### `READ-GDL-FROM-FILE` (function)
+
+ (READ-GDL-FROM-FILE FILENAME)
+
+Read GDL from `filename`
+
### `START-PLAYER` (function)
(START-PLAYER PLAYER &KEY (SERVER :HUNCHENTOOT) (USE-THREAD T))
--- a/docs/04-changelog.markdown Sun Jan 29 12:53:28 2017 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-Changelog
-=========
-
-Here's the list of changes in each released version.
-
-[TOC]
-
-Pending
--------
-
-* `start-player` now takes `:server` and `:use-thread` options which it passes
- along to Clack.
-* Added rudimentary support for writing GDL-II players.
-
-v0.0.1
-------
-
-Initial alpha version. Things are going to break a lot. Don't use this.
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/04-reference-reasoner.markdown Sun Jan 29 22:07:19 2017 +0000
@@ -0,0 +1,68 @@
+# Reasoner API Reference
+
+cl-ggp includes a simple Prolog-based reasoner you can use as a starting point when writing general game players in the `cl-ggp.reasoner` system.
+
+ [TOC]
+
+## Package `GGP.REASONER`
+
+This package contains a simple GGP reasoner. It can be useful as a starting point for writing general game players.
+
+### `GOAL-VALUE-FOR` (function)
+
+ (GOAL-VALUE-FOR REASONER STATE ROLE)
+
+Return the goal value for `role` in `state`, or `nil` if none exists.
+
+ Note that the GDL spec only requires that such values have meaning in terminal
+ states. Game authors sometimes add goal values to nonterminal states, but
+ this is probably not something you should rely on.
+
+
+
+### `INITIAL-STATE` (function)
+
+ (INITIAL-STATE REASONER)
+
+Return the initial state of `reasoner`.
+
+### `LEGAL-MOVES-FOR` (function)
+
+ (LEGAL-MOVES-FOR REASONER STATE ROLE)
+
+Return a list of legal moves for `role` in `state`.
+
+ `ggp:player-select-move` must return exactly one of the items in this list.
+
+
+
+### `MAKE-REASONER` (function)
+
+ (MAKE-REASONER RULES)
+
+Create and return a reasoner for the given GDL `rules`.
+
+ `rules` should be a list of GDL rules with the symbols interned into the
+ appropriate packages. `ggp:player-start-game` will give you this, or you can
+ use `ggp:read-gdl-from-file` to get them without a player if you want to just
+ poke at the reasoner.
+
+
+
+### `NEXT-STATE` (function)
+
+ (NEXT-STATE REASONER STATE MOVES)
+
+Compute and return the successor to `state`, assuming `moves` were made.
+
+ `moves` should be an alist of `(role . move)` pairs, which is what
+ `ggp:player-update-game` will give you.
+
+
+
+### `TERMINALP` (function)
+
+ (TERMINALP REASONER STATE)
+
+Return whether `state` is terminal.
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/05-changelog.markdown Sun Jan 29 22:07:19 2017 +0000
@@ -0,0 +1,19 @@
+Changelog
+=========
+
+Here's the list of changes in each released version.
+
+[TOC]
+
+Pending
+-------
+
+* `start-player` now takes `:server` and `:use-thread` options which it passes
+ along to Clack.
+* Added rudimentary support for writing GDL-II players.
+
+v0.0.1
+------
+
+Initial alpha version. Things are going to break a lot. Don't use this.
+
--- a/docs/api.lisp Sun Jan 29 12:53:28 2017 +0000
+++ b/docs/api.lisp Sun Jan 29 22:07:19 2017 +0000
@@ -1,12 +1,9 @@
(ql:quickload "cl-d-api")
-(defparameter *document-packages*
- (list "GGP" "GGP-RULES"))
-
-(defparameter *output-path*
- #p"docs/03-reference.markdown" )
-
-(defparameter *header*
+(d-api:generate-documentation
+ :cl-ggp
+ #p"docs/03-reference.markdown"
+ (list "GGP" "GGP-RULES")
"The following is a list of all user-facing parts of `cl-ggp`.
If there are backwards-incompatible changes to anything listed here, they will
@@ -15,10 +12,15 @@
Anything not listed here is subject to change at any time with no warning, so
don't touch it.
-")
+"
+ :title "Main API Reference")
(d-api:generate-documentation
- :cl-ggp
- *output-path*
- *document-packages*
- *header*)
+ :cl-ggp.reasoner
+ #p"docs/04-reference-reasoner.markdown"
+ (list "GGP.REASONER")
+ "cl-ggp includes a simple Prolog-based reasoner you can use as a starting point when writing general game players in the `cl-ggp.reasoner` system.
+
+ "
+ :title "Reasoner API Reference")
+
--- a/docs/index.markdown Sun Jan 29 12:53:28 2017 +0000
+++ b/docs/index.markdown Sun Jan 29 22:07:19 2017 +0000
@@ -1,11 +1,17 @@
-`cl-ggp` is a tiny framework for writing [GGP][] players in Common Lisp.
+`cl-ggp` is a tiny framework for writing [general game players][GGP] in Common
+Lisp.
-It handles the GGP protocol for you but *nothing else*. In particular you'll
-need to bring your own logic system to parse the games.
+The `cl-ggp` system handles the GGP protocol for you and *nothing else*. If you
+plan on doing your own GDL reasoning, this is all you need.
+
+The `cl-ggp.reasoner` system contains a simple Prolog-based reasoner using the
+[Temperance][] logic programming library. It's useful as a starting point for
+when writing players.
[GGP]: http://www.ggp.org/
+[Temperance]: https://sjl.bitbucket.io/temperance/
* **License:** MIT/X11
-* **Documentation:** <http://sjl.bitbucket.org/cl-ggp/>
-* **Code:** <http://bitbucket.org/sjl/cl-ggp/>
-* **Issues:** <http://bitbucket.org/sjl/cl-ggp/issues/>
+* **Documentation:** <https://sjl.bitbucket.io/cl-ggp/>
+* **Mercurial:** <https://bitbucket.org/sjl/cl-ggp/>
+* **Git:** <https://github.com/sjl/cl-ggp/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gdl/buttons.gdl Sun Jan 29 22:07:19 2017 +0000
@@ -0,0 +1,85 @@
+(role robot)
+(init (off p))
+(init (off q))
+(init (off r))
+(init (step 1))
+(<= (next (on p))
+ (does robot a)
+ (true (off p)))
+(<= (next (on q))
+ (does robot a)
+ (true (on q)))
+(<= (next (on r))
+ (does robot a)
+ (true (on r)))
+(<= (next (off p))
+ (does robot a)
+ (true (on p)))
+(<= (next (off q))
+ (does robot a)
+ (true (off q)))
+(<= (next (off r))
+ (does robot a)
+ (true (off r)))
+(<= (next (on p))
+ (does robot b)
+ (true (on q)))
+(<= (next (on q))
+ (does robot b)
+ (true (on p)))
+(<= (next (on r))
+ (does robot b)
+ (true (on r)))
+(<= (next (off p))
+ (does robot b)
+ (true (off q)))
+(<= (next (off q))
+ (does robot b)
+ (true (off p)))
+(<= (next (off r))
+ (does robot b)
+ (true (off r)))
+(<= (next (on p))
+ (does robot c)
+ (true (on p)))
+(<= (next (on q))
+ (does robot c)
+ (true (on r)))
+(<= (next (on r))
+ (does robot c)
+ (true (on q)))
+(<= (next (off p))
+ (does robot c)
+ (true (off p)))
+(<= (next (off q))
+ (does robot c)
+ (true (off r)))
+(<= (next (off r))
+ (does robot c)
+ (true (off q)))
+(<= (next (step ?y))
+ (true (step ?x))
+ (succ ?x ?y))
+(succ 1 2)
+(succ 2 3)
+(succ 3 4)
+(succ 4 5)
+(succ 5 6)
+(succ 6 7)
+(legal robot a)
+(legal robot b)
+(legal robot c)
+(<= (goal robot 100)
+ (true (on p))
+ (true (on q))
+ (true (on r)))
+(<= (goal robot 0) (or
+ (not (true (on p)))
+ (not (true (on q)))
+ (not (true (on r)))))
+(<= terminal
+ (true (step 7)))
+(<= terminal
+ (true (on p))
+ (true (on q))
+ (true (on r)))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gdl/tictactoe.gdl Sun Jan 29 22:07:19 2017 +0000
@@ -0,0 +1,135 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Tictactoe
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Roles
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (role xplayer)
+ (role oplayer)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Initial State
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (init (cell 1 1 b))
+ (init (cell 1 2 b))
+ (init (cell 1 3 b))
+ (init (cell 2 1 b))
+ (init (cell 2 2 b))
+ (init (cell 2 3 b))
+ (init (cell 3 1 b))
+ (init (cell 3 2 b))
+ (init (cell 3 3 b))
+ (init (control xplayer))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Dynamic Components
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Cell
+
+ (<= (next (cell ?m ?n x))
+ (does xplayer (mark ?m ?n))
+ (true (cell ?m ?n b)))
+
+ (<= (next (cell ?m ?n o))
+ (does oplayer (mark ?m ?n))
+ (true (cell ?m ?n b)))
+
+ (<= (next (cell ?m ?n ?w))
+ (true (cell ?m ?n ?w))
+ (distinct ?w b))
+
+ (<= (next (cell ?m ?n b))
+ (does ?w (mark ?j ?k))
+ (true (cell ?m ?n b))
+ (or (distinct ?m ?j) (distinct ?n ?k)))
+
+ (<= (next (control xplayer))
+ (true (control oplayer)))
+
+ (<= (next (control oplayer))
+ (true (control xplayer)))
+
+
+ (<= (row ?m ?x)
+ (true (cell ?m 1 ?x))
+ (true (cell ?m 2 ?x))
+ (true (cell ?m 3 ?x)))
+
+ (<= (column ?n ?x)
+ (true (cell 1 ?n ?x))
+ (true (cell 2 ?n ?x))
+ (true (cell 3 ?n ?x)))
+
+ (<= (diagonal ?x)
+ (true (cell 1 1 ?x))
+ (true (cell 2 2 ?x))
+ (true (cell 3 3 ?x)))
+
+ (<= (diagonal ?x)
+ (true (cell 1 3 ?x))
+ (true (cell 2 2 ?x))
+ (true (cell 3 1 ?x)))
+
+
+ (<= (line ?x) (row ?m ?x))
+ (<= (line ?x) (column ?m ?x))
+ (<= (line ?x) (diagonal ?x))
+
+
+ (<= open
+ (true (cell ?m ?n b)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (<= (legal ?w (mark ?x ?y))
+ (true (cell ?x ?y b))
+ (true (control ?w)))
+
+ (<= (legal xplayer noop)
+ (true (control oplayer)))
+
+ (<= (legal oplayer noop)
+ (true (control xplayer)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (<= (goal xplayer 100)
+ (line x))
+
+ (<= (goal xplayer 50)
+ (not (line x))
+ (not (line o))
+ (not open))
+
+ (<= (goal xplayer 0)
+ (line o))
+
+ (<= (goal oplayer 100)
+ (line o))
+
+ (<= (goal oplayer 50)
+ (not (line x))
+ (not (line o))
+ (not open))
+
+ (<= (goal oplayer 0)
+ (line x))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (<= terminal
+ (line x))
+
+ (<= terminal
+ (line o))
+
+ (<= terminal
+ (not open))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--- a/package.reasoner.lisp Sun Jan 29 12:53:28 2017 +0000
+++ b/package.reasoner.lisp Sun Jan 29 22:07:19 2017 +0000
@@ -7,6 +7,6 @@
:terminalp
:legal-moves-for
:goal-value-for)
- (:documentation "The package containing a simple GGP reasoner."))
+ (:documentation "This package contains a simple GGP reasoner. It can be useful as a starting point for writing general game players."))
--- a/src/reasoner.lisp Sun Jan 29 12:53:28 2017 +0000
+++ b/src/reasoner.lisp Sun Jan 29 22:07:19 2017 +0000
@@ -5,8 +5,11 @@
(and (consp form)
(eq (car form) 'ggp-rules::<=)))
+(defun dedupe (things)
+ (remove-duplicates things :test #'equal))
+
(defun normalize-state (state)
- (remove-duplicates state :test #'equal))
+ (dedupe state))
;;;; Reasoner -----------------------------------------------------------------
@@ -39,6 +42,7 @@
;; todo this
rules)
+
(defun load-rule (rule)
(if (gdl-rule-p rule)
(apply #'invoke-rule t (rest rule))
@@ -51,6 +55,14 @@
(defun make-reasoner (rules)
+ "Create and return a reasoner for the given GDL `rules`.
+
+ `rules` should be a list of GDL rules with the symbols interned into the
+ appropriate packages. `ggp:player-start-game` will give you this, or you can
+ use `ggp:read-gdl-from-file` to get them without a player if you want to just
+ poke at the reasoner.
+
+ "
(let ((reasoner (make-instance 'reasoner)))
(load-rules-into-reasoner reasoner rules)
reasoner))
@@ -94,11 +106,18 @@
(defun initial-state (reasoner)
+ "Return the initial state of `reasoner`."
(normalize-state
(query-for (reasoner-database reasoner) ?what
(ggp-rules::init ?what))))
(defun next-state (reasoner state moves)
+ "Compute and return the successor to `state`, assuming `moves` were made.
+
+ `moves` should be an alist of `(role . move)` pairs, which is what
+ `ggp:player-update-game` will give you.
+
+ "
(with-database (reasoner-database reasoner)
(ensure-state reasoner state)
(ensure-moves reasoner moves)
@@ -107,12 +126,54 @@
(defun legal-moves (reasoner state)
+ "Return an alist of `(role . move)` for all legal moves in `state`."
(with-database (reasoner-database reasoner)
(ensure-state reasoner state)
- (query-all t (ggp-rules::legal ?role ?action))))
+ (dedupe (loop :for move :in (query-all t (ggp-rules::legal ?role ?action))
+ :collect (cons (getf move '?role)
+ (getf move '?action))))))
(defun legal-moves-for (reasoner state role)
- (loop :for move :in (legal-moves reasoner state)
- :when (eq (getf move '?role) role)
- :collect (getf move '?action)))
+ "Return a list of legal moves for `role` in `state`.
+
+ `ggp:player-select-move` must return exactly one of the items in this list.
+
+ "
+ (with-database (reasoner-database reasoner)
+ (ensure-state reasoner state)
+ (dedupe (invoke-query-for t '?action `(ggp-rules::legal ,role ?action)))))
+
+
+(defun goal-values (reasoner state)
+ "Return an alist of `(role . value)` pairs of goal values for `state`.
+
+ Note that the GDL spec only requires that such values have meaning in terminal
+ states. Game authors sometimes add goal values to nonterminal states, but
+ this is probably not something you should rely on.
+ "
+ (with-database (reasoner-database reasoner)
+ (ensure-state reasoner state)
+ (dedupe (loop :for goal :in (query-all t (ggp-rules::goal ?role ?value))
+ :collect (cons (getf goal '?role)
+ (getf goal '?value))))))
+
+(defun goal-value-for (reasoner state role)
+ "Return the goal value for `role` in `state`, or `nil` if none exists.
+
+ Note that the GDL spec only requires that such values have meaning in terminal
+ states. Game authors sometimes add goal values to nonterminal states, but
+ this is probably not something you should rely on.
+
+ "
+ (with-database (reasoner-database reasoner)
+ (ensure-state reasoner state)
+ (car (invoke-query-for t '?value `(ggp-rules::goal ,role ?value)))))
+
+
+(defun terminalp (reasoner state)
+ "Return whether `state` is terminal."
+ (with-database (reasoner-database reasoner)
+ (ensure-state reasoner state)
+ (prove t ggp-rules::terminal)))
+