TRANSMUTRIX

2024 April 03

MoonScript LÖVE Template

If you're interested in using the project template I've made, you can find it here:

>>> Download Project Template <<<

If you want to know about the problems I encountered making this template, or why I made the design choices I did, keep reading.

Introduction

Of all the dynamic languages I've used, I enjoy Lua the most. I also really like MoonScript. I also like LÖVE (Love2D) in principle, but so far I've never shipped any games with it.

In practice, I've written several personal runtimes similar to LÖVE and haven't shipped a game with those either, except for my Sokoban clone (which is old and buggy and needs fixing up).

I want to participate in more game jams this year, and I would like to ship something in LÖVE for one of those, just to say I have. I also would love to see more people using MoonScript!

As things stand, setting up MoonScript can require a level of expertise that many Lua novices, and even intermediates, may not possess, and can be annoying even to veterans.

I wanted to provide a different solution for using MoonScript with LÖVE.

Requirements

I wanted to offer a setup which...

...Requires no Git usage.
...Requires no installations.
...Has no build system or build steps.
...Does not drag in a huge hierarchy of files.
...Works out of the box with no setup whatsoever.
...Fixes line numbers in MoonScript errors, like the moon exe.

Results

I was able to meet all these criteria for development, however: If you want to distribute a finished game as a .love (ZIP) file, you will need to compile your MoonScript code to Lua first, and that means you will need to have MoonScript set up on your machine.

This is because moonloader, MoonScript's special Lua loader for .moon files, isn't part of the path LÖVE has in place for loading files out of a ZIP archive, and the .moon files can't be found.

The Journey to Get Here

In the past, several folks have forked LÖVE to add MoonScript support. As much as I like the idea of this being done officially, it hasn't happened yet, and every such fork I'm aware of is unmaintained. If this were to ever happen, it would solve the module loading foible disclaimed above.

Other than these efforts, I'm aware of Selene, which has similar features to this project template, but provided in a much more "structured" way. Selene has different goals to this template, in short.

I wanted my setup to not require running a bunch of commands, installing submodules, etc. I wanted it to be able to be used on a machine with no system-wide Lua install, without LuaRocks, and without even Git. With that being the case, how annoying was it to get here?

MoonScript Source

First, anyone who knows MoonScript knows you don't use the latest release, which is from 2016. You use the latest code on master.

Dependencies

I didn't want to include a pile of source files from external libraries in the template project, so I used splat.moon (found in the MoonScript source repository) to mush moonscript into a single file. In the end, the the template ships with 3 files in its lib/ directory:

-> errorhandler.lua ---- A custom love.errorhandler implementation.
-> lulpeg.lua ---------- The Lua port of LPeg.
-> moonscript.lua ------ MoonScript, splatted.

LuLPeg

LuLPeg is there to avoid having to distribute LPeg for each platform, and cluttering up the template. In my own projects that embed MoonScript, I've always just built LPeg into my executable, instead.

From what I understand, LuLPeg isn't quite a 1:1 port of LPeg, but is close. So far, I haven't had any issues with MoonScript using it, but I should really run MoonScript's battery of unit tests against it.

Hopefully, if anyone using my template finds a broken MoonScript language feature, they will send me an angry tweet.

Basic Structure

After setting up LuLPeg and moonloader, I could use a main.moon file with LÖVE instead of main.lua, however: This didn't actually work. The engine would boot up and have no errors about not finding a main.lua, but the code in main.moon wouldn't actually run. It seemed to get lost somewhere in the module loader.

So, this template uses a main.lua which loads the game's MoonScript code from a different module. The setup is really simple:

conf.lua:
-- Import LPeg, fallback to LuLPeg.
local success, lpeg = pcall(require, "lpeg")
lpeg = success and lpeg or require"lib.lulpeg":register(not _ENV and _G)

-- Import the MoonScript loader.
require 'lib.moonscript'

-- Config.
function love.conf(t)
  t.version = "11.5"
  t.window.title = "Pickin' Sticks"
end
main.lua:
require 'lib.errorhandler'
require 'game'
game.moon:
love.update = -> print "tick"
love.draw = -> print "draw"

Getting MoonScript to Run on my Machine

In order to create the single-file MoonScript splat, I needed to install MoonScript on the machine I was using for this, an M1 Macbook Air. I naively installed Luarocks with homebrew, and then naively installed MoonScript with Luarocks.

This gave me a Lua 5.4 installation with MoonScript and its dependencies LFS, LPeg, alt_getopt, et al. What's silly about that is that alt_getopt was last updated 7 years ago and is incompatible with Lua 5.4 due to its use of module. Not to worry, it's a simple process to update it yourself! For me, the file lives at:

/opt/homebrew/share/lua/5.4/alt_getopt.lua

You need to comment out or remove this line:

module ("alt_getopt")

And you need to add the module exports at the end of the file, like this:

return { get_opts = get_opts, get_ordered_opts = get_ordered_opts }

After this, I was able to run moonc and moon successfully.

Splatting MoonScript

From the root of the moonscript source repo, I did:

$ bin/splat.moon -l moonscript moonscript > moonscript-splat.lua

This threw an error for me, because splat.moon uses unpack at global scope which has been undefined since Lua 5.2 (unless you built Lua 5.2 with backwards compatibility turned on). So I added this at the top of splat.moon:

unpack = require"table".unpack

After fixing this up, I was able to build my splat file and add it into the project.

Distributing a .love Archive

As mentioned earlier in this post, moonloader is unable to pull files out of a ZIP archive, so packaging our game into a .love file will break it.

Not to worry: when it's time to distribute our game, we can... Go through the rigamarole outlined above to get MoonScript running on our machine, and then we can compile our source code to vanilla Lua by running this command from our project folder:

$ moonc .

Note that this will put the Lua files alongside the MoonScript ones. You may want to have moonc put the compiled code in a different directory for your own convenience. Run moonc -h to see the full list of options.

When distributing your game this way, with precompiled code, you can remove all the files that come in the lib/ directory { errorhandler.lua, lulpeg.lua, moonscript.lua }, as they're only needed for using moonscript at runtime.

Future Live Coding Setup

There is a built in watch mode provided by moonc, but it has some limitations, and you can only benefit from it if you want to precompile your MoonScript code to Lua and forgo conveniently reformatted error messages.

On a previous project of mine using embedded MoonScript with C, I wrote a system for hot reloading MoonScript modules and patching classes live during gameplay.

This feature was great for my iteration time! The code isn't currently available for you to try, but I will dig it up and try to get it published in the coming weeks. :)

Minor Soapboxing

It's fine that MoonScript and its dependencies all work out of the box with older versions of Lua and not the current one, but it's bizarre that Luarocks will cheerfully install them against Lua 5.4 in an obviously broken state.

All of these issues seem straightfoward to fix, so I may try to figure out who to send PRs to to fix them (if there aren't already a bunch of "Hey I fixed the two broken lines of code in this!" PRs that are being ignored already).

The more friction is involved in just getting something to work, the fewer people will try it out. I like MoonScript and I want more people to try it. When the only users of a thing are on Linux and have a specific set of skills and preferences, that's a limiting factor on the community.

These kinds of "some assembly required" hassles are part of why I wanted to make this template project, because it allows anyone to just unzip some files and write MoonScript code, regardless of how familiar they are with the greater Lua ecosystem or specific Lua versions.