/*========================================================*/
/*
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
};