Tuesday, July 27, 2010

Generic can be the enemy of progress

As it was said on coding horror, "a blog without comments is not a blog". Earlier today Jotaf posted a comment on my last post that got me thinking. The post was on my work making changes to the skill tree easier, here's the meat of it:

"Nice, I agree completely, although you must always ask yourself the obvious question: "is it worth it?". Since you're gonna change the skill tree many times during balancing the answer is yes. This is almost always true for data-driven gameplay features (ie, stats, skills...). It's almost always not true ("it's not worth it!") for things that are more or less set in stone, like game mechanics, combat, AI, and I've seen many developers getting themselves into an endless loop of refactoring, because you can obviously always make future changes easier by changing your design to be more generic/plugin-driven/offloading more data or logic into external files."

I think this is true and in some cases can prevent roguelikes from ever getting completed. Since none of us get paid to work on our pet projects, the temptation is to never set deadlines or abide by them. The desire to make the code "perfect" can make us spent all of our time making the code more generic to the point we're creating a game engine more that game itself. While this can sometimes be successful (ToME being the most obvious example), for 95% of us it doesn't buy us much.

If I spend 10 hours refactoring the AI to move it to external files, and then spend only 5 hours tweaking it over the next year, the change was obviously a loss. Even if I save some time, that needs to be balanced by the fact that the code is more complicated. As opposed to a function you can debug, a problem can be in the file parser, the external file itself, the code implementing the behavior expressed in the file, etc.

I have a few suggestions for other developers based on my experiences with magecrawl that so far have mitigated this issue for me (beyond my general pragmatic attitude).

  • Set deadlines, preferably short ones - When you want to finish your iteration in three months, spending a month and a half on something like swig'ing libtcod or converting libtcod's build system to cmake, makes you at least think about the trade off you are making.
  • Use bug tracking software - I love fogbugz myself, but the one built into google code or even bugzilla is better than nothing. When you find something you'd like to refactor or make more generic that would take longer than 15 minutes, go add an entry for this or a later iteration and get back to what you were originally working on.
  • Things that change often or by users should live in external configuration files, things that don't shouldn't - How often are you planning on changing your physics system? Even if you think this'd more more often than once, leave it hard coded for now. After the third time you mess with it, then go make it generic. By the time that third time happens, you'll probably have a better view on what to pull into data files anyway.
  • You can ignore any of these rules if you want to - beyond the fact that I'm just a random blogger on the internet, you are writing your game. While I think getting a working game done early is the best way to do things, realize you are doing it for enjoyment. If spending a year writing a engine that can make toast, run a neural net AI, or show pong on the screen, all with just changing configuration files sounds fun, go get coding.
One final node, I love comments on my blog. They are the breakfast of champions. Please feel free to leave them, they make things more interesting around here.


    fu said...

    Agreed! Most of my moments of inactivity usually come after I start refactoring a chunk of code that works perfectly fine and end up losing sight of the goal. At the end of the day working on my project is meant to be fun (and to learn a thing or two of course) so it helps to ask myself "would this be fun to do?" before jumping into a reworking task.

    georgek said...

    donblas, I felt compelled to leave a comment (though I am a regular but usually comment-less reader of your blog nonetheless :) ). Your programming notes and devlog are consistently interesting and inspiring. Also I used cmake to compile libtcod yesterday and it was very nice!

    Worthstream said...

    You're perfectly right!
    Using too many external configuration files, or delegating too many functionality to them, is called "softcoding" and in my opinion is the biggest problem that prevents the relese of new RLs.

    For reference:

    jice said...

    I'm currently in a phase of anti-refactoring with the cave, replacing nice and barely useful config files against static arrays that work as well and can handle change of data structure much faster.
    In the end nothing's important but what is shown on the screen, isn't it ? ;)

    Ed said...

    Thanks for this post! Not only do you point out this common problem, but you propose several ways of dealing with it! I'll have to keep this in mind when working on any projects of my own...

    Heroic Fisticuffs! said...

    Well said!
    It's good it to remind yourself of the trade-offs involved. Chances are, no one will ever look at your code (even if you release it). However, nearly everyone will see the finished product. They probably won't care about your elegant layers of abstraction. :)

    I am constantly catching myself diving into 'refactoring hell' on my current project. So far, so good.

    Pikalek said...

    Sophie Houlden & Tom Francis have both recently made related observations that alternative to trying to turn out perfect code is to acknowledge that first attempts at games might suck, but we can learn to get over it.