Discovering Common Lisp with trivial-gamekit
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:
- Quicklisp
- 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}/quicklisp
The 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