Towards the end of my previous article I launched an open challenge to build games for the Arduino Game Console. Feeling some of that weight on my shoulders, I went ahead and built the first game for it. The most emblematic game in the history is by far Space Invaders. It took Tomohiro Nishikado one year of Japanese blood, sweat and no tears to build on hardware he put together himself. That was long before Bjarne gave us developing elegance embodied in C++, prior to any IDE or Arduinos.
Space Invaders game - on the first revision of the Arduino Game Consoleis a 2D fixed shooter. You can move the ship left or right using the joystick and fire laser beams at enemy ships using the A button. The invaders are getting increasingly faster with every new level, putting up quite a fight towards the end of the game. You earn 10 points for each shot-down invader and loose 10 when you hit your own shields. The highest ever score is remembered in the game's allocated EEPROM space. One life is lost every time you're hit and the game is over when run out of lives or the invaders reach the base.
Simple gameplay for a simple game!
revolves around the SpaceInvaders class, an implementation of GameConsole, which has 16 instances of the Invader class, three Shields and one Ship object. LaserBeams are fired back and forth causing occasional Explosion on both sides. The main point of interest, from the software perspective, is the SpaceInvaders::Execute() method. It harbors a finite state machine that makes the game happen.
States 1 to 6 are looped through every 40 milliseconds. They handle input read, object move, object collision detection, display and wait. The rest of the states deal with initializations, level increments, pause, game over and game complete. Because of the shear size of the sketch, it will not be fully listed. However, here are some of the secondary classes mentioned before.
struct Point { unsigned char m_x, m_y; void Init(unsigned char x, unsigned char y) { // m_x = x; m_y = y; } }; struct Sane { signed char m_integrity; void Init(unsigned char integrity) { // m_integrity = integrity; } bool IsAlive() { // return (m_integrity > 0); } bool JustDied() { // return ((m_integrity <= 0) && (m_integrity > -8)); } void Hit(byte degree) { // m_integrity -= degree; } }; struct SanePoint: public Sane, public Point { void Init(unsigned char x, unsigned char y) { // Point::Init(x, y); Sane::Init(100); } }; struct Invader : public SanePoint { static const byte THICKNESS = 8; static const char MASK[4]; }; const char Invader::MASK[4] = {0x11, 0x7A, 0xB4, 0x3C}; struct Ship : public SanePoint { static const byte THICKNESS = 8; static const char MASK[4]; }; const char Ship::MASK[4] = {0x80, 0xC0, 0xC0, 0xF0}; struct Explosion { static const byte THICKNESS = 7; static const char MASK[4]; }; const char Explosion::MASK[4] = {0x49, 0x2A, 0x14, 0x63}; struct Shield : public SanePoint { static const byte THICKNESS = 10; bool IsStanding() { // return IsAlive(); } }; struct LaserBeam : public Point { /// default constructor LaserBeam() { // initialize the y coordinate outside visible frame m_y = 255; } /// indicates that beam is visible bool IsVisible() { // return (m_y < Display::HEIGHT - 11); } };
Observe the <X>::MASK constants in each class. They're bitmaps for the left-most half of each image representing the corresponding class. Rendering the right-most half is made using the horizontally mirrored version of the same bitmap. That's a neat trick that can greatly reduce the memory needs of your games.
You can find the complete and up-to-date sketch of the game on Github.
for the game idea goes to Nishikado Tomohiro. I did my best, within a limited time-frame, to recreate the feel of the original, not the entire game per-se. I hope you will enjoy it as-is, improve it or start your awesome new game based on it.
I would love to hear about it if you do.