Building My First Game

The other night at NashFP I did a whirlwind tour of a game I wrote, PewPew (play it!). While I was going over the source code with the group, I realized that I learned quite a bit while doing this and needed to share.

pewpew

About 4 months ago I had just launched the new website for Firefly Logic. I love websites with awesome 404 pages. That little bit of detail is just icing on the cake - it's how you know that someone behind it all gives a damn. What better way to solidify our nerdiness than to write a small game to play? I wanted people to know that our site was loved and so I pitched the game idea to the people who sign my paycheck: "I want to write a game for our 404 page, you cool with that?" They agreed and I went to work.

I only had a bit of down time to make it happen. This idea had been bouncing around in my head for a bit and so I had been researching a couple of javascript game engines. I told Calvin about my plans and he suggested I give it a try in Elm. I've been learning about functional programming for a few years, so it felt like an opportunity like this should not be wasted. I looked at some of the examples on their site and knew I could make this happen. I decided that the pong example had enough features to serve as my starting point.

Elm is based on the paradigm of functional reactive programming (FRP). In other languages I've worked with, user inputs are modeled as events. We're all used to the pattern at this point: register for what events we care about and provide some callback. Then later in the app our callback gets invoked and we must reach into some shared state to see if we can even accept the user input and then write back into that state some new data. It's callback spaghetti and debugging it is hell.  FRP is very different; instead of treating user inputs as some blip in time, they are modeled as a value that changes over time. That model is referred to as a signal. Signals aren't limited to only user inputs - time can also serve as a signal in terms of elapsed time or frames per second.  The latter of which serves drives the game and replaces the traditional game loop.

Quick Rundown
This is the first game I've ever built. This took about 9 days from start to finish and that includes my time learning Elm on the fly. Most of the Elm examples are all in a single file. I started out this way too, but quickly started having trouble keeping up with what went where. Then that feeling started to creep in that I was introducing too much coupling in places, so I started hacking bits out and moving them to separate modules. I feel okay about how the code organization shook out, but looking back at it now I can see some places where there is opportunity for improvement. Here's a quick rundown of the modules:

  • Entry - There's nothing too spectacular going on here. We create a level and initialize our game state with that level. I think the most interesting bit here is the foldp which is essentially a left fold over time. That turns your input into a state signal which we finally feed into the renderer.
  • Inputs - This game has very simple inputs: left/right to move and space to fire. On the last day I added touch controls (the touchFire and touchMove functions) and used the merge function to merge the keyboard and touch signals into one. The resulting touch compatible version required no modifications to the game which I thought was awesome. One last note, the final signal is a combination of all of the above plus time sampled at 60 fps. This signal becomes our game loop that drives the foldp in our entry point above.
  • Step - This is where all of the logic for the game lives. Every tick of the game runs this function which advances game objects: ship, enemies, projectiles, and explosions. Here is where we keep track of scoring by doing (very crude) collision detection and then removing those objects from our state that have been destroyed or have flown off screen. The entire job of this module is to take the current inputs + past game state and produce a new game state.
  • View - The view ends up being very straight forward. This module takes a game signal and produces the UI. No tricky business - just render exactly what the state of the game is. It was challenging for me because I was having a hard time overcoming prior web experience with layout. Elm uses Cartesian coordinates and that took a bit to adapt to. I also lost a bit of time to the tweetLink function because I was trying to use Elm's ports functionality to reach out to JavaScript and invoke the encodeURI method on the windows. In the end I just pre-encoded it and moved on.
  • Level - This is the level generator. It would have taken me forever to get the enemy layout right by hand, so I modeled the level as a bit of ascii (art?) and then parsed that into enemy coordinates. This allowed me to try several configurations and enemy sizes without reworking a whole bunch of manual coordinates entries. Spoiler Alert: There's only one level. ;)
  • Model - The data structures that make up the game state.
  • Utils - The dreaded "I don't know where this stuff goes" module. throttle was an experiment in controlling firing rates of the ship at the signal level. Later in the game I modeled the enemy firing rates inside the game state to see what that would look like too. After trying both, I still don't know which place is the most sensible place to manage this. cubicEasing is a port of a JavaScript easing algorithm so I could have enemy fire be very light early on and transition sharply to heavy fire while remaining within some boundaries.

Wrapping Up
This was a fun step up above the basic exercises you typically do when learning a new language. If you haven't looked at any functional languages, I encourage you to do so. Elm would be an excellent first functional language because it's very interactive. It's also pure which forces you down a path without mutability so you can avoid the temptation to fallback to your old object oriented habits. Even if you won't be using Elm day to day, learning functional programming will make your other code better. Give it a try!

Thank you to Calvin and Rachel for letting me bounce ideas off of you while I was building this thing. Also, this game wouldn't look nearly as awesome without the sprites that Joel made.

Digging Out: A Tale of Open Source Neglect

Back in 2007 when I first announced my masked input plugin, I had no idea what I was doing. It was early days in jQuery and still very early in my career. I was just trying to give back a little bit for all that I had been given from open source software. I had no idea that over 7 years later I would still be working on this project. At the time I needed this, I built it to suit just what I needed and put it out there. That's how this OSS thing works. right?

I'm here to say it worked, but quite by accident. At the time jQuery had just recently released 1.1 and soon thereafter they created a plugin repository and I put the plugin up there. Then, something happened. jQuery became massively popular and I pretty much rode the success of that project. I built a plugin that some people needed and it worked with the new JavaScript library. It was right there in the plugin repo and people downloaded it. The last time I had any kind of metrics was version 1.2.2 when I was still hosting the project on google code. You can still go there and see the counts, which at the time of this writing is sitting at 2.8 million downloads between the minified and full source files. After that I moved to github and currently the project currently has almost 1200 stars.

Why am I telling you all of this? Is this a humblebrag?
I'm not telling you this to brag at all. I need you to understand the scale of it all - it's a clue to the source of much of my anxiety. I'm still shocked when I see those numbers because I have no idea how it happened. No, I'm telling you this because it's time to tell a story of what happens when something gets bigger than you and how I'm handling it.

Grab a snack, this might take a while.
I didn't quite realize this going in, but it takes a lot to maintain a project long term.  This is only magnified by the number of people actually using it. Those people are using this as part of a product they are building and each has their own unique requirements. Given my role in this, I feel obligated to provide a certain level of quality. Unfortunately, if I were to build in every feature that was requested, then I would end up with a kitchen sink project. I'm sure you've discovered these in the wild. They have a ton of options and go out of their way to accomodate everyone while sacrificing usability/performance/stability for the majority. In the world of web library development, each new feature comes with a large future cost in effort to maintain it.

There are a lot of browsers out there to test on. Each one broken in it's own special way. At present I test on desktop OSs: IE 8-11, Chrome, Firefox, Safari. Then there are the mobile browsers... Oh, the hell that mobile browsers cause. I wrote this before there were even smartphones. Now I'm trying to make sure it works at least on iOS and Android.  It's safe to say I take pause when a new feature request comes through. That's a lot of work to make it work everywhere given that I have to contend with weird stuff like this.

At this point, it's worth sharing with you that I have a really hard time telling people "no". I have such a hard time with it, that for a while I just didn't. Issues suggesting features and pull request came across my inbox. I wasn't 100% happy with them, so I would just ignore them. Deep down I knew that ignoring them was bad, but somehow I was just hoping that it would all go away. Of course you know how these things go - problems won't magically resolve themselves. So when legitimate issues popped up with bugs, I continued to ignore them because of the other open issues before them. Several times I tried to get back into it only to get upset about the state of the code. Everything was just in deadlock.

A lot of events had happened around this time in my life and it was all overwhelming. Really though, there are always going to be distractions from free work. It's funny, since I've started this project I've had two children, sold one house, purchased another, changed jobs 2 times voluntarily, and got dumped on my ass once because an employer didn't play nice with the IRS. This product has been wearing on me for a while and the words you're reading are basically what my close friends and coworkers have been hearing from me for years.

About 4 years ago I put out a bit of an SOS. That was the first upswing in series of akward cycles of disgust, acceptance, small effort, tiny release, finally ending with denial induced procrastination - "I just did a thing, you should be happy with the thing. I'll pick this up in a few months." Each cycle I would get maybe 1/4 of what needed to be done completed and then fall off. This is how I ended up with nearly 90 issues out there. Ninety. Issues. The project only has 430 lines of code, how can it have 90 issues?!

This is open source and it's all out in the, ahem, open. There sat 90 fingers pointing at me for the world to see. This guy is failing. Every time I would open up my issue list, there was more. For one person it felt pretty unsurmountable. At this point I can just hear you saying, "It's open source, you don't have to do it alone. You should have just accepted the pull requests and moved on." The problem lies above where I mentioned that I felt obligated to meet some self perceived level of quality. Very little of the code being sent my way was meeting that imaginary quality bar. In reality, the problem was me. I had failed to provide any feedback. If something wasn't right then I should have responded and tried to set some expectations about what I wanted the final outcome to be. Instead, I just let the valuable feedback and code sit there and rot.

It's silly for me to expect someone else to play an open source game with me when I haven't told them the rules. Heck, I didn't even know the rules. I just knew when the solution didn't feel right or complete. You can't run a project like that. It doesn't work and I think I've done a good job at proving that. I was about rock bottom mentally on this project and the way I saw it, I had 3 options:

  1. Call it quits - Just throw my hands up and walk away. Put a note on the README letting everyone know that this project is dead. I seriously considered this one, but I felt like leaving when the project was in such terrible shape would be a huge disservice to the many existing users.
  2. Hand off the project - There was already a couple of well established forks out there, so this never felt useful. The people I would have handed it off to were already well into building their vision of this widget. When I looked at the forks I was impressed with the stuff they had done, but it didn't feel like something I would want to use.
  3. Suck it up - Go through the long process of wading through the issues and pull requests and try to make sense out of the madness.

It wasn't until I was on the other side of the pull request that how this process goes finally clicked. I wanted to add a small feature to elixir and stumbled on their excellent document on contributions. There it was, everything I needed to know about getting my code accepted. I knew exactly what was expected of me and I followed the rules laid out in that document. Within no time my pull request had been accepted and that issue was closed and gone. Huh, so that's how it should work. That was a smooth process.

With the help of Jared and the encouragement from several friends, I decided to take option #3. It took a while and I stalled a few times, but we went through each issue; triaged it; set up a plan of attack; slowly worked through the bugs, feature requests, and pull requests; and finally released version 1.4. It was hard because I had to say "no" several times. It was hard because I had to respond to several year old issues and say "I'm sorry this has taken so long." It was hard because I pretty much wanted to be doing anything but staring at the mess I had created.

Now What?
Inspired by the contributing document I mentioned above, I've borrowed heavily from it and added my own contributing document to the project. I'm working to make it easier to have an answer when a feature is requested or a a pull request is sent over. Had I established these guidelines early on, most of these issues would have been as effortless as my experience with the elixir project. There are still some outstanding issues and I'm hopeful we can move those forward and resolve them too. Most of all, I'm hopeful that I can get this project to where I can stay on top of the requests without a massive amount of effort. If not, well there's still option #1; at least I'll know I tried everything I could to keep it going.

For those of you putting a project out there for others to use, don't let any of this scare you. You won't have to worry about any of this up front; just build something awesome and share it. If people start using the thing you've built and want to give back, take some time to really think about the direction you want the project to go. Put that into words and make sure everyone knows what to expect. They might not like your rules, but at least they'll know up front if they want to play.

 

Masked Input 1.4.0

This release of my masked input plugin has been a long time coming. I managed to neglect the project for a while with the birth of my daughter and let the issues and pull requests pile up. I still have over 30 issues out there. It's still not great, but considering there was 90 issues at one point, I'm feeling pretty okay with things. Several of the issues remaining are ones I created for cleaning things up , so that's a good sign. There are a lot of features that people want and I need to consider what I'm capable of maintaining going forward. More on that in a coming post.

Huge thanks to Jared Barboza (@codeimpossible) for helping me work through the massive amount of issues. It's a lot of work to manage an OSS project that people use. It's even worse when you neglect it for a while. :(

This release covers about 40 issues. Most of the issues were bug fixes with a couple of features added. The highlights in no particular order are:

  • Better placeholders - You can now define a placeholder 'mm/dd/yyyy' for a date input and that will be used instead of the default underscore placeholder.
  • Better management of readonly inputs
  • AMD/CommonJS Support
  • Bower support
  • More bug fixes than I want to admit. ;)

 

There are still an issue outstanding with android and certain keyboards, hopefully that will be resolved soonish in newer versions of chromium. I'm still trying to come up with a workaround for current versions. Regardless, the plugin was broken in a few other ways and I didn't want to hold up a release for this android issue.

Podcasted Out

One of the things developers do well is obsess about how to be better developers. The proof is with how much content one can get on the topic: Blogs, link blogs, screencasts, podcasts, presentations, code samples, open source projects, and books (Oh, so many books I've bought and will never read completely.) Even if technology stopped advancing, there is more content out there than any one person could ever consume.

Recently I was talking about this with my friend Calvin. I told him about some of the podcasts I've started listening to and I'm so glad that I did. He gave me a few more and now I've got a enough non technical content to fill 4-5 hours of commute time weekly. Goodbye tech podcasts, I'm using those hours to ponder on new things:

The past few weeks of commuting have been pure awesome because of the above shows. I spend most of my awake time thinking about software and I was just burnt out on listening to people talk about it. After a while, it started to feel like every episode is just a repeat of the same things from a few years back. It's refreshing to hear stories about things I've never thought about. There is more to life than writing code.

Do you listen to any podcasts that stand out? I'd love to hear about them.

Bonus: Calvin insisted I listen to the This American Life episode "The Ghost of Bobby Dunbar" from 2008 and it did not disappoint.

Creating a Culture of Learning

We have a very special user group in Nashville, NashFP. I didn't start the group, that credit goes to Bryan Hunter, though I did manage to wander into that very first meeting where none of us knew exactly what to expect. I got hooked and it has become one of my favorite events to attend monthly. I've seen the group evolve from that first meeting, and there are a few things that I think make this group extra special.

We don't have dinner. Our sponsors bring in a few snacks and lots of coffee to keep stomachs from rumbling and that's it. We're here to work and learn, not shove greasy pizza and soda down our throats.

We don't have monthly presentations. The focus of a single meeting is never dominated by a single person. Every month is a grab bag of labs and micro presentations. In any given meeting it's basically a guarantee that you are going to be exposed to something new.

We don't have monthly agendas. Because we have such a variety of things going on, every month is going to be different from the last. Meetings aren't planned months in advance because the group is driving the direction.

We write code. Everyone brings a laptop and their favorite text editor. If you don't show up with a laptop, you pair up with someone. You don't learn skills from a powerpoint presentation. Videos and presentations prepare you to do the real learning as you fumble through coding exercises.

We share. Everyone eventually gets in front of the group and shares something they've done, something they're doing, or something they're stuck on. It's not forced, but if you participate in our group for any length of time I feel confident that you will feel comfortable hooking up to the projector and giving a quick talk. The bar is low, 10 or 15 minutes is common. No slide deck needed - just show some code and start a conversation around it.

No Masters. I mentioned above that Bryan started the group, but he's not the only leader. That's on purpose. We have a group of facilitators who rotate responsibility for getting the monthly meeting organized. We also have a github repo where we coordinate around programming challenges, and every member is also an admin. No single point of failure.

No one gets left behind. We welcome everyone who is interested in functional programming. Being a functional programming group is broad enough to guarantee that everyone is a n00b at something. No one is judgemental, because we know that we all have a lot to learn. If someone is stuck, those that aren't will become assistants and help that person catch up.

I haven't mentioned this yet, but we do this every month FOR 3 HOURS. By the end of the meeting, you'd think that people would rush out the door, but it's quite the opposite. Frequently meetings "end" and we're still going.

02f0bc83b39d7e2a6c0b351abe2b9125

If you are interested in functional programming and you happen to be close to Nashville, then you owe it to yourself to stop by one of our meetings. You can expect to learn something and I bet you'll want to come back. If you don't live in Nashville, please consider using this as a blueprint to start your own functional programming group in your town.

Next Page »