RetroBoy: SimonSays Console
RetroBoy: la consola de arduino
Este proyecto ha sido realizado por Tomasz Paterek del grupo 10 durante el curso 2021-2022 para la asignatura Diseño de Sistemas Empotrados en la URJC. Consiste en el juego Simón Dice, programado en arduino y montado en una caja para lograr la apariencia de una consola retro.
HARDWARE
A continuación se detalla el montaje de los componentes del proyecto en un circuito de la protoboard que conectará todos los componentes a la placa de arduino.
Esquema del circuito
La siguiente imagen muestra el montaje del circuito con el cual se ha desarrollado el proyecto:
- Pulsadores: los pulsadores se han conectado a las entradas analógicas de la placa de arduino para poder tomar sus valores. Estos a su vez se conectaron a la fuente de alimentación de la placa y a la toma de tierra.
- Diodos LED: los diodos se han conectado a los pines digitales por los cuales se enviarán las señales para encender y apagar los leds.
- Piezoeléctrico: conectado al pin número 7 y a la toma de tierra.
- LCD: se conecta al adaptador I2C para resumir su número de pines de 16 a 4 para que así la placa de arduino tenga pines suficientes para los demás componentes.
- Adaptador I2C: VCC y GND se conectan a sus respectivos pines de la placa de arduino y los pines SDA y SCL a los pines A4 y A5 de arduino.
Materiales
Para el montaje se han necesitado los siguientes materiales:
Componente | Precio | Cantidad |
Placa Arduino Uno | – | 1 |
LCD 16×2 | – | 1 |
Adaptador LCD I2C | 3,50€ | 1 |
Diodos LED | – | 4 |
Pulsadores | 11,20€ | 4 |
Protoboard | – | 1 |
Resistencias 1K | – | 8 |
Piezoeléctrico | – | 1 |
Caja | – | 1 |
Cables | – | 36 |
Solo aparecen los precios de los componentes que se han comprado individualmente. Los demás componentes han sido proporcionados por la universidad.
SOFTWARE
En este apartado se explica el funcionamiento del código del proyecto con el cual se ha podido implementar las funcionalidades del juego.
Funcionamiento
En primer lugar se ha planteado la filosofía del programa que debería de seguir para su correcto funcionamiento. Después de varios prototipos iniciales se ha decidido implementar todos los elementos del programa de la siguiente manera:
Función loop
La función principal del programa consiste en sentencias de control que comprueban el estado en el cual se encuentra RetroBoy. Se verifica si el jugador a empezado una partida y comienza el juego, en caso contrario aparece un mensaje de bienvenida y se pide que el jugador comience el juego, seleccionando el nivel de dificultad deseado.
Comienzo de la partida
Una vez se ha seleccionado el nivel de dificultad, se llama a la función encargada de generar la secuencia de colores correspondientes. Esta secuencia se guarda en un array para posteriormente utilizarla para comprobar si la secuencia introducida por el jugador es correcta. A medida que va avanzando el juego, se van guardando los colores pulsados por el usuario y se comprueba si se corresponden con la secuencia que se generó antes de comenzar la partida. Si es así la partida continúa hasta que el jugador consiga alcanzar el último nivel o introduzca una secuencia incorrecta. En ese caso la partida termina enseñando la puntuación conseguida y suena la melodía de victoria. En el caso contrario, si el jugador en cualquier momento pulsa un color que no se corresponde con la secuencia, la partida finaliza mostrando un game over. Independientemente del resultado, la consola vuelve al estado principal, es decir, vuelve a aparecer la pantalla de inicio para que el jugador comience una nueva partida.
Información acerca del adaptador
Para este proyecto se ha utilizado la librería NewLiquidCrystal que no está integrada por defecto en el programa de arduino. Para poder descargar esta librería se ha usado el siguiente enlace de un repositorio de Github y se ha implementado manualmente en la carpeta de librerias de arduino.
Demonstración del funcionamiento
Por último se presenta una breve demonstración del funcionamiento del proyecto. Para esta demo se muestra el proceso del arranque de la consola, la elección de un nivel de dificultad y el transcurso de una partida.
Documentación adicional
Código fuente
#include <LCD.h>
#include <Wire.h>
// Para el adaptador I2C
#include <LiquidCrystal_I2C.h>
// Iniciar la pantalla lcd
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
//
#define SPEAKER 7
#define BUTTON_RED A0
#define BUTTON_GREEN A2
#define BUTTON_BLUE A1
#define BUTTON_YELLOW A3
#define LED_RED 6
#define LED_GREEN 3
#define LED_BLUE 4
#define LED_YELLOW 5
int melody_GameOver[] = {262, 196, 196, 220, 196, 0, 247, 262};
int melody_GameOverNOTES[] = {4, 8, 8, 4, 4, 4, 4, 4};
int currentLevel = 1;
int lvlSPEED = 500;
int levelSequencePlayer[20];
int levelSequence[20];
int levelEnd;
void setup(){
game_init();
}
void loop(){
if(currentLevel == 1){
display_start();
difficultySelect();
display_gameStatus();
showLevelSequence();
levelSequenceCheck();
}
if(currentLevel != 1){
display_gameStatus();
showLevelSequence();
levelSequenceCheck();
}
}
// PLANTILLAS PARA IMPRIMIR INFO POR EL LCD
// Comienzo de la partida
void display_start(){
boolean start = false;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("RetroBoy");
lcd.setCursor(0,1);
lcd.print("Press R to Start");
while(start == false){
if(digitalRead(BUTTON_RED) == LOW){
start = true;
sound_start();
}
}
}
// Seleccion del nivel de dificultad
void display_difficultySelection(){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Select level");
delay(3000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Easy-B Normal-G");
lcd.setCursor(0,1);
lcd.print("Hard-Y Lunatic-R");
}
// Info sobre el nivel de dificultad seleccionado
void display_difficultySelected(String difficulty){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Selected:");
lcd.setCursor(0,1);
lcd.print(difficulty);
delay(2000);
}
// Nivel actual
void display_gameStatus(){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Current Level:");
lcd.setCursor(0,1);
lcd.print(currentLevel);
}
// Fin de la partida GAME OVER
void display_gameOverStatus(){
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("GAME OVER");
lcd.setCursor(0, 1);
lcd.print("Final Score: ");
lcd.print(currentLevel);
}
// Fin de la partida VICTORIA
void display_gameVictoryStatus(){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Level Completed");
lcd.setCursor(0,1);
lcd.print("Final Score: ");
lcd.print(currentLevel);
}
// Inicializacion de pines y lcd
void game_init(){
pinMode(BUTTON_RED, INPUT);
pinMode(BUTTON_GREEN, INPUT);
pinMode(BUTTON_BLUE, INPUT);
pinMode(BUTTON_YELLOW, INPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
int begin = 0;
ledsOFF();
lcd.begin(16,2);
}
// Encender todos los LEDs
void ledsON(){
digitalWrite(LED_RED, HIGH);
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_BLUE, HIGH);
digitalWrite(LED_YELLOW, HIGH);
}
// Apagar todos los LEDs
void ledsOFF(){
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_BLUE, LOW);
digitalWrite(LED_YELLOW, LOW);
}
// Seleccion de dificultad/modo de juego
void difficultySelect(){
ledsOFF();
display_difficultySelection();
int aux = 1;
while(aux == 1){
if(digitalRead(BUTTON_BLUE) == LOW){
digitalWrite(LED_BLUE, HIGH);
tone(SPEAKER, 500);
delay(300);
noTone(SPEAKER);
digitalWrite(LED_BLUE, LOW);
display_difficultySelected("Easy");
levelGenerator(0);
aux = 0;
}
else if(digitalRead(BUTTON_GREEN) == LOW){
digitalWrite(LED_GREEN, HIGH);
tone(SPEAKER, 500);
delay(300);
noTone(SPEAKER);
digitalWrite(LED_GREEN, LOW);
display_difficultySelected("Normal");
levelGenerator(1);
aux = 0;
}
else if(digitalRead(BUTTON_YELLOW) == LOW){
digitalWrite(LED_YELLOW, HIGH);
tone(SPEAKER, 500);
delay(300);
noTone(SPEAKER);
digitalWrite(LED_YELLOW, LOW);
display_difficultySelected("Hard");
levelGenerator(2);
aux = 0;
}
else if(digitalRead(BUTTON_RED) == LOW){
digitalWrite(LED_RED, HIGH);
tone(SPEAKER, 500);
delay(300);
noTone(SPEAKER);
digitalWrite(LED_RED, LOW);
display_difficultySelected("Lunatic");
levelGenerator(3);
aux = 0;
}
}
}
// Generacion de la secuencia de Simon Dice
void levelGenerator(int difficulty){
randomSeed(millis());
if(difficulty==0){
for(int i = 0; i < 5; i++){
levelSequence[i] = rand() % (6 + 1 - 3) + 3;
}
levelEnd = 5;
}
else if(difficulty==1){
for(int i = 0; i < 10; i++){
levelSequence[i] = rand() % (6 + 1 - 3) + 3;
}
levelEnd = 10;
}
else if(difficulty==2){
for(int i = 0; i < 15; i++){
levelSequence[i] = rand() % (6 + 1 - 3) + 3;
}
levelEnd = 15;
}
else{
for(int i = 0; i < 20; i++){
levelSequence[i] = rand() % (6 + 1 - 3) + 3;
}
levelEnd = 20;
}
}
// Muestra la secuencia de colores
void showLevelSequence(){
ledsOFF();
for(int i = 0; i < currentLevel; i++){
if(levelSequence[i] == LED_RED){
tone(SPEAKER, 200);
delay(200);
noTone(SPEAKER);
}
if(levelSequence[i] == LED_GREEN){
tone(SPEAKER, 300);
delay(200);
noTone(SPEAKER);
}
if(levelSequence[i] == LED_BLUE){
tone(SPEAKER, 400);
delay(200);
noTone(SPEAKER);
}
if(levelSequence[i] == LED_YELLOW){
tone(SPEAKER, 500);
delay(200);
noTone(SPEAKER);
}
digitalWrite(levelSequence[i], HIGH);
delay(lvlSPEED);
digitalWrite(levelSequence[i], LOW);
delay(200);
}
}
// Comprueba la secuencia introducida por el jugador
void levelSequenceCheck(){
int aux = 0;
for(int i = 0; i < currentLevel; i++){
aux = 0;
while(aux == 0){
if(currentLevel == levelEnd+1){
victory();
}
if(digitalRead(BUTTON_YELLOW) == LOW){
digitalWrite(LED_YELLOW, HIGH);
tone(SPEAKER, 500);
delay(300);
noTone(SPEAKER);
levelSequencePlayer[i] = LED_YELLOW;
aux = 1;
delay(200);
if(levelSequencePlayer[i] != levelSequence[i]){
GameOver();
return;
}
digitalWrite(LED_YELLOW, LOW);
}
if(digitalRead(BUTTON_BLUE) == LOW){
digitalWrite(LED_BLUE, HIGH);
tone(SPEAKER, 400);
delay(300);
noTone(SPEAKER);
levelSequencePlayer[i] = LED_BLUE;
aux = 1;
delay(200);
if(levelSequencePlayer[i] != levelSequence[i]){
GameOver();
return;
}
digitalWrite(LED_BLUE, LOW);
}
if(digitalRead(BUTTON_GREEN) == LOW){
digitalWrite(LED_GREEN, HIGH);
tone(SPEAKER, 300);
delay(300);
noTone(SPEAKER);
levelSequencePlayer[i] = LED_GREEN;
aux = 1;
delay(200);
if(levelSequencePlayer[i] != levelSequence[i]){
GameOver();
return;
}
digitalWrite(LED_GREEN, LOW);
}
if(digitalRead(BUTTON_RED) == LOW){
digitalWrite(LED_RED, HIGH);
tone(SPEAKER, 200);
delay(300);
noTone(SPEAKER);
levelSequencePlayer[i] = LED_RED;
aux = 1;
delay(200);
if(levelSequencePlayer[i] != levelSequence[i]){
GameOver();
return;
}
digitalWrite(LED_RED, LOW);
}
}
}
if(currentLevel < 20){
currentLevel++;
lvlSPEED -= 50;
delay(200);
}
}
// Sonido de fin de juego GAME OVER
void sound_GameOver(){
for(int i = 0; i < 8; i++){
int notesDURATION = 1000/melody_GameOverNOTES[i];
tone(SPEAKER, melody_GameOver[i],melody_GameOverNOTES);
int notesPAUSE = notesDURATION * 1.30;
delay(notesPAUSE);
noTone(SPEAKER);
}
}
// Sonido de fin de juego VICTORY
void sound_Victory(){
tone(SPEAKER,523.25,133);
delay(133);
tone(SPEAKER,523.25,133);
delay(133);
tone(SPEAKER,523.25,133);
delay(133);
tone(SPEAKER,523.25,400);
delay(400);
tone(SPEAKER,415.30,400);
delay(400);
tone(SPEAKER,466.16,400);
delay(400);
tone(SPEAKER,523.25,133);
delay(133);
delay(133);
tone(SPEAKER,466.16,133);
delay(133);
tone(SPEAKER,523.25,1200);
delay(1200);
}
void sound_start(){
tone(SPEAKER,523.25,133);
delay(133);
tone(SPEAKER,523.25,133);
delay(133);
tone(SPEAKER,523.25,133);
delay(133);
tone(SPEAKER,523.25,400);
delay(400);
}
// Fin de la partida GAME OVER
void GameOver(){
ledsON();
delay(250);
ledsOFF();
delay(250);
display_gameOverStatus();
sound_GameOver();
delay(2000);
currentLevel = 1;
lvlSPEED = 500;
}
// Fin de la partida VICTORIA
void victory(){
ledsON();
delay(250);
ledsOFF();
delay(250);
display_gameVictoryStatus();
sound_Victory();
delay(2000);
currentLevel = 1;
lvlSPEED = 500;
}