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:
- Arduino Uno R3 (1pcs).
- LoLShield (1pcs).
- MPU6050 (1pcs).
- Microphone Module KY-038 (1pcs).
- Audio Jack 3.5mm (1pcs).
- Mini 3 ways toggle switch (1pcs).
- Mini 2 ways toggle switch (1pcs).
- DIY PCB Prototype (1pcs).
- 40 pin male header (4 pcs).
- 40 pin female header (4 pcs).
- Acrylic Clear Sheet (1pcs).
- Push button (1pcs).
- Long bolts & nuts (1pcs).
- 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.
ou can see LoLShield’s video for first testing & spectrum analyzer testing:
Step 2: GAME BOX SCHEMATIC
The LoL Shield leaves analog pins A0 to A5 free for inputs. For detail, let see picture below for LoLShield pin usage:
- 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:
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:
- Original LoLShield library:
- Charliplexing
- Figure
- Font
- Myfont
- LoLShield_Tetris
- Fix_FFT library:Â The fix_fft library perform forward/inverse fast Fourier transform. In my case, it is used for spectrum analyzer application.
- 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: <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); }
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.
Read more: GYRO & VOICE ARDUIGAMES