---
# System prepended metadata

title: Untitled

---

class Petale:

    """

    Une classe que l'on utilise pour représenter des pétales de fleurs

    Leur durée de vie est de 20 jours et ils fanent au bout de 15.

    On s'intéresse aussi à leur couleur

    ...

    Attributes
    ----------
    age : int
        un entier qui représente l'age du pétale, initiallement nul.

    Methods
    -------
    vieillir()
        On incrémente l'age du pétale de 1.
        Si le pétale est toujours accroché, renvoit vrai. 
        Sinon, renvoit faux.

    couleur()
        Il s'agit de la couleur du pétale : 
        - rouge si il est non fané
        - marron si il l'est

    """

    def __init__(self,age=0):

        """
        Parameters
        ----------
        Age : int
            L'age du pétale
        """

        self.age = age

    def vieillir(self):

        """

        Fait vieillir le pétale de 1 en incrémentant son age.

        Retourne vrai ou faux selon l'age du pétal :

        - Si le pétale possède un age supérieur à 20, retourne faux car le pétale se détache
        - Sinon, il renvoit vrai.
        
        """

        self.age+=1

        if self.age >= 20 :
           return(False)

        return(True)

    def couleur(self):

        """

        Retourne la couleur du pétale en fonction de son âge :
        - Marron si il est fané (age>15)
        - Rouge si il ne l'est pas (age<15)

        """

        if self.age>=15:
            return("marron")

        return("rouge")
        
        

import random
from petale import Petale


class Fleur:

    """

    Une classe que l'on utilise pour représenter les fleurs
    On s'intéresse à leur age en jours et a leur statut de fécondité qui determine leur état :

    — âge jusqu’à 15 jours : en fleur
    — âge entre 16 et 20 jours : fanant
    — âge entre 21 et 30 jours, et fécondée : fruit
    — âge plus de 21 jours non fécondée, ou plus de 31 jours : tombée

    ...

    Attributes
    ----------
    age : int
        un entier qui représente l'age de la fleur, initiallement nul.

    fecondee : bool
        Un booléen qui determine si la fleur a été fécondée

    Methods
    -------
    est_fanant()
        Permet de savoir si la fleur est en train de faner ou non.

    est_fruit()
        Permet de savoir si la fleur est au stade de fruit ou non.

    vieillir()
        Permet d'incrémenter l'age de la fleur de 1 jour.

    """

    def __init__(self, age = 0, fecondee = False, probaf = 10, probat = 0.1):

        """
        Parameters
        ----------
        age : int
            L'age de la fleur
        fecondee: bool
            La fleur est elle fécondée ?
        probaf : float
            La probabilité qu'une fleur soit butinée (et donc qu'elle devienne fécondée) en pourcentage
        probat : float
            La probabilité qu'une fleur tombe spontanément de l'arbre (Vent,animal...) en pourcentage
        """

        self.age = age
        self.fecondee = fecondee
        self.probaf = probaf
        self.probat = probat
        self.__petales = [Petale() for k in range(10)]

    def est_fanant(self):

        """

        Donne l'état de la fleur (est-elle fanante ou non ?)

        """

        return(15<self.age<21)

    def est_fruit(self):

        """

        Détermine si la fleur est à l'état de fruit (suffisament agée et fécondée)

        """

        return(21<self.age and self.fecondee)

    def vieillir(self, probaf = 10, probat = 0.1):

        """

        Incrémente l'age de la fleur de une journée et modifie son état en conséquence (Mise à jour du statut des pétales)

        Selon les probabilités choisies, le statut de la fleur sera aussi influencé :
        - elle peut tomber si son age dépasse 32 ou spontanément
        - elle peut rester fécondée ou le devenir avec une probabilité définie

        On peut mettre à jour ces probabilités en début de cette méthode ou conserver les probabilitées existantes

        Parameters
        ----------
        probaf:
            La nouvelle probabilité d'être butinée (Vaut initiallement l'ancienne) (en pourcentage)
        probat :
            La nouvelle probabilité de tomber spontanément (Vaut initiallement l'ancienne) (en pourcentage)
        ------
        """

        self.probaf = probaf
        self.probat = probat

        if self.age < 32 :
            self.age += 1

        for k in range(10) :
            Petale.vieillir(self.__petales[k])

        self.fecondee = (self.fecondee or (self.age<15 and (random.random() < self.probaf/100)))

        if random.random() < (self.probat/100) :
            self.age = 32

        return((self.age<22) or (self.age<32 and self.fecondee))

    def couleur(self):

        """

        Retourne un dictionnaire qui contient les couleurs de la fleur

        Si c'est un fruit : il est orange, on met 1 dans orange

        Sinon, on renvoit les couleurs pétale par pétale avec la formule suivante :

        le coeur : 1/(1+nb_petales)
        les pétales : nb_petales/(1+ nb_petales)

        Le total des valeurs du dictionnaire est donc toujours égal à 1 (des proportions)
        """

        if 21<self.age<32 and self.fecondee :
            return {"orange": 1}

        return({"jaune": 1/(1+len(self.__petales)), Petale.couleur(self.__petales[0]): len(self.__petales)/(1+len(self.__petales))})
        

import random

class Feuille:

    """

    Cette classe permet de représenter une feuille de l'arbre

    On s'intéresse à l'age et à la couleur

    Le statut détermine si la feuille est séchante (et depuis combien de temps) ou vivante
    ...

    Attributes
    ----------
    sechant : int
        Le nombre de jour pendant lesquels la feuille a séché sur la branche en automne

    Methods
    -------
    statut()
        renvoie "vivante" si la feuille est accrochée et ne sèche pas, "séchante" sinon.
    vieillir()
        Renvoie vrai ou faux selon si la feuille est encore attachée à la branche
    couleur()
        renvoie "vert" si la feuille est vivante, "jaune" si elle sèche depuis au plus 5 jours, "marron" sinon.

    """

    def __init__(self,sechant=0):

        """
        Parameters
        ----------
        sechant: int
            Le nombre de jour pendant lequel la feuille a sèché
        """

        self.sechant = sechant

    def statut(self):

        """

        renvoie "vivante" si la feuille est accrochée et ne sèche pas, "séchante" sinon.

        """

        if self.sechant == 0 :
            return('vivante')
        return('séchante')

    def vieillir(self,saison, hiv = 100, pri = 0, ete = 0, aut = 5) :

        """

        Renvoie vrai si la feuille est toujours accrochée, faux sinon

        De manière aléatoire : la feuille peut commencer à sécher avec des probabilités determinables. (en pourcentage)

        Parameters
        ----------
        Saison : int
            Les saisons dans l'ordre de 0 à 3 : Hiver, printemps, été, automne
        hiv : float, optional
            La probabilité que la feuille commence à sécher spontanément en hiver
        pri : float, optional
            La probabilité que la feuille commence à sécher spontanément au printemps
        ete : float, optional
            La probabilité que la feuille commence à sécher spontanément en été
        aut : float, optional
            La probabilité que la feuille commence à sécher spontanément en automne
        """

        proba_sais = [hiv,pri,ete,aut]

        #Continuer à sécher : 

        if 0<self.sechant<11 :
            self.sechant += 1

        #Commencer à sécher :

        elif random.random() < proba_sais[saison]/100 :
            self.sechant = 1

        #Tomber naturellement : 

        if random.random()< 0.05/100 :
            self.sechant += 1

        return(self.sechant<11)

    def couleur(self):

        """

        renvoie "vert" si la feuille est vivante
        "jaune" si elle sèche depuis au plus 5 jours
        "marron" sinon

        """

        if self.sechant == 0 :
            return('vert')

        if self.sechant < 6 :
            return('jaune')

        return('marron')

from petale import Petale
from fleur import Fleur
from feuille import Feuille
import random


class Branche:

    """
    Une classe utilisée pour représenter une branche et ses caractéristiques

    On s'intéresse à son épaisseur et à son angle par rapport à la branche mère
    ...

    Attributes
    ----------
    epaisseur : float
        L'épaisseur de la branche qui vaut 5 à l'origine
    angle : float
        L'angle par rapport à la branche parente

    Methods
    -------
    nb_branches()
        retourne le nombre de branches filles
    nb_feuilles()
        retourne le nombre de feuilles de la branche, ou de ses branches filles (et de leurs branches filles, etc.)
    nb_fleurs()
        retourne le nombre de fleurs (qui ne sont pas des fruits) de la branche, ou de ses branches filles (et de leurs branches filles, etc.)
    nb_fruits()
        retourne le nombre de fruits de la branche, ou de ses branches filles (et de leurs branches filles, etc.)
    vieillir(saison)
        renvoie vrai si la branche est encore accrochée, faux sinon. Retourne aussi le nombre de fruits tombés au sol.

    """


    def __init__(self,angle,epaisseur=5,__feuille = [Feuille()],__fleur = [],__branche = []):

        """
        Parameters
        ----------
        angle : float
            L'angle formé avec la branche mère
        epaisseur : float
            L'épaisseur initiale de la branche
        feuille : list
            La liste des feuille de la branche
        fleur : list
            La liste des fleurs de la branche
        branche : list
            La liste des branches filles de cette branche

        """

        self.epaisseur = 5
        self.angle = angle
        self.__feuille = [Feuille()]
        self.__fleur = []
        self.__branche = []

    def nb_branches(self):

        """
        Retourne le nombre de branches filles attachées à cette branche

        """
        return(len(self.__branche))

    def nb_feuilles(self):

        """
        Retourne le nombre de feuilles attachées à cette branche

        Soit il n'y a pas de branches filles, on ne rentre pas dans la boucle et on renvoit le nombre de feuilles

        Soit il y a des branches filles, (qui peuvent elle même en avoir, etc), et on rentre dans l'algorithme récursif
        On va descendre la structure d'arbre afin d'aller récupérer l'information des branches inférieures et remonter en sommant toutes les feuilles

        """
        s = 0

        for branche_fille in self.__branche :
            s = s + branche_fille.nb_feuilles()

        return(len(self.__feuille) + s )

    def nb_fleurs(self):

        """
        Retourne le nombre de fleurs (qui ne sont pas des fruits) attachées à cette branche

        Soit il n'y a pas de branches filles, on ne rentre pas dans la boucle et on renvoit le nombre de feuilles

        Soit il y a des branches filles, (qui peuvent elle même en avoir, etc), et on rentre dans l'algorithme récursif
        On va descendre la structure d'arbre afin d'aller récupérer l'information des branches inférieures et remonter en sommant les fleurs

        C'est la même procédure que pour compter les feuilles avec les fleurs.
        """

        s = 0

        for branche_fille in self.__branche :
            s = s + branche_fille.nb_fleurs()

        return(len([f for f in self.__fleur if not(Fleur.est_fruit(f))]) + s)

    def nb_fruits(self):

        """
        Retourne le nombre de fruits attachés à cette branche

        Soit il n'y a pas de branches filles, on ne rentre pas dans la boucle et on renvoit le nombre de feuilles

        Soit il y a des branches filles, (qui peuvent elle même en avoir, etc), et on rentre dans l'algorithme récursif
        On va descendre la structure d'arbre afin d'aller récupérer l'information des branches inférieures et remonter en sommant les fleurs

        C'est la même procédure que pour compter les feuilles avec les fruits.
        """
        s = 0

        for branche_fille in self.__branche :
            s = s + branche_fille.nb_fruits()

        return(len([f for f in self.__fleur if f.est_fruit()]) + s)

    def vieillir(self,saison, casse = 0.1, fleurp = 1.5, feuillep = 3):

        """
        La branche a une probabilité de casser qui dépend de l'épaisseur, on retourne alors faux et le nombre de fruits sur la branche et ses branches filles (tuple en Python).

        Sinon, l'épaisseur grossit de 0.015.
        Les feuilles vieillissent, et si elles tombent, elles sont retirées de la liste correspondante.
        Les fleurs vieillissent, et si un fruit tombe, il est comptabilisé pour retourner le nombre de fruits tombés.
        Chaque jour, les branches filles vieillissent, et les fruits tombés de ces branches sont comptabilisés. Si une branche fille casse, elle est retirée de la liste correspondante.
        Au printemps, si la branche n’a ni feuille, ni fleur, ni branche fille, alors entre 2 et 5 branches sont créées, avec des angles aléatoires.
        Au printemps, si la branche a au moins une feuille, la branche peut voir pousser des feuilles et des fleurs. Une feuille est ajoutée avec probabilité feuillep (en pourcentage). Une fleur est ajoutée avec une probabilité fleurp (en pourcentage)

        Parameters
        ----------
        saison : int
            La saison en cours (de 0 pour hiver à 3 pour l'automne)
        casse : float, optional
            Un paramètre qui permet de changer la probabilité de casser la branche spontanément
        fleurp : float, optional
            La probabilité de voir pousser une fleur au printemps
        feuillep : float, optional
            La probabilité de voir pousser une feuille au printemps
        """

        probac = 0.1 / self.epaisseur

        #Si on casse :

        if random.random()< probac/100:
            return(False,Branche.nb_fruits(self) + sum([Branche.nb_fruits(b) for b in self.__branche]))

        #Sinon, on grossit :

        self.epaisseur+=0.015

        #On fait vieillir les feuilles et on retire celles mortes :
        j=0
        for k in range(len(self.__feuille)) :
            if len(self.__feuille) == 0 :
                break

            if not(Feuille.vieillir(self.__feuille[k-j],saison)) :
                del self.__feuille[k-j]
                j+=1

        #On fait vieillir les fleurs et on compte les fruits si ils tombent
        F = 0
        j=0
        for k in range(len(self.__fleur)) :
            if len(self.__fleur) == 0 :
                break
            B1 = self.__fleur[k-j].vieillir()
            if not(B1) and self.__fleur[k-j].est_fruit() :
                F += 1

            if not(B1) :
                del self.__fleur[k-j]
                j+=1

        #On fait vieillir les branches filles qui peuvent casser et on compte leurs fruits
        j=0
        for k in range(len(self.__branche)) :
            if len(self.__branche) == 0 :
                break
            L1 = self.__branche[k-j].vieillir(saison)
            if not(L1[0]) :
                F += L1[1]
                del self.__branche[k-j]
                j+=1

        # Des nouvelles branches/feuilles/fleurs peuvent pousser au printemps:

        if saison == 1 and self.nb_feuilles() == 0 and self.nb_fleurs() == 0 and self.nb_branches() == 0:
            for k in range(random.randint(2,5)):
                self.__branche.append(Branche(random.randint(-90,90)))

        if (saison == 1) and (Branche.nb_feuilles != 0) :
            if random.random()< (feuillep/100) :
                self.__feuille.append(Feuille())

            if random.random() < (fleurp/100) :
                self.__fleur.append(Fleur())

        return(True,F)

    def couleur(self) :
        
        """
        Renvoit un dictionnaire contenant la proportion de chaque couleur sur la branche

        Utilise la fonction somme_dic pour sommer deux dictionnaires ayant des clés communes (ou non).
        """

        def somme_dic(d1,d2) :
            for key in d2 :
                if key in d1 :
                    d1.update({ key : d1[key] + d2[key] })
                else :
                    d1.update({key : d2[key]})

        dic = {"marron" : 0,"orange" : 0,"jaune" : 0,"rouge" : 0,"vert" : 0}

        nbranches = len(self.__branche) + 1
        nfeuilles = len(self.__feuille)
        nfleurs = len(self.__fleur)

        N = nbranches + nfeuilles + nfleurs

        #Pour les branches filles
        dicbs = {"marron" : self.epaisseur/5,"orange" : 0,"jaune" : 0,"rouge" : 0,"vert" : 0}
        for k in range(nbranches-1):
            dicb = Branche.couleur(self.__branche[k])
            somme_dic(dicbs,dicb)


            #Un dictionnaire contenant la proportion de branches de chaque couleur
        s=0
        for key in dicbs :
            s = s + dicbs[key]

        for key in dicbs :
            dicbs.update({key : dicbs[key]/s})

        #Pour les feuilles :
        dicfes = {"marron" : 0,"orange" : 0,"jaune" : 0,"rouge" : 0,"vert" : 0}
        for k in range(nfeuilles):
            keyfe = Feuille.couleur(self.__feuille[k])
            dicfes.update({keyfe : dicfes[keyfe]+1})

            #Un dictionnaire contenant la proportion de feuilles de chaque couleur

        for key in dicfes :
            if not(nfeuilles == 0) :
                dicfes.update({key : dicfes[key]/nfeuilles})
            else : 
                dicfes.update({key : 0})
        #Pour les fleurs :
        dicfls = {"marron" : 0,"orange" : 0,"jaune" : 0,"rouge" : 0,"vert" : 0}
        for k in range(nfleurs):
            dicfl = Fleur.couleur(self.__fleur[k])
            somme_dic(dicfls,dicfl)

        for key in dicfls :
            dic.update({key : dicfls[key]})

        #On somme ensuite les proportions sur la branche:
        for key in dic :
            dic.update({ key : (nbranches*dicbs[key] + nfeuilles*dicfes[key] + nfleurs*dicfls[key])/N})

        return(dic)

from feuille import Feuille
from branche import Branche

class Arbre(Branche):

    """
    Une classe qui représente un arbre composé de branches

    On s'intéresse à son angle et à ses coordonées
    ...

    Attributes
    ----------
    angle : float
        l'angle de la branche initiale
    coordonees : tuple
        la position de l'arbre dans la forêt

    """

    def __init__(self,coordonnees,angle=0,epaisseur=5,__feuille = [Feuille()],__fleur = [],__branche = []):

        """
        Parameters
        ----------
        angle : float
            l'angle de la branche
        coordones: tuple
            Les coordonees géographiques de l'arbre
        """

        self.angle = 0
        self.coordonnees = coordonnees
        super().__init__(epaisseur,__feuille,__fleur,__branche)

from branche import Branche
from arbre import Arbre
import random


class Foret:

    """
    Une classe pour représenter les arbres dans une forêt
    ...

    Attributes
    ----------
    saison : int
        Permet de définir la saison (0 pour Hiver à 3 pour Automne)
    jour_saison: int
        De 0 à 91, définit quel est le jour de la saison
    nb_graines : str
        Le nombre de fruits tombés l'année précédente à la même époque

    Methods
    -------
    __add__()
        Permet de faire vieillir la forêt d'un nombre de jour donné en utilisant le symbole +
    couleur()
        Permet d'obtenir les proportions de chaque couleur dans la forêt
    """

    def __init__(self, graines, nb_arbres,__arbres = [],fruits = 0,jour = 1, saisonf = 1,probga = 0.1):

        """
        Parameters
        ----------
        saisonf : int
            La saison en cours (de 0 pour l'hiver à 3 pour l'automne)
        jour : str, optional
            Le jour actuel (de 0 à 91)
        graines : int, optional
            Le nombre de graines
        nb_arbres : int, optional
            Le nombre d'arbres 
        probga : float
            La probabilité qu'une graine donne un arbre
        fruits : int
            Le nombre de fruits dans la forêt
            
        """

        self.fruits = fruits
        self.saison = saisonf
        self.jour = jour
        self.graines = graines
        self.nb_arbres = nb_arbres
        self.probga = probga
        self.__arbres = [Arbre((random.random(),random.random())) for i in range(self.nb_arbres)]

    def __add__(self,jours=1) :

        """
        
        Permet de faire vieillir la forêt d'un certain nombre de jours en une seule commande

        Parameters
        ----------
        jours : int, optional
            Le nombre de jours dont la forêt vieilli

        """

        #on fait vieillir les arbres et on compte les fruits

        for k in range(jours) :
            F=0
            j=0
            for k in range(len(self.__arbres)) :

                if len(self.__arbres) == 0 :
                    break

                L1 = Branche.vieillir(self.__arbres[k-j],self.saison)
                if not(L1[0]):
                    del self.__arbres[k-j]
                    F+=L1[1]
                    j+=1
                if L1[0]:
                    F+=L1[1]
            self.graines += F

            #on fait pousser les graines au début du printemps
            if (0<self.jour<16 and self.saison == 1):
                for g in range(self.graines):
                    if random.random()<self.probga :
                        self.graines -= 1
                        self.nb_arbres += 1
                        self.__arbres.append(Arbre((random.random(),random.random())))

            #on change de jour et de saison si besoin
            self.jour += 1
            if self.jour == 91 :
                self.jour = 1
                self.saison += 1

                if self.saison == 4:
                    self.saison = 0

            
            self.fruits = F

    def couleur(self):

        
        """
        Permet de calculer les proportions des couleurs dans la forêt à un instant donné

        On utilise la somme des dictionnaires avant de recalculer une moyenne pondéré.

        Une feuille possède le même poids qu'une branche et qu'une fleur (ainsi qu'un fruit).

        """

        def somme_dic(d1,d2) :
            for key in d2 :
                if key in d1 :
                    d1.update({ key : d1[key] + d2[key] })
                else :
                    d1.update({key : d2[key]})


        dic = {"marron" : 0,"orange" : 0,"jaune" : 0,"rouge" : 0,"vert" : 0}

        narbres = len(self.__arbres)

        for k in range(narbres):
            dica = Branche.couleur(self.__arbres[k])
            somme_dic(dic,dica)

        for key in dic :
            dic.update({key : dic[key]/narbres})

        return dic

    def __str__(self):

        """

        permet un affichage des informations de la forêt

        """


        Tb=0
        Tfe=0
        Tfl=0
        n=len(self.__arbres)
        for k in range(len(self.__arbres)):
            Tb += Branche.nb_branches(self.__arbres[k])
            Tfe += Branche.nb_feuilles(self.__arbres[k])
            Tfl += Branche.nb_fleurs(self.__arbres[k])
        Tb = Tb/n
        Tfe = Tfe/n
        Tfl = Tfl/n

        dic = Foret.couleur(self)

        modele = '\n'.join([
            '{}',
            ' saison : {}',
            ' jour : {}',
            '   fruits du jour : {}',
            '   nombre d arbres : {}',
            '   moyenne du nombre de branches par arbre : {}',
            '   moyenne du nombre de feuilles par arbre: {}',
            '   moyenne du nombre de fleurs par arbre: {}',
            '   marron : {}',
            '   orange : {}',
            '   jaune : {}',
            '   rouge : {}',
            '   vert : {}'])
        return modele.format(
            type(self).__name__,
            self.saison,
            self.jour,
            self.fruits,
            self.nb_arbres,
            Tb,
            Tfe,
            Tfl,
            dic["marron"],
            dic["orange"],
            dic["jaune"],
            dic["rouge"],
            dic["vert"])

from petale import Petale
from fleur import Fleur
from feuille import Feuille
from branche import Branche
from arbre import Arbre
from foret import Foret


#test des classes

a=Petale()
f=Fleur()
b=Branche(2)
fe=Feuille(11)
A=Arbre(0,1)
F=Foret(20,20)

#test de l'évolution d'une forêt sur 100 jours

for i in range (2000):
    F + 1
    if F.fruits != 0 :
        print(F)
        print(i)


