#include <Adafruit_NeoPixel.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#define PIN_NEO_PIXEL 13
#define NUM_PIXELS 64
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800);
const uint8_t face[8] =
{0x00, 0xA5, 0x42, 0xA5, 0x00, 0x7E, 0x81, 0x00};
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
};
#define ROW1 8
#define ROW2 9
#define ROW3 10
#define ROW4 11
#define COL1 A0 // A0 es PC0
#define COL2 A1 // A1 es PC1
#define COL3 A2 // A2 es PC2
#define COL4 A3 // A3 es PC3
#define BUZZER_PIN 12
#define EEPROM_ADDR_HS 0
#define MAX_LEVELS 9
#define CONF 0
#define PLAY 1
#define OVER 2
// Variables del juego
int posPaddleX = 3; //Posición de la paleta
int posPaddleY = 7;
int posBallX = 3; //Posición de la bola
int posBallY = 3;
int dirBallX = 1; //Dirección de la bola
int dirBallY = -1;
int state = CONF;
int nivel = 1;
int score = 0; // Puntaje actual del juego
int highScore = 0; // Puntaje más alto
volatile int lost = 0;
volatile char key = 0;
void setup() {
//cli();
PCICR |= (1 << PCIE1); // Habilita las interrupciones por cambio de pin para el puerto C
PCMSK1 |= (1 << PCINT8) | (1 << PCINT9) | (1 << PCINT10) | (1 << PCINT11); // Habilitar PCINT para A0 a A3
//sei();
Serial.begin(9600);
NeoPixel.begin();
// Configurar pines 8 a 11 como salidas (PORTB)
DDRB |= B00001111;
// Configurar pines A0 a A3 como entradas con pull-up (PORTD)
DDRC &= B11110000;
PORTC |= B00001111;
// Configuración del Timer1 para generar interrupciones cada segundo
TCCR1A = 0; // El registro de control A queda todo en 0
TCCR1B = 0; // Limpia registrador
TCNT1 = 0; // Inicializa el temporizador
TCCR1B |= (1 << WGM12) | (1 << CS12) | (1 << CS10); // Modo CTC, prescaler 1024
OCR1A = 15624; // Valor de comparación para 1 segundo (aproximadamente)
TIMSK1 = (1 << OCIE1A); // Habilitar interrupción por comparación A
PORTB = 0xF0; // Permite activar las interrupciones
Serial.println("Inicio del programa"); // Imprime el mensaje una sola vez al inicio
// Imprime los puntajes más altos almacenados en la EEPROM
for (int i = 0; i < MAX_LEVELS; i++) {
int hs = eeprom_read_word((uint16_t*)(EEPROM_ADDR_HS + i * 2));
Serial.print("Puntaje más alto para nivel ");
Serial.print(i + 1);
Serial.print(": ");
Serial.println(hs);
}
}
void loop() {
if (state == PLAY) {
// Lógica de visualización del juego
displayGame();
if (lost) {
displayFace();
Serial.println("GAME OVER");
Serial.println("-----------");
playGameOverSound(); // Reproduce el sonido de juego terminado
state = OVER;
}
}
}
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(255, 0, 0));
}
}
}
NeoPixel.show(); // Muestra los pixeles
}
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
}
void displayGame() {
NeoPixel.clear();
// Display paddle
NeoPixel.setPixelColor(posPaddleY * 8 + posPaddleX, NeoPixel.Color(0, 0, 255));
NeoPixel.setPixelColor(posPaddleY * 8 + posPaddleX + 1, NeoPixel.Color(0, 0, 255));
// Display ball
NeoPixel.setPixelColor(posBallY * 8 + posBallX, NeoPixel.Color(0, 2550, 0));
NeoPixel.show();
}
void playSound(int toneVal, int duration) {
noTone(BUZZER_PIN); // Si el buzzer no está ya sonando
tone(BUZZER_PIN, toneVal, duration);
}
void playGameOverSound() {
tone(BUZZER_PIN, 300, 500); // Frecuencia baja, duración más larga
delay(500);
tone(BUZZER_PIN, 200, 500);
}
int updateBall() {
posBallX += dirBallX;
posBallY += dirBallY;
bool soundPlayed = false;
// Colisión con las paredes laterales
if (posBallX < 0 || posBallX > 7) {
posBallX = max(0, min(posBallX, 7));
dirBallX *= -1;
if (!soundPlayed) {
playSound(150, 30);
soundPlayed = true;
}
}
// Colisión con la pared superior
if (posBallY < 0) {
posBallY = 0;
dirBallY = 1;
if (!soundPlayed) {
playSound(150, 30);
soundPlayed = true;
}
}
// Verifica colisión con la paleta
if (posBallY == 7 && (posBallX >= posPaddleX) && (posBallX <= posPaddleX + 1)) {
dirBallY = -1;
posBallY = 6;
playSound(300, 50);
updateScore(); // Incrementar el puntaje cuando la bola rebota en la paleta
return 0;
} else if (posBallY >= 8) {
return 1; // La pelota se perdió
}
return 0;
}
void reiniciarJuego() {
posPaddleX = 3;
posBallX = 3;
posBallY = 6;
dirBallX = 1;
dirBallY = -1;
lost = 0;
score = 0;
}
void updateScore() {
score++;
uint16_t addr = EEPROM_ADDR_HS + (nivel - 1) * 2; // Calcula la dirección de memoria para el nivel actual
int currentHighScore = eeprom_read_word((uint16_t*)addr);
if (score > currentHighScore) {
eeprom_update_word((uint16_t*)addr, score);
Serial.print("Nuevo puntaje más alto para nivel ");
Serial.print(nivel);
Serial.print(": ");
Serial.println(score);
}
}
ISR(TIMER1_COMPA_vect) {
if (state == PLAY) {
lost=updateBall();
}
}
char readKeypad() {
static unsigned long lastKeyPressTime = 0;
const unsigned long debounceTime = 100; // tiempo de anti-rebotes en milisegundos
unsigned long currentTime = millis();
volatile char _key = 0;
// Verifica si ha pasado suficiente tiempo desde la última presión registrada
if (currentTime - lastKeyPressTime > debounceTime) {
for (int row = 0; row < 4; row++) {
// Establece la fila actual en bajo y todas las demás en alto
PORTB = ~(1 << row);
// Comprueba cada columna en la fila actual
if (!(PINC & B00000001)) _key = row == 0 ? '1' : row == 1 ? '4' : row == 2 ? '7' : '*';
if (!(PINC & B00000010)) _key = row == 0 ? '2' : row == 1 ? '5' : row == 2 ? '8' : '0';
if (!(PINC & B00000100)) _key = row == 0 ? '3' : row == 1 ? '6' : row == 2 ? '9' : '#';
if (!(PINC & B00001000)) _key = row == 0 ? 'A' : row == 1 ? 'B' : row == 2 ? 'C' : 'D';
// Si se encuentra una tecla, rompe el bucle
if (_key != 0) {
lastKeyPressTime = currentTime; // Actualiza el tiempo de la última tecla presionada
break;
}
}
// Restablece las filas a su estado inicial
PORTB = 0xF0;
}
return _key;
}
ISR(PCINT1_vect) {
//Serial.println(PINC);
if ((PINC & 0x0F) != 0x0F) {
key = readKeypad();
if (state == CONF) {
// Si estamos en modo de configuración
if (key >= '1' && key <= '9') {
nivel = key - '0'; // Actualizar el nivel seleccionado
displayLevelSelection(nivel); // Muestra el nivel seleccionado
}
else if (key == '0') {
state = PLAY;
// Ajustar OCR1A basado en el nivel seleccionado
OCR1A = 4000 - 350 * (nivel);
}
}
else if (state == OVER) {
if (key == 'D') {
// Reiniciar el juego con la tecla 'D'
reiniciarJuego();
NeoPixel.clear();
NeoPixel.show();
state = CONF;
}
}
else if (state == PLAY) {
if (key == '*') {
// Mueve la paleta a la izquierda
if (posPaddleX > 0) posPaddleX--;
} else if (key == '#') {
// Mueve la paleta a la derecha
if (posPaddleX < 6) posPaddleX++;
}
}
}
}