VVVVVV open sourced
On January 11, 2020, Terry Cavanagh announced at AGDQ that he would be open sourcing his well-known puzzle platformer, VVVVVV, in celebration of the 10-year anniversary of its release.
Reddit finds the state machine
Shortly after the announcement, a Reddit user by the name of "sevenseal" posted this Reddit comment:
Here's the link if you want to browse the code yourself:
A user named "infinitive" in TheCherno's Discord server asked:
How do u make a story game that doesnt have an 8 thousand case switch statement like vvvvvv? What's the proper way to do it?
After asking a few clarifying questions, I determined that what our friend infinitive meant by "story game" was really anything that requires a large number of states with a moderately complex graph of events connecting those states.
I had heard about the source release of VVVVVV, but hadn't yet had time to read the code. I figured I may as well take this opportunity to check it out.
I looked at the first few cases of the switch statement:
The first thing I look for when considering of a chunk of code should be refactored is repetition. There is very little repetition in this code. Case 2 and 4 both have a call to dwgfx.createtextbox, but with different text. So far, this is hardly worth refactoring.
Scrolling down a bit farther in the file (again, we're still only looking for obvious repetition), I noticed this:
Ah, now that's repition if I've ever seen it! Cases 300-336 are identical except two incrementing integers. removetrigger takes the state as an argument, and customscript is indexed by an incrementing integer starting at 0. So, how can we improve this?
My first stab at a refactor for cases 300-336 would be as follows:
There are certainly ways to simplify this further, and prevent having to type out all of the cases, but this is the safest way in the context of a switch statement. For instance, we could remove all but case 300, use an if statement to check if the state was within the range of custom scripts, then intentionally fall-through to the next case instead of breaking, but this allows you to make subtle mistakes like have an additional 301 case somewhere else in the switch. When you name all of the cases explicitly, it enables the compiler to complain if there are duplicates elsewhere in the switch.
The remainder of the cases in the switch statement don't appear to have any aggregious repetition. So let's consider other ways we might simplify or improve the readability of this code.
The next thing I would do to make this code easier to read and maintain, is replace all of the case statement magic numbers with an enumeration:
This would have prevented the developer from having to (per the Reddit comments in the screenshot above) "[keep] a notepad nearby with the important numbers written down". If you know what you're looking for, you can simply search for it by name. If you don't remember the name, you can scroll through the enum at the top of the file looking for the state you're interested in, then quickly search for it in the giant state machine once you've reminded yourself of the name. Performance-wise, this is identical to having the state ids directly in the switch statement. The only downside is having to come up with a name for each state, which is hardly a downside considering the obvious benefits it provides in the long run.
The next possible step to abstract this code further, and potentially improve readability if done sensibly, is to move the state-specific logic into functions per state. These could then be organized in code however you like, including in separate files if that were beneficial to readability and maintainability (e.g. a file called
area3.cpp could be created to contain all of the state handlers related to "area 3", if that were a sensible unit of logic for your game).
Here is an example of what that might look like for the arbitrary names I chose as examples in the code snippet above:
You'll notice that I took the liberty to refactor ROOM_0024_ENTER and ROOM_0024_EXIT out into a single function, since their logic was identical. I've named this
next_state_if_fade_complete based on the assumption that this is what
fademode == 1 means in the VVVVVV source, but there's definitely some guesswork hidden in that assumption. Nevertheless, it nicely demonstrates an additional way to alleviate duplication and make the code more readable.
There are of course many more generic approaches, most of which depend on you recognizing patterns of repetition in the state handler logic and finding creative ways to refactor that repetition out into a useful data representation. For example, let's assume for a moment that, using the same strategy as we did for ROOM_0024_ENTER and ROOM_0024_EXIT above, we could distill all of the state logic down into a small number of handlers (e.g. "start_game", "start_fade", "next_state_if_fade_complete", "victory"). Rather than maintain the lookup table in code as a state array, you may want to refactor it out into a data file so that your fancy GUI editor could be used to modify which handler gets called from each state. This would be as simple as loading the lookup table from e.g. a CSV file:
You could store the state enum values as strings if you wanted, and it may even make more sense since you'd have to generate a lookup table from game_state integer values to string names anyway if you want a user-friendly GUI editor.
Any attempts to further optimize or compress this data are probably counter-productive, at least during the development phase. Having an easily readable and hand-editable data format while iterating through different design ideas is a powerful tool, not to be underestimated. Besides, if you go crazy with compressing state logic into a custom binary file format, then you may end up optimizing yourself out of the ability to quickly try out cool new handlers that you didn't foresee when designing the hyper-optimized storage format.
Overall, while the VVVVVV state machine does have a few blatantly obvious cases of repetition, and some clear opportunities for readability improvements, I don't think it should be brushed off as an irreparable mess. It does the job that a state machine is meant to do, and the developers did care enough to keep the cases sorted by increasing state values, which is more than you can say for a lot of codebases. There are certainly benefits to having all of the state logic in one place, and, with the very minor adjustment of adding an enum in place of the magic numbers, this code would be fairly readable and maintainable.
The most important thing is that VVVVVV is stable, it shipped, and it brought both joy and misery (the good kind) to the hearts of thousands of players around the world.
If you somehow missed out on this one, it is a fantastic game that stands up very well against the test of time, and you should go check it out on Steam:
You can find the developer, Terry Cavanagh, on Twitter: https://twitter.com/terrycavanagh