Instantly share code, notes, and snippets.
Created
March 10, 2018 00:49
-
Save parzibyte/5343de41e0d63b03035f3b0d3dd038dc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Simular juego de batalla naval en Arduino | |
@date 9 de diciembre del 2017 | |
@author parzibyte | |
@web https://www.parzibyte.me/blog | |
*/ | |
#include <Wire.h> | |
#include <LiquidCrystal_I2C.h> | |
#include <SoftwareSerial.h> | |
#define DIRECCION_LCD 0x3F | |
#define ALTURA_LCD 4 | |
#define ANCHURA_LCD 20 | |
#define PIN_CLOCK 2 | |
#define PIN_LATCH 3 | |
#define PIN_DATA 4 | |
/* | |
Número de espacios que | |
ocupa cada barco | |
*/ | |
#define LONGITUD_FRAGATAS 1 | |
#define LONGITUD_DESTRUCTORES 2 | |
#define LONGITUD_ACORAZADOS 3 | |
#define LONGITUD_SUBMARINOS 3 | |
#define LONGITUD_PORTA_AVIONES 4 | |
/* | |
Símbolos | |
*/ | |
#define DISPARO_NO_ACERTADO '*' | |
#define DISPARO_ACERTADO '_' | |
#define AGUA ' ' | |
#define FRAGATA_1 'A' | |
#define FRAGATA_2 'B' | |
#define DESTRUCTOR_1 'C' | |
#define DESTRUCTOR_2 'D' | |
#define ACORAZADO_1 'E' | |
#define SUBMARINO_1 'F' | |
#define PORTA_AVIONES_1 'G' | |
#define BARCO_IMPRIMIBLE '#' | |
#define DEBERIA_IMPRIMIR_LETRAS false | |
#define INTENTOS_MAXIMOS 18 | |
/* | |
Pines | |
*/ | |
#define BUZZER 7 | |
/* | |
Duraciones | |
*/ | |
#define DURACION_BUZZER_DISPARO_ACERTADO 200 | |
#define DURACION_BUZZER_DISPARO_NO_ACERTADO 300 | |
//Para interactuar con la LCD | |
LiquidCrystal_I2C lcd(DIRECCION_LCD, ANCHURA_LCD, ALTURA_LCD); | |
/* | |
Los puertos seriales de | |
cada jugador | |
*/ | |
SoftwareSerial serialJugadorUno(10, 11);//TX, RX | |
SoftwareSerial serialJugadorDos(8, 9); | |
/* | |
El arreglo que representa el escenario | |
que será pintado en la pantalla | |
*/ | |
char escenario[ANCHURA_LCD][ALTURA_LCD]; | |
/* | |
Algunas otras variables útiles | |
*/ | |
int intentosJugadorDos = 0; | |
byte leds = B00000000; //Lo que será mandado al registro de corrimiento | |
/* | |
Setup y loop | |
*/ | |
void setup() { | |
prepararLcd(); | |
establecerPines(); | |
serialJugadorUno.begin(9600); | |
serialJugadorDos.begin(9600); | |
vaciarEscenario(); | |
dibujarEscenario(); | |
refrescarLeds(); | |
/* | |
Ciclo infinito. Esperar | |
hasta que todos los barcos | |
estén asignados | |
*/ | |
pedirUbicacionDeBarcos(); | |
/* | |
Ahora es el turno del jugador 2 | |
*/ | |
turnoDelJugadorDos(); | |
/*Fin*/ | |
} | |
void loop() { | |
/*Nada por aquí*/ | |
} | |
/* | |
Se encarga de poner | |
agua en todo el escenario | |
*/ | |
void vaciarEscenario() { | |
for (int x = 0; x < ANCHURA_LCD; x++) | |
for (int y = 0; y < ALTURA_LCD; y++) | |
escenario[x][y] = AGUA; | |
} | |
void refrescarLeds() { | |
digitalWrite(PIN_LATCH, LOW); | |
shiftOut(PIN_DATA, PIN_CLOCK, MSBFIRST, leds); | |
digitalWrite(PIN_LATCH, HIGH); | |
} | |
/* | |
Volca o dibuja | |
el arreglo en | |
la LCD | |
*/ | |
void dibujarEscenario() { | |
lcd.clear(); | |
for (int x = 0; x < ANCHURA_LCD; x++) { | |
for (int y = 0; y < ALTURA_LCD; y++) { | |
lcd.setCursor(x, y); | |
if (DEBERIA_IMPRIMIR_LETRAS) { | |
switch (escenario[x][y]) { | |
case FRAGATA_1: | |
lcd.print(FRAGATA_1); | |
break; | |
case FRAGATA_2: | |
lcd.print(FRAGATA_2); | |
break; | |
case DESTRUCTOR_1: | |
lcd.print(DESTRUCTOR_1); | |
break; | |
case DESTRUCTOR_2: | |
lcd.print(DESTRUCTOR_2); | |
break; | |
case ACORAZADO_1: | |
lcd.print(ACORAZADO_1); | |
break; | |
case SUBMARINO_1: | |
lcd.print(SUBMARINO_1); | |
break; | |
case PORTA_AVIONES_1: | |
lcd.print(PORTA_AVIONES_1); | |
break; | |
case AGUA: | |
lcd.print(AGUA); | |
break; | |
case DISPARO_NO_ACERTADO: | |
case DISPARO_ACERTADO: | |
lcd.print(DISPARO_NO_ACERTADO); | |
break; | |
} | |
} else { | |
switch (escenario[x][y]) { | |
case FRAGATA_1: | |
case FRAGATA_2: | |
case DESTRUCTOR_1: | |
case DESTRUCTOR_2: | |
case ACORAZADO_1: | |
case SUBMARINO_1: | |
case PORTA_AVIONES_1: | |
lcd.print(BARCO_IMPRIMIBLE); | |
break; | |
case AGUA: | |
lcd.print(AGUA); | |
break; | |
case DISPARO_NO_ACERTADO: | |
case DISPARO_ACERTADO: | |
lcd.print(DISPARO_NO_ACERTADO); | |
break; | |
} | |
} | |
} | |
} | |
} | |
void prepararLcd() { | |
lcd.init(); | |
lcd.backlight(); | |
lcd.clear(); | |
} | |
void turnoDelJugadorDos() { | |
String coordenadas; | |
/* | |
Escuchar y dar bienvenida | |
*/ | |
serialJugadorDos.listen(); | |
serialJugadorDos.println("Bienvenido, jugador 2"); | |
int barcosDestruidos = 0; | |
boolean intentosAgotados = false, todosLosBarcosDestruidos = false; | |
do { | |
serialJugadorDos.println("Te quedan " + String(INTENTOS_MAXIMOS - intentosJugadorDos) + " intentos. Ingresa las coordenadas para realizar el ataque: "); | |
while (!serialJugadorDos.available()); | |
coordenadas = serialJugadorDos.readString(); | |
int x, y; | |
boolean deberiaRestarIntento = true; | |
if (esCoordenadaValida(coordenadas, x, y)) { | |
char objetivo = dispararYObtenerObjetivo(x, y); | |
switch (objetivo) { | |
case AGUA: | |
serialJugadorDos.println("Disparo no acertado. Le diste al agua."); | |
marcarDisparoNoAcertadoEn(x, y); | |
indicarDisparoErroneo(); | |
break; | |
case FRAGATA_1: | |
case FRAGATA_2: | |
case DESTRUCTOR_1: | |
case DESTRUCTOR_2: | |
case ACORAZADO_1: | |
case SUBMARINO_1: | |
case PORTA_AVIONES_1: | |
serialJugadorDos.println("Disparo acertado!"); | |
marcarDisparoAcertadoEn(x, y); | |
indicarDisparoAcertado(); | |
break; | |
case DISPARO_ACERTADO: | |
case DISPARO_NO_ACERTADO: | |
deberiaRestarIntento = false; | |
serialJugadorDos.println("Ya habias disparado a esas coordenadas. Intenta en otro lugar"); | |
break; | |
} | |
if (!hayMasInstanciasDe(objetivo)) { | |
apagarLedDe(objetivo); | |
barcosDestruidos++; | |
//Right here, restar los barcos | |
} | |
} else { | |
serialJugadorDos.println("Coordenadas invalidas o fuera de rango"); | |
deberiaRestarIntento = false; | |
} | |
if (deberiaRestarIntento) intentosJugadorDos++; | |
dibujarEscenario(); | |
intentosAgotados = intentosJugadorDos >= INTENTOS_MAXIMOS; | |
todosLosBarcosDestruidos = barcosDestruidos >= 7; | |
if (intentosAgotados || todosLosBarcosDestruidos) break; | |
} while (true); | |
if (todosLosBarcosDestruidos) { | |
jugadorDosGana(); | |
} else if (intentosAgotados) { | |
jugadorDosPierde(); | |
} | |
} | |
void jugadorDosPierde() { | |
serialJugadorDos.println("Pierdes"); | |
serialJugadorUno.println("Ganas"); | |
encenderBuzzer(); | |
delay(2500); | |
apagarBuzzer(); | |
} | |
void jugadorDosGana() { | |
serialJugadorDos.println("Ganas"); | |
serialJugadorUno.println("Pierdes"); | |
for (int x = 0; x < 10; x++) { | |
encenderBuzzer(); | |
delay(125); | |
apagarBuzzer(); | |
delay(125); | |
} | |
} | |
void encenderLedDe(char barco) { | |
switch (barco) { | |
case FRAGATA_1: | |
bitSet(leds, 0); | |
break; | |
case FRAGATA_2: | |
bitSet(leds, 1); | |
break; | |
case DESTRUCTOR_1: | |
bitSet(leds, 2); | |
break; | |
case DESTRUCTOR_2: | |
bitSet(leds, 3); | |
break; | |
case ACORAZADO_1: | |
bitSet(leds, 4); | |
break; | |
case SUBMARINO_1: | |
bitSet(leds, 5); | |
break; | |
case PORTA_AVIONES_1: | |
bitSet(leds, 6); | |
break; | |
} | |
refrescarLeds(); | |
} | |
void apagarLedDe(char barco) { | |
switch (barco) { | |
case FRAGATA_1: | |
bitClear(leds, 0); | |
break; | |
case FRAGATA_2: | |
bitClear(leds, 1); | |
break; | |
case DESTRUCTOR_1: | |
bitClear(leds, 2); | |
break; | |
case DESTRUCTOR_2: | |
bitClear(leds, 3); | |
break; | |
case ACORAZADO_1: | |
bitClear(leds, 4); | |
break; | |
case SUBMARINO_1: | |
bitClear(leds, 5); | |
break; | |
case PORTA_AVIONES_1: | |
bitClear(leds, 6); | |
break; | |
} | |
refrescarLeds(); | |
} | |
boolean hayMasInstanciasDe(char barco) { | |
/* | |
Buscar si hay más "instancias" del barco | |
al que le han disparado. Es decir, si el barco | |
atacado es, por ejemplo, FRAGATA_1 (que ocupa un espacio), | |
su espacio será sustituido por un disparo, por lo que no | |
habrá más FRAGATA_1 en el array | |
En el caso del submarino, que mide 3, si le dan a una parte | |
del mismo sólo habrán eliminado 1 espacio, pero en el | |
arreglo quedarán 2 instancias del mismo, por lo que no se | |
considerará eliminado | |
*/ | |
for (int e = 0; e < ANCHURA_LCD; e++) { | |
for (int n = 0; n < ALTURA_LCD; n++) { | |
if (escenario[e][n] == barco) return true; | |
} | |
} | |
return false; | |
} | |
void establecerPines() { | |
pinMode(PIN_CLOCK, OUTPUT); | |
pinMode(PIN_LATCH, OUTPUT); | |
pinMode(PIN_DATA, OUTPUT); | |
pinMode(BUZZER, OUTPUT); | |
} | |
void encenderBuzzer() { | |
digitalWrite(BUZZER, HIGH); | |
} | |
void apagarBuzzer() { | |
digitalWrite(BUZZER, LOW); | |
} | |
void marcarDisparoNoAcertadoEn(int x, int y) { | |
escenario[x][y] = DISPARO_NO_ACERTADO; | |
} | |
void marcarDisparoAcertadoEn(int x, int y) { | |
escenario[x][y] = DISPARO_ACERTADO; | |
} | |
void indicarDisparoAcertado() { | |
encenderBuzzer(); | |
delay(DURACION_BUZZER_DISPARO_ACERTADO); | |
apagarBuzzer(); | |
} | |
void indicarDisparoErroneo() { | |
for (int x = 0; x < 5; x++) { | |
encenderBuzzer(); | |
delay((DURACION_BUZZER_DISPARO_NO_ACERTADO / 2) / 5); | |
apagarBuzzer(); | |
delay((DURACION_BUZZER_DISPARO_NO_ACERTADO / 2) / 5); | |
} | |
} | |
char dispararYObtenerObjetivo(int x, int y) { | |
return escenario[x][y]; | |
} | |
void escucharJugadorDos() { | |
serialJugadorDos.listen(); | |
} | |
boolean estaVacioEn(int x, int y) { | |
return x >= 0 && x < ANCHURA_LCD && y >= 0 && y < ALTURA_LCD && escenario[x][y] == AGUA; | |
} | |
char queHayEn(int x, int y) { | |
return escenario[x][y]; | |
} | |
void pedirUbicacionDeBarcos() { | |
serialJugadorUno.listen(); | |
serialJugadorUno.println("Bienvenido, jugador 1. Es hora de ubicar tus barcos"); | |
String coordenadas; | |
int barcos = 7, | |
barcosCompletados = 0; | |
do { | |
String nombreBarco = ""; | |
switch (barcosCompletados) { | |
case 0: | |
nombreBarco = "Fragata 1"; | |
break; | |
case 1: | |
nombreBarco = "Fragata 2"; | |
break; | |
case 2: | |
nombreBarco = "Destructor 1"; | |
break; | |
case 3: | |
nombreBarco = "Destructor 2"; | |
break; | |
case 4: | |
nombreBarco = "Acorazado"; | |
break; | |
case 5: | |
nombreBarco = "Submarino"; | |
break; | |
case 6: | |
nombreBarco = "Porta aviones"; | |
break; | |
} | |
serialJugadorUno.println("Introduce las coordenadas de " + nombreBarco + ""); | |
while (!serialJugadorUno.available()); | |
coordenadas = serialJugadorUno.readString(); | |
int x, y; | |
if (esCoordenadaValida(coordenadas, x, y)) { | |
switch (barcosCompletados) { | |
case 0: | |
//Fragata 1 | |
if (intentarDibujarBarco(x, y, FRAGATA_1, LONGITUD_FRAGATAS)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(FRAGATA_1); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
case 1: | |
//Fragata 2 | |
if (intentarDibujarBarco(x, y, FRAGATA_2, LONGITUD_FRAGATAS)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(FRAGATA_2); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
case 2: | |
//Destructor 1 | |
if (intentarDibujarBarco(x, y, DESTRUCTOR_1, LONGITUD_DESTRUCTORES)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(DESTRUCTOR_1); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
case 3: | |
//Destructor 2 | |
if (intentarDibujarBarco(x, y, DESTRUCTOR_2, LONGITUD_DESTRUCTORES)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(DESTRUCTOR_2); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
case 4: | |
//Acorazado 1 | |
if (intentarDibujarBarco(x, y, ACORAZADO_1, LONGITUD_ACORAZADOS)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(ACORAZADO_1); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
case 5: | |
//Submarino 1 | |
if (intentarDibujarBarco(x, y, SUBMARINO_1, LONGITUD_SUBMARINOS)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(SUBMARINO_1); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
case 6: | |
//Porta aviones 1 | |
if (intentarDibujarBarco(x, y, PORTA_AVIONES_1, LONGITUD_PORTA_AVIONES)) { | |
dibujarEscenario(); | |
barcosCompletados++; | |
encenderLedDe(PORTA_AVIONES_1); | |
} else { | |
serialJugadorUno.println("Coordenadas correctas, pero ya existe un barco en esa posición. Intenta de nuevo"); | |
} | |
break; | |
} | |
} else { | |
serialJugadorUno.println("Coordenadas incorrectas o fuera de rango. Recuerda que debes introducirlas en el formato x,y (con la coma incluida)"); | |
} | |
coordenadas = ""; | |
} while (barcosCompletados < barcos); | |
serialJugadorUno.println("Se ha terminado tu tiempo. Turno del jugador 2"); | |
} | |
boolean intentarDibujarBarco(int x, int y, char simbolo, int longitud) { | |
boolean puedeDibujar = true; | |
//A la derecha | |
for (int d = 0; d < longitud; d++) { | |
puedeDibujar = puedeDibujar && estaVacioEn(x + d, y); | |
} | |
if (puedeDibujar) { | |
for (int d = 0; d < longitud; d++) { | |
escenario[x + d][y] = simbolo; | |
} | |
return true; | |
} | |
//Hacia abajo | |
puedeDibujar = true; | |
for (int d = 0; d < longitud; d++) { | |
puedeDibujar = puedeDibujar && estaVacioEn(x, y + d); | |
} | |
if (puedeDibujar) { | |
for (int d = 0; d < longitud; d++) { | |
escenario[x][y + d] = simbolo; | |
} | |
return true; | |
} | |
//Hacia la izquierda | |
puedeDibujar = true; | |
for (int d = 0; d < longitud; d++) { | |
puedeDibujar = puedeDibujar && estaVacioEn(x - d, y); | |
} | |
if (puedeDibujar) { | |
for (int d = 0; d < longitud; d++) { | |
escenario[x - d][y] = simbolo; | |
} | |
return true; | |
} | |
//Hacia arriba | |
puedeDibujar = true; | |
for (int d = 0; d < longitud; d++) { | |
puedeDibujar = puedeDibujar && estaVacioEn(x, y - d); | |
} | |
if (puedeDibujar) { | |
for (int d = 0; d < longitud; d++) { | |
escenario[x][y - d] = simbolo; | |
} | |
return true; | |
} | |
return false; | |
} | |
boolean isNumeric(String str) { | |
/* | |
Gracias a http://tripsintech.com/arduino-isnumeric-function/ | |
*/ | |
unsigned int stringLength = str.length(); | |
if (stringLength == 0) { | |
return false; | |
} | |
boolean seenDecimal = false; | |
for (unsigned int i = 0; i < stringLength; ++i) { | |
if (isDigit(str.charAt(i))) { | |
continue; | |
} | |
if (str.charAt(i) == '.') { | |
if (seenDecimal) { | |
return false; | |
} | |
seenDecimal = true; | |
continue; | |
} | |
return false; | |
} | |
return true; | |
} | |
boolean esCoordenadaValida(String coordenadas, int &x, int &y) { | |
int indice = coordenadas.indexOf(","); | |
if (indice != -1) { | |
String supuestoX = coordenadas.substring(0, indice), | |
supuestoY = coordenadas.substring(indice + 1, coordenadas.length() - 2); | |
if (isNumeric(supuestoX) && isNumeric(supuestoY)) { | |
x = supuestoX.toInt(), y = supuestoY.toInt(); | |
return x >= 0 && y >= 0 && x < ANCHURA_LCD && y < ALTURA_LCD; | |
} | |
} | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment