Implementing SNAAAKE

Implementing SNAAAKE

By Thaddaeus Frogley

Overload, 24(134):11-13, August 2016


Almost everyone knows the game Snake! Thaddaeus Frogley shares a diary of how his implementation grew over time.

Snake! Best practise...

Snake is a very old computer game, which appears to date back to an arcade game from 1976 called Blockade [ Snake ]. Some of our audience may recall playing a variant on a BBC machine or similar in the 1980s. Younger readers may have first encountered this game on an older Nokia mobile phone. You play by moving a dot (or similar shape) which gradually grows around the screen and lose when you run into yourself, edge of the screen or obstacle. There are many variants but I am used to one where ‘eating’ another object by running over it, makes the dots grow longer, thereby making the game more difficult as the snake grows.

Andy Balaam demonstrated how he'd implemented ‘Snake!’ in a variety of different languages at this year's ACCU conference, including Elm (Haskell for your browser) [ Elm ], a ZX spectrum emulator, Ruby, Python 3 with Qt5, Groovy, and Dart [ Dart ]. The idea of re-implementing something in several different ways is appealing. You learn by practising, and redoing the same thing a few times is a common learning technique. If you learn to play the piano, you practise your scales over and over. Some people try the same code kata over and over. Each time allows you to concentrate on improving at a different aspect of the task. Of course, if you implement a game, you can then play it afterwards. What could be better?

I am therefore inviting people to send their attempts at Snake!, or similar, to Overload . You had best get practising! In what follows, Thaddeus Frogley shares a diary of how his implementation in C++ using Emscripten developed. You can play it here: http://thad.frogley.info/snaaake/

Fran

My implementation of snake was a spare time project, which I worked on during the evenings after work. A day in the timeline represents anything from a few minutes to a few hours of work. Working on this, I made 77 commits over the course of 52 different days between Feb 11 and Dec 20. This is probably the equivalent of around 2 weeks worth of work at ‘full time job’ hours. Obviously, focus and flow impact productivity, so making comparisons like that is rather speculative.

The first 16 days worth of work is purely ‘tech’, none of what I’d call the ‘game’ is done until day 17, from then it’s just 10 days until the game is basically done. From then until the end it’s polish (fine tuning) and bug fixing, with no significant changes to how the game looks or feels.

That time breaks down roughly as first 30% on tech investment, then 20% on making the game and the last 50% on polish & bug fixing.

With unused source files removed, the dependency graph looks like Figure 1.

Figure 1

Development timeline

Day 1 (Tue Feb 11)
Setting up Emscripten [ Emscripten ], and getting a simple SDL [ SDL ] “hello world” program to compile and run in a browser.

Day 2 (Thu Feb 13)
Cobbled together from code copy-pasted from other projects, I set up an event loop, some math primitives, and classes for drawing simple shapes with OpenGL, to display some animated geometry to a window in the browser.

Day 3 (Sat Feb 15)
Switched from legacy immediate mode OpenGL to ES2 style rendering, using hard coded vertex and pixel shaders.

Day 4 (Mon Feb 17)
Added support for passing scale and translation through the shaders so that the shapes can be positioned on screen again. Made the shape classes hold a reference to their shader programs.

Day 5 (Tue Feb 18)
Added support for passing the colour used to draw into the shaders from the C++ code as a uniform [ OpenGL ].

Day 6 (Wed Feb 19)
Changed from passing position and scale as separate uniforms, to passing in a 4x4 matrix.

Day 7 (Thu Feb 20)
Moved all the math code copy-pasted into src/geometry on Day 2 into its own git repo, added that as a submodule in libs/geometry [ Geometry ].

Day 8 & 9 (Fri Feb 21, & Wed Feb 26)
Improvements to the matrix class interface in src/geometry to make it easier to integrate with the OpenGL code I’m writing. These changes introduce the first use of a c++11 feature, the initializer_list .

Day 10 (Sun Mar 2)
Creation of matrixes from separate translation, scale, and rotation values. The app now displays animated rotating stars and rings.

Day 11, 12, & 14 (Mon Mar 3, Sat Mar 22, & Sun Apr 27)
Improvements to the geometry code, mostly focused on making it easier to create matrixes for different uses.

Day 15 & 16 (Mon Apr 28, Tue Apr 29)
Added a ‘quad’ shape class, and set up a display grid of 84x48 to emulate the classic Nokia 3310 screen. Up until this point all the work has been preparatory, but now I have settled on making my snake clone a tribute to one of the original mobile phone versions. Using an array as a framebuffer, and then render it with single quad per pixel.

Day 17 (Wed Apr 30 )
First gameplay work: Implemented the tracking of a position, moving in a direction writing to the framebuffer, and then detecting self-collisions and resetting the game. Add to that, the display of a score on screen, and I have a minimal snake game.

Day 18 & 19 (Thu May 1, Fri May 2)
The snake’s tail now only grows when it eats a pick up. Snake wraps around the sides. Code rate of change slows as I spend more time testing the game-feel and thinking about making it fun.

Day 20 (Sun May 4)
Design a more compact score font. Each glyph in the font is defined as a simple array of 6 const char* , and the font is just an array of 10 glyphs.

Day 21 (Tue May 6)
Code and gameplay improvements around spawning food and poison pills.

Day 22 (Thu May 8)
A very important change to the keyboard event handling so players can u-turn reliably. Actions are queued, and processed one per frame. Switched to using std::mt19937 over rand() , as rand was giving inconsistent, and in some cases highly predictable, behaviour across browsers. Did code housekeeping around resetting and restarting.

Day 23 (Thu May 15)
Added ability to grow the snake by arbitrary length for each food pick up collected.

Day 24–28 (Tue May 20, Wed 21, Thurs 22 and Fri 30)
Some refactoring, followed by making the snake 2 pixels thick, instead of 1. Made the spawns fat as well. Cornering with the thick snake was surprisingly tricky to get right. Played it a lot, then fixed a subtle bug in it.

Day 29 & 30 (Sat May 31, Sun Jun 1)
Optimisation: Performance was not ideal on all browsers. Implemented pre-calculating the geometry, and then batching the draw calls. OpenGL and WebGL draw calls can be very expensive. Batching is a common solution to this problem – we gather things than can be drawn in one call and submit them together. This reduces the number of calls per frame from 4032 to 3.

Day 31 (Mon Jun 2)
Moved the text drawing code into its own header and translation unit. Added a system to remove the poison pills when the snake collects food. Definitely an improvement. Fun to play.

Day 32 (Tue Jun 3)
Added ability to pause, with on screen status display.

Day 33 (Thu Jun 12)
Added an on screen game title, and improved removal of poison pills so that single pixel pieces don’t get left behind.

Day 34 (Mon Jun 16)
Minor refactor of score tracking.

Day 35 (Wed Jun 25)
Updated build scripts, and in-game title so that the game is now called SNAAAKE.

Day 36–37 (Thu Jun 26, Wed Jul 2)
HTML wrapper work. Getting the game ready to be put online.

Day 38 (Sun Jul 6)
Double thickness snake behaviour still wasn’t quite right. Fixed a bug with edge wrapping.

Day 39 (Fri Jul 11)
Refactoring, and code tidy up, followed by restricting the spawns from appearing while the snake is growing to improve pacing.

Day 40 (Sat Jul 12)
Changed the food and poison spawning logic to keep them from spawning on top of each other. Started work on an OS X native build.

Day 41 (Sun Jul 13)Working on OS X project, icon, etc to have a native OS X binary distributable.

Day 42 & 43 (Mon Jul 14, Tue Jul 15)
Added on-screen (in-game) instructions, CMD-Q handler to quit in the OS X build, then added a ‘favicon’ and download link, for the OS X version, to the HTML5 container.

Day 44 (Wed Jul 16)
Refactoring, bug fixing, and presentation polish including the player-praise header text.

Day 45 (Fri Jul 18)
Refactoring, bug fixing, and presentation polish.

Day 46 (Mon Jul 21)
Added centre alignment support to the text rendering code to further improve the presentation of instructions.

Day 47 (Fri Jul 25)
Refactoring: Moved non-game event handling out of the game specific event handler.

Day 48 (Sat Aug 2)
Refactoring: Separated ‘Controller’ from ‘Model’, then implemented an AI ‘Controller’ to control the snake for an attract mode, and improved the instructions.

Day 49 (Sun Aug 3)
Bug fix: Prevented the AI from taking high scores.

Day 50 (Mon Aug 4)
Improved the attract mode AI by making it more natural/fallible and less predictable.

Day 51 (Tue Sep 2)
Optimised the thumbnail for the website (optipng).

Day 52 (Sat Dec 20)
Added music (web version only).

Addendum, 2 years later …. Day 53 (Sat May 11)
Finished and committed high score persistence and unused source file removal.

Conclusion

There is no conclusion. Spare time projects don’t end, they just slow down until movement is undetectable. A space to practise and experiment is worthwhile for all programmers. But remember

  • what’s good for your personal projects isn’t the same as what’s good for team projects, and
  • what’s good for small projects isn’t the same as what’s good for large projects.

References

[Dart] https://www.dartlang.org/

[Elm] http://elm-lang.org/

[Emscripten] https://github.com/kripken/emscripten

[Geometry] https://github.com/codemonkey-uk/geometry

[OpenGL] https://www.opengl.org/wiki/Uniform_(GLSL)

[SDL] https://www.libsdl.org

[Snake] https://en.wikipedia.org/wiki/Snake_(video_game)

The source code, with full revision history, can be found on github: https://github.com/codemonkey-uk/snaaake






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.