lthms

PhD Student in Computer Science

FP Enthusiast

Contents

I always wanted to learn some Lisp dialect. On the meantime, lykan —my Slayers Online clone— begins to take shape. So, of course, my brain got an idea: why not writing a client for lykan in some Lisp dialect? I asked on Mastodon if there were good game engine for Lisp, and someone told me about trivial-gamekit.

I have no idea if I will manage to implement a decent client using trivial-gamekit, but why not trying? This article is the first of a series about my experiments, discoveries and difficulties.

The code of my client is hosted on my server, using the pijul vcs. If you have pijul installed, you can clone the repository:

pijul clone "https://pijul.lthms.xyz/lkn/lysk"

In addition, the complete project detailed in this article is available as a gist.

Common Lisp, Quicklisp and trivial-gamekit

The trivial-gamekit website lists several requirements. Two are related to Lisp:

  1. Quicklisp
  2. SBCL or CCL

Quicklisp is an experimental package manager for Lisp project (it was easy to guess, because there is a link to quicklisp website in the trivial-gamekit documentation). As for SBCL and CCL, it turns out they are two Lisp implementations. I had already installed clisp, and it took me quite some times to understand my mistake. Fortunately, sbcl is also packaged in ArchLinux.

With a compatible Lisp implementation, installing Quicklisp as a user is straightforward. Following the website instructions is enough. At the end of the process, you will have a new directory ${HOME}/quicklisp, whose purpose is similar to the go workspace.

Quicklisp is not a native feature of sbcl, and has to be loaded to be available. To do it automatically, you have to create a file ${HOME}/.sbclrc, with the following content:

(load "~/quicklisp/setup")

There is one final step to be able to use trivial-gamekit.

sbcl --eval "(ql-dist:install-dist \"http://bodge.borodust.org/dist/org.borodust.bodge.txt\")" --quit

As for now1, Quicklisp does not support HTTPS.

Introducing Lysk

Packaging

The first thing I search for when I learn a new language is how projects are organized. From this perspective, trivial-gamekit pointed me directly to Quicklisp

Creating a new Quicklisp project is very simple, and this is a very good thing. As I said, the ${HOME}/quicklisp directory acts like the go workspace. As far as I can tell, new Quicklisp projects have to be located inside ${HOME}/quicklisp/local-projects. I am not particularly happy with it, but it is not really important.

The current code name of my Lisp game client is lysk.

mkdir ~/quicklisp/local-projects/lysk

Quicklisp packages (systems?) are defined through asd files. I have firstly created lysk.asd as follows:

(asdf:defsystem lysk
  :description "Lykan Game Client"
  :author "lthms"
  :license  "GPLv3"
  :version "0.0.1"
  :serial t
  :depends-on (trivial-gamekit)
  :components ((:file "package")
               (:file "lysk")))

:serial t means that the files detailed in the components field depends on the previous ones. That is, lysk.lisp depends on package.lisp in this case. It is possible to manage files dependencies manually, with the following syntax:

(:file "seconds" :depends-on "first")

I have declared only one dependency: trivial-gamekit. That way, Quicklisp will load it for us.

The first “true” Lisp file we define in our skeleton is package.lisp. Here is its content:

(defpackage :lysk
  (:use :cl)
  (:export run app))

Basically, this means we use two symbols, run and app.

A Game Client

The lysk.lisp file contains the program in itself. My first goal was to obtain the following program: at startup, it shall creates a new window in fullscreen, and exit when users release the left button of their mouse. It is worth mentioning that I had to report an issue to the trivial-gamekit upstream in order to make my program work as expected. While it may sounds scary —it definitely shows trivial-gamekit is a relatively young project— the author has implemented a fix in less than an hour! He also took the time to answer many questions I had when I joined the #lispgames Freenode channel.

Before going any further, lets have a look at the complete file.

(cl:in-package :lysk)

(gamekit:defgame app () ()
                 (:fullscreen-p 't))

(defmethod gamekit:post-initialize ((app app))
  (gamekit:bind-button :mouse-left :released
                       (lambda () (gamekit:stop))))

(defun run ()
  (gamekit:start 'app))

The first line is some kind of header, to tell Lisp the owner of the file.

The gamekit:defgame function allows for creating a new game application (called app in our case). I ask for a fullscreen window with :fullscreen-p. Then, we use the gamekit:post-initialize hook to bind a handler to the release of the left button of our mouse. This handler is a simple call to gamekit:stop. Finally, we define a new function run which only starts our application.

Pretty straightforward, right?

Running our Program

To “play” our game, we can start the sbcl REPL.

$ sbcl
* (ql:quickload :lysk)
* (lysk:run)

And it works!

A Standalone Executable

It looks like empower a REPL-driven development. That being said, once the development is finished, I don’t think I will have a lot of success if I ask my future players to start sbcl to enjoy my game. Fortunately, trivial-gamekit provides a dedicated function to bundle the game as a standalone executable.

Following the advises of the borodust —the trivial-gamekit author— I created a second package to that end. First, we need to edit the lysk.asd file to detail a second package:

(asdf:defsystem lysk/bundle
  :description "Bundle the Lykan Game Client"
  :author "lthms"
  :license  "GPLv3"
  :version "0.0.1"
  :serial t
  :depends-on (trivial-gamekit/distribution lysk)
  :components ((:file "bundle")))

This second package depends on lysk (our game client) and and trivial-gamekit/distribution. The latter provides the deliver function, and we use it in the bundle.lisp file:

(cl:defpackage :lysk.bundle
  (:use :cl)
  (:export deliver))

(cl:in-package :lysk.bundle)

(defun deliver ()
  (gamekit.distribution:deliver :lysk 'lysk:app))

To bundle the game, we can use sbcl from our command line interface.

sbcl --eval "(ql:quickload :lysk/bundle)" --eval "(lysk.bundle:deliver)" --quit

Conclusion

Objectively, there is not much in this article. However, because I am totally new to Lisp, it took me quite some time to get these few lines of code to work together. All being told I think this constitutes a good trivial-gamekit skeleton. Do not hesitate to us it this way.

Thanks again to borodust, for your time and all your answers!

Appendix: a Makefile

I like Makefile, so here is one to run the game directly, or bundle it.

run:
        @sbcl --eval "(ql:quickload :lysk)" \
              --eval "(lysk:run)"

bundle:
        @echo -en "[ ] Remove old build"
        @rm -rf build/
        @echo -e "\r[*] Remove old build"
        @echo "[ ] Building"
        @sbcl --eval "(ql:quickload :lysk/bundle)" --eval "(lysk.bundle:deliver)" --quit
        @echo "[*] Building"

.PHONY: bundle run

  1. June 2018