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

Projets Discussion :

[WE-JV10] LastGo II : Le retour du Neckara


Sujet :

Projets

  1. #1
    Expert éminent sénior
    [WE-JV10] LastGo II : Le retour du Neckara
    Bonjour,

    Cela faisait longtemps que je n'étais pas revenu dans ce forum.
    Je profite de quelques congés pour faire mon WE-JV10 un peu un avance, en commençant aujourd'hui à 18h pour le finir jeudi à 23h59, heure du commit faisant foi.

    Bon, vous avez l'habitude, je vais reprendre à zéro ce satané projet que je n'ai jamais réussi à finir depuis bientôt 9 ans, vous le connaissez déjà Last Dungeon, Last Engine, euh non, Last Dungeon, Last DunGo ? ah oui, le dernier nom en date était LastGo !


    Bon, j'ai quand même quelques excuses, au début, je n'étais encore qu'un petit débutant, j'apprenais la programmation, après j'ai été pris par mes études, mais c'était aussi et surtout la faute de mon clavier bien évidemment.

    Donc maintenant que je suis un expert de la programmation (et surtout des prototypes torchés avec le cul à rendre pour la veille), je ne vais pas rester sur cet échec, et m'attaquer à cette baleine que je pourchasse depuis presque une décennie !


    Donc voilà, je vous re-présente LastGo, un jeu de Go multi-joueur en 3D (enfin presque), avec son éditeur intégré, et plein de trucs trop cool que je n'aurais très certainement pas le temps de faire. Je vous tiendrais régulièrement au courant de l'état du projet, ainsi que de sa progression via un journal de bord que j'éditerais régulièrement.

    Bon, j'ai un peu triché. J'ai commencé à regarder un peu en avance 2-3 trucs techniques pour voir s'ils étaient réalisables, et j'ai nettoyé mon compte github. Et je dois malheureusement vous décevoir, mais mon super site en XSLT n'est désormais plus accessible (j'étais quand même un peu con spécial à l'époque).


    Donc voilà, sur ce tout est dit, j'ai déjà grillé 20 minutes en écrivant ce message...
    Que l'opprobre soit jeté sur moi si je n'arrive pas à finir cette saloperie de jeu d'ici jeudi !



    Projets précédents:

    Vidéo LastGo : youtube.com/watch?v=wiU3swedl1U&list=PLAA882DA0CF063F47&index=2

    Projet LastDungeon : https://www.developpez.net/forums/d1...-last-dungeon/
    Projet LastGo: https://www.developpez.net/forums/d1...irees-neckara/
    Projet LastGo WE-JV3: https://www.developpez.net/forums/d1...-last-dungeon/
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  2. #2
    Expert éminent sénior
    État actuel du projet:

    Version en-ligne: https://neckara.github.io/LastGo/
    • Jeu: https://neckara.github.io/LastGo/dis...ame/index.html
      • Charger une carte
      • Sauvegarde/Suppression/Import/Export des parties
      • Passer son tour / poser une pierre / manger les pierres adverses / highlights
      • Finir une partie
      • Ne peut revenir à un état antérieur du plateau
      • [Manquant] Online plays

    • Editeur : https://neckara.github.io/LastGo/dis...tor/index.html
      • Créer une carte
      • Sauvegarde automatique des modifications sur la carte courante
      • Sauvegarde/Suppression/Import/Export des cartes
      • Ajouter/Supprimer des joueurs
      • [Manquant] Ajouter des éléments customisés
      • [Manquant] Amélioration de la GUI


    Dépôt Git: https://github.com/Neckara/LastGo



    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  3. #3
    Expert éminent sénior
    Journal de Bord:


    Première journée (3h58) :
    18h00: Présentation du projet
    18h21: On commence
    18h59: Outils de travail installés (Dépôt Git/Framework JS/etc.)
    19h30: Première page créée (l'éditeur), un peu de réflexion sur la manière de dessiner le plateau, et on va manger .
    19h52: Je reprends le travail, et je vais vous mettre un lien vers l'éditeur dans 1 min.
    20h23: La grille se dessine (je suis trô for ).
    22h20: Fonction primitives de dessins terminées.


    Seconde journée (11h29) :
    7h35: Et c'est la reprise après un petite nuit de sommeil
    8h23: Matin un peu laborieux, amélioration de l'affichage (attente de chargement des images + highlight corrigé).
    8h58: Modification de la taille de la grille possible. J'avance plutôt lentement comparé à mes attentes.
    9h42: Possibilité d'ajouter un élément (pion). Manque plus que la sélection de l'élément, l'ajout des joueurs, ainsi que des fonctions de sauvegarde des maps.
    9h46: Petite pause.
    10h ?: Fin pause.
    10h42: Possibilité de sélectionner les éléments à ajouter via clic gauche et de les retirer avec le clic droit.
    11h45: Sélectionnez un links et admirez la technologie du highlight.
    11h45: Pause
    13h05: Reprise du travail.
    13h56: On peut désormais poser des links comme on veut. Manque plus que les colorations des joueurs, et c'est fini.
    15h39: Ajout de la coloration des éléments, éditeur presque fini, manque plus que les sauvegarde/chargements/etc.
    16h12: Auto-save + map clear
    16h12: pause
    16h20: reprise
    17h08: Import/Export. Consomme trop de ressources sur grosses map, optimisations à faire.
    18h08: Dessin optimisé (ne redessine que le nécessaire), évite de tout redessiner à chaque mouvement de souris, bizarrement l'ordi aimait pas.
    19h00: Sauvegarde des maps importées en local, ne reste plus que les fonctions de suppressions et de sauvegarde locale pour l'éditeur.
    19h00: Pause
    20h10: Reprise
    20h27: Éditeur fini. Je vais commencer le jeu, en espérant le finir assez tôt demain pour ajouter 2/3 trucs
    21h35: Possibilité de charger des cartes dans le jeu.
    21h56: C'est tout pour aujourd'hui. Un peu de planification pour le lendemain, et c'est tout. Cela va être un petit challenge d'implémenter toutes les règles, puis d'ajouter les éléments personnalisés (j'espère que j'aurais le temps ).

    Troisième journée (13h54):
    7h57: Reprise du boulot . Journée ambitieuse en perspective, j'ai le réseau à mettre en place (3 servers), les règles, ainsi que les éléments personnalisés, et si possible, un mode fullscreen, ainsi qu'une gestion des utilisateurs d'une map.
    9h48: Management des parties (sauvegarde/export/toussa). Je pense abandonner l'idée du réseau, je le ferais après le WE-JV en bonus. C'est tout bête à faire, mais comme je ne peux pas héberger de serveurs node, ça ne servira à rien pour une démonstration. Donc même si j'arrive à avoir le temps, je pense que ce n'est pas intéressant dans le cadre de ce "défi". Me reste plus qu'à gérer le "game state", puis les règles .
    11h40: On peut désormais passer son tour et naviguer dans l'historique. Pour le moment il n'y a pas encore de règles.
    11h50: Pause.
    13h23: On reprend.
    14h40: Ajout d'éléments "fantômes", i.e. sont affichés sans exister, utile pour montrer où on peut poser une pierre.
    15h48: Possibilité de poser des pions (clic-gauche), ainsi que de gagner des points (clic-droit). Un peu enquiquinant de gérer les derniers éléments modifiés pour ne pas tout redessiner à chaque fois. Bon, manque plus que les règles et les éléments personnalisés... ça va être un peu chaud pour le dernier.
    19h19: Amélioration de la surbrillance, détection des voisins, libertés, et groupes. Règle de pose de pierre ajoutée. Presque fini, mais plein de petits détails à améliorer. Il faudra que je trie selon mes priorités.
    19h20: Pause
    19h54: reprise
    20h32: On peut désormais manger les pierres. Ne reste plus qu'à gérer la fin de jeu, et le fait de retourner à un état antérieur (interdit).
    21h58: Fin du jeu. On peut presque dire que j'ai fini, manque plus qu'à retirer les groupes "morts", et on pourra dire que c'est bon.
    22h06: Après réflexion, les groupes "morts" ne peuvent pas être déterminés du fait des particularités de mes règles.
    22h40: Vérification des états antérieurs fait. La deadline approche, je pourrais faire plus de choses, mais je pense que je vais juste faire un petit truc, et puis voilà.
    23h58: Fini juste à temps . Demain après-midi (?) je posterais un bilan. Je suis plutôt content de moi au final, même si j'aurais pu en faire plus.



    Total: 29h21
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  4. #4
    Expert éminent sénior
    Voilà, bilan de la très courte première journée, je suis capable de dessiner les différents éléments, il ne me reste plus qu'à les dessiner par rapport à un état, charger correctement les images, ainsi qu'à ne pas redessiner l'entièreté du canvas à la moindre modification:



    J'ai donc deux tâches principales à faire demain et après-demain:


    • l'éditeur (demain) ;
    • le jeu avec ses règles (après-demain).


    Vous pouvez commencer à vous "amuser" ici.
    /!\ Cela peut nécessiter de devoir recharger la page plusieurs fois pour que les images s'affichent.


    Je fini donc ma journée, en planifiant les tâches de demain .
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  5. #5
    Expert éminent sénior
    Et voilà que la deuxième journée s'achève


    Éditeur de carte fini avec possibilité de sauvegarder/exporter/importer des cartes. Je n'ai pas encore fini les éléments personnalisés, mais ça va venir (si j'ai le temps demain soir).

    Pour le jeu, vous pouvez dès à présent charger les cartes déjà créées ou celles que vous aurez créées via l'éditeur.


    Vous pouvez tester la version actuelle à cette adresse:
    https://neckara.github.io/LastGo/

    Voilà, je fini là pour aujourd'hui, y'a le nouvel épisode de Re:zero et de Goblin Slayer, j'ai du boulot quoi.

    Quelques petites captures d'écran avant de se quitter



    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  6. #6
    Expert éminent sénior
    Voilà, donc j'ai fini ce projet hier peu avant minuit.

    Voici donc un petit retour.


    Avancement du jeu

    Le jeu est jouable avec toutes les fonctionnalités de base, de même que l'éditeur.

    [IMAGE]


    Si je jeu ne marche pas, il est possible que votre navigateur ai besoin d'une mise à jour... en revanche, ne faites pas comme moi qui ai bousillé mon installation Linux lors de l'installation de Firefox.

    Pour l'éditeur, la gestion des utilisateur a introduit un bug dont je ne me suis aperçu que ce matin (cela marchait pourtant bien hier ).


    Il manque quelques fonctionnalités que j'aurais voulu ajouter :
    • La correction du bug de l'Éditeur ;
    • Choix du fond d'écran (avec un jolie image ) ;
    • Ajout d'éléments personnalisés dans l'éditeur, pas compliqué, l'architecture le permet (+ sélectionner la pierre à poser dans le jeu).
      • ajout un socle spécial étant à plusieurs endroits en même temps (pour faire de la 3D éclatée).

    • Amélioration de la GUI (rendre le tout plus joli), mais ce n'est qu'esthétique.
      Jouer en ligne (comme tout est sérialisable, c'est relativement trivial).
    • Quelques optimisations (règles/index des cases), mais tant que ça marche ça va .


    Je pense donc que ce WE, je continuerais à retravailler dessus.


    Retour sur ces 3 jours

    Bon, j'y suis allé relativement tranquillement, mais le résultat est satisfaisant. J'ai pas mal bloqué sur l'affichage et la modification des fichiers SVG, ainsi que l'optimisation de l'affichage, pour ne dessiner que le nécessaire. J'ai donc perdu un peu de temps à ce niveau là.

    Retour d'expérience

    C'est un projet que je cours après depuis 9ans, que j'ai recommencé à de multiples reprises. Pendant ces 9ans, j'ai fais beaucoup de travail technique, mais j'ai commis plusieurs erreurs.


    1. Vouloir faire du code trop propre: il faut bien évidemment que le code soit un minimum propre... mais ce n'est pas une fin en soit. J'ai perdu beaucoup de temps à encapsuler les bibliothèques que j'utilise, à diviser le code en modules (.so) avec un système de chargement des modules et résolution des dépendances, en tentant d'avoir un moteur de jeu générique, et de pouvoir remplacer un module par un autre à la volée. Ma crainte à l'époque était "et si une bibliothèque n'est plus maintenu et qu'il faut que j'en change en cours de route ou change sa version ?". Bon, c'était en parti à cause de la SDL/SFML qui avaient changées de versions majeures récemment ><. La solution est en réalité assez simple : on prend une bibliothèque, et on la conserve jusqu'à la fin .

      Bon, c'était aussi pour m'amuser et approfondir mes cours, en revanche, pour le jeu final, cela n'est qu'une perte de temps. Il faut que le code soit fonctionnel, maintenable, pas la peine d'en faire trop.
    2. Vouloir faire le cœur du jeu en premier: faire d'abord la logique du jeu, les calculs en premier... avant l'affichage et autres fioritures. Grossière erreur. Il faut faire tout l'inverse, d'abord afficher et ensuite ajouter les règles du jeu. Parce qu'il est plus simple de vérifier que les règles marchent avec un affichage (quitte à modifier l'affichage pour afficher des éléments de débogue), qu'avec de simples affichages sur console. L'affichage donne aussi un visuel, et permet d'avoir quelque chose de tangible, montrant notre progression.
    3. Pourquoi faire simple quand on peut faire compliqué: Il est difficile d'écrire un algorithme optimisé qui fait tout en un premier jet... et sera inutile la très grande majeure partie du temps. Il vaut mieux diviser l'algorithme en sous-parties simples qui permettra que construire plus facilement l'algorithme, même s'il sera à la ramasse niveau performances. Par exemple, savoir si on peut poser ou non une pierre est compliqué, le faire au sein d'une seule fonction c'est "dur". En revanche, obtenir les voisins d'un socle est facile. Récupérer les voisins libres / ennemis / alliés est facile. Relancer le parcours sur les alliés trouvé est facile. Une fois qu'on a ces informations, l'algorithme devient bien plus facile (poser = au moins un voisin libre ou ennemis qui n'a que le socle actuel en voisin libre).
    4. Choix des technologies: J'aime le C++, mais ce n'était peut être pas le meilleur choix. Le JS a l'avantage de pouvoir fournir l'accès à des démonstrations sans aucune installation. À noter aussi que les EDI ont le gros défaut de nous forcer à 50 clics rien que pour créer une nouvelle classe dans un nouveau fichier, ce qui est ridicule, et rébarbatif.
    5. Recruter trop tôt: Pas la peine de mettre la charrue avant les bœufs, les images, utilise des placeholders, les sons, pas besoin. Des programmeurs... on passera plus de temps à les chercher, faire sa communication, et manager qu'on en gagnera. Une fois le jeu bien avancé, là on pourra envisager de recruter pour remplacer les placeholders.
    6. Communiquer trop tôt: C'est bien beau d'avoir un super site en XSLT (), une API documentée, des diagrammes UML, et tout... mais il faut peut-être avoir une version un peu plus potable avant...
    7. Avoir la version finale en tête: C'est bien de savoir ce qu'on veut, mais ce n'est pas la première chose à faire. Par exemple, on veut que le jeu ai un chat en ligne... donc on le fait en premier car c'est relativement simple et ça sert à mettre en place le réseau... on a une idée de l'interface finale, donc on fait tout de suite une jolie interface.

      En réalité avoir une idée d'où on veut aller va jouer contre nous. Il vaut mieux partir par itération, en commençant par le plus important, et non, le plus facile. Cela évite aussi l'erreur n°3. À avoir une vision d'ensemble, on peut être tenté de tout faire d'un coup (très compliqué), au lieu de partir de petites features qui permettront d'en construire de plus grande.

      De même partir directement sur de la 3D complexifie un peu le code, alors qu'une vue éclatée peut tout simplement faire le job

      Par exemple, un chat en ligne, c'est relativement facile à faire... mais cela n'apporte rien au jeu. Au pire les joueurs iront sur Discord en attendant cette feature. Il y a plus important. Pour la GUI, on s'en fout que ce soit joli, le tout est que cela fonctionne dans un premier temps.
    8. Faire des choses inutile: Mon jeu, faut que les utilisateurs puissent l'installer, et puis faudra qu'ils puissent le mettre à jour. Et comme les launchers sont la première chose lancée dans un jeu, et bien c'est la première chose que je vais faire.
      Mais qu'est-ce qu'on s'en fout ! Ayons déjà un jeu avant de penser à le mettre à jour !
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  7. #7
    Membre averti
    Merci pour ce retour d'expérience.
    Vachement utile et intéressant.

  8. #8
    Expert éminent sénior
    Citation Envoyé par Projet Roba Voir le message
    Merci pour ce retour d'expérience.
    Vachement utile et intéressant.
    Content que tu aimes.


    Je vais continuer un peu de discuter en finissant ce projet (je taguerais le commit pour avoir une première version correspondant à la fin du WE-JV).


    Je vais vous parler un peu de mon architecture. Il ne faut pas faire de code trop propre, mais il faut tout de même une architecture qui permette d'écrire son code simplement.

    Editeur


    J'ai commencé par l'Editeur.

    Le premier composant est le dessin, j'ai une classe qui n'a qu'un seul rôle, dessiner (BoardCanvas), sans aucune intelligence à partir d'une seconde classe qui n'a qu'un seul rôle, décrire le plateau de jeu (Board).

    Au début, Board n'a qu'une seule fonction qui retourne la taille du plateau de jeu, que j'ai écrit en dur. Juste pour tester l'affichage. Je commence donc à dessiner la grille de jeu avec CanvasBoard. Une fois que cela marche, j'ajoute la possibilité de changer la grille du jeu dans l'editeur, et je teste que ça marche bien lorsque je change la taille de la grille.

    Prochaine étape, il faut que je puisse mettre des éléments sur mon plateau de jeu. Je commence donc à créer un nouveau composant dont le seul rôle sera de charger mes ressources graphiques et de les stocker (Ressources). Je les donne à BoardCanvas à sa construction, et je les dessines ensuite en dur dans ma fonction de dessins. Lorsque cela marche, je permet à Board de stocker un couple (position, nom de l'élément), que BoardCanvas ira récupérer pour dessiner les éléments. Je dessine toujours en dur en ajoutant des éléments à Board dans la fonction main de mon code.

    J'ajoute ensuite la surbrillance, pour voir où se trouve la souris, et si je détecte correctement les cases. Je permets ensuite de poser un élément donné au clic, n'importe où, puis de le retirer avec un clic droit.

    Il ne reste plus qu'à implémenter un choix des éléments à poser dans l'éditeur, et au clic, d'ajouter cet élément à Board, puis d'appeler la fonction de dessin de BoardCanvas.

    Je fini alors l'éditeur avec des fonctions de sauvegarde/export/import/etc.

    Remarques: Vous voyez, j'ai progressé de manière très itératives, sans tenter de tout faire d'un coup, usant de placeholders, de choses écrites en dur, dans un premier temps. Le fait d'avoir séparé les choses de la sorte, m'a permis de sérialiser très simplement l'état de plateau (Board).

    Après, mon code n'est pas encore parfait, je bidouille, tâtonne un peu. Par exemple au début de redessinait tout à chaque fois, ça a fait planté mon ordi. J'ai donc dû optimisé un peu la fonction de dessins. Ce n'est pas grave, le tout, c'est que ça marche, et d'éviter les optimisation prématurée. I.E. optimiser uniquement lorsqu'on ne peut pas faire sans. Ce n'est pas grave si les méthodes sont un peu dégueulasses, tant que l'architecture est relativement propre, c'est à dire chacun à son propre rôle, et respecte l'encapsulation.

    Par exemple, sur le dessin, il va falloir que je modifie cela, pour dessiner avec des layers (ça sera plus facile de détecter les parties à redessiner), uniquement à la frameRequest, etc. Mais ce n'est pas le plus important, donc je suis passé à la suite. Je tâtonne un peu, et une fois la grande partie des features implémentées, mon code sera un peu plus stable, et là je pourrais faire des petits refactorings très localisés, pour améliorer cela, non pas pour la beauté du code, mais pour mes besoins.

    Le fait de dissocier ainsi le plateau du jeu (Board) du dessin (BoardCanvas), me permet d'afficher des choses qui ne font pas parti du jeu, par exemple afficher l'élément sélectionné dans l'éditeur sur la case survolée, sans qu'il ne soit ajouté au plateau.

    Le jeu

    Je continue donc, d'abord avec mes fonctions d'imports/exports. Je créé une classe Game, qui sera l'état du jeu, il contient donc un plateau de jeu, mais ajoute plus d'options, comme un historique des coups joués, avec une fonction de précédents/suivants. Je commence donc à implémenter le jeu, mais sans aucune règle. Je clique, je pose une pierre, je change le joueur actuel, et c'est tout. Je teste avec l'historique.

    Ce n'est qu'ensuite, que je créé une classe GameRules, qui va recevoir les actions, va en déterminer les conséquence (e.g. ajoute des points, mange des pierres), et qui va les envoyer à la classe Game qui l'ajoute à son historique puis qui à sont tour mettra à jour le Board. Board qui sera ensuite dessiné par BoardCanvas.

    Cela me permet déjà encore une fois de sérialiser les parties. Mais aussi de tester mes fonctions petits à petits, sans m'enquiquiner avec les règles. Une fois que je commence à créer les règles, j'ajoute une fonction de surbrillance pour me montrer les voisins d'un socle. Que je me servirait ensuite pour les règles. Cela me permet de vérifier visuellement et facilement que mon code marche, j'ai juste à déplacer la souris.

    Remarques: Là encore, ce n'est pas parfait. Mon système de coordonnées, [x,y] ne peut pas être contenu correctement dans un Set en JS. Donc je l'ai transformé en un string e.g. '5x4'. C'est assez pratique pour débogué, mais le problème est que je dois sans cesse jongler entre les deux représentations. Mais ça fait le boulot, donc je continue.

    Changer cela, nécessiterait de faire des grosses modifications et donc de risquer d'introduire des bugs, et de me faire perdre du temps. En revanche, ce que je peux faire, c'est utiliser une troisième représentation, et ne l'utiliser dans un premier temps qu'aux endroits où j'en ai besoin, laissant le reste tranquille pour le moment.

    Conclusion

    Je pense que la meilleure stratégie est de se dire : "Je n'ai que 2 jours". Cela nous force à aller à l'essentiel, même si c'est moche, on progresse bien plus vite ainsi. Une fois fait, on peut se permettre des petits refactoring, mais il faut absolument éviter de tout casser, de tout refaire, cela doit rester très minime et localisé.

    L'étape de refactoring légère me paraît importante, pour ne pas se traîner une dette technique dès le début, qui nous explosera à la figure tôt ou tard. Mais il ne faut pas trop en faire non plus. Le tout, c'est que ça marche.


    Il faut absolument éviter, de vouloir tout faire d'un coup, puis de se retrouver avec des fonctions de dessins, d'état de jeu et de règles mélangés pêle-mêle. Puis régulièrement, de faire des gros refactoring ou de tout réécrire à partir de 0. Parce qu'on tâtonne, on essaye, l'architecture n'est pas nécessairement stable.

    Donc on passe du temps à concevoir quelque chose qui permet tout, on a un code compliqué, instable, qu'il faudra de toute manière refactorer. Mieux vaut donc commencer simplement, et modifier en fonction des besoins.
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  9. #9
    Expert éminent sénior
    Désolé, je flood un peu, faites-moi savoir si cela vous gêne.


    Bon, parfait exemple de ce dont je parlais. Depuis hier soir, j'ai commencé un petit refactoring, je n'ai pas encore fini, et j'ai cassé le jeu, ainsi que retiré la coloration des joueurs (régressions).

    J'ai effectué plusieurs modifications :
    • utilisation de layers (c'était pour tester pour un futur projet - ce n'était pas le plus problématique) ;
    • dessin avec animationFrameRequest
    • modification des index d'éléments.



    On pourra alors me dire, "hey guignol, si tu avais prévu cela dès le début, t'aurais pas besoin de refactorer"... sauf qu'il aurait fallu être capable de le prévoir. Et si j'étais aller trop loin dans ma conception, 2 problèmes se seraient posées avec ce refactoring :
    • Code beaucoup plus complexe à modifier ;
    • modification d'un code en cours d'écriture. Refactorer un code qui marche à moitié, ... c'est un peu casse-gueule.


    On voit tout de même que le refactoring n'est pas trivial, il prend beaucoup de temps, et aboutit à des régressions. Alors pourquoi ai-je fais ce refactoring ? Moi qui disait qu'il fallait juste que ça marche ?


    Et bien... parce que ça marchait pas justement.

    animationFrameRequest

    Le problème avec ma fonction de dessin, est que pour colorier un élément à la couleur du joueur, il faut une fonction asynchrone. Ce qui fout la merde si cette fonction de dessin est appelée plusieurs fois de suite, alors que la précédente n'est pas terminée. De plus, il fallait que j'appelle explicitement la fonction de dessin à chaque fois que c'était nécessaire (au risque d'oublier un appel quelque part), et Board stockait une liste des modifications depuis le dernier dessin (ce qui n'est pas son rôle).

    Tout aurait pu aller dans le meilleur des mondes, avec un simple booléen du type:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    draw() {
       if( this.isDrawing )
            return;
     
      this.isDrawing = true;
      // ...
      this.isDrawing = false;
    }


    Le problème est que des joueurs peuvent se faire ajouter/retirer/modifier... donc il faudrait que je dessiner ce que je peux, puis que je redessine lorsque les images sont chargée... ce qui est un gros bordel, ... merci JS.


    Donc j'ai adopté une approche différente, avec animationFrameRequest avec une fonction de dessin synchrone. Ainsi je suis sûr de ne pas faire plusieurs dessins en même temps. J'en ai alors profité pour inclure la détection des changements dans ma fonction de dessin, comme cela je n'ai plus d'appels explicites à draw à la moindre modification de Board.

    Index

    Alors petit problème, pour représenter des coordonnées, 2D, le plus simple en tant qu'humain, c'est d'avoir une représentation de la forme [x, y].
    Le problème c'est qu'en JavaScrypt, on utilise les Object pour stocker des pairs [key, valeurs], ou plutôt dans mon cas des pairs [position, element]. Or les clés sont transformées en String, donc j'ai pris des clés de la forme "4x5" au lieu de [4,5].

    Le problème est que je devais passer d'une représentation à l'autre, ce qui est un peu enquiquinant à la longue. J'ai tenté d'utiliser Map, mais ce dernier ne supporte pas des Array comme index.

    Ma solution a alors été de créer une classe héritant de Map, ElementsList à laquelle je pourrais passer des positions (= key) sous différentes formes, et ainsi ne plus avoir à m'occuper des transformations. J'ai ensuite modifié quelques fonction de Board et BoardCanvas afin qu'elles puissent utiliser différents types d'index en entré.

    Je me retrouve ainsi avec 3 types d'index :
    • "4x5" quand je sérialise, pour avoir des éléments lisibles (et donc facilement débogables) ;
    • [4,5] ou 4,5 quand je fais des opérations sur le Canvas, e.g. pour récupérer ensuite une position en pixels ;
    • (4 << 16) + 5, pour stocker les valeurs dans ma Map, et comme identifiant unique. Pourquoi ce truc bizarre ? Parce que c'est un entier unique (donc facile à manipuler/stocker), qu'on peut facilement passer de cette représentation à [4,5] et inversement, mais aussi parce qu'elle est indépendante de la taille de la grille.


    Avec des fonctions pour, à partir d'un index au format inconnu, récupérer un index au format voulu.


    C'est à dire que je peux faire :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    let idx = foo(); // je ne sais pas quel est le format de idx, et je m'en fou.
     
    draw(idx, 'toto'); // prends n'importe quel format d'index
    draw(4, 5, 'toto'); // si je veux l'écrire à la main
     
    console.log( getIDX(idx), getXY(idx), getStrIDX(idx) ); // pour mettre explicitement l'index à un format voulu.



    Bon, j'aurais pu ne pas le faire, mais cela n'a pas été le plus enquiquinant non plus (une fois qu'on a compris les bêtises de JS ).



    Conclusion:

    Là, on voit que pour des refactoring "utiles", ça peut déjà foutre un peu la merde. Donc imaginez des refactorings pour des finalités purement cosmétiques. Ce serait une horreur on passerait notre temps à casser puis à corriger notre code. On voit aussi qu'il ne faut pas que notre code soit trop compliqué, sinon, le refactoring se complexifie.

    Le refactoring devrait se limiter à 3 choses, (de mon avis, et pour ce type de projet ofc) :
    1. L'ajout d'une nouvelle fonctionnalité pour laquelle l'architecture actuelle n'est pas adaptée ;
    2. La correction d'un bug, nécessitant de faire les choses différemment ;
    3. Rendre plus agréable l'utilisation des méthodes.


    Le faire juste pour avoir du code propre ne sert pas à grand chose, presque personne ne va aller lire votre code, tant qu'il marche, il n'y a pas trop de soucis à ce faire, il y a d'autres priorités.

    En revanche, le (3), me semble important. C'est quelque chose auquel j'ai été confronté dans le cadre de mon travail. Si une API devient enquiquinante à utiliser parce qu'il faut sans cesse faire des transformations explicitement, copier/coller des bouts de codes, etc. Cela va non seulement complexifier le code et sa lecture, mais surtout, il va nous rebuter. Ce qui est problématique si c'est une chose qu'on utilise très souvent.

    Par exemple, si j'utilise un tableau à 5 dimensions :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    tab[user][entry][x][y][z]
    .

    Ben pour se rappeler à quoi correspond chaque dimension, faire quelques opérations dessus, etc. ça devient très vite casse-c****.
    Mieux vaut alors le remplacer par une classe, qui gérera tout cela pour nous, avec des représentations plus faciles.

    Par exemple, je peux stocker toutes les entrées dans le même tableau, plutôt que d'avoir à m'enquiquiner de les fusionner sans cesse lorsque j'en ai besoin. Derrière, je peux avoir une liste d'utilisateurs qui contiennent des méta-données, ainsi que des pointeurs vers les entrées leur appartenant. Cela m'évite à devoir gérer des tableaux de méta-données à côté.

    Ma partie [x][y][z], je peux peut être la stocker de manière flat, et fournir des fonction pour transformer l'ensemble de ma base.


    Ce n'est donc pas purement esthétique, mais cela nous aidera lorsqu'il faudra manipuler cette saloperie, ce qui peut très vite devenir un enfer.



    Donc pour conclure ma conclusion, on se moque que le code soit propre, mais il ne faut pas qu'on se prenne trop la tête à l'utiliser, et il faut aussi faire attention à la propreté de l'architecture, plus qu'à la propreté des méthodes (une méthode, c'est facile à réécrire en cas de besoin... une architecture... un peu plus délicats. ).

    Il faut cependant éviter une architecture "trop propre", il faut juste qu'elle soit "suffisamment propre", "assez propre mais pas trop".
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

  10. #10
    Expert éminent sénior
    Je m'amuse à faire quelques petits essais pour un futur projet, tester 2-3 trucs, etc.

    Je viens de voir que mon jeu est relativement gourmand en ressources CPU, même lorsqu'il ne dessine pas (~25% du CPU). J'ai 4% / 6% qui est lié rien qu'à l'appel de AnimationFrameRequest, et le plus gros gouffre à perfs, c'est lorsque j'essaye de détecter les éléments qui ont changés pour les supprimer/dessiner.

    Cela est relativement rapide, au pire à peine 4ms, mais pour une fonction appelée toutes les 16ms... ça commence à faire.


    Pour un jeu au tour par tour, ce n'est pas vraiment acceptable. Je pense que la solution est d'avoir un callback à chaque modification du board, et d'enregistrer une liste d'éléments qui ont changés pour la future session de dessins. Puis n'appeler le animationFrameRequest que lorsqu'on a quelque chose qui a réellement changé. Cela simplifiera aussi pas mal mon code, même si j'en suis pas très fan ( pas top de trop donner de responsabilités à Board, sinon faut que BoardCanvas modifie les méthodes de Board pour se caler dessus ).


    Bon, normalement je vous dirais qu'on s'en fout, c'est pas important ces perfs... le problème c'est que plus le CPU tourne, plus il faut chaud dans mon appartement.

    Plus sérieusement, je vais tester cette solution le WE prochain. Je testerais aussi le fait de dessiner à partir d'une image, ou à partir d'un canvas. Je teste 2/3 trucs pour mon "prochain" WE-JV10, et comme cela ne sera pas un jeu au tour par tour, ... je prépare un peu le terrain
    "Parce que le diable est dans les détails, une vision sans nuance ne peut prétendre à la compréhension du monde."

    Mon ancienne page perso : https://neckara.developpez.com/

###raw>template_hook.ano_emploi###