summaryrefslogtreecommitdiffstats
path: root/site/posts/lisp-journey-getting-started.org
blob: a198c3d244a0e6365fce0b3ff60d0b019d98c9f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#+BEGIN_EXPORT html
<h1>Discovering Common Lisp with <code>trivial-gamekit</code></h1>

<span class="time">June 17, 2018</span>
#+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]].

#+OPTIONS: toc:nil
#+TOC: headlines 2

* 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
(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