|
|
|
#!/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)
|