
 Tetris.
 Done by Clément CORDE.
 Contact : c1702@yahoo.com


 Quand j'ai mis mon casse-briques en ligne il y a quelques mois, je disais que j'avais aussi un Tetris, plus simple, car sans moteur de sprites. Eh bien, le voici.

 J'avais écrit ce code car un collègue m'avait demandé de lui faire un petit Tetris pour que son fils puisse voir un peu comment on pouvait faire un jeu vidéo. Voici ma façon de faire, il y en a d'autres. Comme pour l'Arkanoid, sur suggestion d'autres collègues, je mets ça en ligne au cas où ça intéresserait quelqu'un.

 Tout celà date donc un peu, la dernière fois que j'y avais touché, c'était le 21/03/2008. Nous sommes aujourd'hui le 18/02/2010. J'ai juste fait une passe dans le code pour nettoyer des vieux bouts de code commentés inutiles. Tant que j'y étais, j'ai rajouté les checksums sur les high-scores (J'avais rajouté ça sur l'Arkanoid, j'ai fait un copier/coller du code, testé, ok). J'ai aussi rajouté un menu d'options, la pièce "ghost" et les blocs étendus. (J'espère ne pas avoir rajouté trop de bugs !).

 A l'époque où j'ai fait ça, je n'avais pas encore codé mon Arkanoid, et donc pas encore écrit mon petit moteur de sprites. Mais bon, pour un Tetris, vu qu'on affiche que des carrés, on y arrive sans ! (^_^)

 Le principe du Tetris est ultra simple. En fait, le coeur du jeu tient dans une seule routine de quelques lignes : Une routine qui répond oui ou non à la question "est-ce que je peux placer la pièce p avec la rotation a à la position (x,y) ?".
 Dans mon code, c'est la fonction "u32 Check(u32 nPosX, u32 nPosY, u32 nAngle)" dans le fichier "game.c". Je vous laisse juger par vous-mêmes de la terrible complexité de cette routine !

 Avec cette routine fort compliquée (^_^), en gros c'est fini.
 Je m'explique :

 Une pièce est en (x,y) avec un angle a.

 Le joueur veut déplacer la pièce à droite.
 Je peux mettre la pièce en (x+1,y) avec l'angle a ? (= Appel à la fonction 'Check').
 Oui ? x=x+1.

 A gauche ? Pareil.

 Tourner une pièce ? Pareil.

 La pièce doit descendre ? Pareil. Si on ne peut plus descendre, on pose la pièce et on passe à la suivante. Et si on ne peut pas poser la prochaine pièce (qu'on teste toujours avec la même routine 'Check' !), Game Over.

 Tout ça se trouve dans la fonction "void TetrisGame(void)" du fichier "game.c".

 Avec ça et la façon de décrire les pièces ainsi que l'aire de jeu (des tableaux), c'est à peu près tout ce qu'il faut.

 Et tout le reste n'est qu'habillage. (Gestion des niveaux, vitesse de descente... et par la suite l'encapsulation dans les menus, etc...).


> Touches :

- Flèche droite + flèche gauche : Déplacement de la pièce en cours.
- Flèche haut : Rotation de la pièce.
- Flèche bas : Accélération vers le bas.
- Espace : Pose la pièce immédiatement.
- P : Pause.

- G : Toggle ghost (*).
- R : Toggle sens de rotation (*).
(*: Option non sauvegardée sur disque à ce moment là, il faudra repasser par le menu des options).

- F9 :  Switch 1x/2x (défaut).
- F10 : Toggle fullscreen / windowed.


> Son :

 Update du 20/02/2010 :
 Le lendemain, j'ai inclus le moteur de son de l'Arkanoid. Je ne suis pas forcément très content des sons utilisés, mais je n'avais rien de mieux sous la main. (Un son mis à part, ce sont les sons du casse-briques). Si ça vous dit de les changer, il suffit de remplacer les fichiers wav dans le répertoire "sfx". Mais bon, au moins, c'est fait ! (^_^)


> License :

 Je n'ai pas la prétention que ce code soit bon, puissant, ou quoi que ce soit. En revanche, il fonctionne. Je distribue les sources pour que les gens puissent s'en inspirer, peut-être y trouver des astuces, voire réutiliser une partie du code. Dans ce dernier cas, il est bien entendu que celà doit rester dans un cadre non commercial ! Dans le cas contraire, vous êtes priés de me contacter avant.

 Et pour ceux à qui ce code aura été utile, un p'tit greeting quelque part, c'est toujours sympa ! (^_^)



>>> A partir de là, c'est un copier/coller de ce que j'ai déjà écrit dans le LISEZMOI du casse-briques.  J'ai relu vite fait, c'est assez général et ça reste à peu près valable.


> Compilation :

 J'utilise Code::Blocks sous Windows XP (SP3), avec gcc et SDL.

 Je n'ai pas inclus le fichier projet pour la raison suivante : Comme il y a peu de chances que vos répertoires soient les mêmes que les miens, le projet ne se lirait de toutes façons probablement pas.

 Créez un nouveau projet SDL, copiez tous les fichiers sources ainsi que les répertoires "gfx" et "sfx" dans le répertoire du projet, incluez tous les fichiers sources au projet, et ça devrait fonctionner.

 Options de compilation :
 -Wall : Voir tous les warnings.
 -O3 : Optimisation du code pour la vitesse.
 -DNDEBUG (= #define NDEBUG) : En mode release, pour que les asserts ne soient plus compilés.

 Le fichier "includes.h". Oui, je sais, il parait que ça ne se fait pas. Moi je trouve ça pratique parce que ça ne me fait inclure qu'un fichier ".h" dans chaque ".c". L'effet chiant, c'est que quand on touche un ".h", on est bon pour recompiler tout le projet. Mais bon, ledit projet n'est pas énorme. Et puis si vous voulez, rien ne vous emêche de refaire les inclusions vous mêmes ! :)

 Le jeu est codé en C. Pourquoi pas en C++ ? Parce que je voulais faire les choses d'une certaine façon, et que je n'avais pas grand chose à gagner à utiliser du C++ qu'en plus je ne maitrise pas forcément aussi bien. Maintenant si ça vous amuse de tout repasser en C++, avec des classes, des méthodes et tout et tout, rien ne vous en empêche (le code est assez compartimenté, ça ne devrait pas être trop violent).

 Un "Makefile" est inclus pour une compilation sous Linux. J'ai essayé rapidement avec une Debian 5, et ça semblait marcher correctement.


> Code :

 * Nomenclature utilisée :

 Variables numériques précédées de 'n'.
 Pointeurs précédés de 'p'.
 Variables générales précédées de 'g'.
 Puis noms de variables commençant par une majuscule.

 Exemples :
 u32	nMonEntier;
 u32	pMonTableau[NB_ELEMENTS];
 u32	*pPtr;
 u32	**ppPtr;	< pp car pointeur de pointeur.
 u32	gnMonEntierGeneral;
 etc...

 Exceptions : Les indices de boucle. Là c'est plutôt i, j, k, ix, iy...


> Questions :

 Je répondrai par mail (c1702@yahoo.com) autant que possible à d'éventuelles questions, mais veillez à préciser un objet bien clair. La raison est que je reçois pas mal de spam, et qu'évidement, je ne lis pas ce spam avant de l'effacer. Alors si vous m'envoyez un mail, évitez de mettre en objet 'viagra' !


> SDL :

 Le jeu utilise SDL au minimum. Je suppose qu'on doit pouvoir faire plein de trucs super avec SDL, mais ce n'était pas mon but. Je n'avais pas codé quelque chose ressemblant à un jeu depuis longtemps, je voulais donc voir si je n'avais pas tout oublié. Donc en gros, je me sers de SDL pour obtenir un buffer en vidéo et blitter dedans, en 256 couleurs parce que c'est ce que je voulais, que les modes 256 couleurs sont faciles à gérer et ont des palettes. SDL me sert aussi à lire les images au format BMP parce que je n'avais pas envie de faire un loader de BMP (même si ça ne doit pas être bien sorcier). Sinon, tout est fait à la main.


> Pour "timer" une routine :

 Evidement, maintenant on ne peut plus timer une routine au raster (càd changer la couleur 0 eu début de la routine et la remettre à sa valeur après). Au début, je pensais faire ça avec SDL_GetTicks(), en en mettant un avant et un après le bout de code à timer. Manque de bol, les bouts de code à tester sont souvent tellement petits ou tellement rapides que la différence vaut très souvent 0.

 Donc je fais ça avec le bout de code ci dessous (windows only), qui est beacoup plus précis :

>>>
#include <windows.h>

Dans la fonction :

LARGE_INTEGER a,b,c;
QueryPerformanceFrequency(&c);
QueryPerformanceCounter(&a);

[Partie à timer]

QueryPerformanceCounter(&b);
printf("%d %d %d\n", (int)(b.QuadPart - a.QuadPart), (int)a.QuadPart, (int)b.QuadPart);
<<<

 Ca peut servir ! :)


> Frame rate :

 Pour la gestion du frame rate, je me suis inspiré de plusieurs bouts de code trouvés dans des tutoriels sur internet. Le problème, c'est que je récupère les fichiers frame.c et frame.h d'un projet à l'autre, alors je ne me rappelle absolument plus d'où ça provient. Si quelqu'un se reconnait ici, qu'il me contacte, je mettrai une référence vers son site avec plaisir.

 Le principe, c'est qu'en gros, la fonction FrameWait() attend qu'un certain nombre de cycles d'horloge se soient écoulés depuis l'appel précédent avant de rendre la main. Ceci car bien sûr, on ne peut pas avoir accès directement au vblank, surtout en mode fenêtré ! :)

 Evidement, c'est une gestion simpliste et qui suppose qu'on ne va pas perdre de frames. Si ça arrive, ce n'est pas géré.

 Petit truc sur le SDL_Delay(3); dans la boucle : C'est ce qui permet au jeu de rendre la main au système d'exploitation un court instant, et de soulager le processeur. Vous pouvez regarder avec le gestionnaire de tâches de windows l'utilisation du processeur, avec et sans cette ligne. C'est assez parlant.

 Après, si on veut faire autrement, on peut mettre en place un timer sous IRQ qui avance un compteur de frames général qui sera pratique pour savoir combien de frames ont été ratées.


--End of file--
