Lobster is a game programming language. Unlike other game making systems that focus on an engine/editor that happens to be able to call out to a scripting language, Lobster is a general purpose stand-alone programming language that comes with a built-in library suitable for making games and other graphical things. It is therefore not very suitable for non-programmers.

It’s also meant to be portable (mostly courtesy of OpenGL/SDL/Freetype), allowing your games to be run on Windows, Mac OS X, iOS, Linux, Android and WebAssembly (in that order of maturity, currently).

Lobster is Open Source (ZLIB license) and can be found on github. Online copy of the full documentation. Discuss things in the google+ community or like the Facebook page.

Features

Features have been picked for their suitability in a game programming language, and in particular to make code terse, quick to write and refactor. It is meant to not hold you back to get an idea going quickly. It is quite the opposite of a robust enterprise language.

  • Language
    • Static Typing that still feels like a Dynamically Typed Language thanks to Flow-Sensitive Type-Inference and Specialization.
    • Python-style indentation based syntax with C-style flavoring
    • Lightweight Blocks / Anonymous Functions that make any function using them look identical to built-in control structures
    • Vector operations (for math and many other builtins)
    • Multimethods (dynamic dispatch on multiple arguments at once)
    • First Class Stackful Asymmetric Coroutines.
    • Compile time reference counting / borrow checker.
    • Immutable “inline” structs (zero overhead).
    • GIL-less, race-less distributed memory model multi-threading.
  • Implementation
    • Choose between using the convenient bytecode VM, or compilation to C++ for extra speed.
    • Reference Counting with cycle detection at exit, 95% of reference count ops removed at
      compile time.
    • Debugging functionality (stack traces with full variable output).
    • Dynamic code loading.
    • Relatively fast (several times faster than Python/Ruby, about as fast as non-JIT Lua) and economical (low overhead memory allocation)
    • Easy to deploy (engine/interpreter exe + compressed bytecode file)
    • Modularly extendable with your own library of C++ functions
  • Engine
    • High level interface to OpenGL functionality, very quick to get going with simple 2D geometric primitives
    • 3D primitive construction either directly from triangles, or using high level primitives made into meshes through marching cubes
    • GLSL shaders (usable accross OpenGL & OpenGL ES 2 without changes)
    • Accurate text rendering through FreeType
    • Uniform input system for mouse & touch
    • Simple sound system supporting .wav and .sfxr synth files.
    • ImGui support.
    • Comes with useful libraries written in Lobster for things like A* path finding and game GUIs

Examples

let’s start with syntax and blocks:

def find(xs, fun):
    for(xs) x, i:
        if fun(x):
            return i
    return -1

var r = 2
var i = find [ 1, 2, 3 ]: _ > r

We can learn a lot about the language from this tiny example:

  • find is a function that takes a vector and a function as argument, and returns the index of the first element for which that function returns true, or -1 if none.
  • It uses an indentation based syntax, though in this example the for-if-return could also have been written on a single line.
  • Blocks / anonymous function arguments are always written directly after the call they are part of, and generally have the syntax of a (possibly empty) list of arguments (separated by commas), separated from the body by a :. The body may either follow directly, or start a new indentation block on the next line. Additionally, if you don’t feel like declaring arguments, you may use variable names starting with an _ inside the body that are automatically declared.
  • for and if look like language features, but they have no special syntactical status compared to find. Any such functions you add will work with the same syntax.
  • Notice the complete lack of type declarations. The code is fully statically typed, however, type inference is smart enough to assign types to everything, and functions like find get specialized to work on whatever arguments they are called with, in this case a list of ints, and a specific lambda. Specialization not only increases the range of code type inference can handle, it allows the compiler to optimize this particular case as if you had hard-coded the loop (much like C++ templates).
  • blocks/functions may refer to “free variables”, i.e. variables declared outside of their own scope, like r. This is essential to utilize the full potential of blocks.
  • i will contain 2 at the end of this (the index of element 3). It does not clash with the other i because of lexical scoping. Here = means assignment, and var defines a new variable (you can also use : as a shorthand for var and = together).
  • return returns from find, not from the enclosing function (which would be the block passed to if). In fact, it can be made to return from any named function, which makes it powerful enough to implement exception handling in Lobster code, instead of part of the language.
  • Not only are higher order functions like find easy to write and use, you can convert any such functions into coroutines trivially, e.g. coroutine for(10) creates a coroutine object that yields values 0..9 on demand. Because in lobster coroutines and higher order functions are written in the same way (there is no yield keyword), they are more composable and interchangable.

Types, multimethods, immutables and vector ops:

value point { x, y }
value circle : point { radius }
value ray : point { dir }

def intersect(p:point, c:circle): magnitude(p - c) < c.radius
def intersect(r:ray, c:circle): ...
def intersect(c1:circle, c2:circle): ...
...

assert intersect(point { 1, 1 }, circle { 2, 2, 2 })

What we learn here:

  • we can declare custom datatypes, that can optionally inherit from existing datatypes
  • we can declare them with either value or struct, where the former means the object is immutable: its fields may not be assigned to after construction. This enforces more functional style programming for objects which can be seen as unit things (like points and vectors).
  • objects are very much like the typed version of the generic vectors we saw earlier, and are treated similarly by the language in many ways (e.g. vector operations also work on them)
  • We can declare multiple version of a function, and the language will pick dynamically which one to run, based on all arguments (most OO languages only use the first argument for this, thus writing a function like intersect requires double dispatch, i.e. 2 levels of methods). If two functions apply to a certain set of arguments, the most specific one (starting from the first argument) is picked. If such an ordering can’t be determined at compile time, that is a compile time error. If no functions apply at runtime, that’s a runtime error (which you can avoid with a catch-all default function version with no types).
  • we can specify types for arguments with :, also for single functions. Besides their use in multimethods, they can be used in regular functions to make compile time type errors simpler. You can even specify the type with ::, which allows you direct access to all members of the type, so you can write x instead of p.x etc.

Enough of dry programming language stuff, how do we draw?

include "vec.lobster"
include "color.lobster"

const directions = [ xy_0, xy_x, xy_y ]

def sierpinski(depth):
    if depth:
        gl_scale 0.5:
            for(directions) d:
                gl_translate d:
                    sierpinski(depth - 1)
    else:
        gl_polygon(directions)

fatal(gl_window("sierpinski", 512, 512))

while gl_frame():
    if gl_wentdown("escape"): return
    gl_clear(color_black)
    gl_scale(float(gl_windowsize()))
    sierpinski(7)

which produces:

What do we see:

  • if we skip to gl_window, this creates the window and sets up OpenGL basics. This can theoretically fail which will return us an error string, but here for the example we’re lazy.
  • rendering in Lobster centers around frames like in most game engines, so we redraw everything every time (this example has no animation or interaction, so that looks a bit silly). gl_frame takes care not only of frame swapping, but updating input etc as well
  • gl_scalevec allows us to scale all rendering by specifying the unit size (compared to the previous scale, which by default is pixel size). Using the current window size thus gets us a canvas with a resolution of 1.0 x 1.0 which is convenient for the algorithm we’re about to run
  • The include pulls in definitions for 2d/3d/4d vectors and some useful constants (e.g. vec_0 is a vector of all zeroes).
  • The recursive function then keeps subdividing and scaling in 3 directions until it gets to the bottom of the recursion where it draws the triangles

To see more about the builtin functionality of Lobster (graphics or otherwise), check out the builtin functions reference
(this particular file may be out of date, it can be regenerated by the running lobster -r). You can also check out draft version of the full Lobster documentation,
in particular the
Language Reference.

Most recent version of everything is on GitHub.

Feel like discussing Lobster? There’s a Google+ community for it, and Facebook page.