GYRO & VOICE ARDUIGAMES

1,820

13

4

About: PLC, Arduino - Do it yourself project

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:

Step 1: BILL OF MATERIAL

Main components list:

  • R10K (3pcs).

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.

You can see LoLShield's video for first testing & spectrum analyzer testing:

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
  • Make Arduino base:
  • Connect Adapter board to Arduino Uno:

  • Connect LoLShield to Adapter board:

  • Cover the game box:

  • Finish:

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 library: The 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: https://github.com/jprodgers/LoLshield
// 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); }

Step 8: PROGRAM EXPLANATION

Base on the program, I count number of pressing at push button to change games mode and animations mode. But for Tetris and Invader game, I have to start these games by using 3 postions toggle switch & use push button to fire on Invader/ rotate blocks on Tetris. We can also use Microphone Module to fire (Invader) / rotate blocks (Tetris) but it looks very weird & funny.

All animations & games mode are summarized in table below:

We can add more games or animations to program but take note value at: "buttonPushCounter % 17".

Step 9: SOME MORE PROJECT'S PICTURES & VIDEOS

PICTURES:

VIDEOS:

Share

    Recommendations

    • PCB Contest

      PCB Contest
    • First Time Author

      First Time Author
    • Big and Small Contest

      Big and Small Contest

    4 Discussions

    0
    None
    jfryar30272

    7 months ago

    Wow - that is really cool! I'm impressed that you put your LED matrix together yourself! Great work!

    1 reply
    0
    None
    tuenhidiyjfryar30272

    Reply 7 months ago

    Thanks! I soldered very carefully and double-checked after each LED soldering.