#+BEGIN_EXPORT html

Discovering Common Lisp with trivial-gamekit

This article has originally been published on June 17, 2018.

#+END_EXPORT I always wanted to learn some Lisp dialect. In the meantime, [[https://github.com/lkn-org/lykan][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 [[https://mastodon.social/@lthms/100135240390747697][Mastodon]] if there were good game engine for Lisp, and someone told me about [[https://github.com/borodust/trivial-gamekit][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: #+BEGIN_SRC bash pijul clone "https://pijul.lthms.xyz/lkn/lysk" #+END_SRC In addition, the complete project detailed in this article is available [[https://gist.github.com/lthms/9833f4851843119c966917775b4c4180][as a gist]]. #+TOC: headlines 2 #+BEGIN_EXPORT html
site/posts/DiscoveringCommonLisp.org
#+END_EXPORT * Common Lisp, Quicklisp and trivial-gamekit The trivial-gamekit [[https://borodust.github.io/projects/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 [[https://quicklisp.org/beta][quicklisp website]] in the trivial-gamekit documentation). As for SBCL and CCL, it turns out they are two Lisp implementations. I had already installed [[https://www.archlinux.org/packages/?name=clisp][clisp]], and it took me quite some times to understand my mistake. Fortunately, [[https://www.archlinux.org/packages/?name=sbcl][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 [[https://github.com/golang/go/wiki/SettingGOPATH][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: #+BEGIN_SRC common-lisp (load "~/quicklisp/setup") #+END_SRC There is one final step to be able to use trivial-gamekit. #+BEGIN_SRC bash sbcl --eval '(ql-dist:install-dist "http://bodge.borodust.org/dist/org.borodust.bodge.txt")' \ --quit #+END_SRC As for now[fn::June 2018], Quicklisp [[https://github.com/quicklisp/quicklisp-client/issues/167][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. #+BEGIN_SRC bash mkdir ~/quicklisp/local-projects/lysk #+END_SRC Quicklisp packages (systems?) are defined through ~asd~ files. I have firstly created ~lysk.asd~ as follows: #+BEGIN_SRC common-lisp (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"))) #+END_SRC ~: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: #+BEGIN_SRC common-lisp (:file "seconds" :depends-on "first") #+END_SRC 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: #+BEGIN_SRC common-lisp (defpackage :lysk (:use :cl) (:export run app)) #+END_SRC 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 [[https://github.com/borodust/trivial-gamekit/issues/30][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. #+BEGIN_SRC common-lisp (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)) #+END_SRC 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. #+BEGIN_SRC bash sbcl --eval '(ql:quickload :lysk)' --eval '(lysk:run)' #+END_SRC 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: #+BEGIN_SRC common-lisp (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"))) #+END_SRC 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: #+BEGIN_SRC common-lisp (cl:defpackage :lysk.bundle (:use :cl) (:export deliver)) (cl:in-package :lysk.bundle) (defun deliver () (gamekit.distribution:deliver :lysk 'lysk:app)) #+END_SRC To bundle the game, we can use ~sbcl~ from our command line interface. #+BEGIN_SRC bash sbcl --eval "(ql:quickload :lysk/bundle)" \ --eval "(lysk.bundle:deliver)" \ --quit #+END_SRC * 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. #+BEGIN_SRC makefile 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 #+END_SRC