<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># -*- coding:Utf-8 -*-


##############################################################################
#             GÃ©nÃ©rateur de terrains alÃ©atoires bidimensionnels              #
#                                                                            #
#         Ce script mettant en place une interface graphique tkinter         #
#    permet la gÃ©nÃ©ration de terrains en deux dimensions (vus en coupe).     #
#                                                                            #
#                               Pour python 3                                #
#                                                                            #
#                              Modules requis :                              #
#         tkinter (interface graphique de la bibliothÃ¨que standard)          #
#                                                                            #
#                                                                            #
#  Merci d'utiliser ce programme, n'hÃ©sitez pas Ã&nbsp; en signaler les Ã©ventuels  #
#                            bugs ou faiblesses !                            #
##############################################################################


from tkinter import *
from tkinter.ttk import Combobox
from random import random as rand
from random import randrange
from math import log as ln


class Default():
    """Valeur par dÃ©faut d'un argument
    """
    pass


class Random():
    """GÃ©nÃ©rateur de nombres alÃ©atoires

    Le nombre alÃ©atoire est calculÃ© lors d'une opÃ©ration ou d'une conversion
    """
    def __init__(self, min_value=0, max_value=1, value_type=float):
        """Constructeur du gÃ©nÃ©rateur

        min_value=0 : (int, float)
            valeur minimale du nombre alÃ©atoire (incluse)

        max_value=1 : (int, float)
            valeur maximale du nombre alÃ©atoire (exclue)

        value_type=float : int | float
            type du nombre alÃ©atoire : entier ou flottant
        """
        if value_type is not int and value_type is not float:
            raise ValueError("value_type doit Ãªtre int ou float")

        self._min_value = min_value
        self._max_value = max_value
        self._value_type = value_type

    def __repr__(self):
        """Renvoie une chaÃ®ne de caractÃ¨res reprÃ©sentant l'objet
        """
        return "Random(%s, %s, %s)" % (self._min_value, self._max_value,
                                       self._value_type)

    ###       On surcharge les fonctions de conversion et de calcul        ###
    ###     pour renvoyer un nombre alÃ©atoire juste avant l'opÃ©ration      ###

    def __int__(self):
        """Calcule le nombre alÃ©atoire et le convertit en int
        """
        return int(self._generate())

    def __float__(self):
        """Calcule le nombre alÃ©atoire et le convertit en float
        """
        return float(self._generate())

    def __add__(self, other):
        """Calcule le nombre alÃ©atoire et lui additionne other
        """
        return self._generate() + other

    def __radd__(self, other):
        """Calcule le nombre alÃ©atoire et l'additionne Ã&nbsp; other
        """
        return other + self._generate()

    def __sub__(self, other):
        """Calcule le nombre alÃ©atoire et en soustrait other
        """
        return self._generate() - other

    def __rsub__(self, other):
        """Calcule le nombre alÃ©atoire et le soustrait de other
        """
        return other - self._generate()

    def __mul__(self, other):
        """Calcule le nombre alÃ©atoire et le multiplie par other
        """
        return self._generate() * other

    def __rmul__(self, other):
        """Calcule le nombre alÃ©atoire et le multiplie par other
        """
        return other * self._generate()

    def __truediv__(self, other):
        """Calcule le nombre alÃ©atoire le divise par other
        """
        return self._generate() / other

    def __rtruediv__(self, other):
        """Calcule le nombre alÃ©atoire et divise other par lui
        """
        return other / self._generate()

    def __floordiv__(self, other):
        """Calcule le nombre alÃ©atoire et le divise (division entiÃ¨re) par other
        """
        return self._generate() // other

    def __rfloordiv__(self, other):
        """Calcule le nombre alÃ©atoire et divise(division entiÃ¨re) other par lui
        """
        return other // self._generate()

    def __pow__(self, other, modulo=None):
        """Calcule le nombre alÃ©atoire et l'Ã©lÃ¨ve Ã&nbsp; la puissance other
        """
        return pow(self._generate(), other, modulo)

    def __rpow__(self, other, modulo=None):
        """Calcule le nombre alÃ©atoire en Ã©lÃ¨ve other Ã&nbsp; la puissance
        """
        return pow(other, self._generate(), modulo)

    def __mod__(self, other, modulo=None):
        """Calcule le nombre alÃ©atoire le renvoie modulo other
        """
        return self._generate() % other

    def __rmod__(self, other, modulo=None):
        """Calcule le nombre alÃ©atoire et renvoie other modulo lui
        """
        return other % self._generate()

    ##########################################################################

    def _generate(self):
        """Renvoie un nombre alÃ©atoire
        """
        if self._value_type is int:
            return randrange(self._min_value, self._max_value)

        else:
            return rand()*(self._max_value - self._min_value) + self._min_value


################################## PRESETS ###################################
#             Ces variables globales peuvent Ãªtre passÃ©es comme              #
#                argument preset du constructeur de Terrain2D                #

CENTERED_MOUNTAIN = [[Random(0.75, 1.25)], [0, 0]]
TWO_MOUNTAINS = [None, [Random(0.75, 1.5), Random(0.75, 1.5)]]
HUGE_MOUNTAIN = [[Random(1., 1.75)], [Random(0.75, 1.25), Random(0.75, 1.25)]]

VOLCANO = [[Random(0.75, 1.5)], [0, 0], [0.25, 0.1, 0.1, 0.25]]
HIGH_VOLCANO = [[Random(1., 1.75)], [0, 0]]

PRESETS = {"Aucun": [],
           "Montagne centrÃ©e": CENTERED_MOUNTAIN,
           "Deux montagnes": TWO_MOUNTAINS,
           "Montagne gÃ©ante": HUGE_MOUNTAIN,
           "Volcan": VOLCANO,
           "Haut volcan": HIGH_VOLCANO}

##############################################################################


class XRange:
    """Objet itÃ©rable similaire Ã&nbsp;  range  mais pouvant utiliser des  float
    """
    def __init__(self, *args):
        """Constructeur de l'objet XRange

        Il peut Ãªtre appelÃ© de trois maniÃ¨res:
        XRange(stop)
        XRange(start, stop)
        XRange(start, stop, step)

        start=0 : (int, float)
            PremiÃ¨re valeur de la sÃ©quence (incluse)

        stop : (int, float)
            DerniÃ¨re valeur de la sÃ©quence (exclue)

        step=1 : (int, float):
            IncrÃ©ment entre deux valeurs
        """
        if len(args) &gt; 3 or len(args) &lt; 1:
            raise TypeError("un Ã&nbsp; trois arguments attendus, %s reÃ§us"
                            % (len(args)))

        elif len(args) == 3:
            start, stop, step = args

        elif len(args) == 2:
            start, stop, step = args + [1]

        else:
            start, stop, step = [0] + args + [1]

        self._start = start
        self._stop = stop
        self._step = step

    def __iter__(self):
        """Renvoie l'itÃ©rateur de XRange()
        """
        if isinstance(self._start, int) and isinstance(self._stop, int)\
                and isinstance(self._step, int):
            return iter(range(self._start, self._stop, self._step))

        number = (self._stop - self._start) / self._step

        for i in range(int(number) if number.is_integer() else int(number + 1)):
            yield self.start + i*self.step


class Options:
    """Support d'options se comportant comme un dictionnaire

    Le nom des options est dÃ©fini lors de la crÃ©ation de l'objet.
    La modification de la valeur d'une option appelle une fonction.
    """
    def __init__(self, function, options):
        """Constructeur de l'objet Options()

        function : callable
            Fonction appelÃ©e lors de la modification d'une option
            Elle sera appelÃ©e de la maniÃ¨re suivante:
            function(nom_de_l_option, nouvelle_valeur)
            La modification de l'option est annulÃ©e si la fonction renvoie
            la chaÃ®ne de caractÃ¨res "break"

        options : (dict)
            dictionnaire des options contenues dans l'objet : syntaxe :
            {nom_de_l_option : (valeur_initiale, type_de_la_valeur)}

            type_de_la_valeur peut Ãªtre un type ou un tuple de types.
            Si la valeur peut Ãªtre de n'importe quel type,
            mettre "object" (sans les guillemets).
        """
        self.function = function

        self._options_values = {}
        self._options_types = {}

        for name, (val, typ) in options.items():
            self._options_values[name] = val
            self._options_types[name] = typ

    def __getitem__(self, item):
        """Fonction appelÃ©e par  options[item]
        """
        return self._options_values[item]

    def __setitem__(self, item, value):
        """Fonction appelÃ©e par  options[item] = value
        """
        # vÃ©rification du type de la valeur
        if isinstance(value, self._options_types[item]):
            # appel de la fonction
            if self.function(item, value) != "break":
                self._options_values[item] = value

        else:
            raise TypeError("l'option %s doit Ãªtre de type %s" % (
                            item, self._options_types[item]))

    def __repr__(self):
        """ChaÃ®ne de caractÃ¨res reprÃ©sentant l'objet
        """
        return repr(self._options_values)

    def set(self, item, value):
        """Fonction alternative pour  options[item] = value
        """
        self[item] = value

    def items(self):
        """Renvoie une liste des tuples (clÃ©, valeur) du dictionnaire
        """
        return self._options_values.items()


class ListScale(Frame):
    """Widget Scale() opÃ©rant sur une liste de valeurs et non une plage
    """
    def __init__(self, master=None, values=[0], command=None, cnf={}, **kw):
        """Constructeur du ListScale()

        master=None : (None | Widget)
            Widget tkinter parent

        values=[0] : (list)
            Liste des valeurs de la ListScale

        command=None : (None) | callable
            fonction Ã&nbsp; appeler lors du changement de valeur

        cnf={} : (dict)           |
                                  | Options tkinter (ne pas utiliser "from",
        **kw : keyword arguments  |                  "to" et "command")
        """
        Frame.__init__(self, master)

        self.values = values

        self.command = command

        self.scale = Scale(self, cnf, from_=0, to=len(values) - 1,
                           command=self._changed, **kw)

        if "showvalue" not in cnf and "showvalue" not in kw:
            self.scale["showvalue"] = 0

        self.scale.pack()

        self.label = Label(self, text=values[0])
        self.label.pack()

        # On applique les mÃ©thodes tkinter de l'objet au widget Scale()
        # qu'il contient
        for func in ["__setitem__", "__getitem__",
                     "config", "configure", "coords", "get", "identify", "set"]:
            setattr(self, func, getattr(self.scale, func))

    def _changed(self, index):
        """Fonction appelÃ©e lorsque la valeur change
        """
        v = self.values[int(index)]

        if self.command is not None:
            self.command(v)

        if isinstance(v, int):
            v_str = str(v)
            for i in range(1, int((len(v_str) - 1)/3 + 1)):
                v_str = v_str[:-i * 4 + 1] + " " + v_str[-i * 4 + 1:]
            v = v_str

        self.label["text"] = v


class Terrain2D:
    """Terrain en deux dimensions, longueur et hauteur
    """
    def __init__(self, length=1024, base_height=50, roughness=.3,
                 preset=[]):
        """GÃ©nÃ¨re un terrain alÃ©atoire en deux dimensions (longueur, hauteur)

        length=1024 : (int, float)
            longueur du terrain ; ce doit Ãªtre une puissance de 2 supÃ©rieure Ã&nbsp; 1

        base_height=50 : (int, float, int[2], float[2])
            hauteur du terrain par rapport au niveau de la mer
            Il peut s'agir d'un nombre (hauteur des deux extrÃ©mitÃ©s), ou d'un
            conteneur indexable (list, tuple, ...) de deux nombres
            (hauteur de chaque extrÃ©mitÃ©)

        roughness=0.3 : (int, float)
            rugositÃ© du terrain, entre 0 et 1

        preset=[] : (list, tuple)
            donnÃ©es initiales pour la crÃ©ation du terrain.
            Elles se prÃ©sentent sous la forme d'une liste ou d'un tuple, qui
            contiennent des listes ou tuples d' int  ou de  float  .
            Chaque sous-liste d'index i dans la liste principale doit contenir
            2**i nombres.
            La sous-liste suivante sera utilisÃ©e Ã&nbsp; chaque Ã©tape Ã&nbsp; la place de
            nombres alÃ©atoires.
            Des nombres ou des sous-listes peuvent Ãªtre remplacÃ©s par None,
            auquel cas un nombre alÃ©atoire sera utilisÃ©.
            Cet argument permet de donner une forme d'ensemble au terrain.

        Le terrain est gÃ©nÃ©rÃ© par une mÃ©thode fractale.
        """
        self.roughness = roughness

        if not (ln(length) / ln(2)).is_integer() or length &lt;= 1:
            raise ValueError("length doit Ãªtre une puissance de 2 "
                             "supÃ©rieure Ã&nbsp; 1.\nValeur reÃ§ue : %s" % (length))

        self._t = [None] * int(length + 1)  # Relief du terrain

        try:
            # conteneur
            self._t[0] = base_height[0]
            self._t[-1] = base_height[1]

        except TypeError:
            # nombre seul
            self._t[0] = base_height
            self._t[-1] = base_height

        self._generate(preset)

    def __repr__(self):
        """Renvoie une chaÃ®ne de caractÃ¨res reprÃ©sentant l'objet
        """
        return ("Terrain2D(length=%s, base_height=%s, roughness=%s, preset=%s)"
                % (self.length, self.base_height, self.roughness, self.preset))

    def __iter__(self):
        """Renvoie l'itÃ©rateur sur le terrain
        """
        return iter(self._t)

    def __len__(self):
        """Renvoie la longueur du terrain

        Ã‰quivaut Ã&nbsp;  len(Terrain2D())
        """
        return len(self._t)

    def __getitem__(self, index):
        """Renvoie la hauteur du terrain Ã&nbsp; la position voulue

        Ã‰quivaut Ã&nbsp;  Terrain2D()[index]
        """
        return self._t[item]

    def _generate(self, preset=[]):
        """Fonction interne : gÃ©nÃ¨re le terrain alÃ©atoirement
        """
        preset_length = len(preset)

        length = len(self._t) - 1
        max_step = ln(length) / ln(2)
        step = 1

        while step &lt;= max_step:
            for i in range(2**(step - 1)):
                l = 2*length / 2**step  # Longueur de la portion en traitement
                i0 = int(l * i)
                i2 = int(l * (i + 1))

                i1 = int((i0 + i2) / 2)  # Point du terrain Ã&nbsp; traiter

                # Ã‰lÃ©vation du point
                if step &gt; preset_length or preset[step - 1] is None\
                        or preset[step - 1][i] is None:
                    # Plus de preset, utilisation des nombres alÃ©atoires
                    delta = self.roughness * (rand() - .5) * l
                else:
                    # Utilisation du preset
                    delta = self.roughness * (preset[step - 1][i] - .5) * l

                self._t[i1] = (self._t[i0] + self._t[i2])/2 + delta

            step += 1

    def show(self, canvas, thickness=2):
        """Affiche le terrain sur un canevas

        canvas : (Canvas)
            canevas oÃ¹ afficher le terrain

        thickness=2 : Ã©paisseur de la ligne
        """
        width = int(canvas.winfo_width())
        height = int(canvas.winfo_height())

        scale = max((len(self) / (width - 10),
                     max(self) / (height - 10)))

        y_max = max(self)
        y_list = ((y_max - h) / scale + 5 for h in self)

        coords = []  # Liste des coordonnÃ©es des points sur le canevas
        for x, y in enumerate(y_list):
            coords.extend([x / scale + 5, y])

        canvas.create_line(*coords, width=thickness)

        # Niveau de la mer
        canvas.create_line(5, y_max / scale + 5,
                           len(self) / scale + 5, y_max / scale + 5)


class App(Frame):
    """Application de gÃ©nÃ©ration de terrains 2D
    """
    def __init__(self, cnf={}, **kw):
        """Constructeur de l'application

        cnf={}
        **kw
        """
        self.toplevel = Tk()
        self.toplevel.title("GÃ©nÃ©rateur de terrains 2D")

        Frame.__init__(self, self.toplevel, cnf, **kw)

        self.pack(expand=YES, fill=BOTH)

        # Recherche de la fenÃªtre contenant l'application.

        # CrÃ©ation des attributs
        self.opt = Options(self._set_options, {
                           "width": (0, int),           # largeur du canevas
                           "height": (0, int),          # hauteur du canevas
                           "length": (1024, int),       # longueur du terrain
                           "base_h": (50, object),      # hauteur des extrÃ©mitÃ©s
                           "roughness": (50, (int,      # rugositÃ©
                                              float)),
                           "preset": ([], list),       # forme du terrain
                           })

        # CrÃ©ation des widgets esclaves
        #   Canevas oÃ¹ sera affichÃ©e le terrain
        self.canvas = Canvas(self, bg="White")

        #   Cadre des options
        self.opt_panel = Frame(self, bd=4, relief=SUNKEN, width=100)

        #       Longueur du terrain
        self.s_length = ListScale(self.opt_panel, [2**i for i in range(5, 15)],
                                  lambda v: self.opt.set("length", v),
                                  label="Longueur du terrain",
                                  orient=HORIZONTAL,
                                  length=150, width=8, sliderlength=20)
        self.s_length.grid(row=0, sticky=W)

        #       Altitude des extrÃ©mitÃ©s du terrain
        self.s_base_h = Scale(self.opt_panel,
                              label="Altitude des extrÃ©mitÃ©s",
                              orient=VERTICAL, from_=1000, to=-1000,
                              resolution=50,
                              command=lambda v: self.opt.set("base_h", int(v)),
                              length=100, width=8, sliderlength=20)
        self.s_base_h.grid(row=1, sticky=W)

        #       RugositÃ© du terrain
        self.s_roughness = Scale(self.opt_panel,
                                 label="RugositÃ©",
                                 orient=VERTICAL, from_=1, to=.01,
                                 resolution=.01,
                                 command=lambda v: self.opt.set("roughness",
                                                                float(v)),
                                 length=50, width=8, sliderlength=20)
        self.s_roughness.grid(row=2, sticky=W, padx=6)
        self.s_roughness.set(.3)

        #       Choix du preset
        self.cmb_preset = Combobox(self.opt_panel,
                                   values=sorted(PRESETS.keys()))
        self.cmb_preset.bind("&lt;Return&gt;", lambda e: self.opt.set(
                             "preset", PRESETS.get(self.cmb_preset.get(), [])))
        self.cmb_preset.grid(row=3)

        #       Bouton GÃ©nÃ©rer
        self.b_generate = Button(self.opt_panel, text="GÃ©nÃ©rer",
                                 command=self.generate, width=20)
        self.b_generate.grid(row=5, pady=10)

        self.opt_panel.columnconfigure(0, weight=1)

        #       Label des informations
        self.l_info = Label(self.opt_panel, wrap=150, font="Times 9",
                            text="Informations sur le terrain :")
        self.l_info.grid(row=6, pady=10)

        self.l_info.base_text = "Informations sur le terrain :\n"\
                                "Longueur : {length} m\n"\
                                "RugositÃ© : {roughness}\n"\
                                "Altitude min : {min_height} m\n"\
                                "Altitude max : {max_height} m\n"\
                                "Hauteur totale : {tot_height} m"

        self.opt_panel.columnconfigure(0, weight=1)

        # Affichage des widgets sur le canevas
        self.canvas.grid(row=0, column=0, sticky="nswe")
        self.opt_panel.grid(row=0, column=1, sticky="ns")

        self.columnconfigure(0, weight=1)  # Seul le canevas sera redimensionnÃ©
        self.rowconfigure(0, weight=1)

        # Gestion du redimensionnement
        self.canvas.bind("&lt;Configure&gt;", self._on_resize)
        self.opt["width"] = int(self.canvas["width"])
        self.opt["height"] = int(self.canvas["height"])

        self.toplevel.minsize(300, 280)

        self.mainloop()

    def _on_resize(self, event):
        """Fonction appelÃ©e lorsque le widget est redimensionnÃ©
        """
        self.opt["width"] = event.width
        self.opt["height"] = event.height

    def _set_options(self, option, value):
        """Fonction appelÃ©e lorsqu'une option du canevas est modifiÃ©e
        """

    def generate(self):
        """Dessine la fractale
        """
        self.cmb_preset.event_generate("&lt;Return&gt;")

        self._generate()

    def _generate(self):
        """Fonction interne : dessine la fractale
        """
        self.canvas.delete(ALL)
        self.cur = Terrain2D(self.opt["length"], self.opt["base_h"],
                             self.opt["roughness"], self.opt["preset"])
        self.cur.show(self.canvas)

        info = {}

        for k, v in self.opt.items():
            info[k] = str(v)
        info["min_height"] = "%8.2f" % (min(self.cur))
        info["max_height"] = "%8.2f" % (max(self.cur))
        info["tot_height"] = "%8.2f" % (max(self.cur) - min(self.cur))

        self.l_info["text"] = self.l_info.base_text.format(**info)


if __name__ == '__main__':
    a = App()
</pre></body></html>