Lorsque j'ai commencé à développer Puzzle Bobble, je m'étais fixé un objectif: il devait tourner sur la GX4000. (et cela sera le cas pour tous les jeux que nous ferons par la suite sur Amstrad).
A la différence de l'Oncle Ced, je n'ai pas un affect particulier pour cette console (j'étais passé au PC lors de sa sortie), mais l'expérience utilisateur d'un jeu console est toujours meilleure. Et la GX4000 étant rétrocompatible avec le CPC, cela en fait un fabuleux petit engin. Niveau jeu: 2 manettes de 4 flèches, 2 boutons. Difficile de faire plus simple. Rien de plus frustrant que devoir aller regarder dans un mode d'emploi pour chercher les règles du jeu.
Donc, pour revenir à Puzzle Bobble, il devait tourner sur la GX4000 et faire avec ces restrictions: 64k et pas d'accès disque. Avec no$cpc, le convertisseur d'image disque vers GX4000, l'accès disque peut être simulé, mais ce n'est pas ce que je voulais.
Si je veux que le programme tourne aussi sur 6128 (ou sur 464 avec extension mémoire), je ne peux bénéficier que d'une banque en lecture seule de 64k en supplément des mes 64k de base. Lecture seule pour qu'il tourne aussi sur la GX4000 (avec un tout petit peu de modifications pour le changement des banques, mais nous y reviendrons)
Cela fait quelques contraintes, mais beaucoup d'avantages...
Puzzle Bobble fonctionne aussi sur Dandanator avec un peu de changement de code, mais je n'en parlerais pas dans cet article.
Sur CPC6128, mon programme de chargement se fait en basic, je dois donc lui réserver l'espace mémoire entre 368 (0x170) et 1024 (0x400). Soit 656 octets tokenizés en Basic. Ce qui est suffisant pour un loader.
Le basic se réserve l'espace entre 42619 (0xA67B) jusque 49152 (0xC000). Ce 42619 correspond a un print himem
sur un CPC6128. Il varie selon le CPC.
À partir de 49152 (0xC000), nous avons la mémoire écran.
Mon application principale ne peut donc faire que 42619-1024 soit 41595 bytes maximum.
La première chose que je fais dans mon application principale, c'est de désactiver le firmware. Il prend du cpu et de l'espace mémoire pour rien. Je pourrais ainsi récupérer les 6533 octets (49152-42619) réservés par le basic. Ce n'est pas négligeable.
Sur GX4000, je n'ai pas cette limitation, mais pour avoir un code plus simple, je me l'impose aussi.
Pour la mémoire haute, c'est plus simple. Je dispose de banque de 4x16kb que je peux facilement charger en basic.
10 FOR i=1 TO 4
20 OUT &7F00,&C3+i
30 LOAD"!slot"+CHR$(48+i)+".bin",&4000
40 OUT &7F00,&C0
50 NEXT
C3: on déplace la banque RAM_4 (donc les 16 premiers ko des 64 ko supérieur) en 0x4000. Même chose pour C4,C5 et C6 (voir tableau ci-dessous) C0 étant la configuration par défaut des banques mémoires:
Switch RAM:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
1 | 1 | x | x | x | y | y | y |
Configuration de la RAM:
Adresse mémoire | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
0000-3FFF | RAM_0 | RAM_0 | RAM_4 | RAM_0 | RAM_0 | RAM_0 | RAM_0 | RAM_0 |
4000-7FFF | RAM_1 | RAM_1 | RAM_5 | RAM_3 | RAM_4 | RAM_5 | RAM_6 | RAM_7 |
8000-BFFF | RAM_2 | RAM_2 | RAM_6 | RAM_2 | RAM_2 | RAM_2 | RAM_2 | RAM_2 |
C000-FFFF | RAM_3 | RAM_7 | RAM_7 | RAM_7 | RAM_3 | RAM_3 | RAM_3 | RAM_3 |
Maintenant que tout est chargé, je devrais disposer d'un espace suffisant pour faire tourner le jeu.
Sauf que, pour rappel, Puzzle Bobble, c'est:
Est-ce que cela ne ferait pas trop pour un CPC6128 ? On fait comment alors ?
On compresse !
J'en avais déjà parlé dans un article sur Brixen dans lequel j'utilisais LZSA2. Pour Puzzle Bobble, j'ai gardé cette compression, mais suis passé en ZX0 pour nos futurs jeux. Sa compression est bien meilleure tout en étant à peine plus lente en décompression.
Pour les écrans, on décompresse directement en mémoire vidéo, c'est assez simple.
Pour les sprites, c'est plus compliqué... Sauf que l'on dispose de 6533 octets non utilisés par l'application (les 6533 octets utilisés par le Basic). Cette zone mémoire sera donc utilisée comme cache. Une partie pour la musique décompressée, une autre pour les sprites et le code. Le code ? Oui, j'ai compressé une certaine partie du code rarement utilisé dans la mémoire haute et la décompresse à la demande dans cette zone cache. Laquelle me demanderez-vous ? Le QR Code ! 2 ko de code inutile 99% du temps. Cela passe en mémoire haute. Chaque octet du programme principal est précieux.
Pas de LZSA2 ici... Il faudrait compresser 100 fois 80 octets. Le résultat n'est pas intéressant. Mais vu que je n'utilise que 12 bulles différentes, je peux mettre 2 bulles sur un octet (2x4 bits) et tout mettre sur 4000 octets.
Ca, c'est en théorie et ce dont je viens de me rendre compte en écrivant cet article.
En réalité, j'ai créé un outil de compression de type RLE. Les premiers 4 bits contenant les taux de répétition de la bulle et les 4 suivants contenant l'ID de la bulle. Via cette méthode, j'arrive à 4175 octets... Donc un peu plus que via la méthode simple. Bien vu...
Ma méthode me semblait tellement logique que je n'ai pas regardé s’il y avait plus simple. Cela me servira de leçon pour l'avenir.
J'en avais parlé au début de cet article, pour décompresser les objets en mémoire haute, il faut faire du switch de banques. Et cela ne se fait pas de la même manière sur le CPC que sur la GX4000.
En basic, c'est expliqué plus haut. En assembleur, c'est à peine plus compliqué:
bankTo4000:
di
ld h,#0
ld bc,#0x7FC4 ; switch RAM to 0x4000
add hl,bc
push hl
pop bc
out (c),c
ei
ret
On charge le slot dans le registre L et appelons la "fonction" bankTo4000
.
Pour le retour dans le mapping par défaut, cela se passe via la fonction restoreBank
.
restoreBank:
di
ld bc,#0x7FC0 ; switch RAM to default
out (c),c
ei
ret
Sur GX4000, les fonctions sont un peu différentes: les slots étant en ROM, les ports à attaquer ne sont donc pas les mêmes.
bankTo4000:
di
ld h,#0
ld bc,#0x7FA8+#4 ; switch ROM to 0x4000
add hl,bc
push hl
pop bc
out (c),c
ld bc,#0x7F88 ; activation de la ROM basse
out (c),c
ei
ret
restoreBank:
di
ld bc,#0x7F8C ; désactivation de la ROM basse
out (c),c
ei
ret
Adressage de la ROM inférieure sur ASIC:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
1 | 0 | 1 | 1 | 1 | x | x | x |
Nous y sommes arrivés.. 250k sur une machine de 112k utilisable.
Ma méthode de compression n'est pas l'unique façon de gagner de l'espace. Il y en a d'autres et sans doute des plus optimales.
Je n'ai repris le développement CPC qu'il y a un peu plus de 2 ans et j'apprends de nouvelles choses à chaque développement. Certaines de mes optimisations peuvent paraitre naïves, mais elles étaient suffisantes dans ce cas-ci.