
Thirteen Years of Bad Game Code - et1337
http://etodd.io/2017/03/29/thirteen-years-of-bad-game-code/
======
hinkley
I tell all the junior Devs: if you aren't horrified by something you wrote a
year ago, you aren't learning fast enough. 20 years in and I still find things
I wrote 8 months ago that I would not do again.

~~~
Klockan3
I disagree, if you consistently think that the code you wrote a year ago is
shit then you are probably just chasing a new fad every year. I'd say that if
a junior developer with a year in a language doesn't write code he can
rightfully be proud of then he is focusing on the wrong things.

~~~
braveo
thank you, I say the same thing every time I see this come up.

That's a young mans game, after you've been doing this stuff long enough the
things you learn are in terms of systems design, maintenance, etc. I regularly
go back to code I wrote 2 and 3 years ago and think to myself "knowing what I
know now, that code is mostly alright".

I can't understand how someone can go back and look at some authentication
code, for example, and think the way they did it was horrible. Just how many
different ways can you write auth code?

It's one thing if you're a young developer, but at some point the improvements
to your code are negligible and have nothing to do with the value you bring to
a project.

~~~
Fifer82
Where do you guys get a job without any time constraints? Also were you born
with all knowledge? You are saying for example that the booking system you
made in 2009, which was the first time you ever "programmed time" is pretty
much perfect? (change the example to shaders, or physics, or your first Node
back end blah blah). I am calling Bullshit.

~~~
braveo
I would think the ones who constantly go back and worry about old code are the
ones you would accuse of having no time constraints.

The thing is, if you're going back to the first time you've ever "programmed
time", then what you're assessing is design, not code, which falls in line
with what I said.

------
geokon
He basically admits that several of the "improvements" boils down to ripping
out patterns that decouple code instead of finding alternate solutions.
They're kinda quick and dirty, not agile, and could make refactoring a
nightmare. I mean, he's not necessarily wrong, but hindsight is 20/20 and once
you're done you can look back and cherry-pick and say "ahh, well obviously
these things would always need to be coupled! I shouldn't have done the
gymnastics to decouple them!" .. but that's a dangerous mindset to start
solving the problem with.

The whole topic of global state is very interesting and one I approach
cautiously. I'd love to hear what more experienced programmers have to saw.
When I go with global state I usually rely on someone else's judgement and use
a framework. Like OpenGL and the graphics pipeline has global state.. and I'm
okay with that. I use Cinder/Arduino, those things have some global states and
for good reasons as well.

But you really try your best to find a stateless solution - and here is feels
like he isn't really trying

~~~
sillysaurus3
_The whole topic of global state is very interesting and one I approach
cautiously. I 'd love to hear what more experienced programmers have to saw._

1\. Quake 3 relied heavily on global state. It was good design, and the engine
produced billions in revenue.

2\. Emacs relies heavily on global state. A global variable is a first-class
citizen. When a package declares a global variable, it's a contract between
the package author and the user that "You may configure the behavior of this
package by binding this variable to a new value, then invoking one of my
functions." Emacs lisp has language constructs which makes this easier: `(let*
((foo-state 42)) (foo-bar))` will update the global variable `foo-state` to
42, call the global function `foo-bar`, and ensures that `foo-state` is
returned to its previous value even if an error occurs and an exception is
propagated. In other words, global variables are the interface to a module,
just like a module's functions, and global variables are almost never left in
an unexpected or invalid state due to errors.

This system works shockingly well in practice. Emacs is basically a gigantic
state simulation, and it parallels a game engine quite strongly.

The Emacs Lisp language itself is cumbersome, which has led many to dismiss it
as ancient. The true power of emacs has little to do with lisp and almost
everything to do with excellent design decisions.

3\. The history of programming informs us that those who hold to dogma are
quickly made obsolete. Every tool and pattern has its place. A technique that
simplifies X in a certain domain will make X far more complicated in a
different domain. Context matters.

~~~
TeMPOraL
> _The true power of emacs has little to do with lisp and almost everything to
> do with excellent design decisions._

Those design decisions didn't come out of the vacuum. The ability to patch and
modify almost everything at runtime is something that came out of Lisp and
Smalltalk, and wasn't even on the map of the (currently) mainstream languages
until very recently. Dynamic scoping is something that AFAIK has strong Lisp
roots too.

Emacs Lisp is less of an accident and more of an old, _old_ language. Even
Lisp family moved on to default to lexical scoping, though it didn't remove
dynamic scoping - as in many cases, it's still extremely convenient.

~~~
smaddox
Dynamic scoping can easily be included when desired in the form of a key-value
store. I can't think of any advantage to having it built into the language
itself. I guess you could argue standardization, but most modern languages
provide a standard kvstore.

------
mirekrusin
I wish people would blog more like this.

Majority of blogging is focused on how to use something or describe some final
idea/conclusion. They are not bad per se but reading about failed ideas, dead
ends etc. - the journey itself that lead to the final shape/conclusion is very
valuable.

------
kpil
I think I overused every single pattern, method, or framework I happened to
learn until maybe after 15 years of coding.

Probably still do to some extent.

Nice read, and something to reflect upon for all young "fundamentalist". It
takes some experience to learn when to bend the rules...

~~~
retox
If you think of patterns and frameworks as tools, when you're a beginner the
only way to master and ultimately discover the limitations of those tools is
to use them everyday.

~~~
AstralStorm
However you shouldn't release a training prototype. Artists do not release
sketches. Architects do not sell concept drawings.

~~~
coldtea
Actually artists do both of those things.

And some artists even specialize in sketches and concept drawings.

~~~
cestith
Sometimes professional graphic designers end up selling one of their concept
pieces to a customer with a request of "no changes". Sometimes it's even the
one the designer least liked and produced as number six of the minimum four
concepts just to show the customer more options.

------
Jimmy
Meh. If your code works, then it's good.

Of course I don't literally mean that; you can write code in a way that makes
it easier to debug, extend, etc. But too many developers get wrapped up in
engineering the perfect system and forget that they're supposed to be, you
know, writing a program. I think this is why a lot of hobbyist projects run
out of steam.

~~~
vkjv
One of the quotes from a senior engineer that has stuck with me over the
years:

> "Working is the lowest bar of quality."

~~~
cestith
If you replace "bar" with "rung" I think you may evaluate that a bit
differently. According to Kent Beck, "Make it work, make it right, make it
fast."

Something that's reached for the most proper implementation, the fastest
execution, and the most compatibility with something else is still useless if
it produces the wrong results quickly and prettily.

------
z3t4
I might be biased as I agree 100% on the takeaway, but this is amazing! Not
only did he use best practice, but most importantly he made his own
conclusions, and learned new things, instead of just accepting things how they
where.

When I started coding, I though you could only have one letter variables, like
a, b, c, x, y, z. I did not question this, but one day I ran out of variables
...

Edit:

I looked at some of his other posts and saw an altitude bed on an old photo,
witch concludes he probably is/was an athlete. I think it's common among
athletes to have the mindset that they want to keep improving their skill
level. This can be a good trait as a developer, but they are hard to manage,
as such persons will quickly outperform their colleges and get bored. They
work best with other people like themselves, or with old experienced
developers. Make sure you protect him so that he do not burn out himself or
his team. While these "10X" developers can work very hard, they are just
humans.

~~~
cableshaft
I started development on Texas Instrument calculators. On most of those, you
could only have one letter variables in its TI-BASIC programming. I lucked out
and convinced my parents to spring a little extra for the TI-85, and it had a
lot of extra goodies, including named variables.

I credit a lot of my initial understanding of how programming works to making
various text-based games on those calculators while only half paying attention
to class in high school.

~~~
kkoomi
The TI-85/6 is a magical device. A widespread built-in text editor, compiler
and GUI library in one.

I was very sad when I realized the BASIC performance was like an order of
magnitude worse than ASM performance.

------
janwillemb
> I was thirteen

No need to be harsh on yourself if you created games like this on that age!

~~~
danbolt
Agreed! While a lot of these games might not have been the most pretty to look
at or play, I'm sure the author learned a lot making each.

------
hopfog
I've been running a browser-based MMORPG for a couple of years and when I
started out I was a total novice in Node.js. I didn't even know how modules
worked so the back end was just one big file. Needless to say, the code is a
complete mess and the refactoring has made it even worse since I've started to
implement some patterns just to regret it.

I'm thinking of making the whole game open source but I'm so ashamed of the
codebase that I don't want anyone to see it. It's a dilemma since I think the
game has potential as an open source project. It's inspired by Ultima Online,
Minecraft and Reign of Kings but with a lot of constraints making it easy to
implement new features. It also has quite a few dedicated players.

~~~
ithinkso
Don't let us hanging, what's the game? I've been looking for something web-
based and casual to log few times a day and click some stuff:)

~~~
hopfog
It's actually almost unplayable right now since a bug I haven't managed to
pinpoint is messing up all the mines (ugly codebase doesn't help). They're
completely strip-mined and the economy is broken with a few players sitting on
all the resources. I wouldn't recommend starting until I've managed to fix it
in and restart the world.

I'm currently in the process of rewriting the front-end from the old jQuery
mess to React. It's like 95% feature complete but as soon as that is done I
will start investigating what's going on with the mines.

Anyway, here it is: [http://canvaslegacy.com/](http://canvaslegacy.com/)

Again, I strongly advise against starting right now since you won't experience
the game like it's supposed to be. It's hard mode even for seasoned players.
:)

~~~
jscholes
> It's actually almost unplayable right now since a bug I haven't managed to
> pinpoint is messing up all the mines ... > I'm currently in the process of
> rewriting the front-end

These two things seem somewhat at odds with each other. Clearly, to players of
your game, a fix for this show-stopping bug would offer a fair amount of
value, whereas rewriting the frontend, from your description at least, sounds
like an exercise that is more for you as the developer.

I'm not criticising, by the way. Just sharing my thoughts based on your
comment :)

~~~
hopfog
Yes, you are right. The thing is though that I'm making the game mostly for
myself and I always tell my players that they are playing at their own risk.
It's very early alpha and they may lose everything.

I tried fixing it initially but eventually gave up and put the whole
development on hold for 6 months. Also note that the problem gradually got
worse. In the beginning it wasn't such a big deal but eventually became post-
apocalyptic when players had a hard time finding even the most basic
resources.

Another aspect is the amount of support I had to take care of when the player
base was at its peak. It almost made me burn out, consdering that it's a side-
project that I'm not making any money of. So when this major bug started
affecting the game and the active players began dropping it was almost a
relief. I never expected that anyone wanted to play it to begin with. I just
put it out there as soon as I had something playable.

So after a long hiatus the joy of rewriting the front-end made me motivated to
work on it again. Perhaps not the best strategic move but it feels good to be
back. :)

~~~
jscholes
Great response, thank you for taking my words in the spirit they were intended
:) I battle hard to find approaches to my next features that will motivate me
to actually get them finished too. I'm learning that this is a big factor in a
programmer's experience. So I understand completely.

------
OskarS
> There are many, many more embarrassing mistakes to discuss. I discovered
> another "creative" method to avoid globals. For some time I was obsessed
> with closures. I designed an "entity" "component" "system" that was anything
> but. I tried to multithread a voxel engine by sprinkling locks everywhere.

Dear God, I feel like I'm having my game dev life read back to me...

------
ianlevesque
Made me wish I had more of my old code to cringe at, but honestly I cringe
after just a few weeks removed from a project.

------
partycoder
For booleans prefer prefixes such as: has, can, is. e.g: hasEnemies.

~~~
NicoJuicy
isActive

~~~
TeMPOraL
In my games now, it's activep ;).

------
TeMPOraL
I have a very similar history (though a lot worse games). I still have the
backups of my first OpenGL programs and game code from ~16 years ago, I might
put up a similar article at some point. The difference is that the author's
game seem to improve visually over the years; mine actually regressed from 3D
to 2D world (since the latter is easier to manage) ;).

Some random comments follow.

> _Still, I loved data binding so much that I built the entire game on top of
> it. I broke down objects into components and bound their properties
> together. Things quickly got out of hand._

Boy, you have to see JavaFX. And I inherited a codebase at work written by a
cow-orker who was absolutely in love with bindings at that moment...

Also the big blob of code reminded me why good macros in a language (not
C-like macros) are a blessing ;).

> _Separate behavior and state._

Yeah, learned that same lesson too. I prefer to think in systems operating on
data as opposed to a web of objects calling each other. One of the reason is
that the web of objects is very implicit; you soon have no idea who actually
refers to who anymore.

> _If I start typing self. in an IDE, these functions will not show up next to
> each other in the autocomplete menu._

That's a good and _practical_ point, though I feel dirty about renaming
functions for solely that reason. I wonder if this isn't time to experiment
with some "first-class editor semantics". Here, ability to explicitly mark
functions as grouped together solely for the purpose of IDE displaying them
like that in autocomplete and outline view. In my evening Lisping, I'd love to
have more control over indentation for macros I write.

> _global variables are Bad™_

Yeah, that's almost like "goto considered harmful", and about as much correct
as a soundbite (i.e. not very). Unfortunately, everyone keeps writing how
global variables are evil, and what I'd love to read is some treatment on how
to use global variables correctly. For the situations where - like often in
games - you absolutely, positively _need_ a lot of globally accessible data.

> _Group interdependent code together into cohesive systems to minimize these
> invisible dependencies. A good way to enforce this is to throw everything
> related to a system onto its own thread, and force the rest of the code to
> communicate with it via messaging._

That's a... very self-conscious approach. Odysseus-tied-to-a-mast style.

~~~
and0
Agreed on globals. I learned to code making a huge Java + OpenGL game engine
some years back, right when scripted web frameworks were really blowing up and
people were becoming test-obsessed. Every new article said that global
variables and static anything were a deadly sin, so I did some really ugly
things to avoid using them. Now I think global state is significantly simpler
for certain systems, better than trying to pass that state around when it'll
never be instanced.

------
mirages
Well one day I didn't foudn anything better to solve my sync issue with
queries to the server than putting a Thread.Sleep(int) the variable name for
it was

    
    
      private int DO_NOT_TOUCH_THIS_IF_YOU_DONT_KNOW_WHAT_YOU_ARE_DOING_Timeout = 7000;
    

The code actually made it and is still in production

------
prance
> Unfortunately, Sun decided that almost all Java errors must be recoverable,
> which results in lazy error handling like the above.

Not quite: Java insists that all (checked) Exceptions be handled or passed up
explicitly. One can argue that this design decision is bad, as has been done
extensively elsewhere.

However, in the particular example given, I'd argue that it would have made
much more sense to display a user-understandable error message to the user
(e.g. "Required gun resource file is missing, please re-download the game
here: (...)"), and then just System.exit() if it's non-recoverable. Maybe
print a stacktrace if the game was started in debug mode.

------
DropbearRob
I really enjoyed this read. I still look at code I write every year and
cringe.

Everything becomes influenced by the flavour of the week.

Don't be so hard on yourself, We all suck in hindsight, but its all about
recognising it an constant improvement.

------
Sir_Cmpwn
One mistake you're still making: cross platform. I'd love to try out Lemma on
Linux :)

------
falsedan
> _Unregistered HyperCam 2_

This is legit af

~~~
falsedan
> _www.fraps.com_

yesssss

~~~
falsedan
Real comment: this advice is excellent, don't fall into the trap of perfecting
your tools before starting. The article presents the code quality issues as
constantly there and always getting more regrettable over time, but the
finished products get better and better!

I say, write bad code if it will get you closer to your goal (here, making a
game people will pay money to play). Learn from your mistakes, and make new
mistakes next time.

------
kuerbel
As a 2nd semester cs student, that was pretty helpful. I have to design a
battleship game in Java and have no idea what I'm doing actually. I'm just
staring at the cursor and don't even know where to start, and it's comforting
to know that other people struggled as well

~~~
TeMPOraL
Battleships? Like that?

[http://static.kidspot.com.au/cm_assets/32032/battleship-
game...](http://static.kidspot.com.au/cm_assets/32032/battleship-game-main-
jpg-20151022131744.jpg~q75,dx720y-u0r1g0,c--.jpg)

I.e. turn-based and probably doable with an UI toolkit (as opposed to drawing
stuff directly with DirectX / OpenGL)?

Anyway, shoot me an e-mail (address in my profile), I'll give you a few quick
hints to get you started.

------
pnathan
Haha, this is great! Memories. All those terrible lines of code I wrote when
younger led up to less terrible later. I too got on some of the cargo cults
until the harsh reality of the projects showed me why not. :)

This year marks 20 years since opening up QBasic as a 13 y/o.

------
kelvin0
The only way to not make any mistakes, is to do nothing. As Nike says: Just Do
It!

------
yblu
This `for` loop looks particularly curious to me:

    
    
      for (int i = this.bindings.Count - 1; i >= 0; i = Math.Min(this.bindings.Count - 1, i - 1))
    

Why isn't the last expression a normal i--?

~~~
et1337
Because bindings[10] might have triggered a process that removes bindings 7-9,
causing "i" to be out of bounds of the array. I wanted to continue updating
the remaining bindings even if some of them got removed.

~~~
yblu
Wow, that's pretty subtle. Thanks for explaining.

------
jlebrech
I want to start writing something that would have been on the original atari
to begin with, just have to look like that style. and keep moving up a
generation in terms of gameplay.

~~~
97s
Pico-8 is a good place to start.

------
davidbiehl
i would love to read a follow up that dove into the PHP dark ages. i was doing
PHP around that time as well.

------
JCDenton2052
I think we have all been guilty of negatively-named booleans at some point or
other.

------
eigenbom
A good retrospective. I'm always inspired by Evan's passion and ability.

