//definizione dei pin
#define RS PB4
#define E PB3
#define D4 PD4
#define D5 PD5
#define D6 PD6
#define D7 PD7
#define SEL_PIN PD2
//salvo la prima colonna di indirizzi del display in un vettore
const uint8_t Colonna[4] = { 0, 64, 20, 84 };
//variabili che contengono il valore digitale del joystick
uint8_t x = 0;
uint8_t y = 0;
//variabili che contengono le coordinate dei vari personaggi
volatile uint8_t PosPacX=0;
volatile uint8_t PosPacY=0;
volatile uint8_t PosFanX=0;
volatile uint8_t PosFanY=0;
volatile uint8_t PosPilX=0;
volatile uint8_t PosPilY=0;
//variabile booleana per alternare la lettura degli assi X e Y sul joystick
volatile bool AlternaXY=true;
//variabili booleane usate per tenere conto dello stato di gioco
volatile bool gameover=false;
volatile bool powerup=false;
volatile bool updatePacman=false;
volatile bool updateFan=false;
volatile bool bottone=false;
//usato per la funzione delay
volatile uint32_t counter=0;
//contatori del tempo di gioco e del tempo di energia
volatile uint16_t contaSecondi=0;
volatile uint8_t powerupTimer=0;
//contatore degli overflow usato per il Timer2
volatile uint8_t timer2OverflowCount = 0;
//definisce le direzioni di movimento possibili
enum Direzione {SU, GIU, DESTRA, SINISTRA, NEUTRA};
volatile Direzione UltimaDirezione = NEUTRA;
//invia comandi al display
void lcdComando(byte cmd) {
PORTD = (cmd & 0xF0); //inviati i primi 4 bit, mascherati gli altri
PORTB &= ~(1<<RS);
PORTB |= (1<<E);
delayMicro(1);
PORTB &= ~(1<<E);
delayMicro(1);
PORTD = ( (cmd<<4) & 0xF0 ); //inviati gli altri 4 bit, mascherati gli altri
PORTB &= ~(1<<RS);
PORTB |= (1<<E);
delayMicro(1);
PORTB &= ~(1<<E);
delayMicro(1);
}
//invia dati al display
void lcdData(byte data) {
PORTD = (data & 0xF0);
PORTB |= (1<<RS);
PORTB |= (1<<E);
delayMicro(1);
PORTB &= ~(1<<E);
delayMicro(1);
PORTD = ( (data<<4) & 0xF0 );
PORTB |= (1<<RS);
PORTB |= (1<<E);
delayMicro(1);
PORTB &= ~(1<<E);
delayMicro(1);
}
void setup(){
//disabilita interrupt
cli();
//configura Timer0 per la funzione delay
TCCR0A = 0;
TCCR0B = 0;
TCCR0A = (1 << WGM01);
TCCR0B = (1 << CS01) | (1 << CS00);
OCR0A = 250;
TIMSK0 = (1 << OCIE0A);
//configura Timer1 per inviare interrupt ogni 0,5s
TCCR1A = 0;
TCCR1B = 0;
TCCR1B = (1 << CS12);
TCCR1B |= (1 << WGM12);
OCR1A = 31250;
TIMSK1 = (1 << OCIE1A);
//configura Timer2 per generare un interrupt "ogni 0.25s"
TCCR2A = 0;
TCCR2B = 0;
TCCR2A = (1 << WGM21);
TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20);
OCR2A = 250;
TIMSK2 = (1 << OCIE2A);
//imposta l'ADC
ADMUX = (1 << ADLAR) | (1 << REFS0);
ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
ADCSRA |= (1 << ADSC);
//riabilita interrupt
sei();
// imposta il pin PD2 come input ed abilita il pull-up (0 se premuto, 1 altrimenti)
DDRD &= ~(1 << PD2);
PORTD |= (1 << PD2);
//imposta i pin del display in output
DDRB |= (1<<RS) | (1<<E);
DDRD |= (1<<D4) | (1<<D5) | (1<<D6) | (1<<D7);
//inizializzano il display
lcdComando(0b00110010);
delayMicro(50);
lcdComando(0b00101000);
delayMicro(50);
lcdComando(0b00001100);
delayMicro(50);
lcdComando(0b00000110);
delayMicro(50);
lcdComando(0b00000001);
delayMilli(2);
//avvia la partita
reset();
}
//gestisce la logica di gioco
void loop(){
if (!gameover) {
if (updatePacman) {
muoviPac(UltimaDirezione); //muove pacman
updatePacman = false; //resetta la flag
}
if (updateFan) {
muoviFan(); //muove il fantasma
updateFan = false; //resetta la flag
}
//se pacman collide con la pillola entra in powerup (iniziano i cinque secondi di energia);
//le posizioni della pillola vengono poi impostate fuori dal display per rimuoverla dal gioco
if (PosPacX==PosPilX && PosPacY==PosPilY) {
powerup = true;
powerupTimer = 10; //10 * 500ms = 5 secondi di powerup
PosPilX = 32; //un qualsiasi numero tra 20 e 255 va bene, 32 è arbitrario
PosPilY = 32;
}
//se pacman collide con il fantasma il gioco termina e viene mostrata la schermata finale;
if (PosPacX==PosFanX && PosPacY==PosFanY) {
gameover = true;
schermataFinale();
}
}
//se sta venendo mostrata la schermata finale, il gioco attende il reset (pulsante joystick)
else {
bottonePremuto();
if(bottone){
bottone=false;
reset();
}
}
}
//permette di spostare il cursore
void spostaCursore(uint8_t x, uint8_t y) {
lcdComando(0x80 + Colonna[y] + x);
}
//formula calcolo distanza con effetto pacman
uint8_t distanzaConEffetto (uint8_t x, uint8_t y, uint8_t j, uint8_t k){
return min(abs(x-j),20-abs(x-j)) + min(abs(y-k),4-abs(y-k));
}
//controlla se è stato premuto il pulsante del joystick
void bottonePremuto() {
static bool statoPrecedente = true; //traccia lo stato precedente, inizialmente "non premuto"
bool statoAttuale = PIND & (1 << PD2); //legge lo stato attuale
if (!statoAttuale && statoPrecedente) { //rileva una pressione (passaggio da alto a basso)
bottone = true;
}
statoPrecedente = statoAttuale; //aggiorna lo stato precedente
}
void muoviPac(Direzione UltimaDirezione){
//cancella pacman dalla posizione precedente
spostaCursore(PosPacX, PosPacY);
lcdData(' ');
//gestione direzioni con effetto pacman
switch (UltimaDirezione){
case SU: {
PosPacY = (PosPacY + 3) % 4;
} break;
case GIU: {
PosPacY = (PosPacY + 1) % 4;
} break;
case DESTRA: {
PosPacX = (PosPacX + 1) % 20;
} break;
case SINISTRA: {
PosPacX = (PosPacX + 19) % 20;
} break;
//in posizione NEUTRA pacman non si muove
default: break;
}
//fa apparire pacman nella prossima posizione
spostaCursore(PosPacX, PosPacY);
lcdData('C');
}
void muoviFan(){
//cancella il fantasma dalla posizione precedente;
//se il fantasma si trovava sopra la pillola (coprendola), questa riappare
spostaCursore(PosFanX, PosFanY);
if (PosFanX==PosPilX && PosFanY==PosPilY) lcdData('o');
else lcdData(' ');
//controlla se il fantasma debba andare verso / fuggire da pacman
int8_t direzione = powerup ? -1 : 1;
//se il fantasma non è sulla stessa colonna di pacman vi ci si avvicina/allontana
if (PosFanX != PosPacX){
//calcolo distanza orizzontale con effetto pacman
int8_t distanzaX = PosPacX - PosFanX;
if (distanzaX > 10) distanzaX -= 20;
if (distanzaX < -10) distanzaX += 20;
//movimento lungo le righe
if (distanzaX * direzione < 0){
PosFanX = (PosFanX + 19) % 20;
}
else if (distanzaX * direzione > 0){
PosFanX = (PosFanX + 1) % 20;
}
}
else if (PosFanY != PosPacY){
//calcolo distanza verticale con effetto pacman
int8_t distanzaY = PosPacY - PosFanY;
if (distanzaY > 2) distanzaY -= 4;
if (distanzaY < -2) distanzaY += 4;
//movimento lungo le colonne
if (distanzaY * direzione < 0) {
PosFanY = (PosFanY + 3) % 4;
}
else if (distanzaY * direzione > 0) {
PosFanY = (PosFanY + 1) % 4;
}
}
//sposta il cursore e disegna il fantasma nella nuova posizione
spostaCursore(PosFanX, PosFanY);
lcdData('$');
}
void generaPersonaggi(){
//sposta il cursore in una cella casuale e vi ci posiziona pacman
PosPacX=random()%20;
PosPacY=random()%4;
spostaCursore(PosPacX,PosPacY);
lcdData('C');
//genera il fantasma ad almeno 5 caselle di distanza da pacman
do{
PosFanX=random()%20;
PosFanY=random()%4;
} while (distanzaConEffetto(PosPacX,PosPacY,PosFanX,PosFanY)<5);
spostaCursore(PosFanX,PosFanY);
lcdData('$');
generaPillola();
}
//genera la pillola
void generaPillola(){
do{
PosPilX=random()%20;
PosPilY=random()%4;
} while (distanzaConEffetto(PosPacX,PosPacY,PosPilX,PosPilY)<5 ||
distanzaConEffetto(PosFanX,PosFanY,PosPilX,PosPilY)<5);
spostaCursore(PosPilX,PosPilY);
lcdData('o');
}
//gestisce la schermata finale al termine della partita
void schermataFinale(){
//pulisce il display e sposta il cursore in alto al centro
lcdComando(0b00000001);
delayMilli(2);
spostaCursore(6,0);
//stampa "Vittoria!" o "Sconfitta". In caso di vittoria stampa anche il tempo impiegato
//visto che I/D = high il cursore automaticamente si sposta a destra dopo ogni carattere
if(powerup){
contaSecondi=contaSecondi/2; //calcola il tempo in secondi
if(contaSecondi>999) contaSecondi=999; //imposta un tempo massimo (999s)
//separa cifre centinaia, decine ed unità e le converte nel loro valore ascii per il display
uint8_t centinaia = (contaSecondi/100)+'0';
uint8_t decine = ((contaSecondi/10)%10)+'0';
uint8_t unita = (contaSecondi%10)+'0';
lcdData('V');
lcdData('i');
lcdData('t');
lcdData('t');
lcdData('o');
lcdData('r');
lcdData('i');
lcdData('a');
lcdData('!');
lcdData(' ');
spostaCursore(8,2);
lcdData(centinaia);
lcdData(decine);
lcdData(unita);
lcdData('s');
}
else{
lcdData('S');
lcdData('c');
lcdData('o');
lcdData('n');
lcdData('f');
lcdData('i');
lcdData('t');
lcdData('t');
lcdData('a');
}
}
//effettua il reset azzerando le variabili usate per tenere conto dello stato di gioco
void reset(){
lcdComando(0b00000001);
delayMilli(2);
contaSecondi=0;
bottone=false;
powerup=false;
powerupTimer=0;
UltimaDirezione=NEUTRA;
AlternaXY=true;
gameover=false;
generaPersonaggi();
}
//legge i valori analogici del joystick e li converte in digitale
ISR(ADC_vect) {
if (AlternaXY) {
x = ADCH;
ADMUX = (1 << REFS0) | (1 << MUX0) | (1 << ADLAR);
}
else {
y = ADCH;
ADMUX = (1 << REFS0) | (1 << ADLAR);
}
//imposta UltimaDirezione in base alla direzione di movimento
//se nessuna soglia è raggiunta (joystick in posizione neutra) la direzione non cambia
if (y>200) UltimaDirezione=SU;
if (y<50) UltimaDirezione=GIU;
if (x<50) UltimaDirezione=DESTRA;
if (x>200) UltimaDirezione=SINISTRA;
//prepara la prossima conversione
AlternaXY = !AlternaXY;
ADCSRA |= (1 << ADSC);
}
//routine per aumentare il counter che conta i millisecondi, usato per i delay
ISR(TIMER0_COMPA_vect) {
counter++;
}
//gestisce lo spostamento, la durata del powerup, il tempo di gioco
ISR(TIMER1_COMPA_vect) {
if(!powerup){updatePacman = true;}
updateFan = true;
if (powerup) {
if (--powerupTimer == 0 && !gameover) {
powerup = false;
generaPillola();
}
}
contaSecondi++;
}
//gestisce lo spostamento di Pacman durante l'energia
ISR(TIMER2_COMPA_vect) {
timer2OverflowCount++;
if (timer2OverflowCount >= 15) { //prescaler "fai da te" = 16 (conta da 0 a 15)
timer2OverflowCount = 0;
if (powerup) {
updatePacman = true;
}
}
}
//funzione di delay in millisecondi utilizzando Timer0
void delayMilli(uint32_t milli) {
uint32_t target = counter + milli;
while ( counter < target) {
}
}
//funzione di delay in microsecondi utilizando Timer0
void delayMicro(uint32_t micro) {
uint32_t millisec = micro / 1000;
uint32_t restantiMicro = micro % 1000;
delayMilli(millisec);
if (restantiMicro > 0) {
uint32_t start = 0;
while (start < restantiMicro) {
start++;
asm volatile ("nop");
}
}
}