I wanna be the very bestDecember 21, 2020
What I learned from (re)writing a Pokémon-themed memory game
My favorite course in school was Java development. It opened a whole new world for me.
I have learned other programming languages before, but Java was the first one I actually
mastered tried to master.
It solidifed my programming skills, and more importantly, it enabled me to build applications for virtually any platform.
Yes! I'm talking about video games, for desktop. Go figure!
Luckily for me, the curriculum included GUI programming. They taught us Java Swing, an old graphics library from the 90s. I particularly enjoyed one of the assignments: we had to build a memory game (also called "concentration" - as I've learned lately).
Apart from a few loose requirements, we were given the freedom to implement the game in whatever way we saw fit. As a recovering Pokémon-addict (grew up watching the series), there was no question about what theme I will choose.
I built the game as well as I could. I even photoshopped artwork (that I got from the internet) to make it as perfect as possible. I received the best possible grade and was so proud I eventually also uploaded it to GitHub - so the whole world can see my fine work.
This was in 2016...
In 2019 I decided to revisit my old GitHub repositories. Why? Because for software developers, GitHub is like a portfolio. And you don't want shitty projects on your portfolio.
What I found there was staggering: past me had no idea how to develop good software. Despite all the love I put into this game, it was a dumpster fire.
I ended up making most of my old repositories private, except this one. I decided to save it.
The bane of GUI programming
The codebase was a textbook example of bad practices.
It was full of duplications, everything was hardcoded, and for some reason I thought committing executable files to Git was a good idea. Fortunately, I managed to fix the obvious problems quite quickly.
The the challenging part was not replacing tabs with spaces or renaming variables. It was the architecture.
Java Swing is a GUI library with basic GUI components like buttons, labels, panels, etc.
There aren't many restrictions on how to organize code. The library doesn't care how you wire the components together. It is the programmer's job to come up with a sufficiently decoupled and modularized architecture.
In my opinion, anyone building an app with this toolkit will face the following two problems:
- How do I make components talk to each other?
- How do I avoid tight coupling?
These are not easy problems. No wonder I couldn't tackle them when I was a beginner. After doing some research I decided on the following solutions...
Inversion of Control
Let's say, the user restarts the game by clicking the restart button.
We expect the following things to happen:
- The board resets the memory cards.
- The timer resets to zero.
- The move counter resets to zero.
That's a lot of responsibilities for a single button!
A naive solution would look something like this:
The problem with this approach is, the restart button should not communicate with other components directly.
It should not keep track of components handling game restarts, and it should not know how to call them. That's not the button's responsibility.
In other words, we should be able to add new objects handling game restarts without modifying the restart button itself.
To achieve this, we need to introduce a new layer of abstraction. We need inversion of control.
An event bus is a design pattern for building event-driven systems. It's pretty simple.
An event bus is a central object where you register all your event handlers (e.g. the timer component). Then, when an event is created (e.g. the restart button is clicked), it is broadcasted to all the registered handlers.
Much better! The components do not depend on each other anymore. They communicate via the event bus.
How can the event bus figure out where to send the events? I'll spare you the details, but the answer is: reflection - if you're interested in the inner workings of the event system (I'm simplifying things a bit here), feel free to check out the source code.
Decoupling with Dependency Injection
In Java Swing, GUI components are defined in Java code, forming a tree-like structure.
The problem is, what if we have to change something at the bottom of the tree? Let's say, add the event bus to the restart button?
Well... we would have to pass the event bus to every component in the hierarchy. Even where it's not needed.
It's a bit inconvenient. Also, it'll be hard to test (we want to write tests... right?).
In this case, the panel container doesn't need to know how to instantiate its child objects. Let's make our lives a little easier and use dependency injection.
Great! We have pushed the responsibility of instantiating the child components one layer up in the hierarchy.
Like squeezing a toothpaste. We are making files slimmer by moving the complexity somewhere else.
That sounds fantastic, but... when does it ever end? Will we have to instantiate everything at startup time? Yes. Kind of.
Enter dependency injection frameworks!
To make the project as over-engineered as possible (gotta show off those skills!), I decided to use a framework that automatically instantiates and injects objects on demand.
I picked Guice.
Marvelous! Guice can handle the creation and wiring of most GUI components out of the box. All you have to do is define some annotations. Naturally, for dynamically generated components (e.g. the memory cards), we'll have to keep using "new".
It's also possible to go a bit further with reflection, and auto-discover/auto-register event handlers on startup. In hindsight, I'm not sure it was my brightest idea - but it's definitely possible! The sky is the limit.
We have covered a lot of ground. There's certainly a difference between understanding event-driven systems and dependency injection and making them work. But, when it comes to these kind of apps, I believe it's the way to go.
I implemented the game in 1-2 weeks. Then I spent 1-2 months refactoring and polishing it.
I'm not complaining, it was a fun intellectual exercise and learning experience.
The game can be downloaded from the releases page (requires Java 8).
Thanks for reading!