# HG changeset patch # User Steve Losh # Date 1486743736 0 # Node ID e5a300df7d4903b40df36c88fe1e1a9efdb948b4 # Parent f5a0fcf62e5ef911a3cae981420b176478d5afcb cl-ggp: Update site. diff -r f5a0fcf62e5e -r e5a300df7d49 cl-ggp/changelog/index.html --- a/cl-ggp/changelog/index.html Sun Jan 29 22:07:43 2017 +0000 +++ b/cl-ggp/changelog/index.html Fri Feb 10 16:22:16 2017 +0000 @@ -15,14 +15,16 @@

Changelog

Here's the list of changes in each released version.

-

Pending

+

v1.0.0

v0.0.1

Initial alpha version. Things are going to break a lot. Don't use this.

diff -r f5a0fcf62e5e -r e5a300df7d49 cl-ggp/index.html --- a/cl-ggp/index.html Sun Jan 29 22:07:43 2017 +0000 +++ b/cl-ggp/index.html Fri Feb 10 16:22:16 2017 +0000 @@ -14,9 +14,9 @@

cl-ggp is a tiny framework for writing general game players in Common Lisp.

-

The cl-ggp system handles the GGP protocol for you and nothing else. If you +

The 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 +

The 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.

In a nutshell: when (get-internal-real-time) returns the number given to you in timeout, your message better have already reached the server.

-

Symbols

+

Symbols

The other tricky part about cl-ggp is how it handles symbols.

Game descriptions are written in GDL, a fragment of which might look like this:

(role x)
@@ -149,45 +154,91 @@
 correctly.

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 ggp.reasoner system contains a Prolog-based GDL reasoner based on the +Temperance logic programming library.

+

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 for more details.

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.

-

First we'll define the player class and implement the required -player-select-move method for it:

-
(defclass simple-player (ggp:ggp-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:

+
(defclass random-player (ggp:ggp-player)
+  ((role          :accessor p-role)
+   (current-state :accessor p-current-state)
+   (reasoner      :accessor p-reasoner)))
+
-(defmethod ggp:player-select-move ((player simple-player) timeout) - 'ggp-rules::wait) + +

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:

+
(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)))
 
-

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.

-

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:

-
(defvar *player*
-  (make-instance 'simple-player
-                 :name "SimplePlayer"
-                 :port 5000))
+

Next we'll implement player-update-game, which will compute the current state +of the game and store it in the current-state slot:

+
(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))))
 
-

And fire it up:

-
(ggp:start-player *player*)
+

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.

+

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:

+
(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)))
 
-

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.

-

Once we're done we can kill it to free up the port:

-
(ggp:kill-player *player*)
+

Finally we can implement player-stop-game. We'll just clear out the player's +slots so the data can be garbage collected:

+
(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 make an instance of our player and start it up!

+
(defvar *random-player*
+  (make-instance 'random-player
+                 :name "RandomPlayer"
+                 :port 4000))
+
+(ggp:start-player *random-player*)