/ 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()