Simple8

Breakout on Game Boy

The Game Boy screen is exactly the size of the playfield. Four grays, hand-drawn tiles, and objects written into a copy of their table, copied by DMA during the vertical blank.

The breakout game on Game Boy
Folder
Examples/nintendo/dmg/CasseBriques_DMG/
Architecture
nintendo.dmg
Bricks
6 hand-drawn tiles, one gray per row
Ball, paddle
4 objects, 8 × 8
Files
5, including the shared rules
Explanations
chapter 14, machine by machine

The source code

All the project's files, as they are in the repository. The tabs switch from one file to another; the page address keeps the open file, so you can share it. The comments in the code are in French, like the rest of the repository.

Examples/nintendo/dmg/CasseBriques_DMG/main.s8pThe main program: the machine's libraries, the two units, main().29 lines
// Casse-briques — Game Boy
//
// Les règles sont dans Examples/common/CasseBriques/Jeu.s8u, les mêmes sur toutes les machines.
// Ici, l'affichage de la Game Boy : les briques en tuiles, la balle et la raquette en objets.
// Croix : gauche, droite, A pour lancer la balle. Sans manette, la démo joue.
architecture nintendo.dmg;

addlib hw.base;
addlib video.lcd;
addlib video.vram;
addlib video.tilemap;
addlib video.oam;
addlib input.joypad;

addunit "../../../common/CasseBriques/Jeu.s8u";   // les règles, communes
addunit "Police.s8u";                               // la police, générée par tools/police.py
addunit "Ecran.s8u";                                // l'affichage de la Game Boy

unit {
    ubyte images;               // compté par l'interruption, attendu par Ecran.attendreImage()

    main() {
        Jeu.jouer();
    }

    interrupt -> vblank {
        .images++;
    }
}
Examples/nintendo/dmg/CasseBriques_DMG/Ecran.s8uThe display for this machine: the eight functions of the Ecran unit's contract.113 lines
// Ecran.s8u : l'affichage du casse-briques sur Game Boy.
//
// La Game Boy affiche un plan de tuiles de 8 x 8 points et 40 objets (ses sprites), en 4 gris.
// Son écran fait 160 x 144 points : exactement le terrain du jeu.
//   - les briques sont des tuiles, d'un gris par rangée ;
//   - la balle est un objet, la raquette trois objets côte à côte. Les objets sont écrits dans
//     une copie de leur table (oam.shadow), recopiée par le DMA au retour vertical.
unit Ecran {
    const uint FOND = 0x9800;                   // le plan de tuiles du fond
    const uint TUILES_VRAM = 0x8010;            // la tuile 1 (la tuile 0 reste vide)

    // Les tuiles : 16 octets chacune, une ligne = plan 0, plan 1 (bit 7 = point de gauche).
    // 1 à 3 : la moitié gauche d'une brique de couleur 1, 2, 3 ; 4 à 6 : la moitié droite ;
    // 7 : la balle ; 8 : un morceau de raquette
    const ubyte TUILES[128] = {
        0xFF,0, 0xFF,0, 0xFF,0, 0xFF,0, 0xFF,0, 0xFF,0, 0xFF,0, 0,0,
        0,0xFF, 0,0xFF, 0,0xFF, 0,0xFF, 0,0xFF, 0,0xFF, 0,0xFF, 0,0,
        0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0,0,
        0xFE,0, 0xFE,0, 0xFE,0, 0xFE,0, 0xFE,0, 0xFE,0, 0xFE,0, 0,0,
        0,0xFE, 0,0xFE, 0,0xFE, 0,0xFE, 0,0xFE, 0,0xFE, 0,0xFE, 0,0,
        0xFE,0xFE, 0xFE,0xFE, 0xFE,0xFE, 0xFE,0xFE, 0xFE,0xFE, 0xFE,0xFE, 0xFE,0xFE, 0,0,
        0x60,0, 0xF0,0, 0xF0,0, 0x60,0, 0,0, 0,0, 0,0, 0,0,
        0xFF,0, 0xFF,0, 0xFF,0, 0xFF,0, 0,0, 0,0, 0,0, 0,0
    };
    // La couleur (1 à 3) de chaque rangée : les 4 gris ne permettent pas mieux
    const ubyte GRIS[] = { 1, 2, 3, 2, 1 };

    void init() {
        lcd.off();
        vram.copy(@Police.TUILES, 0x8200, 1024);        // les caractères : tuiles 32 à 95
        vram.copy(@.TUILES, .TUILES_VRAM, 128);
        tilemap.fill(.FOND, 0);
        // couleur 0 : le blanc, 1 : le noir (le texte), 2 et 3 : deux gris
        writeMem(hw.rBGP, 0x6C);
        writeMem(hw.rOBP0, 0x6C);
        oam.dmaInstall();
        oam.clear(@oam.shadow);
        writeMem(hw.rLCDC, hw.LCDCF_ON | hw.LCDCF_BG8000 | hw.LCDCF_OBJON | hw.LCDCF_BGON);
    }

    // Attendre le retour vertical, puis y recopier les objets par le DMA
    void attendreImage() {
        ubyte avant = Main.images;
        while (Main.images == avant) {
        }
        oam.flush();
    }

    ubyte manette() {
        ubyte p = joypad.read();
        ubyte t = 0;
        if ((p & hw.PADF_LEFT) != 0) {
            t = t | Jeu.GAUCHE;
        }
        if ((p & hw.PADF_RIGHT) != 0) {
            t = t | Jeu.DROITE;
        }
        if ((p & hw.PADF_A) != 0) {
            t = t | Jeu.BOUTON;
        }
        return t;
    }

    // Une case du plan de tuiles
    void poser(ubyte c, ubyte l, ubyte tuile) {
        vram.put(tilemap.at(.FOND, c, l), tuile);
    }

    void dessinerBriques() {
        for (ubyte l = 0; l < 5; l++) {
            for (ubyte c = 0; c < 10; c++) {
                poser((ubyte)(c * 2), (ubyte)(2 + l), .GRIS[l]);
                poser((ubyte)(c * 2 + 1), (ubyte)(2 + l), (ubyte)(3 + .GRIS[l]));
            }
        }
    }

    void effacerBrique(ubyte c, ubyte l) {
        poser((ubyte)(c * 2), (ubyte)(2 + l), 0);
        poser((ubyte)(c * 2 + 1), (ubyte)(2 + l), 0);
    }

    // Un objet dans la copie de la table : y + 16, x + 8, tuile, attributs
    void objet(ubyte n, ubyte x, ubyte y, ubyte tuile) {
        ubyte i = (ubyte)(n * 4);
        oam.shadow[i] = (ubyte)(y + 16);
        oam.shadow[i + 1] = (ubyte)(x + 8);
        oam.shadow[i + 2] = tuile;
        oam.shadow[i + 3] = 0;
    }

    void placer(ubyte raquette, ubyte x, ubyte y) {
        objet(0, x, y, 7);
        objet(1, raquette, Jeu.RAQUETTE_Y, 8);
        objet(2, (ubyte)(raquette + 8), Jeu.RAQUETTE_Y, 8);
        objet(3, (ubyte)(raquette + 16), Jeu.RAQUETTE_Y, 8);
    }

    void afficherInfos() {
        uint at = vram.print(tilemap.at(.FOND, 0, 0), "SCORE ", 32);
        at = vram.print(at, @Jeu.texteScore, 32);
        vram.print(tilemap.at(.FOND, 13, 0), "VIES ", 32);
        vram.put(tilemap.at(.FOND, 18, 0), (ubyte)(32 + Jeu.vies + 16));      // '0' + vies
    }

    void message(uint texte) {
        if (texte == 0) {
            vram.print(tilemap.at(.FOND, 7, 10), "     ", 32);
            return;
        }
        vram.print(tilemap.at(.FOND, 7, 10), texte, 32);
    }
}
Examples/common/CasseBriques/Jeu.s8uThe rules of the game, the same on all seven machines. This file exists only once.310 lines
// Jeu.s8u : les règles du casse-briques, les mêmes sur toutes les machines.
//
// Aucune ligne `architecture` : cette unit est compilée pour la machine du programme. Elle ne
// connaît que l'unit Ecran, que chaque machine écrit à sa façon (tuiles, sprites, bitmap…) :
//
//   Ecran.init()                     préparer l'écran
//   Ecran.attendreImage()            attendre l'image suivante
//   Ecran.manette()                  les touches : GAUCHE, DROITE, BOUTON
//   Ecran.dessinerBriques()          dessiner toutes les briques (Jeu.briques)
//   Ecran.effacerBrique(c, l)        effacer la brique de la colonne c, rangée l
//   Ecran.placer(raquette, x, y)     placer la raquette et la balle
//   Ecran.afficherInfos()            écrire le score et les vies (Jeu.texteScore, Jeu.vies)
//   Ecran.message(texte)             écrire un message au milieu, 0 pour l'effacer
//
// Le terrain fait 160 x 144 points ; chaque machine le place où elle veut sur son écran.
unit Jeu {
    // Le terrain
    const ubyte LARGEUR = 160;
    const ubyte HAUTEUR = 144;

    // Les briques : 10 colonnes de 16 points, 5 rangées de 8 points. 16 et 8 sont des
    // puissances de deux : la case d'un point se calcule par des décalages (x >> 4, y >> 3).
    const ubyte COLONNES = 10;
    const ubyte LIGNES = 5;
    const ubyte BRIQUE_L = 16;
    const ubyte BRIQUE_H = 8;
    const ubyte HAUT_BRIQUES = 16;              // le haut de la première rangée
    const ubyte BAS_BRIQUES = 56;               // HAUT_BRIQUES + LIGNES * BRIQUE_H
    const ubyte DEBUT_LIGNE[] = { 0, 10, 20, 30, 40 };   // une table plutôt qu'une multiplication
    const uint POINTS[] = { 50, 40, 30, 20, 10 };        // les rangées du haut valent plus

    // La raquette et la balle
    const ubyte RAQUETTE_Y = 132;
    const ubyte RAQUETTE_L = 24;
    const ubyte RAQUETTE_H = 4;
    const ubyte BALLE = 4;                      // la balle est un carré de 4 points

    // Les touches, rendues par Ecran.manette()
    const ubyte GAUCHE = 1;
    const ubyte DROITE = 2;
    const ubyte BOUTON = 4;

    // La démo vise un endroit différent de la raquette à chaque rebond : l'angle change, et la
    // balle ne tourne pas en rond dans une zone déjà vide
    const byte VISEES[] = { -9, 6, -3, 9, -6, 3 };

    ubyte briques[50];          // 1 : la brique est là
    ubyte restantes;            // briques encore debout
    ubyte vies;
    uint score;
    ubyte texteScore[6];        // le score en chiffres, pour Ecran.afficherInfos()

    int balleX;
    int balleY;
    int dx;                     // la vitesse de la balle, en points par image
    int dy;
    int raquetteX;
    ubyte collee;               // 1 : la balle attend sur la raquette
    ubyte attente;              // images avant le lancement automatique
    ubyte inactif;              // images sans toucher la manette : la démo prend la main
    ubyte visee;                // la visée de la démo, dans VISEES

    ubyte colonneTouchee;       // la brique trouvée par briqueEn()
    ubyte ligneTouchee;

    // ---------------------------------------------------------------------------------------
    // La boucle du jeu : appelée une fois par main(), ne revient jamais
    void jouer() {
        Ecran.init();
        nouvellePartie();
        while (1) {
            Ecran.attendreImage();
            image(Ecran.manette());
            Ecran.placer((ubyte).raquetteX, (ubyte).balleX, (ubyte).balleY);
        }
    }

    // ---------------------------------------------------------------------------------------
    // Une partie, une balle
    void nouvellePartie() {
        for (ubyte n = 0; n < 50; n++) {
            .briques[n] = 1;
        }
        .restantes = 50;
        .vies = 3;
        .score = 0;
        .inactif = 255;             // la démo joue tant que personne ne touche la manette
        Ecran.dessinerBriques();
        majInfos();
        nouvelleBalle();
    }

    void nouvelleBalle() {
        .raquetteX = 68;            // au milieu : (160 - 24) / 2
        .collee = 1;
        .attente = 60;              // une seconde avant le lancement automatique
        coller();
    }

    // La balle posée sur la raquette, en son milieu
    void coller() {
        .balleX = .raquetteX + 10;
        .balleY = .RAQUETTE_Y - .BALLE;
    }

    // ---------------------------------------------------------------------------------------
    // Une image de jeu
    void image(ubyte touches) {
        bougerRaquette(touches);
        if (.collee == 1) {
            coller();
            if (.attente > 0) {
                .attente--;
            }
            if ((touches & .BOUTON) != 0 || .attente == 0) {
                .collee = 0;
                .dx = 1;
                .dy = -2;
            }
            return;
        }
        deplacerBalle();
        if (.balleY >= .HAUTEUR) {
            ballePerdue();
        }
    }

    // La raquette : les touches, ou la démo quand personne ne joue
    void bougerRaquette(ubyte touches) {
        if (touches != 0) {
            .inactif = 0;
        } else if (.inactif < 255) {
            .inactif++;
        }
        if (.inactif > 120) {
            // la démo : la raquette suit la balle
            int cible = .balleX + 2 - .VISEES[.visee];
            if (.raquetteX + 12 < cible - 1) {
                touches = .DROITE;
            } else if (.raquetteX + 12 > cible + 1) {
                touches = .GAUCHE;
            }
        }
        if ((touches & .GAUCHE) != 0 && .raquetteX > 0) {
            .raquetteX = .raquetteX - 2;
        }
        if ((touches & .DROITE) != 0 && .raquetteX < .LARGEUR - .RAQUETTE_L) {
            .raquetteX = .raquetteX + 2;
        }
    }

    // ---------------------------------------------------------------------------------------
    // La balle : on avance en x, puis en y, et on regarde à chaque fois ce que touche le bord
    // de la balle qui avance
    void deplacerBalle() {
        int x = .balleX + .dx;
        int y;
        int bord;

        // les murs de gauche et de droite
        if (x < 0) {
            x = 0;
            .dx = -.dx;
        } else if (x > .LARGEUR - .BALLE) {
            x = .LARGEUR - .BALLE;
            .dx = -.dx;
        }
        // les briques, en x : le bord gauche ou droit de la balle, en haut et en bas
        bord = x;
        if (.dx > 0) {
            bord = x + .BALLE - 1;
        }
        if (toucher(bord, .balleY) != 0 || toucher(bord, .balleY + .BALLE - 1) != 0) {
            .dx = -.dx;
            x = .balleX;
        }
        .balleX = x;

        // le plafond
        y = .balleY + .dy;
        if (y < 0) {
            y = 0;
            .dy = -.dy;
        }
        // les briques, en y : le bord haut ou bas de la balle, à gauche et à droite
        bord = y;
        if (.dy > 0) {
            bord = y + .BALLE - 1;
        }
        if (toucher(.balleX, bord) != 0 || toucher(.balleX + .BALLE - 1, bord) != 0) {
            .dy = -.dy;
            y = .balleY;
        }
        .balleY = y;

        rebondRaquette();
    }

    // La raquette : la balle descend et chevauche la raquette
    void rebondRaquette() {
        int zone;
        if (.dy < 0) {
            return;
        }
        if (.balleY + .BALLE < .RAQUETTE_Y || .balleY > .RAQUETTE_Y + .RAQUETTE_H) {
            return;
        }
        if (.balleX + .BALLE <= .raquetteX || .balleX >= .raquetteX + .RAQUETTE_L) {
            return;
        }
        // l'endroit touché donne l'angle : le milieu de la balle sur l'une des 4 zones de
        // 6 points de la raquette. Trois comparaisons, pas de division par 6
        zone = .balleX + 2 - .raquetteX;
        if (zone < 6) {
            .dx = -2;
        } else if (zone < 12) {
            .dx = -1;
        } else if (zone < 18) {
            .dx = 1;
        } else {
            .dx = 2;
        }
        .dy = -2;
        .balleY = .RAQUETTE_Y - .BALLE;
        .visee++;
        if (.visee == 6) {
            .visee = 0;
        }
    }

    // ---------------------------------------------------------------------------------------
    // Les collisions avec les briques : la grille, sans regarder l'écran

    // La brique sous le point (x, y), ou 255 s'il n'y en a pas
    ubyte briqueEn(int x, int y) {
        ubyte n;
        if (y < .HAUT_BRIQUES || y >= .BAS_BRIQUES || x < 0 || x >= .LARGEUR) {
            return 255;
        }
        .colonneTouchee = (ubyte)(x >> 4);                     // x / 16
        .ligneTouchee = (ubyte)((y - .HAUT_BRIQUES) >> 3);     // (y - 16) / 8
        n = (ubyte)(.DEBUT_LIGNE[.ligneTouchee] + .colonneTouchee);
        if (.briques[n] == 0) {
            return 255;
        }
        return n;
    }

    // Casse la brique sous le point (x, y) s'il y en a une ; rend 1 si la balle a touché
    ubyte toucher(int x, int y) {
        ubyte n = briqueEn(x, y);
        if (n == 255) {
            return 0;
        }
        .briques[n] = 0;
        .restantes--;
        .score = .score + .POINTS[.ligneTouchee];
        Ecran.effacerBrique(.colonneTouchee, .ligneTouchee);
        majInfos();
        if (.restantes == 0) {
            gagne();
        }
        return 1;
    }

    // ---------------------------------------------------------------------------------------
    // Perdre une balle, gagner, attendre

    void ballePerdue() {
        .vies--;
        majInfos();
        if (.vies == 0) {
            Ecran.message("PERDU");
            pause(150);
            Ecran.message(0);
            nouvellePartie();
            return;
        }
        nouvelleBalle();
    }

    void gagne() {
        Ecran.message("BRAVO");
        pause(150);
        Ecran.message(0);
        for (ubyte n = 0; n < 50; n++) {
            .briques[n] = 1;
        }
        .restantes = 50;
        Ecran.dessinerBriques();
        nouvelleBalle();
    }

    void pause(ubyte images) {
        for (ubyte i = 0; i < images; i++) {
            Ecran.attendreImage();
        }
    }

    // Le score en chiffres dans texteScore (5 chiffres et le 0 de fin), puis l'affichage
    void majInfos() {
        uint v = .score;
        for (ubyte i = 5; i > 0; i--) {
            .texteScore[i - 1] = (ubyte)('0' + v % 10);
            v = v / 10;
        }
        .texteScore[5] = 0;
        Ecran.afficherInfos();
    }
}
Examples/nintendo/dmg/CasseBriques_DMG/Police.s8uCharacters 32 to 95 as Game Boy tiles. File generated by tools/police.py.70 lines
// Police.s8u : les caractères 32 à 95 en tuiles de Game Boy (16 octets chacune).
// FICHIER GÉNÉRÉ par tools/police.py : refaire la génération plutôt que le modifier.
unit Police {
    const ubyte TUILES[1024] = {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // ' '
        0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,   // '!'
        0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // '"'
        0x66, 0x00, 0x66, 0x00, 0xFF, 0x00, 0x66, 0x00, 0xFF, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00,   // '#'
        0x18, 0x00, 0x3E, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x06, 0x00, 0x7C, 0x00, 0x18, 0x00, 0x00, 0x00,   // '$'
        0x62, 0x00, 0x66, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x66, 0x00, 0x46, 0x00, 0x00, 0x00,   // '%'
        0x3C, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x38, 0x00, 0x67, 0x00, 0x66, 0x00, 0x3F, 0x00, 0x00, 0x00,   // '&'
        0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // "'"
        0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x00, 0x00,   // '('
        0x30, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x00, 0x00,   // ')'
        0x00, 0x00, 0x66, 0x00, 0x3C, 0x00, 0xFF, 0x00, 0x3C, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,   // '*'
        0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,   // '+'
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x00,   // ','
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // '-'
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,   // '.'
        0x00, 0x00, 0x03, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x00,   // '/'
        0x3C, 0x00, 0x66, 0x00, 0x6E, 0x00, 0x76, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // '0'
        0x18, 0x00, 0x18, 0x00, 0x38, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7E, 0x00, 0x00, 0x00,   // '1'
        0x3C, 0x00, 0x66, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x60, 0x00, 0x7E, 0x00, 0x00, 0x00,   // '2'
        0x3C, 0x00, 0x66, 0x00, 0x06, 0x00, 0x1C, 0x00, 0x06, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // '3'
        0x06, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x66, 0x00, 0x7F, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00,   // '4'
        0x7E, 0x00, 0x60, 0x00, 0x7C, 0x00, 0x06, 0x00, 0x06, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // '5'
        0x3C, 0x00, 0x66, 0x00, 0x60, 0x00, 0x7C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // '6'
        0x7E, 0x00, 0x66, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,   // '7'
        0x3C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // '8'
        0x3C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3E, 0x00, 0x06, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // '9'
        0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,   // ':'
        0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x30, 0x00,   // ';'
        0x0E, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0E, 0x00, 0x00, 0x00,   // '<'
        0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // '='
        0x70, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x70, 0x00, 0x00, 0x00,   // '>'
        0x3C, 0x00, 0x66, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,   // '?'
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // '@'
        0x18, 0x00, 0x3C, 0x00, 0x66, 0x00, 0x7E, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00,   // 'A'
        0x7C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x7C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x7C, 0x00, 0x00, 0x00,   // 'B'
        0x3C, 0x00, 0x66, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // 'C'
        0x78, 0x00, 0x6C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x6C, 0x00, 0x78, 0x00, 0x00, 0x00,   // 'D'
        0x7E, 0x00, 0x60, 0x00, 0x60, 0x00, 0x78, 0x00, 0x60, 0x00, 0x60, 0x00, 0x7E, 0x00, 0x00, 0x00,   // 'E'
        0x7E, 0x00, 0x60, 0x00, 0x60, 0x00, 0x78, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00,   // 'F'
        0x3C, 0x00, 0x66, 0x00, 0x60, 0x00, 0x6E, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // 'G'
        0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x7E, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00,   // 'H'
        0x3C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x3C, 0x00, 0x00, 0x00,   // 'I'
        0x1E, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x6C, 0x00, 0x38, 0x00, 0x00, 0x00,   // 'J'
        0x66, 0x00, 0x6C, 0x00, 0x78, 0x00, 0x70, 0x00, 0x78, 0x00, 0x6C, 0x00, 0x66, 0x00, 0x00, 0x00,   // 'K'
        0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x7E, 0x00, 0x00, 0x00,   // 'L'
        0x63, 0x00, 0x77, 0x00, 0x7F, 0x00, 0x6B, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x00, 0x00,   // 'M'
        0x66, 0x00, 0x76, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x6E, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00,   // 'N'
        0x3C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // 'O'
        0x7C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x7C, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00,   // 'P'
        0x3C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x0E, 0x00, 0x00, 0x00,   // 'Q'
        0x7C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x7C, 0x00, 0x78, 0x00, 0x6C, 0x00, 0x66, 0x00, 0x00, 0x00,   // 'R'
        0x3C, 0x00, 0x66, 0x00, 0x60, 0x00, 0x3C, 0x00, 0x06, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // 'S'
        0x7E, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,   // 'T'
        0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x00, 0x00,   // 'U'
        0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x18, 0x00, 0x00, 0x00,   // 'V'
        0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x6B, 0x00, 0x7F, 0x00, 0x77, 0x00, 0x63, 0x00, 0x00, 0x00,   // 'W'
        0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x18, 0x00, 0x3C, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00,   // 'X'
        0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3C, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,   // 'Y'
        0x7E, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x7E, 0x00, 0x00, 0x00,   // 'Z'
        0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00,   // '['
        0xC0, 0x00, 0xC0, 0x00, 0x30, 0x00, 0x30, 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x30, 0x00, 0x30, 0x00,   // '\\'
        0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00,   // ']'
        0x33, 0x00, 0x33, 0x00, 0xCC, 0x00, 0xCC, 0x00, 0x33, 0x00, 0x33, 0x00, 0xCC, 0x00, 0xCC, 0x00,   // '^'
        0x33, 0x00, 0x99, 0x00, 0xCC, 0x00, 0x66, 0x00, 0x33, 0x00, 0x99, 0x00, 0xCC, 0x00, 0x66, 0x00    // '_'
    };
}
Examples/nintendo/dmg/CasseBriques_DMG/tools/police.pyThe Python script that builds Police.s8u from the PC Engine font.18 lines
#!/usr/bin/env python3
# Écrit Police.s8u : les caractères 32 à 95 de la police de la PC Engine, en tuiles de Game Boy.
# Une tuile de la PC Engine commence par ses plans 0 et 1, une ligne après l'autre : c'est
# exactement une tuile de Game Boy (16 octets). Usage : python3 tools/police.py police.bin
import sys
font = open(sys.argv[1], "rb").read()
lignes = []
for c in range(32, 96):
    t = font[c * 32: c * 32 + 16]
    lignes.append("        " + ", ".join("0x%02X" % b for b in t) + ",   // " + repr(chr(c)))
lignes[-1] = lignes[-1].replace(",   //", "    //", 1)
print("// Police.s8u : les caractères 32 à 95 en tuiles de Game Boy (16 octets chacune).")
print("// FICHIER GÉNÉRÉ par tools/police.py : refaire la génération plutôt que le modifier.")
print("unit Police {")
print("    const ubyte TUILES[1024] = {")
print("\n".join(lignes))
print("    };")
print("}")

Compiling and running

In Simple8 IDE: open the project folder, then compile and run. From the command line, at the root of the repository:

cd Examples/nintendo/dmg/CasseBriques_DMG/
java -jar ../../../../dist/LitleCompilator.jar -l ../../../../libs8 main.s8p

The compiler writes the assembly code and the program into the project's asm/ folder: asm/main.gb, to open in an emulator. With no joypad, the demo plays by itself.

This program is meant for learning: it is not the best way to write a breakout game optimized for this machine, but a clear way to show how to approach it with Simple8.