SINCLAIR'S SPIRIT

Déplacement du sprite.

Le précédent cour fût un peu long, nous allons essayer ici de faire un peu plus court.
Cependant ce chapitre reste important car je le crée dans le but de vous apprendre les automodifications.
Je vais donc commencer par un peu de théorie pour être le plus compréhensible possible.

Opcodes:

Jusqu'ici nous nous sommes contenté de coder sans vraiment nous soucier de la façon donc le code est stocké.
Dans notre cas, le code est stocké en RAM, mais ce serait aussi valable pour une rom telles que les cartouches Sinclair pour interface 2.

Lorsque vous assemblez votre source, l'instruction ORG donnera l'adresse de début de votre programme.
A partir de cette adresse, les instructions que vous avez entré dans votre source seront traduite dans ce que nous appelons des opcodes.
Un opcode est simplement une valeur 8bits correspondant à une instruction.
Par exemple, l'instruction NOP correspond à l'opcode #00. RET à l'opcode #C9, tandis qu'un DEC A correspondra à #3D et INC A à #3C.
Chaque instruction a donc son opcode.

Certaines instructions ont plusieurs octets d'opcode comme par exemple LDI: #ED,#A0 ou LDIR: #ED,#B0.
D'autres instructions possèdent des paramètres qui sont stockés après l'opcode d'instruction.
C'est le cas par exemple des instruction de chargement de registre.
ex: LD HL,#4071 donnera #21,#71,#40
Retenez que lors d'un stockage d'une valeur 16bits, celle-ci sera inversée en ram. Le poids faible sera avant le poids fort.

Comme notre source contient plusieurs instructions qui se suivent, celles-ci seront écrites en RAM les unes à la suite des autres à partir de l'adresse définie par le ORG

ex:

                            ORG &8000
                            LD  A,2
                            LD  (&4000),A
                            RET

Donnera en RAM:

                #8000 ==> #3E,#02,#32,#00,#40,#C9

Lorsque vous mettez un label, celui-ci est transformé par l'assembleur en adresse:

                            ORG &8000
                .Debut      LD  A,2
                .Poke       LD  (&4000),A
                .Fin        RET

.Debut vaudra &8000 (puisque c'est la première instruction juste après le ORG, elle se trouve donc à cette adresse).
.Poke vaudra &8002 puisque le LD A,2 a pris 2 octets.
.Fin vaudra &8005 puisque cette fois, le LD (adr),A a pris 3 octets.

Notez et c'est important qu'on aurait pû n'avoir que le label .Debut, dans quel cas le LD (&4000),A serait en .Debut+2

N'hésitez pas à aller regarder en RAM vos programmes pour mieux comprendre comment tout ceci fonctionne. C'est assez simple en fait.

Pour terminer la dessus, comme je le disais, l'assembleur au moment de l'assemblage stocke l'adresse des labels.
Quand vous écrivez par exemple un JP .Debut, il va de soit que l'assembleur remplacera en RAM au moment de l'assemblage, le label .Debut par son adresse réelle.
Pour notre exemple précédent ou .Debut était en #8000, si vous écrivez un JP .Debut, ceci sera remplacé en RAM par #C3,#00,#80.
#C3 correspondant à l'instruction JP; les deux octets suivants correspondants à l'adresse #8000 en inversé.

Automodification:

Voici quelque chose de très important pour vous.
L'automodification va vous permettre de modifier non seulement des valeurs, mais aussi des instructions dans votre code pendant que celui-ci fonctionne.

Prenons notre sprite et imaginons que nous voulons le faire changer de position en incrémentant son adresse d'affichage.
En début de notre routine d'affichage, nous avons un LD A,POSX qui contient la coordonnée X de notre sprite.
Nous avons plusieurs solutions pour augmenter cette valeur:
- Sauvegarder la valeur pour l'incrémenter et sauter à l'affichage du sprite. - Modifier la valeur directement dans le LD A,data afin qu'il contienne la nouvelle valeur.

L'automodification correspond justement à la deuxième solution.
On pourra ici la privilégier bien que notre programme pour le moment ne soit pas compliqué.
Mais imaginez un programme avec plein de valeurs différentes à gérer: Vous ne pourrez pas vous permettre de sauvegarder ces valeurs et faire une boucle.

Mais l'automodification ne s'arrete pas à modifier des valeurs.
Comme Nos instructions sont en RAM sous forme d'opcode, rien ne nous empêche de modifier les instructions !!!

Imaginons que pour faire déplacer notre sprite, nous décidons de faire un INC A sur la coordonnée X.
Puis, après avoir testé si on touche le bord de l'écran, on veut faire partir notre sprite vers la gauche.
La solution est simple: il suffit de transformer notre INC A (opcode #3C) en DEC A (opcode #3D) !

C'est exactement ce que nous allons faire pour faire rebondir notre sprite à l'écran

Déplacement en Y

Reprenons le code de notre affichage de sprite.

                                org     &8000           
                .XPOS           EQU     10              
                .YPOS           EQU     36              

                                LD      A,.YPOS         
                                CALL    .CALCLGN        
                                LD      A,.XPOS
                                ADD     A,E             
                                LD      E,A             

                                

                                LD      HL,.Smile       
                                LD      B,16            
                .SPRITE
                                PUSH    BC              
                                LDI                     
                                LDI                     
                                DEC     E               
                                DEC     E               
                                CALL    .LGNINF         
                                POP     BC              
                                DJNZ    .SPRITE         

                                RET

Puisque nous voulons déplacer notre sprite en Y, nous allons récupérer la valeur Y et l'incrémenter.
Pour cela nous allons ajouter un label .POSY juste devant la lecture de celle-ci.
Après notre affichage ajoutons donc la lecture de la coordonnée et incrémentons la:

                                org     &8000           
                .XPOS           EQU     10              
                .YPOS           EQU     36              

                .POSY           LD      A,.YPOS         ;J'ajoute le label au niveau de l'instruction qui donne la coordonnée Y
                                CALL    .CALCLGN        
                                LD      A,.XPOS
                                ADD     A,E             
                                LD      E,A             

                                

                                LD      HL,.Smile       
                                LD      B,16            
                .SPRITE
                                PUSH    BC              
                                LDI                     
                                LDI                     
                                DEC     E               
                                DEC     E               
                                CALL    .LGNINF         
                                POP     BC              
                                DJNZ    .SPRITE         

                                LD      A,(.POSY+1)     ;On lit la coordonnée Y
                                INC     A               ;On l'incrémente
                                LD      (.POSY+1),A     ;On écrit la nouvelle coordonnée

                                RET

Ce que j'ai fait:
J'ai lû en .POSY l'octet qu'il y avait. Pourquoi +1 ? Parce qu'en .POSY nous avons le LD A,.YPOS
Or LD A, c'est #3E. La valeur est donc bien en +1 par rapport à l'adresse pointée par le label qui est sur l'instruction.

Avec le LD A,(.POSY+1), je récupère donc la coordonnée Y que je peux incrémenter avant de la réinjecter la ou elle était.

Afin que notre sprite se déplace il nous faut déjà faire une boucle.
Commençons par cela en ajoutant un label .SYNC (on verra juste après pourquoi) avant .POSY.
Nous supprimerons le RET de fin de programme pour le remplacer par un JP .SYNC ce qui nous fera une boucle infinie.

                                org     &8000           
                .XPOS           EQU     10              
                .YPOS           EQU     36   

                .SYNC           
                                NOP           

                .POSY           
                                LD      A,.YPOS         ;J'ajoute le label au niveau de l'instruction qui donne la coordonnée Y
                                CALL    .CALCLGN        
                                LD      A,.XPOS
                                ADD     A,E             
                                LD      E,A             

                                

                                LD      HL,.Smile       
                                LD      B,16            
                .SPRITE
                                PUSH    BC              
                                LDI                     
                                LDI                     
                                DEC     E               
                                DEC     E               
                                CALL    .LGNINF         
                                POP     BC              
                                DJNZ    .SPRITE         

                                LD      A,(.POSY+1)     ;On lit la coordonnée Y
                                INC     A               ;On l'incrémente
                                LD      (.POSY+1),A     ;On écrit la nouvelle coordonnée

                                JP      .SYNC

Pour le moment j'ai ajouté juste une instruction NOP sans utilité après le Label .SYNC, n'y faites pas attention c'est simplement pour ne pas mettre .SYNC et .POSY à la même adresse. Mais c'est réellement sans importance.

Si vous avez lancé ce code, vous avez dû vous rendre compte de deux choses:
- C'est trop rapide.
- Ca ne s'arrête jamais et ca plante.

Réflechissons deux secondes:
Pour ce qui est du plantage c'est normal: Nous incrémentons La coordonnée Y qui finit par atteindre des valeurs supérieures à la taille de l'écran.
Par conséquent, au lieu d'afficher notre sprite à l'écran, nous finissons par l'afficher n'importe ou en RAM et même sur notre code en lui même au bout d'un moment.

Pour ce qui est de la vitesse c'est simple aussi.
Le moniteur cathodique affiche 50 images par secondes.
Une image est donc affichée tout les 50ème de seconde.
Si nous affichons plus rapidement que cela, nous n'aurons pas le temps de voir l'image car avant qu'elle soit affichée sur le moniteur, d'autres auront été écrites.
Il nous faut donc ralentir l'affichage pour n'avoir qu'un seul déplacement par 50ème de seconde.

Heureusement pour nous sur spectrum nous avons ce que nous appelons des interruptions. Et pour être plus précis, nous en avons une. Une seule...
Pourquoi je vous parle de cela ? Simplement parce que cette seule interruption a lieue en haut de l'écran !!!
En détectant donc cette interruption, nous pouvons donc nous caller sur le début d'une image.

je reviendrai sur les interruptions plus tard dans les cours (très bientôt) pour plus détailler ce que c'est et comment cela fonctionne

En attendant, retenez que l'instruction HALT permet d'attendre une interruption.
Quand le Z80 execute l'instruction HALT, celui-ci va faire des NOPs (donc ne rien faire) et attendre d'intercepter une interruption.
Quand cela sera fait, le Z80 sautera à l'adresse #0038 ou en temps normal est executé le system (qui rafraichis les couleurs entre autre) avant de retourner après le HALT.
Bref, on s'en moque pas mal pour le moment, tout ce qu'on doit retenir c'est que l'instruction:
HALT : Attend une interruption. Sur spectrum il n'y en a qu'une en haut de l'écran et donc au début de chaque image.

En plaçant un HALT en début de notre boucle, nous nous assurons qu'avant de réafficher un sprite nous serons sur le début d'un nouvel écran.

                                org     &8000           
                .XPOS           EQU     10              
                .YPOS           EQU     36   

                .SYNC           
                                HALT                    ;On attend le début d'un nouvel écran          

                .POSY           
                                LD      A,.YPOS         ;J'ajoute le label au niveau de l'instruction qui donne la coordonnée Y
                                CALL    .CALCLGN        
                                LD      A,.XPOS
                                ADD     A,E             
                                LD      E,A             

                                

                                LD      HL,.Smile       
                                LD      B,16            
                .SPRITE
                                PUSH    BC              
                                LDI                     
                                LDI                     
                                DEC     E               
                                DEC     E               
                                CALL    .LGNINF         
                                POP     BC              
                                DJNZ    .SPRITE         

                                LD      A,(.POSY+1)     ;On lit la coordonnée Y
                                INC     A               ;On l'incrémente
                                LD      (.POSY+1),A     ;On écrit la nouvelle coordonnée

                                JP      .SYNC

Maintenant que tout ceci est bien ralentis à la bonne fréquance, nous pouvons corriger notre soucis de débordement.

Puisqu'un écran fait 192 lignes de haut et notre sprite 16 lignes de haut, nous pouvons donc descendre jusqu'à 192-16 en Y.
Nous allons pouvoir tester si la valeur de Y atteind 192-16 avec l'instruction CP val.

CP val : Permet de ComParer val au contenu du registre A. Les flags sont alors mis à jour.
Par exemple, si nous faisons un CP 192-16 et que A=192-16=176, alors le flag Z (Z comme Zéro) sera mis.
Bien entendu tant que A est différent de 176, le flag sera NZ (Non Zéro).
Hors souvenez vous du passage ou je vous expliquais les instructions de saut... Je vous avais indiqué que l'on pouvait faire des sauts conditionnels via les flags.
Nous allons donc pouvoir sauter à une adresse en fonction de l'état des flags.

Instructions de comparaison:

CP val : Permet de comparer la valeur "val" à celle du registre A.
CP r8 : Permet de comparer la valeur d'un registre 8 bits à celle du registre A.
CP (HL) : Permet de comparer la valeur contenue à l'adresse HL à celle du registre A.
CP (IX+disp) : Permet de comparer la valeur contenue à l'adresse IX à celle du registre A.
CP (IY+disp) : Permet de comparer la valeur contenue à l'adresse IY à celle du registre A.

Interprétation des flags avec N qui est r8, val ou (r16):

Si A=N alors Z=1 , sinon Z=0
Si A'<'N alors C=1
Si A'>='N alors C=0

Il existe d'autres instructions de comparaison un peu plus compliquées que nous ne verrons pas ici pour le moment: CPI, CPIR, CPD, CPDR

Je termine la dessus avant de faire un petit écart pour vous parler des flags:
Puisque nous pouvons tester si la valeur de Y=176, nous allons poser cette condition et sauterons plus loin si ce n'est pas le cas.
Dans le cas ou nous serions arrivé en 176, nous allons faire notre sprite changer de direction en automodifiant notre code pour cette fois décrémenter Y.
Rien de plus simple, il suffit alors d'automodifier le INC A (#3C) pour le changer en DEC A (#3D).
Il faudra alors penser à faire un autre test pour le Y. A savoir vérifier qu'il n'arrive pas à 0 dans quel cas on refera l'automodification inverse.
Pour tester si A=0 plutôt que de faire une CP 0, faites un OR A. En effet, OR A est plus rapide qu'un CP 0 et mets lui aussi à jour les flags.

Voici donc le bout de routine que nous pourrons ajouter en lieu et place de la précédente:

                                LD      A,(.POSY+1)
                .PasY   
                                INC     A
                                LD      (.POSY+1),A
                .TestYPlus
                                CP      192-16
                                JP      NZ,.TestYMoins
                                LD      A,&3D
                                LD      (.PasY),A  
                .TestYMoins
                                OR      A
                                JP      NZ,.TestX
                                LD      A,&3C
                                LD      (.PasY),A

                .TestX
                                JP      .SYNC

Je vous laisse faire de même avec le X histoire de faire rebondir votre sprite partout sur l'écran.
Pour le X, L'écran fait 32 octet de large. Comme votre sprite en fait 2 de large, vous devrez donc tester si votre coordonnée X est à 32-2 avant de faire demi-tour.
Si vous avez bien suivi et bien assimilé la chose, cela devrait se faire sans soucis.
Je vous laisse donc avec un petit peu d'information sur les flags et vous souhaite bon courage dans la réalisation de votre routine.

Les Flags

Le registre F est le registre des flags (drapeaux en anglais). Ce registre ne peut être lû directement.
Ce registre est modifié par les calculs, mais aussi par les comparaisons.
Chaque bit de ce registre correspond à un flag. Je ne m'étendrai pas la dessus, vous retrouverez le détail dans la documentation du site.

Nous pouvons tester ces flags via les instructions de saut. Voici la signification de ces conditions:

C : Indique que le résultat d'un calcul a généré une retenue.
NC : Indique que le résultat d'un calcul n'a pas généré de retenue.
Z : Indique que le résultat d'un calcul a donné zéro.
NZ : Indique que le résultat d'un calcul n'a pas donné zéro.
M : Indique que le résultat d'un calcul a donné un résultat négatif.
P : Indique que le résultat d'un calcul a donné un résultat positif.
PE : Indique que le resultat d'un calcul a débordé (overflow) et donne un résultat invalide.
PO : le contraire de PE...

X

CONNEXION




Inscription