AngoLinux

Pallina che rimbalza (con maschera e sfondo)

- A cura del Prof. Stefano Salvi -


Ancora in teoria si stà spiegando la struttura del processore e non c'è materiale per sperimentare l'assembler.
Si introducono quindi alcuni esercizi di vario genere, mirati in particolare alle operazioni bit a bit.

Il testo dell'esercizio è il seguente:

Scrivere un programma C che, utilizzando la svgalib e la svgalibgl, visualizzi un rettangolo sullo schermo, riempito di righe diagonali colorate, larghe un pixel.
Sulla base base del rettangolo si muoverà una paletta rettangolare, alta 5 pixel e larga 30, che si sposterà con i tasti n (sinistra) ed m (destra).
Nel rettangolo si muoverà anche una pallina cicolare, di 4 pixel di raggio che, muovendosi sempre diagonalmente, rimbalzerà sui quattro lati del rettangolo.
Se la pallina non colpisce la paletta, il programma termina. Il programma termina anche se l'utente preme il tasto ESC.
Gestire le maschere per visualizzare lo sfondo colorato al di sotto della palllina.

Note su vgalib e svgalibgl

Oltre alle funzioni già usate nel precedente esercizio e descritte nell'esperienza in Quarta questa volta dovremo anche rispondere alla tastiera. La funzione getchar() è bloccante e non esiste una funzione equivalente ala kbhit () del DOS, per sapere se c'è un tasto in attesa. Per poter gestire i tasti nel ciclo che esegue il movimento è possibile utilizzare una funzione della svgalib:

vga_getkey(void)
Ritorna il codice ASCII del tasto premuto, se è stato premuto un tasto dall'ultima chiamata, oppure 0 se non e' stato premuto alcun tasto. Comunque ritorna senza attendere.
Per poter fare le operazioni di copia da video a memoria e viceversa, oltre a disegnare figure geometriche piene o vuote, si deve utilizzare un'estensione della svgalib, chiamata svgalibgl. Vediamone le principali funzioni:
int gl_setcontextvga(int mode)
Và chiamata subito dopo la vga_setmode, utilizzando lo stesso valore per il parametro, ed è la prima funzione di svgalibgl da chiamare.
ATTENZIONE: non tutti i valori del parametro mode di vga_setmode sono supportati.
void gl_circle(int x, int y, int r, int c)
Disegna un cerchio vuoto con il centro alle coordinate x ed y, di raggio r con il colore di indice c.
void gl_fillcircle(int x, int y, int r, int c)
Disegna un cerchio pieno con il centro alle coordinate x ed y, di raggio r con il colore di indice c.
void gl_fillbox(int x, int y, int w, int h, int c)
Disegna un rettangolo pieno dalle coordinate x ed y, largo w pixel ed alto h, con il colore di indice c.
void gl_line(int x1, int y1, int x2, int y2, int c)
Disegna una linea dal punto (x1,y1) al punto (x2,y2), con il colore di indice c.
void gl_getbox(int x, int y, int w, int h, void *dp)
Copia un rettangolo dello schermo, dalle coordinate x ed y, largo w pixel ed alto h, mettendone il contenuto nel buffer puntato da dp. Il buffer che conterrà l'immagine potrà essere un array di unsigned char di dimensione w*h*BYTESPERPIXEL dove w ed h sono i parametri della funzione e BYTESPERPIXEL indica quanti byte occupa un pixel sullo schermo. Nel nostro caso, usando 256 colori un pixel occupa un byte.
void gl_putbox(int x, int y, int w, int h, void *dp)
È l'inversa della gl_getbox.Riempie un'area dello schermo, dalle coordinate x ed y, largo w pixel ed alto h, con il contenuto del buffer puntato da dp. Il buffer che conterrà l'immagine potrà essere un array di unsigned char di dimensione w*h*BYTESPERPIXEL dove w ed h sono i parametri della funzione e BYTESPERPIXEL indica quanti byte occupa un pixel sullo schermo. Nel nostro caso, usando 256 colori un pixel occupa un byte.

Aree non rettangolari e maschere

Immaginiamo di voler sovrapporre un'immagine ad uno sfondo:

+

=

Sfondo Immagine Risultato
Notiamo che sovrapponendo semplicemente l'immagine allo sfondo, l'immagine risulta dentro ad un riquadro verde. Dovremmo allora cercare di creare una nuova immagine che contenga lo sfondo dove l'immagine è verde, e l'immagine originale dove l'immagine non è verde.
Creiamo allora un'immagine in bianco e nero, che sia nera dove l'immagine è verde e bianca dove l'immagine non è verde. Chiameremo quest'immagine maschera.

=>

Immagine Maschera
Per finire il nostro lavoro, ci servirà anche l'inverso della maschera, vale a dire un'immagine che sia bianca dove l'immagine originale era verde e nera dove non era verde. Chiameremo quest'immagine ~maschera

=>

Maschera ~Maschera
Assumiamo ora che il nero abbia valore 0 (se useremo colori ad 8 bit, assumeremo otto zeri per il nero) ed il bianco 1 (sempre con 8 bit, saranno 8 bit ad 1). A questo punto, se facciamo l'operazione AND tra l'immagine e la maschera otteniamo:

AND

=

Immagine Immagine Risultato 1
Ora possiamo estrarre il tasello di sfondo che si trova sotto l'immagine da inserire:

=>

Sfondo Tassello
A questo punto posiamo fare l' AND tra il tassello e ~maschera:

AND

=

Tassello ~maschera Risultato 2
Ora possiamo fare l'operazione OR tra i due risultati:

OR

=

Risultato 1 Risultato 2 Risultato 3
Adesso possiamo inserire il nostro risultato sullo sfondo per ottenere il risultato desiderato:

+

=

Sfondo Risultato 3 Risultato finale
Per estrarre ed inserire blocchi di immagine useremo le funzioni gl_getbox e gl_putbox. A questo punto potremo comporre l'immagine Risultato 3 a partire da Immagine, tassello e maschera. Tutte le immagini su cui opereremo sono degli array di unsigned char. Noi potremo quindi operare sui singoli elementi dei vettori con operazioni logiche del C. Per ogni elemento faremo la seguente operazione:
Risultato [i] = (Immagine [i] & maschera [i]) | (tassello [i] & ~ maschera [i]);


Note
  • Per far muovere un oggetto sullo schermo, utilizzando le maschere come spiegato, per prima cosa si disegnerà, usando il metodo spiegato, l'oggetto nella posizione iniziale. Il processo prevede il salvataggio dell'immagine che stà sotto l'oggetto. Successivamente, per muovere l'oggeto si incomincerà ripristinando lo sfondo tramite l'immagine salvata, quindi si ridisegerà, sempre con lo stesso procedimento, l'oggetto nella nuova posizione.
  • Per gestire il movimento il metodo più semplice è quello di prevedere due variabili che contengano lo spostamento lungo l'asse delle x e delle y. Quando la pallina raggiungerà uno dei bordi, non faremo altro che cambiare il segno del relativo incremento per ottenere il rimbalzo.
  • Per limitare la velocità della pallina, occorre introdurre un ritardo tra uno spostamento e l'altro. In un sistema multitasking non è opportuno creare ritardi con dei cicli vuoti che, oltre ad essere molto onerosi per il sistema, sono anche imprecisi ed inaffidabili. Per creare un ritardo in maniera affidabile e non onerosa si può usare la funzione usleep (<numero di microsecondi da attendere>);.

Una possibile soluzione è la seguente:
// Tizio - Pinco - 3AIN
// Pallina che rimbalza (con maschera)

#include <stdio.h>
#include <vga.h>
#include <vgagl.h>

#define	ESC		27		/* Tasto di terminazione */

#define	ALTO		10		/* Bordo superiore */
#define	BASSO		190		/* Bordo Inferiore */
#define	SINISTRA	10		/* Bordo Sinistro */
#define	DESTRA		305		/* Bordo Destro */
#define	PASSO		7		/* Passo di spostamento della racchetta */

#define R 		4		/* Raggio della pallina */
#define	DIM		(2*R+1)		/* Dimensione esterna cerchio */
#define H 		5		/* Altezza della racchetta */
#define L 		30		/* Larghezza della racchetta */
#define	PALLAIMG 	(DIM*DIM)	/* Dimensione buffer per copia pallina */
#define	RACCIMG		((H+1)*(L+1))	/* Dimensione buffer racchetta */

struct sprite {
  int x,y;		// Coordinate oggetto
  int l,a;		// Dimensioni oggetto
  unsigned char *img;	// Immagine oggetto
  unsigned char *mask;	// Maschera oggetto
  unsigned char *copre;	// Immagine che stava sotto l'oggetto
  unsigned char *lav;	// Area di lavoro
};

int dx=1,dy=1;				// Spostamento pallina orizzontale e verticale

unsigned char pallaImg [PALLAIMG];	// Immagine Pallina
unsigned char pallaMsk [PALLAIMG];	// Maschera Pallina
unsigned char pallaSfo [PALLAIMG];	// Sfondo sotto Pallina
unsigned char pallaLav [PALLAIMG];	// Area di lavoro Pallina
struct sprite palla;			// Struttura 'sprite' Pallina

unsigned char raccImg [RACCIMG];	// Immagine Racchetta
unsigned char raccMsk [RACCIMG];	// Maschera Racchetta
unsigned char raccSfo [RACCIMG];	// Sfondo sotto Racchetta
unsigned char raccLav [RACCIMG];	// Area di lavoro Racchetta	
struct sprite racc;			// Struttura 'sprite' Racchetta

/* Disegna a video la pallina (nell'angolo in alto a sinsitra),
 * ne cattura l'immagine ed inizializza la struttura 'palla'
 * con i dati della pallina
 */
void creapalla ()
{
int i;	// Indice per creazione maschera
  gl_fillcircle(R,R,R,70);	// Pallina piena
  gl_circle(R,R,R,1);		// Bordo intorno alla pallina
  gl_getbox(0,0,DIM,DIM,pallaImg); // copia la pallina dal video alla variabile pallaImg
  palla.img = pallaImg;		// Assegna alla struttura 'palla' l'immagine
  palla.mask = pallaMsk;	// Assegna alla struttura 'palla' la maschera
  for (i=0;i < PALLAIMG;i ++)	// Per tutti i pixel della maschera (1 pixel = 1 byte)
  {
    if (pallaImg [i])		// Se il pixel dell'immagine non e' nero (colore <> 0)
    {
      pallaMsk [i] = 0xff;	// la maschera e' tutti 1, quindi fa' passare l'immagine
    } else {
      pallaMsk [i] = 0;		// Se e' nero, la maschera e' tutti 0 e fa passare lo sfondo
    }
  }
  palla.copre = pallaSfo;	// Assegno l'area per lo sfondo (per ora vuota)
  palla.lav = pallaLav;		// Assegna l'area di lavoro (ci calcolera' l'immagine da 
				// disegnare)
  palla.l = DIM;		// Larghezza pallina nella struttura
  palla.a = DIM;		// Altezza pallina nella struttura
  gl_fillcircle(R,R,R,0);	// Cancella la pallina dallo schermo
}

/* Disegna a video la racchetta (nell'angolo in alto a sinsitra),
 * ne cattura l'immagine ed inizializza la struttura 'racc'
 * con i dati della pallina
 */
void crearacchetta ()
{
int i;
  gl_fillbox(0,0,L,H,1);	// Paletta (rettangolo blu, per il bordo)
  gl_fillbox(1,1,L-2,H-2,50);	// Paletta (rettangolo della paletta, piu' piccolo)
  gl_getbox(0,0,L,H,raccImg);	// copia la paletta dal video alla variabile raccImg
  racc.img = raccImg;		// Assegna l'immagine alla struttura dello 'sprite'
  racc.mask = raccMsk;		// Assegna la maschera alla struttura dello 'sprite'
  for (i=0;i < RACCIMG;i ++)	// Per tutti i pixel della maschera (1 pixel = 1 byte)
  {
    raccMsk [i] = 0xff;		// Sono tutti a 1 (non copia mai lo sfondo - e' pieno)
  }
  racc.copre = raccSfo;		// Assegno l'area per lo sfondo (per ora vuota)
  racc.lav = raccLav;		// Assegna l'area di lavoro (ci calcolera' l'immagine da 
				// disegnare)
  racc.l = L;			// Larghezza paletta nella struttura
  racc.a = H;			// Altezza paletta nella struttura
  gl_fillbox(0,0,L,H,0);	// Cancella la paletta dallo schermo
}

/* Disegna lo sprite 's' sul video alle coordinate 'x' ed 'y'.
 * Per prima cosa inserisce nella struttura le nuove coordinate,
 * quindi copia lo sfondo nella struttura,
 * poi costruisce in 'lav' l'immagine composta da figura + sfondo,
 * in base alla maschera.
 * Per finire disegna l'immagine ottenuta sullo schermo.
 */
void disegnaSprite (int x, int y, struct sprite *s)
{
int i;
  s -> x = x;				// Assegna la nuova x alla struttura
  s -> y = y;				// Assegna la nuova y alla struttura
  gl_getbox(x,y,s->l,s->a,s->copre);	// Recupera lo sfondo sotto la figura
  for (i=0; i < (s->l * s->a);i++)	// Per ogni byte dell'immagine
  {
    // Costruisce l'immagine da visualizzare componendo disegno e sfondo in base
    // alla maschera
    s->lav [i] = (s->copre [i] & ~s->mask [i]) | (s->img [i] & s->mask [i]);
  }
  gl_putbox(x,y,s->l,s->a,s->lav);	// Visualizza l'immagine ottenuta
}

/* Elimina lo 'sprite' dal video, disegnandoci al posto lo sfondo salvato.
 * Usa le coordinate della struttura.
 */
void cancellaSprite (struct sprite *s)
{
  gl_putbox(s->x,s->y,s->l,s->a,s->copre); // copia lo sfondo a video  
}

/* Muove lo sprite, cancellandolo e poi ridisegnandolo,
 * con le funzioni precedenti
 */
void muoviSprite (int x, int y, struct sprite *s)
{
  cancellaSprite (s);		// Prima cancella lo sprite
  disegnaSprite (x, y, s);	// Poi lo ridisegna nella nuova posizione
}

/* Gestisce il movimento della pallina (parametro p) ed il suo scontro con la 
 * paletta (parametro r).
 * La pallina rimbalza sui bordi del campo.
 * Ritorna il tasto ricevuto (parametro k).
 * Se la pallina esce dal campo (non colpisce la racchetta), ritorna
 * 'ESC' invece del tasto ricevuto.
 * Usa le globali dx e dy, per la direzione corrente del movimento della pallina.
 * Se la pallina colpische i bordi o la paletta, le globali dx e dy vengono
 * modificate.
 */
int ctrlBall(char k, struct sprite *p, struct sprite *r)
{
  if(p -> y==BASSO-DIM-H)		// Arriva al livello della paletta
  {
    dy = -1;				// Rimbalza verso l'alto
    // Se e' fuori della racchetta (HAI PERSO)
    if(((p -> x + (R / 2))> r -> x+L) || ((p -> x + (R / 2)) < r -> x))
    {
      k=ESC;	// Trasforma il tasto in 'ESC' per terminare il gioco
    }
  }

  if(p -> y == ALTO+1)		// Tocca il lato superiore
  {
    dy = 1;			// Rimbalza verso il basso
  }

  if(p -> x == SINISTRA+1)	// Tocca il lato sinistro
  {
    dx = 1;			// Rimbalza verso destra
  }
  if(p -> x == DESTRA-DIM)	// Tocca il lato destro
  {
    dx = -1;			// Rimbalza verso sinistra
  }
  // Porta la pallina nella nuova posizione
  muoviSprite (p -> x + dx, p -> y + dy, p);

  return k;	// Ritrono il tasto, eventualmente modificato
}

/* Gestisce la paletta.
 * Muove la paletta in base al tasto ricevuto.
 * La paletta si sposta a sinistra con il tasto'n',
 * a destra con il tasto 'm'. Il movimento della paletta
 * viene limitato dai bordi del campo.
 */
void ctrlPal (char k, struct sprite *r)
{
int xp;				// Nuova posizioneracchetta, dopo movimento
  if(k=='m')			// L'utente ha premuto 'm', per destra
  { 
    xp = r -> x + PASSO;	// Sposta la paletta
    if (xp + L > DESTRA)	// Se e' uscita a destra
    {
      xp = DESTRA - L;		// La riporta al bordo
    }
    muoviSprite (xp , r -> y, r); // Sposto fisicamente la paletta
  }
  if(k=='n')			// L'utente ha premuto 'n' per sinistra
  {
    xp = r -> x - PASSO;	// Sposto lo sprite a sinistra
    if ( xp <= SINISTRA)	// Se esce dal lato sinistro
    {
      xp = SINISTRA + 1;	// Lo riporto dentro
    }
    muoviSprite (xp , r -> y, r); // Sposto fisicamente la paletta
  }
}

/* Riempie lo sfondo del rettangolo (s,a)(d,b) con linee diagonali variopinte
 */
void sfondo (int s, int a, int d, int b)
{
int numr = d-s+b-a;		// Numero di linee da disegnare
int i;				// Indice della linea da disegnare
int x1=s,x2=s,y1=b,y2=b;	// Coordinate di inizio e fine linea
				// parte dall'angolo a sinistra in basso

  for (i=0;i<numr;i++)		// Per ogni linea da disegnare
  {
    y1--;			// fa salire l'estremo sinistro
    x2++;			// sposta a destra l'estremo basso
    if (y1 < a)			// Se l'estremo sinistro e' arrivato sopra la cima
    {
      y1 = a;			// lo riporta in cima
      x1++;			// e lo sposta a destra
    }
    if (x2 > d)			// Se l'estremo dstro e' arrivato oltre il bordo destro
    {
      x2 = d;			// lo riporta al bordo destro
      y2--;			// e lo alza
    }
    gl_line(x1,y1,x2,y2,i%30+31);	// disegna la linea inclinata
  }
}

main()
{
char k;
	
  vga_init();					// Inizializza il sistema grafico
  vga_setmode(G320x200x256);			// Entra in modo grafico
  gl_setcontextvga(G320x200x256);		// Inizializza le gl con il modo grafico

  creapalla ();					// Crea la palla
  crearacchetta ();				// Crea la racchetta
	
  gl_line(SINISTRA,ALTO,DESTRA,ALTO,30);	// Bordo superiore
  gl_line(SINISTRA,BASSO,DESTRA,BASSO,30);	// Bordo Inferiore
  gl_line(SINISTRA,ALTO,SINISTRA,BASSO,30);	// Bordo Sinistro
  gl_line(DESTRA,ALTO,DESTRA,BASSO,30);		// Bordo Destro
  sfondo (SINISTRA+1,ALTO+1,DESTRA-1,BASSO-1);	// Disegna uno sfondo variegato

  // Disegna la palla iniziale
  disegnaSprite ((SINISTRA+DESTRA)/2, (ALTO+BASSO)/2, &palla);
  // Disegna la paletta iniziale
  disegnaSprite ((SINISTRA+DESTRA-L)/2, BASSO-H, &racc);
	
  do {
    k = vga_getkey();				// Legge l'ordine dell'utente
    ctrlPal (k, &racc);				// Gestisce il movimento della racchetta
    k = ctrlBall(k,&palla,&racc);		// Gestione la pallina (movimento e impatto)
    usleep (20000);				// Pausa un centesimo di secondo
  } while(k!=ESC);				// Ripete fino a che l'utente o il sistema
						// preme il tasto 'ESC'
  vga_setmode(TEXT);				// Ripristina il modo testo
}

Per provare il programma, scaricare il sorgente, compilarlo con il comando cc -lvga -lvgagl pallina1.c ed eseguirlo con il comando ./a.out.

ATTENZIONE: i programmi che utilizzano la vgalib devono essere fatti partire con privilegio di root, altrimenti non possono far entrare la scheda video in modalità grafica.
Per fare questo o si entra in una console di testo (ALT-CTRL-F1) e si fà login come root, oppure si apre una finestra terminale e si assume l'identità di root con il comando su root.


[Home Page dell'ITIS "Fermi"] [Indice Terza] [Precedente]

© Ing. Stefano Salvi - Released under GPL licence