#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "SevSegShift.h"
#define WIDTH 128
#define HEIGHT 64
#define BRICK_WIDTH 16
#define BRICK_HEIGHT 8
#define LED_1 10
#define LED_2 11
#define LED_3 12
#define POTPIN 0
#define LDR_PIN 13
#define UP_BUTTON 9
#define DOWN_BUTTON 8
#define ENTER_BUTTON 7
#define DATA 2
#define SHCP 3
#define STCP 4
#define MAIN_SCREEN 0
#define GAME_SCREEN 1
#define EXIT_SCREEN 2
#define UPDATE_RATE 32
Adafruit_SSD1306 display = Adafruit_SSD1306(WIDTH, HEIGHT, &Wire);
SevSegShift sevsegshift(
DATA,
SHCP,
STCP,
1,
true
);
typedef struct paddle {
uint8_t x;
uint8_t y;
uint8_t width;
} Paddle;
typedef struct brick {
uint8_t x;
uint8_t y;
bool alive;
} Brick;
typedef struct ball {
float x;
float y;
float vert_speed;
float hori_speed;
uint8_t radius;
} Ball;
typedef struct boost {
uint8_t x;
uint8_t y;
bool alive;
} Boost;
typedef enum {
MAIN,
GAME,
EXIT,
CREDITS
} State;
State gameState = MAIN;
#define MENU_SIZE 3
char *menu[MENU_SIZE] = { "START", "EXIT", "CREDITS" };
const byte buttonPins[] = {9, 8, 7};
float base_vert_speed = 1.0;
float base_hori_speed = 1.0;
Paddle* player;
Ball* ball;
Boost* boost;
Brick bricks[10];
uint8_t alive_bricks = 0;
bool level_one[4][7] = {
{0, 1, 1, 1, 1, 1, 0},
{0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 0},
{0, 0, 0, 0, 0, 0, 0}
};
bool level_two[4][7] {
{0, 0, 1, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0},
{1, 0, 0, 1, 0, 0, 1},
{0, 1, 1, 1, 1, 1, 0}
};
unsigned long last_updated;
uint8_t total_lives = 3;
uint8_t points = 0;
uint8_t current_line = 0;
uint8_t current_level;
uint8_t button_up_clicked = 0;
uint8_t button_down_clicked = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
int val = analogRead(POTPIN);
val = map(val, 0, 1023, 0, 116);
byte numDigits = 2;
byte digitPins[] = {5, 6};
byte segmentPins[] = {0, 1, 2, 3, 4, 5, 6};
bool resistorsOnSegments = true;
byte hardwareConfig = COMMON_ANODE;
bool updateWithDelays = false;
bool leadingZeros = true;
bool disableDecPoint = false;
sevsegshift.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments,
updateWithDelays, leadingZeros, disableDecPoint);
sevsegshift.setBrightness(90);
sevsegshift.setNumber(points);
Paddle tempPaddle;
tempPaddle.x = (uint8_t)val;
tempPaddle.y = 63;
tempPaddle.width = 24;
player = &tempPaddle;
Ball tempBall;
tempBall.x = 24.0;
tempBall.y = 36.0;
tempBall.vert_speed = 1.0;
tempBall.hori_speed = 1.0;
tempBall.radius = 2;
ball = &tempBall;
Boost tempBoost;
tempBoost.x = 0;
tempBoost.y = 0;
tempBoost.alive = false;
boost = &tempBoost;
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
pinMode(LED_3, OUTPUT);
digitalWrite(LED_1, HIGH);
digitalWrite(LED_2, HIGH);
digitalWrite(LED_3, HIGH);
pinMode(UP_BUTTON, INPUT_PULLUP);
pinMode(DOWN_BUTTON, INPUT_PULLUP);
pinMode(ENTER_BUTTON, INPUT_PULLUP);
pinMode(LDR_PIN, INPUT);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.setCursor(0, 0);
display.setTextSize(2);
display.setTextColor(WHITE, BLACK);
display.display();
delay(1000);
showMenu();
display.display();
last_updated = millis();
display.invertDisplay(false);
}
void loop() {
// put your main code here, to run repeatedly:
sevsegshift.refreshDisplay();
if (digitalRead(LDR_PIN) == LOW) {
display.invertDisplay(false);
} else {
display.invertDisplay(true);
}
switch (gameState) {
case EXIT:
display.clearDisplay();
display.setCursor(0, 16);
display.setTextSize(1);
display.println("Thank you for playing our game!");
display.display();
return 0;
case CREDITS:
display.clearDisplay();
display.setCursor(16, 16);
display.setTextSize(1);
display.println("Utku Can Aydin");
display.display();
return 0;
case MAIN:
menuMovement();
enterButton();
if (gameState == GAME) {
display.clearDisplay();
initializeBricks(level_one);
break;
}
if (digitalRead(UP_BUTTON) == HIGH && button_up_clicked == 1)
button_up_clicked = 0;
if (digitalRead(DOWN_BUTTON) == HIGH && button_down_clicked == 1)
button_down_clicked = 0;
display.setCursor(0, current_line * 16);
display.print(">");
display.display();
break;
case GAME:
unsigned long current_time = millis();
bool update_display = false;
if (current_time > last_updated) {
update_display = true;
updatePaddle();
updateBall();
updateBoost();
checkCollision();
last_updated += UPDATE_RATE;
}
if (update_display) {
update_display = false;
display.display();
}
if (alive_bricks == 0 && current_level == 1) {
display.drawCircle(ball->x, ball->y, ball->radius, BLACK);
initializeBricks(level_two);
ball->x = 24.0;
ball->y = 36.0;
ball->hori_speed = 1.25;
ball->vert_speed = 1.25;
display.drawCircle(ball->x, ball->y, ball->radius, WHITE);
display.display();
delay(3000);
}
if (alive_bricks == 0 && current_level == 2) {
gameOver();
}
break;
}
}
void showMenu() {
display.clearDisplay();
for (int i = 0; i < MENU_SIZE; i++) {
display.print(" ");
display.println(menu[i]);
}
display.setCursor(0, 0);
display.print(">");
}
void menuMovement() {
if (digitalRead(UP_BUTTON) == LOW && button_up_clicked == 0) {
display.setCursor(0, current_line * 16);
display.print(' ');
current_line--;
button_up_clicked = 1;
if (current_line < 0) current_line = MENU_SIZE - 1;
}
if (digitalRead(DOWN_BUTTON) == LOW && button_down_clicked == 0) {
display.setCursor(0, current_line * 16);
display.print(' ');
current_line++;
button_down_clicked = 1;
if (current_line > MENU_SIZE - 1) current_line = 0;
}
}
void enterButton() {
if (digitalRead(ENTER_BUTTON) == LOW) {
if (current_line == 0 ) {
gameState = GAME;
} else if (current_line == 1) {
gameState = EXIT;
} else if (current_line == 2) {
gameState = CREDITS;
}
}
}
void updatePaddle() {
display.drawLine(player->x, 63, player->x + player->width, 63, BLACK);
int val = analogRead(POTPIN);
val = map(val, 0, 1023, 0, 103);
player->x = (uint8_t)val;
display.drawLine(player->x, 63, player->x + player->width, 63, WHITE);
}
void updateBall() {
display.drawCircle(ball->x, ball->y, ball->radius, BLACK);
ball->x = ball->x + ball->hori_speed;
ball->y = ball->y + ball->vert_speed;
display.drawCircle(ball->x, ball->y, ball->radius, WHITE);
}
void updateBoost() {
if (!boost->alive) {
return;
}
display.drawLine(boost->x, boost->y, boost->x, boost->y + 2, BLACK);
boost->y = boost->y + 1;
display.drawLine(boost->x, boost->y, boost->x, boost->y + 2, WHITE);
}
void checkCollision() {
// boost
if (boost->alive) {
if (player->y <= boost->y + 2) {
if (player->x <= boost->x && boost->x <= player->x + player->width) {
if (total_lives < 3) {
total_lives++;
updateLED();
}
}
display.drawLine(boost->x, boost->y, boost->x, boost->y + 2, BLACK);
boost->alive = false;
}
}
// paddle
if (player->y <= ball->y + ball->radius) {
if (player->x <= ball->x + ball->radius && ball->x - ball->radius <= player->x + player->width) {
ball->vert_speed *= -1;
return;
} else {
delay(1000);
display.drawCircle(ball->x, ball->y, ball->radius, BLACK);
ball->x = 24.0;
ball->y = 36.0;
ball->hori_speed = base_hori_speed;
ball->vert_speed = base_vert_speed;
total_lives--;
updateLED();
checkLives();
return;
}
}
// wall
if (ball->y - ball->radius <= 0) {
ball->vert_speed *= -1;
}
if (ball->x - ball->radius <= 0) {
ball->hori_speed *= -1;
}
if (127 <= ball->x + ball->radius) {
ball->hori_speed *= -1;
}
// bricks
for (int i = 0; i < 10; i++) {
if (bricks[i].alive) {
// check if there is a collision
if (checkCollision(bricks[i])) {
if (!boost->alive) {
uint8_t rand = random(10);
if (rand == 0) {
boost->x = bricks[i].x + BRICK_WIDTH/2;
boost->y = bricks[i].y + BRICK_WIDTH/2;
boost->alive = true;
}
}
display.drawRect(bricks[i].x, bricks[i].y, BRICK_WIDTH, BRICK_HEIGHT, BLACK);
bricks[i].alive = false;
alive_bricks--;
points++;
updateScore();
if (ball->y <= bricks[i].y || bricks[i].y + BRICK_HEIGHT <= ball->y) {
ball->vert_speed *= -1;
return;
}
ball->hori_speed *= -1;
return;
}
}
}
}
bool checkCollision(Brick brick) {
float testX = ball->x;
float testY = ball->y;
if (ball->x < brick.x)
testX = brick.x;
else if (brick.x + BRICK_WIDTH < ball->x)
testX = brick.x + BRICK_WIDTH;
if (ball->y < brick.y)
testY = brick.y;
else if (brick.y + BRICK_HEIGHT < ball->y)
testY = brick.y + BRICK_HEIGHT;
float distX = ball->x - testX;
float distY = ball->y - testY;
float distance = sqrt((distX * distX) + (distY * distY));
if (distance <= ball->radius)
return true;
return false;
}
void checkLives() {
if (total_lives <= 0) {
gameOver();
}
}
void gameOver() {
gameState = MAIN;
total_lives = 3;
current_line = 0;
current_level = 0;
boost->alive = false;
button_up_clicked = 0;
button_down_clicked = 0;
updateLED();
base_hori_speed = 1.0;
base_vert_speed = 1.0;
ball->x =24.0;
ball->y = 36.0;
ball->hori_speed = base_hori_speed;
ball->vert_speed = base_vert_speed;
display.clearDisplay();
display.setCursor(16, 16);
display.setTextSize(1);
display.print("Score: ");
display.println(points);
points = 0;
sevsegshift.setNumber(points);
display.display();
delay(3000);
display.setCursor(0, 0);
display.setTextSize(2);
display.clearDisplay();
showMenu();
display.display();
}
void initializeBricks(bool level[4][7]) {
int currentBrick = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 7; j++) {
if (level[i][j]) {
Brick temp;
temp.x = 8 + j * BRICK_WIDTH;
temp.y = i * BRICK_HEIGHT;
temp.alive = true;
display.drawRect(temp.x, temp.y, BRICK_WIDTH, BRICK_HEIGHT, WHITE);
bricks[currentBrick++] = temp;
}
}
}
current_level++;
alive_bricks = 10;
}
void updateScore() {
sevsegshift.setNumber(points);
return;
}
void updateLED() {
switch (total_lives) {
case 0:
digitalWrite(LED_1, LOW);
break;
case 1:
digitalWrite(LED_1, HIGH);
digitalWrite(LED_2, LOW);
break;
case 2:
digitalWrite(LED_2, HIGH);
digitalWrite(LED_3, LOW);
break;
case 3:
digitalWrite(LED_1, HIGH);
digitalWrite(LED_2, HIGH);
digitalWrite(LED_3, HIGH);
break;
}
}