Battleship este un boardgame in care 2 jucatori plaseaza unitati sub forma de nave de razboi pe un grid unde fiecare celula a grilei este identificata printr-un set de coordonate.
Scopul jocului este de a distruge flota adversarului prin ghicirea pozitiilor navelor acestuia pe grid.
Battleship LED are obiectivul de a fi un hybrid intre experienta fizica si cea digitala, prin adaugarea elementelor digitale unui joc traditional de strategie.
Jocul are 2 faze principale: Battleship Placement si Target Selection. Fiecare jucator are propria lor matrice LED si propriul lor panou de control, separate print-un perete de cele ale adversarului.
Ecranul LCD 1602 este plasat pe margine unde poate fi vazut de ambii jucatori. El este folosit pentru a oferi informatii cruciale, cum ar fi cate nave mai are un jucator, al carui jucator este tura etc .
Buzzerele sunt folosite pentru a reda sunete specifice pentru urmatoarele situatii: start game, hit, miss, ship sunken, end of game.
Panoul de control este alcatuit din 6 pushbuttons multiplexate prin intermediul unui sistem de divizoare de tensiune. Cele 4 butoane directionale deplaseaza un 'cursor' pe ecranul LED reprezentat de un flicker controlat printr-un timer intern. Butonul stanga sus are functia de Rotate in faza de Battleship Placement pentru a roti navele si Change Screen in faza de Target Selection pentru schimba intre boardu tau si cel inamic. Confirm pentru a confirma selecia de locatie pentru nave / tinta.
}
Intern, sunt folosite 2 matrici 2×8 ungsigned char pentru a transmite stadiul actual al jocului catre ecranele LED:
Ecranul unui jucator poate sa fie in 3 moduri de functionare:
Unde: * 1 - LED ON * 0 - LED OFF * f1 - flicker 1 folosit pentru nave, prin timer 2 alterneaza intre 1 si 0 la 1000 ms * f2 - flicker 2 folosit pentru cursor, prin timer 1 alterneaza intre 1 si 0 la 50 ms #define View_Enemy(x,y) ((x&~y)|(x&f1)) #define View_Self(x,y) ((y&f1)|x) #define View_Placement(x,y) ((~x&y)|(x&~y)|(y&f1))
Prin intermediul ADC conectat la A0 pentru Player1 si A1 pentru Player2 citim valori si verificam pentru urmatoarele valori:
#define BTN1 980 //Rotate / Change Screen #define BTN2 903 //UP #define BTN3 853 //Confirm #define BTN4 696 //LEFT #define BTN5 512 //DOWN #define BTN6 179 //RIGHT #define VALUE_IN_RANGE(read_value, threshold_value) \ ((read_value - threshold_value) <= 15 || ((threshold_value - read_value) <= 15)) //Pentru perturbari fizice
In PLACE_PHASE un jucator poate sa-si vada doar propriul ecran si sa-si miste cursorul pentru a plasa nave. Odata ce a plasat 5 nave cursorul devine inactiv si nu se mai poate misca.
In BATTLE_PHASE cursorul jucatorului curent este plasat pe matricea LEDS[enemy] unde are control. Cat timp este tura lui, jucatorul nu poate sa schimbe ecranul, iar jucatorul inamic are cursorul deactivat si poate doar sa schimbe ecranul.
Functiile tone() si notone() din Arduino.h au prezentat doua probleme in acest proiec:
Asa ca am scris o alternativa:
void playTone(uint8_t buzzerpin, int frequency, int duration) { long period = 1000000L / frequency; long cycles = (long)duration * 1000L / period; for (long i = 0; i < cycles; i++) { SET(PORTD, buzzerpin); delayMicroseconds(period / 2); CLR(PORTD, buzzerpin); delayMicroseconds(period / 2); } }
Prin folosirea librariilor din lab-ul de I2C am recreat cateva functionalitatile din LiquidCrystal_I2C
void lcd_send_command(uint8_t command); void lcd_send_data(uint8_t data); void lcd_init(void); void lcd_set_cursor(uint8_t row, uint8_t col); void lcd_print(char *str); void lcd_send(uint8_t value, uint8_t mode) { uint8_t highNib = value & 0xF0; uint8_t lowNib = (value << 4) & 0xF0;
// Send high nibble twi_start(); twi_write(LCD_ADDRESS << 1); twi_write(mode | highNib | 0x0C); // En=1, Rs/Rw as per mode twi_write(mode | highNib | 0x08); // En=0, Rs/Rw as per mode twi_stop();
// Send low nibble twi_start(); twi_write(LCD_ADDRESS << 1); twi_write(mode | lowNib | 0x0C); // En=1, Rs/Rw as per mode twi_write(mode | lowNib | 0x08); // En=0, Rs/Rw as per mode twi_stop(); }
In PLACE_PHASE initial ambi jucatori incep jocul cu 0 nave. La pozitia cursorului este 'plasata' o nava de lungime {2,3,3,3,4} care se 'misca' cu acesta. Ea poate sa treaca peste alte nave, nar nu prin margini.
if (P_cursor[player].r <= row && P_cursor[player].r + length - 1 >= row) displayData[player] = View_Placement(Ships[player][row], (1 << P_cursor[player].c)); //Daca nava este verticala if (row == P_cursor[player].r) { unsigned char temp = 0x00; for (uint8_t j = 0; j < length; j++) temp |= (1 << (P_cursor[player].c + j)); displayData[player] = View_Placement(Ships[player][row], temp); } //Daca nava este orizontala
O nava poate fi introdusa in joc (adaugata la unsigned char Ships si la struct) daca toate segmentele sale nu sunt deja ocupate (idem pentru selectarea unui atac); Odata ce ambii jucatori au plasat cele 5 nave incepe BATTLE_PHASE. Cand se selecteaza o pozitie pentru atac, jocul prima data verifica daca a lovita inainte. Daca e un 'Hit' jucatorul poate sa continue sa atace pana cand este un 'Miss'.
Cu a 5-ea nava doborata, jucatorul inca in viata este declarat 'winner' si nu se pai primesc inputuri.
Librari Folosite * #include <avr/io.h> * #include <stdio.h> * #include <util/delay.h> * #include <SPI.h> * #include <avr/interrupt.h> * #include <util/twi.h>
Nu am mai avut timp sa fac conecturi complete pentru prezentare, asa ca am mers cu varianta auxiliara de a conecta totul la breadboard-uri, ceea ce a lasat bateria si butonul de reset pentru prezentare neatasate si jocul greu de manevrat.
Initial, am vrut sa am tot proiectul in C, dar am aftat foarte tarziu ca SPI.h lucreaza cu clase, si am fost nevoit sa schimb la C++. Daca as fi lucrat cu C++ de la inceput m-as fi folosit de clase pentru implementarea proiectului in loc de struct.
Chiar si asa, jocul a functionat la testare doar cu cateva erori la inputuri cauzate de suduri slabe si panou nefixat pe loc.