GYRO & VOICE ARDUIGAMES

This gaming box is made from Arduino Uno R3, LoLShield, MPU6050 and Microphone module.

  • With LoLShield, we can show a message/scrolling message or animation picture like: plasma effect, sinewave effect…
  • With MPU6050, we can read value from accelerometer and gyroscope to control movement for simple games like: Pong, Tetris, Invader…And we also make a scrolling message with scrolling speed base on accelerometer.
  • With microphone module & 3.5mm audio jack, we can use this game box as spectrum analyzer or sound meter.

Let see video:

https://youtu.be/lBQ7bYUbpdQ

Step 1: BILL OF MATERIAL

Main components list:

For LoLShiled, I did it by myself. It is not easy for etching a double sided copper clad pcb board. You can refer to PCB design at: https://github.com/jprodgers/LoLshield & make one for yourself.

ou can see LoLShield’s video for first testing & spectrum analyzer testing:

https://youtu.be/m6P1OqHqMoE

Step 2: GAME BOX SCHEMATIC

The LoL Shield is a charlieplexing LED matrix for the Arduino and this design DOESN’T include current limiting resistors. The LEDs are individually addressable, so you can use it to display anything in a 9×14 grid. Scroll text, play games, display images, or anything else you want to do.

The LoL Shield leaves analog pins A0 to A5 free for inputs. For detail, let see picture below for LoLShield pin usage:

From schematic, we can see:

  • A0: connect to 2 position toggle switch to select analog input from audio jack or microphone module.
  • A1: connect to push button.
  • A2 & A3: connect to 3 position toggle switch for selecting games/ animations mode.
  • A4 & A5: connect to SDA & SCL pins of MPU6050. (Or we can use DS3231 – Real Time Clock – as optional).

Step 3: ADAPTER SHIELD

This adapter board is used for connecting between Arduino Uno and LoLShield. On the adapter board, we solder all components: button, switches, MPU6050, Microphone module KY-038, audio jack 3.5mm, resistors. At bottom side, we solder 2 row male headers connecting to Arduino & top side, we use 2 row female headers connecting to LoLShield.

  • For 1st version, I didn’t use Microphone module so analog input pin was directly connected to audio jack. You can see some project pictures:

For 2nd version, I added Microphone module and 2 position toggle switch. So analog input A0 can be selected through toggle switch between audio jack or microphone module.

Step 4: GAME BOX

Steps below show how to build a game box (1st version).

  • Game box components

Step 5: GAME BOX – UPDATE

I covered the black silk on the led screen to reduce the glare (glared filter) when it was recorded by a camera. And I also added Microphone module and 2 position toggle switch at backside as explained before. So analog input A0 can be selected through toggle switch between audio jack or microphone module.

Step 6: ARDUINO LIBRARY

Libraries include:

  1. Original LoLShield library:
    • Charliplexing
    • Figure
    • Font
    • Myfont
    • LoLShield_Tetris
  2. Fix_FFT library: The fix_fft library perform forward/inverse fast Fourier transform. In my case, it is used for spectrum analyzer application.
  3. LOLDraw libraryThe LOLDraw library provides easy-to-use functions for drawing geometrical figures.

For full library folder, download HERE.

Step 7: ARDUINO PROGRAM

// THIS PROGRAM IS REFEREED FROM: <a href="https://github.com/jprodgers/LoLshield">https://github.com/jprodgers/LoLshield</a><br>// THANK TO JPRODGERS
#include "Charliplexing.h" //LOL library
#include "Myfont.h"
#include "Figure.h"
#include "Font.h"
#include "Arduino.h"
#include "fix_fft.h"
#include "LOLDraw.h"
#include "LoLShield_Tetris.h"
#include <Wire.h>

char * test = " WELCOME TO ARDUIGAMES! ";

static const char autotest[]= "HELLO WORLD!   ";

int len; // length of this string

/** The current level. */
int level;

/** The score of the user (number of points = speed of each killed ennemy - number of ennemies missed) */
int score;

/** Number of lines cleared at current level. */
byte linesCleared;

/** The game grid size. */
const uint8_t GRID_HEIGHT = 14;
const uint8_t GRID_WIDTH = 9;

boolean playGrid[GRID_HEIGHT][GRID_WIDTH];

const piece_t pieces[7] = {
  {{
    // The single view of the square piece :
    // 00
    // 00
    {{{1,1}, {2,1}, {1,2}, {2,2}}},
    {{{1,1}, {2,1}, {1,2}, {2,2}}},
    {{{1,1}, {2,1}, {1,2}, {2,2}}},
    {{{1,1}, {2,1}, {1,2}, {2,2}}},
  }},
  {{
    // The two views of the bar piece :
    // 0000
    {{{0,1}, {1,1}, {2,1}, {3,1}}},
    {{{2,0}, {2,1}, {2,2}, {2,3}}},
    {{{0,1}, {1,1}, {2,1}, {3,1}}},
    {{{2,0}, {2,1}, {2,2}, {2,3}}},
  }},
  {{
    // The two views of the first S :
    // 00
    //  00
    {{{0,1}, {1,1}, {1,2}, {2,2}}},
    {{{1,1}, {1,2}, {2,0}, {2,1}}},
    {{{0,1}, {1,1}, {1,2}, {2,2}}},
    {{{1,1}, {1,2}, {2,0}, {2,1}}},
  }},
  {{
    // The two views of the second S :
    //  00
    // 00
    {{{0,2}, {1,1}, {1,2}, {2,1}}},
    {{{0,0}, {0,1}, {1,1}, {1,2}}},
    {{{0,2}, {1,1}, {1,2}, {2,1}}},
    {{{0,0}, {0,1}, {1,1}, {1,2}}},
  }},
  {{
    // The four views of the first L :
    // 000
    // 0
    {{{0,1}, {0,2}, {1,1}, {2,1}}},
    {{{0,0}, {1,0}, {1,1}, {1,2}}},
    {{{0,1}, {1,1}, {2,0}, {2,1}}},
    {{{1,0}, {1,1}, {1,2}, {2,2}}},
  }},
  {{
    // The four views of the second L :
    // 000
    //   0
    {{{0,1}, {1,1}, {2,1}, {2,2}}},
    {{{1,0}, {1,1}, {1,2}, {2,0}}},
    {{{0,0}, {0,1}, {1,1}, {2,1}}},
    {{{0,2}, {1,0}, {1,1}, {1,2}}},
  }},
  {{
    // The four views of the T :
    // 000
    //  0
    {{{0,1}, {1,1}, {1,2}, {2,1}}},
    {{{1,0}, {1,1}, {1,2}, {2,1}}},
    {{{0,1}, {1,0}, {1,1}, {2,1}}},
    {{{0,1}, {1,0}, {1,1}, {1,2}}},
  }},
};

const int levelMultiplier[] = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
const int linesMultiplier[] = {100, 400, 900, 2000};

/** The piece being played. */
const piece_t* currentPiece;
/** The current position and view of the piece being played. */
pos_t position;

/* -----------------------------------------------------------------  */
/* -----------------------------------------------------------------  */
/*         SPACE INVADER CODE !!!                                     */
/* -----------------------------------------------------------------  */
/* -----------------------------------------------------------------  */

// Number of fire we can use before having to wait
#define MAXFIRE 3
// Number of lives you have : 
#define STARTLIVES 4
// Maximum number of ennemies at one : 
#define MAXENNEMIES 8
// Speed of ennemies arrival :  (between 0 & 20, 20 = rare, 0 = often )
#define ENNEMIESRATE 6


/** The score of the user (number of points = speed of each killed ennemy - number of ennemies missed) */
//int score=0;
/** Position of the ship between 1 & 7 */
byte shippos=4;
/** Number of lives of the user */
byte lives;

/** Position of the bullets of the ship, [0]=x [1]=y */
byte firepos[9][2]={
  {0,0},  {0,0},  {0,0},  {0,0},  {0,0},  {0,0},  {0,0},  {0,0},  {0,0}
};
/** Position and speed of the ennemies [0]=x [1]=y [2]=speed [3]=speed counter */
byte ennemypos[8][4]={
  {0,0,0,0},  {0,0,0,0},  {0,0,0,0},  {0,0,0,0},  {0,0,0,0},  {0,0,0,0},  {0,0,0,0},  {0,0,0,0}
};

// where we read the audio voltage
#define AUDIOPIN 0
char im[128], data[128];
char data_avgs[14];
int i=0,val;
int leng1=0; //provides the length of the char array
int leng2=0; //provides the length of the char array
unsigned char test1[]=" LEFT - INVADER!...   \0"; //text has to end with '\0' !!!!!!
unsigned char test2[]=" RIGHT - TETRIS...   \0"; //text has to end with '\0' !!!!!!

const uint8_t MPU6050SlaveAddress = 0x68;
//const uint8_t scl = A4;
//const uint8_t sda = A5;
// MPU6050 few configuration register addresses
const uint8_t MPU6050_REGISTER_SMPLRT_DIV   =  0x19;
const uint8_t MPU6050_REGISTER_USER_CTRL    =  0x6A;
const uint8_t MPU6050_REGISTER_PWR_MGMT_1   =  0x6B;
const uint8_t MPU6050_REGISTER_PWR_MGMT_2   =  0x6C;
const uint8_t MPU6050_REGISTER_CONFIG       =  0x1A;
const uint8_t MPU6050_REGISTER_GYRO_CONFIG  =  0x1B;
const uint8_t MPU6050_REGISTER_ACCEL_CONFIG =  0x1C;
const uint8_t MPU6050_REGISTER_FIFO_EN      =  0x23;
const uint8_t MPU6050_REGISTER_INT_ENABLE   =  0x38;
const uint8_t MPU6050_REGISTER_ACCEL_XOUT_H =  0x3B;
const uint8_t MPU6050_REGISTER_SIGNAL_PATH_RESET  = 0x68;

int16_t AccelX, AccelY, AccelZ, Temperature, GyroX, GyroY, GyroZ;

int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;

int buttonPushCounter = 0;   
int buttonState = 0;         
int lastButtonState = 0;     
bool running = true;
byte line = 0;       //Row counter
char buffer[10];
int value;
int x = 6;  
int y = 4;  
int OLDx = 1;  
int OLDy = 1;
//int cxMax = 13;  
//int cyMax = 8 ; 
int cx, cy;

/* ---------------------------------------------------------------------------*/
/** The figures from 0 to 9 encoded in 7 lines of 5 bits :
 */
PROGMEM const byte figures[][7] = { 
{14,17,17,17,17,17,14},
{4,6,4,4,4,4,14},
{14,17,16,14,1,1,31},
{14,17,16,14,16,17,14},
{8,12,10,9,31,8,8},
{31,1,1,15,16,16,15},
{14,17,1,15,17,17,14},
{31,16,8,8,4,4,4},
{14,17,17,14,17,17,14},
{14,17,17,30,16,16,15},
};
 
int8_t dx,dy;
int8_t sh1y,sh2y,s1,s2;

/* ---------------------------------------------------------------------------*/
void setup() {
  //Serial.begin(9600);
  Wire.begin();
  MPU6050_Init();
  LedSign::Init();
  for(int i=0; ; i++){ //get the length of the text
    if(test1[i]==0){
      leng1=i;
      break;
    }
    
  }
      for(int j=0; ; j++){ //get the length of the text
    if(test2[j]==0){
      leng2=j;
      break;
    }
    
  }
    
  //Myfont::Banner(leng1,test1);
  len = strlen (test);
}

void loop(){   

Read_RawValue(MPU6050SlaveAddress, MPU6050_REGISTER_ACCEL_XOUT_H);

  cy = AccelY;
  cx = AccelX;
  OLDx = x;  
  OLDy = y;

  buttonState = digitalRead(A1);
 
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      buttonPushCounter++;
    }
    else {

    }
  }
  lastButtonState = buttonState;

 if (digitalRead(A3))
  {

    static uint32_t counter = 0; 
    playerMovePiece();
    timerPieceDown(counter);
    delay(50);
    
   }
  else if (digitalRead(A2))
  {
    
    moveShip();
    fireShip();
    moveFires();
    moveEnnemies();
    addEnnemies();
    delay(120);
    
  }

else
{

switch (buttonPushCounter % 17) {
  
  case 0: // Scrolling text with scrolling speed base on Accelerometer of MPU6050
  
  Accel_scrolling();
  
  break;
  
  case 1: // Load the Error Board
    LoadTheErrorBoard();
    
    break;
 
  case 2:  // Play the Error Board Game
 
    PlayGame();
   break;
   
  case 3:  // Load the Hell Board Game
 
    LoadTheHellBoard();

    break;

  case 4: // Play the Hell Board Game

    PlayGame();
    break; 

  case 5:  // Load the Picture Board Game
 
    LoadThePicBoard();

    break;
  
  case 6:  // Play the Picture Board Game
    
    PlayGame();
    break; 

  case 7: // Load Pong game
  
    x = 3;
    y = 7;
    sh1y=3;
    sh2y=3;
    dx = 1;
    dy = 1;
    s1 = 0;
    s2 = 0;
  
    randomSeed(analogRead(2));
    //drawscores();
    break;    
  
  case 8: // Play Pong game
  
    PlayPong();
    break;   
  
  case 9:  // Spectrum Analyzer Mode
  
    FFT();
    break;  
     
  case 10: // Measure Voice Value
  
    LedSign::Clear(0);
    BlowMeter();
    break;
    
  case 11: // Accelermeter Mode
  
    LedSign::Clear(0);
    Accelerometer();
    break;
    
  case 12: // Load Invader Game - Toggle switch to A3
  
    LedSign::Clear(0);
    randomSeed(analogRead(2));
    initInvaderGame();
    //drawShip();
    //Myfont::Banner(leng1,test1);
    break;
    
  case 13: // Play Tetris Game - Toggle switch to A2
  
    LedSign::Clear(0);
    randomSeed(analogRead(2));
    startTetrisGame();
    nextPiece();
    //Myfont::Banner(leng2,test2);
    break;
    
  case 14: // Hammer Animation
    Hammer();
    break;
    
  case 15: // Heart Animation
    Heart();
    break;
    
  case 16: // Tsumani Animation
    Tsunami();
    break;
  }
}
 
}

void Accel_scrolling(){

  LedSign::Clear(); 
  int i = map(AccelX/16, 0, 1023, 6, len * 6);  // starting pixel
  for (int j = 0; j < 14; j += 6) 
    Myfont::Draw(j, test[(i + j) / 6]); 
  delay(100);   // hold that image for a moment
}

void Autoscrolling()
{ 
  
  for (int8_t x=DISPLAY_COLS, i=0; ; x--) {
    LedSign::Clear();
    for (int8_t x2=x, i2=i; x2<DISPLAY_COLS;) {
       int8_t w = Font::Draw (autotest[i2], x2, 0);
       x2 += w, i2 = (i2+1) % strlen(autotest);
       if (x2 <= 0)  // off the display completely?
         x = x2, i = i2;
       }
       delay(50);
    }
}
  
void FFT(){
for (i=0; i < 128; i++){                                     
    val = analogRead(AUDIOPIN); // read voltage                                    
    data[i] = val;                                       
    im[i] = 0;                                                     
  };

  fix_fft(data,im,7,0);

  for (i=0; i< 64;i++){                                      
    data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);  // this gets the absolute value of the values in the array, so we're only dealing with positive numbers
  };     

  // average bars together
  for (i=0; i<14; i++) {
    data_avgs[i] = data[i*4] + data[i*4 + 1] + data[i*4 + 2] + data[i*4 + 3];   // average together 
    data_avgs[i] = map(data_avgs[i], 0, 15, 0, 9);                              // remap values for LoL
  }

  // set LoLShield
  for (int x=0; x < 14; x++) {
    for (int y=0; y < 9; y++) {
      if (y < data_avgs[13-x]) { // 13-x reverses the bars so low to high frequences are represented from left to right.
        LedSign::Set(x,y,1);        // set the LED on
      } 
      else {
        LedSign::Set(x,y,0);       // set the LED off
      }
    } 
  }

}

void PlayPong(){
int8_t randommove;

  // The Ball shall bounce on the walls : 
  if (x==12 || x==1) {
    dx=-dx;
    // Collision detection
    if (x==1) {
      // check the first ship (left side)
      if (sh1y!=y && sh1y+1!=y) {
        s2++;
        drawscores();
        checkscores();
      }
    } else {
// check the second ship (right side)
      if (sh2y!=y && sh2y+1!=y) {
        s1++;
        drawscores();
        checkscores();
      }
}
  }
  if (y==8 || y==0) dy=-dy;
 
  
  // Clear the non-active screen
  LedSign::Clear();

  // Move the BALL : 
  x=x+dx; 
  y=y+dy;
  
  // Draw the ball :
  LedSign::Set(x,y,1);

  // Draw the Ship
  LedSign::Set(0, sh1y, 1);
  LedSign::Set(0, sh1y+1, 1);
  LedSign::Set(13, sh2y, 1);
  LedSign::Set(13, sh2y+1, 1);
  
  // The ships moves when the ball go in their direction. They follow it magically 😉 :   
  
  if (dx>0) {
    // the ball goes away from me, let's move randomly
    randommove=random(0,3);
    if (randommove==0) {
      sh1y--;
    }
    if (randommove==1) {
      sh1y++;
    }
  } else {
    if (sh1y>y && (random(0,12)<10 || x<3)) {
      sh1y--;
    }
    if (sh1y<y && (random(0,12)<10 || x<3)) {
      sh1y++;
    }
    if (random(0,8)==0) {
      if (sh1y>y) {
        sh1y++;
      }
      if (sh1y<y) {
        sh1y--;
      }      
    }
  }
  

  // Human Player 
  // 1/4 of the variator is used. If we use it fully, it's too hard to play.
  // To use it fully replace 36 by 146
  sh2y=AccelY/1152; 

  // Sanity checks for the ships : 
  if (sh1y>7) sh1y=7;
  if (sh2y>7) sh2y=7;
  if (sh1y<0) sh1y=0;
  if (sh2y<0) sh2y=0;

  // swap the screens 😉 (sometime we may need this double-buffer algorithm...
  // of course, as of today it's a little bit overkill ...)

  LedSign::Flip();

  // Display the bitmap some times 
  delay(100);
  
  // loop 🙂 
}


void PlayGame(){
  if (cx < 13){ cx = 13; x = x+1;}
  else if (cx > 13) {cx = 13; x = x-1;}
  if (cy > 8) {cy = 8; y = y+1;}
  else if (cy < 8) {cy = 8; y = y-1;} 
  if (y > 8) y = 8;
  if (x > 13) x = 13; 
  if (y < 0) y = 0;
  if (x < 0) x = 0;
  

  LedSign::Set(x,y,1);
  LedSign::Set(OLDx,OLDy,0);
  delay(50);
}

void Accelerometer(){
  if (cx < 13){ cx = 13; x = x+1;}
  else if (cx > 13) {cx = 13; x = x-1;}
  if (cy > 8) {cy = 8; y = y+1;}
  else if (cy < 8) {cy = 8; y = y-1;} 
  if (y > 8) y = 8;
  if (x > 13) x = 13; 
  if (y < 0) y = 0;
  if (x < 0) x = 0;
  
  LOLDraw::Circle(x, y, 1);
  LOLDraw::Circle(x, y, 2);
  
  delay(50);
}
void BlowMeter(){
  val = analogRead(AUDIOPIN); // read voice
  uint8_t i = map(val, 540, 1023, 0, 13);
    for (int j = 0; j < 9; j++)
    {
    LOLDraw::Line( 0, j, i, j); 
    }

  delay(50);
}

void LoadTheErrorBoard(){
  DisplayBitMap(12771);
  DisplayBitMap(13011);
  DisplayBitMap(2772);
  DisplayBitMap(1008);
  DisplayBitMap(816);
  DisplayBitMap(480);
  DisplayBitMap(2052);
  DisplayBitMap(12707);
  DisplayBitMap(12643);
}
void LoadThePicBoard(){
  DisplayBitMap(16383);
  DisplayBitMap(8193);
  DisplayBitMap(12285);
  DisplayBitMap(10245);
  DisplayBitMap(11253);
  DisplayBitMap(10245);
  DisplayBitMap(12285);
  DisplayBitMap(8193);
  DisplayBitMap(16383);
}

void LoadTheHellBoard(){  
  DisplayBitMap(5461);
  DisplayBitMap(0);
  DisplayBitMap(2421);
  DisplayBitMap(2325);
  DisplayBitMap(2423);
  DisplayBitMap(2325);
  DisplayBitMap(7029);
  DisplayBitMap(0);
  DisplayBitMap(10922);
}

void LoadTheFallBoard(){
  DisplayBitMap(0);
  DisplayBitMap(4686);
  DisplayBitMap(4770);
  DisplayBitMap(4770);
  DisplayBitMap(4838);
  DisplayBitMap(4770);
  DisplayBitMap(4770);
  DisplayBitMap(13986);
  DisplayBitMap(0);
}

void DisplayBitMap(int lineint)
{
  for (byte led=0; led<14; ++led) {
    if (lineint & (1<<led)) {
      LedSign::Set(led, line, 1);
    } else {
      LedSign::Set(led, line, 0);
    }
  }
    
  line++;
  if(line >= 9) line = 0;
}

void I2C_Write(uint8_t deviceAddress, uint8_t regAddress, uint8_t data){
  Wire.beginTransmission(deviceAddress);
  Wire.write(regAddress);
  Wire.write(data);
  Wire.endTransmission();
}

// Read all 14 registers
void Read_RawValue(uint8_t deviceAddress, uint8_t regAddress){
  Wire.beginTransmission(deviceAddress);
  Wire.write(regAddress);
  Wire.endTransmission();
  Wire.requestFrom(deviceAddress, (uint8_t)14);
  AccelX = (((int16_t)Wire.read()<<8) | Wire.read());
  AccelY = (((int16_t)Wire.read()<<8) | Wire.read());
  AccelZ = (((int16_t)Wire.read()<<8) | Wire.read());
  Temperature = (((int16_t)Wire.read()<<8) | Wire.read());
  GyroX = (((int16_t)Wire.read()<<8) | Wire.read());
  GyroY = (((int16_t)Wire.read()<<8) | Wire.read());
  GyroZ = (((int16_t)Wire.read()<<8) | Wire.read());
}

// Configure MPU6050
void MPU6050_Init(){
  delay(150);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_SMPLRT_DIV, 0x07);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_PWR_MGMT_1, 0x01);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_PWR_MGMT_2, 0x00);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_CONFIG, 0x00);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_GYRO_CONFIG, 0x00);//set +/-250 degree/second full scale
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_ACCEL_CONFIG, 0x00);// set +/- 2g full scale
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_FIFO_EN, 0x00);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_INT_ENABLE, 0x01);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_SIGNAL_PATH_RESET, 0x00);
  I2C_Write(MPU6050SlaveAddress, MPU6050_REGISTER_USER_CTRL, 0x00);
}
// Absolute Value

float myAbs(float in){
  return (in)>0?(in):-(in);
} 

/* ---------------------------------------------------------------------------*/
/** Check if a player won !
 * If one of the players won, let's show a funny animation 😉 
 */
void checkscores() {  
  // TODO : DO the animation 😉
} 
/* ---------------------------------------------------------------------------*/
/** Draw the scores in a lovely scrolling 🙂 
 * Use the current active screen brutally ...
 */
void drawscores() {
  int8_t i,j,ps1;
  
  for(ps1=0;ps1<8;ps1++) {
    LedSign::Clear();  // Clear the active screen

    LedSign::Set(6,4,1); // dash between the scores
    LedSign::Set(7,4,1);
    
    // Fill it with both scores : 
    // Left score goes up>down
    for (i=ps1, j=6; i>=0 && j>=0; i--, j--) {
      byte f = pgm_read_byte_near(&figures[s1][j]);
      for (uint8_t k = 0; k < 5; k++, f>>=1)
        LedSign::Set(k, i, f & 1);
    }
    // Right score goes down>up
    for (i=8-ps1, j=0; i<=8 && j<=6; i++, j++) {
      byte f = pgm_read_byte_near(&figures[s2][j]);
      for (uint8_t k = 0; k < 5; k++, f>>=1)
        LedSign::Set(k+9, i, f & 1);
    }    

    LedSign::Flip();
    delay(200);
  }
  
  delay(1500);
  
  LedSign::Clear(0);

  if (s1==9 || s2==9) {
    for(ps1=0;ps1<3;ps1++) {
      LedSign::Flip();      
      delay(300);
      
      LedSign::Flip();
      delay(600);
      
    }
    delay(1500);

    s1=0; s2=0;
    drawscores();
  }
}


/* -----------------------------------------------------------------  */
/** Draw the ship at its current position.
 * @param set 1 or 0 to set or clear the led.
 */
void drawShip(uint8_t c=1) {
    LedSign::Set(0,shippos-1,c); 
    LedSign::Set(0,shippos  ,c); 
    LedSign::Set(0,shippos+1,c); 
    LedSign::Set(1,shippos  ,c); 
}

/* -----------------------------------------------------------------  */
/** Draw the number of lives remaining at the top left of the screen
 */
void drawLives() {
  for(byte i=0;i<STARTLIVES;i++) {
    LedSign::Set(13-i,0,(i<lives)?1:0); 
  }
}



/* -----------------------------------------------------------------  */
/** end of the game, draw the Scores using a scrolling
 */
void endGame1() {
  for(byte x=4;x<=8;x++) 
    for(byte y=0;y<=8;y++) 
      LedSign::Set(x,y,0);
  
  Figure::Scroll90(score);
  
  for(byte i=0;i<30;i++) {
   drawShip(0);
   delay(5*(30-i));
   drawShip(0);
   delay(5*(30-i));
  }
}

/* -----------------------------------------------------------------  */
/** Initialize a new game (lives, screen, score, ship ...)
 */
void initInvaderGame() {
   lives=STARTLIVES;
   score=0;
   shippos=4;
   LedSign::Clear();
   drawLives();
   drawShip();
}

/* -----------------------------------------------------------------  */
/** Check the variator to know the position of the ship.
 * Move and redraw the ship in case of change.
 */
void moveShip()
{
  // Ship have 7 positions. Let's use a third of the variator position.
  int newshpos=(AccelY/1152); // we reverse the command (variator wrongly connected 😉 )
  if (newshpos>7) newshpos=7;
  if (newshpos<1) newshpos=1;
  if (newshpos!=shippos) {
    drawShip(0);
    for(byte i=0;i<8;i++)
      LedSign::Set(0,i,0);
    shippos=newshpos;
    drawShip(1);
  }
}

/* -----------------------------------------------------------------  */
/** Check the fire button and fire if it has been pushed.
 * Please note that we use a static status to check that the user 
 * pull the button between each fire.
 */
void fireShip()
{
  static byte status=0;
  if (status==0) {
    // Ship may fire 10 times (not more...)
    if (analogRead(1)>1000) {
        // FIRE ! 
        status=1;
        for(byte i=0;i<MAXFIRE;i++) {
          if (firepos[i][0]==0) {
              firepos[i][0]=2; firepos[i][1]=shippos;
              break;
          }
        }
    }
  } else {
    if (analogRead(1)<1000) status=0;
  }
}

/* -----------------------------------------------------------------  */
/** Crash : called when an ennemy touched the ship (failure!)
 */
void crash() {
   drawShip(1);
   delay(150);
   drawShip(0);
   delay(150);
   drawShip(1);
   delay(150);
   drawShip(0);
   delay(150);

   for(byte i=0;i<MAXENNEMIES;i++)
     if (ennemypos[i][0]!=0) {
       LedSign::Set(ennemypos[i][0],ennemypos[i][1],0);
       ennemypos[i][0]=0;
     }
   for(byte i=0;i<MAXFIRE;i++)
     firepos[i][0]=0;

   lives--;
   if (lives==0) {
     endGame1();
     initInvaderGame();
   } else {
     LedSign::Clear();
     drawLives();
     drawShip();   
   }
}

/* -----------------------------------------------------------------  */
/** Add ennemies at the top randomly, with random speed too
 */
void addEnnemies()
{
  if (random(0,ENNEMIESRATE)==0) {
    // ENNEMY COMING !
        for(byte i=0;i<MAXENNEMIES;i++) {
          if (ennemypos[i][0]==0) {
              ennemypos[i][0]=13; ennemypos[i][1]=random(1,8);
              ennemypos[i][2]=random(2,5); // Speed of ennemies between 1 and 5 (5=slower)
              ennemypos[i][3]=0;
              break;
          }
        }
    }
}

/* -----------------------------------------------------------------  */
/** Move the ennemies, and check the collision with the ship
 */
void moveEnnemies()
{
  for(byte i=0;i<MAXENNEMIES;i++) {
    if (ennemypos[i][0]!=0) {
      ennemypos[i][3]++;
      if (ennemypos[i][2]==ennemypos[i][3]) {
        ennemypos[i][3]=0;
        LedSign::Set(ennemypos[i][0],ennemypos[i][1],0);       
        ennemypos[i][0]--;
        // collision with the top of the ship
        if (ennemypos[i][0]==1 && shippos==ennemypos[i][1]) { 
          crash();
        } else {
          if (ennemypos[i][0]==0) { // Collision detection
            ennemypos[i][0]=0;
            if (score>0) score-=1;
            if (shippos==ennemypos[i][1] || shippos-1==ennemypos[i][1] || shippos+1==ennemypos[i][1]) {
              crash();
            } else {              
              LedSign::Set(ennemypos[i][0],ennemypos[i][1],0);                 
            }
          }
        }
      } else {
        LedSign::Set(ennemypos[i][0],ennemypos[i][1],1);                 
      }
    }
  }
}

/* -----------------------------------------------------------------  */
/** Move the bullets, draw them, and check the collision with
 * ennemies. 
 */
void moveFires()
{
  for(byte i=0;i<MAXFIRE;i++) {
    if (firepos[i][0]!=0) {
      LedSign::Set(firepos[i][0],firepos[i][1],0);       
      firepos[i][0]++;
      // Let's detect collision with ennemies : 
      for(byte j=0;j<MAXENNEMIES;j++) {
        if (ennemypos[j][0]!=0) {
          if ((ennemypos[j][0]==firepos[i][0] || ennemypos[j][0]==firepos[i][0]+1) && ennemypos[j][1]==firepos[i][1]) {  
              // Ennemy destroyed
              LedSign::Set(ennemypos[j][0],ennemypos[j][1],0);                 
              ennemypos[j][0]=0;
              firepos[i][0]=0;
              score+=(6-ennemypos[j][2]); // Change it in case of ennemy speed change 😉 score is 5-speed
          }
        }
      }
      if (firepos[i][0]==14) {
        firepos[i][0]=0;
      } else {
        LedSign::Set(firepos[i][0],firepos[i][1],1);                 
      }
    }
  }
}

////TETRIS

/* -----------------------------------------------------------------  */
/** Switches on or off the display of a Tetris piece.
 * @param piece the piece to be displayed or removed.
 * @param position the position and view of the piece to draw or remove.
 * @param set 1 or 0 to draw or remove the piece.
 */
void switchPiece(const piece_t* piece, const pos_t& position, uint8_t c=1) {
  for(uint8_t i=0;i<4;i++) {
    coordPacked_t element = piece->views[position.view].elements[i];
    uint8_t eltXPos = element.x+position.coord.x;
    uint8_t eltYPos = element.y+position.coord.y;
    LedSign::Set(13-eltYPos, eltXPos, c);
  }
}


/* -----------------------------------------------------------------  */
/**
 * Redraw a section of the tetris play grid between the given
 * indexes (included).
 * @param top the index of the top line of the section to redraw.
 * @param bottom the index the bottom line of the section to redraw.
 *               This parameter MUST be greater or equal than top.
 */
void redrawLines(uint8_t top, uint8_t bottom) {
  for (uint8_t y=top; y<=bottom; y++)
    for (uint8_t x=0; x<GRID_WIDTH; x++)
      LedSign::Set(13-y,x,playGrid[y][x]);
}

/* -----------------------------------------------------------------  */
/**
 * End of the game, draw the score using a scroll.
 */
void endGame() {
  for (uint8_t y=0;y<=13;y++) {
    LedSign::Vertical(13-y, 0);
    delay(100);
  }
  // Draw the score and scroll it
  Figure::Scroll90(score);
}

/* -----------------------------------------------------------------  */
/**
 * Game initialization, or reinitialization after a game over.
 */
void startTetrisGame() {
  // Initialize variables.
  level = 0;
  score = 0;
  linesCleared = 0;
  memset(playGrid, '\0', sizeof(playGrid)); 
  LedSign::Clear();
}

/* -----------------------------------------------------------------  */
/**
 * Test whether a given piece can be put at a given location in the
 * given location. This is used to check all piece moves.
 * @param piece the piece to try and put on the play grid.
 * @param position the position and view to try and put the piece.
 */
boolean checkPieceMove(const piece_t* piece, const pos_t& position) {
  for (uint8_t i=0; i<4; i++) {
    coordPacked_t element = piece->views[position.view].elements[i];
    int8_t eltXPos = element.x+position.coord.x;
    int8_t eltYPos = element.y+position.coord.y;
    // Check x boundaries.
    if (eltXPos>8 || eltXPos<0)
      return false;
    // Check y boundaries.
    if (eltYPos>13 || eltYPos<0)
      return false;
    // Check collisions in grid.
    if (playGrid[eltYPos][eltXPos])
      return false;
  }
  
  return true;
}

/* -----------------------------------------------------------------  */
/**
 * Handle player actions : left/right move and piece rotation.
 */
void playerMovePiece()
{
  boolean moveSuccess;
  int inputPos;
  pos_t newPos;

  // First try rotating the piece if requested.
  // Ensure the player released the rotation button before doing a second one.
  static byte status=0;
  if (status == 0) {
    if (analogRead(1)>1000) {
      status = 1;
      newPos = position;
      newPos.view = (newPos.view+1)&3;
      moveSuccess = checkPieceMove(currentPiece, newPos);
      if (moveSuccess) {
        switchPiece(currentPiece, position, 0);
        switchPiece(currentPiece, newPos, 1);
        position = newPos;
      }
    }
  } else {
    if (analogRead(1)<1000) {
      status = 0;
    }
  }

  newPos = position;
  // Tweak the analog input to get a playable input.
  inputPos=(AccelY/1152); // we reverse the command (variator wrongly connected 😉 )
  if (inputPos != position.coord.x) {
    // Try moving the piece to the requested position.
    // We must do it step by step and leave the loop at the first impossible movement otherwise we might
    // traverse pieces already in the game grid if the user changes the input fast between two game loop iterations.
    int diffSign = (inputPos>position.coord.x) ? 1 : -1;
    for(int i=position.coord.x;(inputPos-i)*diffSign>=0;i+=diffSign) {
      newPos.coord.x = i;
      if (i<-1 || i>8) {
        // Skip out of screen iterations.
        // Don't skip -2, it's a correct x position for certain pieces' views.
        continue;
      } else {
        moveSuccess = checkPieceMove(currentPiece, newPos);
        if (moveSuccess) {
          switchPiece(currentPiece, position, 0);
          switchPiece(currentPiece, newPos, 1);
          position = newPos;
        } else {
          break;
        }
      }
    }
  }
}


/* -----------------------------------------------------------------  */
/**
 * Handle the current piece going down every few game loop iterations.
 * @param count game loop counter, used to know if the piece should go
 *              down at each call.
 */
void timerPieceDown(uint32_t& count) {
  // Every 10-level iterations, make the piece go down.
  // TODO The level change code is largely untested and surely needs tweaking.
  if (++count % (10-level) == 0) {
    pos_t newPos = position;
    newPos.coord.y++;

    boolean moveSuccess = checkPieceMove(currentPiece, newPos);
    if (moveSuccess) {
      switchPiece(currentPiece, position, 0);
      switchPiece(currentPiece, newPos, 1);
      position = newPos;
    } else {
      // Drop the piece on the grid.
      for (uint8_t i=0; i<4; i++) {
        coordPacked_t element = currentPiece->views[position.view].elements[i];
        uint8_t eltXPos = element.x+position.coord.x;
        uint8_t eltYPos = element.y+position.coord.y;
        playGrid[eltYPos][eltXPos] = true;
      }
 
      processEndPiece();
      nextPiece();
    }
  }
}


/* -----------------------------------------------------------------  */
/**
 * Handles :
 * - the dropping of the current piece on the game grid when it can't
 *   go lower ;
 * - the detection and removal of full lines ;
 * - the score update ;
 * - the level update when needed.
 */
void processEndPiece() {
  uint8_t fullLines[4];
  uint8_t numFull = 0;
  for (int8_t y=13; y>=0; y--) {
    boolean full = true;
    for (uint8_t x=0; x<9; x++)
      if (!playGrid[y][x])
  full = false;
    if (full)
      fullLines[numFull++] = y;
  }
  
  if (numFull) {
    // Blink full lines.
    for (uint8_t i=0; i<5; i++) {
      for (uint8_t j=0; j<numFull; j++)
        LedSign::Vertical(13-fullLines[j], i&1);
      delay(150);
    }
 
    // Remove full lines from the array.
    for (uint8_t i=0; i<numFull; i++) {
      uint8_t lineIdx = fullLines[i];
      // Move all lines above one step down.
      for (uint8_t j=lineIdx; j>0; j--)
        memcpy(playGrid+j, playGrid+j-1, GRID_WIDTH*sizeof(boolean));
      memset(playGrid, '\0', GRID_WIDTH*sizeof(boolean));

      // Update the indexes of the other lines to remove.
      for (uint8_t k=i;k<numFull; k++)
        fullLines[k]++;
    }

    // Update the display.
    redrawLines(0, fullLines[0]);

    // Sega scoring algorithm.
    score += levelMultiplier[level] * linesMultiplier[numFull-1];
  
    // Level update.
    linesCleared += numFull;
    if (linesCleared >= 4) {
      linesCleared = 0;
      level++;
    }
  }
}

/* -----------------------------------------------------------------  */
/**
 * Start dropping a new randomly chosen piece.
 */
void nextPiece() {
  currentPiece = &pieces[random(0,7)];
  
  position.coord.x = 3;
  position.coord.y = -1;
  position.view = 0;

  if (!checkPieceMove(currentPiece, position)) {
    endGame();
    startTetrisGame();
  }
}
void Hammer()
{
delay(200);
  DisplayBitMap(7);
  DisplayBitMap(1799);
  DisplayBitMap(1287);
  DisplayBitMap(5954);
  DisplayBitMap(2690);
  DisplayBitMap(1794);
  DisplayBitMap(514);
  DisplayBitMap(512);
  DisplayBitMap(1280);
  delay(200);
  DisplayBitMap(112);
  DisplayBitMap(1904);
  DisplayBitMap(1392);
  DisplayBitMap(5960);
  DisplayBitMap(2692);
  DisplayBitMap(1794);
  DisplayBitMap(512);
  DisplayBitMap(512);
  DisplayBitMap(1280);
  delay(200);
  DisplayBitMap(0);
  DisplayBitMap(224);
  DisplayBitMap(480);
  DisplayBitMap(736);
  DisplayBitMap(624);
  DisplayBitMap(424);
  DisplayBitMap(996);
  DisplayBitMap(48);
  DisplayBitMap(32);

}


void Heart(){
delay(100);
  DisplayBitMap(0);
  DisplayBitMap(0);
  DisplayBitMap(288);
  DisplayBitMap(720);
  DisplayBitMap(528);
  DisplayBitMap(288);
  DisplayBitMap(192);
  DisplayBitMap(0);
  DisplayBitMap(0);
  delay(100);
  DisplayBitMap(0);
  DisplayBitMap(816);
  DisplayBitMap(1224);
  DisplayBitMap(1032);
  DisplayBitMap(1032);
  DisplayBitMap(528);
  DisplayBitMap(288);
  DisplayBitMap(192);
  DisplayBitMap(0);
  delay(100);
  DisplayBitMap(3612);
  DisplayBitMap(4578);
  DisplayBitMap(8193);
  DisplayBitMap(8193);
  DisplayBitMap(8193);
  DisplayBitMap(8193);
  DisplayBitMap(4098);
  DisplayBitMap(2052);
  DisplayBitMap(1032);

}

void Tsunami()
{

  delay(100);
  DisplayBitMap(15);
  DisplayBitMap(8223);
  DisplayBitMap(12351);
  DisplayBitMap(14407);
  DisplayBitMap(14339);
  DisplayBitMap(15363);
  DisplayBitMap(15363);
  DisplayBitMap(16135);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(30);
  DisplayBitMap(63);
  DisplayBitMap(8319);
  DisplayBitMap(12431);
  DisplayBitMap(12295);
  DisplayBitMap(14343);
  DisplayBitMap(14343);
  DisplayBitMap(15887);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(60);
  DisplayBitMap(126);
  DisplayBitMap(255);
  DisplayBitMap(8511);
  DisplayBitMap(8223);
  DisplayBitMap(12319);
  DisplayBitMap(12319);
  DisplayBitMap(15423);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(120);
  DisplayBitMap(252);
  DisplayBitMap(510);
  DisplayBitMap(575);
  DisplayBitMap(31);
  DisplayBitMap(8223);
  DisplayBitMap(8223);
  DisplayBitMap(14399);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(240);
  DisplayBitMap(504);
  DisplayBitMap(1020);
  DisplayBitMap(1150);
  DisplayBitMap(62);
  DisplayBitMap(63);
  DisplayBitMap(63);
  DisplayBitMap(12415);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(480);
  DisplayBitMap(1008);
  DisplayBitMap(2040);
  DisplayBitMap(2300);
  DisplayBitMap(124);
  DisplayBitMap(126);
  DisplayBitMap(126);
  DisplayBitMap(8447);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(960);
  DisplayBitMap(2016);
  DisplayBitMap(4080);
  DisplayBitMap(4600);
  DisplayBitMap(248);
  DisplayBitMap(252);
  DisplayBitMap(252);
  DisplayBitMap(511);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(1920);
  DisplayBitMap(4032);
  DisplayBitMap(8160);
  DisplayBitMap(9200);
  DisplayBitMap(496);
  DisplayBitMap(504);
  DisplayBitMap(504);
  DisplayBitMap(1022);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(3840);
  DisplayBitMap(8064);
  DisplayBitMap(16320);
  DisplayBitMap(2017);
  DisplayBitMap(992);
  DisplayBitMap(1008);
  DisplayBitMap(1008);
  DisplayBitMap(2044);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(7680);
  DisplayBitMap(16128);
  DisplayBitMap(16257);
  DisplayBitMap(4034);
  DisplayBitMap(1984);
  DisplayBitMap(2016);
  DisplayBitMap(2016);
  DisplayBitMap(4088);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(15360);
  DisplayBitMap(15873);
  DisplayBitMap(16131);
  DisplayBitMap(8068);
  DisplayBitMap(3968);
  DisplayBitMap(4032);
  DisplayBitMap(4032);
  DisplayBitMap(8160);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(14337);
  DisplayBitMap(15363);
  DisplayBitMap(15879);
  DisplayBitMap(16136);
  DisplayBitMap(7936);
  DisplayBitMap(8064);
  DisplayBitMap(8064);
  DisplayBitMap(16352);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(12291);
  DisplayBitMap(14343);
  DisplayBitMap(15375);
  DisplayBitMap(15889);
  DisplayBitMap(15872);
  DisplayBitMap(16128);
  DisplayBitMap(16128);
  DisplayBitMap(16321);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(8199);
  DisplayBitMap(12303);
  DisplayBitMap(14367);
  DisplayBitMap(15395);
  DisplayBitMap(15361);
  DisplayBitMap(15873);
  DisplayBitMap(15873);
  DisplayBitMap(16259);
  DisplayBitMap(16383);
  delay(100);
  DisplayBitMap(15);
  DisplayBitMap(8223);
  DisplayBitMap(12351);
  DisplayBitMap(14407);
  DisplayBitMap(14339);
  DisplayBitMap(15363);
  DisplayBitMap(15363);
  DisplayBitMap(16135);
  DisplayBitMap(16383);
}

Source: GYRO & VOICE ARDUIGAMES


About The Author

Muhammad Bilal

I am a highly skilled and motivated individual with a Master's degree in Computer Science. I have extensive experience in technical writing and a deep understanding of SEO practices.

Leave a Comment

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

Scroll to Top