Javaquarium

4 ans et 3 mois après, il est toujours là, et toujours aussi difficile !

a marqué ce sujet comme résolu.

Bonjour tout le monde :)

J'ai recommencé le Javaquarium, cette fois ci en Java. J'ai essayé de créer le dépôt directement depuis git, c'est la première fois que je fais ça donc c'était expérimentale mais j'ai bien un dépôt sur mon compte github, donc c'est que ça a du marcher: https://github.com/Nekyia/Javaquarium-2.git

Je n'ai pas encore fait la partie reproduction, mais j'ai un petit bug:

1
2
3
4
5
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.LinkedList$ListItr.checkForComodification(Unknown Source)
    at java.util.LinkedList$ListItr.next(Unknown Source)
    at Aquarium.tick(Simulation.java:191)
    at Simulation.main(Simulation.java:10)

Je n'ai pas encore vu ça (avant dernier chapitre de TIJ4 que je suis entrain d'étudier) donc un petit coup de pouce serait le bienvenue. J'attends aussi vos critiques sur mon code (mon bidouillage devrais-je dire). Notamment j'ai créé une classe interne DietBehavior pour distinguer les Herbivore des Carnivores et je fais appel à DietBehavior.eat() pour faire manger les poissons. Je ne sais pas ce que ça vaut comme solution. Pour rajouter une race, j'ai juste à rajouter la race dans le switch en lui donnant l'objet Diet adéquat. Je pensais faire la même chose pour la reproduction.

Comme d'habitude, merci d'avance !

Merci. J'ai trouvé une solution de contournement en utilisant un booléen isAlive. Mais comment faire sinon pour supprimer un poisson autre que celui retourné par l'itérateur, et comment supprimer depuis une autre méthode ? Il faut passer l'itérateur à la méthode qui va supprimer le poisson (eat()) ?

Je pense que le plus simple est de simplement modifier le booléen isAlive dans les méthodes et de nettoyer l'aquarium à chaque fin de "tour" dans la boucle principale (en utilisant une boucle comme dans le lien indiqué par Praetonus).

+0 -0

Parce que Kotlin c’est le langage à JVM qui monte, je tente mon propre exercice dans ce langage : https://github.com/SpaceFox/kotlinquarium – pour l’instant jusqu’à l’étape 2.1 qui pose des problèmes de modification concurrente rigolos si on y va naïvement.

À noter que c’est fait avec la doc sous la main, j’ai pour ainsi dire jamais codé dans ce langage avant. J’ai essayé d’être proche de ce que je perçois de la philosophie du langage Kotlin, quitte à m’éloigner de ce qu’on ferait en Java.

Voici ma version en Ruby, c’est mon tout premier programme "sérieux" dans ce langage que je découvre depuis fin 2016 :)

Normalement toutes les étapes sont implémentées, avec quelques petits écarts cependant:

4.2/4.3 La sortie sur console est effectuée dans un format identique à celui du chargement, donc je n’ai pas prévu l’enregistrement dans un fichier séparé, la sortie console si elle est redirigée dans un fichier peut être réutilisée directement en entrée pour charger les êtres vivants

4.4 Au lieu d’être à part en entête, le tour initial est précisé pour chaque être vivant (c’est un attribut de la classe LivingBeing).

Quelques précisions supplémentaires:

  • Les espèces sont également chargées depuis un fichier, on peut donc en définir autant qu’on veut (animaux ou végétaux) avec des propriétés différentes (en terme de points de vie, longévité, valeur nutritive, etc).

  • J’ai rajouté également la gestion des omnivores (pour le fun et ça ne coûtait vraiment pas beaucoup plus cher à coder), donc au lieu d’avoir 7 comportements différents il y en a 10.

  • Le fichier public.txt contient un résumé des classes avec leurs attributs et méthodes publiques. Pour résumer il y 10 classes pour les espèces (classe de base Species) et 10 pour les êtres vivants (classe de base LivingBeing), j’utilise les mixins Ruby pour implémenter les différents comportements.

  • La correspondance entre la classe de l’espèce et la classe de l’être vivant et faite grâce une table de correspondance consultée lors du chargement des êtres vivants.

  • Un numéro séquentiel est utilisé pour nommer les nouveaux-nés

Un exemple de sortie pour un tour de simulation

#
# START OF TURN 7 - EVENTS
# Sepi 3 (Grouper, F) dies of starvation
#
# TURN 7 - EVENTS
# Tissol (Bass, M) attacks One Green Alga
# Rieuse 3 (Flounder, M) attacks One Red Alga
# Rieuse 4 (Flounder, F) attacks One Red Alga
# Enfant de Boheme (Murena, M) attacks Sepa (Grouper, M)
# 254 (Tuna, M) attacks One Red Alga
# 258 (Flounder, M) reproduces with Pleureuse 2 (Flounder, F)
# Their child is 260 (Flounder, F)
# Auguste (Clownfish, M) attacks Sepa 1 (Grouper, M)
# Amine 1 (Bass, F) attacks One Green Alga
# Eve 3 (Shark, F) attacks Diem 2 (Carp, M)
# Amine 2 (Bass, F) attacks One Green Alga
# Eve 4 (Shark, F) attacks Edonnay 4 (Tuna, M)
# Amine 3 (Bass, F) attacks One Red Alga
# Amine 4 (Bass, F) attacks One Green Alga
# Edonnay 3 (Tuna, M) reproduces with Tonelle 6 (Tuna, F)
# Their child is 261 (Tuna, F)
# Amine 7 (Bass, F) attacks One Green Alga
# Diem 1 (Carp, M) attacks One Red Alga
# La Muette 1 (Carp, F) attacks One Red Alga
# Edonnay 5 (Tuna, M) reproduces with 248 (Tuna, F)
# Their child is 262 (Tuna, F)
# Amine 9 (Bass, F) attacks One Red Alga
# Tonelle 2 (Tuna, F) attacks One Green Alga
# Sepi (Grouper, F) attacks Edonnay 2 (Tuna, M)
# La Muette 2 (Carp, F) attacks One Red Alga
# Edonnay 6 (Tuna, M) attacks One Red Alga
# Adam de la mer 4 (Shark, M) attacks Pleureuse (Flounder, M)
# La Muette 3 (Carp, F) attacks One Green Alga
# Edonnay 7 (Tuna, M) attacks One Red Alga
# La Muette (Carp, F) attacks One Red Alga
# La Muette 4 (Carp, F) attacks One Green Alga
# Sepa 4 (Grouper, M) attacks One Red Alga
# Ouille (Murena, M) attacks Sepi (Grouper, F)
# Diem 6 (Carp, M) attacks One Red Alga
# La Muette 6 (Carp, F) attacks One Red Alga
# La Muette 7 (Carp, F) attacks One Red Alga
# Sepa 5 (Grouper, M) attacks One Green Alga
# Diem 8 (Carp, M) attacks One Red Alga
# La Muette 8 (Carp, F) attacks One Green Alga
# Diem 9 (Carp, M) attacks One Green Alga
# Samba 3 (Piranha, F) attacks La Muette 6 (Carp, F)
# Tissol 1 (Bass, M) attacks One Green Alga
# La Muette 9 (Carp, F) attacks One Green Alga
# Sepa 7 (Grouper, M) attacks One Green Alga
# 247 (Flounder, M) attacks One Red Alga
# Tissol 2 (Bass, M) attacks One Green Alga
# Samba 5 (Piranha, F) attacks Tonelle 2 (Tuna, F)
# Pleureuse 1 (Flounder, F) attacks One Red Alga
# Tissol 4 (Bass, M) attacks One Red Alga
# Tissol 5 (Bass, M) attacks One Green Alga
# Pleureuse 3 (Flounder, F) attacks One Red Alga
# Tissol 6 (Bass, M) attacks One Red Alga
# Pleureuse 4 (Flounder, F) attacks One Green Alga
# Diem (Carp, M) attacks One Green Alga
# Tissol 7 (Bass, M) attacks One Red Alga
# Tissol 8 (Bass, M) attacks One Green Alga
# Rieuse 2 (Flounder, F) attacks One Red Alga
# Sepi 2 (Grouper, F) attacks One Red Alga
# 260 (Flounder, F) attacks One Red Alga
#
# END OF TURN 7 - STATE
#
# ANIMALS
#
# Species, Value, Initial turn, Age, Name, Sex
Carp, 10, 4, 4, 251, M
Bass, 11, 1, 7, Tissol 9, M
Bass, 5, 1, 7, Tissol, M
Flounder, 11, 1, 7, Rieuse 3, M
Tuna, 12, 4, 4, 252, F
Flounder, 11, 1, 7, Rieuse 4, F
Murena, 9, 1, 7, Enfant de Boheme, M
Grouper, 9, 1, 7, Sepi 4, F
Piranha, 11, 4, 4, 253, F
Tuna, 10, 4, 4, 254, M
Grouper, 9, 1, 7, Sepi 5, F
Murena, 1, 1, 7, Ouille 1, F
Grouper, 13, 5, 3, 255, M
Grouper, 9, 1, 7, Sepi 6, F
Murena, 8, 1, 7, Ouille 2, M
Shark, 10, 1, 7, Adam de la mer, M
Flounder, 12, 6, 2, 256, F
Grouper, 11, 1, 7, Sepi 7, F
Murena, 8, 6, 2, 257, F
Flounder, 10, 6, 2, 258, M
Clownfish, 12, 1, 7, Auguste, M
Clownfish, 9, 1, 7, Bozo 3, M
Carp, 10, 6, 2, 259, F
Clownfish, 10, 1, 7, Bozo, M
Flounder, 5, 1, 7, Pleureuse, M
Shark, 8, 1, 7, Eve 1, F
Grouper, 1, 1, 7, Sepa, M
Shark, 11, 1, 7, Eve 2, F
Bass, 13, 1, 7, Amine 1, F
Shark, 9, 1, 7, Eve 3, F
Bass, 13, 1, 7, Amine 2, F
Shark, 11, 1, 7, Eve 4, F
Bass, 11, 1, 7, Amine 3, F
Shark, 10, 1, 7, Eve 5, F
Tuna, 9, 1, 7, Edonnay, M
Bass, 13, 1, 7, Amine 4, F
Tuna, 11, 1, 7, Edonnay 1, M
Bass, 11, 1, 7, Amine 5, F
Piranha, 9, 1, 7, Samba, F
Tuna, 7, 1, 7, Edonnay 2, M
Bass, 11, 1, 7, Amine 6, F
Tuna, 8, 1, 7, Tonelle, F
Tuna, 11, 1, 7, Edonnay 3, M
Bass, 13, 1, 7, Amine 7, F
Shark, 12, 1, 7, Adam de la mer 1, M
Carp, 11, 1, 7, Diem 1, M
Tuna, 3, 1, 7, Edonnay 4, M
Bass, 11, 1, 7, Amine 8, F
Shark, 9, 1, 7, Adam de la mer 2, M
Tuna, 11, 1, 7, Tonelle 1, F
Bass, 11, 1, 7, Amine, F
Carp, 3, 1, 7, Diem 2, M
Carp, 7, 1, 7, La Muette 1, F
Tuna, 11, 1, 7, Edonnay 5, M
Bass, 11, 1, 7, Amine 9, F
Shark, 9, 1, 7, Adam de la mer 3, M
Tuna, 3, 1, 7, Tonelle 2, F
Grouper, 4, 1, 7, Sepi, F
Carp, 11, 1, 7, La Muette 2, F
Grouper, 9, 1, 7, 240, M
Tuna, 9, 1, 7, Edonnay 6, M
Grouper, 7, 1, 7, Sepa 1, M
Shark, 15, 1, 7, Adam de la mer 4, M
Tuna, 9, 1, 7, Tonelle 3, F
Carp, 11, 1, 7, Diem 3, M
Carp, 13, 1, 7, La Muette 3, F
Tuna, 9, 1, 7, 241, F
Tuna, 5, 1, 7, Edonnay 7, M
Carp, 11, 1, 7, La Muette, F
Shark, 9, 1, 7, Adam de la mer 5, M
Tuna, 10, 1, 7, Tonelle 4, F
Carp, 11, 1, 7, Diem 4, M
Carp, 11, 1, 7, La Muette 4, F
Tuna, 9, 1, 7, 242, M
Tuna, 13, 1, 7, Tonelle 5, F
Murena, 8, 1, 7, Enfant de Boheme 1, M
Flounder, 11, 1, 7, Rieuse, F
Carp, 11, 1, 7, La Muette 5, F
Flounder, 12, 2, 6, 243, M
Tuna, 9, 1, 7, Tonelle 6, F
Grouper, 9, 1, 7, Sepa 4, M
Murena, 9, 1, 7, Enfant de Boheme 2, M
Murena, 12, 1, 7, Ouille, M
Carp, 7, 1, 7, Diem 6, M
Carp, 5, 1, 7, La Muette 6, F
Bass, 10, 2, 6, 244, M
Tuna, 13, 1, 7, Tonelle 7, F
Carp, 11, 1, 7, Diem 7, M
Piranha, 12, 1, 7, Samba 1, F
Carp, 11, 1, 7, La Muette 7, F
Grouper, 11, 1, 7, Sepa 5, M
Shark, 11, 2, 6, 245, F
Carp, 7, 1, 7, Diem 8, M
Carp, 13, 1, 7, La Muette 8, F
Clownfish, 11, 1, 7, Auguste 1, M
Grouper, 11, 1, 7, Sepa 6, M
Carp, 12, 2, 6, 246, F
Carp, 13, 1, 7, Diem 9, M
Piranha, 5, 1, 7, Samba 3, F
Bass, 13, 1, 7, Tissol 1, M
Carp, 13, 1, 7, La Muette 9, F
Clownfish, 9, 1, 7, Auguste 2, M
Grouper, 12, 1, 7, Sepa 7, M
Flounder, 11, 3, 5, 247, M
Piranha, 9, 1, 7, Samba 4, F
Piranha, 13, 1, 7, Do Brasil 2, M
Bass, 9, 1, 7, Tissol 2, M
Clownfish, 9, 1, 7, Auguste 3, M
Tuna, 13, 3, 5, 248, F
Piranha, 14, 1, 7, Samba 5, F
Piranha, 10, 1, 7, Do Brasil 3, M
Flounder, 11, 1, 7, Pleureuse 1, F
Shark, 8, 4, 4, 249, M
Piranha, 9, 1, 7, Do Brasil 4, M
Bass, 11, 1, 7, Tissol 4, M
Flounder, 11, 1, 7, Pleureuse 2, F
Piranha, 9, 1, 7, Do Brasil 5, M
Bass, 13, 1, 7, Tissol 5, M
Flounder, 11, 1, 7, Pleureuse 3, F
Bass, 5, 1, 7, Tissol 6, M
Flounder, 13, 1, 7, Pleureuse 4, F
Carp, 13, 1, 7, Diem, M
Bass, 9, 1, 7, Tissol 7, M
Flounder, 11, 1, 7, Rieuse 1, F
Grouper, 9, 1, 7, Sepi 1, F
Tuna, 11, 4, 4, 250, F
Bass, 13, 1, 7, Tissol 8, M
Flounder, 11, 1, 7, Rieuse 2, F
Grouper, 9, 1, 7, Sepi 2, F
Flounder, 11, 7, 1, 260, F
Tuna, 9, 7, 1, 261, F
Tuna, 9, 7, 1, 262, F
#
# Animals count 132
#
# VEGETALS
#
# Species, Value, Initial turn, Age, Quantity
Red Alga, 9, 1, 7, 5
Red Alga, 13, 1, 7, 27
Green Alga, 15, 1, 7, 34
Green Alga, 11, 1, 7, 15
Green Alga, 17, 1, 7, 39
Green Alga, 7, 1, 7, 3
Red Alga, 5, 1, 7, 1
Red Alga, 17, 1, 7, 44
Red Alga, 11, 1, 7, 13
Green Alga, 13, 1, 7, 22
Green Alga, 9, 1, 7, 5
Red Alga, 15, 1, 7, 27
Green Alga, 5, 1, 7, 2
Red Alga, 7, 1, 7, 3
#
# Vegetals count 240

J’ai remarqué comme d’autres que les hermaphrodites opportunistes se multipliaient très vite, a priori ils ont 2 fois plus de chance de se reproduire donc j’en ai mis 2 fois moins au départ pour équilibrer l’évolution, du coup le "vainqueur" est assez souvent différent.

Salut,

J’ai tenté l’exercice en R avec des classes S3, ce qui s’est avéré peu pratique puisque ce genre de classe empêche la modification d’un objet. Du coup, a chaque fois qu’une opération modifiant l’aquarium est effectué, la totalité de l’aquarium est recopié puis l’ancienne version est écrasée. Utiliser des classe de type ReferenceClass, qui fonctionnent de la même façon qu’en Java, C# ou C++, aurait sans doute été plus simple (et oui, car en R il y a plusieurs style de programmation objet).

Je me suis arrêté à la partie 3.2 (La reproduction), la suite arrivera peut être, peut être pas. Quoi qu’il en soit vous pouvez trouver le code ici sous la forme d’un package R. Vous pouvez l’installer avec devtools::install_bitbucket("gcaillaut/raquarium") après avoir préalablement installé le package devtools avec install.packages("devtools").

Il y a un example d’aquarium que vous pouvez charger avec data(sample_aquarium), si vous voulez créez le votre, il y a un exemple de script de création dans le dossure data-raw/.

Un exemple d’utilisation ressemblerait à ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
library(Raquarium)

data(sample_aquarium)

step0 <- sample_aquarium
step1 <- next_step(step0)
step2 <- next_step(step1)
# ...
# stepN <- next_step(stepN-1)

# Si on veut sauvegarder un aquarium
save(step2, file = "step2.Rda") # Créer un fichier step2.Rda
load("step2.Rda") # Pour charger le fichier step2.Rda (construit la variable step2)

Bonjour,

Ça faisait un moment que je voulais comprendre et essayer l’ECS. J’ai donc fait le Javaquarium. Je l’ai fait en Python, parce que c’est le langage que je maitrise le mieux.

J’ai fait la partie 2, je veux bien vos retours :

import random


class Carnivore():
    def __init__(self):
        pass

    def cherche_repas(self, mangeur, poissons):
        if len(poissons) == 1:
            raise ValueError
        mangé = random.choice(poissons)
        while mangé == mangeur:
            mangé = random.choice(poissons)
        return mangé

    def mange(self, mangeur, mangé):
        mangé.vie.change_vie(-4)
        mangeur.vie.change_vie(5)

class Herbivore():
    def __init__(self):
        pass
    
    def cherche_repas(self, algues):
        return random.choice(algues)

    def mange(self, mangeur, algue):
        algue.vie.change_vie(-2)
        mangeur.vie.change_vie(3)


class Vivant():
    def __init__(self, vie=10):
        self.vie = vie

    def change_vie(self, quantite):
        self.vie += quantite
    
    def est_vivant(self):
        return self.vie > 0


class Poisson:
    def __init__(self, nom, sexe, race=""):
        self.nom = nom
        self.sexe = sexe
        self.race = race
        if race in ["mérou", "thon", "poisson-clown"]:
            self.repas = Carnivore()
        else:
            self.repas = Herbivore()
        self.vie = Vivant()
    
    def affiche(self):
        print(f"Le poisson {self.nom} est un {self.sexe}, de race {self.race}, avec {self.vie.vie} points de vie.")
    
    def mange(self, truc):
        self.repas.mange(truc)
        
    def update(self):
        self.vie.change_vie(-1)

class Algue:
    def __init__(self):
        self.vie = Vivant()
    
    def update(self):
        self.vie.change_vie(1)


class Aquarium():
    def __init__(self):
        self.poissons = []
        self.algues = []
    
    def ajoute_poisson(self, poisson):
        self.poissons.append(poisson)
    
    def ajoute_algue(self, algue):
        self.algues.append(algue)
    
    def update(self):
        trucs_en_vie = self.poissons + self.algues
        # Phase de croissance des algues et faim des poissons
        for truc in trucs_en_vie:
            truc.update()
        print("Tous les poissons perdent un point de vie, toutes les algues en gagnent deux.")
        # Ménage des morts
        for poisson in self.poissons:
            if not poisson.vie.est_vivant():
                self.poissons.remove(poisson)
        random.shuffle(self.poissons)
        # Recherche de repas
        for poisson in self.poissons:
            if poisson.vie.vie > 5:
                continue
            if isinstance(poisson.repas, Carnivore):
                try:
                    mangé = poisson.repas.cherche_repas(poisson, self.poissons)
                except ValueError:
                    pass
                else:
                    poisson.repas.mange(poisson, mangé)
                    print(f"{poisson.nom} a mangé {mangé.nom}.")
                    if not mangé.vie.est_vivant():
                        self.poissons.remove(mangé)
                        print(f"{mangé.nom} est mort. :(")
            elif isinstance(poisson.repas, Herbivore):
                mangé = poisson.repas.cherche_repas(self.algues)
                poisson.repas.mange(poisson, mangé)
                print(f"{poisson.nom} a mangé une algue.")
                if not mangé.vie.est_vivant():
                    self.algue.remove(mangé)
                    print("Une algue est morte.")

    def affiche(self):
        for poisson in self.poissons:
            poisson.affiche()
        
        print(f"Il y a {len(self.algues)} algue{'s' if len(self.algues) >= 2 else ''} dans l'aquarium. Elles ont comme vie :")
        for algue in self.algues:
            print(f"{algue.vie.vie}")    

Et le code pour tester :

aqua = Aquarium()
poisson1 = Poisson("Albert", "H", "mérou")
poisson2 = Poisson("Jeannette", "F", "bar")
poisson3 = Poisson("Gontrand", "H", "bar")
poisson4 = Poisson("Gudrune", "F", "mérou")
aqua.ajoute_poisson(poisson1)
aqua.ajoute_poisson(poisson2)
aqua.ajoute_poisson(poisson3)
aqua.ajoute_poisson(poisson4)
aqua.ajoute_algue(Algue())
aqua.ajoute_algue(Algue())
aqua.ajoute_algue(Algue())
aqua.affiche()

for t in range(50):
    aqua.update()
    aqua.affiche()

L’idée de l’ECS, si j’ai bien compris, c’est d’éviter l’héritage, permettre une composition arbitraire de caractéristique en donnant aux classes une instance d’une autre classe, qui contient la logique.

Donc l'Entity, c’est Albert le poisson, le Component, c’est mon aquarium, et mon System, ce sont mes sous-classes Carnivore, Vivant

Si c’est bien ça, je crains d’avoir violé le principe dans la recherche des repas. Deux remarques / questions au passage :

  • J’ai facilement tendance à faire de la duplication de code avec de principe de programmation. Sur le produit final, ça va, mais dans les étapes intermédiaires, ça arrive facilement. Typiquement, entre les végétariens et carnivores.
  • J’ai l’impression que le décès des poissons et algues ne peut se gérer que tout en haut, dans l’aquarium. C’est normal et OK, ou c’est une erreur de programmation de ma part ?
+0 -0

(Salut ! Déterrer ne pose pas de problème tant que c’est des sujets comme ça qui rassemblent toutes les tentatives au même endroit. C’est juste à éviter dans un contexte de poser une autre question après le fil de quelqu’un, mais ici c’est totalement légitime :) )

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte