|
Facciamo sperimentare anche la compilazione condizionale.
Il testo dell'esercizio è il seguente:
Scrivere un programma in C per giocare a guardie e ladri contro il calcolatore.
Il programma dovrà essere la fusione dei due precedenti con le
pipe e con i messaggi,
utilizzando una compilazione condizionale per produrre l'una versione o l'altra.
Il ladro sarà rapperesentata da un carattere $, che si sposta
casualmente sullo schermo, cercando di allontanarsi dalla guardia, se il metodo di
comunicazione è la messagebox.
La guardia sarà rappresentata da un carattere #, che verrà
mosso sullo schermo con i tasti freccia dall'utente.
Quando la guardia ed il ladro si incontreranno, il gioco terminerà.
Useremo tre processi:
- Un primo processo (il padre) che gestirà la visualizzazione e
controllerà l'incontro della guardia con il ladro, ponendo fine al gioco
- Un primo figlio che muoverà il ladro a caso, entro un raggio di 1
casella attorno alla posizione precedente, ad un ritmo di 5 spostamenti al secondo,
spostandosi però di più nella direzione opposta alla guardia.
- Un secondo figlio che leggerà la tastiera e sposterà di
conseguenza la guardia.
Questo processo comunicherà di tanto in tanto la sua posizione al ladro
(ad esempio ogni 10 spostamenti) se la comunicazione avviene tramite la
messagebox.
Useremo una messagebox per far comunicare i figli con il padre e tra loro,
oppure una pipe per fare comnunicare i figli con il padre.
Se useremo le messagebox, ogni processo avrà il suo tipo di
messaggio. Entrambi i figli scriveranno un record con le coordinate ed il tipo di
figlio e lo invieranno con il tipo messaggio del padre.
La guardia invierà di tanto in tanto lo stesso messaggio anche con il
tipo di messaggio del ladro, per avvisarlo della sua posizione.
Il padre leggerà dalla pipe e visualizzerà il movimento.
Quando il gioco termina, il padre, che rileva la terminazione, invia un
messaggio ad ogni figlio, per farlo terminare. Questo messaggio avrà un
tipo di figlio uguale a '0'.
I figli, che attenderanno questo tipo messaggio, termineranno la loro esecuzione
all'arrivo del prossimo evento (il prossimo spostamente per il ladro, il prossimo tasto
per la guardia).
Se invece useremo le pipe, useremo un'unica pipe per far comunicare i
figli con il padre. Entrambi i figli scriveranno un record con le coordinate ed il
tipo di figlio e lo invieranno sulla stessa pipe.
Il padre leggerà dalla pipe e visualizzerà il movimento.
Quando il gioco termina, il padre, che rileva la terminazione, termina i
figli.
NOTE:
- Per generare processi in Linux si usa la funzione int fork ().
Questa funzione genera un nuovo processo come copia del processo corrente.
Il nuovo processo avrà una copia esatta delle variabili del processo originale e
l'esecuzione di questo nuovo processo comincerà dal punto immediatamente successivo
alla funzione fork ().
Per distinguere il processo che ha richiamato la fork (chiamato in genere padre) da quello prodotto ex novo dalla funzione stessa (chiamato figlio), si deve analizzare il valore di ritorno della funzione stessa:
- Un valore minore di zero (-1)
- Siamo nel processo padre, nessun figlio è stato creato (errore).
- Zero
- Siamo nel figlio.
- Un valore maggiore di zero
- Siamo nel padre. Il numero ritornato è il Process Identifyer (PID)
del processo figlio generato.
- Per fare un ritardo breve in maniera semplice possiamo usare la funzione
usleep (int microsecondi);. Come parametro indicheremo il numero di
microsecondi di ritardo che vogliamo fare.
- Per creare una nuova coda di messaggi potremo utilizzare la funzione
int msgget(key_t key, int msgflg);. Il parametro
key indica la coda cui collegarsi. Se vogliamo una nuova coda, da usare
solo nel nostro programma, dovremo utilizzare il valore standard IPC_PRIVATE.
Nel parametro msgflg potremo mettere il valore IPC_CREAT pre creare la nuova coda.
Se la creazione non va a buon fine, la funzione ritorna il valore minore do
0. Se invece ritorna un valore maggiore di 0, questo sarà il riferimento
alla coda appena creata.
- Per inserire messaggi nella coda utilizzeremo la funzione
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);.
Il parametro msqid sarà l'identificativo della coda ritornato da
msgget. Il parametro msgp punta alla struttura da
inserire nella coda. Questa struttura portrà contenere tutto ciò che voglamo
spedire, ma il primo campo dovrà essere un long, contenente il tipo di
messaggio (un numero positivo). Il parametro msgsz sarà la
dimensione dei dati ad eccezione del long che indica il tipo di messaggio,
quindi sizeof(msg)-sizeof(long). Il parametro msgflg
conterrà una serie di costanti per modificare il comportamento della funzione. Nel
caso della msgsnd non useremo nessuna opzione. La funzione ritorna un valore minore di 0 se incontra un'errore.
- Per recuperare messaggi dalla coda potremo utilizzare la funzione
ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msg_typ, int msgflg);
Il parametro msqid sarà l'identificativo della coda ritornato da
msgget. Il parametro msgp punta alla struttura nella quale
mettere il dato recuperato dalla coda. Il primo campo di questa struttura dovrà
essere obbligatoriamente un long, nel quale verrà posto il tipo di
messaggio (un numero positivo). Il parametro msgsz sarà la
dimensione del buffer a disposizione ad eccezione del long che indica il
tipo di messaggio, quindi sizeof(buf)-sizeof(long). Il parametro
msg_typ indicherà quali messaggi estrarre dalla coda. Se sarà:
- positivo
- verrà estratto il primo messaggio il cui tipo corrisponde a
msg_typ
- zero
- verrà estratto il primo messaggio della coda, qualunque tipo esso abbia.
- negativo
- verrà estratto il primo messaggio il cui tipo minore o uguale al valore
assoluto di msg_typ
Il parametro msgflg
conterrà una serie di costanti per modificare il comportamento della funzione. Ad esempio potremo utilizzare IPC_NOWAIT se vogliamo che la funzione non si blocchi in attesa di un messaggio, nel caso la coda sia vuota.
- Per creare una pipe, si deve usare la funzione
int pipe (int fildes [2]);. Questa funzione pone nei interi i descrittori
di due file non bufferati:
- fildes [0]
- è il descrittore aperto in ingresso. Potremo usarlo per la funzione
read.
- fildes [1]
- è il descrittore aprerto in uscita, lo potremo usare per la funzione
write.
- Per la visualizzazione si useranno le funzioni di ncurses, come
spiegate nel relativo tutorial.
- Per attendere la terminazione di un figlio, si può utilizzare la
funzione waitpid (int pid, int *status);. Questa funzione attende la
terminazione del processo il cui PID corrisponde al valore del parametro
pid. Nella variabile intera status, il cui
indirizzo verrà passato alla funzione, verranno insrite una serie di
informazioni su come il programma è stato terminato.
- Per terminare un processo si dovrà inviargli un segnale con la funzione
int kill(pid_t pid, int sig);. Dovremo utilizzare il PID che ci ha
consegnato la funzione fork, per il figlio. Come segnale potremo
utilizzare un qualunque numero (ad esempio 1).
- Per creare un movimento casuale potremo sommare un numero casuale che
vada da -RAGGIO a +RAGGIO ad entrambe le coordinate della
posizione corrente del ladro.
La funzione random(); ritorna un numero intero positivo compreso tra 0 e
MAXINT. Per ottenere quindi un numero casuale in un campo minore useremo
l'operatore modulo (ad esempio random () % MAX). Dato che noi vogliamo
un numero compreso tra -RAGGIO e RAGGIO, avremo
RAGGIO numeri negativi, RAGGIO numeri positivi e
lo 0, quindi 2*RAGGIO+1 possibili numeri, che otterremo con
random()%(RAGGIO*2+1).
Queste combinazioni sono per&ogravi tutte positive. Per averne RAGGIO negative, dovremo
sottrarre al numero ottenuto RAGGIO+1, quindi la formula definitiva
sarà random()%(RAGGIO*2+1)-(RAGGIO+1).
Se poi vogliamo che il movimento casuale porti comunque il ladro in una certa
direzione, possiamo caloclare il nonstro spostamento casuale non a partire dalla
vecchia posizione, ma da una posizione spostata nella direzione in cui vogliamo
muoverci.
Ammettiamo di avere un secondo punto e volerci allontanare da esso. Se sottraiamo alle nostre coordinate quelle di quel punto, avremo due numeri ognuno dei quali sarà:
- Positivo
- se la relativa coordinata del punto da sfuggire è inferiore alla nostra,
quindi per fuggire dovremo aumentare la nostra coordinata
- Uguale a 0
- se la relativa coordinata del punto da sfuggire è uguale alla nostra, quindi
per fuggire dovremo lasciare immutata la nostra coordinata e fuggire lungo l'altra
direzione
- Negativo
- se la relativa coordinata del punto da sfuggire è superiore alla nostra,
quindi per fuggire dovremo calare la nostra coordinata
Se noi limitiamo questi numeri al campo -1...1 ed aggiungiamo questi
numeri alle coordinate correnti del ladro nella formula precedente, esso tenderà a
muoversi nella direzione opposta al punto di riferimento.
Nel caso della pipe, potremo usare la stessa formula, ma lasciare a
zero lo spostamento, per non tenere conto della posizione della guardia, che non conosciamo.
- È necessario controllare che né la guardia né il ladro escano
dal campo.
Nel caso della guardia, occorrerà bloccarla quando raggiunge il bordo.
Per il ladro, invece, è opportuno invertire lo spostamento se questo
portasse fuori campo.
- È opportuno scrivere funzioni diverse per i figli ed il campo,
invece che includere tutto il codice nella funzione main.
Una possibile soluzione è la seguente:
/* guardieladripm.c
* Stefano Salvi - 19/9/02
* Gioco 'guardie e ladri'.
* Un 'ladro' (rappresentato da un '$') fugge a caso sullo schermo.
* Una 'guardia' (raprresentata da un '#') lo insegue, pilotato dai tasti del giocatore.
* Il gioco termina quando la guardia raggiunge il ladro.
*
* Per realizzare il gioco utilizzeremo tre processi:
* 1) processo 'padre'. Gestisce la visualizzazione e verifica se la guardia ha preso il ladro.
* Per fare questo deve memorizzarsi le coordinate del ladro.
* Quando il gioco e' terminato, 'uccide' i figli e termina (se usa le pipe) oppure invia
* un messaggio ai figli, che terminano, ed attende la loro terminazione.
*
* 2) processo 'figlio - ladro'. Genera casualmente uno spostamento, che comunica al 'padre'
* per la visualizzazione
*
* 3) processo 'figlio - guardia'. Legge la tastiera e calcola la nuova posizione, in base
* ai tasti letti. Comunica questa nuova posizione al 'padre'.
*
* I tre processi comunicano tramite un'unica pipe oppure con una coda di messaggi.
* Ognuno dei due figli scrivera' un'apposita struttura nella pipe. Il Padre leggera' in
* continuazione dalla pipe ed agira' di conseguenza.
* Nel caso della coda di messaggi, periodicamente la guardia inviera' tramite la coda la
* sua posizione al ladro.
* Il padre inviera' un messaggio ad entrambi i figli, per farli terminare.
*
*
* X compilare:
* gcc guardieladripm.c -o guardieladrip -lncurses (versione pipe)
* gcc -DMESSAGE -oguardieladrim guardieladripm.c -lncurses (versione messaggi)
*/
#include <stdio.h>
#include <curses.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#define RAGGIO 1 /* di quanto si sposta il ladro ad ogni ciclo */
#define CAMPOTYP 2 /* Messaggi per il campo */
#define LADROTYP 3 /* Messaggi per il ladro */
#define GUARDIATYP 4 /* Messaggi per la guardia */
#define SU 65 /* Freccia su */
#define GIU 66 /* Freccia giu */
#define SINISTRA 68 /* Freccia sinsitra */
#define DESTRA 67 /* Freccia destra */
#define MAXX 80 /* Numero di colonne */
#define MAXY 24 /* Numero di righe */
// #define MESSAGE
/* Struttura per la comunicazione tra figli e padre */
struct pos{
#ifdef MESSAGE
long mtype; //tipo di messaggio
#endif
char c; //ladro($) o guardia(#)
int x; //posizione x
int y; //posizione y
};
void ladro (int canale);
void guardia (int canale);
void campo (int canale);
int main()
{
#ifdef MESSAGE
int msqid; // riferimento della coda dei messaggi
#else
int p[2]; // Array per la 'pipe' (coppia di descrittori)
#endif
int status; // stato di ritorno dei processi figli
int pidLadro; // Pid del figlio 'ladro'
int pidGuardia; // Pid del figlio 'guardia'
initscr(); // Inizializza curses
noecho(); // caratteristica della tastiera
curs_set(0); // elimina il cursore
#ifdef MESSAGE
// Crea la coda dei messaggi
msqid = msgget(IPC_PRIVATE,IPC_CREAT | S_IREAD | S_IWRITE);
#else
pipe(p); // Crea la pipe per comunicare
#endif
pidLadro = fork(); // Genera il primo 'figlio'
if(pidLadro==0) // pidLadro == 0 -> e' il figlio (ladro)
{
#ifdef MESSAGE
ladro (msqid); // Esegue il lavoro del ladro
#else
ladro (p [1]); // Esegue il lavoro della guardia
#endif
}
else
{ // Siamo ancora nel padre
pidGuardia=fork(); // Genera il secondo figlio (guardia)
if(pidGuardia==0) // pidGuardia == 0 -> e' il figlio (guardia)
{
#ifdef MESSAGE
guardia (msqid); // Esegue il lavoro della guardia
#else
guardia (p [1]); // Esegue il lavoro della guardia
#endif
}
else
{ // Siamo ancora nel padre
#ifdef MESSAGE
campo (msqid); // Svogliamo il lavoro del padre (gestire il campo)
#else
campo (p [0]); // Svogliamo il lavoro del padre (gestire il campo)
#endif
}
}
#ifdef MESSAGE
waitpid(pidLadro,&status,0);
waitpid(pidGuardia,&status,0);
#else
kill(pidLadro,1); // Termina il ladro (con segnale 1 - uno qualunque va' bene);
kill(pidGuardia,1); // Termina la guardia (con segnale 1 - uno qualunque va' bene)
getchar(); // Attendiamo la pressione di un tasto, prima di chiudere
#endif
endwin(); // Torniamo in modalita' normale
return 0; // Termine del programma
}
/* Funzione 'ladro'.
* Genera un 'movimento casuale' in un raggio RAGGIO rispetto al punto corrente,
* nel campo di 80X25 caratteri.
* Il movimento tende comunque a spostare il ladro lontano dalla guardia.
* La guardia comunica i suoi spostamenti al ladro (se uso la messagebox)
* Il movimento viene comunicato al padre tramite la messagebox o la pipe.
*/
void ladro (int canale)
{
struct pos pladro; // Posizione corrente del ladro
#ifdef MESSAGE
struct pos pinput; // Posizione della guardia, ricevuta
#endif
int deltax; // Spostamento orizzontale
int deltay; // Spostamento verticale
int fugax = 0,fugay = 0;// Spostamento del centro, per allontanarsi
// Posizione iniziale del ladro: in alto a sinistra.
pladro.x = 1;
pladro.y = 1;
pladro.c='$'; // lo caratterizza come ladro
#ifdef MESSAGE
pladro.mtype=CAMPOTYP; //canale di invio della posizione del ladro al padre
// Posizione iniziale della guardia: il ladro sta' fermo.
pinput.x = 1;
pinput.y = 1;
#endif
while(1) // Ciclo infinito. Verra' terminato con una 'return'
{
#ifdef MESSAGE
if (msgrcv(canale,&pinput,sizeof(pinput)-sizeof(long),LADROTYP,IPC_NOWAIT) >0 )
{
if(pinput.c=='0')
{
return;
}
}
fugax = pladro.x - pinput.x; // Individua la direzione della guardia
// normalizza fugax (-1, 0, 1)
if (fugax < 0)
{
fugax = -1;
}
else if (fugax > 0)
{
fugax = 1;
}
fugay = pladro.y - pinput.y; // Individua la direzione della guardia
// normalizza fugay (-1, 0, 1)
if (fugay < 0)
{
fugay = -1;
}
else if (fugay > 0)
{
fugay = 1;
}
#endif
/* Calcola uno spostamento casuale da -RAGGIO ... RAGGIO.
* Abbiamo 2*RAGGIO+1 combinazioni (le RAGGIO negative, le RAGGIO positive e lo 0).
* 'random() % (RAGGIO*2+1)' ritorna combinazioni da 0 a 2*RAGGIO: quelle che servono
* ma tutte positive. Per averne RAGGIO negative, dovremo toglier RAGGIO+1
*/
deltax= ((random() % (RAGGIO*2+1)) - (RAGGIO + 1) + fugax);
// Se con lo spostamento esce, inverto lo spostamento
if(pladro.x + deltax < 1 || pladro.x + deltax >= MAXX)
{
deltax = -deltax; // Inverto lo spostamento
}
pladro.x += deltax; // Spostamento orizzontale
/* Calcola uno spostamento casuale da -RAGGIO ... RAGGIO.
* per l'algoritmo vedere il deltax
*/
deltay = ((random()%(RAGGIO*2+1)) - (RAGGIO + 1) + fugay);
// Se con lo spostamento esce, inverto lo spostamento
if(pladro.y + deltay < 1 || pladro.y + deltay >= MAXY)
{
deltay = -deltay;
}
pladro.y += deltay; // Spostamento verticale
#ifdef MESSAGE
msgsnd(canale,&pladro,sizeof(pladro)-sizeof(long),0);
#else
write(canale,&pladro,sizeof(pladro)); //passo al padre la posizione del ladro
#endif
usleep(200000); // Pausa per 'dare il ritmo'
}
}
/* Funzione 'guardia'.
* Legge i tasti premuti dall'utente. Sposta la guardia nella direzione
* indicata dalle frecce. Si ferma ai bordi del campo di 80X25 caratteri.
* Il movimento viene comunicato al padre tramite la messagebox o la pipe.
* Se uso la messagebox, ogni 10 spostamenti il movimento viene anche comunicato al ladro
*/
void guardia (int canale)
{
struct pos pguardia; // Posizione corrente della guardia
#ifdef MESSAGE
struct pos rcampo; // Risposta dal campo (terminazione)
int i = 0;
#endif
// Posizione iniziale : in basso a destra
pguardia.x = MAXX - 1;
pguardia.y = MAXY - 1;
pguardia.c='#'; // lo caratterizza come 'guardia'
#ifdef MESSAGE
pguardia.mtype=CAMPOTYP; //canale dell'invio della posizione della guardia al padre
//passo al padre la posizione iniziale
msgsnd(canale,&pguardia,sizeof(pguardia)-sizeof(long),0);
#else
write(canale,&pguardia,sizeof(pguardia)); // passo al padre la posizione iniziale
#endif
while(1) // Ciclo infinito. Verra' terminato con una 'return'
{
char c;
c = getch(); // Legge il tasto
if (c==SU && pguardia.y > 0) // Freccia SU
{
pguardia.y-=1; // Cala la y
}
if(c==GIU && pguardia.y < MAXY - 1) // Freccia GIU
{
pguardia.y+=1; // Cresce la y
}
if((c==SINISTRA)&&pguardia.x > 0) // Freccia SINISTRA
{
pguardia.x-=1; // Cala la x
}
if(c==DESTRA && pguardia.x < MAXX - 1) // Freccia DESTRA
{
pguardia.x+=1; // Cresce la x
}
#ifdef MESSAGE
//passo al padre la posizione la posizione corrente
pguardia.mtype=CAMPOTYP; //canale dell'invio della posizione della guardia al padre
msgsnd(canale,&pguardia,sizeof(pguardia)-sizeof(long),0);
if (msgrcv(canale,&rcampo,sizeof(rcampo)-sizeof(long),GUARDIATYP,IPC_NOWAIT) >0 )
{
if(rcampo.c=='0')
{
return;
}
}
i++;
if(!(i%10))
{
pguardia.mtype=LADROTYP; //canale dell'invio della posizione della guardia al ladro
msgsnd(canale,&pguardia,sizeof(pguardia)-sizeof(long),0);
}
#else
write(canale,&pguardia,sizeof(pguardia)); //passo al padre la posizione della guardia
#endif
}
}
/* Funzione di visualizzazione e controllo (padre).
* Attende 'eventi' dalla pipe o dalla messagebox (nella quale scrivono entrambi i figli).
*
* Se non e' il primo evento (per ogni figlio), prima cancello la posizione
* vecchia.
*
* Se la posizione della guardia corrisponde a quella del ladro, termina il gioco.
*/
void campo (int canale)
{
struct pos ladro,guardia, transito;
ladro.x=-1; // Mai usato
guardia.x=-1; // Mai usato
while(-1) // Ciclo infinito: terminero' con un return
{
#ifdef MESSAGE
if (msgrcv(canale,&transito,sizeof(transito)-sizeof(long),CAMPOTYP,0) >0 )
{
#else
read(canale,&transito,sizeof(transito)); // Leggo l'evento
#endif
if(transito.c=='$') // e' il ladro
{
if (ladro.x>=0) // Se non e' la prima volta
{
mvaddch(ladro.y,ladro.x,' '); // Cancello il vecchio ladro
}
ladro=transito; // Aggiorno la posizione del ladro
}
else //e' la guardia
{
//devo prima cacellare il la pos precedente della guardia
if (guardia.x>=0) // Se non e' la prima volta
{
mvaddch(guardia.y,guardia.x,' '); // Cancello la vecchia guardia
}
guardia=transito; // Aggiorno la posizione della guardia
}
mvaddch(transito.y,transito.x,transito.c); // Disegno il nuovo 'personaggio'
curs_set(0); // Rielimino il cursore
refresh(); // Aggiorno le modifiche (cancell. e disegno)
if(guardia.x==ladro.x&&guardia.y==ladro.y)
{ // Il ladro ha raggiunto la guardia
#ifdef MESSAGE
transito.c='0';
// Invia messaggio di terminazione al ladro
transito.mtype=LADROTYP; //canale di invio al ladro della fine del programma
msgsnd(canale,&transito,sizeof(transito)-sizeof(long),0);
// Invia messaggio di terminazione alla guardia
transito.mtype=GUARDIATYP; //canale di invio alla guardia della fine del programma
msgsnd(canale,&transito,sizeof(transito)-sizeof(long),0);
#endif
return;
}
#ifdef MESSAGE
}
#endif
}
}
|
Per provare il programma, scaricare il sorgente,
compilarlo con il comando cc guardieladripm.c -o guardieladrip -lncurses per ottenere
la versione con la pipe, da eseguire con il comando ./guardieladrip.
Per la versione con la messegebox compilarlo con il comando cc
guardieladripm.c -DMESSAGE -o guardieladrim -lncurses oppure togliere il commento alla
istruzione #define MESSAGE ed eseguire con il comando
./guardieladrim.
[Home Page dell'ITIS "Fermi"]
[Indice Quarta]
[Precedente]
[Successivo]
© Ing. Stefano Salvi - Released under GPL licence
|