# HG changeset patch # User Steve Losh # Date 1486744049 0 # Node ID 4e100d7ed0b0702ce240a7dc75946e6ef54d39bd # Parent e33f59e97ecbed9c46a0c526ddf1a3486fc98ee8 Prep for initial release diff -r e33f59e97ecb -r 4e100d7ed0b0 cl-ggp.asd --- 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 " :maintainer "Steve Losh " :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") diff -r e33f59e97ecb -r 4e100d7ed0b0 cl-ggp.reasoner.asd --- 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 " :maintainer "Steve Losh " :license "MIT/X11" - :version "0.0.1" + :version "1.0.0" :depends-on (:temperance :cl-ggp) diff -r e33f59e97ecb -r 4e100d7ed0b0 docs/02-overview.markdown --- 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*) diff -r e33f59e97ecb -r 4e100d7ed0b0 docs/04-reference-reasoner.markdown --- 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] diff -r e33f59e97ecb -r 4e100d7ed0b0 docs/05-changelog.markdown --- 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. diff -r e33f59e97ecb -r 4e100d7ed0b0 docs/api.lisp --- 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") diff -r e33f59e97ecb -r 4e100d7ed0b0 package.lisp --- 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