AngoLinux

Gioco Guardie e Ladri con tre processi, ncurses e mailbox

- A cura del Prof. Stefano Salvi -


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