Structures de contrôle
Jusqu'ici, les programmes exécutaient leurs instructions l'une après l'autre, de haut en bas. Les structures de contrôle changent ce chemin : elles permettent de choisir ce qui s'exécute, et de répéter des instructions.
Les conditions : if
Si…
if exécute un bloc seulement si une condition est vraie. La condition
s'écrit entre parenthèses, le plus souvent avec une comparaison (chapitre 3) :
if (.vies == 0) {
// ce bloc ne s'exécute que si vies vaut 0
.partieFinie = 1;
}
… sinon
else ajoute un bloc exécuté quand la condition est fausse. L'un des deux
blocs s'exécute, jamais les deux :
if (.vies == 0) {
.partieFinie = 1;
} else {
.vies--;
}
… sinon si
Pour plus de deux cas, on enchaîne des else if. Les conditions sont testées
dans l'ordre, et seule la première vraie s'exécute :
if (.score >= 2000) {
.rang = 3; // or
} else if (.score >= 1000) {
.rang = 2; // argent : avec 1500 points, c'est cette branche
} else {
.rang = 1; // bronze
}
Une valeur comme condition
Une condition peut aussi être une simple valeur : 0 est faux, toute autre valeur est vraie. C'est pratique pour un indicateur qui vaut 0 ou 1 :
if (.touche) { // vrai si touche est différent de 0
.tir = 1;
}
== et non =. Pour comparer, on écrit
==. if (.vies = 0) est refusé par le compilateur : =
range une valeur, il ne compare pas.
Combiner des conditions
Les opérateurs logiques assemblent plusieurs conditions en une seule :
| Opérateur | Se lit | Vrai quand… | Exemple |
|---|---|---|---|
&& | et | les deux conditions sont vraies | .x > 0 && .x < 320 |
|| | ou | l'une au moins est vraie | .vies == 0 || .temps == 0 |
! | non | la condition est fausse | !(.vies > 0) |
// la partie s'arrête si le joueur n'a plus de vies, ou plus de temps
if (.vies == 0 || .temps == 0) {
.partieFinie = 1;
}
// le joueur est dans l'écran
if (.x >= 0 && .x < 320 && .y >= 0 && .y < 200) {
.visible = 1;
}
Les parenthèses précisent l'ordre quand on mélange && et
|| : (.a == 1 || .b == 1) && .c == 1.
Le second test peut être sauté. Dès que le résultat est connu, la
suite n'est pas évaluée : dans a && b, si a est faux,
b n'est pas calculé ; dans a || b, si a est vrai,
non plus. Placer le test le plus simple en premier fait gagner du temps.
Choisir parmi plusieurs valeurs : switch
Quand une même variable est comparée à plusieurs valeurs, switch est plus
lisible qu'une suite de else if :
switch (.direction) {
case 1:
.x--; // gauche
break;
case 2:
.x++; // droite
break;
case 3:
.y--; // haut
break;
case 4:
.y++; // bas
break;
default:
// aucune direction : on ne bouge pas
}
- Chaque
casedonne une valeur. Le programme saute directement aucasequi correspond. breaktermine leswitch.default, facultatif, s'exécute quand aucuncasene correspond.
Ne pas oublier break. Sans lui, l'exécution continue dans
le case suivant. Avec .direction qui vaut 1, ce code ajoute 1
puis 10 :
switch (.direction) {
case 1: .total += 1; // pas de break...
case 2: .total += 10; // ... on continue ici : total vaut 11
break;
}
La valeur d'un case doit être connue à la compilation : un nombre, un
caractère ('A') ou une constante. C'est l'occasion de donner un nom aux
valeurs :
const ubyte GAUCHE = 1;
const ubyte DROITE = 2;
switch (.direction) {
case .GAUCHE: .x--; break;
case .DROITE: .x++; break;
}
Une variable n'est pas acceptée : case c: not a constant declared before the
switch.
La boucle for
for répète un bloc un nombre de fois connu. Sa première ligne a trois
parties, séparées par des points-virgules :
for (ubyte i = 1; i <= 10; i++) {
.total = .total + i; // 1 + 2 + ... + 10 : total vaut 55
}
| Partie | Ici | Rôle |
|---|---|---|
| le départ | ubyte i = 1 | exécuté une fois, avant le premier tour : crée le compteur |
| la condition | i <= 10 | testée avant chaque tour : la boucle continue tant qu'elle est vraie |
| le pas | i++ | exécuté après chaque tour : fait avancer le compteur |
Le compteur peut aussi descendre, avec i--. Comme toute variable, il a un
type : un byte peut devenir négatif, un ubyte non.
Le piège du compte à rebours. Cette boucle ne s'arrête jamais :
for (ubyte n = 7; n >= 0; n--) { ... }
Un ubyte est toujours supérieur ou égal à 0 : après 0, il repart à 255
(le débordement du chapitre 3), et la condition reste vraie. Avec
byte n, la boucle fait bien ses 8 tours, de 7 à 0.
Des boucles dans des boucles
Une boucle peut en contenir une autre. Pour chaque tour de la boucle extérieure, la boucle intérieure fait tous les siens : c'est la façon naturelle de parcourir des lignes et des colonnes.
for (ubyte y = 0; y < 3; y++) { // 3 lignes
for (ubyte x = 0; x < 4; x++) { // de 4 colonnes chacune
.cases++; // 3 × 4 : cases vaut 12
}
}
Les boucles while et do … while
while
while répète un bloc tant qu'une condition est vraie,
sans compteur. Elle convient quand on ne sait pas d'avance combien de tours il faudra :
uint n = 1;
while (n < 1000) {
n = n * 2; // 1, 2, 4, 8... la boucle s'arrête à 1024
}
La condition est testée avant chaque tour : si elle est fausse dès le départ, le bloc ne s'exécute pas du tout.
do … while
La condition est testée après chaque tour : le bloc s'exécute donc au moins une fois. Attention au point-virgule final :
do {
.touche = io.getButtons(0); // lire la manette...
} while (.touche == 0); // ... jusqu'à ce qu'un bouton soit appuyé
La boucle sans fin
while (1) a une condition toujours vraie : la boucle ne s'arrête jamais.
C'est la forme habituelle de la boucle principale d'un jeu, qui lit la manette, déplace
les personnages et redessine l'écran, encore et encore :
while (1) {
// un tour de jeu
}
Sortir ou sauter un tour : break et continue
Dans une boucle (for, while ou do … while) :
breakquitte la boucle immédiatement ;continuepasse au tour suivant, sans finir le tour en cours.
// chercher le premier multiple de 7 après 50
for (ubyte i = 51; i < 100; i++) {
if (i % 7 == 0) {
.trouve = i; // 56
break; // trouvé : inutile de continuer
}
}
// compter les nombres impairs de 0 à 9
for (ubyte i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // pair : on passe au suivant
}
.impairs++; // impairs vaut 5 à la fin
}
Dans un switch placé dans une boucle, break sort du
switch, pas de la boucle.
Un exemple : le sapin
Ce programme dessine un sapin d'étoiles. Chaque ligne du feuillage a deux étoiles de plus que la précédente, et une ligne sur deux change de couleur.
// Structures de contrôle : un sapin (Commodore 64)
architecture commodore.c64;
addlib vic.base;
addlib vic.screentext;
unit {
const ubyte HAUTEUR = 8; // le nombre de lignes du feuillage
main() {
ubyte t = 0;
vic.setBorderColor(0);
vic.setBackgroundColor(0); // fond noir
vic.initScreen();
vic.clearScreen();
// le feuillage : une ligne par tour de boucle
for (ubyte ligne = 0; ligne < .HAUTEUR; ligne++) {
// une ligne sur deux en vert clair
if (ligne % 2 == 0) {
vic.setFontColor(5); // vert
} else {
vic.setFontColor(13); // vert clair
}
vic.setCursor((ubyte)(20 - ligne), (ubyte)(6 + ligne));
// 1, 3, 5, 7... étoiles
for (ubyte i = 0; i < ligne * 2 + 1; i++) {
vic.putChar('*');
}
}
// le tronc : deux lignes
vic.setFontColor(9); // marron
while (t < 2) {
vic.setCursor(20, (ubyte)(6 + .HAUTEUR + t));
vic.putChar('#');
t++;
}
}
}
- La boucle
lignedessine le feuillage, une ligne par tour. if (ligne % 2 == 0)est vrai pour les lignes 0, 2, 4… : les couleurs alternent.- La ligne commence plus à gauche à chaque tour (
20 - ligne) : le sapin reste centré. - La boucle
i, à l'intérieur, écritligne * 2 + 1étoiles : 1, 3, 5, 7… - Le tronc utilise un
while, avec son compteurt.
La version PC Engine
Les boucles et la condition sont les mêmes. Chaque couleur de texte a sa palette (0, 1 et 2), et le sapin est centré sur la colonne 16, le milieu des 32 colonnes de l'écran.
// Structures de contrôle : un sapin (PC Engine)
architecture nec.pce;
addlib sys.resource;
addlib vce.base;
addlib vdc.base;
addlib vdc.screentext;
unit {
const int font = addResource("font.bin");
const ubyte HAUTEUR = 8; // le nombre de lignes du feuillage
main() {
ubyte t = 0;
int source;
int length;
// l'écran, la police et les couleurs, comme au chapitre 2
vdc.setBATSize(vdc.BAT_32x32);
vdc.initScreen();
resource.mapToMPR2(.font);
source = 0x4000 + resource.getOffset(.font);
length = resource.getLength(.font);
vdc.writeToVRAM(source, 0x1000, length);
vce.setPalette(0, 0, RGB(0, 0, 1)); // le fond, bleu nuit
vce.setPalette(0, 1, RGB(0, 5, 0)); // palette 0 : vert
vce.setPalette(1, 1, RGB(3, 7, 3)); // palette 1 : vert clair
vce.setPalette(2, 1, RGB(4, 2, 0)); // palette 2 : marron
vdc.clearScreen();
// le feuillage : une ligne par tour de boucle
for (ubyte ligne = 0; ligne < .HAUTEUR; ligne++) {
// une ligne sur deux en vert clair
if (ligne % 2 == 0) {
vdc.setFontColor(0);
} else {
vdc.setFontColor(1);
}
vdc.setCursor((ubyte)(16 - ligne), (ubyte)(8 + ligne));
// 1, 3, 5, 7... étoiles
for (ubyte i = 0; i < ligne * 2 + 1; i++) {
vdc.putChar('*');
}
}
// le tronc : deux lignes
vdc.setFontColor(2);
while (t < 2) {
vdc.setCursor(16, (ubyte)(8 + .HAUTEUR + t));
vdc.putChar('#');
t++;
}
}
}
À retenir
if,else ifetelsechoisissent un bloc selon une condition ; 0 est faux, toute autre valeur est vraie.&&(et),||(ou) et!(non) combinent des conditions.switchcompare une valeur à des constantes ; chaquecasefinit parbreak.forrépète un nombre de fois connu,whiletant qu'une condition est vraie,do … whileau moins une fois.breakquitte une boucle,continuepasse au tour suivant.- Attention au type du compteur : un
ubytene descend jamais sous 0.
À vous de jouer
- Passez la hauteur du sapin à 12 lignes, en ne changeant qu'une seule ligne du
programme.
Voir la solutionMasquer la solution
La hauteur du sapin n'est écrite qu'à un seul endroit : la constante
HAUTEUR. Toutes les boucles et toutes les positions sont calculées à partir d'elle. On change donc cette seule ligne :const ubyte HAUTEUR = 12; // le nombre de lignes du feuillageLa boucle du feuillage fait alors 12 tours, la ligne la plus large a 12 × 2 - 1 = 23 étoiles, et le tronc descend tout seul sous la dernière ligne, puisque sa position utilise aussi
.HAUTEUR. C'est tout l'intérêt d'une constante : si le nombre 8 avait été recopié dans chaque calcul, il aurait fallu tous les retrouver.
Un sapin de 12 lignes sur Commodore 64. Le sapin tient encore dans l'écran : sur C64, il commence à la colonne 20 - 11 = 9 et finit à la ligne 6 + 12 + 1 = 19 ; sur PC Engine, il va de la colonne 5 à la ligne 21, sur 28 lignes visibles. Au-delà d'une vingtaine de lignes, il sortirait de l'écran.
- Élargissez le tronc à trois colonnes, avec une boucle
fordans lewhile.Voir la solutionMasquer la solution
Le tronc garde ses deux lignes, faites par le
while. À chaque ligne, une boucleforécrit trois#au lieu d'un seul. CommeputCharavance le curseur d'une case, il suffit de placer le curseur une seule fois, une colonne plus à gauche pour que le tronc reste centré.Sur Commodore 64
// le tronc : deux lignes de trois colonnes vic.setFontColor(9); // marron while (t < 2) { vic.setCursor(19, (ubyte)(6 + .HAUTEUR + t)); // une colonne plus à gauche for (ubyte c = 0; c < 3; c++) { vic.putChar('#'); // trois caractères côte à côte } t++; }Sur PC Engine
// le tronc : deux lignes de trois colonnes vdc.setFontColor(2); while (t < 2) { vdc.setCursor(15, (ubyte)(8 + .HAUTEUR + t)); // une colonne plus à gauche for (ubyte c = 0; c < 3; c++) { vdc.putChar('#'); // trois caractères côte à côte } t++; }
Le tronc élargi, sur PC Engine. La boucle
fora son propre compteur,c, qui repart de 0 à chaque tour duwhile. Les deux boucles sont imbriquées : 2 lignes de 3 caractères, soit 6#. - Remplacez le
ifpar unswitchsurligne % 3pour alterner trois couleurs.Voir la solutionMasquer la solution
ligne % 3est le reste de la division par 3 : il vaut 0, 1, 2, puis de nouveau 0, 1, 2… Unswitchsur ce reste choisit une couleur parmi trois. Les deux premiers cas finissent parbreak; le troisième est ledefault, qui reçoit le reste 2.On range d'abord le reste dans une variable,
couleur, puis on fait leswitchsur elle. Le cast(ubyte)est nécessaire, car le calcul se fait enint.Sur Commodore 64
// le feuillage : une ligne par tour de boucle for (ubyte ligne = 0; ligne < .HAUTEUR; ligne++) { // trois couleurs, l'une après l'autre : 0, 1, 2, 0, 1, 2... couleur = (ubyte)(ligne % 3); switch (couleur) { case 0: vic.setFontColor(5); // vert break; case 1: vic.setFontColor(13); // vert clair break; default: vic.setFontColor(7); // jaune : une guirlande } vic.setCursor((ubyte)(20 - ligne), (ubyte)(6 + ligne)); // 1, 3, 5, 7... étoiles for (ubyte i = 0; i < ligne * 2 + 1; i++) { vic.putChar('*'); } }Il faut aussi déclarer
ubyte couleur;en tête demain(), à côté det.
Trois couleurs qui alternent : vert, vert clair, jaune. Sur PC Engine, chaque couleur de texte demande sa palette. Les palettes 0, 1 et 2 sont déjà prises par le vert, le vert clair et le marron du tronc : la troisième couleur va dans la palette 3.
Sur PC Engine
vce.setPalette(0, 0, RGB(0, 0, 1)); // le fond, bleu nuit vce.setPalette(0, 1, RGB(0, 5, 0)); // palette 0 : vert vce.setPalette(1, 1, RGB(3, 7, 3)); // palette 1 : vert clair vce.setPalette(2, 1, RGB(4, 2, 0)); // palette 2 : marron vce.setPalette(3, 1, RGB(7, 7, 0)); // palette 3 : jaune// le feuillage : une ligne par tour de boucle for (ubyte ligne = 0; ligne < .HAUTEUR; ligne++) { // trois couleurs, l'une après l'autre : 0, 1, 2, 0, 1, 2... couleur = (ubyte)(ligne % 3); switch (couleur) { case 0: vdc.setFontColor(0); // vert break; case 1: vdc.setFontColor(1); // vert clair break; default: vdc.setFontColor(3); // jaune : une guirlande } vdc.setCursor((ubyte)(16 - ligne), (ubyte)(8 + ligne)); // 1, 3, 5, 7... étoiles for (ubyte i = 0; i < ligne * 2 + 1; i++) { vdc.putChar('*'); } }
La même alternance sur PC Engine, avec la palette 3. Pourquoi passer par la variable
couleur? Avec la version actuelle du compilateur,switch (ligne % 3)compile sans erreur, mais le reste n'est pas calculé : le sapin s'affiche en jaune, ou presque (toutes les lignes sauf la pointe sur C64, toutes sur PC Engine). Ranger le calcul dans une variable avant leswitchévite le problème, et le code reste aussi lisible. - Dessinez le sapin la tête en bas, avec une boucle qui compte à rebours. Quel type
faut-il donner au compteur ?
Voir la solutionMasquer la solution
Pour retourner le sapin, on dessine d'abord le tronc en haut, puis le feuillage de la ligne la plus large jusqu'à la pointe. Le compteur
lignepart deHAUTEUR - 1et descend jusqu'à 0 compris : la condition estligne >= 0. Les calculs de la boucle ne changent pas (largeur, couleur, colonne de départ) : seul l'ordre change, et la ligne d'écran est calculée pour que la ligne la plus large soit en haut.// le tronc, en haut : deux lignes vic.setFontColor(9); // marron while (t < 2) { vic.setCursor(20, (ubyte)(6 + t)); vic.putChar('#'); t++; } // le feuillage, de la ligne la plus large jusqu'à la pointe : // le compteur descend de HAUTEUR - 1 à 0, il faut donc un byte for (byte ligne = .HAUTEUR - 1; ligne >= 0; ligne--) { if (ligne % 2 == 0) { vic.setFontColor(5); // vert } else { vic.setFontColor(13); // vert clair } vic.setCursor((ubyte)(20 - ligne), (ubyte)(6 + 2 + .HAUTEUR - 1 - ligne)); for (ubyte i = 0; i < ligne * 2 + 1; i++) { vic.putChar('*'); } }Le compteur doit être un
byte, signé. Avec unubyte, la conditionligne >= 0est toujours vraie : après 0,ligne--donne 255, et la boucle ne s'arrête jamais. C'est le piège du compte à rebours vu dans ce chapitre. Le compilateur l'accepte sans erreur ni avertissement ; à l'exécution sur C64, le programme écrit des étoiles et des caractères au hasard sur tout l'écran :
Avec un compteur ubyte : la boucle ne s'arrête plus. Avec
byte ligne, la boucle fait bien ses 8 tours, de 7 à 0. Sur PC Engine, le code est le même, avecvdc., les palettes 0, 1 et 2, la colonne 16 et la ligne 8 :// le tronc, en haut : deux lignes vdc.setFontColor(2); while (t < 2) { vdc.setCursor(16, (ubyte)(8 + t)); vdc.putChar('#'); t++; } // le feuillage, de la ligne la plus large jusqu'à la pointe : // le compteur descend de HAUTEUR - 1 à 0, il faut donc un byte for (byte ligne = .HAUTEUR - 1; ligne >= 0; ligne--) { if (ligne % 2 == 0) { vdc.setFontColor(0); } else { vdc.setFontColor(1); } vdc.setCursor((ubyte)(16 - ligne), (ubyte)(8 + 2 + .HAUTEUR - 1 - ligne)); for (ubyte i = 0; i < ligne * 2 + 1; i++) { vdc.putChar('*'); } }
Le sapin la tête en bas, sur PC Engine.
