The Blog of James Long, a Mozilla webdev

Open-Sourcing My Gambit Scheme iOS Game from 2010

March 08 2014

Back in 2009-2010, I got Gambit Scheme running on iOS and decided to build a game with it. The result was Farmaggedon, a stupid game where you blow up farm animals to avoid being hit by them.

I blogged about my progress working with Scheme on iOS back then and evidently a lot of people were inspired by it. This was the main blog post, in addition to a bunch of videos. Recently another iOS game was featured on Hacker News that was written in Gambit Scheme, and it inspired me to dredge up the source of my game and completely open source it and talk about it.

Background

I used to work with Lang Martin and Ben Weaver at a small webdev shop right out of college. They were a little older than me and far more technically grounded than I was at the time. Occasionally I would hear "lisp" and "scheme" murmured around the office while trying to focus on my C++ game engine side project, and I thought they were just trying to sound cool.

Boy was my mind about to be blown. Eventually we all decided to play around with Scheme and see if we could use it internally. I knew nothing about it, but I tried to keep up with the conversation and more often than not ended up saying foolish things. Tired of feeling out of my depth, I committed to studying Scheme and it still influences me to this day. This is why it's so important to surround yourself with people smarter than you. I got lucky.

Fast-forward a few years later, I was feeling burned out at my job and decided to quit and try freelancing. I set aside the first few months to try and make an iOS game (this was right around the time iOS was exploding). Having fallen in love with Scheme, I endeavoured to make a game with Scheme and prove that it can be practical and performant, as well as making you more productive.

And so I made Farmageddon.

Show Me the Source!

Enough talking, here's the source. You're looking at a completely unfiltered, raw project. Everything I was thinking of is in there somewhere. You're also looking at the messiest project with the worst code, ever.

I was so naïve back then. Set aside a couple months to build a game from scratch, including porting a whole language to a completely new platform? Are you kidding me?

I ported Gambit Scheme to iOS, which basically just means cross-compiling with the right options and writing the necessary FFIs. The actual port wasn't too much work, which was exciting but dangerous because it blinded me to the fact that I would have to build everything myself. Not only was I lacking an OpenGL rendering library, I didn't even have access to the OpenGL API. I had to write an FFI for that. (Actually, I wrote a Scheme program that parsed C++ header files and auto-extracted it.)

Additionally, I created sounds, 3d models, game mechanics, user interfaces, and a basic 3d engine. See all the resources here. I did hire a local designer to make some really cool gritty nuclear farm graphics for the game, but everything else I did myself. Which is why the game is terrible.

Regardless of how badly Farmageddon failed commercially, it was one of the most transformative experiences of my life. I learned tons about project scope, marketing, games, and a lot of other stuff. But even more, I got to experience working in a minimal but powerful language that I could shape to my needs, with a REPL/debugger always there to incrementally play with things.

It wasn't just continuations, green threads, macros, records, and tail-call optimizations that made me a better programmer. It was the idea of incremental development, where you could always redefine a function at run-time to try something new, or inspect and change any data structure. We've come close to that with browser devtools, but the experience still isn't quite what it should be.

So if you haven't aready, you really should learn a Lisp. Personally I like Gambit, but Chicken and Racket are really good too. Clojure is great too, just a different flavor because it's not a minimal Scheme. It doesn't matter. Learn one of them.

Development Videos

These are some videos I made showing off the real-time REPL and debugger. The first two were the most popular.

There are a few other ones as well.

Source Commentary

The code is incredibly messy, but I feel warm and nostalgic looking at it. There are a few interesting things to point out about it.

  1. Most of the Obj-C code is in src/app. The entry point is in main.m which initializes and configures the Gambit virtual machine. EAGLView.mm is where most of the code lies to interact with the iOS UI.

  2. The main entry point for Scheme is in src/init.scm. At that bottom of the file are two FFI functions: c-init and c-render. Those are exposed as init and render at the C level and the Obj-C code calls into them.

  3. All of the FFIs are in src/ffi. I think I wrote most of them by hand, and auto-generated a few of them. What's need about Gambit is that you can embed any kind of C/C++/Obj-C code. For example, here is the FFI for invoking methods in the iOS view for changing the UI. The scheme methods embed Obj-C code straight into them. You can see more of this in the iOS FFI which lets me allocate native iOS data structures. Lastly, you can see my attempts at optimizations by converting Scheme vectors into native C arrays.

  4. The main game loop is in farmageddon.scm. Most of the work is in the various screens, like level.scm which renders and updates the main game.

  5. The main component of the game engine is in src/lib/scene.scm. I used Gambit's native record types and wrote a macro to generate fields that dynamically dispatched of the type for making game entities.

  6. All of my tests were simply top-level Scheme code that I live evaluated when the game was running. No automation for me!

  7. Gambit has a powerul cooperative threading system, and I used it extensively. The game and sound system each had a thread and would send messages to the main thread for changing the game state. Each level had a thread running to fire off events at random intervals, and I could simply call thread-sleep! to wait for a certain period. Note that these aren't real threads, just cooperative so it was all safe.

  8. The remote debugger is in the emacs directory and my Emacs integration was called grime. Since I had a live REPL to my game in Emacs, I even wrote helper functions in Emacs to change game state and bound them to keys so I could quickly invoke them.

There's a lot more in there, and like I said it's very messy. But there's a lot of gems in there too. I hope it continues to inspire others.