Abbiamo implementato una versione semplificata del gioco Snake (link agli eseguibili per Linux 32/64 bit e macOS). Il gioco può essere controllato comunicando con il programma tramite pipe: lo scopo della verifica è implementare un programma che controlla il serpente e lo fa mangiare 20 volte.
Ecco uno screenshot della grafica super avanzata del gioco, dove il cibo è rappresentato con *, la testa del serpente con @ e il resto del corpo con O.
+--------------------+ | | | | | * | | | | @ | | O | | O | | O | | | | | | | | | | | | | | | | | | | | | | | | | | | +--------------------+
Snake legge dalla pipe /tmp/pipeSnakeIn i comandi di gioco. In particolare:
- S per iniziare la partita;
- > per girare a destra;
- < per girare a sinistra.
Se non viene inviato alcun comando, il serpente prosegue nel suo tragitto senza svoltare.
Ad ogni movimento, Snake scrive sulla pipe /tmp/pipeSnakeOut la distanza tra la testa del serpente e il cibo nel formato x, dove x è un numero floating point e la virgola fa da delimitatore.
Osservate inoltre che:
- la posizione iniziale del serpente è scelta in modo casuale ad ogni partita;
- la lunghezza del serpente è 4 e non varia quando viene mangiato del cibo;
- i muri possono essere attraversati senza perdere la partita.
Per questi motivi, è possibile realizzare un algoritmo per giocare usando come metrica solamente la distanza dal cibo e come questa varia quando viene fatto uno spostamento.
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #define PIPEIN "/tmp/pipeSnakeIn" #define PIPEOUT "/tmp/pipeSnakeOut" #define BUFSIZE 16 /* * Lo scopo della verifica è realizzare un programma in grado di giocare in maniera * automatica ad una versione semplificata di Snake, dove il serpente è di lunghezza * tale per cui sia impossibile che collida su sè stesso e i muri possono essere * attraversati senza perdere la partita. * Il programma fornito riceve i comandi di inizio partita e cambiamento della direzione * su PIPEIN e scrive su PIPEOUT la distanza (numero floating point sotto forma di stringa) * tra la testa del serpente e il cibo. * I cambiamenti di direzione vengono fatti in due casi: * - quando la distanza dal cibo aumenta rispetto alla lettura precedente; * - quando la distanza è 1, condizione necessaria affinché il serpente non ruoti intorno * al cibo all'infinito senza mai mangiarlo. * Il programma termina quando la read restituisce zero, ovvero la pipe è vuota ed è stata * chiusa in scrittura dall'altro processo. Questo accade quando la partita è stata vinta. */ int main() { int i, fd_in, fd_out; float prev_distance, distance; char won, overflow, buffer[BUFSIZE]; /* Crea le pipe (se non esistono) ed aprile. Termina se si verifica un errore durante * l'apertura di una delle due. */ mkfifo(PIPEOUT, 0666); mkfifo(PIPEIN, 0666); fd_in = open(PIPEOUT, O_RDONLY); fd_out = open(PIPEIN, O_WRONLY); if (fd_in < 0 || fd_out < 0) { perror("Cannot open pipes.\n"); exit(EXIT_FAILURE); } /* Inizializza la distanza precedente con un valore arbitrario. La scelta è irrilevante, * al massimo viene fatta una mossa in più ad inizio partita. */ prev_distance = 100; /* Inizia la partita inviando il carattere 'S' nella pipe. */ write(fd_out, "S", 1); won = overflow = 0; while (!won && !overflow) { /* Copia il contenuto della pipe nel buffer finché non si verifica una delle seguenti * condizioni: * - i >= BUFSIZE: overflow, ma non dovrebbe mai accadere; * - read restituisce zero: in questo caso la partita è terminata; * - è stato letto il separatore ','. */ i = 0; while (i < BUFSIZE && read(fd_in, buffer + i, 1) && buffer[i] != ',') { i++; } if (i == 0) { /* Dal momento che non possiamo avere due virgole consecutive nella pipe, ci * ritroviamo in questo caso quando read ha restituito 0, ovvero la partita * è terminata. */ won = 1; } else if (i >= BUFSIZE) { /* Per sicurezza, evitiamo gli overflow... */ overflow = 1; } else { /* Converti la distanza, espressa come stringa, in un numero floating point. */ sscanf(buffer, "%f,", &distance); /* Invia una mossa se si verifica una delle condizioni descritte nel commento iniziale. * Quando si usano i numeri in virgola mobile è preferibile evitare le uguaglianze (vedi * https://it.wikipedia.org/wiki/Numero_in_virgola_mobile#Problemi_con_l.27uso_della_virgola_mobile * per maggiori dettagli). */ if (distance <= 1.005 || distance >= prev_distance) { write(fd_out, "<", 1); } /* Aggiorna la distanza precedente. */ prev_distance = distance; } } /* Chiudi le pipe e stampa un messaggio di errore in caso di overflow. La rimozione delle pipe * dal filesystem viene fatta dall'altro programma. */ close(fd_in); close(fd_out); if (overflow) { fprintf(stderr, "Overflow? Non dovrebbe mai succedere!\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; }