John Bradnam
Published © GPL3+

Red/Green Disco Tile

A 16x16 Red/Green matrix display driven by shift registers, MOSFETs and a Arduino Pro Micro (old school electronics).

IntermediateFull instructions provided12 hours474
Red/Green Disco Tile

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
Any variant will do - I used a Arduino Pro Mini but you can use a UNO or Nano
×1
Red/Green 8x8 LED matrix display
60mmx60mm Common Anode
×1
DM13A Shift Registers with CC outputs
×2
74HC154 4 to 16 line decoder/driver
0.3in wide (narrow variant)
×1
AO3401 P-Channel MOS-FET
SOT-23 SMD package
×16
Passive Components
2 x 2K7 1/4W through hole resistors, 3 x 0.1uF 0805 SMD capacitors
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Schematic

PCB

Eagle files

Schematic & PCB in Eagle format

Code

Dance_Floor_Tile_RG.ino

C/C++
/*
The 16x16 Red-Green Dance Floor Tile

by John Bradnam

*/

#include <SPI.h>// SPI Library used to clock data out to the shift registers
#include <math.h>

#define LATCH_PIN 2    //can use any pin you want to latch the shift registers (YEL)
#define BLANK_PIN 4    // same, can use any pin you want for this, just make sure you pull up via a 1k to 5V (GRN)
#define DATA_PIN 11    // used by SPI, must be pin 11 (BRN)
#define CLOCK_PIN 13   // used by SPI, must be 13 (ORG)
#define ANODE_A 5      //74154 A Input (BLU)
#define ANODE_B 6      //74154 B Input (PUR)
#define ANODE_C 7      //74154 C Input (GRY)
#define ANODE_D 8      //74154 D Input (WHI)

#define TILE_SIZE 16			        //Number of columns or rows in Tile
#define TILE_MAX (TILE_SIZE - 1)                //Max tile index
#define LEDS_PER_ROW TILE_SIZE                  //Number of LEDS per row
#define COLOR_ARRAY_SIZE ((TILE_SIZE * TILE_SIZE) / 8)

//***variables***variables***variables***variables***variables***variables***variables***variables
//These variables are used by multiplexing and Bit Angle Modulation Code
//This is how the brightness for every LED is stored,  
//Each LED only needs a 'bit' to know if it should be ON or OFF, so 32 Bytes gives you 256 bits= 256 LEDs
//Since we are modulating the LEDs, using 4 bit resolution, each color has 4 arrays containing 256 bits each
byte red0[COLOR_ARRAY_SIZE], red1[COLOR_ARRAY_SIZE], red2[COLOR_ARRAY_SIZE], red3[COLOR_ARRAY_SIZE];
byte blue0[COLOR_ARRAY_SIZE], blue1[COLOR_ARRAY_SIZE], blue2[COLOR_ARRAY_SIZE], blue3[COLOR_ARRAY_SIZE];
byte green0[COLOR_ARRAY_SIZE], green1[COLOR_ARRAY_SIZE], green2[COLOR_ARRAY_SIZE], green3[COLOR_ARRAY_SIZE];

int row=0;//keeps track of which row we are shifting data to
int anodeRow=0;//this increments through the anode rows
int BAM_Bit, BAM_Counter=0; // Bit Angle Modulation variables to keep track of things
int animation = 0; //Keeps track of animation in main loop

//****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup****setup
void setup()
{
  SPI.setBitOrder(MSBFIRST);//Most Significant Bit First
  SPI.setDataMode(SPI_MODE0);// Mode 0 Rising edge of data, keep clock low
  SPI.setClockDivider(SPI_CLOCK_DIV2);//Run the data in at 16MHz/2 - 8MHz

  //Serial.begin(115200);// if you need it?
  noInterrupts();// kill interrupts until everybody is set up

  //We use Timer 1 to refresh the cube
  TCCR1A = B00000000;//Register A all 0's since we're not toggling any pins
  TCCR1B = B00001011;//bit 3 set to place in CTC mode, will call an interrupt on a counter match
  //bits 0 and 1 are set to divide the clock by 64, so 16MHz/64=250kHz
  TIMSK1 = B00000010;//bit 1 set to call the interrupt on an OCR1A match
  OCR1A=15; // you can play with this, but I set it to 30, which means:
  //our clock runs at 250kHz, which is 1/250kHz = 4us
  //with OCR1A set to 30, this means the interrupt will be called every (30+1)x4us=124us, 
  // which gives a multiplex frequency of about 8kHz

  //finally set up the Outputs
  pinMode(LATCH_PIN, OUTPUT);//Latch
  pinMode(DATA_PIN, OUTPUT);//MOSI DATA
  pinMode(CLOCK_PIN, OUTPUT);//SPI Clock
  pinMode(ANODE_A, OUTPUT);//74154 A Input
  pinMode(ANODE_B, OUTPUT);//74154 B Input
  pinMode(ANODE_C, OUTPUT);//74154 C Input
  pinMode(ANODE_D, OUTPUT);//74154 D Input
  digitalWrite(ANODE_A, LOW);
  digitalWrite(ANODE_B, LOW);
  digitalWrite(ANODE_C, LOW);
  digitalWrite(ANODE_D, LOW);
  
  //pinMode(BLANK_PIN, OUTPUT);//Output Enable  important to do this last, so LEDs do not flash on boot up
  SPI.begin();//start up the SPI library
  interrupts();//let the show begin, this lets the multiplexing start
  
  Serial.begin(9600);           // set up Serial library at 9600 bps
  Serial.println("16x16 Matrix");
  randomSeed(micros());
}

//***start loop***start loop***start loop***start loop***start loop***start loop***start loop***start loop***start loop
void loop()
{
  //Each animation located in a sub routine
  // To control an LED, you simply:
  // LED(row you want 0-TILE_MAX,  column you want 0-TILE_MAX, red brighness 0-15, green brighness 0-15, blue brighness 0-15);

  //test_leds();
    clean();
    animation = animation + 1;
    switch (animation) {
      case 1: drawParticles(10); break;
      case 2: drawSpiral(10); break;
      case 3: drawHatch(10); break;
      case 4: playGameOfLife(30); break;
      case 5: playSnake(60); break;
      case 6: randomColor(10); break;
      case 7: squareInOut(20); break;
      case 8: spiralOut(20); break;
      case 99: animation = 0; break;
    }
}

//****LED Routine****LED Routine****LED Routine****LED Routine
void LED(int row, int column, byte red, byte green, byte blue)
{ 
  //This is where it all starts
  //This routine is how LEDs are updated, with the inputs for the LED location and its R G and B brightness levels

  // First, check and make sure nothing went beyond the limits, just clamp things at either 0 or 7 for location, and 0 or 15 for brightness
  row = constrain(row, 0, TILE_MAX);
  column = constrain(column, 0, TILE_MAX);
  red = constrain(red, 0, 15);
  green = constrain(green, 0, 15);
  blue = constrain(blue, 0, 15);

  // Get the LED number 0 - 511
  int wholebyte = ((TILE_MAX - column) * TILE_SIZE) + (row);
  // Get the index into the array. Each indexed location holds one byte or 8 bits;
  int whichbyte = int(wholebyte / 8);
  int whichbit = (wholebyte & 7);
  //swap high low byte
  whichbyte = (whichbyte ^ 1);
  
  //This will all make sense in a sec

  //This is 4 bit color resolution, so each color contains x4 64 byte arrays, explanation below:
  bitWrite(red0[whichbyte], whichbit, bitRead(red, 0));
  bitWrite(red1[whichbyte], whichbit, bitRead(red, 1));
  bitWrite(red2[whichbyte], whichbit, bitRead(red, 2)); 
  bitWrite(red3[whichbyte], whichbit, bitRead(red, 3)); 

  bitWrite(green0[whichbyte], whichbit, bitRead(green, 0));
  bitWrite(green1[whichbyte], whichbit, bitRead(green, 1));
  bitWrite(green2[whichbyte], whichbit, bitRead(green, 2)); 
  bitWrite(green3[whichbyte], whichbit, bitRead(green, 3));

  bitWrite(blue0[whichbyte], whichbit, bitRead(blue, 0));
  bitWrite(blue1[whichbyte], whichbit, bitRead(blue, 1));
  bitWrite(blue2[whichbyte], whichbit, bitRead(blue, 2)); 
  bitWrite(blue3[whichbyte], whichbit, bitRead(blue, 3));

  //Are you now more confused?  You shouldn't be!  It's starting to make sense now.  Notice how each line is a bitWrite, which is,
  //bitWrite(the byte you want to write to, the bit of the byte to write, and the 0 or 1 you want to write)
  //This means that the 'whichbyte' is the byte from 0-63 in which the bit corresponding to the LED from 0-511
  //Is making sense now why we did that? taking a value from 0-511 and converting it to a value from 0-63, since each LED represents a bit in 
  //an array of 64 bytes.
  //Then next line is which bit 'wholebyte-(8*whichbyte)'  
  //This is simply taking the LED's value of 0-511 and subracting it from the BYTE its bit was located in times 8
  //Think about it, byte 63 will contain LEDs from 504 to 511, so if you took 505-(8*63), you get a 1, meaning that,
  //LED number 505 is is located in bit 1 of byte 63 in the array

  //is that it?  No, you still have to do the bitRead of the brightness 0-15 you are trying to write,
  //if you wrote a 15 to RED, all 4 arrays for that LED would have a 1 for that bit, meaning it will be on 100%
  //This is why the four arrays read 0-4 of the value entered in for RED, GREEN, and BLUE
  //hopefully this all makes some sense?

}

//***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM***MultiPlex BAM
ISR(TIMER1_COMPA_vect)
{

  //This routine is called in the background automatically at frequency set by OCR1A
  //In this code, I set OCR1A to 30, so this is called every 124us, giving each row in the cube 124us of ON time
  //There are 16 rows, so we have a maximum brightness of 1/16, since the level must turn off before the next level is turned on
  //The frequency of the multiplexing is then 124us*16=1984us, or 1/9=1984us= about 500Hz

  PORTD |= 1 << BLANK_PIN;  //The first thing we do is turn all of the LEDs OFF, by writing a 1 to the blank pin
  //Note, in my bread-boarded version, I was able to move this way down in the cube, meaning that the OFF time was minimized
  //do to signal integrity and parasitic capcitance, my rise/fall times, required all of the LEDs to first turn off, before updating
  //otherwise you get a ghosting effect on the previous level

  //This is 4 bit 'Bit angle Modulation' or BAM, There are 8 levels, so when a '1' is written to the color brightness, 
  //each level will have a chance to light up for 1 cycle, the BAM bit keeps track of which bit we are modulating out of the 4 bits
  //Bam counter is the cycle count, meaning as we light up each level, we increment the BAM_Counter
  if (BAM_Counter == (TILE_SIZE * 1))
    BAM_Bit++;
  else if (BAM_Counter == (TILE_SIZE * 3))
    BAM_Bit++;
  else if (BAM_Counter == (TILE_SIZE * 7))
    BAM_Bit++;

  BAM_Counter++;//Here is where we increment the BAM counter

  int bytes_per_row = LEDS_PER_ROW / 8;
  int shift_max = row + bytes_per_row;
  switch (BAM_Bit) {
    //The BAM bit will be a value from 0-3, and only shift out the arrays corresponding to that bit, 0-3
    //Here's how this works, each case is the bit in the Bit angle modulation from 0-4, 
    //Next, it depends on which level we're on, so the byte in the array to be written depends on which level, 
    //For 8x8x8 each level contains 64 LED, we only shift out 8 bytes for each color
    //For 4x4x4 each level contains 16 LED, we only shift out 2 bytes for each color
    case 0:
      //for (int shift_out = row; shift_out < shift_max; shift_out++)
      //  SPI.transfer(blue0[shift_out]);
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(green0[shift_out]); 
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(red0[shift_out]);
      break;
  
    case 1:
      //for (int shift_out = row; shift_out < shift_max; shift_out++)
      //  SPI.transfer(blue1[shift_out]);
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(green1[shift_out]); 
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(red1[shift_out]);
      break;
  
    case 2:
      //for (int shift_out = row; shift_out < shift_max; shift_out++)
      //  SPI.transfer(blue2[shift_out]);
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(green2[shift_out]); 
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(red2[shift_out]);
      break;
  
    case 3:
      //for (int shift_out = row; shift_out < shift_max; shift_out++)
      //  SPI.transfer(blue3[shift_out]);
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(green3[shift_out]); 
      for (int shift_out = row; shift_out < shift_max; shift_out++)
        SPI.transfer(red3[shift_out]);
  
      //Here is where the BAM_Counter is reset back to 0, it's only 4 bit, but since each cycle takes 8 counts,
      //, it goes 0 8 16 32, and when BAM_counter hits 64 we reset the BAM
      if (BAM_Counter == (TILE_SIZE * 15)) {
        BAM_Counter=0;
        BAM_Bit=0;
      }
      break;

  }//switch_case

  //SPI.transfer(anode[anodeRow]);//finally, send out the anode level byte
  digitalWrite(ANODE_A, bitRead(anodeRow, 0));
  digitalWrite(ANODE_B, bitRead(anodeRow, 1));
  digitalWrite(ANODE_C, bitRead(anodeRow, 2));
  digitalWrite(ANODE_D, bitRead(anodeRow, 3));

  PORTD |= 1<<LATCH_PIN;//Latch pin HIGH
  PORTD &= ~(1<<LATCH_PIN);//Latch pin LOW
  PORTD &= ~(1<<BLANK_PIN);//Blank pin LOW to turn on the LEDs with the new data

  anodeRow = (anodeRow + 1) & (TILE_MAX);   //inrement the anode level
  row = (row + bytes_per_row) & (COLOR_ARRAY_SIZE - 1);   //increment the level variable by 8, which is used to shift out data, since the next level would be the next 8 bytes in the arrays

  pinMode(BLANK_PIN, OUTPUT);  //moved down here so outputs are all off until the first call of this function
}


void fill(byte red, byte green, byte blue)
{
  for(int jj=0; jj < TILE_SIZE; jj++) {
    for(int kk=0; kk < TILE_SIZE; kk++) {
      LED(jj,kk,red,green,blue);
    }
  }
}

void clean()
{
  fill(0, 0, 0);
}


//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE
//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE
//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE
//*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE*+*+*+*+*+*+*+*+*+*+*+*+PUT ANIMATIONS DOWN HERE

/*---------------------------------------------------------------------------------------------------
  Test LEDS
*/  
void test_leds()
{
  
  clean();
  delay(1000);
  fill(15, 0, 0);
  delay(1000);
  fill(0, 15, 0);
  delay(1000);
  fill(15, 15, 0);
  delay(1000);
  //fill(0, 0, 15);
  //delay(1000);
  clean();
  for (int c = 1; c <= 3; c++) {
    for (int y = 0; y < TILE_SIZE; y++) {
      for (int x = 0; x < TILE_SIZE; x++) {
        LED(y,x,((c & 1) > 0) ? 15 : 0,((c & 2) > 0) ? 15 : 0,0);
        delay(40);
        LED(y,x,0,0,0);
      }
    }
  }
}

/*---------------------------------------------------------------------------------------------------
  Display random colors
*/
void randomColor(unsigned long runtimeInSeconds) 
{ 
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    for(int m=0; m<3; m++) {
      for(int k=0; k<200; k++) {
        LED(random(TILE_SIZE),random(TILE_SIZE),random(16),random(16),0);
        LED(random(TILE_SIZE),random(TILE_SIZE),random(16),0 ,random(16));
        //LED(random(TILE_SIZE),random(TILE_SIZE),0, random(16),random(16));
        //c1=random(8);
        //c2=random(8);
        //c3=random(8);
        //LED(c1,c2,c3,15,15,15);
      }
      for(int k=0; k<200; k++){
        LED(random(TILE_SIZE),random(TILE_SIZE),0,0,0);
      }
    }//m
    // Clear the cube to ready it for the next iteration. 
    clean();
    //Next iteration
    iteration++;
  }
}  

/*---------------------------------------------------------------------------------------------------
  Display squares
*/
void squareInOut(unsigned long runtimeInSeconds) 
{ 
  int halfSize = TILE_SIZE / 2;
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    for (int d = 0; d < halfSize; d++) {
      int red = ((iteration & 1) == 0) ? 15 - (d * 2) : 0;
      int green = ((iteration & 1) != 0) ? 15 - (d * 2) : 0;
      int blue = 0;
      for (int x = d; x < (TILE_SIZE - d); x++) {
        LED(d, x, red, green, blue);
        LED(TILE_SIZE - d - 1, x, red, green, blue);
      }
      for (int y = d; y < (TILE_SIZE - d); y++) {
        LED(y, d, red, green, blue);
        LED(y, TILE_SIZE - d - 1, red, green, blue);
      }
      delay(100);
    }
    for (int d = halfSize - 1; d >= 0; d--) {
      int red = 15 - ((halfSize - 1 - d) * 2);
      int green = 15 - ((halfSize - 1 - d) * 2);
      int blue = 0;
      for (int x = d; x < (TILE_SIZE - d); x++) {
        LED(d, x, red, green, blue);
        LED(TILE_SIZE - d - 1, x, red, green, blue);
      }
      for (int y = d; y < (TILE_SIZE - d); y++) {
        LED(y, d, red, green, blue);
        LED(y, TILE_SIZE - d - 1, red, green, blue);
      }
      delay(100);
    }
    //Next iteration
    iteration++;
  }
}  

/*---------------------------------------------------------------------------------------------------
  Display squares
*/

#define SPIRAL_LEFT 0
#define SPIRAL_UP 1
#define SPIRAL_RIGHT 2
#define SPIRAL_DOWN 3

void spiralOut(unsigned long runtimeInSeconds) 
{ 
  int halfSize = TILE_SIZE / 2;
  int penX = 0;
  int penY = 0;
  int newX = 0;
  int newY = 0;
  int red = 0;
  int green = 0;
  int blue = 0;
  int lineDirection = 0;
  int lineLength = 0;
  int iteration = 0;
  int period = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    lineLength = 1;
    if ((iteration & 1) == 0) {
      lineDirection = SPIRAL_LEFT;
      penX = halfSize;
      penY = halfSize;
    }
    else {
      lineDirection = SPIRAL_RIGHT;
      penX = halfSize;
      penY = halfSize - 1;
    }
    red = random(0,8);
    green = random(0,8);
    period = random(20, 100);
      
    while ((penX >= 0) && (penX < TILE_SIZE) && (penY >= 0) && (penY < TILE_SIZE)) {
      switch (lineDirection) {
        case SPIRAL_LEFT:
          newX = penX - lineLength;
          for (int x = penX; x >= newX; x--) {
            LED(penY, x, red, green, blue);
          }
          penX = newX;
          lineDirection = SPIRAL_UP;
          lineLength = lineLength + 1;
          break;
          
        case SPIRAL_UP:
          newY = penY - lineLength;
          for (int y = penY; y >= newY; y--) {
            LED(y, penX, red, green, blue);
          }
          penY = newY;
          lineDirection = SPIRAL_RIGHT;
          lineLength = lineLength + 1;
          break;
          
        case SPIRAL_RIGHT:
          newX = penX + lineLength;
          for (int x = penX; x <= newX; x++) {
            LED(penY, x, red, green, blue);
          }
          penX = newX;
          lineDirection = SPIRAL_DOWN;
          lineLength = lineLength + 1;
          break;
          
        case SPIRAL_DOWN:
          newY = penY + lineLength;
          for (int y = penY; y <= newY; y++) {
            LED(y, penX, red, green, blue);
          }
          penY = newY;
          lineDirection = SPIRAL_LEFT;
          lineLength = lineLength + 1;
          break;
      }
      delay(period);
    }
    //Next iteration
    iteration++;
  }
}  

/*---------------------------------------------------------------------------------------------------
    Snake game by John Bradnam
*/
//Global variables and definitions
#define MAX_SNAKE_SIZE 100
#define SNAKE_DOWN 0
#define SNAKE_LEFT 10
#define SNAKE_RIGHT 20
#define SNAKE_UP 30
#define PIECE_EMPTY -1
#define PIECE_SNAKE 0
#define PIECE_APPLE 1
#define APPLE_MOVE_TIMEOUT 5
bool gameOver;
bool gameResult;
int board[TILE_SIZE * TILE_SIZE];
int snakePoints;     //Point interval when snakes speeds up
int snakeDelta;      //Number of mS taken off delay each time it speeds up
int snake[100];
int snakeHead;
int snakeTail;
int snakeApple;
int snakeLength;
int snakeDirection;
int snakeSpeed;
unsigned long appleTimeout;

void playSnake(unsigned long runtimeInSeconds) 
{
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    gameOver = false;
    gameResult = false;
    snakePoints = 10;     //Point interval when snakes speeds up
    snakeDelta = 10;      //Number of mS taken off delay each time it speeds up
    snakeTail   = 0;
    snakeApple  = 78;
    snakeLength = 2;
    snakeHead   = snakeLength - 1;
    snakeDirection = SNAKE_LEFT;
    snakeSpeed  = snakePoints * snakeDelta;
    appleTimeout = 0;
    
    for (int i = 0;i < (TILE_SIZE * TILE_SIZE);i++)
      board[i] = PIECE_EMPTY;
    for (int i = 0;i < snakeLength;i++) {
      snake[i] = 136 - i;
      snakeDraw(snake[i], true);
    }
    appleDraw(snakeApple,true);
    appleTimeout = millis() + (APPLE_MOVE_TIMEOUT * 1000);
    
    //main loop
    while (!gameOver) {
      if (millis() >= appleTimeout) {
        appleMove(true);
        appleTimeout = millis() + (APPLE_MOVE_TIMEOUT * 1000);
      }
      snakeDirection = snakeGetComputerMove();
      snakeMove();
      delay(snakeSpeed); 
    }
    
    //Next iteration
    iteration++;
  }
}

//Move the snake in snakeDirection by adding a pixel to the head and removing a pixel from the tail
void snakeMove() 
{
  int newLocation = 0;
  int x  = snake[snakeHead] % TILE_SIZE;
  int y  = (int)floor(snake[snakeHead] / TILE_SIZE);
  
  switch (snakeDirection) {
    case SNAKE_LEFT: x--; break;
    case SNAKE_RIGHT: x++; break;
    case SNAKE_UP: y--; break;
    case SNAKE_DOWN: y++; break;
  }
  if ((x < 0) || (x == TILE_SIZE) || (y < 0) || (y == TILE_SIZE)) {
    //Game over - You hit the wall!
    //Serial.println("Game over - You hit the wall!");
    gameResult = false;
    gameOver = true;
  }
  else {
    newLocation  = y * TILE_SIZE + x;
    if ((board[newLocation] == PIECE_SNAKE) && (newLocation != snakeTail)) {
      //Game over - You hit yourself!
      //Serial.println("Game over - You hit yourself!");
      gameResult = false;
      gameOver = true;

    }
    else if ((board[newLocation] == PIECE_APPLE) && (snakeLength == (MAX_SNAKE_SIZE - 1))) {
      //Game over - You won!
      //Serial.println("Game over - You won!");
      gameResult = true;
      gameOver = true;
    }
    else {
      if (board[newLocation] == PIECE_APPLE) {
        snakeLength = snakeLength + 1;
        snakeSpeed = ((int)((MAX_SNAKE_SIZE - snakeLength) / snakePoints) * snakeDelta) + snakeDelta;
      }
      else {
        snakeDraw(snake[snakeTail],false);
        snake[snakeTail] = -1;
        snakeTail = snakeTail + 1;
        if (snakeTail >= MAX_SNAKE_SIZE)
          snakeTail = 0;
      }
      snakeHead = snakeHead + 1;
      if (snakeHead >= MAX_SNAKE_SIZE)
        snakeHead = 0;
      snake[snakeHead] = newLocation;
      snakeDraw(newLocation,true);
      if (newLocation == snakeApple) {
        appleMove(false);
        appleTimeout = millis() + (APPLE_MOVE_TIMEOUT * 1000);
      }
    }
  }
}

//Show or hide snake pixel at index
void snakeDraw(int location, bool show)
{
  int x  = location % TILE_SIZE;
  int y  = (int)floor(location / TILE_SIZE);
  board[location] = (show) ? PIECE_SNAKE : PIECE_EMPTY;
  if (show)
    LED(y,x,0,15,0);
  else
    LED(y,x,0,0,0);
}

//Randomly move the apple to a location that isn't used
void appleMove(int hide) 
{
  if (hide)
    appleDraw(snakeApple,false);
  bool foundLocation = false;
  while (!foundLocation) {
    int x = random(0, TILE_SIZE);
    int y = random(0, TILE_SIZE);
    snakeApple = y * TILE_SIZE + x;
    if (board[snakeApple] == PIECE_EMPTY) {
      foundLocation = true;
      appleDraw(snakeApple, true);
    }
  }
}

void appleDraw(int location, bool show)
{
  int x  = location % TILE_SIZE;
  int y  = (int)floor(location / TILE_SIZE);
  board[location] = (show) ? PIECE_APPLE : PIECE_EMPTY;
  if (show)
    LED(y,x,15,0,0);
  else
    LED(y,x,0,0,0);
}

//Computer emulating a player
int snakeGetComputerMove()
{
  int newDirection = snakeDirection;  
  bool turn = false;
  int ax  = snakeApple % TILE_SIZE;
  int ay  = (int)floor(snakeApple / TILE_SIZE);
  int sx  = snake[snakeHead] % TILE_SIZE;
  int sy  = (int)floor(snake[snakeHead] / TILE_SIZE);
  int nx = sx;
  int ny = sy;
  switch (snakeDirection) {
    case SNAKE_LEFT: nx--; break;
    case SNAKE_RIGHT: nx++; break;
    case SNAKE_UP: ny--; break;
    case SNAKE_DOWN: ny++; break;
  }
  if ((nx < 0) || (nx == TILE_SIZE) || (ny < 0) || (ny == TILE_SIZE)) {
    //If we keep going we will hit a wall so we must turn
    turn = true;
  }
  else {
    int ni = ny * TILE_SIZE + nx;
    if (board[ni] == PIECE_SNAKE) {
      //If we keep going we will hit ourselves to se must turn
      turn = true;
    }
    else if (board[ni] == PIECE_APPLE) {
      //If we keep going, we will hit the apple so keep going
      turn = false;
    }
    else if ((snakeDirection == SNAKE_LEFT) || (snakeDirection == SNAKE_RIGHT)) {
      //Turn if we are at the same x axis as the apple
      turn = (sx == ax);
    }
    else {
      //Turn if we are at the same y axis as the apple
      turn = (sy == ay);
    }
  }
  if (turn) {
    //Aways try and turn towards the apple
    if ((snakeDirection == SNAKE_LEFT) || (snakeDirection == SNAKE_RIGHT)) {
      if ((sy < ay) || ((sy == ay) && (random(0,2) == 0))) {
        //Preferrably move down
        if (snakeTestTurn(sx, sy, SNAKE_DOWN))
          newDirection = SNAKE_DOWN;
        else if (snakeTestTurn(sx, sy, SNAKE_UP))
          newDirection = SNAKE_UP;
      }
      else {
        //Preferrably move up
        if (snakeTestTurn(sx, sy, SNAKE_UP))
          newDirection = SNAKE_UP;
        else if (snakeTestTurn(sx, sy, SNAKE_DOWN))
          newDirection = SNAKE_DOWN;
      }
    }
    else {
      if ((sx < ax) || ((sx == ax) && (random(0,2) == 0))) {
        //Preferrably move right
        if (snakeTestTurn(sx, sy, SNAKE_RIGHT))
          newDirection = SNAKE_RIGHT;
        else if (snakeTestTurn(sx, sy, SNAKE_LEFT))
          newDirection = SNAKE_LEFT;
      }
      else {
        //Preferrably move left
        if (snakeTestTurn(sx, sy, SNAKE_LEFT))
          newDirection = SNAKE_LEFT;
        else if (snakeTestTurn(sx, sy, SNAKE_RIGHT))
          newDirection = SNAKE_RIGHT;
      }
    }
  }
  return newDirection;
}
            
bool snakeTestTurn(int tx, int ty, int td)
{
  bool okToTurn = true;
  switch (td) {
    case SNAKE_LEFT: tx--; break;
    case SNAKE_RIGHT: tx++; break;
    case SNAKE_UP: ty--; break;
    case SNAKE_DOWN: ty++; break;
  }
  if ((tx < 0) || (tx > TILE_MAX) || (ty < 0) || (ty > TILE_MAX)) {
    //If we keep going we will hit a wall so we must turn
    okToTurn = false;
  }
  else {
    int ti = ty * TILE_SIZE + tx;
    if (board[ti] == PIECE_SNAKE) {
      //If we keep going we will hit ourselves to se must turn
      okToTurn = false;
    }
  }
  return okToTurn;
}
            
/*---------------------------------------------------------------------------------------------------
    Gaem of life game by John Bradnam
*/
//Global variables and definitions
#define LIFE_DENSITY 50

void playGameOfLife(unsigned long runtimeInSeconds) 
{
  int red = 15;
  int green = 0;
  int blue = 0;
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    
    //Random set start
    int i = 0;
    for (int y = 0; y < TILE_SIZE; y++) {
      for (int x = 0; x < TILE_SIZE; x++) {
        if (random(100) >= LIFE_DENSITY)
          board[i] = 0;
        else {
          board[i] = 1;
          LED(y,x,red,green,blue);
        }
        i++;
      }
    }
    
    bool allDead = false;
    while ((millis() < endTime) && (!allDead)) {
      // Birth and death cycle
      delay(100);
      allDead = true;
      i = 0;
      for (int y = 0; y < TILE_SIZE; y++) {
        for (int x = 0; x < TILE_SIZE; x++) {
          board[i] = board[i] | (board[i] << 1);
          int count = neighbours(y, x);
          if ((count == 3) && ((board[i] & 0x01) == 0)) {
            //A new cell is born
            board[i] = board[i] | 2;
            LED(y,x,red,green,blue);
            allDead = false;
          }
          else if (((count < 2) || (count > 3)) && ((board[i] & 0x01) != 0)) {
            //A cell is dies
            board[i] = board[i] & ~2;
            LED(y,x,0,0,0);
          }
          else if ((board[i] & 0x01) != 0)
            allDead = false;
          i++;
        }
      }
      //Transfer new board to old board
      for (int i = 0; i < (TILE_SIZE * TILE_SIZE); i++) {
        board[i] = board[i] >> 1;
      }
    }    
    red = 15 - red;
    green = 15 - green;
    
    //Next iteration
    iteration++;
  }
}

// This function is for the 4 laws in conway's game of life
int neighbours(int y, int x) 
{
  int yt = (((y == 0) ? TILE_SIZE : y) - 1) * TILE_SIZE;
  int yb = ((y + 1) % TILE_SIZE) * TILE_SIZE;
  y = y * TILE_SIZE;
  int xl = ((x == 0) ? TILE_SIZE : x) - 1;
  int xr = (x + 1) % TILE_SIZE;
  return (board[yt+xl] & 1) + (board[yt+x] & 1) + (board[yt+xr] & 1) + (board[y+xl] & 1) + (board[y+xr] & 1) + (board[yb+xl] & 1) + (board[yb+x] & 1) + (board[yb+xr] & 1);
}

/*------------------------------------------------------------------------------------------------------------------------------------
  Hatch
*/

void drawHatch(unsigned long runtimeInSeconds) 
{
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    for (int y = 0; y < TILE_SIZE; y = y + 2) {
      for (int x = 0; x < TILE_SIZE; x++) {
        LED(y, x, 0, 15, 0);
        delay(10);
      }
    }
    for (int x = 0; x < TILE_SIZE; x = x + 2) {
      for (int y = 0; y < TILE_SIZE; y++) {
        LED(y, x, 0, 15, 0);
        delay(10);
      }
    }
    for (int y = 1; y < TILE_SIZE; y = y + 2) {
      for (int x = 0; x < TILE_SIZE; x++) {
        if ((x & 3) != 0) {
          LED(y, x, 15, 15, 0);
        }
        delay(10);
      }
    }
    for (int x = 1; x < TILE_SIZE; x = x + 2) {
      for (int y = 0; y < TILE_SIZE; y++) {
        if ((y & 3) != 0) {
          LED(y, x, 15, 15, 0);
        }
        delay(10);
      }
    }
    for (int y = 2; y < TILE_SIZE; y = y + 4) {
      for (int x = 0; x < TILE_SIZE; x++) {
        if ((x & 3) == 2) {
          LED(y, x, 15, 0, 0);
        }
        delay(10);
      }
    }
    delay(1000);
    
    //Next iteration
    iteration++;
  }
}

/*------------------------------------------------------------------------------------------------------------------------------------
  Spiral
*/

void drawSpiral(unsigned long runtimeInSeconds) 
{
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    int halfSize = TILE_SIZE / 2;
    float spiralStep = (3 * M_PI) / 1000;
    float spiralAngle = 0;
    float spiralOffset = 0;
    for (int i = 0; i < 950; i++) {
      int x = spiralAngle * cos(spiralAngle) + halfSize;
      int y = spiralAngle * sin(spiralAngle) + halfSize;
      LED(y, x, 15, 0, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    spiralAngle = 0;
    spiralOffset = 2 * M_PI / 3;
    for (int i = 0; i < 950; i++) {
      int x = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
      int y = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
      LED(y, x, 15, 15, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    spiralAngle = 0;
    spiralOffset = (2 * M_PI / 3) * 2;
    for (int i = 0; i < 950; i++) {
      int x = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
      int y = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
      LED(y, x, 0, 15, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    delay (1000);
    
    clean();
    spiralAngle = 0;
    spiralOffset = 0;
    for (int i = 0; i < 950; i++) {
      int y = spiralAngle * cos(spiralAngle) + halfSize;
      int x = spiralAngle * sin(spiralAngle) + halfSize;
      LED(y, x, 15, 0, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    spiralAngle = 0;
    spiralOffset = 2 * M_PI / 3;
    for (int i = 0; i < 950; i++) {
      int y = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
      int x = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
      LED(y, x, 15, 15, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    spiralAngle = 0;
    spiralOffset = (2 * M_PI / 3) * 2;
    for (int i = 0; i < 950; i++) {
      int y = spiralAngle * cos(spiralAngle + spiralOffset) + halfSize;
      int x = spiralAngle * sin(spiralAngle + spiralOffset) + halfSize;
      LED(y, x, 0, 15, 0);
      spiralAngle = spiralAngle + spiralStep;
    }
    delay (1000);
    
    //Next iteration
    iteration++;
  }
}

/*------------------------------------------------------------------------------------------------------------------------------------
  Particles
  x = v*cos(a)*t
  y = v*sin(a)*t - gt  
*/

void drawParticles(unsigned long runtimeInSeconds) 
{
  #define MAX_PARTICLES 8
  int halfSize = TILE_SIZE / 2;
  int last[MAX_PARTICLES];
  int iteration = 0;
  unsigned long endTime = millis() + (runtimeInSeconds * 1000);
  while (millis() < endTime) {
    //Initialise
    clean();
    //Fire firework
    int lx = 0;
    int ly = 0;
    for (int t = 0; t < halfSize; t++) {
      if (t != 0) {
        LED(ly, lx, 0, 0, 0);
      }
      ly = TILE_SIZE - t - 1;
      lx = t;
      LED(ly, lx, 15, 15, 0);
      delay(40);
    }
    float halfGravity = 9.8 / 2;
    float timeStep = 0.01;
    float time = 0;
    for (int t = 0; t < 600; t++) {
      float timeSquared = time * time;
      float angle = 0;
      float angleStep =  2 * PI / MAX_PARTICLES;
      bool finished = true;
      for(int a = 0; a < MAX_PARTICLES; a++) {
        int red = ((a & 1) == 0) ? 15 : 0;
        int green = ((a & 1) != 0) ? 15 : 0;
        int v = halfSize;
        int x = halfSize + floor(v * cos(angle) * time);
        int y =  halfSize - 1 - floor(v * sin(angle) * time - (halfGravity * timeSquared));
        if (t != 0) {
          ly = floor(last[a] / TILE_SIZE);
          lx = last[a] % TILE_SIZE;
          LED(ly, lx, 0, 0, 0);
        }
        if ((y >= 0) && (y < TILE_SIZE) && (x >= 0) && (x < TILE_SIZE)) {
          LED(y, x, red, green, 0);
          finished = false;
        }
        last[a] = y * TILE_SIZE + x;
        angle = angle + angleStep;
      }
      time = time + timeStep;
      if (finished)
        break;
    }
    //delay (1000);
...

This file has been truncated, please download it to see its full contents.

Credits

John Bradnam
149 projects • 181 followers
Thanks to Kevin Darrah.

Comments