|
Sperimentiamo anche le message box.
Il testo dell'esercizio è il seguente:
Scrivere un programma in C per giocare a guardie e ladri contro il calcolatore.
Il ladro sarà rapperesentata da un carattere $, che si sposta
casualmente sullo schermo, cercando di allontanarsi dalla guardia.
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).
Useremo una messagebox per far comunicare i figli con il padre e tra loro.
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 messagebox 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).
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 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 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.
- È 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:
/* guardieladrim.c
* Stefano Salvi - 17/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, 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 una message box. Ognuno dei due figli inviera' un'apposita
* struttura nella coda. Il Padre leggera' in continuazione dalla coda ed agira' di conseguenza.
* Periodicamente la guardia inviera' tramite la coda la sua posizione al ladro.
* Il padre inviera' un messaggio ad entrambi i figli, per farli terminare.
*
*
* Per compilare:
* gcc guardieladrim.c -o guardieladrim -lncurses
*/
#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 */
/* Struttura per la comunicazione tra figli e padre */
struct pos{
long mtype; //tipo di messaggio
char c; //ladro($) o guardia(#)
int x; //posizione x
int y; //posizione y
};
void ladro (int msqid);
void guardia (int msqid);
void campo (int msqid);
int main()
{
int msqid; // riferimento della coda dei messaggi
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
// Crea la coda dei messaggi
msqid = msgget(IPC_PRIVATE,IPC_CREAT | S_IREAD | S_IWRITE);
pidLadro = fork(); // Genera il primo 'figlio'
if(pidLadro==0) // pidLadro == 0 -> e' il figlio (ladro)
{
ladro (msqid); // Esegue il lavoro del ladro
}
else
{ // Siamo ancora nel padre
pidGuardia=fork(); // Genera il secondo figlio (guardia)
if(pidGuardia==0) // pidGuardia == 0 -> e' il figlio (guardia)
{
guardia (msqid); // Esegue il lavoro della guardia
}
else
{ // Siamo ancora nel padre
campo (msqid); // Svogliamo il lavoro del padre (gestire il campo)
}
}
/* Attendo la fine dei due figli */
waitpid(pidLadro,&status,0);
waitpid(pidGuardia,&status,0);
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 periodicamente i suoi spostamenti al ladro tramite la messagebox
* Il movimento viene comunicato al padre tramite la messagebox.
*/
void ladro (int msqid)
{
struct pos pladro; // Posizione corrente del ladro
struct pos pinput; // Posizione della guardia, ricevuta
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
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;
while(1) // Ciclo infinito. Verra' terminato con una 'return'
{
if (msgrcv(msqid,&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;
}
/* 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
msgsnd(msqid,&pladro,sizeof(pladro)-sizeof(long),0);
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 'msqid'.
* Ogni 10 spostamenti il movimento viene anche comunicato al ladro
*/
void guardia (int msqid)
{
struct pos pguardia; // Posizione corrente della guardia
struct pos rcampo; // Risposta dal campo (terminazione)
int i = 0;
// Posizione iniziale : in basso a destra
pguardia.x = MAXX - 1;
pguardia.y = MAXY - 1;
pguardia.c='#'; // lo caratterizza come 'guardia'
pguardia.mtype=CAMPOTYP; //canale dell'invio della posizione della guardia al padre
//passo al padre la posizione iniziale
msgsnd(msqid,&pguardia,sizeof(pguardia)-sizeof(long),0);
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
}
//passo al padre la posizione la posizione corrente
pguardia.mtype=CAMPOTYP; //canale dell'invio della posizione della guardia al padre
msgsnd(msqid,&pguardia,sizeof(pguardia)-sizeof(long),0);
if (msgrcv(msqid,&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(msqid,&pguardia,sizeof(pguardia)-sizeof(long),0);
}
}
}
/* Funzione di visualizzazione e controllo (padre).
* Attende 'eventi' 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 msqid)
{
struct pos ladro,guardia, transito;
ladro.x=-1; // Mai usato
guardia.x=-1; // Mai usato
while(-1) // Ciclo infinito: terminero' con un return
{
if (msgrcv(msqid,&transito,sizeof(transito)-sizeof(long),CAMPOTYP,0) >0 )
{
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 (cancellazione e disegno)
if(guardia.x==ladro.x&&guardia.y==ladro.y)
{ // Il ladro ha raggiunto la guardia
transito.c='0';
// Invia messaggio di terminazione al ladro
transito.mtype=LADROTYP; //canale di invio al ladro della fine del programma
msgsnd(msqid,&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(msqid,&transito,sizeof(transito)-sizeof(long),0);
return;
}
}
}
}
|
Per provare il programma, scaricare il sorgente,
compilarlo con il comando cc guardieladrim.c -o guardieladrim -lncurses ed eseguirlo
con il comando ./guardieladrim.
[Home Page dell'ITIS "Fermi"]
[Indice Quarta]
[Precedente]
[Successivo]
© Ing. Stefano Salvi - Released under GPL licence
|