// Space Hinvader by Davie Gatti of Survival Hacking Channel - wwww.survivalhacking.it
// A small Space Invaders game, with 2 MAX7219 8x8 dot matrix module.
// for a total of 8x16 pixels, that is a small game in a couple of original invaders sprites space.
// Due lack of display space, there are some game limitation. Aliens never shoot, and ther are not any saucers
// Also ther are not a barriers. Each time you kill all aliens, the game speed was increased.
// At the end of game, a Score is shown alternate with HigScore (underlined).
//
#include "LedControl.h" // Need the library to be added from arduino ide
#include "pitches.h" // Musical Notes include file
#include "EEPROM.h" // EEprom management include file
// pin 5 is connected to the DIN of MAX7219 pin 1
// pin 3 is connected to the CLK of MAX7219 pin 13
// pin 4 is connected to the LOAD of MAX7219 pin 12
// 2 as we are using 2 MAX7219
LedControl lc=LedControl(5,3,4,2);
// Variables initialization
long Score = 0; // Global Variable for Actual Score
long HighScore; // Global Variable for HiScore
long AlienSpeed = 80000; // Global Variable for Aliens speed
long AlienLevel = 0; // Global Variable for Aliens speed on the level
long AlienMul = 5000; // Global Variable for step speed increasing when shooting alien
long AlienDelay = 0; // Global Variable for Aliens Delay
char AllKilled = 0; // Global Variable that count active alien on screen
long HiscoreDelay =0; // Global Variable used to flip score with hiscore
long CannonDelay = 0; // Global Variable for Cannon Delay
byte CannonPos = 8; // Global Variable for Cannon position
long Explode = 0; // Global Variable for Explosion Marker
byte Ex = 0; // Global Variable for Explosion X
byte Ey = 0; // Global Variable for Explosion Y
byte AlienXo = 16; // Global Variable for Alien X previus (to remove aliens before redraw new position)
byte AlienYo = 0; // Global Variable for Alien Y previus (to remove aliens before redraw new position)
byte AlienX = 16; // Global Variable for Alien X
byte AlienY = 0; // Global Variable for Alien Y
byte MaxAlienX = 0; // Global Variable for Maximum Alien X position
byte MaxAlienY = 0; // Global Variable for Maximum Alien Y position
byte MinAlienX = 0; // Global Variable for Minimum Alien X position
byte AlienSound = 0; // Global Variable for Alien Sound sequance
boolean AlienXs = false; // Global Variable for define alien direction march
long FireDelay = 0; // Global Variable for Missile Delay
byte FireXPos = 8; // Global Variable for Missile X
byte FireYPos = 1; // Global Variable for Missile Y
const long CannonSpeed = 7000; // Cannon speed
const long FireSpeed = 5000; // Missile shooted speed
const byte FirstMax = 0; // Addres of First DotMatrix display
const byte SecondMax = 1; // Addres of Second DotMatrix display
const long ExplodeSpeed = 3000; // Explosions Speed
const long HiscoreSpeed = 100000; // Speed of flip from score to hiscore
const byte btnLeft = 12; // Left Button assigned to IO 13
const byte btnRight = 11; // Right Button assigned to IO 12
const byte btnFire = 10; // Fire Button assigned to IO 11
const byte buzzer = 9; // Buzzer assigned to IO 9
int win[] = { // Win melody array
NOTE_G3, NOTE_A4, NOTE_B4, NOTE_G3, NOTE_C4, 0, NOTE_C3
};
int noteDurations[] = {
8, 8, 8, 8, 8, 8, 8
};
// Arrays for define X, Y and status (on off) of alien pattern
const byte MaxAliens = 16; // Number of aliens on screen (see following array)
byte AliensY[MaxAliens] = { // Y coordinate of lighted led
0,0,0,0, // row 0
1,1,1,1, // row 1
2,2,2,2, // row 2
3,3,3,3, // row 3
};
byte AliensX[MaxAliens] = { // X coordinate of lighted led
0,3,6,9, // row 0
1,4,7,10, // row 1
2,5,8,11, // row 2
0,3,6,9, // row 3
};
boolean AliensStat[MaxAliens] = {// status (true=ON false=OFF)
true,true,true,true, // row 0
true,true,true,true, // row 1
true,true,true,true, // row 2
true,true,true,true, // row 3
};
// define a graphic array for draw number on dot matrix used with pointer multiplied by 5
byte Numbers[] =
{
B00000111, // Number 0
B00000101,
B00000101,
B00000101,
B00000111,
B00000010, // Number 1
B00000010,
B00000010,
B00000010,
B00000010,
B00000111, // Number 2
B00000100,
B00000111,
B00000001,
B00000111,
B00000111, // Number 3
B00000100,
B00000110,
B00000100,
B00000111,
B00000101, // Number 4
B00000101,
B00000111,
B00000100,
B00000100,
B00000111, // Number 5
B00000001,
B00000111,
B00000100,
B00000111,
B00000111, // Number 6
B00000001,
B00000111,
B00000101,
B00000111,
B00000111, // Number 7
B00000100,
B00000100,
B00000100,
B00000100,
B00000111, // Number 8
B00000101,
B00000111,
B00000101,
B00000111,
B00000111, // Number 9
B00000101,
B00000111,
B00000100,
B00000111,
};
// Array space needed to be filled with graphic data for print the score
byte Score1[] =
{
B00000000, // Score 1
B00000000,
B00000000,
B00000000,
B00000000,
B00000000,
B00000000,
B00000000,
};
byte Score2[] =
{
B00000000, // Score 2
B00000000,
B00000000,
B00000000,
B00000000,
B00000000,
B00000000,
B00000000,
};
// Game Over Alien animation graphics array
byte invader1[] =
{
B00011000, // Invader 1
B00111100,
B01111110,
B11011011,
B11111111,
B00100100,
B01011010,
B10100101
};
byte invader2[] =
{
B00011000, // Invader 2
B00111100,
B01011010,
B11111111,
B11111111,
B00100100,
B01011010,
B01000010
};
// Main Screen animation graphics array
byte SH1[] =
{
B00100000, // Main Screen 1
B01000000,
B11110011,
B10111001,
B11111011,
B11101010,
B00101011,
B01100000,
};
byte SH2[] =
{
B00000010, // Main Screen 2
B10100001,
B10100111,
B11101110,
B10101111,
B10101011,
B00001010,
B00000011,
};
byte SH3[] =
{
B00110000, // Main Screen 3
B01000011,
B11111001,
B11111011,
B10111010,
B11101011,
B00100000,
B00110000,
};
byte SH4[] =
{
B00000110, // Main Screen 4
B00000001,
B10101111,
B10101111,
B11101110,
B10101011,
B10100010,
B00000110,
};
// Startup function
void setup()
{
Serial.begin(9600); // Start Serial debug line
Serial.println("Space Hinvader"); // Debug string
pinMode(btnLeft, INPUT_PULLUP); // Define I/O data direction and pullups
pinMode(btnRight, INPUT_PULLUP);
pinMode(btnFire, INPUT_PULLUP);
pinMode(buzzer, OUTPUT);
lc.shutdown(FirstMax,false); // turn off power saving, enables display
lc.setIntensity(FirstMax,8); // sets brightness (0~15 possible values)
lc.clearDisplay(FirstMax); // clear screen
lc.shutdown(SecondMax,false); // turn off power saving, enables display
lc.setIntensity(SecondMax,8); // sets brightness (0~15 possible values)
lc.clearDisplay(SecondMax); // clear screen
MainScreen(); // Print Main Screen
GameInit(); // Initialize Variables
HighScore=EEPROMReadlong(0); // Read Hiscore From EEprom
PrintCannon(CannonPos); // Print initial cannon on screen
PrintAliens(AlienX,AlienY,AlienXo,AlienYo); // Print inital aliens
}
// read number (higscore) from eeprom
long EEPROMReadlong(long address) {
long four = EEPROM.read(address);
long three = EEPROM.read(address + 1);
long two = EEPROM.read(address + 2);
long one = EEPROM.read(address + 3);
return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
}
// write number (higscore) to eeprom
void EEPROMWritelong(int address, long value) {
byte four = (value & 0xFF);
byte three = ((value >> 8) & 0xFF);
byte two = ((value >> 16) & 0xFF);
byte one = ((value >> 24) & 0xFF);
EEPROM.write(address, four);
EEPROM.write(address + 1, three);
EEPROM.write(address + 2, two);
EEPROM.write(address + 3, one);
}
// Show main screen animation (Press fire to start)
void MainScreen() {
long MainAni = 0;
while (digitalRead(btnFire) == true) {
MainAni++;
if (MainAni == 30000) {
for (int i = 0; i < 10; i++) {
lc.setColumn(FirstMax,i,SH1[i]);
lc.setColumn(SecondMax,i,SH2[i]);
}
} else if (MainAni == 60000) {
for (int i = 0; i < 10; i++) {
lc.setColumn(FirstMax,i,SH3[i]);
lc.setColumn(SecondMax,i,SH4[i]);
}
MainAni=0;
}
}
while (digitalRead(btnFire) == false); // wait release fire key to continue
}
// Caluclate max/min of X & Y coordinates of active aliens on screen, and detect if all aliens are killed
// In this function I add an offset of 16 to fix a negative calculation, next when this result was used.
void CalcMaxAlien() {
MaxAlienX=0;
MaxAlienY=0;
MinAlienX=999;
AllKilled=0;
for (int nal = 0; nal < MaxAliens; nal++) { // cicle on all aliens in array
if (AliensStat[nal] == true) { // if alien is enabled, check coordinates
AllKilled++;
if (AliensX[nal] >= MaxAlienX) MaxAlienX = AliensX[nal]; // set new maximum X
if (AliensY[nal] >= MaxAlienY) MaxAlienY = AliensY[nal]; // set new maximum Y
if (MinAlienX >= AliensX[nal]) MinAlienX = AliensX[nal]; // set new minimum X
}
}
if (AllKilled==0) { // if all alien are disabled mean you killed all aliens !!!
PrintCannon(CannonPos); // Print cannon on screen to refresh at last kill
NextLevel(); // go to next level
}
}
// Play WIN melody and set parameters for next level
void NextLevel() {
delay(500);
PlayMelody(); // play WIN melody
AlienLevel+=500; // increase alien speed
Explode =ExplodeSpeed*4; // set variable to clear last explosion starting new level
GameInit();
}
// Set Default Variable for initial Game values
void GameInit() {
lc.clearDisplay(FirstMax); // clear screen
lc.clearDisplay(SecondMax); // clear screen
for (int nal = 0; nal < MaxAliens; nal++) { // Enable all aliens
AliensStat[nal] = true;
}
AlienX =16; // reset alien position on top (16 means position 0)
AlienY =0;
AlienXo =16;
AlienYo =0;
AlienXs = false; // Se flag alien go to left direction
Explode = 0;
PrintCannon(CannonPos); // Print initial cannon on screen
PrintAliens(AlienX,AlienY,AlienXo,AlienYo); // Print inital aliens
CalcMaxAlien(); // calculate new aliens edges
AlienSpeed = (AllKilled * AlienMul) + AlienMul; // set alien speed
AlienSpeed -= AlienLevel; // Add more speed trough level
Serial.print("Speed=");
Serial.println(AlienSpeed);
}
// Draw cannon on screen position, also clear all pixel around cannon to avoid shadows
void PrintCannon(byte pos)
{
pos--; // fix offset due position variable go fro 1 to 16, bud display go fro 0 to 15
SetLed(pos,6,true); // draw cannon head
SetLed(pos,7,true); // draw cannon base1
if (pos>0) { // check if outside the screen
SetLed(pos-1,6,false); // remove head shadow left
SetLed(pos-1,7,true); // draw cannon base2
}
if (pos>1) { // check if outside the screen
SetLed(pos-2,7,false); // remove base shadow left
}
if (pos<16) { // check if outside the screen
SetLed(pos+1,6,false); // remove head shadow right
SetLed(pos+1,7,true); // draw cannon base3
}
if (pos<15) { // check if outside the screen
SetLed(pos+2,7,false); // remove base shadow right
}
}
// Print Aliens giving values from array with X, Y offset and clear old alien with oX and oY offset
void PrintAliens(byte x, byte y,byte ox, byte oy){
for (int nal = 0; nal < MaxAliens; nal++) { // Remove old Aliens
if (AliensStat[nal] == true) { // check if enabled
SetLed( (AliensX[nal]+ox)-16, AliensY[nal]+oy,false);
}
}
for (int nal = 0; nal < MaxAliens; nal++) { // Draw Aliens if enabled
if (AliensStat[nal] == true) { // check if enabled
SetLed((AliensX[nal]+x)-16, AliensY[nal]+y,true); // draw
}
}
}
// Game Over animation e scoring
void GameOver() {
int mel = 2600; // setup local var
long InvaderAni = 0;
int AniCount = 0;
while (AniCount <= 10) { // 10 cycle animation
InvaderAni++;
mel--;
tone(buzzer, mel/10, 10); // Make sound
if (InvaderAni == 100) {
for (int i = 0; i < 10; i++) // show invaders
{
lc.setColumn(FirstMax,i,invader1[i]);
lc.setColumn(SecondMax,i,invader1[i]);
}
} else if (InvaderAni == 200) {
for (int i = 0; i < 10; i++) // show invaders
{
lc.setColumn(FirstMax,i,invader2[i]);
lc.setColumn(SecondMax,i,invader2[i]);
}
InvaderAni=0;
AniCount++;
}
}
ShowScore(); // show score and wait for a keypress
}
// Show final Score an High score
void ShowScore() {
int waitk = 0; // Set waikey flag
PrintNumber(Score,false); // Show score
while (waitk == 0) { // wait fire key to continue
if (digitalRead(btnFire) == false) { // If Fire button pressed
while (digitalRead(btnFire) == false); // wait release fire key to continue
MainScreen(); // Show Main Screen
Score=0; // reset score
GameInit(); // Initialize game
waitk =1; // reset waitkey flag
}
HiscoreDelay++;
if (HiscoreDelay >= HiscoreSpeed * 2) {
PrintNumber(HighScore,true); // Show Hi score
HiscoreDelay = 0;
} else if (HiscoreDelay == HiscoreSpeed) {
PrintNumber(Score,false); // Show score
}
}
}
// Function to play melody
void PlayMelody() {
for (int mel = 0; mel < 7; mel++) {
int noteDuration = 1000 / noteDurations[mel];
tone(buzzer, win[mel], noteDuration);
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
noTone(buzzer);
}
}
// Function to set pixel on 16x8 matrix addressed by column, row and status 0,0 = top left
void SetLed(byte X,byte Y, boolean OnOff)
{
if (X>7) {
lc.setLed(SecondMax,8-(X-7),Y,OnOff); // turns on/off LED at X, Y
} else {
lc.setLed(FirstMax,7-X,Y,OnOff); // turns on/off LED at X, Y
}
}
// Function to print decimal number from 0000 to 9999 on screen with underline flag
void PrintNumber(long num, boolean underline) {
byte Digit1; // convert long to 4 digit BCD
byte Digit2;
byte Digit3;
byte Digit4;
Digit1 = num/1000; // convert long to 4 digit BCD
Digit2 = (num-(Digit1*1000))/100;
Digit3 = (num-((Digit2*100)+(Digit1*1000)))/10;
Digit4 = num-((Digit3*10)+(Digit2*100)+(Digit1*1000));
for (int i = 0; i < 5; i++) // create bitmap graphics
{
Score1[i] = (Numbers[(Digit2*5)+i] << 4) + (Numbers[(Digit1*5)+i] & B00001111);
Score2[i] = (Numbers[(Digit4*5)+i] << 4) + (Numbers[(Digit3*5)+i] & B00001111);
}
if (underline == true) { // add underline
Score1[6] = 255;
Score2[6] = 255;
} else { // remove underline
Score1[6] = 0;
Score2[6] = 0;
}
for (int i = 0; i < 8; i++) // show score
{
lc.setColumn(FirstMax,i,Score1[i]);
lc.setColumn(SecondMax,i,Score2[i]);
}
}
// main loop game
void loop()
{
if (digitalRead(btnRight) == false) { // If Right button pressed
if (CannonDelay==0) { // check cannon delay counter to decide if cannon need be moved
CannonPos++; // move the cannon
if (CannonPos>16) CannonPos = 16; // check if position is outside display (right)
PrintCannon(CannonPos); // draw cannon in updated position
}
CannonDelay++; // if button continu to be pressed increase cannon delay counter
if (CannonDelay > CannonSpeed) CannonDelay = 0 ; // if cannon delay reach cannon speed value, clear it to allow to move cannon again
} else if (digitalRead(btnLeft) == false) { // If Left button pressed
if (CannonDelay==0) { // check cannon delay counter to decide if cannon need be moved
CannonPos--; // move the cannon
if (CannonPos < 1) CannonPos = 1; // check if position is outside display (right)
PrintCannon(CannonPos); // draw cannon in updated position
}
CannonDelay++; // if button continue to be pressed increase cannon delay counter
if (CannonDelay > CannonSpeed) CannonDelay = 0 ; // if cannon delay reach cannon speed value, clear it to allow to move cannon again
} else { // If left and right are released
CannonDelay=0;
}
if (digitalRead(btnFire) == false) { // Press fire button
if (FireYPos==0) { // check if fire is already shooted
FireXPos = CannonPos-1; // if not shooted, assign currect cannon X position to missile
FireYPos = 7; // set Missile Y position (Y>1 = shooting in progress)
tone(buzzer, NOTE_C6, 100);
}
}
// explosion management
if (Explode != 0) { // Check if active Explosion to Ex and Ey Position
Explode++;
if (Explode > ExplodeSpeed * 5) {
SetLed(Ex,Ey,false); // Clear explosion
Explode =0;
} else if (Explode == (ExplodeSpeed * 4)) {
SetLed(Ex,Ey,true); // Set explosion
} else if (Explode == (ExplodeSpeed * 3)) {
SetLed(Ex,Ey,false); // Clear explosion
} else if (Explode == (ExplodeSpeed * 2)) {
SetLed(Ex,Ey,true); // Set explosion
} else if (Explode == ExplodeSpeed) {
SetLed(Ex,Ey,false); // Clear explosion
}
}
// move aliens with tipycal pattern
if (AlienDelay > AlienSpeed) { // speed checking function
AlienDelay = 0;
if (AlienXs == false) { // check direction
AlienX++; // direction R
} else {
AlienX--; // direction L
}
if (MaxAlienX+AlienX > 31) { // if last alien on screen reach right edge of screen, invert direction and step down
AlienXs = true; // invert direction
AlienX--;
AlienY++; // scrolling down aliens
} else if (MinAlienX+AlienX < 16) { // if last alien on screen reach left edge of screen, invert direction and step down
AlienXs = false; // invert direction
AlienX++;
AlienY++; // scolling down aliens
}
if (MaxAlienY+AlienY >= 6) { // If aliens reached cannon GAME OVER
GameOver(); // call gameover function
}
PrintAliens(AlienX, AlienY, AlienXo, AlienYo); // draw updated aliens on screen
if (AlienSound == 0) { // Generate alien sound pattern
tone(buzzer, NOTE_F3, 100);
AlienSound =1;
} else if (AlienSound == 1) {
tone(buzzer, NOTE_E3, 100);
AlienSound =2;
} else if (AlienSound == 2) {
tone(buzzer, NOTE_D3, 100);
AlienSound =3;
} else if (AlienSound == 3) {
tone(buzzer, NOTE_C3, 100);
AlienSound =0;
} else {
AlienSound = 0;
}
AlienXo = AlienX; // store previous alien position offset, used to clear it when moving
AlienYo = AlienY;
} else {
AlienDelay++;
}
// missile management
if (FireYPos != 0) { // Fire active and check for collisions
if (FireDelay==0) { // Wait until firedelay was 0 to do next fire step
FireYPos--; // decrease missile position
if (FireYPos < 1) { // if missile reached top
SetLed(FireXPos,FireYPos,false); // Clear missile shadow when reach top
FireYPos = 0;
}
SetLed(FireXPos,FireYPos-1,true); // Draw missile
for (int nal = 0; nal < MaxAliens; nal++) { // Check for missile collided Alien
if ((((AliensX[nal]+AlienX)-16) == FireXPos) && (AliensY[nal]+AlienY == FireYPos-1) && (AliensStat[nal] == true)){
tone(buzzer, NOTE_C4, 100);
SetLed(FireXPos,FireYPos,false); // clear Alien fired
Ex=FireXPos; // Set X of explosion
Ey=FireYPos-1; // Set y of explosion
Explode = 1; // Activate Explosion
Score += (6-FireYPos)*4; // Increase score
if (Score >= 9999) Score = 9999; // Set 9999 limit to High Score due limitation to 4 digit
if (Score >= HighScore) HighScore = Score; // Set HiScore
EEPROMWritelong(0, HighScore); // Write HigScore on EEprom
Serial.print ("Score=");
Serial.println (Score);
FireYPos = 0; // Reset Fire status
AliensStat[nal] = false; // Set alien as fired in the array
CalcMaxAlien(); // Calculate new alien edges on screen
AlienSpeed = (AllKilled * AlienMul) + AlienMul;// set alien speed
}
}
if ((FireYPos > 0) && (FireYPos < 6)) {
SetLed(FireXPos,FireYPos,false); // clear missile shadow
}
}
FireDelay++; // increase delay variable
if (FireDelay > FireSpeed) FireDelay = 0 ; // check if fredelay reached firespeed setting
}
}
Comments