Casse-briques sur Game Boy
L'écran de la Game Boy fait exactement la taille du terrain. Quatre gris, des tuiles dessinées à la main, et des objets écrits dans une copie de leur table, recopiée par DMA au retour vertical.
- Dossier
Examples/nintendo/dmg/CasseBriques_DMG/- Architecture
nintendo.dmg- Briques
- 6 tuiles dessinées, un gris par rangée
- Balle, raquette
- 4 objets de 8 × 8
- Fichiers
- 5, dont les règles communes
- Explications
- le chapitre 14, machine par machine
Les sources
Tous les fichiers du projet, tels qu'ils sont dans le dépôt. Les onglets passent d'un fichier à l'autre ; l'adresse de la page garde le fichier ouvert, pour le partager.
main().// 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++;
}
}// 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);
}
}// 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();
}
}tools/police.py.// 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 // '_'
};
}Police.s8u à partir de la police de la PC Engine.#!/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("}")Compiler et lancer
Dans Simple8 IDE : ouvrez le dossier du projet, puis compilez et lancez. En ligne de commande, depuis la racine du dépôt :
cd Examples/nintendo/dmg/CasseBriques_DMG/
java -jar ../../../../dist/LitleCompilator.jar -l ../../../../libs8 main.s8p
Le compilateur écrit l'assembleur et le programme dans le dossier asm/ du
projet : asm/main.gb, à ouvrir dans un émulateur. Sans manette, la démo joue
toute seule.
Ce programme est pédagogique : ce n'est pas la meilleure façon d'écrire un casse-briques optimisé pour cette machine, mais une façon claire de montrer comment on l'aborde avec Simple8.
