--- a/cl-ggp.asd Sun Jan 29 22:29:18 2017 +0000
+++ b/cl-ggp.asd Fri Feb 10 16:27:29 2017 +0000
@@ -1,18 +1,17 @@
(asdf:defsystem :cl-ggp
- :name "ggp"
:description "A framework for writing General Game Playing clients."
:author "Steve Losh <steve@stevelosh.com>"
:maintainer "Steve Losh <steve@stevelosh.com>"
:license "MIT/X11"
- :version "0.0.1"
+ :version "1.0.0"
- :depends-on (#:clack
- #:flexi-streams
- #:optima
- #:fare-quasiquote-optima
- #:fare-quasiquote-readtable)
+ :depends-on (:clack
+ :flexi-streams
+ :optima
+ :fare-quasiquote-optima
+ :fare-quasiquote-readtable)
:serial t
:components ((:file "package")
--- a/cl-ggp.reasoner.asd Sun Jan 29 22:29:18 2017 +0000
+++ b/cl-ggp.reasoner.asd Fri Feb 10 16:27:29 2017 +0000
@@ -1,13 +1,11 @@
(asdf:defsystem :cl-ggp.reasoner
- :name "ggp.reasoner"
-
:description "A reasoner to use as a starting point for General Game Playing clients."
:author "Steve Losh <steve@stevelosh.com>"
:maintainer "Steve Losh <steve@stevelosh.com>"
:license "MIT/X11"
- :version "0.0.1"
+ :version "1.0.0"
:depends-on (:temperance
:cl-ggp)
--- a/docs/02-overview.markdown Sun Jan 29 22:29:18 2017 +0000
+++ b/docs/02-overview.markdown Fri Feb 10 16:27:29 2017 +0000
@@ -1,16 +1,21 @@
Overview
========
-`cl-ggp` handles the GGP protocol for you. Players are implemented as CLOS
-objects.
+This document assumes you know what [General Game Playing][] is, what [GDL][]
+is, and how the GGP community/competitions/etc work.
-This document assumes you know what General Game Playing is, what GDL is, and
-how the GGP community/competitions/etc work.
+[General Game Playing]: http://ggp.org/
+[GDL]: https://en.wikipedia.org/wiki/Game_Description_Language
[TOC]
-Basics
-------
+GGP Protocol
+------------
+
+The `cl-ggp` system handles the GGP network protocol and game flow for you.
+Players are implemented as CLOS objects.
+
+### Basics
You can create your own player by extending the `ggp-player` class, creating an
object, and calling `start-player` on it to fire it up:
@@ -30,8 +35,7 @@
You can kill a player with `kill-player`.
-Functionality
--------------
+### Game Functionality
`cl-ggp` defines four generic methods that are called on players at various
points in each game. You can provide method definitions for some or all of
@@ -40,10 +44,10 @@
At a minimum you **must** implement `player-select-move`. The others are
optional and will default to doing nothing.
-### player-start-game
+#### player-start-game
:::lisp
- (defmethod player-start-game ((player YOUR-PLAYER) rules role timeout)
+ (defmethod ggp:player-start-game ((player YOUR-PLAYER) rules role timeout)
...)
This is called when a new game starts.
@@ -56,10 +60,10 @@
`timeout` is the timestamp that the response to the server is due by, in
internal-real time units (more on this later).
-### player-update-game
+#### player-update-game
:::lisp
- (defmethod player-update-game ((player YOUR-PLAYER) moves)
+ (defmethod ggp:player-update-game ((player YOUR-PLAYER) moves)
...)
This is called once per turn, to allow you to update the game state with the
@@ -68,10 +72,10 @@
`moves` will be an association list of `(role . move)` conses representing the
moves made by each player last turn.
-### player-select-move
+#### player-select-move
:::lisp
- (defmethod player-select-move ((player YOUR-PLAYER) timeout)
+ (defmethod ggp:player-select-move ((player YOUR-PLAYER) timeout)
...)
This is called once per turn. It needs to return the move your player wants to
@@ -80,17 +84,16 @@
`timeout` is the timestamp that the response to the server is due by, in
internal-real time units (more on this later).
-### player-stop-game
+#### player-stop-game
:::lisp
- (defmethod player-stop-game ((player YOUR-PLAYER))
+ (defmethod ggp:player-stop-game ((player YOUR-PLAYER))
...)
This is called when the game is stopped. You can use it for things like tearing
down any extra data structures you've made, suggesting a GC to your Lisp, etc.
-Timeouts
---------
+### Timeouts
The GGP protocol specifies time limits for players.
@@ -128,8 +131,7 @@
[get-internal-real-time]: http://www.lispworks.com/documentation/HyperSpec/Body/f_get_in.htm#get-internal-real-time
[internal-time-units-per-second]: http://www.lispworks.com/documentation/HyperSpec/Body/v_intern.htm#internal-time-units-per-second
-Symbols
--------
+### Symbols
The other tricky part about `cl-ggp` is how it handles symbols.
@@ -162,52 +164,106 @@
This is kind of shitty, and the author is aware of that. Suggestions for less
shitty alternatives that still feel vaguely lispy are welcome.
+Reasoning
+---------
+
+The `cl-ggp.reasoner` system contains a Prolog-based GDL reasoner based on the
+[Temperance][] logic programming library.
+
+[Temperance]: https://sjl.bitbucket.io/temperance/
+
+It's useful as a starting point if you just want to get a bot up and running
+quickly. If you want more speed or control over the reasoning process you'll
+probably want to replace it with your own implementation.
+
+You can make a reasoner for a set of GDL rules (e.g. the rules given to you by
+`player-start-game`) with `(make-reasoner rules)`.
+
+Once you've got a reasoner you can ask it for the initial state of the game with
+`(initial-state reasoner)`. This will give you back a state object -- it's
+currently just a list, but this may change in the future, so you should just
+treat it as an opaque object.
+
+Once you've got a state and a set of moves you can compute the next state with
+`(next-state reasoner current-state moves)`.
+
+States can be queried for their terminality, goal values, and legal moves with
+`terminalp`, `goal-values-for`, and `legal-moves-for` respectively.
+
+See the [Reasoner API Reference][reasoner] for more details.
+
+[reasoner]: ../reference-reasoner/
+
Example Player
--------------
-`cl-ggp` is pretty bare bones, so it's tough to show an example player on its
-own without bringing in a logic library. But we can at least sketch out
-a stupid little player that just returns the same move all the time, regardless
-of whether it's valid or not, just to show the end-to-end process of creating
-a player.
+Let's create a small example player that uses the reasoner to play any GGP game
+legally. We'll start by creating a class for the player, with three slots to
+keep track of the data we need to play a game:
+
+ :::lisp
+ (defclass random-player (ggp:ggp-player)
+ ((role :accessor p-role)
+ (current-state :accessor p-current-state)
+ (reasoner :accessor p-reasoner)))
-First we'll define the player class and implement the required
-`player-select-move` method for it:
+Now we can implement `player-start-game`. We'll store the role we've been
+assigned, and create a reasoner so we can compute our legal moves later. We'll
+ignore the deadline because we're not doing any extensive processing:
+
+ :::lisp
+ (defmethod ggp:player-start-game
+ ((player random-player) rules role deadline)
+ (declare (ignore deadline))
+ (setf (p-role player) role
+ (p-reasoner player) (ggp.reasoner:make-reasoner rules)))
+
+Next we'll implement `player-update-game`, which will compute the current state
+of the game and store it in the `current-state` slot:
:::lisp
- (defclass simple-player (ggp:ggp-player)
- ())
-
- (defmethod ggp:player-select-move ((player simple-player) timeout)
- 'ggp-rules::wait)
+ (defmethod ggp:player-update-game
+ ((player random-player) moves)
+ (setf (p-current-state player)
+ (if (null moves)
+ (ggp.reasoner:initial-state (p-reasoner player))
+ (ggp.reasoner:next-state (p-reasoner player)
+ (p-current-state player)
+ moves))))
-Our player doesn't store any state of its own, so it doesn't need any extra
-slots. Notice how `player-select-move` returns a symbol from the `GGP-RULES`
-package as discussed above.
+If `moves` is null we ask the reasoner for the initial state, otherwise we
+compute the next state from the current one and the moves that were made.
-The move our stupid player always returns is `WAIT`. If the game supports that
-move we'll make it every time, otherwise the game server will reject it as
-invalid and just choose a random move for us.
-
-Now we can actually create a player:
+Now we can implement `player-select-move`. We'll just ask the reasoner for all
+our legal moves and choose one at random. If we wanted to make a smarter
+player, this is where we would search the game tree to find a good move:
:::lisp
- (defvar *player*
- (make-instance 'simple-player
- :name "SimplePlayer"
- :port 5000))
+ (defmethod ggp:player-select-move
+ ((player random-player) deadline)
+ (declare (ignore deadline))
+ (let ((moves (ggp.reasoner:legal-moves-for
+ (p-reasoner player)
+ (p-current-state player)
+ (p-role player))))
+ (nth (random (length moves)) moves)))
-And fire it up:
+Finally we can implement `player-stop-game`. We'll just clear out the player's
+slots so the data can be garbage collected:
:::lisp
- (ggp:start-player *player*)
+ (defmethod ggp:player-stop-game
+ ((player random-player))
+ (setf (p-current-state player) nil
+ (p-reasoner player) nil
+ (p-role player) nil))
-Now we can play a few games with it. We'll probably lose every time unless
-we're playing an unscrambled game of [Don't Press the Button][dptb].
-
-Once we're done we can kill it to free up the port:
+Now we can make an instance of our player and start it up!
:::lisp
- (ggp:kill-player *player*)
+ (defvar *random-player*
+ (make-instance 'random-player
+ :name "RandomPlayer"
+ :port 4000))
-[dptb]: https://bitbucket.org/snippets/sjl/erRjL
+ (ggp:start-player *random-player*)
--- a/docs/04-reference-reasoner.markdown Sun Jan 29 22:29:18 2017 +0000
+++ b/docs/04-reference-reasoner.markdown Fri Feb 10 16:27:29 2017 +0000
@@ -1,6 +1,6 @@
# 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.
+cl-ggp includes a simple Prolog-based reasoner you can use as a starting point when writing general game players in the `ggp.reasoner` system.
[TOC]
--- a/docs/05-changelog.markdown Sun Jan 29 22:29:18 2017 +0000
+++ b/docs/05-changelog.markdown Fri Feb 10 16:27:29 2017 +0000
@@ -5,8 +5,8 @@
[TOC]
-Pending
--------
+v1.0.0
+------
* `start-player` now takes `:server` and `:use-thread` options which it passes
along to Clack.
--- a/docs/api.lisp Sun Jan 29 22:29:18 2017 +0000
+++ b/docs/api.lisp Fri Feb 10 16:27:29 2017 +0000
@@ -19,7 +19,7 @@
: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.
+ "cl-ggp includes a simple Prolog-based reasoner you can use as a starting point when writing general game players in the `ggp.reasoner` system.
"
:title "Reasoner API Reference")
--- a/package.lisp Sun Jan 29 22:29:18 2017 +0000
+++ b/package.lisp Fri Feb 10 16:27:29 2017 +0000
@@ -16,8 +16,7 @@
:start-player
:kill-player
- :read-gdl-from-file
- )
+ :read-gdl-from-file)
(:documentation "The main GGP package."))
(defpackage :ggp-rules