Beacoup de programmeurs qui débutent en
TI-BASIC 83+ (et en programmation en général) me demandent
comment faire un jeu de serpent.
Certe ce jeu est très simple à jouer, mais en y regardant de
plus près sa programmation n'est pas toujours évidente lorsque
l'on débute.
Cet exemple est assez complexe pour voir pas mal d'aspects de la programmation
et en est ainsi ludique.
Nous ne dévelloperont ici que l'essentiel, c'est à dire le
moteur. Celui-ci sera basic : à vous de l'adapter, de créer vos
propres modes et de faire une interface digne de ce nom...
1/ Initialisations :
- Començons par le commencement : réfléchissons à l'utilisation des variables.
Nous prendrons X et Y pour la position actuelle du serpent.
Nous prendrons ensuite H et V pour l'incrémentation horizontale et verticale
(expliqué un peu plus tard).
Les variables I et J stoqueront les coordonnées de la pomme qui doit être mangée.
Enfin, K stoquera la touche appuyée.
- Pour plus de simplicité, le jeu se joura sur l'écran graphique (et en plus
c'est plus beau pour un jeu) pour pouvoir bénéficier de pxl-Test().
En effet, sur écran texte il faudrai utiliser une matrice...
Donc, configurons l'écran graphique :
Nous prendrons 0->Xmin:94->Xmax:0->Ymin:62->Ymax.
Il s'agit d'une configuration que je prend toujours. Elle est très pratique
car un point sur le graph correspond à un pixel sur l'écran.
Rapellons que l'origine (0,0) de la notation en points se situe en bas à gauche
avec cette configuration graphique. La notation en pixel a toujours une origine
(0,0) en haut à gauche de l'écran.
Ainsi, avec cette configuration graphique les X en points ou en pixels sont
les même. Par contre, pour traduire une ordonnée Y, il suffit de faire une seule
soustraction 62-Y, et ce dans les deux sens.
Line() demande une notation en
points et pxl-Test() une notation
en pixels : prennez toujours l'habitude d'utiliser cette configuration. Une
autre configuration possible serait 0->Xmin:94->Xmax:-62->Ymin:0->Ymax.
Dans ce cas la transformation en Y se fait juste par -Y. C'est plus simple mais on travaille
avec des entiers négatifs pour Y : à vous de faire le choix que vous convenez
le meilleur. Nous prendrons la première configuration dans ce dossier.
- L'écran sera effacé avec ClrDraw
et nous dessineront le rectangle de jeu avec quatres lignes : Line(1,0,1,61):Line(1,61,94,61):Line(94,61,94,0):Line(94,0,1,0).
Au préalable, nous utiliseront AxesOff
pour ne pas voir... les axes !
- Il faudra maintenant initialiser les variables.
Arbitrairement, nous feront débuter le serpent dans la position 47->X et 31->Y : le milieu de l'écran.
Il se dirigera vers le haut. Comme nous le verrons en 3 cela correspond à 0->H
et -1->V.
- I et J recevront une valeur aléatoire entre 2 et 60 pour I et entre 2 et 93
pour J. Ces limites sont imposées par le rectangle du jeu dessiné
ci-avant. Puis nous affichons cette pomme avec Pxl-On().
>> Voici donc se que la partie initialisation donne :
0->Xmin:94->Xmax
0->Ymin:62->Ymax
AxesOff
ClrDraw
Line(1,0,1,61
Line(1,61,94,61
Line(94,61,94,0
Line(94,0,1,0
47->X:31->Y
0->H:-1->V
randInt(2,93->I
randInt(2,60->J
Pxl-On(J,I
2/ Le squelette du moteur :
- Ce jeu ne devant pas finir (dans le meilleur des cas, celui où vous ne vous
êtes pas encore planté). Comme la grande majorité des jeux cela nécessite une
boucle infinie.
Vu que l'on apprend ici, autant apprendre les meilleures choses et parir sur
de bonnes bases : l'une des plus grande règle de programmation est de n'utiliser
de labels qu'en cas d'extrème nécéssités (sinon ils sont source de trop grandes
erreurs). Ainsi, notre boucle sera faite avec une boucle Repeat qui offre aussi l'avantage d'être
plus rapide que Goto (qui, rappelons-le parcours tous
le programme à la recherche du label, ce qui fait perdre du temps, surtout dans
ce genre de jeu qui demande le plus de rapidité possible). Repeat
veut dire jusqu'à ce que la condition soit égale à 1. En faissant Repeat 0, la condition n'est jamais vraie
donc la boucle infinie. Le moteur du jeu sera donc entièrement contenu dans
cette boucle.
3/ Déplacer le serpent :
- C'est encore assez simple. La première chose à faire est de stoquer la touche
pressée dans K avec getKey->K.
- Ensuite, voici comment utiliser les variables H et V :
If K=24:-1->H
If K=26:1->H
If K=24 Or K=26:0->V
If K=25:-1->V
If K=34:1->V
If K=25 Or K=34:0->H
Ok ! Comment cela fonctionne ?
C'est simple : si on va vers la droite, K=26, H vaudra alors 1 et V=0 :: on
ce déplace horizontalement de 1 (positivement) et on ne se déplace
pas en vertical. Idem pr les trois autres directions...
Avec ça le serpent ne se déplace toujours pas mais on sait dans
quelle direction il irra. Note : si aucune touche n'est appuyée, H eet
V garderont leur valeurs respectives et on continuera d'avancer.
Passons à l'utilisation que l'on va en faire :
X+H->X
Y+V->Y
ET C'EST TOUT !!! C la magie des optimisations :-)
Pour explication, si H vaux 1 alors X augmentera et s'il vo -1 il baissera,
et le serpent se déplacera. C tout con !
- Maintenant qu'on a la nouvelle position du serpent, on l'affiche avec Pxl-On().
- Pour savoir si on se mange la queue ou si on rentre dans un mur, on faira
juste un pxl-Test().
Pour optimiser le tout, on placera cette commande dans le test de Repeat.
Ce qui donne Repeat pxl-Test(Y,X).
Comme cela, la boucle est exécutée, le test est fait et s'il est
vrai c'est que l'on a perdu : la boucle est alors interompue et on continue
le programme avec l'annonce de la terrible nouvelle au joueur. Si la condition
est fausse, la boucle continue alors.
- Seulement, le Pxl-On() allumera
le pixel et ensuite la boucle le testera. Dans ce cas on perdra toujours. On
affichera alors le pixel AVANT de calculer la nouvelle position.
>> Récapitulons la boucle de jeu. Insérez-la dans un nouveau
programme en n'oubliant pas de mettre d'abord la partie initialisations : vous
pourrez alors vous déplacer !
Repeat pxl-Test(Y,X
Pxl-On(Y,X
getKet->K
If K=24:-1->H
If K=26:1->H
If K=24 or K=26:0->V
If K=25:-1->V
If K=34:1->V
If K=25 or K=34:0->H
X+H->X
Y+V->Y
End
4/ Faire "avancer" le serpent :
Voilà : on peut se déplacer. Mais seulement, il va falloir effacer
les pixels au fur et à mesure que l'on en affiche d'autres, pour faire
croire qu'il avance vraiment.
- Pour cela, nous allons avoir besoin de stoquer les coordonnées du serpent
pour pouvoir ensuite effacer les pixels au bon endrois.
Comme le serpent va grandir de plus en plus il va y avoir de plus en plus de
coordonnées à stoquer. Elles seront donc stoquées dans
des listes. Deux listes pour être plus précis, car il y a deux
variables par coordonnées (X et Y).
Nous prendrons donc L1
et L2. La longueur
de ces listes sera stoquée dans L.
- Voyons comment cela fonctionera :
Il va nous falloir une autre variable, pour savoir à quelle position
de la liste stoquer les coordonnées courantes. Nous prendrons P.
On incrémentera P pour trouver les nouvelles coordonnées qui se
trouveront dans L1(P)
et L2(P) (ces coordonnées
ont été stoquées précédemment comme décrit
un peu plus bas).
On effacera ce pixel, et on remplacera les valeurs dans la liste (vu qu'on vient
d'effacer le pixel ces cases sont libres) par les nouvelles coordonées
(celles en cours, qui serviront plus tard à effacer ce pixel comme décrit
un peu plus haut), puis on affichera ce pixel. Il faut aussi prévoir
que P sorte de la liste (vu qu'on incrémente toujours). Dans ce cas,
P reviendra tout simplement à 1 et ainsi de suites...
>> Nous allons donc rajouter du code dans la partie initialisations :
{X->L1:{Y->L2
1->L:1->P
- Explications : notre serpent nait avec une queue de 1 pixel.
Les listes ont donc une seule cas chacune mais doivent aussi contenir d'office
le pixel de départ pour qu'il puisse être effacé correctement.
On y met donc X et Y, initialisées plus haut.
Ensuite, L contient 1 vu que les dimentions des listes sont de 1. La position
P est bien entendu 1.
- Maintenant, voyons comment faire dans la boucle de jeu : on a dit qu'on incrémentait
P : P+1->P. Ensuite, il faut
gérer le cas où P sorte des listes. Ceci est fait simplement :
If P>L:1->P. Et on continue
de pzarcourir toutes les cases des listes... Rappelons que L contient la dimension
de ces listes. Ensuite, on efface le pixel à la queue du serpent avec
Pxl-Off(L2(P),L1(P)).
Pour ne pas donner l'impression que l'on efface un pixel puis que l'n en affiche
un autre, on va tout de suite après afficher le pixel à la tête
du serpent : l'animation sera ainsi fluide et on aura vraiment l'impression
que c'est le serpent qui bouge (dans le cas contraire, si on place beaucoup
d'instructions entre leffacement du dernier pixel et l'allumage du premier,
le joueur aurra l'impression que le serpent se rétrécit puis peu
de temps après se régrandit). Ne pas oublier de stoquer les coordonnées
actuelles dans les listes à la position P : X->L1(P:Y->L2(P.
>> Voici donc un résumé de ce qu'il faut introduire dans
la boucle de jeu à la place de Pxl-On(Y,X
:
P+1->P
If P>L:1->P
Pxl-Off(L2(P),L1(P
Pxl-On(Y,X
X->L1(P:Y->L2(P
5/ Gestion des pommes et faire grandir le serpent :
Maintenant il faut gréer le cas où le serpent mange une pomme.
Cela se traduit par le fait que X=I et Y=J.
Comme il faudra exécuter plusieurs instructions, nous engloberont la
condition par un If:Then:End.
Que faut-il faire lorsque l'on mange une pomme ?
- D'abord le serpent va grandir (de 1 pixel dans notre jeu pour ne pas compliquer
les choses), donc les listes devront être plus grandes de une case. L
augmentera de 1, nous stoqueront se résultat dans la dimention de L1
et de L2.
- Ensuite il faudra aficher une nouvelle pomme : ce sera fait de la même
manière que lors des initialisations puis on l'affichera également.
Et c'est tout !
- Mais vous vous appercevez que lorsque l'on essaye de jouer, le programme s'arrête
quand vous touchez une pomme.
Pourquoi ? Cela vient de la condition d'arrêt de la boucle Repeat
: la pomme étant un pixel allumé (au même titre que le queue
ou les murs), le jeu croit que vous vous plantez. Nous allons la modifier pour
qu'elle ne s'arrête plus dans ce cas. C'est à dore dans le cas
où X=I et Y=J. La nouvelle condition sera :
Repeat pxl-Test(Y,X) and not(X=I and Y=J
- Ainsi not(X=I and Y=J)
renverra toujours 1 (à cause du not())
dans le cas où on ne mange pas la pomme. Ainsi, tout dépendra
du Pxl-Test(). S'il renvoi 1
(donc on touche un pixel, qui n'est pas la pomme dans ce cas) alors 1
and 1 donnera 1 : la boucle sera arrêtée. Si le Pxl-Test()
renvoi 0, 0 and 1 donnera toujours
0 : la boucle se poursuivra. Au cas où not(X=I
and Y=J) renverrai 0, cela voudrais dire que l'on est sur la pomme. qqc
and 0 vaut toujours 0 et même si Pxl-Test() renvoi 1 (ce qui sera
toujours vrai dans ce cas), la boucle n'en tiendra pas compte et continuera.
>> Résumons donc ce qu'il faut rajouter juste après le code
qui fait avancer le serpent :
If X=I and Y=J
Then
L-1->L
Ans->dim(L1
Ans->dim(L2
randInt(2,93->I
randInt(2,60->J
Pxl-On(J,I
End
6/ Conclusion :
- Vous remarquerez quece jeu est vraiment basic et que personne n'aurait envi
d'y jouer.
Je vous ai juste donné l'essentiel pour faire un jeu de serpent : le
moteur le plus basic possible, le plus petit et très rapide (pas le plus
rapide car les optimisations nécessaires auraient beaucoup compliqué
les choses pour un gain mineur de vitesse et que ce n'est pas le sujet de ce
dossier).
>> Récapitulons une dernière fois le résultat que
cela doit donner :
0->Xmin:94->Xmax
0->Ymin:62->Ymax
AxesOff
ClrDraw
Line(1,0,1,61
Line(1,61,94,61
Line(94,61,94,0
Line(94,0,1,0
47->X:31->Y
0->H:-1->V
randInt(2,93->I
randInt(2,60->J
Pxl-On(J,I
{X->L1:{Y->L2
1->L:1->P
Repeat pxl-Test(Y,X) and not(X=I and Y=J
P+1->P
If P>L:1->P
Pxl-Off(L2(P),L1(P
Pxl-On(Y,X
X->L1(P:Y->L2(P
If X=I and Y=J
Then
L-1->L
Ans->dim(L1
Ans->dim(L2
randInt(2,93->I
randInt(2,60->J
Pxl-On(J,I
End
getKet->K
If K=24:-1->H
If K=26:1->H
If K=24 or K=26:0->V
If K=25:-1->V
If K=34:1->V
If K=25 or K=34:0->H
X+H->X
Y+V->Y
End