
Game development
A story about childhood dreams, a love for software development and some funky mathematics. For the people who read my other posts on modal type theory and quantum logic, this post will also contain type theory. 😁
In my fifth year of high school, I had to choose between taking two hours of applied sciences or two hours of mathematics and programming on top of the regular mathematics and science curriculum. The programming part was about the basics of Java (through BlueJ1). At that time, my father had already taught me some things about programming and had shown me some of the projects he had made in Delphi (a Pascal derivative). However, this was the real deal. It took a bit before I started enjoying it, but once I was able to play with it myself, I quickly learned how amazing programming was.
My first project was developping a game. In the fifth year of primary school, during the last week of school, our teacher introduced us to the wonderful world of Age of Empires (AoE I to be specific). No wonder my first own game had to be something like Age of Empires: a real time strategy game. I started implementing unit and building classes, resource systems, graphics and an AI (clearly at that time this did not consist of much, but it worked!) I quickly got the support of two class mates and we worked on this project together for a while (adding multiplayer amongst other things). However, due to holiday breaks and, more importantly, at the end of the last year of high school, the change to university, this project fell apart.
However, over the years, I often returned to this project. At some point, I tried to salvage the code I could recover from my high school days, but, sadly, this was to no avail. I had to (re)start from scratch. This blog post will cover my latest iteration, which covers a few years (be it with lengthy breaks). When I started learning Java, version (1.)7 was in use. At the time of writing, version (1.)24 was the latest one. Many things have changed and many improvements have been added to the language. This means that, after obtaining a PhD in statistics, which implies working with Python for 4 years (and more), I had to relearn a lot and also learned new things along the way. I selected a handful of topics that I want to cover in more detail, give my opinion about their use, and explain some relations to formal mathematics.
The following practice is common in Java code:
public void someMethod(ClassA obj) {
if(obj != null)
obj.doSomething();
}
Although this is very clear from the perspective of readability, it is slightly verbose, increase the amount of brackets that need to be inserted (for longer code blocks), etc. For example, assume that we want to perform a chain of operations. If we want to foolproof the following method call obj.getSomeAttribute().doSomething()
,
we would have to write
public void someMethod(ClassA obj) {
if(obj != null && obj.getSomeAttribute() != null)
obj.getSomeAttribute().doSomething();
}
which requires calling getSomeAttribute()
twice, an approach that ought be avoided in general (especially for costly methods), or
public void someMethod(SomeClass obj) {
if(obj != null) {
ClassB attr = obj.getSomeAttribute();
if(attr != null)
attr.doSomething();
}
}
The situation gets even worse if null
values need to be handled. This introduces even more braces and control statements. Moreover, without inspection of a method’s implementation, it is not clear a priori whether it can return null
or not (except when its return type is void
). Accordingly, in general, we would need to check for null every time we use a method that is not void
or returns a primitive type.
To this end, Java 8 introduced a new (generic) class
: Optional<T>
. This new class
solves both issues. When using a method whose return type is of the form, it is immediately clear that the value could correspond to null
(of course, it should not exactly be null as this would defeat the whole purpose). The way Optional<T>
is implemented also allows for easier program flow. The situation considered above will now look as follows:
public void someMethod(Optional<SomeClass> obj) {
obj.ifPresent(ClassA::getSomeAttribute).ifPresent(ClassB::doSomething);
}
As in the Stream
API, methods can easily be chained. (We will come back to this in a section further below.) Now, given an instance Optional<T> opt
, how do we handle null
values? This is covered by the forElse()
construction:2 The line T result = opt.orElse(alternative);
will
- return the instance of
class T
contained byopt
if it is notnull
, or - return
alternative
otherwise.
In the preceeding section, we saw how we can handle null
values in a safe and controlled manner. The only piece that remains is how to instantiate Java’s Optional<T>
’s. The most general method to obtain an Optional<T>
reads as Optional<T> ofNullable(T obj)
. If obj == null
, it will represent an “empty” Optional<T>
. Let us introduce the notations “Nothing” and “Just obj”, depending on whether obj == null
or not.
Combined with the functionality presented in the preceeding section, it follows that Optional
API gives a common example of a monad (see my previous post on diagrammatic calculus for more information). In particular, it constitutes the Maybe monad. Formally, it is characterized by the following natural deduction rules:
- Formation: $A:\mathrm{Type}\vdash\mathrm{Maybe}\,A:\mathrm{Type}$,
- Introduction: $a:A\vdash\mathrm{Just}\,a:\mathrm{Maybe}\,A \mid \mathrm{Nothing}:\mathrm{Maybe}\,A$,
- Elimination: $o:\mathrm{Maybe}\,A,f:A\rightarrow B\vdash f(o):\mathrm{Maybe}\,B$,
- Computation:
- $f(\mathrm{Nothing}) \equiv \mathrm{Nothing}$, and
- $f(\mathrm{Just}\,a) \equiv \mathrm{Just}\,f(a)$.
Let us unpack these rules (in Java):
- The formation rule says that for every
class A
, there exists aclass Optional<A>
. - The introduction rule says that there are two ways to obtain an instance of
Optional<A>
. Either by wrapping an instance ofA
or the emptyOptional<A>
. - The elimination rule says that, if we have a method
public B f(A obj)
, we can map anyOptional<A>
to anOptional<B>
, i.e. we obtain a methodpublic Optional<B> f(Optional<A> opt)
.3 - The implementation of this elimination rule goes as follows:
- If
obj == null
, we are working with $\mathrm{Nothing}$, and $\mathrm{Nothing}$ is always mapped to $\mathrm{Nothing}$. Once we start withnull
, it remainsnull
until the end of time. - For nonnull objects, we are working with an instance of the form $\mathrm{Just}\,a$ and applying $f$ is straightforward. $\mathrm{Just}\,a$ is simply a wrapper of $a$, so we can unwrap it, apply $f$ and then wrap it again.
- If
-
What a nightmare. ↩
-
ifPresent(Consumer<? super T> consumer)
also admits an alternativeifPresentOrElse(Consumer<? super T> consumer, Runnable alternative)
since Java 9. ↩ -
Here, and in the preceeding section, I have given into some abuse of notation. Since the function induced by $f$ is not exactly equal to $f$, it should have been denoted by something like $\mathrm{Maybe}\,f$. ↩