Thing-a-day, Day 12: Snake game

I re-purposed the display I made earlier to act as a snake game. It’s not perfect but it’s a good start. Code is after the break. Photos here.

/* Snake Game for Arduino
 *
 * By Matt Mets, completed in 2008
 *
 * This code is released into the public domain.  Attribution is appreciated.
 */
 
/***** Board setup ************************************************************************/
 
/* Note that the data and row pins are accessed using the PORTB and PORTBD macros, so they are
 * not defined here.
 */
int rowEnable = 9;
int redClock = 10;
int greenClock = 11;
int blueClock = 12;
 
int buttonN = 7;               // Input buttons
int buttonS = 8;
int buttonE = 14;
int buttonW = 15;
 
#define BOARD_SIZE    7        // board dimension per side (so 7x7 array)
 
#define MAX_SNAKE_SIZE 20      // Maximum length our snake can grow to
#define APPLES_BEFORE_GROW 4   // Number of apples the snake must eat before growing
#define START_SPEED  60        // Starting speed, in screen refresh times per turn
 
 
/***** Type Defines ***********************************************************************/
typedef struct row_data_t {
  byte red;
  byte green;
  byte blue;
} row_data_t;
 
typedef struct coord_t {
  byte x;
  byte y;
} coord_t;
 
enum colors_t {
  COLOR_BLACK,
  COLOR_RED,
  COLOR_GREEN,
  COLOR_BLUE,
  COLOR_YELLOW,
  COLOR_CYAN,
  COLOR_PURPLE,
  COLOR_WHITE
};
 
enum direction_t {
  DIR_NORTH,
  DIR_EAST,
  DIR_SOUTH,
  DIR_WEST
} direction_t;
 
/***** Library functions ******************************************************************/
 
/* Store the data byte in a color bank register */
int writeColor(byte data, int clockPort)
{
  PORTD = data;
  digitalWrite(clockPort, LOW);
  digitalWrite(clockPort, HIGH);
 
  return 0;
}
 
/* Cause a row to be displayed by writing its address to the DEMUX, then strobing the enable
 * line high for a short time.
 */
int strobeRow(byte row)
{
  PORTD = row << 4;
  digitalWrite(rowEnable, HIGH);
  delay(1);
  digitalWrite(rowEnable, LOW);
 
  return 0;
}
 
/* Draw an entire page, by drawing each row in succession. */
int writePage(struct row_data_t* page)
{
  int i;
  for(i = 0; i < 7; i++)
  {
    // write each color, then hold the row high for a small time to show it
    writeColor(page[i].red,   redClock);
    writeColor(page[i].green, greenClock);
    writeColor(page[i].blue,  blueClock);
    strobeRow(i);
  }
 
  return 0;
}
 
/* Set a pixel in an image page to a given color */
int setPixel(row_data_t* page, struct coord_t coord, int color)
{
  if(color == COLOR_RED || color == COLOR_YELLOW ||
     color == COLOR_PURPLE || color == COLOR_WHITE) {
    page[coord.y].red |= (1 << coord.x);          // Set the bit
  } else {
    page[coord.y].red &= ~(1 << coord.x);         // Clear the bit
  }
 
  if(color == COLOR_GREEN || color == COLOR_CYAN ||
     color == COLOR_YELLOW || color == COLOR_WHITE) {
    page[coord.y].green |= (1 << coord.x);          // Set the bit
  } else {
    page[coord.y].green &= ~(1 << coord.x);         // Clear the bit
  }
 
  if(color == COLOR_BLUE || color == COLOR_PURPLE ||
     color == COLOR_CYAN|| color == COLOR_WHITE) {
    page[coord.y].blue |= (1 << coord.x);          // Set the bit
  } else {
    page[coord.y].blue &= ~(1 << coord.x);         // Clear the bit
  }  
  return 0;
}
 
/* Return the color of a pixel at a given coordinate */
int getPixel(row_data_t* page, struct coord_t coord)
{
  // Look for red
  if(page[coord.y].red & (1 << coord.x)) {
    if(page[coord.y].green & (1 << coord.x)) {
      if(page[coord.y].blue & (1 << coord.x))
        return COLOR_WHITE;
      return COLOR_YELLOW;
    } else {
      if(page[coord.y].blue & (1 << coord.x))
        return COLOR_PURPLE;
      return COLOR_RED;
    }
  } else {
    if(page[coord.y].green & (1 << coord.x)) {
      if(page[coord.y].blue & (1 << coord.x))
        return COLOR_CYAN;
      return COLOR_GREEN;
    } else {
      if(page[coord.y].blue & (1 << coord.x))
        return COLOR_BLUE;
      return COLOR_BLACK;
    }    
 
  }
}
 
 
 
/* Snake Game */
void snakeGame()
{
  int i;
  int snakeLength = 3;                 // Length of our snake; this grows for every x 'apples' eaten
  coord_t snakeBody[MAX_SNAKE_SIZE];   // Table of each piece of the snakes body
  int snakeAlive = 1;                  // The state of our snake
  int snakeDirection = DIR_EAST;       // The direction the snake is heading
  coord_t applePosition;               // Location of the apple
  int applesEaten = 0;                 // Number of apples that have been eaten
  int gameSpeed = START_SPEED;         // Speed of the game (in ms per turn)
 
  row_data_t gamePage[BOARD_SIZE];     // The page to draw each frame of the game
 
  // Put the snake in an initial position
  snakeBody[0].x = 2; snakeBody[0].y = 4;
  snakeBody[1].x = 1; snakeBody[1].y = 4;
  snakeBody[2].x = 0; snakeBody[2].y = 4;
 
  // Put the apple in an initial position (this could collide with the snake!)
  applePosition.x = random(0, 6);
  applePosition.y = random(0, 6);
 
  /* Game loop */
  while(snakeAlive)
  {
    /*** Draw the screen ***/
      /* clear the frame */
      memset(gamePage, 0, sizeof(row_data_t)*BOARD_SIZE);    // Clear the screen
 
      /* paint the snake */
      for(i = 0; i < snakeLength; i++)
        setPixel(gamePage, snakeBody[i], COLOR_GREEN);
 
      /* paint the apple */
      setPixel(gamePage, applePosition, COLOR_RED);
 
      /* draw the screen x times, watching for a change in input */
      for(i = 0; i < gameSpeed; i++) {
        writePage(gamePage);
 
        /* Look for change in input- if button pressed, update direction.
         * If more than one button pressed, the last one counts. */
        if(digitalRead(buttonN) == HIGH)
          snakeDirection = DIR_NORTH;
        if(digitalRead(buttonS) == HIGH)
           snakeDirection = DIR_SOUTH;
        if(digitalRead(buttonE) == HIGH)
          snakeDirection = DIR_EAST;
        if(digitalRead(buttonW) == HIGH)
          snakeDirection = DIR_WEST; 
      }
 
    /*** Update the game state ***/
      /* update snake position */
      for(i = snakeLength-1; i >= 0; i--)    // Push the current position back one
        snakeBody[i+1] = snakeBody[i];    // (this causes the end of the tail to dissapear)
      switch(snakeDirection)              // Update the head (this causes the snake to move forward)
      {
        case DIR_EAST:
          snakeBody[0].x = (snakeBody[0].x + 1) % BOARD_SIZE;
          break;
        case DIR_WEST:
          snakeBody[0].x = (snakeBody[0].x + BOARD_SIZE - 1) % BOARD_SIZE;
          break;
        case DIR_NORTH:
          snakeBody[0].y = (snakeBody[0].y + BOARD_SIZE - 1) % BOARD_SIZE;
          break;
        case DIR_SOUTH:
          snakeBody[0].y = (snakeBody[0].y + 1) % BOARD_SIZE;
          break;
      }
 
      /* see if the snake head collided with anything */
      switch(getPixel(gamePage, snakeBody[0]))
      {
        case COLOR_RED:
          /* Snake ate an apple */
          /* increment score */
          applesEaten++;
 
          /* draw a new apple somewhere that doesnt overlap the snake */
          applePosition.x = random(0, 6);
          applePosition.y = random(0, 6);
 
          /* if x apples eaten, grow snake and speed up */
          if(applesEaten % APPLES_BEFORE_GROW == 0)
          {
            snakeLength++;
            gameSpeed -= 5;
          }
          break;
        case COLOR_GREEN:
          /* Snake hit self */
          snakeAlive = 0;
          break;
        case COLOR_YELLOW:
        /* Snake hit a barrier (note: there are no barriers) */
          snakeAlive = 0;
          break;
      }
 
  }
}
 
/* Initialize the IO ports */
void setup()
{
  DDRD = B11111111;              // data port
  DDRB = B00111111;              // row addresses, data clock
  pinMode(rowEnable, OUTPUT);
 
  pinMode(buttonN, INPUT);
  pinMode(buttonS, INPUT);
  pinMode(buttonE, INPUT);
  pinMode(buttonW, INPUT);  
}
 
/* Main loop */
void loop()
{
  /* Just play the game, then restart if the player dies. */
  snakeGame();
}
This entry was posted in tech, thingaday. Bookmark the permalink.

4 Responses to Thing-a-day, Day 12: Snake game

  1. Yaniv says:

    Can i have the electrical schematic for this?

  2. Yaniv says:

    You are right, i am using a different LED matrix, and its only one color, but its a good way to lear more about the aurduino….

  3. shubham agarwal says:

    cn i have the concept you are using in building this snake game
    how to produce a food,and how to increase the length of the snake
    i want to do with different programming
    top view

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>