
Show HN: Space Invaders in C - loadzero
http://blog.loadzero.com/blog/si78c/
======
loadzero
Author here.

This is a bit of a love letter to Space Invaders, and video games in general.

I started working on this as a simple emulation project, to rekindle my own
passion in low level video game hacking, but then realized with a bit of care
I could take it further.

So, this is not a simple clone of the game, but rather a painstaking
recreation of the source code in clean, readable C code.

I wanted to make something nice that would last a while, as a tribute to the
original, and hopefully function a bit like a rosetta stone for future
audiences.

Enjoy.

~~~
tombert
I have to ask an honest question (as someone who is interested in emulation
and old-game restoration as a whole): why C? Did you consider a newer hacker-
news-friendly language like D or Rust?

(my usual disclaimer: I'm not asking this passive aggressively, I'm genuinely
interested in the answer to this).

~~~
mrfredward
I'm obviously not the author, but I can think of a good reason not to use
rust.

The project works exactly as the original and recreates the memory state byte
for byte, so like the original it has different tasks running at once that are
reading and writing to shared memory. Rust's borrow check exists to prevent
this sort of thing, because it is so hard to do it correctly or prove it is
correct once you have done it. So to use rust, the author would have needed to
either totally re-architect Space Invaders, or write the whole thing in ugly,
non-idiomatic rust.

Rust simply doesn't let you do the things assembly and C programmers did all
the time in 1978 (and with the complexity of our software now and the extra
computing power, that's usually for the better). C, on the other hand, has at
times been described as "portable assembly," which makes it a good choice for
someone wanting to stay true to the original program flow.

~~~
loadzero
This is a good answer, and does reflect a good chunk of the reason for picking
C. Dealing with such low level tasks is what the language was made for.

The other big reason I used C, is that it is more of a lingua franca than
something more modern, and will make the code more accessible to a wider
audience.

------
vagab0nd
Story time: I wrote an 8080 emulator to play the Space Invaders. I implemented
just enough instructions so I could play the game. Problem was, the game's
title screen would show, but after a few seconds, it would immediately jump
back to the beginning.

First I thought "oh I must have implemented an instruction wrong". So I re-
checked all the instructions, read through the Intel 8080 programming manual
multiple times. I did find a few errors related to the carry flag, but fixing
them didn't change anything.

I then started to actually debug the game code. This was significantly harder
than I'd expected. Since the game was in assembly, it was not obvious what
each instruction did. What I had to do was pretty much annotate each and every
instruction, along with all the memory locations. E.g. address 0xABCD is for
player score, 0xCDEF is for player position, instruction X is for drawing the
bunker, etc.

It was truly a pain, but it paid off. So the game had an interrupt handler
registered to the display refresh signal. And I finally realized the game was
constantly interrupted when it was not supposed to. Turns out I had forgotten
to disable the signal when entering the handler.

I fixed it, and it ran perfectly.

------
nikconwell
Great write up, thanks. I was intrigued by the "tilt" keymapping on the last
line of your posting.
[https://computerarcheology.com/Arcade/SpaceInvaders/](https://computerarcheology.com/Arcade/SpaceInvaders/)
notes:

> In the early eighties you would have found the Space Invaders cabinet in an
> arcade right next to the pinball machines. So a "tilt" switch, like you
> would find in a pinball machine, would not have seemed as strange as it does
> today. If you shake, slap, or otherwise physically abuse an SI cabinet you
> will get a TILT message and your game will end.

~~~
PhasmaFelis
Interesting! With pinball machines, that was to prevent cheating, since you
could change the course of the ball by smacking the cabinet. (Some players
consider doing this without triggering the tilt sensor a legitimate part of
the game.) Hilariously, _Video Pinball_ on the Atari 2600 supported it
specifically: holding the button while you move the joystick nudges the ball
around, but if you do it too much you get a TILT message and can't score any
more points until you lose the ball.

It seems like a weird sort of reflex action to put a tilt sensor in a video
game, though. Maybe they were just worried about frustrated players damaging
the equipment?

~~~
vidarh
Supporting "tilt" was fairly common on home computer pinball games.

~~~
reificator
I'm firmly in the no-tilt camp, and it's very frustrating that all the pinball
games out on Switch right now have no option to disable the tilt controls.

Brush up against the analog stick? Sorry, TILT.

------
jacquesm
Interesting metrics, you'd expect C to do a lot better in the line-count
department than assembly.

I tried building it on Ubuntu, if you follow the instructions the SDL library
include files will end up in a directory called SDL so you have to include
SDL/SDL.h and even then the build fails with lots of SDL related definitions
missing (SDL_Window for instance).

That's because you really should be doing

    
    
      sudo apt-get install libsdl2-dev
    

Then change the include file line to

    
    
      #include <SDL2/SDL.h>
    

and type:

    
    
      make
    

The roms can be found here:

[http://www.freevintagegames.com/MAME/invaders.php](http://www.freevintagegames.com/MAME/invaders.php)

After downloading you'll have to rename the files because the names will all
be uppercase:

    
    
      cd inv1
      mv INVADERS.E invaders.e
      mv INVADERS.F invaders.f
      mv INVADERS.G invaders.g
      mv INVADERS.H invaders.h
      cd ..
    

Now the game should work:

    
    
      ./bin/si78c
    

Some minor nitpicks about the code:

\- bracket your ifs and place the starting { on the same line as if/while, or
one day you'll sit there staring at the screen for 8 hours trying to figure
out why your code no longer works due to an accidentally deleted line.

So:

    
    
      while (num < 64)
        {
            int has = SDL_PollEvent(&event_buffer[num]);
            if (!has) break;
            num++;
        }
    

becomes:

    
    
      while (num < 64) {
        if (SDL_PollEvent(&event_buffer[num])) {
          break;
        }
        num++;
      }
    
    

Neat project!

~~~
Luc
> bracket your ifs and place the starting { on the same line as if/while, or
> one day you'll sit there staring at the screen for 8 hours trying to figure
> out why your code no longer works due to an accidentally deleted line.

This is some rather strange advice. That and the rewrite dropped a '!'.

~~~
jacquesm
That's advice borne from a lifetime of programming C. Good catch about the !,
but that was just as an example of the form, not meant as a cut-and-paste
replacement.

~~~
kazinator
I'm at 30 years of programming in C, and don't understand the comment. I've
never had a problem with either curly brace placement. If a line is deleted,
and that line is a curly brace, there will be a syntax error. "git diff" (or
whatever you're using) will show you what lines you have deleted.

Both ways have some merit. Cuddled curly is good for ismple one-liners. Curly
on next line is when the statement header is stuffed with multi-line
expressions.

After all the years, I have settled on:

    
    
      while (simple) {
      }
    
      while ((big || complex)
             (condition && multiple && lines))
      {
      }
    
    
      for (i = 0; i < MAX; i++) {
    
      }
    
      for (big_initialization;
           big && step;
           complex, increment++)
      {
      }

~~~
jacquesm
Those all work because in all cases single deleted lines will generate an
error message.

~~~
kazinator
Why are you still going on about this bizarre single line deletion phenomenon?

The only way a single line deletion that contains curly braces will still
compile is if it contains _balanced_ curly braces:

    
    
      if (cond)
      { foo }
      stmt;
    

All styles which avoid this do not have an issue with the deletion of random
single line that contains a curly brace.

Regardless of bracing style, we can easily find lines in a program which can
be deleted without causing a compile error, just not ones which open a syntax
without closing it and _vice versa_.

Try not to randomly delete lines from your program, and inspect your "git
diff" or what have you should such a thing happen.

------
Tepix
If you like this kind of thing, there's also a faithful re-implementation of
the original Elite game, with (readable) source code.

Quoting Wikipedia:

"... _around 1999 Christian Pinder developed Elite: The New Kind as a modern
PC port of the original BBC Micro version. He achieved a faithful port by
reverse-engineering the original assembly written BBC Micro version and
recreating a platform neutral C code variant from it, but at David Braben 's
request this version was withdrawn from distribution in 2003. In September
2014, on Elite's 30th birthday, Ian Bell blessed Elite: The New Kind and re-
released it for free on his website. Since then, Elite: The New Kind is also
distributed again in version 1.1 by Christian Pinder; a source code mirror is
hosted on GitHub._"

Link: [https://github.com/fesh0r/newkind](https://github.com/fesh0r/newkind)

~~~
cptnapalm
There's a crash bug in the source: when the local star is to be rendered, the
game crashes because it tried to explode the star. There are a couple of forks
that solve that rather glaring issue, however I don't recall if they're in a
playable state themselves.

~~~
Tepix
I see. I noticed that the source code repository contains version 1.0 but the
latest binary version is 1.1.

------
ZeroGravitas
The white invaders on a black background was iconic in my childhood, so I was
kind of blown away when I first saw a real arcade machine with the painted
backdrop and the screen reflected over it with a half silvered mirror (or
whatever crazy tech they used in those days).

~~~
donpdonp
Thank you for posting this! Me too! I was at a local arcade and came across a
real/original spaceinvader cabinet and I was floored at how much of the
experience is not captured by an emulator on a laptop. The silvered screen
layer is very eye catching and the sound - so much bass really adds to the
experience of the dropping invaders.

------
guiambros
Amazing work, thank you for sharing! The amount of detail is prey impressive.

How hard would it be to port the sound as well?

~~~
loadzero
Good question. The sound is a hardware component (synth circuit) that I
haven't looked into much, because it's essentially outside the game code.

si78c is faithfully sending all the right bits to the right port, but the
hardware component would have to be emulated to get it going.

The relevant code could most likely be borrowed from MAME.

~~~
tyingq
Probably this and the corresponding cpp file:

[https://github.com/mamedev/mame/blob/master/src/devices/soun...](https://github.com/mamedev/mame/blob/master/src/devices/sound/sn76477.h)

Emulates the TI SN76477 sound chip.

------
theunamedguy
Excellent work! Clean C is underrated nowadays.

------
i_am_not_elon
Both the project and the writeup are awesome, Jason! Thank you for sharing - I
enjoyed it!

------
arrakeen
see also Cannonball, which I discovered recently—a reimplementation and
improvement of OutRun

[https://github.com/djyt/cannonball](https://github.com/djyt/cannonball)

------
jimws
This is an awesome project with awesome write up. I miss the days of pixelated
console games. The constrained environment led to so much creativity. Are
there any new games like this being written anymore?

~~~
speps
Check out the PICO-8 ecosystem:
[https://www.lexaloffle.com/pico-8.php](https://www.lexaloffle.com/pico-8.php)

------
zoomablemind
Nice job! Such a pleasure to read a clean C code.

BTW, in case anyone tries to build it on Ubuntu 14.04 x86 with gcc, you'd need
`-std=gnu99 -D_GNU_SOURCE` flags, otherwise it barks about stack_t in
ucontext.

~~~
veganjay
Thanks for posting this - I also needed this flag on Ubuntu 16.04 64-bit

------
FullyFunctional
I love it. Thanks for doing this.

How long did it take you? How did you verify the memory accuracy? It sounds
like you co-simulated your implementation with the emulation?

~~~
loadzero
Yes, I ran with the original in lockstep and confirmed the same reads and
writes were occurring.

~~~
matsemann
I'd like to read more about this setup!

~~~
loadzero
Yeah, there's probably a good article or two in that.

Cliff notes:

It's basically an extension of the Dual CPU setup mentioned in
[http://www.gtoal.com/sbt/](http://www.gtoal.com/sbt/)

In that article, Graham Toal discusses a hack where you can bootstrap a new
emulator core from an existing one by making a custom emulator that drives
both cores at the same time.

After each clock tick, you check the states of both processors for divergence
and halt with the diff if there is one.

~~~
zentiggr
First I've heard of the technique, that is amazing.

Makes me consider getting back to lower level coding again, even just test
code, just to see it in action.

~~~
FullyFunctional
Co-simulation is widely used for verifying RTL implementations, see fx.
dromajo.org but it's a general useful thing and I've used in many contexts,
even for verifying a compiler against an interpreter.

------
jonny383
Hats off, very cool!!!

I have always dreamed of building a classic ROM to C transpiler for native,
portable, future-proof(ish) binary builds (just like this).

------
ianai
Oh I really appreciate having a neat, complicated C program to explore and
learn from!

Could this be done for Galaga? That’s my personal favorite.

~~~
loadzero
Looks like someone is doing it:

[https://github.com/neiderm/arcade/](https://github.com/neiderm/arcade/)

Don't know if it's finished though - Galaga would be about 10 times harder to
do than this project.

------
jonathanzufi
This is magnificent. Thank you for doing this.

------
ddtaylor
> Error code: MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT

Not accessible where HTTPS is required here is a mirror that has SSL
[https://archive.is/E6y37](https://archive.is/E6y37)

------
bachmeier
Is this open source? I don't see a license with the code, but maybe I missed
it.

~~~
loadzero
It most likely will be, I have not decided upon a license yet.

------
avip
Was able to build for alpine, but I get a segfault on running.

------
hoseja
Too bad that for a lot of newer games, the source will never be released. We
need abandonware Indiana Jones.

~~~
jacquesm
It's not like the source for this one was released, this is a re-enactment
rather than the original source. But back then the software was compact enough
to allow reverse engineering to the point that you could re-implement it
faithfully.

------
joosters
A build process of ‘make 2>/dev/null’ is an ominous sign for any project...

------
rootVIII
really cool

------
thenewnewguy
Somewhat off-topic rant, but why do websites display long videos as GIFs? Is
there some size or compatibility benefit? Because it's really annoying to not
be able to pause or rewind these GIFs that take the place of where a video
would usually be.

Also, get an HTTPS certificate friend, it's 2019. Dreamhost provides LE certs
literally for free.

~~~
kick
Firefox already has the video player control interface for gifs; you just have
to right-click.

HTTPS is unnecessary for this site.

~~~
thenewnewguy
> Firefox already has the video player control interface for gifs; you just
> have to right-click.

And do what? I'm using (latest stable) FF but nothing in the right click menu
seems to allow me to control playback.

> HTTPS is unnecessary for this site.

A: HTTPS prevents your website from being defaced

B: I care about protecting the privacy of which websites and web pages I visit
- even if you don't

C: The website hosts the author's PGP key

~~~
gwd
D: The more internet data is encrypted, the less encrypted data stands out.
I.e., if you only encrypt data when it's important nobody listen, everyone can
tell when you're doing something you don't want anyone to hear. If you encrypt
data all the time, nobody can tell "secret" from "normal".

~~~
zentiggr
This! ^^^ (I know I should have something substantial to add, so: Everybody
hear this, this is the reason for HTTPS Everywhere, all communications
encrypted by default in all possible channels 24/7/365.)

------
app4soft
> as long as they support _ucontext_ and _SDL2_

Why not SDL1.x? It would be more portable,

What about Symbian support?[0]

[0] [http://anotherguest.se/](http://anotherguest.se/)

