Tetris SDL

Présentation
C'est mon premier "vrai" projet en C++.
Et c'est vraiment le premier avec la SDL (hors tuto).

Ce programme n'utilise ni son ni image (les formes des pièces sont calculés).
Le jeu se pilote au clavier et l'on peut y jouer avec une manette (uniquement les boutons et les Hat)

Le code n'est pas parfait.
L'héritage de CTetris par F_Jeu n'est pas forcément judicieuse mais je pensais rajouter des procédures virtuelles pour jouer des sons ou faire clignoter les lignes avec qu'elles disparaissent (ce qui n'est pas le cas).

Pour ce qui est des tableaux à 2 dimensions (l'air de jeu, les pièces, autres), il y en a de 2 sortes :
- des vector de vector
- des pointeur de pointeur
J'imagine bien que le 2e est plus rapide et prend moins de place en mémoire, mais au final je préfère la première solution.
Les pointeur de pointeur qui reste datent du début du code notamment la classe CTetris qui provient d'un code de décembre 2013 lorsque j'ai fait un essai avec QT.

Lorsque je dois faire des recherches dans des tableaux, je fais un boucle for. ce n'est optimisé mais je ne vois pas comment faire autrement (une recherche dichotomique sur un petit tableau me paraît un peu lourd d'autant plus qu'il faudrait trier mes tableaux de structures).

Je ne limite pas le frame rate, un simple SDL_Delay(1) pourrait faire passer l'utilisation processeur de 100% à 0% (ou presque) mais je me dit qu'un frame rate élevé m'indique un code optimisé.
C'est pour obtenir un chiffre élevé que notamment je stock pas mal de texture en mémoire (pour ne pas avoir à les recalculer à chaque affichage).
A noter que le calcul du frame rate (affichable en appuyant sur F12) n'est pas calculé correctement c'est le nombre de frame affiché durant la seconde précédente (plus ou moins seconde car cela dépend de la boucle de l'affichage). On trouve facilement des algorithmes de calcul du frame rate sur internet (qui ne sont pas plus compliqué que le mien) mais pour l'instant mon calcul me suffit.


Je fais des include inutile dans les classes C* mais c'est parce que je les souhaites réutilisables hors de ce projet et donc autonomes (ce n'est pas toujours le cas).

La suppression des pointeurs de p_Fen_* dans le code de la classe Application ne me paraît pas idéale mais là encore, je ne voyais pas d'autre solution.

C'est la classe F_Jeu_Selection qui initialise la liste des pièces. C'est parce que j'ai (avais) dans l'idée de rendre les listes de pièce paramétrable (permettre à l'utilisateur de rajouter des pièces et de choisir alors son jeu de pièce).

Mes fichiers *.cpp sont un peu brouillon. Je devrais rajouter plus de commentaire pour bien séparer chaque fonction, éventuellement y répéter la documentation Doxygen.

Il doit y avoir beaucoup de code qui ressemble plus à du C que du C++.

J'ai beaucoup de structures. Est-ce correct ? Je me suis dit qu'il ne serait pas correcte de faire des classes là où je n'ai besoin que de membres.
Sur la liste des choix des résolutions, la SDL me renvoie 2 fois chaque résolution. J'ignore pourquoi.

J'ai appris beaucoup durant la réalisation de ce programme. Ce serait à refaire, la programmation serait un peu différente.

Bref, voici sous vos yeux mon premier programme SDL complet mais fortement améliorable que ce soit au niveau du code ou au niveau des fonctions de l'application.
N'hésitez pas à commenter ce programme et me dire ce que vous en pensez.
Téléchargement
Compatibilité
Windows
1  0 
Téléchargé 66 fois Voir les 3 commentaires
Détails
Catégories : Jeux SDL
Avatar de Erwan Allain
Membre confirmé
Voir tous les téléchargements de l'auteur
Licence : Libre
Date de mise en ligne : 11 novembre 2014




Avatar de LittleWhite LittleWhite - Responsable 2D/3D/Jeux https://www.developpez.com
le 11/11/2014 à 11:45
Bonjour,

Première vérification classique : activer tous les avertissements du compilateur (-Wall -Wextra) (cela est possible dans Code::Blocks aussi ).
Et on trouve :
Application.cpp: In constructor ‘Application::Application()’:
Application.cpp:32:43: attention : deleting object of polymorphic class type ‘F_Configuration’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete(p_Fen_Configuration);
^
Application.cpp:49:43: attention : deleting object of polymorphic class type ‘F_Jeu_Selection’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete(p_Fen_Jeu_Selection);
^
Application.cpp:53:33: attention : deleting object of polymorphic class type ‘F_Jeu’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete(p_Fen_Jeu);
^
F_ConfigAffichage.cpp:327:6: attention : unused parameter ‘io_p_n_EtatApplication’ [-Wunused-parameter]
void F_ConfigAffichage::ValideOptionEnCours(int *io_p_n_EtatApplication) {
^
F_Jeu.cpp:453:6: attention : unused parameter ‘i_t_n_ListeLigneSuppr’ [-Wunused-parameter]
void F_Jeu::v_SupprimeLigne(std::vector<int> i_t_n_ListeLigneSuppr) {
Donc, quelques petits trucs à corriger, tout de même.

Les choses à améliorer. Je pense que vous en avez déjà cité une partie :
Mes fichiers *.cpp sont un peu brouillon. Je devrais rajouter plus de commentaire pour bien séparer chaque fonction, éventuellement y répéter la documentation Doxygen.
Il doit y avoir beaucoup de code qui ressemble plus à du C que du C++.
Autre chose à améliorer :
&#65533;chec de l'initialisation de la SDL (SDL not built with haptic (force feedback) support)
Erreur de segmentation
C'est violent

J'imagine donc que vous gérez très mal les cas d'erreurs.

Code : Sélectionner tout
const std::string CST_ZE_TTF = "police\\kremlin.ttf";
Fonctionne tout aussi bien en écrivant :
Code : Sélectionner tout
const std::string CST_ZE_TTF = "police/kremlin.ttf";
et cela, même sous Windows, malgré ce que la majorité des gens peuvent croire.

En C++11, vous pouvez éviter tous les pointeurs en utilisant des pointeurs intelligents.
D'ailleurs pour l'histoire de vos tableaux 2D, le mieux, ce sont les std::vector<std::vector> ou encore, les std::array lorsque l'on est en statique.

Pour moi, pas besoin de mettre "C" ou "str" en début de classe ou structure. Pareil pour les autres types. En réalité, je déteste la notation hongroise. Il faudrait la garder uniquement, pour les pointeurs (avec un p devant le nom de la variable) et si nécessaire les variables membres, c'est tout. Le reste, c'est inutile et cela n'aide pas.
Conceptuellement, F_Jeu n'a aucune raison d'hériter de CTetris.

J'ai du mal à voir pourquoi une classe "ConfigAffichage" fait l'affichage, alors que c'est une classe Config
Code : Sélectionner tout
using namespace std;
Cela devrait être interdit dans un .h -> http://cpp.developpez.com/faq/cpp/?p...sing-namespace

Il y a des constantes qui se trimbalent un peu partout (certaines dans le fichier const, d'autres, dans d'autres fichiers de classe). Je ne suis pas pour toutes les regroupper au même endroit, enfin, pas toujours. Mais les chemins d'accès aux fichiers, si.

Ce genre de documentation est inutile :
Code : Sélectionner tout
1
2
3
 /*!
         *  \brief Constructeur
         *  Constructeur de la classe F_Configuration
Dans le sens, oui, nous voyons que c'est un constructeur pour cette classe.

Voilà pour aujourd'hui. J'attends vos prochaines versions du code
Avatar de Narwe Narwe - Membre confirmé https://www.developpez.com
le 11/11/2014 à 17:20
Bonjour,

Merci de cette réponse.
Je viens de mettre à jour les sources (pas l'installable).

Première vérification classique : activer tous les avertissements du compilateur (-Wall -Wextra) (cela est possible dans Code::Blocks aussi ).
Et on trouve :
Application.cpp: In constructor ‘Application::Application()’:
Application.cpp:32:43: attention : deleting object of polymorphic class type ‘F_Configuration’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete(p_Fen_Configuration);
^
Application.cpp:49:43: attention : deleting object of polymorphic class type ‘F_Jeu_Selection’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete(p_Fen_Jeu_Selection);
^
Application.cpp:53:33: attention : deleting object of polymorphic class type ‘F_Jeu’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
delete(p_Fen_Jeu);
^
F_ConfigAffichage.cpp:327:6: attention : unused parameter ‘io_p_n_EtatApplication’ [-Wunused-parameter]
void F_ConfigAffichage::ValideOptionEnCours(int *io_p_n_EtatApplication) {
^
F_Jeu.cpp:453:6: attention : unused parameter ‘i_t_n_ListeLigneSuppr’ [-Wunused-parameter]
void F_Jeu::v_SupprimeLigne(std::vector<int> i_t_n_ListeLigneSuppr) {
Donc, quelques petits trucs à corriger, tout de même.
Je voyais bien ces warning mais je ne m'en suis pas trop inquiétait. Je ne comprenais pas les messages sur les destructeurs virtuelles et pour les variables non utilisé, il s'agit dans un cas d'une procédure non utilisée et dans l'autre d'une procédure qui n'utilise pas le paramètre passait.
J'ai corrigé le problème de destructeur virtuelle et j'ai mis en commentaire les paramètres non utilisé .

Les choses à améliorer. Je pense que vous en avez déjà cité une partie :
Mes fichiers *.cpp sont un peu brouillon. Je devrais rajouter plus de commentaire pour bien séparer chaque fonction, éventuellement y répéter la documentation Doxygen.
Il doit y avoir beaucoup de code qui ressemble plus à du C que du C++.
Ceci je n'y ait pas touché si ce n'est que j'ai remplacé les fprintf par ces cerr .

Autre chose à améliorer :
&#65533;chec de l'initialisation de la SDL (SDL not built with haptic (force feedback) support)
Erreur de segmentation
C'est violent
J'imagine donc que vous gérez très mal les cas d'erreurs.
Je faisais un SDL_INIT_EVRYTHING. ce qui provoquait l'erreur. lorsqu'il voulait initialiser le module HAPTIC.
J'ai modifié mon SDL_INIT pour n'utiliser que les modules que j'utilise mais la gestion d'erreur est la même. S'il ne peut pas initialiser les 3 modules, il arrête le programme en ayant juste indiqué l'erreur sur la sortie d'erreur standard.
Comment faudrait-il s'y prendre pour mieux gérer les cas d'erreurs car à part indiquer l'erreur et arrêter le programme, je ne sais pas quoi rajouter ?

Code : Sélectionner tout
const std::string CST_ZE_TTF = "police\\kremlin.ttf";
Fonctionne tout aussi bien en écrivant :
Code :
Sélectionner tout
Code : Sélectionner tout
1
2
const std::string CST_ZE_TTF = "police/kremlin.ttf";
et cela, même sous Windows, malgré ce que la majorité des gens peuvent croire.
Je l'ignorais. J'ai fait le changement dans ce sens.

En C++11, vous pouvez éviter tous les pointeurs en utilisant des pointeurs intelligents.
J'ai rapidement lu des articles sur ce sujet mais ce n'est pas encore limpide pour moi. Pour l'instant, je préfère gérer les pointeurs "manuellement".

Pour moi, pas besoin de mettre "C" ou "str" en début de classe ou structure. Pareil pour les autres types. En réalité, je déteste la notation hongroise. Il faudrait la garder uniquement, pour les pointeurs (avec un p devant le nom de la variable) et si nécessaire les variables membres, c'est tout. Le reste, c'est inutile et cela n'aide pas.
Je ne connaissais pas le terme "notation hongroise", merci.
Je pense que l'utiliser ou non dépend de chacun. Personnellement, je trouve que ne pas l'utiliser rend le code plus compliqué. Je suis obligé de retourner à la déclaration des variables pour savoir ce que je manipule et j'ai peur de confondre variable et fonctions. A contrario de votre point de vue, quand je tombe sur du code qui n'utilise pas cette notation, je déteste cela .

Conceptuellement, F_Jeu n'a aucune raison d'hériter de CTetris.
ça permet de différencier le moteur du jeu (CTetris) et l'affichage. Les classes F_* correspondent à des écrans. CTetris et donc indépendant de la SDL.
Mais en écrivant ces mots peut être que je devrais comprendre de votre remarques que ce n'est pas le fait qu'il y ait 2 classes qui pose problème mais l'héritage proprement dit et que j'aurais du déclarer un objet CTetris dans la classe F_Jeu. dans ce cas, je n'ai pas d'argument à opposer à cela. Si ce n'est que j'avais dans l'idée (non mise en pratique) d'avoir une fonction virtuel dans CTetris qui serait donc redéclarée dans F_Jeu pour la suppression de ligne (ce qui est peut-être là aussi une erreur de conception).

J'ai du mal à voir pourquoi une classe "ConfigAffichage" fait l'affichage, alors que c'est une classe Config
F_ConfigAffichage est la fenêtre servant à sélectionner la configuration d'affichage (résolution, plein écran, accélération matérielle).

Code : Sélectionner tout
using namespace std;
Cela devrait être interdit dans un .h -> http://cpp.developpez.com/faq/cpp/?p...sing-namespace
J'ai enlevé les using namespace des .h. Par contre je les ai rajouté dans les .cpp. Est-ce correcte ?
Autre question, le fait de ne pas utilisé cette ligne dans les .h, cela implique qu'à chaque fois qu'un déclare un membre de type de string, on doit le faire avec std::string et non directement string. Je trouve ça un peu dommage mais si je comprend qu'il ne faut pas mettre using namespace std dans un .h

Il y a des constantes qui se trimbalent un peu partout (certaines dans le fichier const, d'autres, dans d'autres fichiers de classe). Je ne suis pas pour toutes les regrouper au même endroit, enfin, pas toujours. Mais les chemins d'accès aux fichiers, si.
J'ai déplacé les constante indiquant des chemins externe dans le fichier const.
Pour ce qui est d'avoir un fichier de constante général pour les éléments externe et les autres constantes placé là où elles sont utiles, j'en prend note et j'essayerais d'appliquer ça.

Ce genre de documentation est inutile
Ah, je me forçais à la mettre pour avoir un belle documentation Doxygen .
Je ne m’embêterais plus à commenter les choses évidentes à l'avenir.

Voilà pour aujourd'hui. J'attends vos prochaines versions du code

Merci ! beaucoup.
Avatar de LittleWhite LittleWhite - Responsable 2D/3D/Jeux https://www.developpez.com
le 06/12/2014 à 16:24
J'ai enlevé les using namespace des .h. Par contre je les ai rajouté dans les .cpp. Est-ce correcte ?
Autre question, le fait de ne pas utilisé cette ligne dans les .h, cela implique qu'à chaque fois qu'un déclare un membre de type de string, on doit le faire avec std::string et non directement string. Je trouve ça un peu dommage mais si je comprend qu'il ne faut pas mettre using namespace std dans un .h
Si on met les using namespace std; dans le CPP où std::string est utilisé, alors, il n'y a pas besoin du std, devant string.

Ah, je me forçais à la mettre pour avoir un belle documentation Doxygen .
Je ne m’embêterais plus à commenter les choses évidentes à l'avenir.
Il faut commenter la logique. Les commentaires, si c'est pour dire la même chose que le code, ils ne servent plus à rien. Une belle documentation, c'est une documentation où on comprend l'implémentation (le code) et comment sont pensées/codées les fonctions sans même voir le code
S'il n'y a rien à dire, ne dite rien

Le fichier StdSDL.h moi, je trouve ça nul
Je préfère géré mes includes fichier par fichier.

Le jeu ne devrait pas se dérouler dans un constructeur. Cela ne semble pas vraiment logique.
Les switch case pour la gestion des écrans me semble barbare. On pourrait trouver mieux en C++, pour gérer la désallocation automatiquement ou presque.

Code : Sélectionner tout
p_tf_Police = TTF_OpenFont(CST_GAMEOVER_TTF.c_str(), 36);
en plein milieu d'un DessinerEcran ? Il faut absolument éviter les allocations/désallocations lors de la boucle principale du jeu (question de performances).

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // On ajoute le carré
    n_IdCouleur = 6;
    st_Piece.n_TaillePiece  = 2;
    st_Piece.t_st_CasePiece = new str_CaseJeu* [2];
    st_Piece.t_st_CasePiece[0] = new str_CaseJeu[2];
    st_Piece.t_st_CasePiece[1] = new str_CaseJeu[2];
    st_Piece.t_st_CasePiece[0][0].b_EstPleine = true;
    st_Piece.t_st_CasePiece[0][1].b_EstPleine = true;
    st_Piece.t_st_CasePiece[1][0].b_EstPleine = true;
    st_Piece.t_st_CasePiece[1][1].b_EstPleine = true;
    st_Piece.t_st_CasePiece[0][0].n_IdCouleur = n_IdCouleur;
    st_Piece.t_st_CasePiece[0][1].n_IdCouleur = n_IdCouleur;
    st_Piece.t_st_CasePiece[1][0].n_IdCouleur = n_IdCouleur;
    st_Piece.t_st_CasePiece[1][1].n_IdCouleur = n_IdCouleur;
    m_p_st_ModeJeu->t_ListePiece.push_back(st_Piece);
Ça peut être factorisable (avec le reste de la série d'initialisation des pièces).

Pour le Menu aussi, je pense (CalculMenu).

Voilà pour le moment. Je pense qu'il y a beaucoup de code superfly, dans le sens, factorisable.

 
Developpez.com décline toute responsabilité quant à l'utilisation des différents éléments téléchargés.