# PREUD'HOMME BONTOUX Geoffrey - PeiP 12 - 2014/2015
# TP n°8 donné le 14/11/2014 - Dessiner avec la tortue ep02
# http://www.fil.univ-lille1.fr/~allegraud/info_l1s1/tp_chaines_et_listes.html

from turtle import *
import doctest

# Réalisez une procédure nommée dessine


def dessine(ordres, l, α):
    """
    Trace sur turtle la séquence d'ordre donnée, avec la longueur l et l'angle α
    donné.

    CU : ordres str, l numérique, α numérique → ∅ 
    """
    assert(type(ordres) is str), "ordres doit être du type str"
    assert(type(l) is float or type(l) is int), "l doit être numérique"
    assert(type(α) is float or type(α) is int), "α doit être numérique"

    for i in ordres:
        if i == 'F' or i == 'G':
            forward(l)
        elif i == '+':
            left(α)
        elif i == '-':
            right(α)
        else:
            assert (False), "ordres doit être composé des caractères 'F', 'G', '+' \
ou '-'"

# Testez votre procédure sur l’exemple précédent.

# dessine('F--F--F', 100, 60)

# Tracez chacune des figures ci-dessous :

# un carré

# dessine('F-F-F-F', 100, 90)

# un hexagone régulier

# dessine('F-F-F-F-F-F', 100, 360/6)

# (facultatif) une maison

# Version compressée

# reset()
# dessine('FFF+++FF+++F+++FF+++FFF+++FFFFF+FFFFF++++FFFFF++++FFFFF++++++FFFFF+++FFFFF', 10, 30)

# Version expliquée

# L = 10  # On choisi une longueur sufisament petite pour les détails
# ANGLE_DEG = 30  # On a besoin d'angles de 90° (pour les murs) et de 60° (pour
# # le triangle équilatéral qui sert de toit), on prend donc le PGCD des deux
# MUR = 5
# ANGLE_DROIT = 90 // ANGLE_DEG
# ANGLE_TRIANGLE_DEG = 180 // 3
# ROTATION_TRIANGLE_DEG = 180 - ANGLE_TRIANGLE_DEG
# ROTATION_TRIANGLE = ROTATION_TRIANGLE_DEG // ANGLE_DEG
# DEMITOUR = 180 // ANGLE_DEG
# DECALAGE_PORTE_GAUCHE = MUR // 2
# HAUTEUR_PORTE = 2
# LARGEUR_PORTE = 1
# DECALAGE_PORTE_DROITE = MUR - DECALAGE_PORTE_GAUCHE

# reset()
# dessine(# Mur du bas et porte 
#           'F' * (DECALAGE_PORTE_GAUCHE + LARGEUR_PORTE)
#         + '+' * ANGLE_DROIT
#         + 'F' * HAUTEUR_PORTE
#         + '+' * ANGLE_DROIT
#         + 'F' * LARGEUR_PORTE
#         + '+' * ANGLE_DROIT
#         + 'F' * HAUTEUR_PORTE
#         + '+' * ANGLE_DROIT
#         + 'F' * DECALAGE_PORTE_DROITE
#         # Mur de droite
#         + '+' * ANGLE_DROIT
#         + 'F' * MUR
#         # Toit
#         + '-' * ANGLE_DROIT
#         + '+' * ROTATION_TRIANGLE
#         + 'F' * MUR
#         + '+' * ROTATION_TRIANGLE
#         + 'F' * MUR
#         + '+' * ROTATION_TRIANGLE
#         + 'F' * MUR
#         + '+' * DEMITOUR
#         + 'F' * MUR
#         + '+' * ANGLE_DROIT
#         + 'F' * MUR, L, ANGLE_DEG)

# (facultatif) un mouton

# Version compressée

# reset()
# dessine('FFFFFFF+++FFF+++FFFFFFF+++FFF+++FFFFFFF+FFF++FFF++++FFF++++++FFF+++++FFFFFFF+FFF', 20, 30)

# Version expliquée

# L = 20
# ANGLE_DEG = 30
# LARGEUR = 7
# LONGUEUR = 3
# PROFONDEUR = 3
# ANGLE_DROIT = 90 // ANGLE_DEG
# ANGLE_PERSPECTIVE = 1
# DEMITOUR = 180 // ANGLE_DEG
# 
# print(# Face avant
#       'F' * LARGEUR
#     + '+' * ANGLE_DROIT
#     + 'F' * LONGUEUR
#     + '+' * ANGLE_DROIT
#     + 'F' * LARGEUR
#     + '+' * ANGLE_DROIT
#     + 'F' * LONGUEUR
#     + '+' * ANGLE_DROIT
#     # Arète oblique en bas
#     + 'F' * LARGEUR
#     + '+' * ANGLE_PERSPECTIVE
#     + 'F' * PROFONDEUR
#     # Arète verticale à droite
#     + '+' * (ANGLE_DROIT - ANGLE_PERSPECTIVE)
#     + 'F' * LONGUEUR
#     # Arète oblique au milieu
#     + '+' * (ANGLE_DROIT + ANGLE_PERSPECTIVE)
#     + 'F' * PROFONDEUR
#     + '+' * DEMITOUR
#     + 'F' * PROFONDEUR
#     # Arète horizontale en haut
#     + '+' * (DEMITOUR - ANGLE_PERSPECTIVE)
#     + 'F' * LARGEUR
#     # Arète oblique à gauche
#     + '+' * ANGLE_PERSPECTIVE
#     + 'F' * PROFONDEUR
#     , L, ANGLE_DEG)


# « Ça c'est la caisse. Le mouton que tu veux est dedans. »
# - Antoine de Saint Exupery

# Dériver un ordre par rapport à une variable

# Soit une chaîne so et une autre chaîne r. On appelle la dérivation de so
# par r par rapport à 'F' la chaîne obtenue en remplaçant chacune des
# occurrences du caractère 'F' de so par tous les caractères de r.

# Par exemple, la dérivation de 'F+F' par 'F-F' est 'F-F+F-F'.

# Réalisez une fonction derive qui revoie la dérivation de la première chaîne passée en paramètre par la seconde par rapport à 'F'.
# On définit la dérivée n-ième de so par r par rapport à 'F' comme le
# terme un de la suite (un)n∈N définie par récurrence :

def derive(so, r):
    """
    Revoie la dérivation de la première chaîne passée en paramètre par la
    seconde par rapport à 'F'.
    Remplace dans une chaîne donnée toutes les occurences d’un caracère donné
    par une autre chaîne de caractères.

    CU : so str et r str → str

    >>> derive('', '++')
    ''
    >>> derive('F+F', 'F-F')
    'F-F+F-F'
    """
    assert(type(so) is str), "so doit être du type str"
    assert(type(r) is str), "r doit être du type str"

    retour = ''
    for i in so:
        if i == 'F':
            retour += r
        else:
            retour += i
    return retour

# Programmez une fonction derive_n


def derive_n(so, r, n):
    """
    Renvoie la dérivation n-ième de la première chaîne par la seconde par
    rapport à 'F'

    CU : CU : so str et r str et n int → str

    >>> derive_n('F+F', 'F-F', 3)
    'F-F-F-F-F-F-F-F+F-F-F-F-F-F-F-F'
    >>> derive_n('FFF', 'F+F+F', 0)
    'FFF'
    """
    assert(type(n) is int), "n doit être du type int"
    # derive() s'occupe des autres assertions, inutile de vérifier so deux fois

    retour = so
    for i in range(0, n):
        retour = derive(retour, r)
    return retour


# Fractales

# N’hésitez pas à accélerer la tortue en utilisant la fonction speed du
# module turtle.

speed(0)

# Merci.

# Tracez pour n compris entre 0 et 5, l=35−n et α=60 le résultat de la séquence
# d’ordres obtenu en dérivant n fois 'F--F--F' par 'F+F--F+F'

# n = 3
# l = 3 ** (5 - n)
# α = 60
# ordres = derive_n('F--F--F', 'F+F--F+F', n)
# dessine(ordres, l, α)


# Essayez maintenant de dériver plusieurs fois 'F' par 'F+F-F-F+F' avec un angle
# de 90°.

# dessine(derive_n('F', 'F+F-F-F+F', 3), 5, 90)


# Dérivation à plusieurs variables

# Écrivez une fonction cherche_regle

def cherche_regle(ordre, liste_vars, liste_regles):
    """
    Renvoie ordre si ordre n’appartient pas à liste_vars ou liste_regles[i] si
    il existe un indice i vérifiant ordre == liste_vars[i].

    CU : ordre str de taille 1, liste_vars une liste contenant exclusivement des
    types str, liste_regles une liste contenant exclusivement des types str de
    même taille que liste_vars → str

    >>> cherche_regle('F', ['F', 'G'], ['F-F', 'G+G'])
    'F-F'
    >>> cherche_regle('A', ['F', 'G'], ['F-F', 'G+G'])
    'A'
    """
    assert(type(ordre) is str and len(ordre) == 1), "ordre doit être du type \
    str et être de taille 1"
    assert(type(liste_vars) is list), "liste_vars doit être du type list"
    for i in liste_vars:
        assert(type(i) is str), "liste_vars doit contenir exclusivement des \
        types str"
    assert(type(liste_regles) is list), "liste_regles doit être du type list"
    for i in liste_regles:
        assert(type(i) is str), "liste_regles doit contenir exclusivement des \
        types str"
    assert(len(liste_vars) == len(liste_regles)), "liste_regles doit être de \
    même taille que liste_vars"

    if (ordre in liste_vars):
        return liste_regles[liste_vars.index(ordre)]
    else:
        return ordre

# Écrivez une fonction derive_mult


def derive_mult(so, liste_regles, liste_vars):
    """
    Renvoie la dérivation de so par liste_regles par rapport à liste_vars.

    CU : so str, liste_vars une liste contenant exclusivement des types str,
    liste_regles une liste contenant exclusivement des types str de même taille
    que liste_vars → str

    >>> derive_mult('FGF', ['F', 'G'], ['GG', 'FFF'])
    'GGFFFGG'
    """
    assert(type(so) is str), "so doit être de type str"
    # cherche_regle s'occupe des autres assertions

    retour = ''
    for i in so:
        retour += cherche_regle(i, liste_regles, liste_vars)
    return retour

# Écrivez une fonction derive_mult_n


def derive_mult_n(so, liste_regles, liste_vars, n):
    """
    Renvoie la dérivée de la séquence d’ordres par la liste de règles par
    rapport à la liste des variables.

    CU : so str, liste_vars une liste contenant exclusivement des types str,
    liste_regles une liste contenant exclusivement des types str de même taille
    que liste_vars, n int → str

    >>> derive_mult_n('FGF', ['F', 'G'], ['GG', 'FFF'], 3)
    'GGGGGGGGGGGGFFFFFFFFFFFFFFFFFFGGGGGGGGGGGG'
    """
    assert(type(n) is int), "n doit être du type int"
    # deriv_mult() s'occupe des autres assertions

    retour = so
    for i in range(0, n):
        retour = derive_mult(retour, liste_regles, liste_vars)
    return retour

# Tracez pour n compris entre 0 et 6, l=27−n et α=90 le résultat de la séquence
# d’ordres obtenu en dérivant 2n fois 'FX' par ['X+YF+', '-FX-Y'] par rapport
# à ['X','Y'].

# n = 4
# l = 2 ** (7 - n)
# α = 90
# ordres = derive_mult_n('FX', ['X', 'Y'], ['X+YF+', '-FX-Y'], 2 * n)
# ordres = derive_mult(ordres, ['X', 'Y'], ['', '']) # Pour éviter les erreurs
#                                                    # d'assertion
# dessine(ordres, l, α)


# Sauvegarde d’une position

# Écrivez une commande sauvegardant l’état de la tortue
# dans une liste de 3 éléments.

etat = [0, 0, 0]


def sauvegarder_etat():
    """
    Sauvegarde l’état de la tortue dans la variable globale etat

    CU : ∅ → ∅
    """

    global etat
    etat[0] = xcor()
    etat[1] = ycor()
    etat[2] = heading()

# Écrivez une commande permettant de restaurer un état contenu
# dans une liste à 3 éléments.


def restaurer_etat():
    """
    Restaure l’état de la tortue depuis la variable globale etat

    CU : ∅ → ∅
    """

    global etat
    penup()
    goto(etat[0], etat[1])
    setheading(etat[2])
    pendown()

# Modifiez votre procédure dessine pour prendre en compte les deux
# nouveaux ordres.


def dessine2(ordres, l, α):
    """
    Trace sur turtle la séquence d'ordre donnée, avec la longueur l et l'angle α
    donné.

    CU : ordres str, l numérique, α numérique → ∅ 
    """
    assert(type(ordres) is str), "ordres doit être du type str"
    assert(type(l) is float or type(l) is int), "l doit être numérique"
    assert(type(α) is float or type(α) is int), "α doit être numérique"

    for i in ordres:
        if i == 'F' or i == 'G':
            forward(l)
        elif i == '+':
            left(α)
        elif i == '-':
            right(α)
        elif i == '(':
            sauvegarder_etat()
        elif i == ')':
            restaurer_etat()
        else:
            assert (False), "ordres doit être composé des caractères 'F', 'G', '+' \
'-', '(' ou ')'"

# Tracez pour n compris entre 0 et 6, l=4 et α=60 le résultat de la
# séquence d’ordres obtenu en dérivant n fois 'G' par ['FF', 'F+(G)--G']
# par rapport à ['F','G'].

# n = 6
# l = 4
# α = 60
# ordres = derive_mult_n('G', ['F', 'G'], ['FF', 'F+(G)--G'], n)
# dessine2(ordres, l, α)


def tester():
    doctest.testmod(verbose=True)