frogger

This is a straight conversion of the original Gameduino Frogger game.

It is controlled via the four touch buttons on the right hand side.

#include <EEPROM.h>
#include <SPI.h>
#include <GD2.h>

#include "frogger_assets.h"

#define CONTROL_LEFT  1
#define CONTROL_RIGHT 2
#define CONTROL_UP    4
#define CONTROL_DOWN  8

class Controller {
  public:
    void begin() {
      prev = 0;
    }
    byte read() {
      byte r = GD.inputs.tag & (CONTROL_LEFT | CONTROL_RIGHT | CONTROL_UP | CONTROL_DOWN);
      byte edge = r & ~prev;
      prev = r;
      return edge;
    }
  private:
    byte prev;
};

static Controller Control;

static void draw_score(byte x, byte y, long n)
{
  GD.cmd_number(8 * x, 8 * y, FONT_HANDLE, 5, n);
}

// Game variables
static unsigned int t;
static int frogx, frogy;    // screen position
static int leaping;         // 0 means not leaping, 1-8 animates the leap
static int frogdir;         // while leaping, which direction is the leap?
static int frogface;        // which way is the frog facing, in furmans for CMD_ROTATE
static int dying;           // 0 means not dying, 1-64 animation counter
static long score;
static long hiscore;
static byte lives;
static byte done[5];
static byte homes[5] = { 24, 72, 120, 168, 216 };
static int time;

void frog_start()
{
  frogx = 120;
  frogy = 232;
  leaping = 0;
  frogdir = 0;
  frogface = 0x0000;
  dying = 0;
  time = 120 << 7;
}

void level_start()
{
  for (byte i = 0; i < 5; i++)
    done[i] = 0;
}

void game_start()
{
  lives = 4;
  score = 0;
}

void game_setup()
{
  Control.begin();
  game_start();
  level_start();
  frog_start();
}


static void sprite(byte x, byte y, byte anim, uint16_t rot = 0xffff)
{
  x -= 16;
  y -= 8;
  if (rot != 0xffff) {
    GD.cmd_loadidentity();
    GD.cmd_translate(F16(8),F16(8));
    GD.cmd_rotate(rot);
    GD.cmd_translate(F16(-8),F16(-8));
    GD.cmd_setmatrix();
  }
  if (x > 224) {
    GD.Cell(anim);
    GD.Vertex2f(16 * (x - 256), 16 * y);
  } else {
    GD.Vertex2ii(x, y, SPRITES_HANDLE, anim);
  }
}

static void turtle3(byte x, byte y)
{
  byte anim = 50 + ((t / 32) % 3);
  sprite(x, y, anim);
  sprite(x + 16, y, anim);
  sprite(x + 32, y, anim);
}

static void turtle2(byte x, byte y)
{
  byte anim = 50 + ((t / 32) % 3);
  sprite(x, y, anim);
  sprite(x + 16, y, anim);
}

void log1(byte x, byte y)
{
  sprite(x, y,      86);
  sprite(x + 16, y, 87);
  sprite(x + 32, y, 88);
}

void log(byte length, byte x, byte y)
{
  sprite(x, y,      86);
  while (length--) {
    x += 16;
    sprite(x, y, 87);
  }
  sprite(x + 16, y, 88);
}

static int riverat(byte y, uint16_t tt)
{
  switch (y) {
  case 120: return -tt;
  case 104: return tt;
  case 88:  return 5 * tt / 4;
  case 72:  return -tt / 2;
  case 56:  return tt / 2;
  }
}

static void squarewave(uint16_t freq, byte amp)
{
  GD.wr(REG_VOL_SOUND, amp);
  GD.wr16(REG_SOUND, (freq << 8) | 0x01);
  GD.wr(REG_PLAY, 1);
}

static void sound()
{
  byte note;

  if (dying) { 
    note = 84 - (dying / 2);
    squarewave(note, 100);
  } else if (leaping) {
    if (leaping & 1)
      note = 60 + leaping;
    else
      note = 72 + leaping;
    squarewave(note, 100);
  } else {
    squarewave(0, 0);  // silence
  }
}

static void rotate_around(int x, int y, int a)
{
    GD.cmd_loadidentity();
    GD.cmd_translate(F16(x),F16(y));
    GD.cmd_rotate(a);
    GD.cmd_translate(F16(-x),F16(-y));
    GD.cmd_setmatrix();
}

void game_over()
{
  GD.wr(REG_VOL_SOUND, 0);
  for (byte i = 0; i < 60; i++) {
    GD.Clear();

    // Draw "F R O G G E R" using the sprites 90-94

    int x = 160, y = 50, space = 24;
    GD.Begin(BITMAPS);
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 90); x += space;   // F
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 91); x += space;   // R
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 92); x += space;   // O
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 93); x += space;   // G
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 93); x += space;   // G
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 94); x += space;   // E
    GD.Vertex2ii(x, y, SPRITES_HANDLE, 91); x += space;   // R

    GD.cmd_text(240, 136, FONT_HANDLE, OPT_CENTER, "GAME OVER");
    if (i == 59)
      GD.cmd_text(240, 200, FONT_HANDLE, OPT_CENTER, "PRESS TO PLAY");
    GD.swap();
  }
  while (GD.inputs.x == -32768)
    GD.get_inputs();
  while (GD.inputs.x != -32768)
    GD.get_inputs();
}

void loop()
{
  static uint32_t counter;
  static int prevt;

  GD.Clear();
  GD.Tag(1);
  GD.BitmapHandle(SPRITES_HANDLE);
  GD.SaveContext();
  GD.ScissorSize(224, 256);
  GD.Begin(BITMAPS);
  GD.Vertex2ii(0, 0, BACKGROUND_HANDLE, 0);   // Background bitmap

  GD.wr(REG_TAG_X, frogx - 8);
  GD.wr(REG_TAG_Y, frogy);

  GD.Tag(2);
  GD.AlphaFunc(GREATER, 0); // on road, don't tag transparent pixels

  // Completed homes
  for (byte i = 0; i < 5; i++) {
    if (done[i])
      sprite(homes[i], 40, 63);
  }

  // Yellow cars
  sprite(-t,       216, 3);
  sprite(-t + 128, 216, 3);

  // Dozers
  sprite(t, 200, 4);
  sprite(t + 50, 200, 4);
  sprite(t + 150, 200, 4);

  // Purple cars
  sprite(-t,       184, 7);
  sprite(-t + 75,  184, 7);
  sprite(-t + 150, 184, 7);

  // Green and white racecars
  sprite(2 * t,    168, 8);

  // Trucks
  sprite(-t/2,       152, 5);
  sprite(-t/2 + 16,  152, 6);
  sprite(-t/2 + 100, 152, 5);
  sprite(-t/2 + 116, 152, 6);

  GD.AlphaFunc(GREATER, 0); // on river, tag transparent pixels

  // Turtles
  for (int i = 0; i < 256; i += 64)
    turtle3(riverat(120, t) + i, 120);

  // Short logs
  for (int i = 0; i < 240; i += 80)
    log(1, riverat(104, t) + i, 104);

  // Long logs
  for (int i = 0; i < 256; i += 128)
    log(5, riverat(88, t) + i, 88);

  // Turtles again, but slower
  for (int i = 0; i < 250; i += 50)
    turtle2(riverat(72, t) + i, 72);

  // Top logs
  for (int i = 0; i < 210; i += 70)
    log(2, riverat(56, t) + i, 56);

  // The frog himself, or his death animation

  GD.TagMask(0);

  if (!dying) {
    static byte frog_anim[] = {2, 1, 0, 0, 2};
    sprite(frogx, frogy, frog_anim[leaping / 2], frogface);
  } else {
    static byte die_anim[] = {31, 32, 33, 30};
    sprite(frogx, frogy, die_anim[dying / 16], frogface);
  }

  prevt = t;
  t++;
  time = max(0, time - 1);
  if ((time == 0) && (dying == 0))
    dying = 1;
 
  // Draw 'time remaining' by clearing a black rectangle
  {
    byte tw = 120 - (time >> 7);
    byte tx = 72;
    GD.SaveContext();
    GD.ScissorXY(tx, 248);
    GD.ScissorSize(tw, 8);
    GD.Clear();
    GD.RestoreContext();
  }

  GD.TagMask(1);

  // player control.  If button pressed, start the 'leaping' counter
  byte con = Control.read();
  if (!dying && (leaping == 0) && con) {
    frogdir = con;
    leaping = 1;
    score += 10;
  } else if (leaping > 0) {
    if (leaping <= 8) {
      if (frogdir == CONTROL_LEFT) {
        frogx -= 2;
        frogface = 0xc000;
      } if (frogdir == CONTROL_RIGHT) {
        frogx += 2;
        frogface = 0x4000;
      } if (frogdir == CONTROL_UP) {
        frogy -= 2;
        frogface = 0x0000;
      } if (frogdir == CONTROL_DOWN) {
        frogy += 2;
        frogface = 0x8000;
      }
      leaping++;
    } else {
      leaping = 0;
    }
  }

  GD.RestoreContext();
  GD.SaveContext();
  GD.Begin(BITMAPS);

#define PADX(x) (480 + (x - 3) * 48)
#define PADY(y) (272 + (y - 3) * 48)

  GD.Tag(CONTROL_RIGHT);
  GD.Vertex2ii(PADX(2), PADY(1), ARROW_HANDLE, 0);
  rotate_around(24, 24, 3 * 0x4000);

  GD.Tag(CONTROL_UP);
  GD.Vertex2ii(PADX(1), PADY(0), ARROW_HANDLE, 0);
  rotate_around(24, 24, 2 * 0x4000);

  GD.Tag(CONTROL_LEFT);
  GD.Vertex2ii(PADX(0), PADY(1), ARROW_HANDLE, 0);
  rotate_around(24, 24, 1 * 0x4000);

  GD.Tag(CONTROL_DOWN);
  GD.Vertex2ii(PADX(1), PADY(2), ARROW_HANDLE, 0);
  GD.RestoreContext();

  GD.ColorRGB(255, 85, 0);
  draw_score(3, 1, score);
  draw_score(11, 1, hiscore);

  GD.ColorRGB(255, 255, 255);
  for (byte i = 0; i < lives; i++)
    GD.Vertex2ii(8 * i, 30 * 8, LIFE_HANDLE, 0);

  // for (byte i = 0; i < 16; i++)
  //  GD.wr(atxy(i, 30), (i < lives) ? BG_LIFE : BG_BLACK);

  GD.swap();

  GD.get_inputs();
  byte tag = GD.rd(REG_TAG);
  byte touching = (tag == 2);

  if (dying) {
    if (++dying == 64) {
      if (--lives == 0 || time == 0) {
        game_over();
        game_start();
        level_start();
      }
      frog_start();
    }
  }
  else if (frogx < 8 || frogx > 224) {
    dying = 1;
  }
  else if (frogy >= 136) {    // road section
    // if touching something, frog dies
    if (tag == 2)
      dying = 1;
  }
  else if (frogy > 40) {      // river section
    if (!leaping) {
      // if touching something, frog is safe
      if (tag != 1) {
        // move frog according to lane speed
        int oldx = riverat(frogy, prevt);
        int newx = riverat(frogy, t);
        int river_velocity = newx - oldx;
        frogx += river_velocity;
      } else {
        dying = 1;
      }
    }
  }
  else 
  {                      // riverbank section
    if (!leaping) {
      byte landed = 0;
      for (byte i = 0; i < 5; i ++) {
        if (!done[i] && abs(homes[i] - frogx) < 4) {
          done[i] = 1;
          landed = 1;
          score += 10;
        }
      }
      if (landed) {
        if (done[0] && done[1] && done[2] && done[3] && done[4])
          level_start();
        frog_start();
      } else // if frog did not land in a home, die!
        dying = 1;
    }
  }
  sound();
  hiscore = max(score, hiscore);
}

void setup()
{
  GD.begin();
  LOAD_ASSETS();
  game_setup();
}