diff --git a/generators/thimblerig.py b/generators/thimblerig.py new file mode 100755 index 0000000..c6e2557 --- /dev/null +++ b/generators/thimblerig.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 + +import sys +import random +import math +import argparse +import collections +import blup.frame +import blup.output +import blup.animation +import blup.writebml +import colorsys +import functools + + +WIDTH = 22 +HEIGHT = 16 +DEPTH = 256 + + +class Point(collections.namedtuple('Point', ['x', 'y'])): + def __add__(self, other): + return Point(self.x + other.x, self.y + other.y) + def __sub__(self, other): + return Point(self.x - other.x, self.y - other.y) + def __mul__(self, other): + if isinstance(other, Point): + raise Exception('not implemented') + else: + return Point(self.x * other, self.y * other) + def __truediv__(self, other): + if isinstance(other, Point): + raise ValueError() + else: + return self * (1/other) + @property + def len(self): + return math.sqrt(self.x**2 + self.y**2) + @property + def n(self): + n = Point(self.y, -self.x) + return n / n.len + @property + def intp(self): + return Point(int(round(self.x)), int(round(self.y))) + + +def convert_color(c): + return list(map(lambda x: int(round(x*(DEPTH-1))), c)) + + +def get_random_color(): + return colorsys.hsv_to_rgb(random.random(), 0.5 + random.random() / 2, 1) + + +class CurveFunction(): + def __init__(self, p1, p2): + self.p1, self.p2 = p1, p2 + d = p2 - p1 + self.pp = p1 + d/2 + d.n * (d.len * 0.8) + + def __call__(self, t): + ret = (self.p1 - self.pp*2 + self.p2) * t**2 + ret += (self.p1 * -2 + self.pp * 2) * t + ret += self.p1 + return ret + + + +class Box(): + def __init__(self, pos, size, hue, swaptime, content=None, + contentcolor=None): + self.pos, self.size, self.hue, self.content, self.contentcolor = \ + pos, size, hue, content, contentcolor + self.swaptime = swaptime + self.covered = False + self.swapstart = None + self.newpos = None + self.cf = None + self.colorcycletime = 1 + + def swap(self, other): + self.newpos = other.pos + other.newpos = self.pos + + def update(self, t, dt): + self.hue += 0.05 # dt / self.colorcycletime + if self.hue > 1: + self.hue -= 1 + if self.newpos is not None: + if self.cf is None: + self.cf = CurveFunction(self.pos, self.newpos) + self.swapstart = t + elif t - self.swapstart >= self.swaptime: + self.pos = self.newpos + self.newpos = None + self.cf = None + self.swapstart = None + else: + self.pos = (self.cf((t - self.swapstart) / self.swaptime)).intp + + def draw(self, frame): + if self.covered: + for x in range(self.size): + for y in range(self.size): + if x == 0 or x == self.size - 1 \ + or y == 0 or y == self.size -1: + color = (0.2, 0.2, 0.2) + else: + color = colorsys.hsv_to_rgb(self.hue, 1, 1) + try: + frame.setPixel(self.pos.x + x, self.pos.y + y, + convert_color(color)) + except ValueError: + pass + else: + if self.content is not None: + for p in self.content: + try: + frame.setPixel(self.pos.x + p.x + 1, + self.pos.y + p.y + 1, + convert_color(self.contentcolor)) + except ValueError: + pass + + +class Game(): + def __init__(self, size, boxsize, gap, content, contentcolor, swaptime): + self.swaptime = swaptime + self.boxes = [] + boxbase = Point((size.x - (2*boxsize + gap)) / 2, + (size.y - (2*boxsize + gap)) / 2) + self.n_boxes = 4 + for i in range(self.n_boxes): + boxpos = boxbase + Point( + (i % (self.n_boxes/2)) * (boxsize + gap), + (i // (self.n_boxes/2)) * (boxsize + gap) + ) + self.boxes.append(Box( + boxpos.intp, + boxsize, + i * (1/self.n_boxes), + swaptime + )) + winner = random.randint(0, self.n_boxes - 1) + self.boxes[winner].content = content + self.boxes[winner].contentcolor = contentcolor + self.statetimes = [2, 1, 5, 2, 1, 2] + self.statestarts = [0] + for t in self.statetimes[:-1]: + self.statestarts.append(self.statestarts[-1] + t) + self.done = False + + def update(self, t, dt): + state = 0 + for i, st in enumerate(self.statestarts): + if st < t: + state = i + else: + break + state_t = t - self.statestarts[state] + + if state == 1 or state == 4: + for i, b in enumerate(self.boxes): + if state_t >= i * (self.statetimes[state] / self.n_boxes): + b.covered = (state == 1) + elif state == 2: + if True not in map(lambda b: b.newpos is not None, self.boxes) \ + and random.random() > 0.2: + b1 = random.randint(0, self.n_boxes - 1) + b2 = random.randint(0, self.n_boxes - 1) + while b1 == b2: + b2 = random.randint(0, self.n_boxes - 1) + self.boxes[b1].swap(self.boxes[b2]) + + if state > 0 and state < 5: + for b in self.boxes: + b.update(t, dt) + + if state == len(self.statetimes) - 1: + self.done = True + + def draw(self, frame): + for b in self.boxes: + if b.newpos is None: + b.draw(frame) + for b in self.boxes: + if b.newpos is not None: + b.draw(frame) + + +BOXCONTENT = { + Point(0, 0), + Point(3, 0), + Point(0, 2), + Point(3, 2), + Point(1, 3), + Point(2, 3), +} + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Generate animations') + parser.add_argument('-d', '--delay', type=int, default=50) + parser.add_argument('-t', '--time', type=int, default=15) + parser.add_argument('output_file') + args = parser.parse_args() + + dim = blup.frame.FrameDimension(WIDTH, HEIGHT, DEPTH, 3) + anim = blup.animation.Animation(dim) + anim.tags['description'] = ' '.join(sys.argv) + + + t = 0 + dt = args.delay / 1000 + gamestart = 0 + game = None + while t < args.time or not game.done: + t += dt + frame = blup.animation.AnimationFrame(dim, args.delay) + + if (game is None or game.done) and t < args.time: + game = Game(Point(WIDTH, HEIGHT), 6, 2, BOXCONTENT, + get_random_color(), 0.5) + gamestart = t + game.update(t - gamestart, dt) + game.draw(frame) + + anim.addFrame(frame) + + + blup.writebml.writeBml(anim, args.output_file)