#!/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.6, 0.6, 0.6) 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 and state_t > 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)