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