Vito Van

Porting Lisp Game to the Web

2023/06

TL;DR

Here is a game written in Common Lisp and ported to the web:

Pelúsica: Survive and make melodies

pelusica

It's an action game in which you can control the blue dot with your keyboard to create music and avoid colliding with other dots.

You can play it in your browser:

open in the browser

or download it for Linux, macOS, and Windows.

Motivation

I made a game, and I sent it to Jack.

Jack asked: "How to run it?"

I thought Jack was stupid, so I ignored him and sent it to Emma.

Emma said: "It cannot be opened."

I was like: "Really?"

It turns out Microsoft and Apple were sitting between me and my friends, saying:

"No, you are not opening this, because Vito didn't pay."

Like this:

Windows protected your PC

or this:

"Pelusica.app" cannot be opened because the developer cannot be verified.

I hate this.

"Who watches the watchmen?" I don't know.

I just want to share my game without making deal with the OS police. Since I don't have the resources to fight them, I have to find a workaround. So I tried porting this game to the web.

Hopefully, we won't encounter the web police too soon.

Exploration

to Lisp

The first step should be running Lisp in the browser.

Today we can run Linux and Windows inside a browser, running a Lisp system is just a piece of cake, compared to an operating system.

I found the following two approaches:

I didn't list everything here, you could find more. I also didn't list the approach of (Lisp (inside an emulated x86 (inside a browser))), because it sounds too aggressive.

The point is the work could be done or has already been done.

So, what now?

to Game

We need to be able to provide an interactive multimedia experience to the players, that is:

Lisp the great and powerful itself won't do any good to that, unless you cut the graphic and sound parts, and decide to make a text-based game.

Compiling SDL2 and Cairo

I was using SDL2 and Cairo to provide the interactive multimedia experience on the desktop, with the assistance of cl-sdl2 and cl-cairo2.

How do I use them in the browser?

Maybe we could try WebAssembly.

SDL2 is WebAssembly-ready since the last year.

And I spent some time on Cairo and compiled Cairo into WebAssembly.

Try to Use SDL2 and Cairo With Lisp

Now we know that we can use SDL2 and Cairo in the browser, but how?

On the desktop, we use cl-sdl2 and cl-cairo2, which is CFFIload-shared-objectdlopen under the hood.

How do we do it in WebAssembly? Or, is it possible?

It is possible.

Dynamic Linking and dlopen in WebAssembly is not a problem, the problem is to make CFFI work with it.

No, not with it, with the Lisp implementation we chose.

CFFI depends on ASDF and libffi. libffi had its WebAssembly support earlier this year, so we only need to focus on ASDF.

Only one of the above Lisps was officially supported by ASDF, which is ECL. So, to save time, we should stick to ECL and see if we can make any progress.


I paused.


I was intimidated by ECL when I was young and fragile.

It was a long long time ago, one day morning, I loaded my project into ECL and tried to generate a standalone application. It took a thousand years to compile and end up with some weird ASDF-related error that I still can't solve today. I also failed to upgrade ASDF for ECL after many hours of scrambling. I must say this was solely because of my stupidity, ECL is great, I am just too dumb.


And I paused.


I was so frightened by this task, so I asked Daniel, whose name will be printed out along with the word "Copyright" when you were starting ECL, like this:

ECL (Embeddable Common-Lisp) 21.2.1 (git:6af4b3e51f80d569d88f0e8a4782b21c8b04970e)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2013 Juan J. Garcia-Ripoll
Copyright (C) 2018 Daniel Kochmanski ;; <---- this Daniel
Copyright (C) 2021 Daniel Kochmanski and Marius Gerbershagen
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.
Top level.
>

And Daniel said: "go for it!"

Then I went for it.

That's when the nightmare began. It turns out that making ASDF work with ECL in the browser is way more difficult than on the desktop.

I was drowned in the sea of ECL source code; suffocated on the mountain of Emscripten reference; and lost in the forest of 13987 lines of asdf.lisp.

Although Marius Gerbershagen (another ECL maintainer, also listed in the ECL copyright notice) pointed out another path (static linking) for me to achieve the goal, I still haven't made it.

My intelligence was heavily questioned.


I felt sad and paused again.

Until one day.


I was talking with Jack on the phone:

"Now, press the return key."

"Where is the return key?"

"It's on your keyboard, damn! The one above shift!"

"Oh, you mean Enter?"

"......"

Jack was using a Windows PC, and the letter on his keycap is "Enter".

I was using a MacBook, it is "return".

So: If my game were running in the browser one day, how do I know the player's operating system?

Did you just say navigator.userAgent? navigator.platform?

Yeah, that's it!

Wait, what language is that? JavaScript?

No, shit.

If Using JavaScript Were Inevitable

It seems to be it.

Running in the browser, you have to deal with the browser.

Not only to detect the operating system but also:

With C, we can call JavaScript like this:

#include <emscripten.h>

EM_JS(void, call_alert, (), {
  alert('hello world!');
  throw 'all done';
});

int main() {
  call_alert();
  return 0;
}

With Lisp, how?

We can:

  1. write JavaScript in a C function
  2. expose that the C function
  3. defcfun in Lisp with CFFI to use it

If the above feels too complicated, we can also use SFFI from ECL, to (write JavaScript code (inside C code (inside Lisp code))), maybe like this:

(defun alert (x)
  (ffi:clines "#include <emscripten.h>")
  (ffi:c-inline (x) (:cstring) :void "
        EM_JS(void, call_alert, (), {
          alert('hello world!');
          throw 'all done';
        });
        call_alert(#0);
"))

Beautiful, right? I wouldn't dare to touch things like that, it's holy and sacred, and it should be reserved for sainthood.

Now let's forget the SFFI approach and sort out our tasks:

I am only good at the last one.

Using SDL2 and Cairo With Lisp in JSCL

If

  1. using JavaScript were inevitable
  2. using SDL2 and Cairo with WASM Lisp were hard to achieve

then, why don't we

Let's try JSCL, to call JavaScript within Lisp:

(#j:alert "this is an alert")

If we exported SDL_GetTicks via emscripten, we could call it in Lisp, like:

(#j:_SDL_GetTicks)

Convenient, right?

Now, let's sort out our tasks again:

Oh, really?

Yes, really.

I exported all the frequently used SDL2 and Cairo functions, then wrapped them with some Lisp code, and then I can call them as if I were on the desktop operating system:

(defun draw-blade (&optional (degree 0))
  (c:save)
  (c:translate 300 170)
  (c:rotate (* degree (/ pi 180)))
  (c:move-to 0 -15)
  (c:line-to 0 -100)
  (c:curve-to 0 -110 100 -65 15 0)
  (c:stroke-preserve)

  (c:set-source-rgba (/ 12 255) (/ 55 255) (/ 132 255) 0.1)
  (c:fill-path)
  (c:restore))

Calm Example Fan

You can try it yourself with this link, the source code is here.

As I clicked on the blue circle in the upper right corner, a gentle breeze carrying the scent of freshly cut grass wafted out of the screen. It was then that I knew it was time to call it a day.

What Now?

With the availability of Lisp in the browser, along with SDL2 and Cairo, the possibilities for interesting projects are endless.

The day-to-day development could happen on the desktop with SBCL + SLIME enabled to get an interactive programming experience. When done, compile everything to WebAssembly + JavaScript, and publish the result online, then you have an application that supports Linux, macOS, Windows, and the web browser!

But, JSCL is far from complete.

In the perfect new world, we should expect:

If you're interested, then go for it!

And we don't need a complete Lisp to have fun, right?

I can't wait to see what you are going to make.

;-)