lthms' avatar, a hand drawing looking person, wearing a headset, close to a window on a raining night
Thomas Letan
lthms · he/him

Did you come across something which caught your attention? Don’t hesitate to shoot me an email in my public inbox.

 Published on

Discovering Common Lisp with trivial-gamekit

 lisp

I always wanted to learn some Lisp dialect. In 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?Spoiler alert: this wasn’t the most efficient approach for the lykan project. But it was fun. I asked on Mastodon  if there were good game engines 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 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 projects, while SBCL and CCL are two Lisp implementations. I had already installed Clisp , and it took me quite some time 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}/quicklispThe purpose of this directory is similar to the Go workspace . .

Quicklisp is not a native feature of SBCL, and requires a small bit of configurations to be made available 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 of June 2018, 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 straightforward. From my understanding, new Quicklisp projects have to be located inside ${HOME}/quicklisp/local-projects. I am not particularly happy with this, 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 create 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 sound scary —it suggests 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, let’s 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!

Running our Program

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

sbcl --eval '(ql:quickload :lysk)' --eval '(lysk:run)'

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 advice 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 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 use 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