/ animations / flowsnake.py
flowsnake.py
  1  import random
  2  from math import pi, cos, sin, atan, sqrt, acos
  3  
  4  import pygame
  5  
  6  from animations import Animation
  7  from turtle import Turtle
  8  
  9  COLORS = [
 10      0xFF0000,
 11      0xFFFF00,
 12      0x008800,
 13      0x00FFFF,
 14      0x0000FF,
 15  ]
 16  BACKGROUND = (17, 17, 17)
 17  
 18  WIDTH, HEIGHT = 600, 600
 19  
 20  RECORD = False
 21  FPS = 30
 22  SECONDS = 7
 23  FRAMES = FPS * SECONDS
 24  
 25  
 26  def get_color(t):
 27      k = int(t * (len(COLORS) - 1))
 28      color1 = [COLORS[k] >> j & 0xFF for j in range(16, -1, -8)]
 29      color2 = [COLORS[min(k + 1, len(COLORS) - 1)] >> j & 0xFF for j in range(16, -1, -8)]
 30      t -= k / (len(COLORS) - 1)
 31      t *= len(COLORS) - 1
 32      return [round(color1[j] * (1 - t) + color2[j] * t) for j in range(3)]
 33  
 34  
 35  class FlowSnake(Animation):
 36      def __init__(self):
 37          super().__init__(WIDTH, HEIGHT, "flowsnake", FPS, RECORD)
 38  
 39          self.order = 7
 40          self.lines = []
 41          self.calculate()
 42  
 43      def calculate(self):
 44          points = [(0, 0)]
 45  
 46          def draw_line(_, b):
 47              points.append(b)
 48  
 49          turtle = Turtle(0, 0, 0, draw_line)
 50          self.draw_flowsnake(turtle, self.order, True, 1)
 51  
 52          padding = 50
 53  
 54          dx = points[-1][0] - points[0][0]
 55          dy = points[-1][1] - points[0][1]
 56          angle = -atan(dy / dx) - acos(5 / 2 / sqrt(7))
 57          if points[0][0] > points[-1][0]:
 58              angle += pi
 59  
 60          minx = 1e1337
 61          maxx = -1e1337
 62          miny = 1e1337
 63          maxy = -1e1337
 64          for i, (x, y) in enumerate(points):
 65              x, y = x * cos(angle) - y * sin(angle), x * sin(angle) + y * cos(angle)
 66              points[i] = x, y
 67              minx = min(minx, x)
 68              maxx = max(maxx, x)
 69              miny = min(miny, y)
 70              maxy = max(maxy, y)
 71  
 72          last = None
 73          lines = []
 74          for i, (x, y) in enumerate(points):
 75              x = (x - minx) / (maxx - minx) * (self.width - padding * 2) + padding
 76              y = (y - miny) / (maxy - miny) * (self.height - padding * 2) + padding
 77              if last:
 78                  t = (i - 1) / (len(points) - 1)
 79                  lines.append((last, (x, y), t))
 80              last = x, y
 81  
 82          self.lines = lines
 83  
 84      def render(self):
 85          k = 100_000
 86          lines = [line for _, line in sorted([(i + random.randint(-k, k), line) for i, line in enumerate(self.lines)])]
 87  
 88          self.win.fill(BACKGROUND)
 89  
 90          for a, b, t in lines:
 91              pygame.draw.line(self.win, get_color(t), a, b)
 92  
 93          for _ in range(FPS // 2):
 94              yield
 95  
 96          head = 0
 97          tail = -len(lines) // 3
 98          cnt = 0
 99          total = (len(lines) - tail) // FRAMES
100          while tail < len(lines):
101  
102              if head < len(lines):
103                  a, b, _ = lines[head]
104                  pygame.draw.line(self.win, (24, 24, 24), a, b)
105  
106              if tail >= 0:
107                  a, b, t = lines[tail]
108                  pygame.draw.line(self.win, get_color(t), a, b)
109  
110              head += 1
111              tail += 1
112  
113              cnt += 1
114              if cnt % total == 0:
115                  yield
116  
117      def draw_flowsnake(self, turtle: Turtle, order: int, axiom: bool, length: float):
118          if not order:
119              turtle.forward(length)
120              return
121  
122          for k in "A-B--B+A++AA+B-" if axiom else "+A-BB--B-A++A+B":
123              if k == "A":
124                  self.draw_flowsnake(turtle, order - 1, True, length)
125              elif k == "B":
126                  self.draw_flowsnake(turtle, order - 1, False, length)
127              elif k == "+":
128                  turtle.turn(-60)
129              elif k == "-":
130                  turtle.turn(60)
131  
132  
133  animation = FlowSnake()
134  animation.run()