Programming A Space Invader In OCaml and OpenGL: Lessons Learned
Tags: ocaml, reason, opengl, tutorial
Published: 2020-02-21 14:17:43

---
title: Programming A Space Invader In OCaml and OpenGL: Lessons Learned
canonical_url: https://marmelab.com/blog/2020/02/21/ocaml-and-opengl-in-practice.html
published: true
description: After a first experience with ReasonML, I wanted to check if its parent language, OCaml, was also that good. It took me an entire game to form a clear opinion.
tags: ocaml,reasonml,opengl,tutorial
---

_Note: This post was originally posted on [marmelab.com](https://marmelab.com/blog/2020/02/21/ocaml-and-opengl-in-practice.html)._

During a [previous hackday](https://marmelab.com/blog/2018/04/09/enjoy-painless-typing-with-reason.html), I had the opportunity to try a fairly recent programming language called [ReasonML](https://reasonml.github.io/). Amazed by the simplicity and power of ReasonML, I promised myself to get closer to its underlying language to give credit where it is due. Do ReasonML qualities come from [OCaml](https://caml.inria.fr/pub/docs/manual-ocaml/index.html)?

As an experiment, I took this opportunity to test **OpenGL** and prove that it is possible to code a humble game in OCaml! Here's what the game looks like at the end, after a few hours of development.

{% youtube 72ULChMvdvo %}

The purpose of this article is to determine the magnitude of the differences between ReasonML and OCaml, and to understand **why ReasonML has become so popular** so quickly, and why OCaml hasn't. The aim is also to determine if it is still useful to use OCaml rather than ReasonML for future projects.

![ReasonML vs OCaml](https://marmelab.com/static/c43161f8409d18f9fc3f8533cfb79bfe/df77d/reason-and-ocaml.webp)

## OCaml, Powerful But Discreet

**OCaml** is the main implementation of [Caml](https://en.wikipedia.org/wiki/Caml) created in 1996 by a group of researcher from the [French Institute for Research in Computer Science and Automation (a.k.a. INRIA)](https://en.wikipedia.org/wiki/French_Institute_for_Research_in_Computer_Science_and_Automation).

Even though it is known for its robustness and its large standard library, its main power lies in its strong [static-typing and type inference](http://www.ffconsultancy.com/ocaml/benefits/type_system.html) system.

Although part of the [ML (Meta-Language)](<https://en.wikipedia.org/wiki/ML_(programming_language)>) family that is purely [functional-oriented](https://en.wikipedia.org/wiki/Functional_programming) by nature, OCaml brings many [object-oriented](https://en.wikipedia.org/wiki/Object-oriented_programming) features that make it special. That's mainly the reason it was named **Objective Caml (aka OCaml)**.

So, it's perfectly possible to work with [Classes, Objects](https://caml.inria.fr/pub/docs/manual-ocaml/objectexamples.html), [Constructors (named "Initializers")](https://caml.inria.fr/pub/docs/manual-ocaml/objectexamples.html#sec30), [Inheritance](https://caml.inria.fr/pub/docs/manual-ocaml/objectexamples.html#sec34) and so on.

## OpenGL, The Swiss Army Knife of Rendering

OpenGL (for **Open Graphics Library**) is a cross-platform and cross-language API that allows to render 2D and 3D vector graphics. It takes care of handling interactions between the source code and the system GPU to benefit from hardware rendering capabilities.

![OpenGL](https://marmelab.com/static/e7c80556d18e5fedfe5ecd60ae1dfefa/df77d/opengl.webp)

OpenGL was developed in 1992 by an old and famous commercial company called [Silicon Graphics](https://en.wikipedia.org/wiki/Silicon_Graphics). Since 2006, the project has been managed under a BSD like license by a non-profit consortium called the [Khronos Group](https://en.wikipedia.org/wiki/Khronos_Group). This consortium is also responsible of the [WebGL](https://en.wikipedia.org/wiki/WebGL) and [Vulkan](<https://en.wikipedia.org/wiki/Vulkan_(API)>) standards that are very popular today.

Vulkan is usually refered to as the _OpenGL next_ API for graphics rendering.

## Practical Case: A Space Invader in OCaml

In this part, I'll give you the most objective feedback about my development experience using OCaml and OpenGL on a concrete project: the [Space Invader](https://en.wikipedia.org/wiki/Space_Invaders) game.

![Space Invader](https://marmelab.com/static/83b16ea042f6743940724c1dc4f30d86/df77d/invader.webp)

Also, I'll explain the intellectual journey that led me to design the project foundation. Then, I'll describe the different stages of project building.

### How Does it Work? What Do We Need?

As for most programming languages, we need to setup a _compiler_ which is capable of transforming our OCaml source code to a suite of machine instructions.

According to the official documentation, the OCaml core distribution provides two compilers, `ocamlc` and `ocamlopt`.

-   `ocamlc` is the base standard bytecode compiler for OCaml. It produces bytecode files (usualy `*.cmi` for interfaces and `*.cmo` files for implementations) that can be run using `ocamlrun`, the OCaml bytecode runner.
-   `ocamlopt` is the native code compiler for OCaml. As for `ocamlc`, it can take `*.ml` (and `*.mli`) files as argument and generates an executable (targeting specific architectures according to the provided parameters).

Of course, I do not pretend to give you here the [full functionalities](https://v1.realworldocaml.org/v1/en/html/the-compiler-backend-byte-code-and-native-code.html) of these tools, which are in fact much more complicated to understand.

I can already imagine your faces after reading these few lines... but don't go away, I assure you that the rest will be much more exciting!

### Project Setup

In practice, built-in compilers are almost never directly used on real world projects because they require a lot of work to setup and maintain. It is common to use higher level tools that also facilitate the import of dependencies (including external modules). Among these tools, we can mention [Dune](https://dune.readthedocs.io/en/stable/index.html), [Oasis](https://ocaml.org/learn/tutorials/setting_up_with_oasis.html) and [OCamlBuild](https://ocaml.org/learn/tutorials/ocamlbuild/).

![OPAM](https://marmelab.com/static/b3c6fead92e75bb105550b825939352f/df77d/opam.webp)

In my case, I choose a more undisclosed tool ([obuild](https://github.com/ocaml-obuild/obuild)) because of its simplicity and its brief documentation. So, the first step is to install the [OCaml Package Manager (aka opam)](https://opam.ocaml.org/) in order to install the `obuild` builder.

As for every new projet, my approach consists in setting up the whole tech stack in [Docker](https://www.docker.com/) so as to avoid overloading my workstation, and to allow other people to participate easily. Therefore, I started looking for a Docker Image that includes `opam` on [docker-hub](https://hub.docker.com/).

Thanksfully, the [ocaml/opam2](https://hub.docker.com/r/ocaml/opam2) docker image exists, here is my `Dockerfile` configuration.

```dockerfile
FROM ocaml/opam2:debian-10-ocaml-4.08

RUN sudo mkdir /app
WORKDIR /app
ADD . /app/

RUN sudo apt-get update

RUN opam install obuild
```

Since I need to work with OpenGL to draw the game, and I won't develop an OpenGL library from scratch, I'll install [lablgl](http://wwwfun.kurims.kyoto-u.ac.jp/soft/olabl/lablgl.html) at the same time by adding this line at the end of the `Dockerfile`:

```dockerfile
RUN opam depext lablgl
```

`lablgl` is one of the most advanced OpenGL interfaces for OCaml. Despite its lack of documentation, it remains the most talked about library after several minutes spent on search engines...

Lablgl is based on [Glut (The OpenGL Utility Toolkit)](https://www.opengl.org/resources/libraries/glut/). Glut takes care of all the system specific mechanisms to **create windows**, **initializing OpenGL** and **capture input events**.

Now I have all the ingredients, I need to build the application. I create my entry point file in `src/main.ml`.

```bash
.
├── Dockerfile
├── ocaml-invader.obuild
└── src
    └── main.ml
```

Then, I create the obuild config file `ocaml-invader.obuild`.

```yaml
name: ocaml-invader
version: 0.0.1
description: Simple OCaml Invader Game
licence: MIT
authors: Julien Demangeon <julien@marmelab.com>
obuild-ver: 1
homepage: http://github.com/marmelab/ocaml-invader

executable ocaml-invader
    main-is: main.ml
    src-dir: src
    buildDepends: lablgl, lablgl.glut
```

And a `Makefile` to facilitate development.

```makefile
BIN = docker run -i -t --rm \
	-v "${PWD}:/app" \
	ocamlinvader

install: ## Install dependencies
	docker build --tag=ocamlinvader .

build: ## Build project
	$(BIN) bash -c "obuild configure && obuild build"

clean: ## Clean project
	$(BIN) bash -c "obuild clean"

start: ## Start project
	$(MAKE) build
	./ocaml-invader
	$(MAKE) clean
```

OK, everything is set, I can now start developing the game!

### Game Architecture

As with most video games, the whole system is based on a **game state**, some **state mutations** (through **system inputs**), and a **rendering engine / loop**.

![Game Archicture](https://marmelab.com/static/6c8a4ee61598cfda819321406aec0ff6/df77d/game-architecture.webp)

Fortunately, I won't have to manage all these components by myself. Indeed, `labgl` (through its `Glut` module) offers most of these features out of the box:

-   The windowing system through `Glut.createWindow`
-   The rendering loop through `Glut.mainLoop`, `Glut.displayFunc` and `Glut.idleFunc`
-   Input management through `Glut.keyboardFunc` and `Glut.specialFunc`
-   The timers / tickers through `Glut.timerFunc`

Therefore, I only have to care about:

-   The rendering of the game in OpenGL (through the `Glmat` and `Gldraw` modules)
-   Changing the game state from the input actions

### Main Program

Now that the basis of the architecture is established, let's get down to business by expanding the main function in `main.ml`:

```ocaml
(* main.ml *)

(* Declare rendering function, buffering mode, and create window *)
let initDisplay ~w ~h ~title =
  Glut.initDisplayMode ~double_buffer:true ~depth:true ~alpha:true ();
  Glut.initWindowSize ~w ~h;
  Glut.createWindow ~title;
  Glut.idleFunc ~cb:(Some Glut.postRedisplay)

(* Initialize OpenGL rendering options *)
let initView ~w ~h =
  GlDraw.viewport ~x:0 ~y:0 ~w ~h;
  GlMat.mode `projection;
  GlMat.load_identity ();
  GluMat.ortho2d ~x:(0.0, float_of_int(w)) ~y:(0.0, float_of_int(h));
  GlMat.mode `modelview

(* Create some tickers that send actions to the Game Controller *)
(* The GameController uses these actions to mutate the game state each N ms *)
let initTickers ~game =
  Glut.timerFunc ~ms:1000 ~cb:(invaderTicker game) ~value:0;
  Glut.timerFunc ~ms:10 ~cb:(bulletTicker game) ~value:0;
  Glut.timerFunc ~ms:10 ~cb:(collisionCheckTicker game) ~value:0

(* Map keyboard inputs to game actions *)
(* The GameController uses these actions to mutate the game state *)
let initInputs ~game =
  Glut.keyboardFunc ~cb:(gameController game specialKeyToAction);
  Glut.specialFunc ~cb:(gameController game commonKeyToAction)

(* Initialize each part of the game engine *)
(* Then, it returns a callable mainLoop *)
let initEngine ~game ~w ~h =
  initDisplay ~w ~h ~title: "OCaml Invader";
  initView ~w ~h;
  initTickers ~game;
  initInputs ~game;
  Glut.displayFunc (fun () -> Game.render !game);
  Glut.mainLoop

(* This is the first function executed by OCaml *)
(* We init the game object reference and pass it to the engine *)
let () =
  ignore @@ Glut.init Sys.argv;
  let game = ref (Game.init()) in
  let run = initEngine ~game ~w:450 ~h:600 in
    run()
```

### Zoom In The Game Controller

**Mutations on the game state are managed by the game controller**. The game controller is a simple function that associates an action (with pattern matching on it) to a mutation on the game state (the whole game being declared mutable).

```ocaml
(* game.ml *)

type direction =
    | Left
    | Right

type action =
    | Move of direction
    | Shoot
    | AdvanceInvaders
    | InvaderShoot
    | AdvanceBullets
    | CheckCollisions

type game = {
    mutable over: bool;
    mutable score: int;
    mutable lifes: int;
    mutable invaders: Invader.invader list;
    mutable invadersDirection: direction;
    mutable spaceship: Spaceship.spaceship;
    mutable bullets: Bullet.bullet list;
}

let controller game = function
    | Move(direction) ->
        let op = match direction with
        | Left -> (-.)
        | Right -> (+.) in
        let coord = op game.spaceship.x 10. in
        game.spaceship.x <- min (max coord 10.) 440.;
        game
    | Shoot ->
        (* ... mutate game ... *)
        game
    | InvaderShoot ->
		(* ... mutate game ... *)
        game
    | CheckCollisions ->
		(* ... mutate game ... *)
        game
    | AdvanceInvaders ->
		(* ... mutate game ... *)
        game
    | AdvanceBullets ->
        (* ... mutate game ... *)
        game
    | _ -> game
```

The link between the game controller and actions (tickers and keyboard inputs) is declared in the `gameController` function of the `Main` module.

```ocaml
(* main.ml *)

let gameController game keyToAction = fun ~key ~x ~y ->
  match (keyToAction ~key ~x ~y) with
    | Some(action) -> game := Game.controller !game action
    | None -> ()
```

### Rendering Logic

The entire rendering logic for the game is located in the `Game` module of the `game.ml` file. This logic is composed of a switch between two screens, a game screen, and an end screen depending on whether the game is finished or not.

```ocaml
(* game.ml *)

let renderHome game =
    GlClear.clear [ `color ];
    let endText = match game.score with
        | 600 -> Printf.sprintf "YOU WIN"
        | _ -> Printf.sprintf "GAME OVER" in
    Utils.drawString ~font:Glut.BITMAP_TIMES_ROMAN_24 150. 290.  endText;
    Glut.swapBuffers ()

(* Each module (Score, Life, Invader, Bullet, ...) has its own render *)
(* So we clear buffer, draw the game and swap buffers to render it to the screen *)
let renderGame game =
    GlClear.clear [ `color ];
    Score.render game.score;
    Life.render game.lifes;
    List.iter Invader.render game.invaders;
    List.iter Bullet.render game.bullets;
    Spaceship.render game.spaceship;
    Glut.swapBuffers ()

let render game =
    match game.over with
        | false -> renderGame game
        | true -> renderHome game
```

Each component of the game (Invader, Spaceship, ...) has its own module and its own `render` method, which makes it possible to separate the responsibilities in the code. For example, here are some parts of the `Invader` rendering.

```ocaml
(* invader.ml *)

type race =
    | Octopus
    | ShapeShifting
    | Crab

type invader = {
  mutable x: float;
  mutable y: float;
  race: race;
}

let renderOctopus () =
    GlDraw.color(1., 1., 1.);
    GlDraw.begins `quads;
    List.iter GlDraw.vertex2 [-10., -10.; -10., 10.; 10., 10.; 10., -10.];
    GlDraw.ends ()

let renderCrab () =
    GlDraw.color(1., 0., 1.);
    GlDraw.begins `quads;
    List.iter GlDraw.vertex2 [-10., -10.; -10., 10.; 10., 10.; 10., -10.];
    GlDraw.ends ()

let renderShapeShifting () =
    GlDraw.color(1., 0., 0.);
    GlDraw.begins `quads;
    List.iter GlDraw.vertex2 [-10., -10.; -10., 10.; 10., 10.; 10., -10.];
    GlDraw.ends ()

let render invader =
    GlMat.load_identity ();
    GlMat.translate3(invader.x, invader.y, 0.0);
    match invader.race with
        | Octopus -> renderOctopus()
        | ShapeShifting -> renderShapeShifting()
        | Crab -> renderCrab()
```

### OpenGL In Practice

To summarize quickly, **OpenGL is a large state machine** on which it is possible to send instructions. Once all rendering instructions have been sent to it, it is possible to trigger the on-screen display using Buffer flush.

For each rendering, it may be necessary to:

-   Modify state variables (anti-aliasing enabled, ...)
-   Define the textures (aka `Materials`) to be used for the display
-   Send geometrical instructions (aka `Vertex`) to the Buffer
-   Trigger the Buffer flush

In the example above, for each invader I:

-   Reset the rendering matrix state with `GlMat.load_identity()`
-   Place cursor at the invader position with `GlMat.translate3(inv.x, inv.y, 0.0);`
-   Define a color to be used with `GlDraw.color`
-   Declare that I'll draw a quadrilateral with `` GlDraw.begins `quads ``
-   Send 4 vertices with `GlDraw.vertex2`
-   Close the Invader figure (a square in my case) with `GlDraw.ends`

In the case of the invaders, I used simple squares for lack of time. But I know that **it is possible to make much more complex shapes**, as in the Spaceship below:

```ocaml
type spaceship = {
    mutable x: float;
    mutable y: float;
}

let renderAt ~x ~y =
    GlMat.load_identity ();
    GlMat.translate3(x, y, 0.);
    GlDraw.color(0.51, 1., 0.);
    GlDraw.begins `quads;
    List.iter GlDraw.vertex2 [-20., -5.; -20., 5.; 20., 5.; 20., -5.];
    List.iter GlDraw.vertex2 [-2., 5.; -2., 14.; 2., 14.; 2., 5.];
    GlDraw.ends ()

let render spaceship =
    renderAt ~x:spaceship.x ~y:spaceship.y
```

### The Result

As a result, the game is **very fast**, **no lag** and the **compilation time is almost instantaneous**, what more could you ask for? If you are interested in the code (or want to play), the game is available in open-source on [Github](https://github.com/marmelab/ocaml-invader).

## Conclusion

Although confused at first, I am very proud to have achieved this project. This was an opportunity for me to measure the particularities that makes OCaml so intimidating for most developers.

Indeed, **it is very complicated to install the base stack to develop in OCaml**. And although some tools exist to make our life easier, they are **poorly documented**. This lack of documentation also applies to the language itself. The documentation is scattered among many academic and research-oriented sites, making it **very intimidating for a beginner**.

Clearly, **it is in OCaml's weaknesses that we find the strength of ReasonML**. The languages are not so different, but their communities and ways of working are clearly in contrast.

In my opinion, I would say that **OCaml suffers from its original academic use**, and that it deserves a little more love from mainstream developers. **It is incredibly fast to compile and run**, and it competes well with popular languages.

To conclude, **I would still advise to prefer ReasonML over OCaml** (as its community keeps growing). This way, you will adopt a functional way of coding that can be used in other derived languages such as [F#](<https://en.wikipedia.org/wiki/F_Sharp_(programming_language)>) or [Scala](<https://en.wikipedia.org/wiki/Scala_(programming_language)>).