AngoLinux

Gioco Guardie e Ladri con tre processi, ncurses e pipe

- A cura del Prof. Stefano Salvi -


Aggiungiamo la comunicazione tra processi.

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.
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.
  • Un secondo figlio che leggerà la tastiera e sposterà di conseguenza la guardia.
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 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 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).
  • È 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:
/* guardieladrip.c
 * Stefano Salvi - 15/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.
 *
 * 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. Ognuno dei due figli scrivera' un'apposita
 * struttura nella pipe. Il Padre leggera' in continuazione dalla pipe ed agira' di conseguenza.
 *
 *
 * X compilare:
 * gcc guardieladrip.c -o guardieladrip -lncurses
 */

#include <stdio.h>
#include <curses.h>
#include <stdlib.h>
#include <unistd.h>


#define RAGGIO	 1	/* di quanto si sposta il ladro ad ogni ciclo */

#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{
  char c;	// 'soggetto' che invia il dato
  int x;	// Coordinate
  int y;	// Idem
};

void ladro (int pipeout);
void guardia (int pipeout);
void campo (int pipein);

int main()
{
int p[2];	// Array per la 'pipe' (coppia di descrittori)
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

  pipe(p);		// Crea la pipe per comunicare	

  pidLadro = fork();	// Genera il primo 'figlio'

  if(pidLadro==0)       // pidLadro == 0 -> e' il figlio (ladro)
  {
    ladro (p [1]);	// 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 (p [1]);	// Esegue il lavoro della guardia
    }
    else
    {			// Siamo ancora nel padre
      campo (p [0]);	// Svogliamo il lavoro del padre (gestire il campo)
    }
  }

  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
  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 viene comunicato al padre tramite la pipe 'pipeout'.
 */
void ladro (int pipeout)
{
struct pos pladro;
int deltax;		// Spostamento orizzontale
int deltay;		// Spostamento verticale

  // Posizione iniziale del ladro: in alto a sinistra.
  pladro.x = 1;
  pladro.y = 1;
  pladro.c='$';	// lo caratterizza come ladro

  write(pipeout,&pladro,sizeof(pladro));  //passo al padre la posizione iniziale del ladro

  while(1)    // ciclo infinito. viene terminato 'a forza' dal padre
  {
    /* 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));

    // 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));

    // Se con lo spostamento esce, inverto lo spostamento
    if(pladro.y + deltay < 1 || pladro.y + deltay >= MAXY)
    {
      deltay = -deltay;
    }

    pladro.y += deltay;		// Spostamento verticale
			
    write(pipeout,&pladro,sizeof(pladro));  //passo al padre la posizione del ladro

    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 pipe 'pipeout'.
 */
void guardia (int pipeout)
{
struct pos pguardia;

  // Posizione iniziale : in basso a destra
  pguardia.x = MAXX - 1;
  pguardia.y = MAXY - 1;
  pguardia.c='#';	// lo caratterizza come 'guardia'

  write(pipeout,&pguardia,sizeof(pguardia));  //passo al padre la posizione iniziale

  while(1)
  {
  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
    }

    write(pipeout,&pguardia,sizeof(pguardia));  //passo al padre la posizione della guardia
  }
}

/* Funzione di visualizzazione e controllo (padre).
 * Attende 'eventi' dalla pipe (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 pipein)
{
struct pos ladro, guardia, letto;

  ladro.x=-1;	// Mai usato
  guardia.x=-1;	// Mai usato

  do {
    read(pipein,&letto,sizeof(letto));	// Leggo l'evento

    if(letto.c=='$')			// e' il ladro
    {
      if (ladro.x>=0)			// Se non e' la prima volta
      {
        mvaddch(ladro.y,ladro.x,' ');	// Cancello il vecchio ladro
      }
      ladro=letto;			// 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=letto;			// Aggiorno la posizione della guardia
    }

    mvaddch(letto.y,letto.x,letto.c);	// Disegno il nuovo 'personaggio'
    curs_set(0);			// Rielimino il cursore
    refresh();				// Aggiorno le modifiche (cancellazione e disegno)
    // Continuo fino a che' le coordinate non coincidono
  } while (guardia.x != ladro.x || guardia.y != ladro.y);
}

Per provare il programma, scaricare il sorgente, compilarlo con il comando cc guardieladrip.c -o guardieladrip -lncurses ed eseguirlo con il comando ./guardieladrip.


[Home Page dell'ITIS "Fermi"] [Indice Quarta] [Precedente] [Successivo]

© Ing. Stefano Salvi - Released under GPL licence