Tableaux, chaînes et pointeurs
Une variable range une valeur. Un jeu en manipule des dizaines du même genre : les positions des ennemis, les cases d'un niveau, les lignes d'un menu. Ce chapitre montre comment les ranger côte à côte dans des tableaux, écrire du texte, et désigner une donnée par son adresse.
Les tableaux
Un tableau est une suite de cases du même type, rangées les unes après les autres en mémoire, sous un seul nom.
Déclarer un tableau
On donne le type des cases, le nom, puis le nombre de cases entre crochets :
ubyte vies[4]; // 4 cases, à 0 au démarrage
ubyte pluie[5] = { 3, 7, 2, 9, 5 }; // avec des valeurs de départ
ubyte bonus[] = { 10, 20, 50 }; // taille déduite des valeurs : 3
ubyte niveaux[6] = { 1, 2 }; // les cases 2 à 5 valent 0
Un tableau sans taille ni valeurs est refusé :
Array 't' must have explicit size or initializer. Avec trop de valeurs, les
dernières sont ignorées, et un avertissement le signale :
Array initializer truncated from 7 to 5 elements.
Lire et écrire une case
Une case se désigne par son indice entre crochets. Les indices commencent à 0 : un tableau de 5 cases va de 0 à 4.
| indice | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| pluie | 3 | 7 | 2 | 9 | 5 |
ubyte mardi = .pluie[1]; // 7
.pluie[3] = 12; // jeudi : 12 au lieu de 9
L'indice peut être une variable ou un calcul, ce qui rend les tableaux inséparables des boucles :
uint total = 0;
for (ubyte i = 0; i < 5; i++) {
total = total + .pluie[i]; // 3 + 7 + 2 + 9 + 5 = 26
}
La taille d'un tableau : sizeof
sizeof donne la taille d'un tableau, calculée à la compilation. Plutôt
que d'écrire 5 en dur, la boucle suit alors le tableau si on lui ajoute des cases :
uint mots[4];
sizeof(.mots[]) // 4 : le nombre de cases
sizeof(.mots) // 8 : la taille en octets (4 cases de 2 octets)
Rester dans le tableau
Avec un indice constant, le compilateur vérifie qu'on ne sort pas du tableau :
ubyte a[3];
.a[3] = 99; // erreur : les cases vont de 0 à 2
Array index out of bounds: a[3] - array size is 3
Avec un indice variable, rien ne vous protège. Si i vaut
3, .a[i] = 99; écrit juste après le tableau, dans la variable qui s'y trouve
en mémoire, et l'écrase sans aucun message. Les machines rétro ne vérifient rien à
l'exécution : c'est au développeur de garder ses indices dans les limites.
Les tableaux constants
Un tableau const est rangé dans la ROM, avec le programme : il ne prend
pas de mémoire vive, mais on ne peut que le lire. C'est l'outil idéal pour des données
fixes : des couleurs, des niveaux, ou des résultats calculés d'avance.
const ubyte CARRES[] = { 0, 1, 4, 9, 16, 25 }; // une table toute prête
ubyte c = .CARRES[3]; // 9, sans faire de multiplication
Lire un résultat dans une table est souvent bien plus rapide que le calculer : sur un
6502, qui ne sait pas multiplier, c'est une astuce classique. Écrire dans un tableau
constant est refusé : Cannot assign to constant 'CARRES'.
Les tableaux à deux dimensions
Un tableau peut avoir deux dimensions, comme une grille : des lignes et des colonnes. Le premier indice est la ligne, le second la colonne.
ubyte grille[3][4]; // 3 lignes de 4 colonnes
.grille[2][1] = 42; // ligne 2, colonne 1
for (ubyte y = 0; y < 3; y++) { // une boucle par dimension
for (ubyte x = 0; x < 4; x++) {
.grille[y][x] = 0;
}
}
En mémoire, les lignes sont rangées l'une après l'autre : la grille occupe 3 × 4 = 12 cases.
Les chaînes de caractères
Un texte est un tableau d'octets : un code par caractère, suivi d'un octet
0 qui marque la fin. C'est grâce à ce 0 que print sait où
s'arrêter.
Un texte fixe
Un texte entre guillemets, comme dans vic.print("BONJOUR"), est rangé dans
la ROM. Pour en garder plusieurs, on les déclare dans un tableau constant de chaînes,
const string. Chaque case contient l'adresse d'une chaîne,
que l'on obtient avec @ :
const string JOURS[] = { "LUN", "MAR", "MER", "JEU", "VEN" };
vic.print(@.JOURS[2]); // MER
Un texte modifiable
Un texte que le programme change (un nom saisi, un message construit) se range dans
un tableau d'ubyte, sans oublier le 0 final :
ubyte nom[8];
.nom[0] = 'A';
.nom[1] = 'B';
.nom[2] = 0; // la fin du texte
vic.print(@.nom); // AB
Pour plusieurs textes modifiables, toBytes remplit un tableau à deux
dimensions, une ligne par texte, chacune terminée par 0 :
ubyte menu[3][10] = toBytes({ "JOUER", "OPTIONS", "QUITTER" });
.menu[0][0] = 'X'; // XOUER
vic.print(@.menu[1]); // OPTIONS
Deux formes à éviter pour l'instant.
const string T = "AB"; est refusé : écrivez
const string T[] = { "AB" };. Et string s = "HELLO";, sans
const, échoue à l'assemblage (Undefined symbol: Main_s) :
utilisez un tableau d'ubyte.
Les adresses : @
La mémoire de la machine est une longue suite de cases d'un octet, numérotées. Le numéro d'une case est son adresse : sur une machine 8 bits, un nombre de 16 bits, de 0 à 65 535. Chaque variable occupe une ou plusieurs de ces cases.
L'opérateur @ donne l'adresse de ce qui le suit :
ubyte tab[4] = { 10, 11, 12, 13 };
uint a = @.tab; // l'adresse du tableau, par exemple 41 121
uint b = @.tab[2]; // l'adresse de la case 2 : 41 123
Les cases d'un tableau d'ubyte se suivent d'un octet en un octet. Pour un
tableau d'uint, chaque case prend 2 octets : @.mots[2] est 4
octets après @.mots. Le compilateur fait ce calcul.
Les pointeurs
Un pointeur est une variable qui contient une adresse. On le déclare
avec une * devant son nom, et le type de ce qu'il désigne :
ubyte *p; // p désigne des ubyte
uint *q; // q désigne des uint
On le fait pointer sur un tableau avec @, puis on s'en sert comme du
tableau lui-même :
ubyte tab[4] = { 10, 11, 12, 13 };
.p = @.tab; // p pointe sur tab
ubyte v = .p[2]; // 12 : la case 2 de tab
.p[1] = 99; // écrit dans tab : tab[1] vaut 99
p[i] lit ou écrit la case i à partir de l'adresse contenue
dans p. La donnée pointée elle-même est p[0] : il n'y a pas
d'opérateur *p comme en C.
Déplacer un pointeur
Un pointeur est un nombre : on peut le changer. Attention, + le fait
avancer d'un nombre d'octets, pas de cases. Pour avancer de cases,
utilisez @ :
uint mots[3] = { 100, 200, 300 };
.q = @.mots;
.q = .q + 2; // 2 octets plus loin : q[0] vaut 200
.q = @.mots;
.q = @.q[2]; // 2 cases plus loin : q[0] vaut 300
Un pointeur fast est plus rapide. Sur 6502, la lecture
par un pointeur passe par la page zéro. Déclaré fast ubyte *p;, il y est
déjà ; sinon, il y est d'abord recopié à chaque utilisation.
Passer un tableau à une fonction
C'est l'usage le plus courant des pointeurs. Une fonction ne reçoit pas le tableau entier (ce serait trop long à copier), mais son adresse, dans un paramètre pointeur :
uint somme(uint *valeurs, ubyte nombre) {
uint s = 0;
for (ubyte k = 0; k < nombre; k++) {
s = s + valeurs[k];
}
return s;
}
.total = somme(@.mots, 3); // 600
La même fonction marche ainsi pour n'importe quel tableau d'uint, de
n'importe quelle taille. Comme elle travaille sur le tableau lui-même, et non sur une
copie, elle peut aussi le modifier.
Le @ est obligatoire. Sans lui,
somme(.mots, 3) ne passe pas l'adresse du tableau, mais la valeur de sa
première case (100). La fonction lit alors la mémoire à l'adresse 100, et le résultat
est faux, sans message d'erreur.
Les pointeurs de fonction
Une fonction aussi est rangée en mémoire, à une adresse. @ donne cette
adresse, et une variable peut la garder : c'est un pointeur de fonction.
On l'appelle ensuite comme la fonction elle-même. Le programme peut ainsi choisir,
pendant qu'il tourne, quelle fonction appeler.
Déclarer, affecter, appeler
Le type s'écrit function, suivi du type du résultat et des types des
paramètres :
function uint(uint, uint) operation; // un résultat uint, deux paramètres uint
main() {
.operation = @addition; // operation désigne addition
.total = .operation(40, 2); // 42 : appelle addition(40, 2)
.operation = @multiplication; // puis multiplication
.total = .operation(6, 7); // 42 : appelle multiplication(6, 7)
}
uint addition(uint a, uint b) {
return a + b;
}
uint multiplication(uint a, uint b) {
return a * b;
}
Comme une variable, un pointeur de fonction peut être local
(function ubyte(ubyte) h = @moitie;, appelé par h(18)), ou être un
paramètre.
Un tableau de fonctions : la machine à états
Un jeu passe par des états : l'écran titre, la partie, la fin. Un tableau
constant de fonctions, une par état, remplace une longue suite de if : une
seule ligne appelle la fonction de l'état en cours.
const ubyte TITRE = 0;
const ubyte JEU = 1;
const ubyte FIN = 2;
const function void() ETATS[] = { @titre, @jeu, @fin };
ubyte etat = 1;
main() {
.ETATS[.etat](); // etat vaut 1 : appelle jeu()
}
void titre() { }
void jeu() { }
void fin() { }
Pour changer d'état, il suffit de changer la variable : .etat = .FIN;.
Une fonction en paramètre
Une fonction peut en recevoir une autre, et l'appeler elle-même. Prenons le tableau de
pluie de ce chapitre : on veut tantôt doubler toutes ses valeurs, tantôt les diviser par
deux, tantôt leur ajouter 2. Plutôt que d'écrire trois boucles presque identiques, on
écrit une seule fonction transformer, qui parcourt le
tableau, et on lui donne en paramètre l'opération à faire sur chaque case :
// Une fonction en paramètre : transformer toutes les cases d'un tableau
architecture commodore.c64;
unit {
ubyte pluie[5] = { 3, 7, 2, 9, 5 };
main() {
transformer(@.pluie, 5, @doubler); // pluie : 6, 14, 4, 18, 10
transformer(@.pluie, 5, @moitie); // pluie : 3, 7, 2, 9, 5
transformer(@.pluie, 5, @plusDeux); // pluie : 5, 9, 4, 11, 7
}
// Applique la fonction f à chaque case du tableau
void transformer(ubyte *valeurs, ubyte nombre, function ubyte(ubyte) f) {
for (ubyte i = 0; i < nombre; i++) {
valeurs[i] = f(valeurs[i]);
}
}
// Trois transformations possibles, de la même forme : un ubyte reçu, un ubyte rendu
ubyte doubler(ubyte x) {
return (ubyte)(x * 2);
}
ubyte moitie(ubyte x) {
return (ubyte)(x / 2);
}
ubyte plusDeux(ubyte x) {
return (ubyte)(x + 2);
}
}
Ce qui se passe, ligne par ligne :
function ubyte(ubyte) f: le troisième paramètre detransformerest une fonction qui reçoit unubyteet rend unubyte.doubler,moitieetplusDeuxont toutes cette forme.transformer(@.pluie, 5, @doubler): on passe l'adresse du tableau (@.pluie), son nombre de cases, et l'adresse de la fonction à appliquer (@doubler).- Dans la boucle,
f(valeurs[i])appelle la fonction reçue.fest un paramètre, donc une variable locale : il s'appelle sans point.
| Après l'appel | Contenu de pluie |
|---|---|
| au départ | 3, 7, 2, 9, 5 |
transformer(@.pluie, 5, @doubler) | 6, 14, 4, 18, 10 |
transformer(@.pluie, 5, @moitie) | 3, 7, 2, 9, 5 |
transformer(@.pluie, 5, @plusDeux) | 5, 9, 4, 11, 7 |
Pour une nouvelle transformation, il suffit d'écrire une petite fonction de la même
forme : transformer ne change pas.
La signature doit correspondre. La fonction donnée à un pointeur doit
avoir le même nombre de paramètres, de mêmes tailles, et le même genre de résultat.
Sinon, le compilateur refuse :
@f: int f(int) does not match function int(int, int).
Pour l'instant, cette vérification n'a lieu que si la fonction est écrite
avant l'affectation : écrite après, une fonction de mauvaise signature est
acceptée sans message. Et une variable de pointeur de la unit ne reçoit pas de valeur
de départ : on l'affecte dans main().
Un exemple : la pluie de la semaine
Ce programme dessine un histogramme : une barre par jour, de la hauteur de la pluie tombée. Il trouve aussi le jour le plus pluvieux.
// Tableaux, chaînes et pointeurs : la pluie de la semaine (Commodore 64)
architecture commodore.c64;
addlib vic.base;
addlib vic.screentext;
unit {
const ubyte BAS = 20; // la ligne du bas des barres
const string JOURS[] = { "LUN", "MAR", "MER", "JEU", "VEN" };
const ubyte COULEURS[] = { 3, 7, 5, 13, 14 }; // une couleur par barre
ubyte pluie[5] = { 3, 7, 2, 9, 5 }; // en millimètres
main() {
ubyte record;
vic.setBorderColor(0);
vic.setBackgroundColor(0);
vic.initScreen();
vic.clearScreen();
vic.setFontColor(1);
vic.setCursor(8, 3);
vic.print("PLUIE DE LA SEMAINE (MM)");
dessinerBarres(@.pluie, (ubyte)sizeof(.pluie[]));
record = indiceMax(@.pluie, (ubyte)sizeof(.pluie[]));
vic.setFontColor(1);
vic.setCursor(8, 23);
vic.print("LE PLUS PLUVIEUX : ");
vic.print(@.JOURS[record]);
}
// Une barre par valeur, de la hauteur de la valeur
void dessinerBarres(ubyte *valeurs, ubyte nombre) {
for (ubyte i = 0; i < nombre; i++) {
ubyte x = (ubyte)(9 + i * 5);
ubyte h = valeurs[i];
vic.setFontColor(.COULEURS[i]);
for (ubyte y = 0; y < h; y++) {
vic.setCursor(x, (ubyte)(.BAS - y));
vic.print("##");
}
vic.setFontColor(1);
vic.setCursor(x, (ubyte)(.BAS - h)); // la valeur, au-dessus de la barre
vic.putChar((ubyte)('0' + h));
vic.setCursor(x, (ubyte)(.BAS + 1)); // le jour, en dessous
vic.print(@.JOURS[i]);
}
}
// L'indice de la plus grande valeur du tableau
ubyte indiceMax(ubyte *valeurs, ubyte nombre) {
ubyte meilleur = 0;
for (ubyte i = 1; i < nombre; i++) {
if (valeurs[i] > valeurs[meilleur]) {
meilleur = i;
}
}
return meilleur;
}
}
pluieest un tableau en mémoire vive ;JOURSetCOULEURSsont des tableaux constants, dans la ROM.dessinerBarresetindiceMaxreçoivent le tableau par un pointeur,@.pluie, et sa taille parsizeof.- La case
ideCOULEURSdonne la couleur de la barrei, et celle deJOURSson nom. indiceMaxrenvoie un indice, qui sert ensuite à lire le nom du jour :@.JOURS[record].
La version PC Engine
Les tableaux et les deux fonctions de dessin et de recherche sont identiques. Sur PC
Engine, COULEURS contient des numéros de palette, que
preparerEcran() remplit une à une.
// Tableaux, chaînes et pointeurs : la pluie de la semaine (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 BAS = 21; // la ligne du bas des barres
const string JOURS[] = { "LUN", "MAR", "MER", "JEU", "VEN" };
const ubyte COULEURS[] = { 1, 2, 3, 4, 5 }; // une palette par barre
ubyte pluie[5] = { 3, 7, 2, 9, 5 }; // en millimètres
main() {
ubyte record;
preparerEcran();
vdc.setFontColor(0);
vdc.setCursor(4, 4);
vdc.print("PLUIE DE LA SEMAINE (MM)");
dessinerBarres(@.pluie, (ubyte)sizeof(.pluie[]));
record = indiceMax(@.pluie, (ubyte)sizeof(.pluie[]));
vdc.setFontColor(0);
vdc.setCursor(4, 24);
vdc.print("LE PLUS PLUVIEUX : ");
vdc.print(@.JOURS[record]);
}
// L'écran, la police, et une palette par couleur de texte
void preparerEcran() {
int source;
int length;
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, 0)); // le fond
vce.setPalette(0, 1, RGB(7, 7, 7)); // palette 0 : blanc
vce.setPalette(1, 1, RGB(3, 7, 7)); // palette 1 : cyan
vce.setPalette(2, 1, RGB(7, 7, 2)); // palette 2 : jaune
vce.setPalette(3, 1, RGB(1, 6, 1)); // palette 3 : vert
vce.setPalette(4, 1, RGB(4, 7, 4)); // palette 4 : vert clair
vce.setPalette(5, 1, RGB(4, 4, 7)); // palette 5 : bleu clair
vdc.clearScreen();
}
// Une barre par valeur, de la hauteur de la valeur
void dessinerBarres(ubyte *valeurs, ubyte nombre) {
for (ubyte i = 0; i < nombre; i++) {
ubyte x = (ubyte)(5 + i * 5);
ubyte h = valeurs[i];
vdc.setFontColor(.COULEURS[i]);
for (ubyte y = 0; y < h; y++) {
vdc.setCursor(x, (ubyte)(.BAS - y));
vdc.print("##");
}
vdc.setFontColor(0);
vdc.setCursor(x, (ubyte)(.BAS - h)); // la valeur, au-dessus de la barre
vdc.putChar((ubyte)('0' + h));
vdc.setCursor(x, (ubyte)(.BAS + 1)); // le jour, en dessous
vdc.print(@.JOURS[i]);
}
}
// L'indice de la plus grande valeur du tableau
ubyte indiceMax(ubyte *valeurs, ubyte nombre) {
ubyte meilleur = 0;
for (ubyte i = 1; i < nombre; i++) {
if (valeurs[i] > valeurs[meilleur]) {
meilleur = i;
}
}
return meilleur;
}
}
À retenir
- Un tableau range des cases du même type ; les indices commencent à 0.
sizeof(.t[])donne le nombre de cases,sizeof(.t)la taille en octets.- Avec un indice variable, rien ne vérifie qu'on reste dans le tableau.
- Un tableau
constest dans la ROM : il ne coûte pas de mémoire vive, et ne se modifie pas. - Un texte est une suite d'octets terminée par 0 ;
const stringpour des textes fixes, un tableau d'ubytepour un texte modifiable. @donne une adresse ; un pointeur (ubyte *p) en contient une, et s'utilise comme un tableau :p[i].- On passe un tableau à une fonction avec
@. - Un pointeur de fonction (
function void() f;) garde l'adresse d'une fonction, donnée par@, et s'appelle comme elle :.f().
À vous de jouer
- Écrivez une fonction
uint somme(ubyte *valeurs, ubyte nombre)et affichez le total de la semaine sous l'histogramme.Voir la solutionMasquer la solution
sommereprend la boucle du chapitre : une variablespart de 0, et on lui ajoute chaque case. CommedessinerBarresetindiceMax, elle reçoit le tableau par un pointeur, et son nombre de cases à part : elle marche ainsi pour n'importe quel tableau d'ubyte.Les cases sont des
ubyte, mais le résultat est unuint. Unubytene dépasse pas 255 : une semaine très pluvieuse, sept jours de 50 mm, ferait 350 et ne tiendrait pas. Pour écrire le total, on reprendecrireNombre()du chapitre 5, recopiée telle quelle soussomme(avecvdc.putCharsur PC Engine).main() { ubyte record; vic.setBorderColor(0); vic.setBackgroundColor(0); vic.initScreen(); vic.clearScreen(); vic.setFontColor(1); vic.setCursor(8, 3); vic.print("PLUIE DE LA SEMAINE (MM)"); dessinerBarres(@.pluie, (ubyte)sizeof(.pluie[])); record = indiceMax(@.pluie, (ubyte)sizeof(.pluie[])); vic.setFontColor(1); vic.setCursor(8, 23); vic.print("LE PLUS PLUVIEUX : "); vic.print(@.JOURS[record]); vic.setCursor(8, 24); vic.print("TOTAL : "); ecrireNombre(somme(@.pluie, (ubyte)sizeof(.pluie[])), 3); vic.print(" MM"); } // La somme des cases du tableau uint somme(ubyte *valeurs, ubyte nombre) { uint s = 0; for (ubyte i = 0; i < nombre; i++) { s = s + valeurs[i]; } return s; }Le total, 3 + 7 + 2 + 9 + 5 = 26, s'affiche
026: on a demandé trois chiffres. Sur PC Engine, le code est le même avecvdcà la place devic, et la ligne 26 au lieu de 24 (vdc.setCursor(4, 26)), sous « LE PLUS PLUVIEUX ».
Le total de la semaine sur Commodore 64. 
Et sur PC Engine. - Avant de dessiner, ajoutez 2 mm à chaque jour avec une boucle.
Voir la solutionMasquer la solution
La boucle parcourt toutes les cases de
pluieet ajoute 2 à chacune. Elle se place dansmain(), avant l'appel àdessinerBarres: c'est le tableau lui-même qui change, les barres et le record suivent. Le compilateur accepte aussi la ligne sans la conversion(ubyte); nous la gardons pour rappeler que le résultat est rangé dans un octet, qui ne dépasse pas 255. Grâce àsizeof, la boucle suivra le tableau si on lui ajoute des jours.for (ubyte i = 0; i < sizeof(.pluie[]); i++) { .pluie[i] = (ubyte)(.pluie[i] + 2); // 2 mm de plus } dessinerBarres(@.pluie, (ubyte)sizeof(.pluie[]));En lançant le programme, une surprise : au-dessus de la barre du jeudi, qui vaut maintenant 11, s'affiche
;au lieu de11. En effet,'0' + hne donne un chiffre que pourhde 0 à 9 : le caractère qui suit9dans la table des caractères est:, puis;.
Avant correction : 11 s'écrit « ; ». Il faut donc écrire la valeur en deux chiffres quand elle atteint 10 : les dizaines (
h / 10), puis les unités (h % 10). DansdessinerBarres:vic.setCursor(x, (ubyte)(.BAS - h)); // la valeur, au-dessus de la barre if (h >= 10) { vic.putChar((ubyte)('0' + h / 10)); // le chiffre des dizaines } vic.putChar((ubyte)('0' + h % 10)); // le chiffre des unités
Chaque jour a 2 mm de plus, et 11 s'écrit correctement. Sur PC Engine, les deux modifications sont les mêmes, avec
vdc; nous l'avons vérifié, la barre de 11 tient sous le titre. On aurait aussi pu écriretransformer(@.pluie, 5, @plusDeux);avec la fonction de la section sur les pointeurs de fonction : c'est la même boucle, rangée dans une fonction. - Écrivez
indiceMin, et affichez aussi le jour le plus sec.Voir la solutionMasquer la solution
indiceMinest la copie deindiceMax, avec un seul signe changé : on garde la case dont la valeur est plus petite que la meilleure trouvée jusque-là. Le comparateur est strict (<) : si deux jours ont la même pluie, c'est le premier qui est gardé.// L'indice de la plus petite valeur du tableau ubyte indiceMin(ubyte *valeurs, ubyte nombre) { ubyte meilleur = 0; for (ubyte i = 1; i < nombre; i++) { if (valeurs[i] < valeurs[meilleur]) { meilleur = i; } } return meilleur; }Dans
main(), on range l'indice dans une nouvelle variable locale,sec, déclarée à côté derecord, puis on l'affiche sous le jour le plus pluvieux. Les espaces dans le texte alignent les deux-points :sec = indiceMin(@.pluie, (ubyte)sizeof(.pluie[])); vic.setCursor(8, 24); vic.print("LE PLUS SEC : "); vic.print(@.JOURS[sec]);Sur PC Engine, la fonction est identique ; seul l'affichage change :
vdc, et la ligne 26 (vdc.setCursor(4, 26)).
Le mercredi, avec 2 mm, est le jour le plus sec. - Ajoutez le samedi et le dimanche. Que faut-il changer pour que les sept barres
tiennent dans l'écran ?
Voir la solutionMasquer la solution
Les trois tableaux reçoivent deux valeurs de plus : le nom des jours, leur couleur, et la pluie. Rien d'autre ne change dans les boucles : elles s'arrêtent à
nombre, qui vient desizeof(.pluie[]), et suivent donc le tableau. C'est tout l'intérêt de ne pas écrire 5 en dur. Attention à la taille depluie: avecpluie[5]et sept valeurs, le compilateur ne garderait que les cinq premières, avec l'avertissementArray initializer truncated from 7 to 5 elements. On écritpluie[7], oupluie[]pour laisser le compilateur compter.const string JOURS[] = { "LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM" }; const ubyte COULEURS[] = { 3, 7, 5, 13, 14, 10, 15 }; // une couleur par barre ubyte pluie[7] = { 3, 7, 2, 9, 5, 1, 6 }; // en millimètresSi l'on s'arrête là, le dessin déborde. Chaque barre est 5 colonnes plus loin que la précédente, à partir de la colonne 9 : la septième commence en colonne 39, et l'écran du C64 n'a que 40 colonnes (0 à 39). La barre et le nom du dimanche continuent au début de la ligne suivante :

Sans changer la position des barres : le dimanche déborde. Il faut donc recalculer la position
xdansdessinerBarres. Sept barres espacées de 5 colonnes occupent 6 × 5 + 3 = 33 colonnes (le nom du dernier jour en prend 3) : on commence en colonne 3 pour les centrer dans les 40.ubyte x = (ubyte)(3 + i * 5); // 7 barres : de 3 à 33
Les sept jours sur Commodore 64. Sur PC Engine
Les tableaux changent de la même façon. Sur cette machine,
COULEURScontient des numéros de palette : les deux nouveaux jours utilisent les palettes 6 et 7.const string JOURS[] = { "LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM" }; const ubyte COULEURS[] = { 1, 2, 3, 4, 5, 6, 7 }; // une palette par barre ubyte pluie[7] = { 3, 7, 2, 9, 5, 1, 6 }; // en millimètresCes deux palettes doivent être remplies, dans
preparerEcran(), à la suite des autres :vce.setPalette(6, 1, RGB(7, 3, 3)); // palette 6 : rouge clair vce.setPalette(7, 1, RGB(5, 5, 5)); // palette 7 : grisEnfin, l'écran n'a que 32 colonnes : un écart de 5 demanderait 33 colonnes, c'est trop. On resserre les barres à 4 colonnes (6 × 4 + 3 = 27 colonnes), à partir de la colonne 3. Il reste un espace entre deux noms de jour.
ubyte x = (ubyte)(3 + i * 4); // 7 barres : de 3 à 27
Les sept jours sur PC Engine, avec deux nouvelles palettes. En résumé : les données (les tableaux) et la mise en page (la position
x) changent ; le code des boucles, lui, n'a pas bougé.
