/*========================================================*/ /* 05-833 Applied Gadgets, Sensors and Activity Recognition in HCI Class by Scott Hudson Carnegie Mellon University Spring 2012 *SpaceVaders* by Elwin Lee Entertainment Technology Center Carnegie Mellon University Credits: Drive matrix pattern http://arduino.cc/playground/Main/DirectDriveLEDMatrix Play tones through speaker http://arduino.cc/en/Tutorial/Tone */ /*========================================================*/ #include "pitches.h" /****TESTING****/ byte tempMatrix[8][8] = { {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}, {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} }; /*** Level 1 ***/ #define level1_ani { \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 0, 0, 0, 1, 1, 1}, \ {1, 1, 0, 0, 0, 1, 1, 1}, \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 0, 0, 0, 0, 1, 1}, \ {1, 1, 0, 0, 0, 0, 1, 1} \ } #define level1 { \ {0, 1, 0, 1, 0, 1, 0, 0}, \ {0, 0, 1, 0, 1, 0, 1, 0}, \ {0, 1, 0, 1, 0, 1, 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, 0, 0}, \ {0, 0, 0, 0, 0, 0, 0, 0} \ } /*** Level 2 ***/ #define level2_ani { \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 0, 0, 0, 0, 1, 1}, \ {1, 1, 0, 1, 1, 0, 0, 1}, \ {1, 1, 1, 1, 1, 0, 0, 1}, \ {1, 1, 1, 1, 0, 0, 1, 1}, \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 0, 0, 0, 0, 0, 1}, \ {1, 1, 0, 0, 0, 0, 0, 1} \ } #define level2 { \ {0, 1, 0, 1, 0, 1, 0, 1}, \ {0, 0, 1, 0, 1, 0, 1, 0}, \ {0, 1, 0, 1, 0, 1, 0, 1}, \ {0, 0, 1, 0, 1, 0, 1, 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, 0} \ } /*** Level 3 ***/ #define level3_ani { \ {1, 1, 1, 0, 0, 0, 1, 1}, \ {1, 1, 0, 0, 0, 0, 0, 1}, \ {1, 1, 0, 1, 1, 0, 0, 1}, \ {1, 1, 1, 1, 0, 0, 1, 1}, \ {1, 1, 1, 1, 0, 0, 0, 1}, \ {1, 1, 0, 1, 1, 0, 0, 1}, \ {1, 1, 0, 0, 0, 0, 0, 1}, \ {1, 1, 1, 0, 0, 0, 1, 1} \ } #define level3 { \ {0, 0, 0, 1, 1, 1, 0, 0}, \ {0, 0, 1, 1, 1, 1, 1, 0}, \ {0, 1, 1, 0, 1, 0, 1, 1}, \ {0, 1, 0, 1, 1, 1, 0, 1}, \ {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} \ } /*** WIN ***/ #define win { \ {1, 1, 1, 0, 0, 1, 1, 1}, \ {1, 1, 0, 0, 0, 0, 1, 1}, \ {1, 0, 1, 0, 0, 1, 0, 1}, \ {0, 0, 0, 0, 0, 0, 0, 0}, \ {0, 0, 0, 0, 0, 0, 0, 0}, \ {1, 0, 1, 0, 0, 1, 0, 1}, \ {1, 0, 0, 1, 1, 0, 0, 1}, \ {1, 1, 1, 0, 0, 1, 1, 1} \ } /*** GENERAL ***/ const int cols[8] = { 12, 11, 10, 9, 8, 7, 6, 5 }; // Array of all the Column pin numbers const int rows[8] = { 4, 3, 2, A0, A1, A2, A3, A4 }; // Array of all the Row pin numbers // 2-dimensional array of pixels in LED Matrix: int pixels[8][8]; int loopDelay = 2; boolean levelAni = true; //true boolean levelStart = false; boolean levelComplete = false; int levelsArrayIndex = 0; const int _lvls = 7; //2 * number of levels byte levels[_lvls][8][8] = { level1_ani, level1, level2_ani, level2, level3_ani, level3, win }; //copy of levels array for resetting purposes byte initial[_lvls][8][8] = { level1_ani, level1, level2_ani, level2, level3_ani, level3, win }; /*** SOUND ***/ int boom = NOTE_C4; boolean loseSound = true; int lose[] = { NOTE_F4, NOTE_A4, NOTE_C5 }; int loseNoteDurations[] = { 2, 4, 4}; /*** TIMERS ***/ unsigned long aniTime; unsigned long completeTime; unsigned long enemyTime; unsigned long gameOverTime; /*** PLAYER ***/ int bulletRow = 5; int bulletCol; byte bulletArray[6]; int bulletDelayCount = 0; boolean fired = false; /*** CONTROLS ***/ int btnPin = A5; int potPin = A6; int potVal; boolean btnDown = false; /*** ENEMY ***/ boolean enemyWon = false; boolean enemyWaiting = false; boolean enemyFired = false; boolean enemyBulletCollision = false; int enemyAttackSpeed[7] = {0, 150, 0, 100, 0, 25, 0}; int enemyBulletSpeed[7] = {0, 16, 0, 12, 0, 8, 0}; int enemyBulletRow; int enemyBulletDelayCount = 0; int enemyBulletArray[8][2] = { {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0} }; int enemyBottomCount = 0; int randomBullet; /*** SHIFTING ***/ boolean shiftLevel = false; boolean shiftLeft = true; boolean shiftDown = false; boolean shiftRight = false; boolean shiftUp = false; int shiftCount = 0; int shiftSpeed[2] = {70, 35}; void setup() { Serial.begin(9600); // initialize the I/O pins as outputs: // iterate over the pins: for (int thisPin = 0; thisPin < 8; thisPin++) { // initialize the output pins: pinMode(cols[thisPin], OUTPUT); pinMode(rows[thisPin], OUTPUT); // take the col pins (i.e. the cathodes) high to ensure that // the LEDS are off: //both HIGH so no connection digitalWrite(cols[thisPin], HIGH); digitalWrite(rows[thisPin], HIGH); } //set button and potentio meter as input pinMode(btnPin, INPUT); pinMode(potPin, INPUT); clearLeds(); //clear LED Matrix // shiftLevel = true; aniTime = millis(); //start level # animation timer } void loop() { /***reset all to no circuit***/ for (int i = 0; i < 8; i++) { digitalWrite(rows[i], HIGH); //all rows to high digitalWrite(cols[i], LOW); } /***Row 0***/ for (int col = 0; col < 8; col++) { if (pixels[0][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[0], LOW); delay(loopDelay); digitalWrite(rows[0], HIGH); setPattern(levelsArrayIndex); //create levels /***Row 1***/ for (int col = 0; col < 8; col++) { if (pixels[1][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[1], LOW); delay(loopDelay); digitalWrite(rows[1], HIGH); if (levelAni) playLevelAni(); //play animation /***Row 2***/ for (int col = 0; col < 8; col++) { if (pixels[2][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[2], LOW); delay(loopDelay); digitalWrite(rows[2], HIGH); if (levelStart) readPot(); //read potentiometer value /***Row 3***/ for (int col = 0; col < 8; col++) { if (pixels[3][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[3], LOW); delay(loopDelay); digitalWrite(rows[3], HIGH); if (levelStart) refreshPlayer(); //update player's position based on potentiometer value if (levelStart) readBtn(); //read button state /***Row 4***/ for (int col = 0; col < 8; col++) { if (pixels[4][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[4], LOW); delay(loopDelay); digitalWrite(rows[4], HIGH); if (levelStart && !enemyWon) readEnemy(); //get highest row # of enemy and fire bullet /***Row 5***/ for (int col = 0; col < 8; col++) { if (pixels[5][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[5], LOW); delay(loopDelay); digitalWrite(rows[5], HIGH); if (levelStart && !levelComplete) checkLevelState(); //check all pixels of the level are gone if (levelComplete) levelFinished(); //win level /***Row 6***/ for (int col = 0; col < 8; col++) { if (pixels[6][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[6], LOW); delay(loopDelay); digitalWrite(rows[6], HIGH); if (levelStart && shiftLevel) shiftRow(); //shift pixels in the level /***Row 7***/ for (int col = 0; col < 8; col++) { if (pixels[7][col] == 1) { digitalWrite(cols[col], HIGH); } else { digitalWrite(cols[col], LOW); } } digitalWrite(rows[7], LOW); delay(loopDelay); digitalWrite(rows[7], HIGH); if (enemyWon) gameOver(); //lose if hit by enemy updatePixels(); //update all pixels of the LED Matrix } void clearLeds() { // Clear display array for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { pixels[i][j] = 0; } } } /*** Populate Level on LED Matrix***/ void setPattern(int pattern) { if (levelsArrayIndex < 7) { //if not last level for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if ( levels[levelsArrayIndex][i][j] == 1 ) { //copy only "ones" to temporary matrix tempMatrix[i][j] = levels[levelsArrayIndex][i][j]; } } } } else { restart(); //restart if end of the game has been reached } } /***READ POTMETER***/ void readPot() { potVal = analogRead(potPin); //read potentiometer value potVal = map(potVal, 0, 1024, 0, 6); //map 1023 to 6 values for LED Matrix; //only column 0~6 are used to display player //raise max_in and max_out by one for even value distribution http://arduino.cc/forum/index.php?topic=72153.0 } /*** Refresh PLAYER ***/ void refreshPlayer() { byte _playerRows[2][8] = { //temp array to store player's position {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0} }; //draw player pixels _playerRows[0][potVal+1] = 1; _playerRows[1][potVal] = 1; _playerRows[1][potVal+1] = 1; _playerRows[1][potVal+2] = 1; for (int i = 6; i < 8; i++) { //rows 6-7 (where the player is) for (int j = 7; j >= 0; j--) { //update player positions if(_playerRows[i-6][j] == 1) { //copy only "ones" to temporary matrix tempMatrix[i][j] = _playerRows[i-6][j]; } } } } /***READ BUTTON***/ void readBtn() { int btnPress = digitalRead(btnPin); //read button state if (btnPress == 1 && !fired && !btnDown){ //if button is pressed bulletCol = potVal+1; //bullet column value on matrix btnDown = true; fired = true; } if (btnDown) { if (btnPress == 0) { btnDown = false; } } if (fired) { shoot(bulletCol); //play shoot animation } } /*** SHOOT ***/ void shoot(int _bulletCol) { if (bulletDelayCount == 2) { //act as a delay & determines bullet's speed bulletRow--; if (bulletRow < 0) { //if bullet reaches top (row 0) bulletRow = 5; //reset bullet row position fired = false; //bullet fire done } else { if (tempMatrix[bulletRow][_bulletCol] == 0) { //check if next pixel position is empty or not tempMatrix[bulletRow][_bulletCol] = 1; //new bullet pixel position and turn on } else { collisionBullet(bulletRow, _bulletCol); //bullet collides with pixel } } } else { tempMatrix[bulletRow][_bulletCol] = 1; //draw bullet pixel } bulletDelayCount = (bulletDelayCount+1) % 3; //bullet's speed counter } /*** BULLET COLLISION ***/ void collisionBullet(int _row, int _col) { int boomNoteDuration = 1000/4; //sound duration tone(13, boom, boomNoteDuration); //play sound when hit // noTone(13); levels[levelsArrayIndex][_row][_col] = 0; //remove 1 pixel that got shot from original level matrix tempMatrix[_row][_col] = 0; //update temp matrix for pixel display bulletRow = 5; //set original bullet row position back to 5 fired = false; //bullet fire done } /*** READ ENEMY ***/ void readEnemy() { if (!enemyWaiting && !enemyFired && !enemyBulletCollision){ //if enemy isn't doing anything enemyTime = millis(); //start timer enemyWaiting = true; } if (enemyWaiting) { if (millis() - enemyTime > enemyAttackSpeed[levelsArrayIndex]) { //attack speed for each level according to array int _row = 3; //temp row value boolean dobreak = false; //for double break; //loop to get the enemy's pixels of highest row (closest to the player) for (int i = 3; !dobreak && i >= 0; i--) { //backwards loop to get highest row for (int j = 0; j < 8; j++) { if(levels[levelsArrayIndex][i][j] == 1) { //if a "one" is found in the row _row = i; //save row index enemyBulletRow = _row + 1; //value to put enemy bullet 1 row below enemy dobreak = true; //double break; break; } } } enemyBottomCount = 0; //counter for number of "one" pixels in highest row (closest to the player) for (int c = 0; c < 8; c++) { //loop through highest row if(levels[levelsArrayIndex][_row][c] == 1) { //check if column contains "one" enemyBulletArray[enemyBottomCount][0] = _row; enemyBulletArray[enemyBottomCount][1] = c; enemyBottomCount++; } } enemyWaiting = false; enemyFired = true; //fire enemy bullet randomBullet = random(enemyBottomCount); //randomly select one of the column pixels } } if (enemyFired) { if (enemyBulletDelayCount == (enemyBulletSpeed[levelsArrayIndex]-1)) { //enemy bullet speed (higher value = slower) enemyBulletArray[randomBullet][0]++; //row + 1 if (enemyBulletArray[randomBullet][0] > 7) { //if bullet reaches bottom enemyBulletDelayCount = 0; //reset and remove bullet enemyFired = false; } else { if (tempMatrix[enemyBulletArray[randomBullet][0]] [enemyBulletArray[randomBullet][1]] == 0) { //if next row is empty tempMatrix[enemyBulletArray[randomBullet][0]] [enemyBulletArray[randomBullet][1]] = 1; //bullet claims row } else { //bullet hit something!! if (!levelComplete) { enemyBulletDelayCount = 0; enemyFired = false; enemyBulletCollision = true; } } } } else { tempMatrix[enemyBulletArray[randomBullet][0]][enemyBulletArray[randomBullet][1]] = 1; } enemyBulletDelayCount = (enemyBulletDelayCount+1) % enemyBulletSpeed[levelsArrayIndex]; //enemy bullet speed } if (enemyBulletCollision) { if (enemyBulletArray[randomBullet][0] == 6 || enemyBulletArray[randomBullet][0] == 7) { //check if enemy's bullet hit player // digitalWrite(13, HIGH); Serial.println("HIT!"); enemyWon = true; gameOverTime = millis(); } enemyBulletCollision = false; } } /*** SHIFTING LEVEL ***/ void shiftRow() { //testing purposes int _speed; if (levelsArrayIndex == 3) { _speed = shiftSpeed[0]; } else if (levelsArrayIndex == 5) { _speed = shiftSpeed[1]; } byte _levelTemp[5][8] = { //temp matrix array to store level pixel positions {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}, {0, 0, 0, 0, 0, 0, 0, 0} }; for (int i = 0; i < 5; i++) { //store in temp array for (int j = 0; j < 8; j++) { _levelTemp[i][j] = levels[levelsArrayIndex][i][j]; } } if (shiftCount == (_speed-1)) { //delay before shifting (roughly 100*18ms) if (shiftLeft == true) { //shift left //loops through top 5x8 of the matrix for (int i = 0; i < 5; i++) { for (int j = 0; j < 8; j++) { if (j == 7) { //prevents shifting column index 8 which doesn't exist levels[levelsArrayIndex][i][7] = _levelTemp[i][0]; } else { levels[levelsArrayIndex][i][j] = _levelTemp[i][j+1]; } } } shiftCount = 0; shiftLeft = false; shiftDown = true; } else if (shiftDown == true) { //shift down for (int i = 0; i < 5; i++) { for (int j = 0; j < 8; j++) { if ( i == 0 ) { //prevents shifting row index -1 which doesn't exist levels[levelsArrayIndex][i][j] = 0; } else { levels[levelsArrayIndex][i][j] = _levelTemp[i-1][j]; //shift down } } } shiftDown = false; shiftRight = true; } else if (shiftRight == true) { //shift right for (int i = 0; i < 5; i++) { for (int j = 7; j >= 0; j--) { if (j == 0) { //prevents shifting column index -1 which doesn't exist levels[levelsArrayIndex][i][0] = _levelTemp[i][7]; } else { levels[levelsArrayIndex][i][j] = _levelTemp[i][j-1]; } } } shiftCount = 0; shiftRight = false; shiftUp = true; } else if (shiftUp == true) { //shift up for (int i = 0; i < 5; i++) { for (int j = 0; j < 8; j++) { if ( i == 4 ) { //prevents shifting row index 5 levels[levelsArrayIndex][i][j] = 0; } else { levels[levelsArrayIndex][i][j] = _levelTemp[i+1][j]; //shift down } } } shiftUp = false; shiftLeft = true; } } shiftCount = (shiftCount+1) % _speed; //shifting delay } /*** PLAY LEVEL NUMBER ANIMATION ***/ void playLevelAni() { if (millis() - aniTime < 3000) { //time before transition from level number to actual level // if (levelsArrayIndex < 6 && millis() - aniTime > 3750) { //3000 // delay(500); // } } else { levelAni = false; //end animation levelStart = true; levelsArrayIndex++; if (levelsArrayIndex == 3 || levelsArrayIndex == 5) { //if level 2 or 3, initiate shifting shiftLeft = true; shiftDown = false; shiftRight = false; shiftUp = false; shiftLevel = true; } else { shiftLevel = false; } clearLeds(); } } void checkLevelState() { //check if all level pixels (enemies) are dead int _countLevelPixels = 0; for (int i = 0; i < 5; i++) { for (int j = 0; j < 8; j++) { if(levels[levelsArrayIndex][i][j] == 1){ _countLevelPixels++; //count the number of "one" pixels } } } if((_countLevelPixels) == 0){ //if nothing left, congratz! you beat the level! // digitalWrite(13, HIGH); // Serial.println("COMPLETE!"); completeTime = millis(); levelComplete = true; } } void levelFinished() { if (millis() - completeTime > 2000) { //time before transitioning to next level //reset all booleans enemyWaiting = false; enemyFired = false; enemyBulletCollision = false; levelStart = false; levelComplete = false; levelAni = true; aniTime = millis(); levelsArrayIndex++; } } /*** UPDATE PIXEL MATRIX ***/ void updatePixels() { //Use temporary 8x8 matrix to update actual matrix to prevent any bugs //Tends to get buggy when apply values straight to pixels[] matrix //Instead, storing all matrix values in a temp and apply values to pixels[] at once is more stable //copy all temp matrix values to actual display matrix for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { pixels[i][j] = tempMatrix[i][j]; } } //reset all values for temp matrix for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { tempMatrix[i][j] = 0; } } } void gameOver() { if( loseSound) { loseSound = false; for (int thisNote = 0; thisNote < 3; thisNote++) { // to calculate the note duration, take one second // divided by the note type. int noteDuration = 250/loseNoteDurations[thisNote]; tone(13, lose[thisNote],noteDuration); // to distinguish the notes, set a minimum time between them. // the note's duration + 30% seems to work well: int pauseBetweenNotes = noteDuration * 1.30; delay(pauseBetweenNotes); // stop the tone playing: noTone(13); } } if (millis() - gameOverTime < 3000) { //time before restarting delay(250); } else { restart(); } } void restart() { //reset all booleans enemyWon = false; enemyWaiting = false; enemyFired = false; enemyBulletCollision = false; levelAni = true; levelStart = false; levelComplete = false; levelsArrayIndex = 0; loseSound = true; //reset all levels for (int level = 0; level<_lvls; level++) { for (int x = 0; x<8; x++) { for (int y = 0; y<8; y++) { levels[level][x][y] = initial[level][x][y]; } } } aniTime = millis(); //run level 1 animation };