Simple8

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.

indice01234
pluie37295
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 :

Après l'appelContenu de pluie
au départ3, 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;
    }
}
Écran du Commodore 64 : un histogramme de cinq barres de couleur, LUN à VEN, et LE PLUS PLUVIEUX : JEU
La pluie de la semaine sur Commodore 64.
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;
    }
}
Écran de la PC Engine : un histogramme de cinq barres de couleur, LUN à VEN, et LE PLUS PLUVIEUX : JEU
La pluie de la semaine sur PC Engine.

À retenir

À vous de jouer

  1. Écrivez une fonction uint somme(ubyte *valeurs, ubyte nombre) et affichez le total de la semaine sous l'histogramme.
    Voir la solutionMasquer la solution

    somme reprend la boucle du chapitre : une variable s part de 0, et on lui ajoute chaque case. Comme dessinerBarres et indiceMax, 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 un uint. Un ubyte ne 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 reprend ecrireNombre() du chapitre 5, recopiée telle quelle sous somme (avec vdc.putChar sur 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 avec vdc à la place de vic, et la ligne 26 au lieu de 24 (vdc.setCursor(4, 26)), sous « LE PLUS PLUVIEUX ».

    Écran du Commodore 64 : l'histogramme, puis TOTAL : 026 MM sous LE PLUS PLUVIEUX : JEU
    Le total de la semaine sur Commodore 64.
    Écran de la PC Engine : l'histogramme, puis TOTAL : 026 MM
    Et sur PC Engine.
  2. Avant de dessiner, ajoutez 2 mm à chaque jour avec une boucle.
    Voir la solutionMasquer la solution

    La boucle parcourt toutes les cases de pluie et ajoute 2 à chacune. Elle se place dans main(), 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 de 11. En effet, '0' + h ne donne un chiffre que pour h de 0 à 9 : le caractère qui suit 9 dans la table des caractères est :, puis ;.

    Écran du Commodore 64 : au-dessus de la barre du jeudi, un point-virgule au lieu de 11
    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). Dans dessinerBarres :

    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
    Écran du Commodore 64 : les barres 5, 9, 4, 11, 7
    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 écrire transformer(@.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.

  3. Écrivez indiceMin, et affichez aussi le jour le plus sec.
    Voir la solutionMasquer la solution

    indiceMin est la copie de indiceMax, 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é de record, 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)).

    Écran de la PC Engine : LE PLUS PLUVIEUX : JEU, puis LE PLUS SEC : MER
    Le mercredi, avec 2 mm, est le jour le plus sec.
  4. 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 de sizeof(.pluie[]), et suivent donc le tableau. C'est tout l'intérêt de ne pas écrire 5 en dur. Attention à la taille de pluie : avec pluie[5] et sept valeurs, le compilateur ne garderait que les cinq premières, avec l'avertissement Array initializer truncated from 7 to 5 elements. On écrit pluie[7], ou pluie[] 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ètres

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

    Écran du Commodore 64 : la barre du dimanche coupée, une partie à droite, une partie à gauche de l'écran
    Sans changer la position des barres : le dimanche déborde.

    Il faut donc recalculer la position x dans dessinerBarres. 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
    Écran du Commodore 64 : sept barres, de LUN à DIM
    Les sept jours sur Commodore 64.

    Sur PC Engine

    Les tableaux changent de la même façon. Sur cette machine, COULEURS contient 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ètres

    Ces 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 : gris

    Enfin, 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
    Écran de la PC Engine : sept barres, de LUN à DIM
    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é.