|
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
|