Partage
  • Partager sur Facebook
  • Partager sur Twitter

[RUBY] Une condition dans une condition.

Sujet résolu
    2 avril 2022 à 19:50:20

    Bonjour,

    Je travail sur un programme pour attribuer des caractéristiques à des monstres.

    Tout se passe bien à part à un endroit, où en théorie je conditionne l'obtention de la caractéristique "Spé 2" à l’obtention préalable de la compétence "Spé 1".
    Le programme me renvoi parfois des monstres sans la caractéristique "Spé 1" mais avec la caractéristique "Spé 2" (ce qui ne devrait pas être le cas).

    Exemple:



    La partie du code qui gère ça prend la forme d'une condition contenue dans une autre.
    Voici la partie en question:

    ## Spé 
    
    	alea = rand(99)
    
    	if alea < 50
    		tab_monstres[2] = "Spé 1"
    		compt_Spé_1 = compt_Spé_1 + 1
    
    		alea = rand(99)
    		if alea < 50
    			tab_monstres[3] = "Spé 2"
    			compt_Spé_2 = compt_Spé_2 + 1
    		else
    			tab_monstres[3] = "/"
    		end
    	else
    		tab_monstres[2] = "/"
    	end

    Voici le code complet si besoin:

    tab_monstres = []
    
    nb = 0
    
    # Je peux tout regrouper dans un seul tableau?
    compt_Cica = 0 
    compt_Spé_1 = 0
    compt_Spé_2 = 0
    compt_Petite = 0
    compt_Grande = 0
    compt_Discret = 0 
    compt_Craintif = 0 
    compt_Passif = 0 
    compt_Agressif = 0 
    compt_Enrage = 0
    
    100.times do
    
    	nb = nb + 1
    
    
    	### Boucle principale.
    
    	tab_monstres[0] = nb
    
    	## Cica 
    
    	alea = rand(99)
    
    	if alea < 10
    		tab_monstres[1] = "Cicatrisant"
    		compt_Cica = compt_Cica + 1
    	else
    		tab_monstres[1] = "/"
    	end
    
    	## Spé 
    
    	alea = rand(99)
    
    	if alea < 50
    		tab_monstres[2] = "Spé 1"
    		compt_Spé_1 = compt_Spé_1 + 1
    
    		alea = rand(99)
    		if alea < 50
    			tab_monstres[3] = "Spé 2"
    			compt_Spé_2 = compt_Spé_2 + 1
    		else
    			tab_monstres[3] = "/"
    		end
    	else
    		tab_monstres[2] = "/"
    	end
    
    	## Petite zone
    
    	alea = rand(99)
    
    	if alea < 50
    		tab_monstres[4] = "Petite"
    		compt_Petite = compt_Petite + 1
    	else
    		tab_monstres[4] = "/"
    	end
    
    	## Grande zone
    
    	alea = rand(99)
    
    	if alea < 25
    		tab_monstres[5] = "Grande"
    		compt_Grande = compt_Grande + 1
    	else
    		tab_monstres[5] = "/"
    	end
    
    	## Carcatère:
    
    	alea = rand(99)
    
    	case alea
    	when 0..5
    		tab_monstres[6] = "Discret"
    		compt_Discret = compt_Discret + 1 
    	when 6..25
    		tab_monstres[6] = "Craintif"
    		compt_Craintif = compt_Craintif + 1
    	when 26..75
    		tab_monstres[6] = "Passif"
    		compt_Passif = compt_Passif + 1
    	when 75..95
    		tab_monstres[6] = "Agressif"
    		compt_Agressif = compt_Agressif + 1
    	when 95..99
    		tab_monstres[6] = "Enragé"
    		compt_Enrage = compt_Enrage + 1
    	end
    
    	## Résultat
    
    	puts "-------------- Le monstre #{tab_monstres[0]} --------------"
    	# J'affiche le tableau à partir du second objet.
    	puts tab_monstres[1, tab_monstres.size]
    end
    
    puts "--------------------------------------------"
    puts "Il y a:"
    puts " #{compt_Cica} monstres avec des facultés cicatrisantes."
    puts " #{compt_Spé_1} monstres avec un type particulier."
    puts " #{compt_Spé_2} monstres avec deux types particuliers."
    puts " #{compt_Petite} monstres avec une petite attaque de zone."
    puts " #{compt_Grande} monstres avec une grande attaque de zone."
    puts "Dont:"
    puts " #{compt_Discret} monstres discrets."
    puts " #{compt_Craintif} monstres craintifs."
    puts " #{compt_Passif} monstres passifs."
    puts " #{compt_Agressif} monstres agressifs."
    puts " #{compt_Enrage} monstres enragés."


    Je n'ai jamais rencontré ce genre de souci et je ne comprend pas pourquoi ça ne fonctionne pas.
    Auriez-vous une idée pour m'aiguiller ?


    Édit:

    Pour préciser, j'ai l'impression que lorsque le problème survient, le 2eme tirage de la variable "alea" renvoi " ", ce que je suppose être un "nil".
    Et une condition sous cette forme (voir ci-dessous) ne résout pas le problème:

    ## Spé 
    
    	alea = rand(99)
    
    	if alea < 50
    		tab_monstres[2] = "Spé 1"
    		compt_Spé_1 = compt_Spé_1 + 1
    
    		alea = rand(99)
    		if alea < 50 && alea != nil
    			tab_monstres[3] = "Spé 2"
    			compt_Spé_2 = compt_Spé_2 + 1
    		else
    			tab_monstres[3] = "/"
    		end
    	else
    		tab_monstres[2] = "/"
    	end



    -
    Edité par Getrox 2 avril 2022 à 20:25:38

    • Partager sur Facebook
    • Partager sur Twitter
      2 avril 2022 à 21:50:45

      Salut, il vas falloir retravailler le naming et la semantique, c'est assez difficile de rentrer dans votre code.

      Vous utilisez un seul et unique tableau pour tout vos monstres, c'est la raison de pourquoi `Spe 2` est present quand `Spe 1` ne l'est pas.

      En effet, ligne 54 vous faites un "reset" de `Spe 1` mais pas de `Spe 2` donc si il est set, il reste present.

      Mais corriger ca, c'est pas une bonne idee, il faut reprendre la sementique.

      On a quoi ici ?

      Faisons un point de l'ubiquitous language :

      Monstre : Entiee Malefique ayant un pouvoir cicatrisant, des spe, une zone et un caractere.

      Pouvoir Cicatrisant: ??

      Spe : ??

      Zone : ??, celle ci comprend deux valeurs possibles, [Petite, Grande]

      Caractere : ??, celui-ci comprend 5 valeurs possibles, [Discret,Craintif,Passif,Agressif,Enragé]

      Il me manque des notions ici represente par des `??`, c'est un devoir en tant de developpeur que de comprendre ce que l'on cherche a faire, ici sans info complementaire je vais avoir du mal.

      En revanche on a deja une bonne base de depart.

      Au niveau des capacitee, un point important, si Spe depend d'une autre, alors il faut un lien dans le code qui le represente, ici c'est volatile.

      Une structure comme celle-ci serais bien plus saine pour la suite de ton programme.

      Bonne chance pour la suite.

      -
      Edité par necros211 2 avril 2022 à 21:52:04

      • Partager sur Facebook
      • Partager sur Twitter

      Architecte logiciel - Software craftsmanship convaincu.

        3 avril 2022 à 12:10:38

        Bonjour @Necros211,

        Merci pour ta résolution du problème qui était en effet que le "Spé 2" avait besoin d'un reset en cas d'absence de "Spé 1".

        • Partager sur Facebook
        • Partager sur Twitter
          3 avril 2022 à 16:51:33

          Attention, ce n'est que un pansement, comme dit dans ma resolution c'est la conception qui est a revoir (sans parler de la semantique du Ruby, la je parle vraiment du code en lui meme).

          Je trouve ton code assez interessant, tu pourrais iterer un grand nombre de fois dessus en ayant le focus sur 1 point a la fois, cela serais vraiment un bon exercice.

          Hesite pas a revenir avec une partie de ton code corrige pour qu'on puisse te faire des critiques dessus.

          -
          Edité par necros211 3 avril 2022 à 16:52:20

          • Partager sur Facebook
          • Partager sur Twitter

          Architecte logiciel - Software craftsmanship convaincu.

            4 avril 2022 à 14:47:10

            A noter aussi que ton tableau ne stocke aucun monstre, mais les caractéristiques du monstre nb

            • Partager sur Facebook
            • Partager sur Twitter
              5 avril 2022 à 0:27:11

              @necros211:

              J'ai appris le Ruby (et la programmation) via des tutos.
              J'ai conscience que je passe à côté de quelque chose, mais pour dire vrai je ne comprend pas ce que tu essai de me dire.

              J'ai essayé de prendre du recul, mais mon code me semble logique.
              Même si pas du tout optimisé j'en convient (et sûrement très moche pour des gens qui ont un plus gros niveau).

              Lorsque tu parle itération, je suppose que tu veux dire que comme mes conditions se ressemblent je peux créer une fonction unique à appeler?
              Je pense pouvoir également réunir mes nombreux compteurs dans un tableau unique.
              Mes premiers jets de code sont toujours très basiques, je comprend mieux ce que je fais au moins.

              @umfred:
              Tout à fait.
              Puisque j'affiche directement les caractéristiques du monstre en question, il ne me semble pas essentiel de stocker ce dernier non?

              ****

              Quoi qu'il en soit, mon code fait bien ce que je lui demande.
              Il me sort les caractéristiques de 100 monstres ainsi qu'une synthèse, que j'enregistre dans un fichier texte. 

              Voici le code actuellement:

              # Purge du fichier de sauvegarde.
              fichier_listeMonstres = File.open("./listeMonstres", "w")
              fichier_listeMonstres.puts("")
              fichier_listeMonstres.close
              
              tab_monstres = []
              stats_monstre = []
              tab_lvl_monstres = []
              
              nb = 0
              lvl_monstre = 0 
              
              # Je peux tout regrouper dans un seul tableau?
              compt_Cica = 0 
              compt_Spé_1 = 0
              compt_Spé_2 = 0
              compt_Petite = 0
              compt_Grande = 0
              compt_Normal = 0
              compt_Discret = 0 
              compt_Craintif = 0 
              compt_Passif = 0 
              compt_Agressif = 0 
              compt_Enrage = 0
              
              100.times do
              
              	nb = nb + 1
              	# Reset pour pas avoir de Spé 2 sans Spé 1.
              	tab_monstres[3] = "/"
              
              	### Boucle principale.
              
              	tab_monstres[0] = nb
              
              	## Cica 
              
              	alea = rand(99)
              
              	if alea < 10
              		tab_monstres[1] = "Cicatrisant"
              		compt_Cica = compt_Cica + 1
              	else
              		tab_monstres[1] = "/"
              	end
              
              	## Spé 
              
              	alea = rand(99)
              
              	if alea < 50
              		tab_monstres[2] = "Spé 1"
              		compt_Spé_1 = compt_Spé_1 + 1
              
              		alea = rand(99)
              		if alea < 50
              			tab_monstres[3] = "Spé 2"
              			compt_Spé_2 = compt_Spé_2 + 1
              		else
              			tab_monstres[3] = "/"
              		end
              	else
              		tab_monstres[2] = "/"
              	end
              
              	## Petite zone
              
              	alea = rand(99)
              
              	if alea < 50
              		tab_monstres[4] = "Petite"
              		compt_Petite = compt_Petite + 1
              	else
              		tab_monstres[4] = "/"
              	end
              
              	## Grande zone
              
              	alea = rand(99)
              
              	if alea < 25
              		tab_monstres[5] = "Grande"
              		compt_Grande = compt_Grande + 1
              	else
              		tab_monstres[5] = "/"
              	end
              
              	## Monstre avec seulement une attaque normale.
              
              	if tab_monstres[1] == "/" && tab_monstres[2] == "/" && tab_monstres[3] == "/" && tab_monstres[4] == "/" && tab_monstres[5] == "/"
              		compt_Normal = compt_Normal + 1
              	end
              
              	## Caractère:
              
              	alea = rand(99)
              
              	case alea
              	when 0..5
              		tab_monstres[6] = "Discret"
              		compt_Discret = compt_Discret + 1 
              	when 6..25
              		tab_monstres[6] = "Craintif"
              		compt_Craintif = compt_Craintif + 1
              	when 26..75
              		tab_monstres[6] = "Passif"
              		compt_Passif = compt_Passif + 1
              	when 75..95
              		tab_monstres[6] = "Agressif"
              		compt_Agressif = compt_Agressif + 1
              	when 95..99
              		tab_monstres[6] = "Enragé"
              		compt_Enrage = compt_Enrage + 1
              	end
              
              	## Statistiques: Calcul à ajuster !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
              
              	# lvl
              	alea = rand (1..10)
              	lvl = alea
              	stats_monstre[0] = lvl
              	tab_lvl_monstres[lvl] = tab_lvl_monstres[lvl].to_i + 1
              
              	# vita
              	alea = rand (50..100)
              	stats_monstre[1] = alea*lvl
              
              	# force
              	alea = rand (10..50)
              	stats_monstre[2] = alea*lvl
              
              	# agilité
              	alea = rand (10..50)
              	stats_monstre[3] = alea*lvl
              
              	# intelligence
              	alea = rand (10..50)
              	stats_monstre[4] = alea*lvl
              
              	# constitution
              	alea = rand (10..50)
              	stats_monstre[5] = alea*lvl
              
              	# vita_max
              	stats_monstre[1] = stats_monstre[5]/2 + stats_monstre [1]
              
              	## Résultat
              	
              	# Sauvegarde des caractéristiques de chaque monstres.
              	fichier_listeMonstres = File.open("./listeMonstres", "a")
              
              	fichier_listeMonstres.puts("-------------- Le monstre #{tab_monstres[0]} --------------")
              	fichier_listeMonstres.puts(tab_monstres[1, tab_monstres.size])
              	fichier_listeMonstres.puts("lvl #{stats_monstre[0]}.")
              	fichier_listeMonstres.puts("#{stats_monstre[1]} de vitalité max.")
              	fichier_listeMonstres.puts("#{stats_monstre[2]} de force.")
              	fichier_listeMonstres.puts("#{stats_monstre[3]} d'agilité.")
              	fichier_listeMonstres.puts("#{stats_monstre[4]} d'intelligence.")
              	fichier_listeMonstres.puts("#{stats_monstre[5]} de constitution.")
              	fichier_listeMonstres.close
              end
              
              # Affichage de la synthèse.
              puts("--------------------------------------------")
              puts("Il y a:")
              puts(" #{compt_Normal} monstres avec seulement une attaque normal.")
              puts(" #{compt_Cica} monstres avec des facultés cicatrisantes.")
              puts(" #{compt_Spé_1} monstres avec un type particulier.")
              puts(" #{compt_Spé_2} monstres avec deux types particuliers.")
              puts(" #{compt_Petite} monstres avec une petite attaque de zone.")
              puts(" #{compt_Grande} monstres avec une grande attaque de zone.")
              puts("")
              puts("Dont:")
              puts(" #{compt_Discret} monstres discrets.")
              puts(" #{compt_Craintif} monstres craintifs.")
              puts(" #{compt_Passif} monstres passifs.")
              puts(" #{compt_Agressif} monstres agressifs.")
              puts(" #{compt_Enrage} monstres enragés.")
              puts("")
              puts("Ils sont:")
              10.times do
              	lvl_monstre = lvl_monstre + 1
              puts(" #{tab_lvl_monstres[lvl_monstre]} de lvl #{lvl_monstre}.")
              end
              
              # Sauvegarde de la synthèse.
              fichier_listeMonstres = File.open("./listeMonstres", "a")
              fichier_listeMonstres.puts("--------------------------------------------")
              fichier_listeMonstres.puts("Il y a:")
              fichier_listeMonstres.puts(" #{compt_Normal} monstres avec seulement une attaque normal.")
              fichier_listeMonstres.puts(" #{compt_Cica} monstres avec des facultés cicatrisantes.")
              fichier_listeMonstres.puts(" #{compt_Spé_1} monstres avec un type particulier.")
              fichier_listeMonstres.puts(" #{compt_Spé_2} monstres avec deux types particuliers.")
              fichier_listeMonstres.puts(" #{compt_Petite} monstres avec une petite attaque de zone.")
              fichier_listeMonstres.puts(" #{compt_Grande} monstres avec une grande attaque de zone.")
              fichier_listeMonstres.puts("")
              fichier_listeMonstres.puts("Dont:")
              fichier_listeMonstres.puts(" #{compt_Discret} monstres discrets.")
              fichier_listeMonstres.puts(" #{compt_Craintif} monstres craintifs.")
              fichier_listeMonstres.puts(" #{compt_Passif} monstres passifs.")
              fichier_listeMonstres.puts(" #{compt_Agressif} monstres agressifs.")
              fichier_listeMonstres.puts(" #{compt_Enrage} monstres enragés.")
              fichier_listeMonstres.puts("")
              fichier_listeMonstres.puts("Ils sont:")
              # Purge de la variable lvl_monstre
              lvl_monstre = 0
              10.times do
              	lvl_monstre = lvl_monstre + 1
              fichier_listeMonstres.puts(" #{tab_lvl_monstres[lvl_monstre]} de lvl #{lvl_monstre}.")
              end
              fichier_listeMonstres.close

              Je serai ravi de progresser grâce à vos critiques et conseils.
              N'hésitez pas.

              Par contre je vais surement vous faire détailler vos propos, car comme je vous l'ai dis je suis un peu à la masse niveau vocabulaire et concepte de programmation.

              • Partager sur Facebook
              • Partager sur Twitter
                5 avril 2022 à 15:58:38

                Hello,

                > Par contre je vais surement vous faire détailler vos propos, car comme je vous l'ai dis je suis un peu à la masse niveau vocabulaire et concepte de programmation.

                Pas de probleme, je vais expliciter mon 1er message. Je vais mettre pas mal de lien externe, considere mon message plus comme un mini-cours que tu vas devoir suivre plusieurs fois, et probablement par petites iterations ;)

                > Puisque j'affiche directement les caractéristiques du monstre en question, il ne me semble pas essentiel de stocker ce dernier non?

                En fait le soucis n'est pas:

                • Vu que j'ecrit de suite dans un fichier, pas besoin d'enregistrer

                Mais

                • Vu que j'ecrit de suite dans un fichier, mon code est pas modulable

                En programmation on essaie de suivre quelques principes assez pratique. L'un des plus important et des moins bien compris est SOLID.

                Le blog d'Arolla a fait un super article https://www.arolla.fr/blog/2017/02/principes-solid-vie-de-jours/ Assez generaliste sans ligne de code et donc assez "universel".

                Pour en revenir sur le stockage dans une variable, c'est bien mieux de stocker tes monstres dans un seul array que tu vas ensuite passer a une unitee de code qui vas le parcourir et enregistrer dans un fichier.

                Pourquoi separer dans une unitee de code distincte ?

                Aujourd'hui tu enregistres dans un fichier, rien ne t'interdis demain d'enregistrer dans une base de donnee. L'architecture logiciel a pour objectif de remmetre au plus tard les choix sans retours.

                > Lorsque tu parle itération, je suppose que tu veux dire que comme mes conditions se ressemblent je peux créer une fonction unique à appeler?

                Lorsque je parle d'iteration, c'est le nombre de fois ou tu vas revenir dessus. Tu ne peux pas corriger tout les soucis d'une traite a ton niveau et c'est bien normal.

                Justement c'est pour ca que c'est interessant pour toi de poster au fur et a messure ton code ici. Les personnes de la communautee pourrons alors le critiquer et te faire avancer, c'est le seul moyen de bien progresser en developpement.

                > Je pense pouvoir également réunir mes nombreux compteurs dans un tableau unique.

                > Mes premiers jets de code sont toujours très basiques, je comprend mieux ce que je fais au moins.

                Les premiers bout de codes sont rarement les bons, sauf si tu te fait guider. Il y a deux grand guide plutot pas mal.

                • Papier crayon
                • Tests automatiques

                Pour le Papier crayon, c'est de poser a l'avance ton probleme et de verifier ses limites. C'est un travail cerebral assez important en general, qui demande pas mal de competences en amons

                Pour les tests en revanche c'est un des exercices les plus bete possible. Tu vas ecrire une unitee de code qui vas dire "Je veux un monstre" et tu vas ensuite coder une autre unitee de code qui vas creer ce monstre.

                Je parle d'unitee de code pour eviter de dire fonction, classe ou methode car on vas y revenir plus tard. En revanche le mot "unitee" est assez important, car il dit que le code est auto-suffisant.

                Bon, reprenons des parties de mon message initial, le soucis vu que tu ne me dit pas ce que tu n'as pas compris je vais devoir deviner :)

                > ubiquitous language

                C'est une sorte de dictionnaire de ton domaine. On programme toujours pour un domaine donnee, ici ca a l'air d'etre le jeu de role. On vas donc utiliser le vocabulaire du jeu de role.

                https://martinfowler.com/bliki/UbiquitousLanguage.html http://referentiel.institut-agile.fr/ubiquitous.html

                Le gros plus de l'ubiquitous language, c'est que ton code vas utiliser exactement les meme termes que ceux qui sont present dans celui-ci.

                Ainsi, tu vas pouvoir partager ton code avec ton ubiquitous language a n'importe qui et si la conception est propre, il devrais comprendre le code sous ses yeux sans forcement connaitre la programmation. Il m'est arriver de pair-programmer avec une personne du metier grace au fait que l'on parlait la meme langue, c'est l'une des fonctionnalitee avec le moins de bug que j'ai produit de ma vie :D

                > Monstre : Entiee Malefique ayant un pouvoir cicatrisant, des spe, une zone et un caractere.

                > Pouvoir Cicatrisant: ??

                > Spe : ??

                > Zone : ??, celle ci comprend deux valeurs possibles, [Petite, Grande]

                > Caractere : ??, celui-ci comprend 5 valeurs possibles, [Discret,Craintif,Passif,Agressif,Enragé]

                Ici c'est assez important donc de combler les ?? histoire de pouvoir comprendre ce qu'est une Spe par exemple. Je suis un joueur donc pas de probleme pour moi, en revanche ca ne sera pas le cas pour tout le monde,

                Je remarque un nouveau concepte dans ton nouveau code, les statistiques, il faut donc les ajouters a l'UL.

                Statistique: ??

                Vu qu'aujourd'hui elles ne servent qu'a du stockage en db, je ne peux pas deviner a quoi servent les statistiques.

                Bon, entrons dans le dur du sujet, ta sementique. Elle decoulera naturellement de ton ubiquitous language.

                Creons notre element central, le monstre

                class Monstre
                  attr_reader :pouvoir, :spes, :zones, :caractere, :statistiques
                
                  def initialize(pouvoir, spes, zones, caractere, statistiques)
                    @pouvoir = pouvoir
                    @spes = spes
                    @zones = zones
                    @caractere = caractere
                    @statistiques = statistiques
                  end
                
                  def normal_attack?
                    pouvoir.nil? &amp;&amp; spes.empty? &amp;&amp; zones.empty?
                  end
                end
                

                Ici toutes les info de ton monstres sont centraliser dans une classe. Elle utilise d'autres element sementique, alors il faut les creer aussi.

                class Statistiques
                  attr_reader :lvl, :vita, :force, :agilite, :intelligence, :constitution, :vita_max
                
                  def initialize(lvl, vita, force, agilite, intelligence, constitution)
                    @lvl = lvl
                    @vita = vita
                    @force = force
                    @agilite = agilite
                    @intelligence = intelligence
                    @constitution = constitution
                    @vita_max = constitution / 2 + vita
                  end
                end
                

                Deja les statistiques, comme ca on sais ce que l'on manipule, je t'avoue que stats_monstre[5] n'est pas parlant pour moi, et je pense que pour toi non plus dans quelques jours/mois.

                Pour les autres, pour le moment je ne sais pas trop comment tu comptes les utiliser a l'avenir, on vas donc seulement les utilisers comme simple string

                CARACTERES = {
                  discret: 'Discret',
                  craintif: 'Craintif',
                  passif: 'Passif',
                  agressif: 'Agressif',
                  enrage: 'Enrage',
                }
                
                ZONES = {
                  petite: 'Petite',
                  grande: 'Grande',
                }
                
                SPES = {
                  spe_une: 'Spe 1',
                  spe_deux: 'Spe 2'
                }
                

                En revanche j'aime bien les enums, et ruby n'en propose pas, donc je fait sans ici. Dans un vrais projet j'utiliserais cette lib bien utile: https://dry-rb.org/gems/dry-types/1.2/ Comme ca je pourrais utiliser des Enum pour les zones et spe, mais aussi des Monades pour la presence ou non d'un pouvoir.

                Bon, il serais temps de creer un monstre tu en dis quoi? :D

                Pour construire un objet un peu complexe on utilise ce qu'il s'appelle une factory. Cette factory vas creer des stats par rapport a des lancer de des.

                Il nous faut une classe pour ces lancer de des :)

                class RandomDice
                  def roll(min: 0, max:)
                    rand(min..max)
                  end
                end
                

                Pourquoi avoir une classe pour les lancer de des? Pour pouvoir tester ton code !

                Comment tu peux tester ton code si le comportement change a chaque lancer. Tu vas pouvoir faire un des comme suit

                class TestDice
                  def initialize(suite)
                    @suite = suite
                    @current = 0
                  end
                
                  def roll(min: 0, max:)
                    @suite[current].tap { @current += 1 }
                  end
                end
                

                La ton test deviens predictible.

                Viens ensuite la factory:

                class MonstreFactory
                  def initialize(dices)
                    @dices = dices
                  end
                
                  def build
                    Monstre.new(
                      POUVOIRS[build_pouvoir],
                      build_spes.map { SPES[_1] },
                      build_zones.map { ZONES[_1] },
                      CARACTERES[build_caractere],
                      build_statistiques
                    )
                  end
                
                  private
                
                  def build_pouvoir
                    roll = @dices.roll(max: 99)
                
                    if roll &lt; 10
                      :cicatrisant
                    end
                  end
                
                  def build_spes
                    spes = []
                
                    roll = @dices.roll(max: 99)
                
                    if roll &lt; 50
                      spes &lt;&lt; :spe_une
                
                      second_spe_roll = @dices.roll(max: 99)
                
                      if second_spe_roll &lt; 50
                        spes &lt;&lt; :spe_deux
                      end
                    end
                
                    spes
                  end
                
                  def build_zones
                    zones = []
                
                    petite_zone_roll = @dices.roll(max: 99) 
                
                    if petite_zone_roll &lt; 50
                      zones &lt;&lt; :petite
                    end
                
                    grande_zone_roll = @dices.roll(max: 99) 
                
                    if grande_zone_roll &lt; 25
                      zones &lt;&lt; :grande
                    end
                
                    zones
                  end
                
                  def build_caractere
                    roll = @dices.roll(max: 99)
                 
                    case roll
                    when 0..5 then :discret
                    when 6..25 then :craintif
                    when 26..75 then :passif
                    when 75..95 then :agressif
                    when 95..99 then :enrage
                    end
                  end
                
                  def build_statistiques
                    Statistiques.new(
                      @dices.roll(min: 1, max: 10),
                      @dices.roll(min: 50, max: 100),
                      @dices.roll(min: 10, max: 50),
                      @dices.roll(min: 10, max: 50),
                      @dices.roll(min: 10, max: 50),
                      @dices.roll(min: 10, max: 50)
                    )
                  end
                end
                

                Ici on construit le monstre.

                On vas pouvoir en constuire plein regarde

                # On construit la factory
                factory = MonstreFactory.new(RandomDice.new)
                # On peux l'appeller autant de fois que l'on veux
                monstres = 2.times.map { factory.build }
                

                Bon, on a des monstres, faut les enregistrer, pour ca, il nous faut un moyen de persistance.

                Un repository fera l'affaire, c'est une classe qui se charge des entree/sortie de ton programme avec le monde exterieur.

                Comme dit plus haut, tu veux qu'une unitee de code s'en occupe pour pouvoir la changer au besoin.

                Cependent, on veux cette classe la plus debile possible, et on veux enregistrer des monstres, mais aussi une synthese. Si on donne notre array de monstre dans le repository, il vas devoir trouver combiens il y a de monstres normal par exemple.

                C'est pas notre envie, on veux pas melanger une ecrire et un traitement, alors on vas faire appel encore a une autre classe.

                Une collection de monstres c'est quoi ? Un bestiaire ?

                Hop, un nouveau terme dans notre ubiquitous language.

                Bestiaire : Catalogue de monstre

                Et on construit la classe. Je vais le faire en 2 temps.

                Dans un 1er temps je la declare comme enumerable, car ca reste un array de monstre

                class Bestiaire
                  include Enumerable
                
                  def initialize(monstres)
                    @monstres = monstres
                  end
                
                  def each(&amp;block)
                    if block_given?
                      @monstres.each(&amp;block)
                    else
                      to_enum(:each)
                    end
                  end
                end
                

                Ensuite viens les counts !

                class Bestiaire
                  include Enumerable
                
                  def initialize(monstres)
                    @monstres = monstres
                  end
                
                  def each(&amp;block)
                    if block_given?
                      @monstres.each(&amp;block)
                    else
                      to_enum(:each)
                    end
                  end
                
                  def count_normal
                    count { |monstre| monstre.normal_attack? }
                  end
                
                  def count_cica
                    count { |monstre| monstre.pouvoir == POUVOIRS[:cicatrisant] }
                  end
                
                  def count_spe(spe)
                    count { |monstre| monstre.spes.include?(SPES[spe]) }
                  end
                
                  def count_zone(zone)
                    count { |monstre| monstre.zones.include?(ZONES[zone]) }
                  end
                
                  def count_caractere(caractere)
                    count { |monstre| monstre.caractere == CARACTERES[caractere] }
                  end
                
                  def levels
                    map { |monstre| monstre.statistiques.lvl }.tally
                  end
                end
                

                La c'est les differentes stats.

                Point important, on viens de depasser le nombre de lignes de ton code actuelle, et il manque encore la sauvegarde en fichier. C'est tout a fait normal, on a un peu plus d'outil donc un peu plus de lignes de code.

                En revanche on arrive enfin a la last step, le repository.

                On vas lui donner un bestiaire, et il vas ecrire dans un fichier.

                require 'stringio'
                
                class BestiaireFicherRepository
                  def initialize(path)
                    @path = path
                  end
                
                  def store_bestiaire(bestiaire)
                    fichier = File.open(@path, 'a')
                    io = ::StringIO.new
                
                    bestiaire.each_with_index do |monstre, index|
                      io &lt;&lt; &lt;&lt;~TEXT
                      -------------- Le monstre #{index} --------------"
                      #{monstre.pouvoir} #{monstre.spes.join(' ')} #{monstre.zones.join(' ')} #{monstre.caractere}
                      lvl #{monstre.statistiques.lvl}
                      #{monstre.statistiques.vita_max} de vitalité max 
                      #{monstre.statistiques.force} de force   
                      #{monstre.statistiques.agilite} d'agilité   
                      #{monstre.statistiques.intelligence} d'intelligence   
                      #{monstre.statistiques.constitution} de constitution   
                      TEXT
                    end
                
                    io &lt;&lt; &lt;&lt;~TEXT
                    --------------------------------------------
                    Il y a:
                    #{bestiaire.count_normal} monstres avec seulement une attaque normal.
                    #{bestiaire.count_cica} monstres avec des facultés cicatrisantes.
                    #{bestiaire.count_spe(:spe_une)} monstres avec un type particulier
                    #{bestiaire.count_spe(:spe_deux)} monstres avec deux types particuliers
                    #{bestiaire.count_zone(:petite)} monstres avec une petite attaque de zone
                    #{bestiaire.count_zone(:grande)} monstres avec une grande attaque de zone
                
                    Dont:
                
                    - #{bestiaire.count_caractere(:discret)} monstres discrets.
                    - #{bestiaire.count_caractere(:craintif)} monstres craintifs.
                    - #{bestiaire.count_caractere(:passif)} monstres passifs.
                    - #{bestiaire.count_caractere(:agressif)} monstres agressifs.
                    - #{bestiaire.count_caractere(:enrage)} monstres enragés.
                
                    Ils sont:
                    #{level_display(bestiaire).join("\n")}
                    TEXT
                
                    fichier.puts(io.string)
                  end
                
                  private def level_display(bestiaire)
                    bestiaire.levels.each_with_object([]) do |(level, count), acc|
                      acc[level - 1] = count
                    end.map.with_index { |count, level| "#{count} de level #{level + 1}" }
                  end
                end
                

                C'est un peu dense, possiblement un peu decousu car je l'ai ecrit au fur et a mesure. Avec ce code, tu t'en sortira mieux pour ajouter des fonctionnalitee, et le tester.

                Je te le met au complet ici, et bonne chance. Hesite pas a la moindre question.

                CARACTERES = {
                  discret: 'Discret',
                  craintif: 'Craintif',
                  passif: 'Passif',
                  agressif: 'Agressif',
                  enrage: 'Enrage',
                }
                
                ZONES = {
                  petite: 'Petite',
                  grande: 'Grande',
                }
                
                SPES = {
                  spe_une: 'Spe 1',
                  spe_deux: 'Spe 2'
                }
                
                POUVOIRS = {
                  cicatrisant: 'Cicatrisant'
                }
                
                class Monstre
                  attr_reader :pouvoir, :spes, :zones, :caractere, :statistiques
                
                  def initialize(pouvoir, spes, zones, caractere, statistiques)
                    @pouvoir = pouvoir
                    @spes = spes
                    @zones = zones
                    @caractere = caractere
                    @statistiques = statistiques
                  end
                
                  def normal_attack?
                    pouvoir.nil? &amp;&amp; spes.empty? &amp;&amp; zones.empty?
                  end
                end
                
                class Statistiques
                  attr_reader :lvl, :vita, :force, :agilite, :intelligence, :constitution, :vita_max
                
                  def initialize(lvl, vita, force, agilite, intelligence, constitution)
                    @lvl = lvl
                    @vita = vita
                    @force = force
                    @agilite = agilite
                    @intelligence = intelligence
                    @constitution = constitution
                    @vita_max = constitution / 2 + vita
                  end
                end
                
                class TestDice
                  def initialize(suite)
                    @suite = suite
                    @current = 0
                  end
                
                  def roll(min: 0, max:)
                    @suite[current].tap { @current += 1 }
                  end
                end
                
                class RandomDice
                  def roll(min: 0, max:)
                    rand(min..max)
                  end
                end
                
                class Bestiaire
                  include Enumerable
                
                  def initialize(monstres)
                    @monstres = monstres
                  end
                
                  def each(&amp;block)
                    if block_given?
                      @monstres.each(&amp;block)
                    else
                      to_enum(:each)
                    end
                  end
                
                  def count_normal
                    count { |monstre| monstre.normal_attack? }
                  end
                
                  def count_cica
                    count { |monstre| monstre.pouvoir == POUVOIRS[:cicatrisant] }
                  end
                
                  def count_spe(spe)
                    count { |monstre| monstre.spes.include?(SPES[spe]) }
                  end
                
                  def count_zone(zone)
                    count { |monstre| monstre.zones.include?(ZONES[zone]) }
                  end
                
                  def count_caractere(caractere)
                    count { |monstre| monstre.caractere == CARACTERES[caractere] }
                  end
                
                  def levels
                    map { |monstre| monstre.statistiques.lvl }.tally
                  end
                end
                
                class MonstreFactory
                  def initialize(dices)
                    @dices = dices
                  end
                
                  def build
                    Monstre.new(
                      POUVOIRS[build_pouvoir],
                      build_spes.map { SPES[_1] },
                      build_zones.map { ZONES[_1] },
                      CARACTERES[build_caractere],
                      build_statistiques
                    )
                  end
                
                  private
                
                  def build_pouvoir
                    roll = @dices.roll(max: 99)
                
                    if roll &lt; 10
                      :cicatrisant
                    end
                  end
                
                  def build_spes
                    spes = []
                
                    roll = @dices.roll(max: 99)
                
                    if roll &lt; 50
                      spes &lt;&lt; :spe_une
                
                      second_spe_roll = @dices.roll(max: 99)
                
                      if second_spe_roll &lt; 50
                        spes &lt;&lt; :spe_deux
                      end
                    end
                
                    spes
                  end
                
                  def build_zones
                    zones = []
                
                    petite_zone_roll = @dices.roll(max: 99) 
                
                    if petite_zone_roll &lt; 50
                      zones &lt;&lt; :petite
                    end
                
                    grande_zone_roll = @dices.roll(max: 99) 
                
                    if grande_zone_roll &lt; 25
                      zones &lt;&lt; :grande
                    end
                
                    zones
                  end
                
                  def build_caractere
                    roll = @dices.roll(max: 99)
                 
                    case roll
                    when 0..5 then :discret
                    when 6..25 then :craintif
                    when 26..75 then :passif
                    when 75..95 then :agressif
                    when 95..99 then :enrage
                    end
                  end
                
                  def build_statistiques
                    Statistiques.new(
                      @dices.roll(min: 1, max: 10),
                      @dices.roll(min: 50, max: 100),
                      @dices.roll(min: 10, max: 50),
                      @dices.roll(min: 10, max: 50),
                      @dices.roll(min: 10, max: 50),
                      @dices.roll(min: 10, max: 50)
                    )
                  end
                end
                
                require 'stringio'
                
                class BestiaireFicherRepository
                  def initialize(path)
                    @path = path
                  end
                
                  def store_bestiaire(bestiaire)
                    fichier = File.open(@path, 'a')
                    io = ::StringIO.new
                
                    bestiaire.each_with_index do |monstre, index|
                      io &lt;&lt; &lt;&lt;~TEXT
                      -------------- Le monstre #{index} --------------"
                      #{monstre.pouvoir} #{monstre.spes.join(' ')} #{monstre.zones.join(' ')} #{monstre.caractere}
                      lvl #{monstre.statistiques.lvl}
                      #{monstre.statistiques.vita_max} de vitalité max 
                      #{monstre.statistiques.force} de force   
                      #{monstre.statistiques.agilite} d'agilité   
                      #{monstre.statistiques.intelligence} d'intelligence   
                      #{monstre.statistiques.constitution} de constitution   
                      TEXT
                    end
                
                    io &lt;&lt; &lt;&lt;~TEXT
                    --------------------------------------------
                    Il y a:
                    #{bestiaire.count_normal} monstres avec seulement une attaque normal.
                    #{bestiaire.count_cica} monstres avec des facultés cicatrisantes.
                    #{bestiaire.count_spe(:spe_une)} monstres avec un type particulier
                    #{bestiaire.count_spe(:spe_deux)} monstres avec deux types particuliers
                    #{bestiaire.count_zone(:petite)} monstres avec une petite attaque de zone
                    #{bestiaire.count_zone(:grande)} monstres avec une grande attaque de zone
                
                    Dont:
                
                    - #{bestiaire.count_caractere(:discret)} monstres discrets.
                    - #{bestiaire.count_caractere(:craintif)} monstres craintifs.
                    - #{bestiaire.count_caractere(:passif)} monstres passifs.
                    - #{bestiaire.count_caractere(:agressif)} monstres agressifs.
                    - #{bestiaire.count_caractere(:enrage)} monstres enragés.
                
                    Ils sont:
                    #{level_display(bestiaire).join("\n")}
                    TEXT
                
                    fichier.puts(io.string)
                  end
                
                  private def level_display(bestiaire)
                    bestiaire.levels.each_with_object([]) do |(level, count), acc|
                      acc[level - 1] = count
                    end.map.with_index { |count, level| "#{count} de level #{level + 1}" }
                  end
                end
                
                
                # On construit la factory
                factory = MonstreFactory.new(RandomDice.new)
                # On peux l'appeller autant de fois que l'on veux
                monstres = 50.times.map { factory.build }
                
                bestiaire = Bestiaire.new(monstres)
                
                repository = BestiaireFicherRepository.new("./listeMonstres")
                repository.store_bestiaire(bestiaire)
                

                -
                Edité par necros211 5 avril 2022 à 16:05:29

                • Partager sur Facebook
                • Partager sur Twitter

                Architecte logiciel - Software craftsmanship convaincu.

                  11 avril 2022 à 11:36:31

                  Bonjour @necros211,

                  Merci pour cet exposé détaillé et sourcé.
                  Je suis dans le rush en ce moment alors je n'ai pour l'instant pas mis en application tous tes conseils (je viens de voir ton message pour dire vrai).
                  Dès que j'ai un créneau pour l'ordi je m'attaque à tout ça.

                  Merci encore une fois.

                  ps: Pour pas flood le fofo, je passerai sûrement via mp pour la suite de nos échange.

                  • Partager sur Facebook
                  • Partager sur Twitter

                  [RUBY] Une condition dans une condition.

                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                  • Editeur
                  • Markdown