Javaquarium

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

a marqué ce sujet comme résolu.
  1. À l’origine prévu pour Java, cet exercice peut être fait avec n’importe quel langage orienté objet.
  2. Je ne garantis pas l’authenticité des informations piscicoles et aquicoles que vous pourrez trouver dans cet exercice…

Thèmes

Parce qu’on est en Java, et que c’est super horriblement vraiment très très important en Java, le thème principal de cet exercice est les objets. Et puis les poissons, mais ça c’est parce que c’est l’exo d’avril.

N’ayez pas peur ! L’énoncé est assez long, mais tentez l’exercice : tout ce blabla se code en peu de lignes.

Comment ça marche ?

Vous lisez l’énoncé et vous répondez à l’exercice. Là, deux manières de faire :

  • Soit vous postez votre code fonctionnel ci-dessous et profitez des conseils des autres membres.

  • Soit vous m’envoyez un MP, et je vous donnerai une correction.

    Sachant que je détaillerai moins des corrections personnelles que ce qui peut profiter à tout le monde.

Pas la peine d’envoyer n’importe quoi pour avoir une correction, surtout par MP. C’est pour vous que vous travaillez, l’exercice n’a d’intérêt que si vous tentez de le faire.

Un peu d’enrobage…

Monsieur Shingshan, aquariophile accompli, vous demande de faire un programme de simulation pour son prochain aquarium : il compte y mettre un certain nombre d’espèces de poissons rares et chères, et n’a pas envie qu’au bout de deux mois son précieux investissement se transforme en désert…

Votre mission (et vous l’acceptez) : lui concevoir un programme qui fera la chose suivante :

Citation : le cahier des charges

Je devrai pouvoir mettre des poissons et des plantes dans mon aquarium virtuel, et faire passer le temps pour savoir si tout se passe bien.

Traduit en geek / développeur java, ça veut dire qu’il faudra un programme en deux phases :

  1. L’initialisation : les poissons et les algues sont ajoutés dans l’aquarium
  2. Le temps qui passe : on résous les actions d’un tour, et on fait un rapport de la situation.

On est partis ? On y va !

Partie 1 : Peuplons notre Javaquarium

Exercice 1.1 : Remplissage de l’aquarium

Il s’agit de la base du programme : avoir un aquarium rempli. L’aquarium contient des poissons et des algues, un nombre quelconque de chaque. L’algue ne fait rien : c’est une algue, elle se contente d’exister ou non. Le poisson, lui, a un nom (monsieur Shingshan aime ses poissons et leur donne un nom à chacun) et un sexe (mâle ou femelle). L’aquarium a une méthode une méthode pour ajouter un poisson (avec son nom et son sexe), et une autre méthode pour ajouter une algue. L’aquarium a une méthode pour faire passer le temps : à chaque nouveau tour, on fait toutes les actions (ce qui n’est pas très passionnant pour l’instant puisqu’il n’y en a aucune) et on affiche sur la console :

  • Le nombre d’algues présentes
  • La liste des poissons avec leur nom et leur sexe.

Exercice 1.2 : un peu de diversité

En fait, des poissons tous identiques, c’est pas très passionnant. Dans le magasin où se fournit monsieur Shingshan, on trouve des poissons carnivores et d’autres herbivores. Il y a six races de poissons : Poissons carnivores : Mérou, Thon, Poisson-clown. Poissons herbivores : Sole, Bar, Carpe. Chaque poisson peut donc manger (une méthode) : selon s’il est carnivore, il mange une algue (paramètre : une algue), ou un autre poisson (paramètre : un poisson quel qu’il soit).

Partie 2 : Mange, tu ne sais pas qui te mangera

Exercice 2.1 : Miam miam miam !

Nos poissons sont des goinfres : à chaque tour, tous les poissons mangent, et ils mangent tout ce qu’ils ont commencé. En clair, ça veut dire que ce qui et mangé disparaît purement et simplement. Attention : un poisson ne peut pas se manger lui-même, et un poisson mangé ne peut rien faire. L’algue ou le poisson mangé est choisi au hasard.

Astuce : Tirer un nombre aléatoire en Java :

// 1- Initialiser le générateur de nombres aléatoires.
// (une seule fois pour tout le programme suffit)
Random r = new Random();
 
// 2- Récupérer le prochain entier entre 0 (inclus) et n (exclus) :
int x = r.nextInt(n);
 
// On a des méthodes de ce genre pour tous les types primitifs : nextDouble(), nextBoolean(), ...

Exercice 2.2 : un peu de douceur dans ce monde de brutes

Tout ça est beaucoup trop brutal, introduisons la notion de points de vie (PV).

Les poissons et les algues sont des êtres vivants. Tous les êtres vivants commencent avec 10 PV. Un être vivant qui arrive à 0 PV meurt.

Chaque algue grandit à chaque tour : elle gagne 1 PV. Une algue mangée par un poisson perds 2 PV.

A chaque tour qui passe, le poisson a de plus en plus faim : il perds 1 PV. Un poisson qui a suffisamment faim (5 PV ou moins) cherche à manger. Les herbivores n’ont pas trop de problème, mais les algues ne sont pas très nourrissantes : +3 PV. Les carnivores mangent de la bonne viande de poisson, qui fait gagner +5 PV. Un carnivore attaque chaque tour un poisson au hasard : il n’a qu’une seule chance. Hélas pour lui, il ne peut ni se manger lui-même, ni manger un poisson de son espèce. Se faire mordre fait très mal et fait perdre 4 PV.

Partie 3 : Reproductions

Exercice 3.1 : Le désastre du vieillissement

Tous les êtres vivants naissent à l’âge de 0 tours. Les êtres vivants qui ont plus de 20 tours meurent de vieillesse.

On peut acheter des poissons et des algues à un âge quelconque.

Exercice 3.2 : Le miracle de la jeunesse

Notre aquarium précédent est condamné à être désert au bout de 20 tours, ou alors il faut remettre sans arrêt des algues et des poissons. Pas très intéressant, non ? Nous avons des poissons mâles et femelles ? Faisons-les se reproduire !

Un poisson qui n’a pas faim va aller voir un autre poisson (au hasard). Si ce poisson est de même race et de sexe opposé, les deux poissons se reproduisent et donnent naissance à un troisième poisson, de même race et de sexe aléatoire. Il n’a qu’un seul essai par tour.

Concernant les algues, c’est plus simple : Une algue qui a 10 PV ou plus se sépare en deux pour donner naissance à une algue deux fois plus petite, donc avec deux fois moins de PV. Évidemment l’algue parente perds la moitié de ses PV dans le processus, mais garde son âge.

Exercice 3.3 : Mais… la sexualité des poissons est horriblement compliquée !

Eh bien oui, la sexualité des poisson est horriblement compliquée. Pourquoi ? Parce qu’ils ne sont pas simplement "mâle" ou "femelle" ! On distingue : Le poisson mono-sexué : Comme vous et moi, ces poissons naissent mâle ou femelle et n’en changent plus. Le poisson hermaphrodite avec l’âge : Ce poisson passe les 10 premiers tours de sa vie en tant que mâle et les 10 suivants en tant que femelle. Le poisson hermaphrodite opportuniste : Ce poisson va rencontrer un membre de son espèce. Si ce membre est de même sexe que lui, notre poisson change de sexe pour pouvoir se reproduire.

Je rappelle que nous avons déjà des races herbivores et carnivores, ce qui nous donne le tableau suivant1 : 1: Pour ceux que ça intéresse, cette histoire de poisson hermaphrodites est parfaitement exacte… par contre je ne garantis pas le classement des races dans le tableau.

Poisson Herbivore Carnivore
Mono-sexué Carpe Thon
Hermaphrodite avec l’âge Bar Mérou
Hermaphrodite opportuniste Sole Poisson-clown

Partie 4 : Rendons notre simulateur d’aquarium pratique

Exercice 4.1 : Sauvez Willy !

Monsieur Shingshan voudrait pouvoir sauvegarder l’état actuel de la simulation (par exemple au tour 35) dans un fichier (par exemple un .poisson) pour pouvoir la reprendre plus tard.

Cette question est beaucoup plus simple qu’elle n’en a l’air, mais nécessitera sans doute de modifier toutes les classes.

Exercice 4.2 : Un fichier pour les enregistrer tous…

Monsieur Shingshan voudrait que les rapport de ce qui s’est passé dans le tour s’enregistrent dans un fichier en plus d’être affichés sur la console.

Exercice 4.3 : … et dans un fichier les charger

Monsieur Shingshan est aquariophile mais pas programmeur, et n’a pas envie de re-compiler le programme à chaque fois qu’il veut changer les poissons et les algues introduits au début. De toutes façons, il ne sait pas ce qu’est "recompiler". Il veut bien lancer le programme en ligne de commande, mais il veut pouvoir écrire les poissons et les algues dans un fichier texte dont il passerait le nom au programme. Il fournit un exemple de fichier (les lignes qui commencent par // sont des commentaires et n’apparaitrons jamais dans le fichier).

// 1- Algues
// Format : [Nombre d'algues] algues [âge] ans (= tours)
// Exemple :
1 algues 10 ans
10 algues 4 ans
 
// 2- Poissons
// Format : [nom], [race], [âge] ans (= tours)
// Exemple :
Lin, Morue, 10 ans
Anaclet, Poisson-clown, 1 ans
Évariste, Thon, 3 ans

Exercice 4.4 : Tourne, la roue tourne…

Monsieur Shingshan voudrait pouvoir ajouter des poissons et des algues à n’importe quel tour. Le tour d’insertion serait indiqué par des séparateurs comme celui-ci :

===== tour 5 ====

Tout ce qu’il y a après (et avant le séparateur suivant) suit le format de l’exercice 4.1.

Attention, cette question n’est pas si simple qu’elle en a l’air…

Exercice 4.5 : Le petit neveu

Monsieur Shingshan voudrait donner le simulateur à son petit-neveu, Ashini. Sauf que celui-ci, bien qu’aquariophile, n’est pas précautionneux : il ne faudrait pas qu’il puisse planter son PC avec le logiciel.

Si vous avez programmé nickel, vous avez déjà réalisé cet exercice.

Voilà, amusez-vous bien !

En fait, cet exercice a été spécialement conçu pour 2 choses :

  1. Faire utiliser au maximum les notions d'objets – beaucoup de cours de langages OO commencent par une partie procédurale, donc les élèves pensent en procédural, et donc appliquent mal, voire ne comprennent pas l'intérêt, de la partie objet.
  2. Nécessiter un héritage multiple dans un langage qui ne le possède pas (Java à l'origine), ce qui oblige à réfléchir à la manière de mettre en place ce genre de chose.

Le point 2 rend l'exercice difficile en Java dès qu'on essaie de faire propre (i.e. qu'un fait autre chose qu'un gros tas de instanceof dans le code).

J'ai quelques questions sur cet exercice que je ne ferai pas mais dont je trouve l'énoncé juste excellent.

Quand on passe à la phase où les poissons doivent manger :

  • Comment l'ordre des poissons est choisi pour décider ce qu'ils vont manger ? Genre que se passe-t-il si un poisson doit se faire bouffer par un autre mais qu'il n'a pas mangé à ce tour-ci, on s'en fiche ?
  • Au niveau des PV, les nouveaux PV doivent être attribués avant la phase de nutrition ? Ou ça peut se faire après ? Genre si le poisson vient de passer à 0 PV, il est toujours comestible ? Ou il doit être retirer de l'aquarium directement ?
  • Même problème avec l'âge ?
  • Que se passe-t-il si la chaîne est A veut manger B puis C veut manger A. B est quand même affecté ? C'est à dire, c'est complètement séquentiel en fonction de l'ordre dans lequel on décide que les poissons mangent ?

En gros tu ne décris pas, où dans ce cas là j'ai mal lu, comment se déroule un tour, ce qui peut créer des ambiguïtés.

Bonjour, Ca a l'air pas mal comme exercice. J'aimerais le faire en JS est-ce possible ? (avec si possible images, liste des actions, liste des poissons et plus si les idées viennent)

En plus des questions de Saroupille, j'aimerais comprendre la nutrition : les carnivores cherchent toujours à se nourrir ? Quand alors cherchent-ils à se reproduire ? Et comment cherchent-ils ? Est-ce un poisson au hasard dans tout l'aquarium et tant pis si c'est eux ou un de leur espèce ou bien cherchent-ils parmi les poissons comestibles ? (de même pour la reproduction). Comment nommer les poissons nouveaux-nés ? Y a-t-il une limite sur le nombre d'êtres (l'aquarium n'est pas infini) ? Merci d'avance

Bonsoir,

Je me permet de d'expliquer ce que je pense avoir compris de l'exercice.

les carnivores cherchent toujours à se nourrir ?

[Ccile]

Comme les herbivores, ils cherchent à se nourrir lorsqu'ils ont faim^^. Dans les premières parties de l'exercice, les poissons ont constamment faim. Une fois la reproduction introduite, ce n'est plus vrai.

Après, beaucoup des questions que tu te poses, ont leur réponses dans l'énoncé de l'exercice.

Quand alors cherchent-ils à se reproduire ?

[Ccile]

A chaque tour qui passe, le poisson a de plus en plus faim : il perds 1 PV. Un poisson qui a suffisamment faim (5 PV ou moins) cherche à manger.

[Spacefox]

Et comment cherchent-ils ? Est-ce un poisson au hasard dans tout l'aquarium et tant pis si c'est eux ou un de leur espèce ou bien cherchent-ils parmi les poissons comestibles ?

[Ccile]

L'algue ou le poisson mangé est choisi au hasard.

[Spacefox]

(de même pour la reproduction) (de même pour la reproduction)

[Ccile]

Un poisson qui n'a pas faim va aller voir un autre poisson (au hasard). Si ce poisson est de même race et de sexe opposé, les deux poissons se reproduisent et donnent naissance à un troisième poisson, de même race et de sexe aléatoire. Il n'a qu'un seul essai par tour.

[Spacefox]

Concernant le nom des nouveaux nés, savoir si les être vivant sont mis à jour(vieillissement, évolution des PV, etc…), avant d'effectuer une action n'est pas préciser il me semble. Perso, j'ai trouvé logique, de les faire évoluer avant qu'ils effectuent leurs action.

J'aimerais le faire en JS est-ce possible ? (avec si possible images, liste des actions, liste des poissons et plus si les idées viennent)

[Ccile]

Aucun soucis pour le faire en Javascript à mon avis. Dans n'importe quel langage c'est faisable(bien qu'en Brainfuck, je demande à voir^^). Même si Spacefox, précise que c'est plutôt destiné aux langages objets, pour ma part je ne vois pas trop ce qui empêche de le réaliser en utilisant d'autres paradigmes.

Après attention, à ne pas tomber dans le piège qui consiste à plus s'intéresser à l'enrobage. Dans un premier temps pour éviter la dispersion, une première implémentation en console me semble plus judicieuse, pour vraiment se concentrer sur l'interaction entre les élément de l'aquarium.

Hello,

Pour commencer, on peut faire l'exo dans n'importe quel langage. Néanmoins il a été spécifiquement pensé pour utiliser à fond le modèle objet (et poser des problèmes aux langages sans héritage multiple) ; donc je n'ai pas la moindre idée de la difficulté de la chose dans d'autres types de langages;

Sinon, voici des réponses aux questions que j'ai vues :

  • Comment l'ordre des poissons est choisi pour décider ce qu'ils vont manger ? Non défini, donc ce que tu veux, tant qu'ils y passent tous.
  • Genre que se passe-t-il si un poisson doit se faire bouffer par un autre mais qu'il n'a pas mangé à ce tour-ci, on s'en fiche ? En fait, il n'y a pas de problèmes ici, à cause des précisions des points suivants.
  • Au niveau des PV, les nouveaux PV doivent être attribués avant la phase de nutrition ? Ou ça peut se faire après ? Ça se fait avant : gestion des PV pour tous les êtres vivants, puis nutrition.
  • Genre si le poisson vient de passer à 0 PV, il est toujours comestible ? Ou il doit être retirer de l'aquarium directement ? Même problème avec l'âge ? Un être vivant (poisson ou algue, mangé ou mort de vieillesse) disparaissent instantanément de l'aquarium. Il n'y a pas de nécrophage. De plus toute mort est instantanée.
  • Que se passe-t-il si la chaîne est A veut manger B puis C veut manger A. B est quand même affecté ? Si tu programmes bien, tu n'as jamais ce genre de problème, puisque tu résous les morts instantanément. Par exemple si A mange B, B disparaît de l'aquarium et donc ne peut plus être mangé par quiconque.
  • j'aimerais comprendre la nutrition : les carnivores cherchent toujours à se nourrir ? Au point 2.1, oui. Au point 2.2, uniquement s'ils ont faim (cf les règles).
  • Quand alors cherchent-ils à se reproduire ? Quand ils n'ont pas faim.
  • Et comment cherchent-ils ? Est-ce un poisson au hasard dans tout l'aquarium et tant pis si c'est eux ou un de leur espèce ou bien cherchent-ils parmi les poissons comestibles ? (de même pour la reproduction). C'est exactement ça : ils cherchent au hasard un poisson comestible ou un poisson avec lequel se reproduire. À noter que les poissons sont assez intelligents pour ne pas se chercher eux-même
  • Comment nommer les poissons nouveaux-nés ? Comme tu veux :)
  • Y a-t-il une limite sur le nombre d'êtres (l'aquarium n'est pas infini) ? L'aquarium est "suffisamment grand", la limite est celle que tu veux (virtuellement infinie, ce qui signifie "les contraintes techniques" en réalité).

Je voudrais aussi revenir sur ceci :

(avec si possible images, liste des actions, liste des poissons et plus si les idées viennent)

Le but de l'exercice est vraiment de faire fonctionner l'aquarium. On a rien à faire de l'affichage, ce serait éventuellement l'objet d'un autre exercice. Un simple affichage en mode texte suffit largement.

@GurneyH, j'essaie de trouver un peu de temps pour jeter un coup d'œil à ton code :)

Bonsoir,

Merci beaucoup. Javascript peut être "en mode objet" non ? (les guillemets sont là parce que je ne suis pas sûre de l'expression) Oui, certes, au départ seul l'exercice compte, mais rien n'empêche d'ajouter quelque chose ensuite non ?

Merci encore

Une autre question qui n'a encore pas de réponse et qui pourrait être importante est la suivante: si A croque B mais que B n'est pas mort (B avait plus que 4pv), A gagne quand même ses 5pv ou pas?

J'ai essayé l'exercice moi-même, j'ai pas encore fini, mais j'ai trouvé un truc marrant: le poisson carnivore hermaphrodite opportuniste est l'espèce qui finit quasiment toujours par battre toutes les autres…

+0 -0

Une autre question qui n'a encore pas de réponse et qui pourrait être importante est la suivante: si A croque B mais que B n'est pas mort (B avait plus que 4pv), A gagne quand même ses 5pv ou pas?

Bien sûr ! Il a avalé et digéré le morceau de poisson qu'il a croqué :)

Bonjour,

Petite question d'héritage : quand on dissocie les poissons carnivores des poissons herbivores, tu sous-entends deux classes hérités de Poisson ? Si oui, comment fais t-on pour récupérer la liste des poissons (et algues) de l'Aquarium pour notre méthode Manger ? Je n'ai pas encore le feeling avec les langages stricts, ça me perturbe. :3

Voici ma classe Aquarium (avec l'idée de créer une méthode Manger() à l'intérieur) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package javaquarium;
import java.util.*;

/**
 *
 * @author Yarflam
 */
public class Aquarium {
    private ArrayList<Poisson> poissons;
    private ArrayList<Algue> algues;

    /**
     * CONSTRUCTEUR DE LA CLASSE Aquarium
     */
    public Aquarium () {
        this.poissons = new ArrayList();
        this.algues = new ArrayList();
    }

    /**
     * Ajoute un poisson à l'aquarium
     * 
     * @param nom
     * @param sexe 
     * @param race 
     * @param alimentation 
     */
    private void ajouterPoisson (String nom, Character sexe, String race, String alimentation) {
        Poisson poisson = new Poisson(nom, sexe, race, alimentation);
        this.poissons.add(poisson);
    }

    /*
    *   POISSONS CARNIVORES
    */
    public void ajouterMerou (String nom, Character sexe) {
        this.ajouterPoisson(nom, sexe, "Mérou", "Carnivore");
    }

    public void ajouterThon (String nom, Character sexe) {
        this.ajouterPoisson(nom, sexe, "Thon", "Carnivore");
    }

    public void ajouterPoissonClown (String nom, Character sexe) {
        this.ajouterPoisson(nom, sexe, "Poisson-clown", "Carnivore");
    }

    /*
    *   POISSONS HERBIVORES
    */
    public void ajouterSole (String nom, Character sexe) {
        this.ajouterPoisson(nom, sexe, "Sole", "Herbivore");
    }

    public void ajouterBare (String nom, Character sexe) {
        this.ajouterPoisson(nom, sexe, "Bare", "Herbivore");
    }

    public void ajouterCarpe (String nom, Character sexe) {
        this.ajouterPoisson(nom, sexe, "Carpe", "Herbivore");
    }

    /**
     * Ajouter une algue
     * 
     * @param algue 
     */
    public void ajouterAlgue (Algue algue) {
        this.algues.add(algue);
    }
}
+0 -0

Lu'!

A noter : si on ajoute beaucoup de comportements et de classes utilisant ces comportements, on peut se retrouver à soit écrire beaucoup de code redondant soit à se prendre la tête pour maintenir ça en ordre (et toucher une limitation de l'objet). Pour les gens qui se baladent sur le forum C++ d'un site que l'on connaît bien, une solution souvent proposée est d'utiliser une approche par composants (Entity Component System pour le plus cité). Or ce modèle est un peu complexe à saisir et à implémenter parce qu'il apparaît généralement dans les cas compliqués, il me semble que cet exercice est aussi indiqué pour pratiquer ce modèle :) .

Ciao !

Je n'ai pas regardé dans le détail pour implémenter cet exercice (je voulais le faire en Groovy) mais y'a plusieurs petits trucs qui font tilt à la lecture de ce sujet :

  • pallier le manque d'héritage multiple

  • comportements de classes et classes utilisant ces comportements

  • entity component system

Tout ça me fait penser à une fonctionnalité super cool de Groovy qui existe désormais en Java depuis Java 8 : les traits.

Grovvy avait les @Category et @Mixin qui étaient assez sympas pour gérer ce genre de problématiques par le passé. Depuis Groovy 2.3 le langage a introduit les Traits qui sont tout à fait indiqués pour ce genre de cas.

Java 8 a introduit les interfaces avec implémentation par défaut. Ça pourrait être intéressant également dans ce genre de cas, mais ça reste "Stateless" (contrairement aux Traits Groovy), à voir donc si c'est juste pour décrire un comportement générique ça peut passer, si c'est un comportement "à état", ça ne sera pas la panacée.

Si certains se lancent dans le Javaquarium avec Java 8 ou Groovy, ça peut être un très bon cas d'école pour ce genre de fonctionnalités.

+2 -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