i think i just need to better educate myself, but here is the code.
/**************************************************************************
*
* ARDUINO SPACE INVADERS 1.2
*
* Jan/2022 Giovanni Verrua
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Connections to Arduino Uno/Nano:
*
* Connect a button to pin 12 (digital) and +5V (button Fire / Start)
* Connect a 100 kohm resistor between pin 12 (digital) and GND
*
* If you want to use a Wheel, connect a 100 k ohm potentiomenter to:
* GND (left pin), +5V (right pin), A0 (middle pin)
*
* If you wan to use two buttons for left and right (like the original arcade)
* Connect two buttons to pin 4 and +5V (but.left), and pin 5 and +5V (but.right)
* Connect two 100 kohm resistors between pin 5 and GND, and pin 6 and GND*
*
* TV composite out:
* Connect 1k ohm resistor from pin 9 to TV out (+)
* Connect 470 ohm resistor from pin 7 to TV out (+)
* Connect GND to TV out (-)
*
* Audio:
* Connect a speaker between pin 11 and GND
*
* Release history
*
* 1.2 - added audio effects
* - added an option for using buttons or potentiometer
* to control the cannon (see: #define USE_WHEEL)
* - emproved the size of the cannon (you can go back to the
* old size changing cn1, cn2, cn3 with ca1, ca2, ca3
* (look in the cannon section below)
*
* 1.1 fixed some bugs
* 1.0 initial release
*
**************************************************************************/
#include <TVout.h>
#include "bitmap.h"
#include <fontALL.h>
//----------------------------------------------------------------------------------
//#define USE_WHEEL activate this if you want to use potentiometer instead of buttons
#define CANNON_SPEED 20 //higher value, slower cannon - only for button control
#define BUTTON_LEFT 4
#define BUTTON_RIGHT 5
#define BUTTON_FIRE 12
#define WHEEL_MOVE A0 //Analog A0 - potentiometer
#define RATIO_WHEEL 8.2 //Ratio Video X resolution Vs. potentiometer resolution
//----------------------------------------------------------------------------------
#define CAN_Y_POS 86 //Cannon Y Position
#define SHD_Y_POS 70 //Shield Y Position
#define ASHIP_SPEED 50 //higher value, slower ship (must to be >0)
#define ALIEN_SPEED 20 //higher value, slower aliens (must to be >0)
TVout TV;
int gamSt = 0; //0 = menu, 1 = in game, 2 = game over
//int canMove = 0; //cannon move (wheel value)
//int canShot = 0; //cannon shot (button pressed)
int canPosX = 0; //cannon position
int canOldX = 0; //cannon old position
int canFire = 0; //cannon fire 1 = bullet flying, 0 = idle
int canFirX = 0; //bullet X position
int canFirY = 0; //bullet Y position
//int lives = 3;
int aSound = 0;
int gLevel = 0;
int begiY = 0; //Y begin
int shftY = 1; //Y movement
int shftX = 0; //X movement
int direX = 1; //X direction (1 ; -1)
int corrZ = 0;
int coluR = 8; //rightmost column with at least 1 alien
int rwLow = 4; //last lower row with at least 1 alien
int alien = 5*9; //aliens still alive
int aliFire = -1; //alien fire 1 = bomb falling, 0 = idle
int aliFirX = 0; //bomb X position
int aliFirY = 0; //bomb Y position
int aMatrix[5][9] = { {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} };
//===================================================================================== SETUP
void setup() {
//Serial.begin(9600);
TV.begin(_PAL); //128x96 default
#ifdef USE_WHEEL
pinMode(WHEEL_MOVE,INPUT);
#else
pinMode(BUTTON_LEFT,INPUT);
pinMode(BUTTON_RIGHT,INPUT);
#endif
pinMode(BUTTON_FIRE,INPUT);
canFire = 0;
canOldX = -2;
gamSt = 0;
}
//===================================================================================== AlienReset
void AlienReset() {
for (int i=0;i<5;i++){
for (int j=0;j<9;j++){
aMatrix[i][j] = 1;
}
}
begiY = gLevel*2; //Y begin - every level 2 pixels lower
shftY = 1; //Y movement
shftX = 0; //X movement
direX = 1; //X direction (1 ; -1)
corrZ = 0;
coluR = 8; //rightmost column with at least 1 alien
rwLow = 4; //last lower row with at least 1 alien
alien = 5*9; //aliens still alive
aliFire = -1;
}
//============================================================================= AlienCheck
void AlienCheck() {
alien = 0;
for (int i=0;i<5;i++){
for (int j=0;j<9;j++){
if (aMatrix[i][j] == 1) alien ++; //how many aliens still alive
}
}
if (alien >0){ //checking what are the leftmost and rightmost columns with at least 1 alien alive
//shift all the columns leftward if the leftmost column is all empty
// if (shftX == 0 && direX == -1) {//solo quando sto tornando, cos evito di fare degli scatti bruschi
if (aMatrix[0][0] == 0 && aMatrix[1][0] == 0 && aMatrix[2][0] == 0 && aMatrix[3][0] == 0 && aMatrix[4][0] == 0){
for (int i=0;i<5;i++){
for (int j=0;j<8;j++){
aMatrix[i][j] = aMatrix[i][j+1] ;
}
}
for (int i=0;i<5;i++){
aMatrix[i][8] = 0;
}
}
// }
//check if the rightmost column (that at last check had at least 1 alien) is now empty
if (aMatrix[0][coluR] == 0 && aMatrix[1][coluR] == 0 && aMatrix[2][coluR] == 0 && aMatrix[3][coluR] == 0 && aMatrix[4][coluR] == 0) coluR -- ;
//check if the lowest row (that at lat check had at least 1 alien) is now empty
if (aMatrix[rwLow][0] == 0 && aMatrix[rwLow][1] == 0 && aMatrix[rwLow][2] == 0 && aMatrix[rwLow][3] == 0 && aMatrix[rwLow][4] == 0 &&
aMatrix[rwLow][5] == 0 && aMatrix[rwLow][6] == 0 && aMatrix[rwLow][7] == 0 && aMatrix[rwLow][8] == 0) rwLow -- ;
}
}
//============================================================================= AlienDraw()
void alienDraw() {
TV.draw_rect( 0, 00+begiY+shftY-corrZ, 127, (10*rwLow)+9, 0, 0 ); //deleting the old aliens position
for (int j=0;j<=coluR;j++){ //drawing the columns, considering the left/rightmost columns with at least 1 alien alive
if( shftX % 2 == 0){
if (aMatrix[0][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+00+shftY, a3a);
if (aMatrix[1][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+10+shftY, a2a);
if (aMatrix[2][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+20+shftY, a2a);
if (aMatrix[3][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+30+shftY, a1a);
if (aMatrix[4][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+40+shftY, a1a);
}
else {
if (aMatrix[0][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+00+shftY, a3b);
if (aMatrix[1][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+10+shftY, a2b);
if (aMatrix[2][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+20+shftY, a2b);
if (aMatrix[3][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+30+shftY, a1b);
if (aMatrix[4][j] == 1) TV.bitmap((j)*10+(shftX*2), begiY+40+shftY, a1b);
}
}
aSound++;
if (aSound >3) aSound = 0;
TV.tone(100 - aSound*22,50);
//TV.delay_frame(3); NOOOO!!!!!!!!!! This will compromise the game speed, don't activate!
}
//============================================================================= AlienMove
void AlienMove() {
shftX += direX;
corrZ = 0;
if (begiY+shftY >=2) corrZ = 2;
if ( (shftX<= 20 + (8-coluR)*5 && direX == 1) || (shftX>=0 && direX == -1) ){ //checking the horizontal movement of the aliens, considering the left/rightmost columns with at least 1 alien alive
alienDraw() ;
}
else {
shftY = shftY + 2;
direX = direX *-1;
shftX += direX; //don't remove!
alienDraw() ;
}
}
//============================================================================= AlienHit
void alienHit() {
for (int j=0;j<=coluR;j++){ //drawing the columns, considering the left/rightmost columns with at least 1 alien alive
for (int i=0;i<=4;i++){ //drawing the columns, considering the left/rightmost columns with at least 1 alien alive
if (canFirX >=(j)*10+(shftX*2) && canFirX <=(j)*10+(shftX*2)+8 && canFirY >= begiY+ i*10 +shftY && canFirY <= begiY+ i*10 +shftY+8) {
aMatrix[i][j] = 0;
j = coluR+1;
i = 5;
canFire = -1;
TV.tone(2000,50);
TV.delay_frame(1);
}
}
}
alienDraw() ;
}
//===================================================================================== LOOP
void loop() {
unsigned long miab = millis() ; //delay - alien bomb
unsigned long micb = millis() ; //delay - cannon bullet
unsigned long mill = millis() ; //delay - aliens
unsigned long mish = millis() ; //delay - ship
#ifndef USE_WHEEL
unsigned long cand = millis() ; //cannon delay
#endif
int canMove = 0;
int Score = 0;
int lives = 3;
int shpMove = 0;
int shpPosX = 0;
//-----------------------------------------------------------------------------------------------------MENU
if (gamSt == 0){ //MENU
TV.clear_screen();
TV.bitmap(0, 0, logo);
TV.bitmap(0, 32, start);
delay(500);
while ( digitalRead(BUTTON_FIRE) == 0){
//canShot = digitalRead(BUTTON_FIRE); //cannon shot
}
gamSt = 1;
delay(300);
}
//-----------------------------------------------------------------------------------------------------IN GAME
if (gamSt == 1){ // IN GAME
Score = 0;
lives = 3;
canOldX = -2;
aSound = 3;
gLevel = 0;
TV.clear_screen();
TV.bitmap(10,SHD_Y_POS,shd);
TV.bitmap(35,SHD_Y_POS,shd);
TV.bitmap(60,SHD_Y_POS,shd);
TV.bitmap(85,SHD_Y_POS,shd);
AlienReset();
AlienCheck();
delay(50);
/* shouldn't be needed....
#ifdef USE_WHEEL
canMove = analogRead(WHEEL_MOVE); //cannon move
#endif
canShot = digitalRead(BUTTON_FIRE); //cannon shot
*/
shftY = 0 ;
//-----------------------------------------------------------------------------------------------------Main Cycle-begin-\
while ( (shftY/2 < 19 + (4 - rwLow)*5) && gamSt != 2 ) { //Vertical moving.
//-------------------------------------------------------------Cannon-begin-\
//-----------------------------------cannon move
#ifdef USE_WHEEL //------------------------------------- using potentiometer -----------------------------------------------|
canMove = analogRead(WHEEL_MOVE); //cannon move
canPosX = canMove / RATIO_WHEEL ;
if (canPosX -1 == canOldX || canPosX + 1 == canOldX) canPosX = canOldX; //Avoiding flickering
#else //------------------------------------- using buttons left and right----------------------------------------|
if (cand + CANNON_SPEED < millis()) {
cand = millis() ;
if (digitalRead(BUTTON_LEFT )==1 && canPosX >0 ) canPosX-- ;
if (digitalRead(BUTTON_RIGHT)==1 && canPosX <110) canPosX++ ;
}
#endif //-----------------------------------------------------------------------------------------------------------|
if (canOldX != canPosX) {
TV.bitmap(canOldX, CAN_Y_POS, cnn);
if (lives == 3) TV.bitmap(canPosX, CAN_Y_POS, cn3); //use ca3 instead of cn3 if you want a wider cannon
if (lives == 2) TV.bitmap(canPosX, CAN_Y_POS, cn2); //use ca2 instead of cn2 if you want a wider cannon
if (lives == 1) TV.bitmap(canPosX, CAN_Y_POS, cn1); //use ca1 instead of cn1 if you want a wider cannon
canOldX = canPosX;
}
//-----------------------------------cannon fire
//canShot = digitalRead(BUTTON_FIRE); //cannon shot
if (canFire == -1){ //cannon fire 1 = bullet flying, 0 = idle -1 = Hit
TV.draw_line(canFirX,canFirY, canFirX,canFirY+4,0);
canFire = 0;
}
if (canFire == 0){ //cannon fire 1 = bullet flying, 0 = idle
if (digitalRead(BUTTON_FIRE) == 1 && micb < millis() ) {
canFire = 1;
canFirX = canPosX+8; //bullet X position
canFirY = 81; //bullet Y position
}
}
if (canFire == 1){ //cannon fire 1 = bullet flying, 0 = idle
if (micb+10 < millis()){
if (TV.get_pixel(canFirX,canFirY-1) == 1){
if (shpMove == 1 && canFirY < 10) {
//hit the ship
TV.tone(2000,50);
Score += 10;
shpMove = 0;
canFire = -1;
TV.draw_rect(00,00,128,9,0,0);
TV.draw_line(canFirX,canFirY-2,canFirX,canFirY+4,0);
//micb = millis() + 200; //delay before next shot
}
else {
if (canFirY <=begiY+(10*rwLow)+9+shftY){
//hit an alien
alienHit();
Score ++;
//micb = millis() + 200; //delay before next shot
if ((00+begiY+shftY-corrZ) > 4 && shpMove == 0){
TV.select_font(font4x6);
TV.print(00,00,"SCORE:");
TV.print(40,00,Score);
}
}
else{
//hit the shield
canFire = -1;
TV.draw_line(canFirX,canFirY-2,canFirX,canFirY+4,0);
//micb = millis() + 200; //delay before next shot
}
}
micb = millis() + 200; //delay before next shot
TV.draw_line(canFirX,canFirY,canFirX,canFirY+4,0);
canFire = -1; //error trap
}
else
{
canFirY --;
micb = millis();
if (canFirY <= 0){
canFire = 0;
TV.draw_line(canFirX,canFirY,canFirX,canFirY+4,0);
micb = millis() + 200; //delay before next shot
}
}
if (canFire == 1){
TV.draw_line(canFirX,canFirY+1,canFirX,canFirY+4,0);
TV.draw_line(canFirX,canFirY, canFirX,canFirY+3,1);
}
}
}
if (alien == 0){
gLevel++;
TV.clear_screen();
AlienReset();
TV.bitmap(10,SHD_Y_POS,shd);
TV.bitmap(35,SHD_Y_POS,shd);
TV.bitmap(60,SHD_Y_POS,shd);
TV.bitmap(85,SHD_Y_POS,shd);
canFire = 0;
canOldX = -2;
}
//-------------------------------------------------------------Cannon-end---/
//-------------------------------------------------------------Ship begin--\
if ((00+begiY+shftY-corrZ) > 8 ){ //if there's enough space in the upper part of the screen
if (shpMove == 0){
if (mish + 15000+random(10000,25000) < millis()){ //every 25-40 seconds
shpMove = 1;
TV.draw_rect(00,00,128,9,0,0);
mish = millis() - ASHIP_SPEED - 1 ;
shpPosX = 1; //not = 0!
}
}
if (shpMove == 1){
if (mish + ASHIP_SPEED < millis()){
TV.bitmap(shpPosX-1,0,cnn);
TV.bitmap(shpPosX,0,shp);
shpPosX++ ;
mish = millis();
if (shpPosX >= 112){
shpMove = 0;
TV.draw_rect(00,00,128,9,0,0);
}
}
}
}
//-------------------------------------------------------------Aliens-begin-\
if (mill + ALIEN_SPEED*alien < millis()){
mill = millis();
AlienCheck();
AlienMove();
if ((00+begiY+shftY-corrZ) > 4 && shpMove == 0){
TV.select_font(font4x6);
TV.print(00,00,"SCORE:");
TV.print(40,00,Score);
}
}
//-------------------------------------------------------------Aliens-end---/
//--------------------------------------------------------Aliens bomb-begin-\
//----------------------------------------------------
if (aliFire == -1){ // end of bomb fall
//TV.draw_line(aliFirX,aliFirY, aliFirX,aliFirY+4,0);
aliFire = 0;
miab = millis() + 40*alien ;
}
//----------------------------------------------------
if (aliFire == 0){ //0 = idle - waiting for the next bomb
if (miab+10 < millis()){ //drop the bomb at the right time
aliFirY = begiY+(10*rwLow)+9+shftY; //bomb Y position
aliFire = 1;
//to be improved: should not drop bomb from an empty column
if (canPosX >=(shftX*2) && canPosX <=10*coluR+(shftX*2)){ //the cannon is below the aliens
aliFirX = canPosX+8+random(-4,+4); //bomb X position = center of cannon
}
if (canPosX < shftX*2){ //drop the bomb as closest as possible to the cannon
aliFirX = shftX*2;
}
if (canPosX > 10*coluR+(shftX*2)){ //drop the bomb as closest as possible to the cannon
aliFirX = 10*coluR+(shftX*2);
}
//check if there's at least an alien over it
int fndAlien = 0;
for (int i=aliFirY;i >begiY+9+shftY;i-=3){
if (TV.get_pixel(aliFirX,i) == 1) fndAlien = 1;
}
if (fndAlien==0){ //the cannon is under an empty column, so there's no need to drop a bomb
aliFire = 0;
}
}
}
//----------------------------------------------------
if (aliFire == 1){ //bomb dropping
if (miab+10 < millis()){
TV.draw_line(aliFirX,aliFirY-1,aliFirX,aliFirY-4,0);
TV.draw_line(aliFirX,aliFirY,aliFirX,aliFirY-3,1);
if (TV.get_pixel(aliFirX,aliFirY+1) == 1){
aliFire = -1;
TV.draw_line(aliFirX,aliFirY,aliFirX,aliFirY-3,0);
if (aliFirY+1 >=CAN_Y_POS) {
//hit the cannon.
for (int i=0;i<27;i++){
TV.tone(70,10);
TV.delay_frame(1);
TV.bitmap(canPosX, CAN_Y_POS, hit);
delay(5);
TV.tone(40,10);
TV.delay_frame(1);
if (lives == 3) TV.bitmap(canPosX, CAN_Y_POS, cn3); //use ca3 instead of cn3 if you want a wider cannon
if (lives == 2) TV.bitmap(canPosX, CAN_Y_POS, cn2); //use ca2 instead of cn2 if you want a wider cannon
if (lives == 1) TV.bitmap(canPosX, CAN_Y_POS, cn1); //use ca1 instead of cn1 if you want a wider cannon
delay(5);
}
lives --;
delay(500);
if (lives <=0) gamSt = 2;
if (lives == 3) TV.bitmap(canPosX, CAN_Y_POS, cn3); //use ca3 instead of cn3 if you want a wider cannon
if (lives == 2) TV.bitmap(canPosX, CAN_Y_POS, cn2); //use ca2 instead of cn2 if you want a wider cannon
if (lives == 1) TV.bitmap(canPosX, CAN_Y_POS, cn1); //use ca1 instead of cn1 if you want a wider cannon
}
else {
//hit the shield. Just open an hole.
TV.draw_line(aliFirX,aliFirY+2,aliFirX,aliFirY-3,0);
}
}
else
{
aliFirY ++;
miab = millis();
if (aliFirY >= 92){
aliFire = -1;
TV.draw_line(aliFirX,aliFirY,aliFirX,aliFirY-4,0);
}
}
}
}
//--------------------------------------------------------Aliens bomb-end---/
}
//-----------------------------------------------------------------------------------------------------Main Cycle-end--- /
gamSt = 2 ;
}
//-----------------------------------------------------------------------------------------------------GAME OVER
if (gamSt == 2){ //GAME OVER
gamSt = 0;
TV.clear_screen();
TV.bitmap(0, 0, logo);
TV.bitmap(0, 32, over);
TV.select_font(font6x8);
TV.print(40,90,"SCORE:");
TV.print(76,90,Score);
delay(1000);
//must to release and press again the button before to continue
while ( digitalRead(BUTTON_FIRE) == 1){
//canShot = digitalRead(BUTTON_FIRE); //cannon shot
}
while ( digitalRead(BUTTON_FIRE) == 0){
//canShot = digitalRead(BUTTON_FIRE); //cannon shot
}
}
}