• # Beaucoup de temps pour animer et afficher joli

    Posté par (Mastodon) . En réponse au message Avent du Code, jour 14. Évalué à 4.

    Parce que un truc de 351 * 175 dans le terminal c'est galère quand même...

    Bref, j'ai perdu plein de temps à faire une animation pas trop lente (j'ai fini par exponentiellement sauter des images : j'affiche l'image 2n), et réussir à faire entrer ça proprement dans le terminal. Après coup j'ai réalisé qu'il suffisait d'afficher chaque grain de sable une fois calculé, au lieu de tout retracer... Dans un terminal... Diantre...

    J'avais un bug débile aussi pour l'exo 2, comme je voulais un sol pas trop large pour l'affichage, j'ai essayé d'être intelligent, et ça n'a pas marché, j'ai fini par faire un affichage avec un sol pas très large, puis rajouter un sol très très large.

    Après j'ai triché et j'ai juste mis le sol en dur aux bonnes dimensions pour tout afficher, une fois le résultat obtenu.

    Deux belles images :
    Premier exercice
    Deuxième exercice

    Et un code probablement trop complexe.
    L'affichage déjà

    class display:
     sand = " " # "▒"
     sandcolor = 220
     void = " "
     rock = " " # "█"
     rockcolor = 94
     start = "+"
     def __init__(self, x0, x1, y0, y1, *rocks):
     self.x0, self.x1, self.y0, self.y1 = x0, x1, y0, y1
     self.bg = [[
     f"{self.color(bg=0)}{self.void}" for x in range(x0, x1+1)
     ] for y in range(y0, y1+1)]
     for x in rocks:
     self.bg[x[1]-y0][x[0]-x0] = f"{self.color(bg=self.rockcolor)}{self.rock}"
     self.bg[0][500-x0] = self.start
     self.background = "\n".join(
     "".join(self.bg[y][x] for x in range(len(self.bg[0])))
     for y in range(len(self.bg))
     )
     self.width = len(self.bg[0])
     self.height = len(self.bg)
     def color(self, bg=None, fg=None, rgb=None):
     bg = f"033円[48;5;{bg}m" if bg else ""
     fg = f"033円[38;5;{fg}m" if fg else ""
     fg = f"033円[38;2;{rgb[0]};{rgb[1]};{rgb[2]}m" if rgb else fg
     if not bg and not fg:
     return "033円[0m"
     return f"{bg}{fg}"
     def cursor(self, row=0, col=0):
     if row < 0:
     row = self.height - row
     return f"033円[{row+1};{col+1}H"
     def __call__(self, *items, wait=0):
     print(self.str(items))
     if wait:
     time.sleep(wait)
     def item(self, item):
     return (
     f"{self.cursor(col=item[0]-self.x0, row=item[1]-self.y0)}"
     f"{self.color(bg=self.sandcolor)}"
     f"{self.sand}"
     ) if (
     item[0] >= self.x0 and
     item[0] <= self.x1 and
     item[1] >= self.y0 and
     item[1] <= self.y1
     ) else ""
     def str(self, items):
     return (
     f"{self.cursor()}"
     f"{self.color()}"
     f"{self.background}"
     f"{''.join(self.item(item) for item in items)}"
     )

    Et le code en lui-même :

    def input():
     for line in sys.stdin:
     x = list(tuple(map(int, r.split(','))) for r in line.split(' -> '))
     for _ in zip(x[:-1], x[1:]):
     yield _
    class RockFormation:
     def __init__(self, input):
     self.finished = False
     self.source = (500, 0)
     self.carte = set()
     self.sand = set()
     for _ in input():
     self._add_rocks(*_)
     self.dim = self.calcdim(self.carte)
     self.rock = self.carte.copy()
     self.sand = set()
     def _add_rocks(self, r1, r2):
     x1 = min(r1[0], r2[0])
     x2 = max(r1[0], r2[0])
     y1 = min(r1[1], r2[1])
     y2 = max(r1[1], r2[1])
     self.carte.update(
     (x, y)
     for x in range(x1, x2+1)
     for y in range(y1, y2+1)
     )
     def add_rocks(self, r1, r2):
     self._add_rocks(r1, r2)
     self.dim = self.calcdim(self.carte)
     self.rock = self.carte.copy()
     def calcdim(self, carte):
     return (
     min(x[0] for x in carte),
     max(x[0] for x in carte),
     0,
     max(x[1] for x in carte),
     )
     def __call__(self):
     s = self._sand(*self.source)
     if not self.finished:
     if s in self.carte:
     print(f"Re-adding {s} !")
     raise IndexError
     self.carte.add(s)
     self.sand.add(s)
     if s == self.source:
     self.finished = True
     return s
     def _sand(self, x, y):
     if x < self.dim[0] or x > self.dim[1] or y == self.dim[3]:
     self.finished = True
     return x, y
     while (x, y+1) not in self.carte:
     y += 1
     if (x-1, y+1) not in self.carte:
     x, y = self._sand(x-1, y+1)
     elif (x+1, y+1) not in self.carte:
     x, y = self._sand(x+1, y+1)
     return x, y
    def simulation(rocks):
     d()
     while rocks.finished is False:
     print(d.item(rocks()))
    # Inputs
    rocks = RockFormation(input)
    # First simulation
    d = display(*rocks.dim, *rocks.rock)
    simulation(rocks)
    result1 = len(rocks.sand)
    print(f"{d.cursor(row=-4)}{d.color()}Resting sand : {len(rocks.sand)}")
    # Second simulation preparation
    rocks.finished = False
    # Limited floor for display
    # rocks.add_rocks(
    # (rocks.dim[0]-1, rocks.dim[3]+2),
    # (rocks.dim[1]+1, rocks.dim[3]+2)
    # )
    # d = display(*rocks.dim, *rocks.rock)
    # # Big (not too huge) floor for simulation
    # rocks.add_rocks(
    # (rocks.dim[0]-1000, rocks.dim[3]),
    # (rocks.dim[1]+1000, rocks.dim[3])
    # )
    # Enough floor knowing the solution
    rocks.add_rocks((325, 174), (675, 174))
    d = display(*rocks.dim, *rocks.rock)
    simulation(rocks)
    print(f"{d.cursor(row=-3)}{d.color()}", end='')
    print(f"Resting sand : {result1}")
    print(f"Filling sand : {len(rocks.sand)}")
    print(f"Dimensions 1 : {dim1}")
    print(f"Dimensions 2 : {rocks.dim}")
    display2 = d.str(rocks.sand)
    # Et enfin afficher le résultat pour les screenshots
    w, h = tuple(os.get_terminal_size())
    print("\n"*h)
    print(display1)
    print("\n"*h)
    print(display2)
    print(f"{d.cursor(row=-2)}{d.color()}", end='')

    Il faut vachement zoomer arrière dans le terminal, après c'est écrit trop petit pour lire, mais on a une belle animation :)

    • Yth.