IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Mise en place d'un algorithme MCTS sur un jeu puissance 4


Sujet :

C++

  1. #1
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2024
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2024
    Messages : 5
    Par défaut Mise en place d'un algorithme MCTS sur un jeu puissance 4
    Cher(e)s toutes et tous,

    Je me permets de vous solliciter car je suis actuellement étudiant en Master 2 et je rencontre quelques difficultés dans un travail en C++ que je dois fournir. En pièce jointe, vous trouverez l'énoncé, le code source donné par l'énoncé et enfin mon code source sur lequel je travaille.

    Mon problème principal réside dans l'implémentation d'une partie de Puissance 4 entre un joueur humain (JHP4) et un joueur utilisant l'algorithme JMCTS. Bien que j'aie déjà accompli une partie du travail, cette étape spécifique me pose quelques soucis.

    Pour celles et ceux parmi vous qui seraient disposés à m'aider, je serais ravi d'approfondir davantage mon code. Votre assistance serait d'une grande valeur pour moi, et je suis prêt à fournir tous les détails nécessaires pour faciliter notre échange.

    Je suis conscient de l'importance de rendre cet échange agréable et productif, et je m'engage à faire tous les efforts possibles en ce sens.

    Par ailleurs si vous souhaitez m'aider, n'hésitez pas à me poser le plus de question possible, car je ne veux vraiment pas vous faire perdre du temps.

    Merci infiniment pour votre attention et votre éventuelle assistance.
    Images attachées Images attachées  
    Fichiers attachés Fichiers attachés

  2. #2
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 476
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 476
    Par défaut
    Des variables globales, comment le dire poliment ?
    Tout dans 2 fichiers cpp, c'est cracra.
    Une paire de fichier .h/.cpp par classe et mettre les templates dans les .h.
    Des pointeurs nus, comment qu'on gère l'ownership dans vos contrées (parce que l'énoncé montre que c'est pas des "spécialistes" du C++ vos profs (méthode ça n'existe pas en C++)).
    On frôle le code 3 étoiles.
    Des statiques dans des templates, c'est tendax.
    C'est quoi ce paramètre p d'un constructeur de noeud ? (ça donne ne nombre de fils mais on les copies pas ?)
    Pourquoi crosse{0}, win{0} et pas p{p} ?
    Comme vous n'utilisez pas de smartpointer, votre gestion de la mémoire et lourdingue avec pas mal de bugs en perceptive.
    Ca manque de "const correctness".
    Ca manque de référence par rapport au nombre de pointeur omniprésent.
    Pourquoi tant de pointeurs et si peu de std::vector ?
    Quelle version de la norme C++ est sensé être utilisée ? (utilisation de std::format par exemple)
    Mélange calcul de solutions et message d'affichage, à séparer.
    Attention à l'indentation (surtout avec des if sans parenthèse), utilisez un IDE qui fait de la mise en forme.
    Des débordements d'accès dans la fonction Eval de partout.
    Pourquoi des fois Eval retourne rien et des fois un "Resultat" ?
    "exit" au milieu du code, vraiment ?

    etc...

  3. #3
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2024
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2024
    Messages : 5
    Par défaut prise de connaissance du message
    Bonjour à vous, je vous remercie chaleureusement pour vos listes de remarques. Je vais prendre le temps de répondre à chacune d'entre elles dès que possible.

  4. #4
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 476
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 476
    Par défaut
    listes exhaustives de remarques.
    C'est loin d'être le cas.

  5. #5
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2024
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2024
    Messages : 5
    Par défaut Réponses à certaines remarques
    Bonjour, j'ai essayé de répondre à la plupart des remarques. Je tiens par ailleurs à préciser que mon objectif est de pouvoir organiser une partie entre un être humain et un programme suivant l'algorithme MCTS ( l'une des questions de l'énoncé ). N'hésitez pas à me faire part de nouvelles remarques ou si je dois approfondir mes réponses. Je vous remercie infiniment pour l'attention que vous portez à mon problème et je m'excuse d'avance s'il y a des fautes d'orthographe. Bonne soirée à vous.
    Images attachées Images attachées
    Fichiers attachés Fichiers attachés

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut, et bienvenue sur le forum

    Avant toute chose, et pour ta facilité, et la notre, tu devrais aussi envisager la création d'un compte Git par exemple sur https://github.com/), et d'apprendre comment fonctionne ce systeme dit de "gestion de version concurrentes".

    Cela te permettra, non seulement, de garder "l'historique" des modifications apportées à ton projet, mais aussi, et surtout, de permettre "à tout le monde" d'accéder à ton code sans avoir à copier d'une manière ou d'une autre l'ensemble des fichiers qui composent ton projet.

    Pour l'instant, cela ne pose pas vraiment de problème, car tu n'as que deux fichiers, mais, dés que ton projet commencera à prendre de l'ampleur, tu te rendras compte que ca devient rapidement ingérable d'avoir à transmettre manuellement tous les fichiers

    Ceci étant dit, j'ai quelques remarques à faire sur le code...

    1- N'inclut dans ton fichier d'en-tête que le stricte minimum :
    Tu as inclus dans ton fichiers d'en-tête toute une liste de fichiers issus de la bibliothèque standard. C'est très bien d'utiliser cette bibliothèque (cela t'évite d'essayer de recréer des fonctionnalités comme std::string, par exemple), mais le système de fonctionnement de la directive #include fait que tu vas perdre énormément de temps à traiter ton fichier d'en-tête, car chacun de ces fichiers va devoir être "copié" (de manière récursive, car ils incluent eux-même d'autres fichiers, qui incluent eux aussi des fichiers qui ...) dans ton fichier d'en-tête, et c'est l'ensemble du contenu de tous les fichiers qui ont été inclus, de manière directe ou indirecte, qui devra être traité par le compilateur.

    Pour te donner une petite idée de ce que cela donne, dis toi que le simple fait d'inclure le fichier <iostream> (tout seul) va résulter en un fichier -- une fois l'inclusion récursive de tout ce qu'il contient effectuée -- de près de ... 12 000 lignes, même si ton code de départ est aussi simple que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    include <iostream>
    int main(){
        std::cout<<"hello world\n";
        return 0;
    }
    Encore une fois, ce n'est pas catastrophique dans le cas présent, car tu n'a qu'un fichier d'en-tête qui n'est inclu que dans un fichier d'implémentation.

    Mais, quand tu te retrouveras avec des milliers de fichiers d'en-tête qui sont inclus (parfois à plusieurs) dans des milliers de fichiers d'implémentation, le simple fait d'inclure des fichiers qui ne sont pas nécessaires dans tes fichiers d'en-tête peut ralentir considérablement ton processus de compilation. Et ca peut finir par devenir un problème...

    L'idée est donc que l'on ne met dans chaque fichier que ce qui est absolument nécessaire, par exemple:

    Dans ton fichier puissance4.hpp, on voit apparaitre
    • std::string et
    • std::vector

    Cela signifie que les deux seuls fichiers que tu doive inclure dans ce fichier sont <string> et <vector>. Tous les autres, s'il sont nécessaires, ne le seront que dans le cadre du fichier puissance4.cpp, et donc, que c'est dans ...puissance4.cpp qu'ils doivent être inclus.

    2- L'implémentation des fonctions template se fait -- typiquement --dans un fichier d'en-tête
    Encore une fois, cela n'a pas beaucoup d'importance dans le cas présent, car tu n'as qu'un seul fichier, mais...

    Il faut te dire que le principe de base d'une fonction ou d'une classe template est de dire au compilateur quelque chose comme
    Je ne sais pas encore (au moment d'écrire le code) quel sera le type des données que je vais manipuler.
    Par contre, je sais comment je vais le faire
    Ce qui aura pour conséquence de demander au compilateur, une fois que le type des données manipulées sera connu, de générer le code binaire qui correspond à ce type de donnée particulier.

    Et, pour cela, il doit avoir accès ... au "modèle" de code qu'on a mis au point. Et pour qu'il puisse y avoir accès à chaque fois qu'il en a besoin -- peut-être dans dix fichiers d'implémentation différents, pour dix types de données différents -- il faut que ce "modèle de code" puisse être inclu (avec la directive préprocesseur #include) dans le fichier d'implémentation.

    Et le seul type de fichier qui puisse être inclu avec cette directive est ... le fichier d'en-tête.

    Bien sur, l'explication est très simplifiée ici, car le compilateur se fout pas mal des extensions qui seront utilisées, et il ne verra aucune objection à trouver un code proche de #include <fichier.cpp>. Mais comme on associe l'extension .cpp à un fichier d'implémentation, et que, pour différentes raisons, il ne faut jamais inclure un fichier d'implémentation, c'est un code que l'on évitera comme la peste.

    Je peux, en cas de besoin, t'expliquer tout cela plus en détail, si tu le souhaite (mais prépare ton thermos de café pour la lecture, si tu me demande de le faire )

    3- Une paire de fichier .hpp + .cpp par classe

    Bacelar te l'a déjà signalé, il faut séparer les différentes fonctonnalités en plusieurs fichiers...

    C'est à dire que, dans l'idéal, tu devrait avoir un fichier d'en-tête (*.hpp) et un fichier d'implémentation (.cpp) par classe qui existe dans ton projet, et donc, que tu devrais avoir des fichier
    enum.hpp qui contiendra l'énumération (pour pouvoir l'inclure seule là où elle est nécessaire)
    • joueur.hpp, qui contiendra la classse joueur<P>, ainsi que l'implémentation des fonctions de cette classe (cf la remarque N°2)
    • p4.hpp et p4.cpp pour la classe P4
    • jhp4.hpp et jhp4.cpp
    • noeud.hpp (noeud est une structure template )
    • jmtc.hpp et jmtc.cpp
    • partie.hpp et partie.cpp
    • main.cpp, car il est toujours bon de placer la fonction main dans un fichier séparé


    ... Et, c'est à ce moment là que l'on commence à se rendre compte qu'il est sans doute effectivement intéressant d'intégrer ce projet dans une système de gestion concurrente, car, copier 11 fichiers à chaque fois, ca va devenir galère

    4- Rendre ton projet "système agnostique"

    Je suis sur que tu as surement compilé ton projet au moins dix fois pour arriver à ce résultat.

    Si tu es très courageux, tu l'as fait directement en ligne de commande, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    g++ puissance4.cpp -I .
    Ou, plus vraisemblablement, tu auras utilisé un EDI (environnement de développement intégré) come VisualStudio, VisualCode ou Code::Blocks (ou d'autres) qui l'aura fait pour toi.

    Il y a donc "quelque part" sur ta machine un fichier qui permet à ton EDI de savoir comment s'organise le projet, de savoir quel(s) fichier(s) doit (doivent) être compilé(s), dans quel ordre, et plein de choses dont on n'a pas forcément conscience au début...

    Si tu commence effectivement à multiplier les fichiers source, comme je viens de te le conseiller, tu te rendra rapidement compte que, si tu n'utilises pas encore un EDI, il va vraiment commencer à être temps de le faire, car tu te retrouves désormais avec quatre fichiers d'implémentations, qui devront chacun être compilés avant que le résultat de cette compilation ne puisse être regroupé en un exécutable unique...

    Et il va rapidement devenir très chiant de devoir réintroduire à chaque fois les différentes instructions afin de générer l'exécutable.

    Seulement, tous les IDE utilisent des systèmes différents pour "décrire" le projet sur lequel on travaille. Tu pourrais donc ajouter le fichier utilisé par ton IDE pour gérer ton projet aux fichiers que tu mets à notre disposition, mais... cela nous obligerait à utiliser le même IDE pour pouvoir compiler ton projet de notre coté.

    Or, je ne veux pas savoir si tu utilise windows ou linux. Et je ne veux pas devoir perdre peut être une demi heure à installler le même IDE que toi pour pouvoir compiler ton projet de mon coté, car je risque de ne devoir utiliser cet IDE que... pour toi...

    C'est d'autant plus vrai que, si cela se trouve, je n'utilise pas le même système d'exploitation que toi et que l'utilisation de ton IDE est moins "naturelle" sur mon système que sur le tien...

    L'idéal est donc de faire en sorte que ton projet (qui commence à prendre de l'ampleur, avec ses onze fichiers source) puisse être compilé avec n'importe quel EDI, et, pour cela, il faut avoir recours à un outil supplémentaire qui permettra de configurer le projet pour l'EDI qui sera utilisé. On pourrait parler de "configurateur de projet".

    L'un des outils qui a de plus en plus le vent en poupe pour ce faire est Cmake ( https://cmake.org/ ), mais d'autres peuvent préférer Xmake (https://xmake.io/#/ ), et il y en a sans doute d'autres

    Bien sur, cela va te demander un peu de temps, histoire d'apprendre comment t'y prendre pour décrire ton projet avec ces outils, mais, au final, entre leur utilisation et celle de git, tu te rendras compte que ta vie de développeur prendra un toute autre tournure

    En conclusion

    Il y a toujours pas mal de problèmes dans ton code.

    Beaucoup de problèmes sont conceptuels. Plus encore sont "systémiques", car ils sont gravés dans le marbre dans une description du travail à effectuer foireuse (ton prof a vraiment fait une analyse horrible du problème ).

    Comme je ne veux pas (tout de suite) entrer en conflit avec ton prof, je me suis intéressé au problèmes "organisationnels" de ton code, à ce qui ne risque pas d'invalider les différentes instructions que tu as reçues.

    Mais il y a encore énormément de choses à dire, dont des choses de pure conception qui ne seront sans doute pas tendre pour ton prof (qui semble vraiment n'avoir pas tout compris au C++).
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2024
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2024
    Messages : 5
    Par défaut connaissance du message
    Bonjour à vous et merci infiniment pour l'attention que vous portez à mon problème. Je travaille sur vos remarques et je reviendrai vers vous dès que possible. Je partagerai également avec vous à quel niveau je me trouve dans mon projet. Merci encore pour votre soutien !

  8. #8
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2024
    Messages
    5
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 24
    Localisation : France, Val d'Oise (Île de France)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2024
    Messages : 5
    Par défaut Réponse partielle
    Tout d'abord, je réponds à chacune des remarques concernant les 2 fichiers les plus aboutis de mon projet (par rapport aux questions de l'énoncé), mais en termes de qualité de code, malheureusement. Voici un lien Github utile :

    [https://github.com/Dydicode95/C-Puis...93a462d40e611)

    Dites-moi si cela vous facilite.

    Voici mes réponses à vos remarques :
    - Il m'est impossible de retirer les lignes 3 à 9 de mon fichier 4inrow.hpp, sinon cela me provoque des erreurs.
    - Les templates ont été déclarés dans le fichier d'en-têtes et implémentés dans le fichier source. Je ne sais pas si c'est quelque chose de mauvais par contre.
    - Je travaille actuellement avec 2 fichiers 4inrow (voir lien Github), mais j'ai également mis à disposition des paires de fichiers.
    - Personnellement, je compile via le terminal et j'utilise Sublime Text pour travailler sur mon code : cependant, vous me conseillez d'utiliser CMake ou Xmake. Après avoir envoyé ce message, je vais apprendre à connaître ces 2 outils et ainsi mieux entreprendre mes projets C++.

    Encore une fois merci beaucoup pour les conseils, je compte les approfondir avec le temps. Mon objectif personnel est de prouver qu'un joueur JMCTS respecte bien l'algorithme MCTS et que la manipulation de ces paramètres (a et le temps) puisse le rendre plus fort ou non (comprendre ce qui pourrait le rendre plus performant). Dans la fonction main du fichier 4inrowOFF.cpp, il y a un duel entre 2 joueurs JMCTS : l'exécution se passe bien, cependant, je doute de la qualité de l'algorithme. N'hésitez pas à me faire un retour, je suis très enthousiaste d'en avoir un de votre part.

  9. #9
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Oui, c'est plus facile, même si tu peux en réalité te contenter de donner un lien vers le "repository" (je n'ai jamais trouvé de terme correct pour traduire celui-ci) du projet, c'est à dire https://github.com/Dydicode95/C-Puissance-4 .
    Maintenant, il faut juste que tu prenne le plis, à chaque fois que tu "valide" une modification de ton code, à chaque fois que tu te dis "bon, ici, j'ai effectué un changement correct, j'ai atteint un point avec mon code qui mérite être pris en compte comme code", de le commiter en y ajoutant un petit message décrivant rapidement ce que tu as fait.

    De cette manière, nous pourrons suivre les modifications que tu apportes à ton code presque "en temps réel": il suffira que tu nous dise "j'ai mis mon code à jour", pour nous signaler qu'il est temps de mettre le repository à jour de notre coté, et on aura -- directement -- accès à l'ensemble des modifications que tu as apportées, ainsi qu'au message qui les décrit ...

    Ca va donc faciliter la vie à tout le monde:

    A toi, car tu n'a qu'à envoyer les fichiers modifiés sur le serveur, car tu peux, en cas de besoin, retrouver une version "d'avant", si tu te rends compte qu'une modification a "tout cassé", et à nous, car, en pas longtemps, on peut savoir exactement ce qui a été fait (on peut voir les différences exactes qu'il y a dans le code entre deux versions particulières d'un fichier bien précis) et on peut récupérer l'ensemble des modifications avec la raison pour laquelle elle a été apportée (au travers des messages que tu auras fournis).

    Je sais que c'est un système assez déroutant à utiliser au début, mais tu ne tardera sans doute pas à te rendre compte que c'est un truc absolument génial, surtout si tu décide de travailler "avec quelqu'un d'autre" qui pourrait décider d'apporter des modifications qui risqueraient d'entrer en conflit avec les tiennes (il y a un système de "branches" qui permet à chacun de travailler "chacun sur ses propres fichiers", en attendant de tout mettre en commun )

    Alors, pour ce qui est de l'organisation des fichiers (et des branches sur ton repo git), je vais encore me permettre deux conseils:

    Primo, n'hésites pas à placer les fichiers "qui n'ont rien à voir" les uns avec les autres dans des dossiers différents.

    Par exemple, si j'ai bien compris ce que j'ai vu sur ton repo, les fichiers 4inrow.hpp et 4inrow.cpp correspondent à l'ensemble du projet, lorsque tous les autres fichiers que l'on voit pour l'instant correspondent chacun à une tentative de respecter ce conseil qui t'a été donné d'avoir un fichier par classe...

    Pour ta propre santé mentale (et la notre), il serait peut être bon de séparer ces deux choses "distinctes" en les mettant dans deux dossiers séparés. Ils pourraient, par exemple, s'appeler "SingleFile" (FichierUnique, si tu préfères en Français ) et "SeparatedFile" (FichiersSepares, si tu préfères en francais).

    A vrai dire, le nom de dossier n'a pas énormément d'importance, pour autant que tu garde en tête le fait que "nommer, c'est créer" et que tu essayes en permanence de trouver (c'est vrai pour les noms de fichiers et de dossiers, mais aussi pour les noms de types, de données et de fonctions) LE NOM (ou "un des noms") qui permettra "le mieux" à la personne qui lira ton code (peut-être toi, dans trois mois, ou n'importe qui d'autre sur le forum) de savoir "à quoi ca (le dossier, le fichier, le type de donnée, la donnée ou la fonction) sert", le "but poursuivi lorsque ca a été créé".

    En effet, si, rien qu'en lisant un nom de dossier ou de fichier, on peut déjà se dire "ok, ce dossier ou ce fichier contient <CECI>, et c'est ce qui m'intéresse pour l'instant", on vient déjà de gagner cinq minutes à essayer de comprendre "ce qui fait quoi", et beaucoup de fatigue nerveuse

    Il en va de même pour les type de données (énumération, structures, unions, classes et autres), pour les données (variables, constantes, paramètres) et pour les fonctions (qu'ils s'agisse de fonctions membres d'une classe ou d'une structure ou d'une fonction libre), parce que si tu trouve une donnée nommée "currentState" (ou "etatCourant", si tu préfères en Français), ben tu peux imaginer beaucoup plus facilement à quoi elle est sensée servir que si la donnée s'appelle r6PO. De même si tu trouve une fonction sortResults (ou trieLesResultats).

    En gros, ce que je viens de te conseiller ici, c'est de garder en mémoire que le tout premier destinataire de ton code c'est ... toi, ou n'importe quel humain susceptible de poser les yeux sur ton code. C'est ... à cet humain que ton code doit s'adresser, bien avant d'essayer de s'adresser au compilateur, parce que c'est l'humain qui devra le comprendre suffisemment que pour arriver à le modifier (si besoin) correctement

    Le deuxième gros conseil que je voulais te donner, c'est que, comme tu as maintenant un repository git pour ton projet, n'hésites pas à créer des branches, sans oublier bien sur de les envoyer sur le serveur; surtout si tu veux essayer "une approche tout à fait différente", car toutes les modifications que tu vas apporter sur une branche vont ... rester "cantonnées" sur cette branche. Du moins, jusqu'à ce que tu décides que le résultat est "suffisamment stable" que pour servir de "nouveau point de départ" et donc de "merger" (de regrouper) une branche avec "le code d'origine".

    Tu peux créer autant de branches que tu veux, à partir de n'importe quel point de sauvegarde connu. C'est à dire que tu peux revenir, au besoin, à l'état que tu avais atteint cinq modification envoyées plus tôt et dire que tu veux créer une nouvelle branche à partir de là

    Tu peux passer très facilement d'une branche à l'autre, pour autant que toutes les modifications apportées à la branche que tu t'apprêtes à quitter aient été sauvegardées.

    Enfin, tu peux prendre deux branches totalement différentes -- par exemple, une que tu as créée il y a dix jour et celle sur laquelle tu travaille depuis aujourd'hui -- et dire que tu veux regrouper dans une troisième branche toutes les modifications apportées aux deux branches.

    Et le mieux de tout, à partir du moment où tu as effectivement "commité" (avec la command git commit -m "un message descriptif" ou avec l'équivalent graphique) une modification, quelque soit la branche sur laquelle elle a été effectuée, tu es sur de pouvoir retrouver la modification apportée "n'importe quand" en cas de besoin.

    Voilà la facilité que va t'apporter un système comme git. Voilà pourquoi je t'ai conseillé d'y avoir recours. En plus, bien sur, de nous permettre à nous (autres utilisateurs du forum) de suivre les modifications "en temps réel"
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  10. #10
    Expert confirmé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 476
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 476
    Par défaut
    Mettre les Q&A dans un PDF, c'est pas top pour le suivi des discutions dans les forum de développement.

    J'ai pris la liberté de reprendre le contenu de votre fichier PDF dans ma réponse pour que la communauté puisse suivre "facilement" le débat.

    Des variables globales, comment le dire poliment ?
    1. Variable globales dans mon code ?
    Question 1 : Sachant qu’une variable globale est une variable déclarée en dehors d’une fonction
    ou d’une classe, alors mon fichier projet 5 n'en contient pas
    C'est bien de connaitre la définition, c'est mieux de savoir les voir (et c'est pas compliqué).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    std::random_device rd;
    std::default_random_engine e(rd());
    C'est pas des variables globales ça ???
    Et en plus, leurs initialisations sont liées, on n'est pas loin du "fiasco dans l'ordre d'initialisation des variables statiques" (variables globales étant un des types spécifiques de variables statiques)

    Je vous accorde que c'est un cas qui pourrait, vaguement, dans un programme jouet, justifier l'utilisation d'un Design Pattern Singleton (qui est une globale "déguisée"), mais elle est déguisée et on gère le "fiasco dans l'ordre ...". Mais si vous êtes pas foutu de voir les variables globales, comment vous en prémunir ?

    Tout dans 2 fichiers cpp, c'est cracra.
    Une paire de fichier .h/.cpp par classe et mettre les templates dans les .h.
    2. Pourquoi une paire de fichier .h/.cpp ?
    Question 2 : D’après mon cours, pour pouvoir partager des variables et des fonctions entre ces
    parties, on sépare les déclarations et les définitions. Il est conseillé d’écrire les déclarations dans
    des fichiers hpp que l’on inclut avec `#include`. Selon ChatGPT, « dans le développement en C++,
    il est courant de séparer la déclaration des fonctions, des classes et des variables (interface) de
    leur implémentation réelle. Cette pratique est généralement réalisée en utilisant des fichiers d'en-tête (.hpp ou .h) pour les déclarations et des fichiers source (.cpp) pour les implémentations ».
    En pièce jointe vous aurez les fichiers puissance4.hpp et puissance4.cpp.
    "il est courant de séparer", doux euphémisme.
    En plus, comme vous utilisez des templates, vous êtes "obligé" d'utiliser des ".hpp/.tpp".
    Dans votre projet "puissance4.hpp/.cpp", vous passez entre les gouttes de l'obligation parce que vous n'utilisez votre template que pour un seul type "paramétrique" et que vous collez tout le code d'implémentation dans un seul fichier .cpp (bien dégueu).
    Ajoutez l'utilisation d'un autre type paramétrique, ou structurez un peu plus "proprement" votre code d'implémentation (.cpp) et vous verrez que correctement comprendre à quoi sert la séparation hpp/cpp n'est pas une "option", mais obligatoire.

    Des pointeurs nus, comment qu'on gère l'ownership dans vos contrées (parce que l'énoncé montre que c'est pas des "spécialistes" du C++ vos profs (méthode ça n'existe pas en C++)).
    On frôle le code 3 étoiles.
    3. À quel moment un pointeur est nue ? C’est quoi l’ownership ?
    Question 3 : Un pointeur nu est un pointeur sans gestion automatique de la mémoire. Le terme
    "ownership" fait référence à la responsabilité de gestion de la mémoire allouée dynamiquement
    pour les ressources, telles que la mémoire allouée avec `new` pour créer des objets dynamiques
    ou des tableaux.
    Vous ne trouvez pas que votre code est inutilement très compliqué pour gérer très très mal (pas besoin de regarder dans le détail, vous ne gérez absolument pas les exceptions ( un catch, bin non, c'est pour les faibles !)) la mémoire.
    Donc avant d'utiliser des pointeurs nus comme un goret, connaissez un minimum ses limitations (vos cours me semblent bien light !!!).

    Des statiques dans des templates, c'est tendax.
    4. Quelles relation doit avoir lieu entre des static et des templates ?
    Question 4 : Étant donné qu’un template est une fonction paramétrée par un ou plusieurs types.
    Il y a effectivement une variable statique à la ligne 252 dans un template d’une structure `noeud`.
    Une variable statique conserve sa valeur entre les appels successifs d'une fonction, contrairement
    aux variables locales ordinaires qui sont détruites à la sortie de la fonction. Malheureusement, je
    ne vois pas ce qui gêne à avoir un `static` dans un template. N’hésitez pas à m’expliquer la raison
    si ça ne vous dérange pas, s'il vous plaît.
    Votre définition "naïve" d'une variable statique n'est que l'un des cas d'implémentation/usage mais je crois que la norme C++ en défini 6 liés au mot clé "static" (variables locales à une fonction libre, globales non exportées, variables de classe, ...).
    Votre définition : "variables locales à une fonction libre", n'est en plus loin d'être le cas le plus répandu.
    Et dans votre code ligne 251 de "projet5.cpp", c'est même pas une "variable locale à une fonction libre", c'est une variable de classe. Sachant qu'une classe template n'est pas une classe, c'est chacune des spécialisations complètes du template qui sera une classe.
    Alors, votre "compt", il doit compter l'ensemble des instances de nœuds de "noeud<PA>" et de "noeud<PB>" ensemble ou chacun son compteur ?
    Je suis même pas sûr que ce cas d'usage soit défini dans la norme, donc potentiellement pas compilable sur tous les compilateurs et potentiellement des implémentations différentes ayant des comportements différents => Tendax !!! (et on parle même pas de l'implémentation même pas thread-safe)
    Les trucs de comptage d'instances "interne" à une classe ; ça toujours été merdique (limité, non portable, toujours très inférieurs à un comptage "externe", et très souvent inutile ou contreproductif).

    C'est quoi ce paramètre p d'un constructeur de noeud ? (ça donne le nombre de fils mais on les copies pas ?)
    Pourquoi crosse{0}, win{0} et pas p{p} ?
    5. C’est quoi le paramètre p d’un constructeur de noeud ( ligne 254 ) ?
    Question 5 : Ça peut être une instance d’une classe ‘P’ (notamment ‘P4’), qui est une classe
    représentant le jeu Puissance 4 respectant l’interface (un attribut `bool j1aletrait`, une méthode
    `unsigned Nbcoups()`, une méthode `Resultat Eval()`, une méthode `void EffectuerCoup(unsigned
    i)`, une méthode `std::string ToString()`) il me semble. Dans mon cours, une méthode est une
    fonction déclarée dans une classe qui peut manipuler les attributs comme des variables globales.
    (j'ai mis les 2 lignes de ma réponse parce qu'elles étaient liées dans ma tête)
    Généralement, le type paramétré d'un template, c'est T et pas P, car c'est un type générique qui peut prendre n'importe quel type possible.
    P, ça ressemble aux types que vous voulez utiliser, mais le template est là pour tous les types possibles.
    Si des "P" vont apparaître dans les messages d'erreurs des templates, qui sont déjà passablement illisibles, vous allez tout mélanger.
    Si vous voulez "renforcer" les contraintes sémantiques sur le type du template, utilisez les concepts :
    https://en.cppreference.com/w/cpp/language/constraints
    On initialise avec des accolades {}, pas avec des parenthèses (), si ça passe pas avec des {}, c'est que vous avez merdé un truc au niveau des constructeurs de "P".
    Votre utilisation du paramètre "p" de votre constructeur est "bizarre", car vous vous en servez pour paramétrer la structure de votre objet, via le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
      nbfils = p.Nbcoups();
      fils = new noeud<P>*[nbfils];
    Par "structure" : un truc qui ne change pas au cours de la vie de l'instance mais aussi qui doit être identique pour toutes les instances d'une classe.
    C'est un truc qu'on fait normalement au niveau de la classe, avec le type paramétré du template, ici "P" et pas avec un argument "p" du constructeur.
    Peut-être que je me plante sur votre intention de variabilité de "fils" mais votre code est piégeux avec l'utilisation d'un getter et d'un setter de même nom : Nbcoups.
    Vous devriez avoir une fonction "get_Nbcoups" et une classe "set_Nbcoups" et pas vous servir du "Koenig lookup" pour utiliser des fonctions membres aux mêmes noms mais à la sémantique différente.

    Comme vous n'utilisez pas de smartpointer, votre gestion de la mémoire et lourdingue avec pas mal de bugs en perceptive.
    7. C’est quoi un smartpointer ?
    Question 7 : Un smart pointer (ou pointeur intelligent) est un objet C++ qui agit comme un
    pointeur mais fournit également une gestion automatique de la mémoire. Cependant, je n’ai pas
    vu dans mon cours l’utilisation de tels pointeurs : `std::unique_ptr`, `std::shared_ptr`,
    `std::weak_ptr` et `std::unique_ptr`.
    Ne vous présenter que les pointeurs nus, sans en donner leurs limitations, et les avantages des smart-pointeurs sur ces antiquités, c'est une bien grosse connerie de votre prof.

    Ca manque de référence par rapport au nombre de pointeur omniprésent.
    8. Pourquoi mettre bcp de pointeur ? Alors que référence ferai l’affaire.
    Question 8 : Une référence est un alias pour des variables existantes et un pointeur est une
    variable qui contient l'adresse mémoire d'une autre variable.
    Différence entre référence et pointeur
    - Les références sont des alias pour des variables existantes, tandis que les pointeurs
    contiennent l'adresse mémoire d'une variable.
    - Une référence doit être initialisée lors de sa déclaration et ne peut pas être nulle, tandis qu'un
    pointeur peut être initialisé à `nullptr`.
    - Une fois initialisée, une référence ne peut pas être réassignée pour faire référence à une autre
    variable, alors qu'un pointeur peut être réassigné pour pointer vers différentes adresses
    mémoire.
    - Les références facilitent la lecture du code et sont souvent utilisées pour passer des
    paramètres à des fonctions sans copie, tandis que les pointeurs offrent plus de flexibilité et
    sont utilisés pour la gestion dynamique de la mémoire.
    N’hésitez pas à me dire à quelle moment je pourrai mettre des références au lieu de pointeurs
    ( j’ai un peu de mal on va dire ) s’il vous plait ? Si vous estimez que je dois faire des recherches
    alors je m’adapterai.
    Simplement en n'utilisant des références partout où c'est possible et ne gérer jamais la mémoire "dynamiquement". Les smart-pointeurs quand les références ne peuvent pas fonctionner.
    L'utilisation des références, c'est la règle, les pointeurs, c'est l'exception.

    Pourquoi tant de pointeurs et si peu de std::vector ?
    9. Pourquoi tant de pointeurs et si peu de std::vector ?
    Question 9 : La plupart des pointeurs ont été introduits par mon professeur, cependant je ne suis
    pas contre l’idée de modifier cela. Peut-être que j’ai le devoir d’ajuster cette partie de code.
    Concernant les vecteurs, j’en ai créé pour représenter la grille de jeu et il y en a peu effectivement.
    C'est bien ce que je reproche le plus à votre prof.. Mais peut-être que vous n'avez pas été attentifs à ces explications et qu'une refonte de ce code "jouet" est implicitement demandé par celui-ci.
    Mais quand vous citer votre cours, on a l'impression de voir une superposition d'un vieux prof qui n'a pas tourné la page du "C with class" des années 1980 et d'un prof plus jeune formé par le premier qui n'a pas non plus évolué car le C++, c'est pas son langage de prédilection.

    Quelle version de la norme C++ est sensé être utilisée ? (utilisation de std::format par exemple)
    10. Quelle version de la norme C++ est sensé être utilisée ? Pour cette question, je n’ai de
    réponse concrète à vous donner, mise à part que je compile le fichier cpp de la manière
    suivante : « g++ -std=c++11 projet5.cpp -o prog1 »
    Question 10 : Pour cette question, je n’ai de réponse concrète à vous donner, mise à part que je
    compile le fichier cpp de la manière suivante : « g++ -std=c++11 projet5.cpp -o prog1 »
    "-std=c++11", c'est du C++11, donc vieux de 13 ans (il a eu de puis, le C++ 14, le C++17, le C++20)
    Réponse ChatGPT :
    Depuis la norme C++11, plusieurs nouvelles normes du langage C++ ont été publiées :

    C++14 : Cette norme a apporté des améliorations et des clarifications à C++11. Elle a introduit des fonctionnalités comme les variables de type auto, les expressions constexpr étendues, les fonctions lambda généralisées, etc.
    C++17 : Publiée en décembre 2017 sous le nom de ISO/CEI 14882:2017. Elle a introduit des fonctionnalités comme l’inférence de type automatique pour les variables non initialisées, les espaces de noms en ligne, les attributs de dépréciation, et bien d’autres.
    C++20 : C’est la plus grande mise à jour du langage depuis C++11. Elle a introduit des fonctionnalités majeures comme les modules et les coroutines. Les modules offrent une nouvelle alternative aux fichiers d’en-tête et permettent d’isoler les effets des macros. Les coroutines sont des fonctions qui peuvent suspendre et reprendre leur exécution sans modifier leur état. D’autres nouveautés incluent une bibliothèque de synchronisation, des améliorations dans le traitement du temps de compilation, des macros de test de fonctionnalités et de nouveaux algorithmes de télémétrie.
    Il est à noter que la norme C++ est mise à jour tous les trois ans2. La prochaine version majeure, C++23, est actuellement en cours de développement.

    Donc 13 ans en informatique, comment dire ...

    Mélange calcul de solutions et message d'affichage, à séparer.
    11. Comment corriger « Mélange calcul de solutions et message d'affichage, à séparer. » ?
    Question 11 : Pouvez-vous indiquer les lignes où ça se passe, s'il vous plaît ? Je ne comprends
    pas le fait de séparer, excusez-moi. Si vous pouvez prendre un cas d’exemple, s'il vous plaît. Qui
    dit affichage dit « `std::cout` », on en trouve 14 dans mon code : lignes 124, 125, 132, 133, 211,
    406, 412, 415, 420, 423, 433, 435, 437, où les 8 derniers font partie de `Commencer()` (ligne 403
    qui est une méthode classe définie par mon professeur).
    A chaque utilisation de cout, vous avez de très grosses chances de faire appel à l'OS et de vous prendre des centaines de milliers de cycle pour faire un truc qui n'a rien à voir avec votre algorithme. Quel est la pertinence de vos mesures de temps dans ce cadre ? Aucune.
    Votre prof a pris la "peine" de "garder" l'appel de "cout" avec un champ booléen "affichage" pour n'afficher vraisemblablement que pour du débugging. Pas votre code.
    (C'est quoi cette indentation de merde dans le code de "votre" prof. ?)
    Il doit avoir des scrupules à utiliser des MACRO, mais c'est un cas où leurs usages est très répandus.

    Pourquoi des fois Eval retourne rien et des fois un "Resultat" ?
    12. Pourquoi des fois Eval retourne rien et des fois un "Resultat" ?
    Question 12 : À quel moment ‘Eval()’ ne renvoie rien, dites-moi ? Dans mon code, cette fonction
    est définie ligne 155 et retourne un élément de type ‘Resultat'.
    ligne 233 de codesource.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    void Eval(Resultat r){ eval = r;};
    ligne 240 de codesource.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Resultat Eval() const { return eval;}
    Mais je pense que c'est votre manière un peu foireuse d'implémenter une paire de getter/setter.
    cf. Nbcoups de la question 5.


    "exit" au milieu du code, vraiment ?
    13. "exit" au milieu du code, vraiment ?
    Question 13 : Il me paraît logique qu’il y ait une fin de jeu lorsque la grille est pleine sans aucun
    alignement (partie nulle) ou bien si il y a un gagnant. Les « exit » apparaissent lignes 126 et 134.
    Ok, et votre gestion "dynamique" de la mémoire, faite à la main, comme un bourrin, elle fait quoi à ce moment-là, à part des fuites de mémoire de la taille des chutes du Niagara ?
    Merci aux OS modernes qui peuvent facilement récupérer ces conneries, tant que vous n'utilisez pas des schémas mémoire plus complexes (tas multiples, mémoire partagée, etc...)
    La seule sortie "sûr", c'est la sortie par le main.
    Et si vous voulez afficher des choses autrement que pour du débugging, c'est où que vous allez les afficher ?
    Donc on dégage tous ces "exit" à la con.

Discussions similaires

  1. Réponses: 6
    Dernier message: 25/08/2014, 15h53
  2. Réponses: 1
    Dernier message: 30/03/2010, 22h03
  3. Mise en place d'un algorithme de color picking
    Par GLDavid dans le forum OpenGL
    Réponses: 46
    Dernier message: 26/08/2008, 16h25
  4. Réponses: 4
    Dernier message: 07/01/2006, 22h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo