Controlli condizionali: if, else if, else

Abbandoniamo temporaneamente la casualità generata dalla funzione random(), che abbiamo visto la scorsa settimana, per immergerci nel mondo dei controlli condizionali.

Nella vita di tutti i giorni siamo condizionati da scelte: siamo abituati a pensare che se facciamo qualcosa allora accadrà qualcosa di specifico altrimenti (cioè nel caso in cui non si verifichi la condizione iniziale), non succederà nulla.

Il nostro obiettivo di oggi è avere la possibilità di creare una logica simile all’interno dei nostri programmi ovvero far eseguire determinate parti di codice solo al verificarsi di condizioni specifiche. Stiamo cercando di fare qualcosa di simile a quello che abbiamo già visto con mousePressed() e keyPressed() ma, questa volta, vogliamo avere ancora più controllo.

Espressioni e variabili booleane

I computer, purtroppo (o per fortuna), non sono macchine intelligenti e seguono una logica abbastanza semplice: una condizione può essere vera oppure falsa, in inglese true false. Nell’elenco dei tipi di variabili più comuni ho volutamente escluso quelle di tipo booleano: variabili, dunque, che possiamo creare e inizializzare e a cui possiamo assegnare solo uno di questi due valori.

Una cosa molto importante è che non può esistere in nessun programma una variabile che sia contemporaneamente vera falsa.

Operatori di confronto

Dal momento che ci troveremo spesso a lavorare con dei valori numerici, possiamo utilizzare gli operatori di confronto/comparazione:

  • Maggiore: >
  • Minore: <
  • Maggiore o uguale: >=
  • Minore o uguale: <=
  • Uguale: ==
  • Diverso: !=

Controlli condizionali

Per inserire un controllo condizionale è sufficiente tradurre in inglese le parole che ho sottolineato precedentemente e creare dei blocchi di codice con le classiche parentesi graffe: il se diventa, dunque, if mentre l’altrimenti si traduce con else.

Nel campo condizione dobbiamo inserire la nostra espressione di controllo.

Esempio 1:

/*
 * Controlli condizionali: if, else if, else
 * by Federico Pepe
 * http://blog.federicopepe.com
*/
void setup() {
  size(500, 500);
  stroke(255);
}
void draw() {
  if(mouseX > width/2) {
    background(0);
  } else {
    background(127);
  }
  line(width/2, 0, width/2, height); 
}

Con questo programma verifichiamo se la posizione X del mouse è maggiore della metà della larghezza. In caso positivo (true), lo sfondo della finestra verrà colorato di nero, in caso contrario (false) di un grigio scuro.

Per comodità ho creato una linea bianca che mi segnala esattamente il punto da oltrepassare per vedere lo sfondo cambiare colore. Avendo utilizzato la condizione maggiore se il valore di mouse X è pari a 250, la metà della larghezza, la condizione sarà ancora considerata false.

Esempio 2:

/*
 * Controlli condizionali: if, else if, else
 * by Federico Pepe
 * http://blog.federicopepe.com
 */
void setup() {
  size(500, 500);
  stroke(255);
}
void draw() {
  if (mouseX > 200) {
    background(200);
  } else if (mouseX > 100) {
    background(127);
  } else {
    background(0);
  }
  line(100, 0, 100, height);
  line(200, 0, 200, height);
}

In questo secondo esempio abbiamo due condizioni differenti: se la X del mouse è maggiore di 200 lo sfondo sarà grigio chiaro, altrimenti se (else if) è maggiore di 100 lo sfondo sarà grigio scuro, altrimenti sarà nero. È possibile concatenare molteplici condizioni else if per coprire tutti i casi che ci interessa verificare.

La prossima settimana faremo altri esempi per essere sicuri di aver appreso il funzionamento dei controlli condizionali; nel frattempo sbizzarritevi con i vostri sketch.

println() e random()

println()

La settimana scorsa ho accennato alla funzione println(); per mostrarne bene il funzionamento, ho realizzato questo breve video:

Il codice di questo sketch è molto semplice:

void setup() {
  size(700, 500);
}
void draw() {
  println("Mouse X: " + mouseX + " : Mouse Y: " + mouseY);
}

Ho creato una finestra di 700×500 pixel e, senza che venga disegnato nulla, chiedo al programma di mostrarmi nella console di debug il valore di mouseX e di mouseY in ogni istante. Anche se in questo caso le variabili sono soltanto due, per aiutarmi a capire di quale stiamo parlando ho aggiunto un breve testo esplicativo. Come si evince dal codice, questo testo aggiuntivo è inserito tra virgolette ” ” e utilizzo il simbolo + per concatenare le varie parti della frase.

Per completezza è giusto dire che esistono anche le funzioni print() printArray(). Quest’ultima ci sarà molto utile quando cominceremo a utilizzare gli array, mentre, per quanto riguarda la prima, l’unica differenza rispetto a println() è che il testo non va automaticamente a capo alla fine della frase.

random()

Possiamo utilizzare la funzione random() per generare un po’ di caos e rendere i nostri sketch interessanti affidandoci alla casualità. Questa funzione può accettare uno o due valori float in input e restituisce sempre un valore di tipo float. Se usiamo un parametro, la funzione restituirà un valore causale compreso tra 0 e il parametro che abbiamo inserito; se, invece, inseriamo due parametri, il valore in output sarà compreso tra essi.

Come dicevo, questa funzione è utile per creare dei programmi semplici ma di impatto.

/*
 * Utilizzo della funzione random() - Esempio: 1
 * by Federico Pepe
 * http://blog.federicopepe.com
 */

void setup() {
  size(700, 500);
  background(255);
}
void draw() {
  ellipse(random(width), random(height), 10, 10 );
}

Questo sketch crea dei cerchi di dimensioni 10, 10 in posizioni casuali all’interno della finestra: la x è compresa tra 0 e la larghezza della finestra e la y tra 0 e l’altezza.

random - esempio 1

Problema:

Se volessimo fare in modo che i cerchi abbiano tutti dimensioni diverse comprese tra 10 e 50? Immagino che qualcuno di voi abbia pensato a qualcosa del genere:

ellipse(random(width), random(height), random(10, 50),  random(10,50));

Purtroppo questa soluzione non è corretta: quante possibilità abbiamo che il numero casuale estratto sia per la larghezza che per l’altezza del cerchio sia uguale disegnando, dunque, un cerchio? Se inserite questa riga di codice nell’esempio qui sopra, noterete che verranno disegnati principalmente ellissi.

Dobbiamo, quindi, fare in modo che la dimensione sia casuale e compresa tra 10 e 50 ma che sia uguale per larghezza e altezza. Vi viene qualche idea?

Forse una variabile potrebbe tornarci utile!

Creiamo una variabile di nome radius di tipo float e in ogni ciclo draw() gli assegnamo un valore compreso tra 0 e 50 con la funzione random. Utilizziamo, poi, la variabile all’interno della funzione ellipse sia per la larghezza che per l’altezza.

/*
 * Utilizzo della funzione random() - Esempio: 2
 * by Federico Pepe
 * http://blog.federicopepe.com
 */
float radius;

void setup() {
  size(700, 500);
  background(255);
}
void draw() {
  radius = random(10, 50);
  ellipse(random(width), random(height), radius, radius);
}

Random esempio 2

Come ultimo esempio, modifichiamo ulteriormente il nostro programma per aggiungere un po’ di colore. Anche in questo caso, utilizziamo la funzione random limitandola in un range compreso tra 0 e 255 (vi ricordate la lezione sui colori RGB, vero?).

Aggiungiamo nel nostro programma:

fill(random(255), random(255), random(255));

Processing random - esempio 3

Voilà!

Variabili in Processing II: operazioni matematiche

Con il post della settimana scorsa, la vostra vita da programmatori ha subìto un cambiamento radicale: imparare a utilizzare le variabili è un notevole passo avanti che apre scenari inediti.

Aumentare il valore delle variabili

Il mio obiettivo per questo primo esempio è di disegnare un cerchio che, partendo da una posizione predefinita, si muova verso destra.

Vi ricordo che la funzione ellipse() accetta quattro parametri: posizione x e y di origine, larghezza e altezza dell’ellisse. Dunque, di quante variabili avrò bisogno? Siccome ho deciso di far muovere il mio cerchio da sinistra verso destra sull’asse x, la risposta è molto semplice: una soltanto che chiamerò ellipseX.

Il tipo di dato di cui avrò bisogno sarà sicuramente numerico ma dovrò utilizzare un numero intero (integer) o decimale (float)? Tecnicamente potrei usare entrambi ma, dal momento che i pixel non sono suddivisibile, utilizzerò un integer.

Ultimo problema: come faccio ad aumentare il valore della variabile? Visto che il blocco di codice draw() viene eseguito ripetutamente dal programma, sarà sufficiente fare un’addizione all’interno di esso.

Per aumentare il valore della variabile di 1 ogni ciclo, abbiamo tre diverse possibilità:

ellipseX = ellipseX + 1;
ellipseX += 1;
ellipseX ++;

Il risultato che otteniamo è sempre lo stesso; l’unico caso in cui c’è una reale differenza tra le tre linee di codice sopra elencate è se volessimo aumentare la variabile di un valore diverso da 1 a ogni ciclo; in tal caso non potremmo usare il terzo modo ma dovremmo scrivere:

ellipseX = ellipseX + 2;
ellipseX += 2;

Ecco, dunque, il codice del nostro primo esempio:

/*
 * Utilizzo delle variabili II: Esempio 1
 * by Federico Pepe
 * http://blog.federicopepe.com
 */

int ellipseX; // dichiarazione della variabile

void setup() {
  size(540, 540);
  ellipseX = 0; // inizializzazione
  
}

void draw() {
  background(255);
  ellipse(ellipseX, height/2, 50, 50);
  ellipseX = ellipseX + 1; // aumento il valore di ellipseX
}

Se vi state chiedendo come mai la funzione background() sia in draw(), consiglio di rileggere questo post.

Questo è il risultato:

Aumento della variabile ellipseX

Attenzione: Per comodità ho creato una gif animata che viene riprodotta in loop; con il codice sopra indicato, una volta che il cerchio esce dallo schermo a dx, non ricompare al suo punto di partenza sulla sinistra.

Operazioni matematiche con le variabili

Abbiamo visto come effettuare una somma, per le altre operazioni matematiche, il codice è simile:

Sottrazione:

ellipseX = ellipseX - 1;
ellipseX -= 1;
ellipseX --;

Moltiplicazione:

ellipseX = ellipseX * 2;
ellipseX *= 2

Divisione:

ellipseX = ellipseX / 2;
ellipseX /= 2;

Nel caso della moltiplicazione e della divisione è molto improbabile trovare esempi di codice che utilizzano il secondo metodo.

Debugging delle variabili: println();

Quando si comincia a lavorare con le variabili, una delle funzioni più utili da imparare è println(). Essa ci permette di tenere costantemente monitorato il valore che assumono le variabili che stiamo utilizzando attraverso la console di debugging.

Aggiungiamo questa nuova funzione nel nostro esempio di prima:

/*
 * Utilizzo delle variabili II: Esempio 2
 * by Federico Pepe
 * http://blog.federicopepe.com
 */

int ellipseX; // dichiarazione della variabile

void setup() {
  size(540, 540);
  ellipseX = 0; // inizializzazione
  
}

void draw() {
  background(255);
  ellipse(ellipseX, height/2, 50, 50);
  ellipseX = ellipseX + 1; // aumento il valore di ellipseX
  println(ellipseX);
}
Debugging variabili in Processing
Premendo “Run” nella console viene mostrato l’aumentare del valore di ellipseX

Sulla funzione println() faremo un breve approfondimento in futuro perché, essendo una delle funzioni che utilizzeremo di più, è importante che sia chiaro il suo funzionamento; nel frattempo sbizzarritevi a creare sketch animati utilizzando le variabili.

Variabili in Processing: creazione e personalizzazione

Abbiamo già incontrato il termine variabile un paio di settimane fa quando avevo descritto, ad esempio, come utilizzare mouseX e mouseY per rendere i nostri sketch interattivi.

Come avevo scritto in quel post:

[le variabili sono] dei parametri che possono assumere un valore che può essere cambiato durante l’esecuzione del programma attraverso, ad esempio, semplici funzioni matematiche.

Oggi impareremo a creare delle variabili personalizzate e capiremo come utilizzarle nei nostri programmi. Quella di oggi è una lezione di fondamentale importanza perché, come avrete modo di vedere, cambierà completamente il vostro modo di programmare.

Creare delle variabili

Creare una variabile è un processo relativamente semplice. I passaggi che dobbiamo fare sono i seguenti:

  1. Dichiarazione della variabile
    • Assegnazione di un data type
    • Assegnazione di un nome alla variabile
  2. Inizializzazione della variabile
  3. Utilizzo della variabile

In poche parole dobbiamo dare un nome alla variabile, dirgli che tipologia di informazione vogliamo che contenga e dobbiamo assegnargli un valore iniziale. Se questi passaggi sono fatti nel modo corretto (e, tra poco, vi spiegherò come fare), poi potremmo usare quelle variabili a nostro piacimento.

Partiamo da un esempio:

int lunghezza;
lunghezza = 110;

Cosa abbiamo fatto con queste due linee di codice? Abbiamo creato una variabile di tipo integer (abbreviato in int) chiamata lunghezza e l’abbiamo inizializzata assegnando un valore pari a 110.

A una variabile possiamo assegnare il nome che vogliamo senza particolari restrizioni a patto che tale nome non sia già utilizzato dalla sintassi di Processing. La regola empirica per capire se possiamo utilizzare un nome che abbiamo scelto è: se il nome che assegnamo non cambia colore, non dovremmo avere problemi.

Una buona pratica da seguire è quella di dare un nome alle variabili che sia significativo e rifletta l’utilizzo che verrà fatto successivamente: questo renderà il nostro codice facilmente interpretabile anche a mesi o anni di distanza. Quindi, benché io possa nominare delle variabili tiziocaio, e sempronio se quella variabile verrà utilizzata per definire la X di un ellisse è meglio chiamarla, ad esempio, circleX.

Dal momento che il nome che assegniamo alle variabili deve essere un’unica parola, i programmatori utilizzano il cosiddetto CamelCase per nominare variabili complesse. Nella pratica, si tratta di unire parole diverse tra loro lasciando le iniziali maiuscole (la prima lettera della variabile sempre minuscola). Ecco perché, vedendo dei programmi in Processing, troverete variabili come: backgroundColor oppure radiusValue, ecc…

Ultima nota sui nomi delle variabili: potendo scegliere liberamente le parole da utilizzare non c’è restrizione sulla lingua da utilizzare. Se volete rendere il vostro codice leggibile il più possibile, conviene ovviamente usare l’inglese.

Tipi di dati

Assegnare il tipo di dato corretto è importante per evitare che il nostro programma smetta di funzionare e ci restituisca un errore. Se, infatti, il programma si aspetta di trovare un numero intero e riceve un numero decimale si bloccherà.

I principali tipi di dati sono:

  • integer: abbreviato in int e descrive un numero intero (es: 45)
  • float: descrive un numero decimale (es: 34,20394)
  • char: descrive un singolo carattere (es: ‘A’);
  • String: descrive una sequenza di caratteri (es: ‘Questa è una stringa’)

Esistono anche altri tipi di dati e l’elenco completo può essere trovato nel Reference. Per il momento è sufficiente che impariate quelli descritti qui sopra.

Scopo delle variabili

Le variabili non devono essere per forza dichiarate e inizializzate tutte all’inizio del nostro programma. Possiamo farlo tranquillamente mentre procediamo con la scrittura del codice. Dobbiamo, però, fare attenzione allo scopo delle variabili ovvero in quale parte di codice verrà utilizzata.

Anche in questo caso, credo sia meglio partire da un esempio:

int circleX; // dichiarazione

void setup() {
  size(400, 400);
  circleX = 100; // inizializzazione
}

void draw() {
  ellipse(circleX, 110, 50, 50); // utilizzo
}

In questo breve programma ho creato una variabile circleX di tipo integer all’inizio del programma, nel blocco di codice setup() l’ho inizializzata assegnando un valore pari a 110 e l’ho utilizzata in draw() per disegnare un cerchio. Se copiate e incollate il codice nell’editor di Processing e provate a farlo funzionare, non avrete alcun problema.

Ora cambiamo leggermente il nostro codice:

int circleX; // dichiarazione

void setup() {
  size(400, 400);
  circleX = 100; // inizializzazione
  int circleY = 110; // dichiarazione e inizializzazione
}

void draw() {
  ellipse(circleX, circleY, 50, 50); // utilizzo
}

In questo secondo esempio ho fatto una piccola modifica: ho creato e inizializzato una variabile chiamata circleY all’interno della funzione setup() e l’ho utilizzata all’interno della funzione draw(). Se proviamo a far partire questo programma, otterremo un errore Cannot find anything named “circleY”:

Scopo delle variabili in Processing

Perché Processing non riesce a trovare la variabile circleY pur essendo stata dichiarata e inizializzata? La differenza tra circleX e circleY è che la prima è una variabile pubblica mentre la seconda è privata. Cosa significa? Nel primo caso, essendo stata dichiarata all’inizio, circleX può essere usata ovunque all’interno del programma; circleY, invece, è stata dichiarata all’interno della funzione setup() ma viene utilizzata dalla funzione draw() e questo non è possibile. È come se ogni blocco di codice fosse un’entità privata a sé stante e le variabili che vengono dichiarate al suo interno non possono essere utilizzate dagli altri blocchi di codice.

Per far funzionare questo secondo esempio abbiamo due possibilità: spostare la dichiarazione di circleY all’inizio del programma – l’inizializzazione può, invece, rimanere all’interno di setup() o all’interno di draw() – oppure possiamo spostare la funzione ellipse() dentro il blocco di codice setup().

Nel prossimo post vedremo insieme come modificare una (o più) variabili, controllarne in ogni momento il valore e, attraverso esempi pratici, utilizzarle.