#include <Adafruit_NeoPixel.h>
#include <avr/interrupt.h>
#include <SoftwareSerial.h>
#define PIN_NEO_PIXEL 13
#define NUM_PIXELS 64
#define BUZZER_PIN 12
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800);
const uint8_t levels[9][8] = {
{0x00, 0x44, 0x4C, 0x44, 0x44, 0x6E, 0x00, 0x00}, // Nivel 1
{0x00, 0x4E, 0x42, 0x4E, 0x48, 0x6E, 0x00, 0x00}, // Nivel 2
{0x00, 0x4E, 0x42, 0x4E, 0x42, 0x6E, 0x00, 0x00}, // Nivel 3
{0x00, 0x4A, 0x4A, 0x4E, 0x42, 0x62, 0x00, 0x00}, // Nivel 4
{0x00, 0x4E, 0x48, 0x4E, 0x42, 0x6E, 0x00, 0x00}, // Nivel 5
{0x00, 0x4E, 0x48, 0x4E, 0x4A, 0x6E, 0x00, 0x00}, // Nivel 6
{0x00, 0x4E, 0x42, 0x44, 0x48, 0x68, 0x00, 0x00}, // Nivel 7
{0x00, 0x4E, 0x4A, 0x4E, 0x4A, 0x6E, 0x00, 0x00}, // Nivel 8
{0x00, 0x4E, 0x4A, 0x4E, 0x42, 0x6E, 0x00, 0x00} // Nivel 9
};
const uint8_t face[8] =
{0x00, 0xA5, 0x42, 0xA5, 0x00, 0x7E, 0x81, 0x00}; // Lose
// Variables del juego
int posPaddleX1 = 3; //Posición de la paleta
int posPaddleY1 = 7;
int posPaddleX2 = 3;
int posPaddleY2 = 0;
int posBallX = 5; //Posición de la bola
int posBallY = 5;
int dirBallX = 1; //Dirección de la bola
int dirBallY = -1;
char buffer[100];
char buffer2[100];
char bufferb[100];
int len;
int len2;
int lenb;
int val;
int val2;
int level;
int numPlayers = 1;
int playerLost;
int Button;
int state = 0;
bool soundPlayed = false; // Añade esto al principio del archivo, junto a tus otras variables globales.
bool gameOverSoundPlayed = false; // Variable para rastrear si el sonido se ha reproducido
SoftwareSerial Mando2 (9, 10);//Mando 2: 9 - TX, 10 - RX.
volatile boolean updateBallFlag = false; // Bandera para actualizar la posición de la pelota
ISR(TIMER1_COMPA_vect) {
updateBallFlag = true; // Establece la bandera para actualizar la pelota
}
void setup() {
NeoPixel.begin();
Serial.begin(9600);
Serial.print("*IDN1?\r");
Mando2.begin(9600);
Mando2.print("*IDN2?\r");
TCCR1A = 0; // El registro de control A queda todo en 0
TCCR1B = 0; // Limpia registrador
TCNT1 = 0; // Inicializa el temporizador
//OCR1A = 15624;
TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS10); // Modo CTC, prescaler 1024
TIMSK1 = (1 << OCIE1A); // Habilitar interrupción por comparación A
}
void loop() {
switch (state) {
case 0: // Estado de configuración inicial
Read_Button();
Read_Control1();
level = (val * 9) / 256;
level += 1;
displayLevelSelection(level);
if (Button == 1) {
numPlayers = NumPlayers();
state = 1; // Cambiar al estado de juego
delay(1000);
}
break;
case 1: // Estado de juego activo
if (updateBallFlag) {
updateBallFlag = false; // Restablece la bandera
playerLost = updateBall();
if (playerLost != 0) {
Serial.println("Perdió Jugador " + String(playerLost));
displayFace();
playGameOverSound();
state = 2; // Cambio al estado de juego terminado
delay(500);
return; // Salir inmediatamente del loop() para evitar mostrar el juego
}
}
displayGame(); // Solo se llama si el estado sigue siendo 1
Read_Control1();
if (numPlayers == 2) {
Read_Control2();
}
break;
case 2: // Estado de juego terminado
displayFace();
Read_Button();
if (Button == 0) {
state = 0;
Serial.println("RST");
NeoPixel.clear();
NeoPixel.show();
resetGameSettings();
}
break;
}
}
int updateBall() {
// Actualizar posición de la bola antes de revisar colisiones
posBallX += dirBallX;
posBallY += dirBallY;
// Revisar colisiones con los bordes del área de juego
checkCollisions();
// Condiciones específicas basadas en el número de jugadores
if (numPlayers == 1) {
// Colisión con el paddle del jugador 1
if (posBallY == 7 && posBallX >= posPaddleX1 && posBallX <= posPaddleX1 + 1) {
dirBallY *= -1; // Invierte la dirección vertical
playSound(1000, 50); // Reproduce un sonido agudo
// Invertir dirección horizontal aleatoriamente para variar la trayectoria
}if (posBallY == 0 && posBallX >= posPaddleX2 && posBallX <= posPaddleX2 + 1) {
dirBallY *= -1; // Invierte la dirección vertical
} else if (posBallY > 7) { // Si la pelota pasa el borde inferior
return 1; // Jugador 1 pierde
}
} else if (numPlayers == 2) {
// Colisión con el paddle del jugador 2
if (posBallY == 0 && posBallX >= posPaddleX2 && posBallX <= posPaddleX2 + 1) {
dirBallY *= -1; // Invierte la dirección vertical
playSound(1000, 50); // Reproduce un sonido agudo
} else if (posBallY == 7 && posBallX >= posPaddleX1 && posBallX <= posPaddleX1 + 1) {
dirBallY *= -1; // Invierte la dirección vertical
playSound(1000, 50); // Reproduce un sonido agudo
} else if (posBallY < 0 || posBallY > 7) {
if (posBallY < 0) return 2; // Jugador 2 pierde
if (posBallY > 7) return 1; // Jugador 1 pierde
}
}
// No hay pérdidas, devolver 0
return 0;
}
void checkCollisions() {
// Colisión con los lados del área de juego
if (posBallX < 0 || posBallX > 7) {
dirBallX *= -1; // Invertir dirección horizontal
posBallX += dirBallX; // Ajustar posición para evitar que la pelota "salte"
playSound(150, 30);
}
if (posBallY < 0 || posBallY > 7) {
dirBallY *= -1; // Invertir dirección Y en colisiones superior/inferior
playSound(150, 30);
}
}
void displayGame() {
NeoPixel.clear();
//Display paddle Player 1
NeoPixel.setPixelColor(posPaddleY1*8+posPaddleX1, NeoPixel.Color(0, 255, 255));
NeoPixel.setPixelColor(posPaddleY1*8+posPaddleX1+1, NeoPixel.Color(0, 255, 255));
//Display paddle Player 2
if (numPlayers==2) {
NeoPixel.setPixelColor(posPaddleY2*8+posPaddleX2, NeoPixel.Color(255, 255, 0));
NeoPixel.setPixelColor(posPaddleY2*8+posPaddleX2+1, NeoPixel.Color(255, 255, 0));
}
//Display ball
NeoPixel.setPixelColor(posBallY*8+posBallX, NeoPixel.Color(0, 255, 0));
NeoPixel.show();
}
void resetGameSettings() {
posBallX = 5;
posBallY = 5;
dirBallX = 1;
dirBallY = -1;
Button = 0;
gameOverSoundPlayed = false; // Resetear la variable para permitir que el sonido se reproduzca de nuevo
}
void playSound(int toneVal, int duration) {
noTone(BUZZER_PIN); // Si el buzzer no está ya sonando
tone(BUZZER_PIN, toneVal, duration);
}
void playGameOverSound() {
if (!gameOverSoundPlayed) {
tone(BUZZER_PIN, 200, 500); // Frecuencia baja, duración más larga
delay(500);
noTone(BUZZER_PIN); // Detiene el sonido
gameOverSoundPlayed = true; // Marca el sonido como reproducido
}
}
//Función para seleccionar el nivel
void displayLevelSelection(int level) {
NeoPixel.clear();
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
if (bitRead(levels[level - 1][r], 7 - c)) {
NeoPixel.setPixelColor(r * 8 + c, NeoPixel.Color(191, 255, 0));
}
}
}
NeoPixel.show(); // Muestra los pixeles
OCR1A = 4000 - 350 * (level);
//Serial.print("OCR1A for level "); Serial.print(level); Serial.print(" is "); Serial.println(OCR1A);
}
void displayFace() {
NeoPixel.clear(); //Permite borrar cualquier estado anterior
for (int r = 0; r < 8; r++) { // bucles "for" los cuales recorren filas y columnas
for (int c = 0; c < 8; c++) {
if (bitRead(face[r], 7 - c)) { // selecciona el byte correspondiente a la fila actual de la matriz
NeoPixel.setPixelColor(r * 8 + c, NeoPixel.Color(255, 0, 0));
}
}
}
NeoPixel.show(); // Muestra los pixeles en el hardware
}
int NumPlayers(){
int contador = 0;
Serial.print("SENSE:PADDLE?\r");
len = Serial.readBytesUntil('\r', buffer, sizeof(buffer));
if(len>0){
contador ++;
}
Mando2.print("SENSE:PADDLE?\r");
len2 = Mando2.readBytesUntil('\r', buffer2, sizeof(buffer2));
if (len2>0) {
contador++;
}
return contador;
}
void Read_Control1(){
Serial.print("SENSE:PADDLE?\r");
len = Serial.readBytesUntil('\r', buffer, sizeof(buffer));
if (len>0) {
buffer[len]=0;
val = atoi(buffer);
posPaddleX1 = min(6, max(0, (val / 31))); // Ajusta para asegurarse de que el paddle no se salga de la pantalla
}
}
void Read_Control2(){
Mando2.print("SENSE:PADDLE?\r");
len2 = Mando2.readBytesUntil('\r', buffer2, sizeof(buffer2));
if (len2>0) {
buffer2[len2]=0;
val2 = atoi(buffer2);
posPaddleX2 = min(6, max(0, (val2 / 38))); // Asegúrate de ajustar esto también según cómo calcules val2
}
}
void Read_Button() {
Serial.print("SENSE:START?\r");
lenb = Serial.readBytesUntil('\r', bufferb, sizeof(bufferb));
if (lenb > 0) {
bufferb[lenb] = 0;
Button = atoi(bufferb);
}
}