Modularità delle funzioni

Proseguiamo la nostra esplorazione nel mondo delle funzioni personalizzate e rendiamo lo sketch che abbiamo realizzato con l’esercizio Bouncing Ball (parte 1, parte 2) modulare.

Per diventare dei buoni programmatori è necessario lavorare costantemente alla riscrittura del proprio codice: è un processo continuo di riorganizzazione e ottimizzazione.

Riprendiamo il codice a cui eravamo arrivati con l’ultimo esercizio:

int ellipseX;
int ellipseY;
int speedX = 1;
int speedY = 1;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  if(ellipseX > width || ellipseX < 0) {
    speedX = speedX * -1;
  }
  if(ellipseY > height || ellipseY < 0) {
    speedY = speedY * -1;
  }
  ellipseX = ellipseX + speedX;
  ellipseY = ellipseY + speedY;
  //println("EllipseX: " + ellipseX + " SpeedX: " + speedX);
}

Divisione in  moduli

In quanti moduli possiamo suddividere il nostro programma? Rileggiamo il codice e analizziamo ogni porzione di codice:

  • Righe 1 - 4: dichiarazione delle variabili
  • Righe 6- 10: configurazione generale con la funzione setup()
  • Righe 13-14: disegniamo la nostra palla
  • Righe 15-20: controlliamo che la palla non vada oltre i bordi della finestra e, se necessario, invertiamo la direzione del movimento.
  • Righe 21-22: facciamo muovere la nostra palla

Chiaramente i primi due punti non possono essere modificati: le variabili devono funzionare in vari punti del nostro programma quindi sono pubbliche mentre la parte di configurazione è già all'interno della funzione setup().

Gli altri 3 punti, invece, sono tutti all'interno della funzione draw() ma si occupano di tre attività differenti: disegnare, controllare e muovere la palla. Creiamo un modulo per ciascuna di esse.

Ricordo che non dovendo restituire nessun valore, utilizzeremo la parola void e che possiamo nominare ciascuna funzione come preferiamo purché non si utilizzino parole già riservate.

La funzione drawBall() sarà quella dedicata al disegno della palla

void drawBall() {
 background(0);
 ellipse(ellipseX, ellipseY, 50, 50);
}

moveBall() si occuperà del movimento

void moveBall() {
 ellipseX = ellipseX + speedX;
 ellipseY = ellipseY + speedY;
}

E infine checkEdges() verificherà se la palla ha raggiunto un bordo

void checkEdges() {
 if (ellipseX > width || ellipseX < 0) {
 speedX = speedX * -1;
 }
 if (ellipseY > height || ellipseY < 0) {
 speedY = speedY * -1;
 }
}

Bouncing Ball: modulare

Ecco il codice riscritto:

int ellipseX;
int ellipseY;
int speedX = 1;
int speedY = 1;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  drawBall();
  checkEdges();
  moveBall();
}

void drawBall() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
}

void moveBall() {
  ellipseX = ellipseX + speedX;
  ellipseY = ellipseY + speedY;
}

void checkEdges() {
  if (ellipseX > width || ellipseX < 0) {
    speedX = speedX * -1;
  }
  if (ellipseY > height || ellipseY < 0) {
    speedY = speedY * -1;
  }
}

Se clicchiamo su Run non noteremo alcuna differenza di funzionamento tra questo codice e quello incollato a inizio post.

Qualcuno potrebbe giustamente notare che, però, siamo passati da 24 linee di codice a 34: ben 10 righe di codice in più senza aver, di fatto, cambiato nulla nel funzionamento del nostro programma. Di contro, però, il nostro codice è più pulito e leggibile.

Il reale miglioramento si capirà quando, con il prossimo post, cominceremo a parlare di oggetti.

Esercizio: Bouncing Ball, parte 2

Prima di procedere con la lettura di questo post, assicuratevi di aver letto la prima parte: Esercizio: Bouncing Ball, parte 1. Avete provato a trovare la soluzione al quesito posto alla fine dell’articolo?

Bouncing Ball

Eravamo rimasti con un cerchio che, una volta disegnato sullo schermo, si muoveva da destra verso sinistra e, una volta raggiunto il bordo, invertiva il suo moto tornando indietro. Una volta raggiunto il bordo destro, però, proseguiva il suo moto sparendo dalla nostra vista.

Per fare in modo che il cerchio rimbalzi avanti e indietro, è sufficiente aggiungere una semplice condizione:

if(ellipseX > width || ellipseX < 0)

Ecco quindi il codice completo:

int ellipseX;
int ellipseY;
int speedX = 1;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  if(ellipseX > width || ellipseX < 0) {
    speedX = speedX * -1;
  }
  ellipseX = ellipseX + speedX;
  println("EllipseX: " + ellipseX + " SpeedX: " + speedX);
}

Aggiungiamo anche l'asse verticale

Ora che la nostra "palla" si muove solo sull'asse X, implementiamo il movimento anche sull'asse verticale. La variabile ellipseY è già presente nel nostro codice, dobbiamo solo creare una nuova variabile speedY e ricopiare parte del codice modificando solo i parametri necessari:

int ellipseX;
int ellipseY;
int speedX = 1;
int speedY = 1;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  if(ellipseX > width || ellipseX < 0) {
    speedX = speedX * -1;
  }
  if(ellipseY > height || ellipseY < 0) {
    speedY = speedY * -1;
  }
  ellipseX = ellipseX + speedX;
  ellipseY = ellipseY + speedY;
  //println("EllipseX: " + ellipseX + " SpeedX: " + speedX);
}

Utilizzando delle variabili per controllare la velocità, variando un numero possiamo modificare l'andamento della nostra palla. Se assegniamo a speedX o speedY un valore iniziale di 5, il cerchio si muoverà molto più velocemente.

Eliminando background(0) all'inizio del blocco di codice draw(), potrete tenere monitorato il movimento della vostra palla sullo schermo, come mostrato nell'immagine qui sotto:

Bouncing ball: eliminiamo background(0) dalla funzione draw()

A questo punto il nostro sketch è pronto per essere migliorato utilizzando funzioni e oggetti; nel prossimo post scopriremo come rendere il nostro codice modulare.

Esercizio: Bouncing Ball, parte 1

Con il post di oggi, cominciamo a lavorare a un classico problema di programmazione che raccoglie gran parte delle nozioni viste finora. L’evoluzione di questo esercizio comprenderà, ovviamente, l’utilizzo di funzioni personalizzate e ci servirà per cominciare a parlare di programmazione orientata agli oggetti.

Punto di partenza

Il nostro obiettivo è scrivere un programma in cui faremo muovere un cerchio all’interno della finestra. Una volta raggiunto un bordo, il nostro cerchio non dovrà fermarsi o “sparire” uscendo dallo spazio a nostra disposizione ma dovrà invertire la direzione del suo movimento.

Per comodità e per seguire un modello di sviluppo coerente, suddividerò il nostro obiettivo in problemi più semplici che affronterò uno alla volta facendo riferimento ai post in cui abbiamo già trattato problemi simili.

Disegniamo il cerchio e facciamolo muovere in una direzione

Questa è la parte più semplice: ricordate il post Variabili in Processing: operazioni matematiche?

A differenza dell’esempio sopra citato, creiamo uno sketch con due variabili: ellipseX e ellipseY che utilizzeremo per determinare la posizione del cerchio nella finestra.

int ellipseX;
int ellipseY;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  ellipseX++;
}

Il programma funziona correttamente: il cerchio si muove da sinistra verso destra. L’unico problema è che una volta raggiunto il bordo destro, la variabile ellipseX continua ad aumentare il suo valore di 1 fino a far scomparire il cerchio. Attenzione: il nostro cerchio è sparito dalla finestra ma, anche se noi non lo vediamo, sta proseguendo il suo movimento verso destra.

Abbiamo raggiunto il bordo?

int ellipseX;
int ellipseY;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  if(ellipseX < width) {
    ellipseX++;
  }
}

Inseriamo un semplice controllo condizionale: se ellipseX è minore della larghezza della finestra aumentiamo la variabile. Quando ellipseX sarà uguale a 700, in questo caso specifico, il cerchio si fermerà.

Invertiamo la direzione del movimento

A questo punto dobbiamo invertire la direzione del movimento. Normalmente si è portati a pensare a una cosa del genere:

int ellipseX;
int ellipseY;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  if(ellipseX < width) {
    ellipseX++;
  } else {
    ellipseX--;
  }
}

Se avviate questo sketch vi accorgerete, però, che il cerchio non torna indietro. Com'è possibile? Analizziamo i valori che assume la variabile ellipseX: ricordatevi che è sempre possibile inserire println(); per fare il debugging.

Cliccando su Run, il valore di ellipseX è uguale a 0. A ogni ciclo draw() ellipseX viene aumentato di 1 quindi la variabile assumerà i valori, 1, 2, 3 ecc... fino ad arrivare a 700. Chiaramente a ogni ciclo viene effettuato il controllo if: se ellipseX è minore della variabile width (che ha valore 700) allora verrà aggiunto 1.

Quando ellipseX assume il valore 700, la condizione if(ellipseX < width) sarà false e scatterà il blocco di codice presente nell'else e quindi a ellipseX verrà assegnato un valore di 699. Al ciclo successivo il controllo if(ellipseX < width) sarà di nuovo true e quindi alla variabile verrà sommato 1 e tornerà a un valore di 700.

Questo si ripeterà all'infinito e la variabile assumerà solo valori pari a 699 o 700 bloccando, di fatto, il movimento del cerchio.

Per invertire la direzione del movimento dobbiamo fare qualcosa in più: aggiungere una variabile per controllare la direzione dello spostamento a cui invertiremo il segno (da positivo a negativo) per far si che il cerchio torni effettivamente indietro.

Per cambiare il segno di un numero da positivo a negativo sappiamo che è sufficiente moltiplicarlo per -1. Mi rendo conto che, spiegandolo a parole, questo passaggio è un po' complicato; per aiutarvi nella comprensione ho inserito un println() alla fine del codice per mostrare in console i valori di ellipseX e speedX.

Altra puntualizzazione: ho modificato anche il controllo condizionale da if(ellipseX < width) a if(ellipseX > width) per far funzionare il programma correttamente e cambiare il segno solo al raggiungimento del bordo destro.

int ellipseX;
int ellipseY;
int speedX = 1;

void setup() {
  size(700, 500);
  ellipseX = 0;
  ellipseY = height/2;
}

void draw() {
  background(0);
  ellipse(ellipseX, ellipseY, 50, 50);
  if(ellipseX > width) {
    speedX = speedX * -1;
  }
  ellipseX = ellipseX + speedX;
  println("EllipseX: " + ellipseX + " SpeedX: " + speedX);
}

Il nostro cerchio ora torna indietro ma, una volta raggiunto il bordo sinistro, scompare nuovamente dalla finestra.

Ora tocca a voi dimostrare di aver capito come procedere: dovete fare in modo che il cerchio torni indietro una volta raggiunto anche il bordo sinistro e poi, ovviamente, implementare lo stesso procedimento anche sull'asse verticale. La soluzione sarà pubblicata nella seconda parte!