Discovering Common Lisp with trivial-gamekit
This article has originally been published on June 17, 2018.
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? 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.
Revisions
This revisions table has been automatically generated
from the git
history
of this website repository, and the change
descriptions may not always be as useful as they
should.
You can consult the source of this file in its current version here.
2020-02-27 | Theme reloading | 1a9268f |
2020-02-19 | Various improvement in the content generation process | 06809e8 |
2020-02-16 | Automatically generate a revision table from git history | b47ee36 |
2020-02-05 | Rename org posts | 7c47d2b |
2020-02-04 | Change the date format | 1f851ca |
2020-02-04 | Various fixes related to absolute URLs | ed3ce3e |
2020-02-04 | Initial commit with previous content and a minimal theme | 9754a53 |
1 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 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.
2 Introducing Lysk
2.1 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
.
2.2 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?
2.3 Running our Program
To “play” our game, we can start the sbcl REPL.
sbcl --eval '(ql:quickload :lysk)' --eval '(lysk:run)'
And it works!
2.4 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
3 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!
4 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
Footnotes:
June 2018