This post, on the other hand, is about more than just type safety. It also has a lot to do with a childhood game that helped turn me into a strong typist.
Do You Even Type, Bro?
For most of my life I've been able to type fast, thanks in no small part to an old MS-DOS program called Typing Tutor. This had a really fun game that worked well to motivate users to type faster. Here's a screenshot from the game:
Here we have words and characters falling from the top of the screen. The player has to type them in to shoot them down with a missile, represented by the red arrow in the picture. If a word makes it to the bottom, it damages the base, and your game will be over after a certain amount of damage.
For a long time now I've had the personal goal of creating a fun web game using ReasonML, because I like the language and I wanted to build something fun with it. I’ve gotten some things started in the past and eventually abandoned them all, but as a result I've learned some valuable ideas to prevent another lost project. This story is about putting those ideas to the test as I worked to build a version of this typing game for the web in a strongly typed language. This is a story of success.
Planning For Success
How can we say we’ve succeeded if we don’t know what we want the result to be? Defining what success looks like for you is essential to completing a project, because we can always come up with more things to do that add scope to the project and prevent us from shipping anything. This is even more important when it comes to personal projects, where we are often our only resource and have so much else of importance going on in our lives. Here are some goals that I used while building my game. Following these was key to my success.
I can’t stress this enough: Your personal project should make you happy! Don’t do it to impress people or advance your career. Chances are, your project won’t make you rich and famous. If it does, kudos! But if those are your motivations, you’ll wind up dissatisfied with the outcome in most cases. Instead you should focus on building something you like, using the tech that interests you.
I’ve had lots of side projects in the past. These include web apps I didn’t finish, open source libraries I no longer maintain, and screencast videos that virtually no-one watches. Many of these projects made me genuinely unhappy for a variety of reasons! In some cases, I just didn’t care about the subject, so working on it made me feel bored or worse. In other cases, I stretched myself too thin hoping for a payout that I never got. This all led to a period of serious burnout, which tends to be the result of feeling like your work has been for nothing. It also led to me being very tired and unhealthy.
Picking a project you like does not eliminate all of these problems. Feeling overworked, unhealthy, and burned out is a risk for side projects, too, so it’s important to stay balanced. We only get so much time outside of our work to care for ourselves, our families, and other responsibilities that we have. We need to get adequate sleep, exercise, and nutrition to be healthy. Many of us are lucky to have people and pets in our lives, and they need our attention! Project time shouldn’t come at a cost to these things. In practice, achieving a balance probably means allowing yourself to be happy with small, frequent wins. Learn to be satisfied with making the tiniest improvement, or making the smallest thing work. Write a single function, or a single test. But make it count. Think about the one line of code that will get you closest to achieving your goals. Then when you find time in your day, write it and congratulate yourself. You just made progress!
Know What You're Building
One of the traps I’d fallen into in the past was a failure to clearly define how my games actually worked. Establishing constraints creates fewer choices and a clearer path forward. When I had the idea to recreate the game from Typing Tutor, I realized that copying something you already know is a great way around this problem! You have a set of predefined constraints including game mechanics and feature set. No more time wasted dithering on those decisions! Below is what I decided to build.
It's got the essential elements:
- Words falling from sky: ✔
- User input highlighted: ✔
- A base that shows where words have crashed: ✔
The base is just a straight line, a decision I made in the interest of simplicity. Remember that you can always cut scope if a feature is not essential to making your game playable. With a flat line, I thought I could finish a first version quickly and then improve on it later if needed. Being willing to start simple and iterate is key to success here!
Put Your Favorite Tech to the Test
Your decision depends on your goals. If your first priority is to ship quickly, you might decide to go with a programming language that you're already strong with and avoid any learning curves. On the other hand, if you have a goal of getting your hands dirty with something less familiar, your side project is the perfect opportunity! If that's the path you choose, here's some advice for making it an enjoyable experience: go in with an awareness of the strengths and weaknesses of your chosen tech, try to find out how others have used it to solve similar problems, and be prepared for unexpected points of frustration!
One potential downside that was on my mind as I built this game was performance. It's been suggested to me that functional programming languages naturally create lots of garbage for the garbage collector. For some game developers, building a game in any garbage collected language is out of the question because the dreaded GC pause can cause a noticeable jerk in the animation. Maybe because I'm oppositional, or maybe because I feel the position is absolutist and extreme, or perhaps only because I wanted to see the truth for myself, I developed an itch to challenge this common knowledge, at least to some degree.
The conclusion? Despite making zero attempts at optimizing the code, I could not detect any performance issues at all. To be fair, the game loop doesn't do very much! Here's the bulk of it:
This code takes the falling words and then determines which ones match the user input, which ones have crashed into the bottom, and which ones are still falling. It places them into corresponding groups, and lowers the words that are still falling by a small step. In ReasonML, instead of modifying objects in place, we make modified copies of them, so all of this regrouping and lowering of words is creating new objects. This means that we're occuping more memory, plain and simple. Profiling revealed that garbage was certainly generated and cleaned up, but there was no noticeable impact to the game and no memory leaks observed. Had this been a more complex game, with lots more objects being rendered on each frame, it might have made a difference. But I'm glad I didn't spend much unnecessary time trying to manage memory manually with shared array buffers, and I'm certainly glad that I didn't let the purist sentiments of other people stop me from trying my hand at something fun.
KISS (Keep it Simple, Stupid)
Unnecessary complexity is an easy trap to fall into, and avoiding it can pay dividends. In my case, an early disappointment accidentally propelled me towards a simpler implementation. I had a dream of making this game cross platform, so it could be played on the web as well as the native desktop. Developers who build games in ReasonML often use a library called Reprocessing to achieve this very thing. Sadly, I could not get my project to build with this library, so I decided to abort that mission, at least temporarily. To decide what to do next, I established some criteria.
- My UI code would be strictly separate from the game code. They would communicate through a minimal interface that would allow me to swap out front ends in future iterations.
- My game will only be playable on the web for the initial release.
- My web UI solution should be as simple as possible.
The last point leads me straight to the browser's canvas API. The mechanics of my game are simple enough that I shouldn't need any abstractions like PixiJS. Avoiding libraries also means less content that players have to download, and there's also no chance of complicating my build process. This is a big win, especially if I manage to convince other developers to help me out. This decision also forces me to learn more about how the platform actually works.
Using the Platform
Avoiding abstractions can come at a cost. In particular, it's often traded for verbosity. If you've worked with the canvas before, you know that drawing something on the canvas is conceptually very different from using HTML and CSS. The latter allows us to use declarative style programming, where the former is strictly imperative. For example, take the problem of painting a word two different colors, which we have to do in this game in order to indicate which words match the user input. In HTML, you could wrap the matching portion in a
<span className="orange"> and call it a day. Not so with canvas calls, as you can see below.
This is the ReasonML code that I ended up with, and I'd say it's fairly verbose. Some things worth noticing:
- I have to know exactly where to start drawing the matching portion, and exactly where to start drawing the rest of the word. I'm calculating the width of the matching portion to figure this out.
- These calls are stateful. I call
fillStyleSetto set the color, and then any text that I draw afterward will be in that color until I call
- I have to call a lot of functions to draw my scene! That's a lot of opportunities to make mistakes.
widthGet calls from the previous example show these getters and setters in use.
measureText above. Notice that these are methods defined on the
context object, but in ReasonML we define them as functions that take a
context object as the first argument. The most obvious way to call this function is like this:
1fillText(context, matching, left, bottom);
You probably noticed, though, that the previous example looked like this instead:
1context->fillText(matching, left, bottom);
-> operator is called the fast pipe operator, which allows us a more fluent API and helps us avoid "Russian doll" nesting of parentheses.
The moral of this part of the story is that ReasonML and Bucklescript made the platform a lot easier to work with for me by forcing me to define a safe way to use the canvas API, and as a bonus, they keep coming up with great syntactic sugar to make those definitions easier to create and use.
I list this last, but don't mistake it for the least. This post is about finishing a project, and it's not done unless it's shipped. To achieve this, you have to stick to the other goals you've defined for yourself and be willing to fight off scope creep, which can come in the form of added features as well as unnecessarily complex software.
When we build things, we can usually think of lots of ways to improve it. We have to be able to say no to ourselves! If you want to add something to your project, ask yourself if you can ship without it. Would you be willing to show your project to customers and ask them to use it? If so, get it out there now! You can always iterate later. There's also always a chance that the thing you want to add doesn't actually result in more happy customers! Perhaps some readers will be familiar with this quote:
It sounds blasphemous but the ones that are important will keep bubbling up anyway. Those are the only ones you need to remember. Those are the truly essential ones. Don’t worry about tracking and saving each request that comes in. Let your customers be your memory. If it’s really worth remembering, they’ll remind you until you can’t forget.
This is the approach to fielding feature requests at Basecamp. While your project is in development, it is virtually impossible to tell which of your thousand feature ideas will actually make customers happy. If you really want to know how to improve your product, implement a system for collecting feedback, and let your customers tell you what makes them happy!
To Be Continued
Today’s post was all about how defining goals and sticking to them helps you stay on track. I listed some high level goals that I had for my project and attempted to generalize them into reasonable objectives for most personal programming pursuits. Next time you have an idea you want to hack on, I hope you’ll consider adapting these goals to fit your own personal vision. I really think they will help you ship something! But they won’t take you all the way. In my next post, I’ll talk about how I executed my plan and stayed aligned with my goals along the way. Farewell for now!