Indice
Progetto finale: il gioco "Simon"
Vogliamo realizzare un clone di un famoso gioco elettronico degli anni '80: Simon. Il gioco consisteva nel ricordare e riprodurre una sequenza sonora e luminosa. Ad ogni turno i tasti colorati venivano illuminati in maniera casuale (ogni bottone emetteva anche un suono) poi il giocatore doveva ripetere la sequenza premendo i tasti nel giusto ordine. Ad ogni turno aumentava il numero di tasti da premere.
Per realizzare questo progetto dovremo generare una sequenza casuale con cui pilotare quattro LED colorati e verificare che il giocatore prema quattro pulsanti nel giusto ordine.
Il risultato finale è questo:
Progetto: realizzare un clone del gioco Simon
Scopo | realizzare un gioco di memoria interattivo |
---|---|
Componenti | 4 LED (giallo, blu, verde, rosso), 4 resistori da 220Ω, 4 pulsanti, 4 resistori da 10kΩ |
Approfondimento sul linguaggio di programmazione: le funzioni
Nello sketch, oltre alle funzioni setup()
e loop()
, compaiono due funzioni definite nel programma:
visualizzaSequenza()
, che mostra la sequenza dei LED da ricordareinputGiocatore()
, che attende che il giocatore prema un numero di bottoni corrispondenti al turno (un bottone al primo turno, due al secondo e così via) e interrompe il gioco in caso di errore
In uno sketch come questo, lungo un centinaio di righe, è utile usare le funzioni per raggruppare le istruzioni che si occupano di un compito ben preciso; in questo modo il programma è più leggibile ed è più facile modificarlo senza commettere errori.
E' importante sapere che le variabili definite all'interno di una funzione sono accessibili solo dalle istruzioni della funzione stessa mentre quelle definite all'esterno sono accessibili ovunque nel programma. Ad esempio è possibile accedere alla variabile turno
dentro la funzione inputGiocatore()
ma non è possibile accedere alla variabile pulsantePremuto
dalle funzioni visualizzaSequenza()
o loop()
. Questo è un bene perché permette di tenere separate le variabili che servono nell'intero programma da quelle utili solo dentro una certa funzione.
L'istruzione return
serve a restituire un risultato nelle funzioni che lo prevedono; nel nostro caso invece viene usata per uscire dalla funzione inputGiocatore()
in caso di errore e ridare il controllo alla funzione principale loop()
che l'aveva chiamata.
Schema elettrico
sketch
/* * Progetto Finale: un clone del gioco elettronico Simon. * Il giocatore deve memorizzare una sequenza luminosa casuale * e ripeterla accendendo i LED nell'ordine giusto. * Ad ogni turno il numero di LED accesi aumenta. * * si gioca una sola partita con dieci turni (per ricominciare resettare) * * Nome Studente: * Creato il: data */ // pin collegati ai quattro LED int led[] = { 2, 3, 4, 5}; // pin collegati ai quattro pulsanti int pulsante[] = { 8, 9, 10, 11}; // turno del gioco int turno = 1; // sequenza casuale per l'accensione dei LED int sequenza[10]; void setup(){ // pin collegati ai quattro LED for(int x = 0; x < 4; x++){ pinMode(led[x], OUTPUT); } // pin collegati ai quattro pulsanti for(int x = 0; x < 4; x++){ pinMode(pulsante[x], INPUT); } // comunicazione seriale per il debugging Serial.begin(9600); // GENERAZIONE DELLA SEQUENZA CASUALE // inizializzo il generatore di numeri casuali con un valore // casuale (l'input su un pin analogico non collegato) randomSeed(analogRead(0)); // genero 10 numeri tra 0 e 3 for(int x = 0; x < 10; x++){ sequenza[x] = random(0, 4); } } void loop(){ // giochiamo dieci turni poi loop continua senza fare niente while(turno <= 10){ Serial.print("turno "); Serial.println(turno); // chiamo la funzione che visualizza la sequenza visualizzaSequenza(); // chiamo la funzione che memorizza l'input del giocatore // e controlla se sono stati premuti i tasti giusti inputGiocatore(); // al prossimo turno un LED in più turno = turno + 1; // pausa tra un turno e l'altro delay(2000); } } void visualizzaSequenza(){ // SEQUENZA DI LED DA RICORDARE PER IL TURNO CORRENTE // primo turno: un solo LED, secondo turno due e così via for(int x = 0; x < turno; x++){ // accendo e spengo il LED corrispondente al numero casuale // contenuto nell'array sequenza digitalWrite(led[sequenza[x]], HIGH); delay(500); digitalWrite(led[sequenza[x]], LOW); delay(500); // stampo la sequenza Serial.print(sequenza[x]); } Serial.println(); } void inputGiocatore(){ // INPUT DEL GIOCATORE CON I PULSANTI CORRISPONDENTI AI LED // il giocatore preme un numero di pulsanti pari al turno for(int x = 0; x < turno; x++){ Serial.println("attesa input"); // attende la pressione di un pulsante memorizzata nella // variabile pulsantePremuto (0 = non premuto, 1 = premuto) int pulsantePremuto = 0; while(pulsantePremuto == 0){ // controlla lo stato dei quattro pulsanti for(int y = 0; y < 4; y++){ // se il pulsante è premuto if(digitalRead(pulsante[y]) == HIGH){ // stampo il numero del pulsante Serial.print("pulsante "); Serial.print(y); Serial.println(" premuto"); // accendo il LED corrispondente digitalWrite(led[y], HIGH); delay(200); digitalWrite(led[y], LOW); delay(200); // se è il pulsante sbagliato if(y != sequenza[x]){ Serial.println("errore!"); // segnalo l'errore facendo lampeggiare i LED for(int i = 0; i < 4; i++){ digitalWrite(led[i], HIGH); } delay(500); for(int i = 0; i < 4; i++){ digitalWrite(led[i], LOW); } delay(500); // azzero la variabile turno e comincio una nuova // partita con la stessa sequenza turno = 0; // esco dalla funzione inputGiocatore return; } // registro la pressione del pulsante pulsantePremuto = 1; // esco dal ciclo for (non controllo altri pulsanti) // e torno al ciclo while che termina break; } } } // pausa (debounce) tra la pressione di un tasto e l'altra delay(100); } }
Osservazioni
- per generare dei numeri interi casuali usiamo la funzione
random(minimo incluso, massimo escluso)
- il generatore di numeri casuali di Arduino produce una serie molto lunga ma prevedibile di numeri; per non avere ripetizioni si usa la funzione
randomSeed(numero casuale)
che fa partire la sequenza da una posizione a caso della serie; il numero casuale è ottenuto leggendo l'input da un pin analogico scollegato, il cui valore di tensione è ignoto e fluttuante - la sequenza casuale di numeri da 0 a 3 corrispondenti ai LED da accendere è contenuta nell'array
sequenza[]
; ad ogni turno accendere i LED dasequenza[0]
asequenza[turno-1]
(ricordiamo che si conta a partire da zero) - nella funzione
inputGiocatore()
- il ciclo for chiede al giocatore di premere un numero di tasti pari al turno
- il ciclo while attende la pressione di uno dei quattro pulsanti e verifica che il pulsante premuto sia corretto
- l'istruzione
break
viene usata per uscire dal ciclo for quando viene rilevata la pressione di un pulsante - l'istruzione
return
viene usata per uscire dalla funzione quando viene rilevato un'errore; in questo modo il giocatore non immette l'intera sequenza ma il gioco si interrompe appena si commette un errore - quando si sbaglia la variabile
turno
viene azzerata e il gioco riprende dall'inizio ma con la stessa sequenza casuale
- per ricominciare il gioco con una nuova sequenza casuale bisogna resettare
Variante: aggiungere il suono
Il gioco originale produceva un suono diverso per ogni LED. Per aggiungere questa funzionalità colleghiamo un cicalino piezoelettrico al pin 12. Poi modifichiamo il programma. Definiamo l'array suono[]
con le frequenza corrispondenti alla tonalità da emettere per ogni LED:
int suono[] = {262, 330, 392, 523};
Aggiungiamo la funzione tone()
quando viene acceso il LED nella funzione visualizzaSequenza()
:
tone(12, suono[sequenza[x]], 500);
Facciamo lo stesso nella fuzione inputGiocatore()
:
tone(12, suono[y], 500);
Volendo si potrebbe suonare una piccola melodia quando si commette un errore o quando si indovinano tutti e 10 i LED.
Navigazione
Torna all'indice.