INITIATION A L'ASM - Partie 4

 

L'assembleur pour les nuls

Les précédents textes de cette série ne m'ont pas rapportés les réactions que j'attendais. Ma vision des choses n'a pas suscité de "Ah, ouai, je comprend mieux comme ça", mais plutôt des "Oh lui hé, c'est quoi cette initiation pour les pros ?".

Cette fois ci, nous allons donc nous rabattre sur quelque chose de beaucoup plus classique pour une initiation à l'asm : un petit jeu de moto "à la Tron". Mais ouiiii, vous savez bien, le jeu où il faut diriger un petit véhicule qui laisse un mur derrière lui, aussi connu sous le nom de "Nibbles".

Il fut une époque où on payait pour jouer à ca dans les cafés. Vous rendez-vous compte de l'incroyable économie que je vous fait faire ?

 

Le Daytona des pauvres

Inutile de vous dire que le jeu ne sera pas en 375x490 256 couleurs, smooth scrolling et autres sprites plein-écran. Aux extravagences du mode X nous préfererons un mode 13h sans histoire, à l'animation vertigineuse nous substituerons la stabilité tranquille d'un écran fixe, et aux sprites capricieux nous opposerons sans faillir le calme résolu de notre unique point-moto. Ca devrait convenir à tout le monde.

Je rappelle qu'en mode 13h, pour afficher un point aux coordonnées (X,Y), il faut écrire la couleur du point (sur un octet, donc entre 0 et 255) à l'adresse A0000h+(320*Y)+X. Donc, avec la valeur A000h dans un registre de segment, il suffit d'écrire à l'offset (320*Y)+X, où X est compris entre 0 et 319 (inclus) et Y entre 0 et 199 (inclus aussi). Pour en savoir plus là dessus, relisez le texte de Mogar dans le Rep5.

L'organigramme est donc dépouillé de toute difficultée ((X,Y) sont les coordonnées du point-moto) :


                     +----------------------------+
                     | Initialiser l'écran de jeu |
                     |(mode 13h + tracer le cadre)|
                     +-------------+--------------+
                  +----------------+------------------+
                  |  Verifier les coordonnées (X,Y).  +-+
                  |S'il y a quelquechose, c'est perdu.| |
                  +----------------+------------------+ |
                 +-----------------+------------------+ |
                 |Afficher la moto... heu... le point,| |
                 |        aux coordonnées (X,Y)       | |
                 +-----------------+------------------+ |
                      +------------+-------------+      |
                      |Lire une touche au clavier|      |
                      +------------+-------------+      |
                  +----------------+-----------------+  |
                  |   Si Haut : Xinc = 0 et Yinc =-1 |  |
                  |                                  |  |
                  |    Si Bas : Xinc = 0 et Yinc = 1 |  |
                  |                                  |  |
                  | Si Gauche : Xinc =-1 et Yinc = 0 |  |
                  |                                  |  |
                  | Si Droite : Xinc = 1 et Yinc = 0 |  |
                  +----------------+-----------------+  |
                       +-----------+-------------+      |
                       | Faire avancer la moto : |      |
                       |      X = X + Xinc       |      |
                       |      Y = Y + Yinc       |      |
                       +-----------+-------------+      |
                              +----+----+               |
                              | Boucler |               |
                              +----+----+               |
                                   +--------------------+

 

Le listing certifié sans bug

Voici donc le programme (TASM) :


 +-------------------------------------------------------------------------+
 |                                                                         |
 | ; PROGRAMME DE SIMULATION DE MOTO WOA BALEZE ! (c) MegaSoft Corp.       |
 |                                                                         |
 | .386                                                                    |
 |                                                                         |
 | CODE SEGMENT USE16                                                      |
 | ASSUME cs:CODE                                                          |
 | ORG 100h                                                                |
 |                                                                         |
 | Start:                                                                  |
 |                                                                         |
 | ;      **************************                                       |
 | ;      INITIALISER L'ECRAN DE JEU                                       |
 | ;      **************************                                       |
 |                                                                         |
 |        mov ax,13h    ; Mode vidéo 13h (320x200 bébête)                  |
 |        int 10h       ; et remplit la mémoire vidéo avec des zeros       |
 |                                                                         |
 |        mov ax,0a000h ; ES = A000h  (mémoire vidéo)                      |
 |        mov es,ax                                                        |
 |                                                                         |
 |        xor bx,bx     ; BX = 0                                           |
 |        mov cx,320    ; 320 points pour les bordures horizontales        |
 |                                                                         |
 | TraceBordsHorizontaux:                                                  |
 |                                                                         |
 |        mov byte ptr es:[bx],15       ; Trace le bord suppérieur         |
 |        mov byte ptr es:[bx+63680],15 ; et le bord inférieur             |
 |        inc bx                        ; on avance                        |
 |        loop TraceBordsHorizontaux                                       |
 |                                                                         |
 |        xor bx,bx                                                        |
 |        mov cx,200    ; 200 points pour les bordures verticales          |
 |                                                                         |
 | TraceBordsVerticaux:                                                    |
 |                                                                         |
 |        mov byte ptr es:[bx],15      ; Bord gauche                       |
 |        mov byte ptr es:[bx+319],15  ; Bord droit                        |
 |        add bx,320                   ; on descend d'une ligne            |
 |        loop TraceBordsVerticaux                                         |
 |                                                                         |
 |                                                                         |
 | ;      Maintenant la boucle principale commence                         |
 |                                                                         |
 | BigBoucle:                                                              |
 |                                                                         |
 | ;      ******************************                                   |
 | ;      VERIFIER LES COORDONNEES (X,Y)                                   |
 | ;      ******************************                                   |
 |                                                                         |
 |        movzx esi,Y          ; esi = Y  (>0)                             |
 |        lea si,[esi+esi*4]   ; si = esi*5   ]  Au final :                |
 |        shl si,6             ; si = si*64   ]  si = Y * 320              |
 |        add si,X             ; si = si+X  ->  si = adresse du point      |
 |                                                                         |
 |        cmp byte ptr es:[si],0    ; Y a t-il deja quelquechose ?         |
 |        jne Exit                  ; Si oui, alors c'est perdu            |
 |                                                                         |
 | ;      ***********************                                          |
 | ;      AFFICHER LE POINT (X,Y)                                          |
 | ;      ***********************                                          |
 |                                                                         |
 |        mov byte ptr es:[si],2  ; 2 est la couleur de la moto            |
 |                                ; (pourquoi pas, hein ?)                 |
 |                                                                         |
 |        mov dx,3dah      ; on attend un 70eme de seconde                 |
 |        in al,dx         ; sinon vous n'aurez pas le temps de voir       |
 |        test al,8        ; le cadre s'afficher que vous serez deja       |
 |        jnz $-3          ; écrasé contre un mur.                         |
 |        in al,dx                                                         |
 |        test al,8                                                        |
 |        jz $-3                                                           |
 |                                                                         |
 |                                                                         |
 | ;      **************************                                       |
 | ;      LIRE UNE TOUCHE AU CLAVIER                                       |
 | ;      **************************                                       |
 |                                                                         |
 |        mov ah,1           ; Y a t-il une touche pour nous ?             |
 |        int 16h                                                          |
 |        jz PasTouche       ; Sinon, tant pis, on repassera plus tard     |
 |                                                                         |
 |        mov ah,0           ; Oui ?  Alors on la prend.                   |
 |        int 16h                                                          |
 |                                                                         |
 |        cmp al,'i'         ; 'i' c'est pour monter                       |
 |        jne PasI                                                         |
 |        mov Xinc,0                                                       |
 |        mov Yinc,-1                                                      |
 |        jmp PasTouche                                                    |
 | PasI:                                                                   |
 |        cmp al,'k'         ; 'k' c'est pour descendre                    |
 |        jne PasK                                                         |
 |        mov Xinc,0                                                       |
 |        mov Yinc,1                                                       |
 |        jmp PasTouche                                                    |
 | PasK:                                                                   |
 |        cmp al,'j'         ; 'j' c'est pour aller à gauche               |
 |        jne PasJ                                                         |
 |        mov Xinc,-1                                                      |
 |        mov Yinc,0                                                       |
 |        jmp PasTouche                                                    |
 | PasJ:                                                                   |
 |        cmp al,'l'         ; 'l' c'est pour la droite                    |
 |        jne PasTouche                                                    |
 |        mov Xinc,1                                                       |
 |        mov Yinc,0                                                       |
 |                                                                         |
 | PasTouche:                                                              |
 |                                                                         |
 | ;      *********************                                            |
 | ;      FAIRE AVANCER LA MOTO                                            |
 | ;      *********************                                            |
 |                                                                         |
 |        mov ax,Xinc                                                      |
 |        add X,ax                                                         |
 |                                                                         |
 |        mov ax,Yinc                                                      |
 |        add Y,ax                                                         |
 |                                                                         |
 | ;      *******                                                          |
 | ;      BOUCLER                                                          |
 | ;      *******                                                          |
 |                                                                         |
 |        jmp BigBoucle                                                    |
 |                                                                         |
 | ;      ****                                                             |
 | ;      EXIT                                                             |
 | ;      ****                                                             |
 |                                                                         |
 | Exit:                                                                   |
 |                                                                         |
 |        mov ax,3h            ; mode video texte 80x25                    |
 |        int 10h                                                          |
 |                                                                         |
 |        mov ah,4ch           ; Retour au DOS                             |
 |        int 21h                                                          |
 |                                                                         |
 | ;      *****                                                            |
 | ;      DATAS                                                            |
 | ;      *****                                                            |
 |                                                                         |
 | X      DW 30                ; coordonnées de départ : (30,100)          |
 | Y      DW 100                                                           |
 |                                                                         |
 | Xinc   DW 1                 ; direction de départ : droite              |
 | Yinc   DW 0                                                             |
 |                                                                         |
 | Code ENDS                                                               |
 |                                                                         |
 | END Start                                                               |
 |                                                                         |
 +-------------------------------------------------------------------------+

Voilà. C'est tout de même difficile d'imaginer plus simple (et réciproquement : c'est simple d'imaginer plus difficile). Nous allons quand même détailler le programme pour que tout soit réellement limpide.

Avant cela, je tient à vous faire remarquer le soin tout particulier avec lequel j'ai choisi des noms de labels explicites. En effet, on a souvent l'habitude de frapper le clavier du poing pour obtenir un joli nom de label auquel il ne manque plus que les deux points (:) terminaux, tout en mémorisant l'impulsion du geste et l'amplitude du mouvement pour pouvoir réutiliser ce nom de label plus tard (quoique personnelement je sois plutôt un adepte des toto1, toto2, toto3, etc...).

 

GOTO TOTO

Commençons par la première ligne, où un ".386" peut surprendre ceux qui ne lisent pas la doc de leur assembleur (c'est c'laaaa ouiiii, on y croit) : Cette directive sert à dire à l'assembleur que l'on veut utiliser des instructions du 386. Si vous ne la mettez pas, l'assembleur refusera d'assembler notre MOVZX et notre LEA un peu compliqué. Vous penserez donc aussi à ne pas oublier de préciser à notre cher TLink son option /3 sinon vous devrez souffrir un message d'erreur de ce type : "32 bits instructions encountered. Restart with /3 option" ("quoi, tu peux pas le faire toi même ??").

De la même manière, le "USE16" derrière la déclaration de segment signale à l'assembleur que le code du segment est en mode 16 bits par défaut. Si vous ne le mettez pas, l'assembleur va considérer que votre code est en 32 bits par défaut, et il va vous assembler ça n'importe comment. Et si vous ne savez pas ce que sont ces modes, vous n'avez qu'à relire l'init du Rep6.

Le ORG 100h sert à initialiser le compteur d'adresses de l'assembleur sur 100h (=256), de telle manière que le premier offset assemblé soit à l'adresse 100h. C'est obligatoire pour un programme COM.

Start est le point d'entrée du programme. En fait, c'est idiot de préciser le point d'entrée, puisque c'est forcément la première instruction du programme car c'est un COM. Mais je ne suis pas là pour énumerer toutes les inepties de Tasm/TLink...

L'initialisation de l'écran ne devrait pas poser de problème particulier. On en arrive donc aux fameuses instructions 386...

MOVZX sert à recopier un opérande registre ou mémoire (mémoire dans notre cas) dans un registre de taille plus grande, en initialisant les bits supérieurs à zéro (MOV with Zero eXtend). Dans notre cas, ca va copier Y dans SI et mettre les 16 bits hauts de ESI à zéro.

On est obligé de passer par ESI pour l'instruction suivante, "LEA SI,[ESI+ESI*4]", car "ESI+ESI*4" est un mode d'adressage 32 bit impossible à réaliser avec des registres 16 bits. Donc, notre LEA (Load Effective Address) va mettre dans SI l'adresse formée par ESI+ESI*4, c'est à dire ni plus ni moins ESI+ESI*4. Pour ceux qui n'auraient pas compris la subtilité, cela revient à multiplier SI par 5.

Le "SHL SI,6" qui suit remultiplie SI par 2^6 = 64.

En fin de compte, SI est donc multiplié par 320. C'est plutôt bonnard car nous avions placé Y dans SI. Il ne reste plus qu'à additionner X à SI pour avoir l'adresse du point (320*Y+X) dans SI.

Et voilà comment on se débarasse d'une multiplication.

En fait, dans ce cas, il aurait été équivalent de faire :

 MOV SI,Y
 IMUL SI,320
 ADD SI,X

Mais bon, c'est toujours kewl de savoir que l'on peut éliminer des multiplications par des combinaisons d'additions, de décalages et de LEAs (quand il est nécessaire d'aller vite, ce qui n'est pas vraiment le cas ici, même si votre fournisseur n'a pas encore reçu sa nouvelle gamme de p6).

Ensuite, on regarde ce qui se trouve à cet offset en mémoire vidéo. S'il y a autre chose que zéro, c'est qu'on a à faire à un mur ou au cadre. Donc le joueur s'est planté. Dans ce cas, on saute au label EXIT qui le ramènera au DOS sans ménagement. Ca lui apprendra à ce nul.

Evidement, ce test est toujours OK la première fois, mais nous allons repasser ici à chaque tour avant d'afficher le point.

Après l'affichage du point, vous remarquerez une série de OUT/IN. Ne vous en occupez pas, ceci est juste destiné à attendre un tour de faisceau, soit environ 1/70eme de seconde, pour ralentir le jeu, histoire d'avoir le temps de voir quelque chose. La fois prochaine nous verrons plus en détails ce qu'est le champ d'I/O.

La partie lecture du clavier est assez répétitive, et s'appuie sur les deux fonctions 0 et 1 de l'interruption BIOS 16h (interruption clavier).

La fonction 1, appellée en premier, renseigne sur l'état du clavier. Si une touche est en attente, le flag ZF sera mis à zéro, sinon il sera mis à un.

La fonction 0, appellée dans notre programme si un caractère est en attente de traitement, attends l'appuie d'une touche et renvoit dans al le code ASCII du caractère associé à cette touche. Dans notre cas, il n'y aura bien sur pas d'attente car nous n'appelons cette fonction que dans le cas où un carractère est effectivement en attente, ceci afin que le point se déplace continuellement.

Vient le moment de faire avancer le point. Pour cela, on utilise, comme vous l'avez remarqué, deux variables : Xinc et Yinc. Ces deux variables peuvent prendre les valeurs -1, 0 et 1, et sont rajoutées respectivement à X et à Y. Donc, pour allez à droite, il faut Xinc = 1 et Yinc = 0, pour allez en haut il faut Xinc = 0 et Yinc = -1, etc...

Les variables sont placées à la fin du programme (vous devez donc assembler avec l'option /m de TASM), et sont des Words. En toute rigeur, Y pourrait être un byte (0<Y<199), ainsi que Xinc et Yinc. Mais bon, il est plus simple d'uniformiser un peut les choses, et de toute façon avoir des variables tantôt sur un octet et tantôt sur un mot nous aurait sans doute couté quelques instructions supplémentaires, ce qui fait qu'au bout du compte, on gagne à simplifier les choses.

 

MOV AH,4Ch ; INT 21h

Voilà, on a fait le tour du programme. A vous de l'améliorer, en rajoutant par exemple un second joueur (il suffit grosso-modo de doubler le nombre de touches à tester et le nombre de variables), en programmant une vitesse qui augmente avec le temps, en affichant un message plutôt que de retourner sauvagement au DOS à la fin de la partie, etc...

Entrainez vous tant que c'est simple, parceque la prochaine fois il ne sera peut etre pas 2 heures du matin, je n'aurai peut etre pas un amphi de maths dans à peine 6 heures, et je vous concocterais donc certainement quelquechose de plus compliqué.

A la fois prochaine, les codeurs !

Arf :)

( Hep ! Une petite partie de Nibble en vitesse ? Allez-y, personne ne vous regarde ! : {Nibble} )

         ========================================================

         Attention  lorsque vous jouez à Nibble. Jouez aussi loin
         de  l'écran  que  le  permet  le  cordon de racordement.
         Faites  une  pause  de  trente  minutes  toutes les deux
         heures  environ.  Et  n'oubliez  pas  vos études... Zut,
         mon amphi de maths !

         ========================================================