diff --git a/blup/output.py b/blup/output.py index 6837631..0aacff6 100644 --- a/blup/output.py +++ b/blup/output.py @@ -331,10 +331,10 @@ class BLPOutput(Output): # class BlinkenbuntHDOutput(Output): - moduleRegexDesc = 'bbunthd' - moduleRegex = '^bbunthd$' + moduleRegexDesc = 'bbunthd[:BRIGHTNESS]' + moduleRegex = '^bbunthd(:(?P\d+))?$' - def __init__(self): + def __init__(self, brightness): import neopixel self.w = 22 self.h = 16 @@ -342,20 +342,20 @@ class BlinkenbuntHDOutput(Output): LED_PIN = 13 LED_FREQ_HZ = 800000 LED_DMA = 5 - LED_BRIGHTNESS = 50 LED_INVERT = False LED_PWM = 1 self.strip = neopixel.Adafruit_NeoPixel( LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, - LED_BRIGHTNESS, LED_PWM + brightness, LED_PWM ) self.strip.begin() self._color_cls = neopixel.Color @classmethod def fromRegexMatch(cls, regexMatch): - return cls() + b = int(regexMatch.groupdict().get('brightness', 50)) + return cls(brightness=b) def sendFrame(self, frame): for y in range(self.h): diff --git a/miniplayer.py b/miniplayer.py index b697681..297fbcb 100755 --- a/miniplayer.py +++ b/miniplayer.py @@ -104,7 +104,7 @@ class MiniPlayer(object): # play the animation in case it had been successfully loaded before if currentAnim is not None: if currentAnim.duration < self.loopTime: - count = self.loopTime / currentAnim.duration + count = self.loopTime // currentAnim.duration else: count = 1 player.play(currentAnim, self.__output, count=count) diff --git a/sd_to_hd.py b/sd_to_hd.py new file mode 100755 index 0000000..1f7c0d0 --- /dev/null +++ b/sd_to_hd.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import blup.frame +import blup.animation +import writebml +import sys + +if len(sys.argv) != 3: + print('usage: %s INFILE OUTFILE' % (sys.argv[0]), file=sys.stderr) + sys.exit(1) +infname = sys.argv[1] +outfname = sys.argv[2] + +anim = blup.animation.load(infname) + +dim = blup.frame.FrameDimension(22, 16, 256, 3) +newanim = blup.animation.Animation(dim) + +for f in anim: + newf = blup.animation.AnimationFrame(dim, f.delay) + for x in range(f.dimension.width): + for y in range(f.dimension.height): + newf.setPixel(x + 2, y*2, tuple(map(lambda x: int((x/anim.dimension.depth)*dim.depth), f.getPixel(x, y)))) + newf.setPixel(x + 2, y*2 + 1, tuple(map(lambda x: int((x/anim.dimension.depth)*dim.depth), f.getPixel(x, y)))) + newanim.addFrame(newf) + +writebml.writeBml(newanim, outfname) + diff --git a/ttrs.py b/ttrs.py index a01ef23..1d8c738 100755 --- a/ttrs.py +++ b/ttrs.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import argparse +import sys import socket import struct import time @@ -9,6 +11,7 @@ import collections import blup.frame import blup.output import random +import threading class InvalidMoveError(Exception): @@ -183,146 +186,110 @@ class Playground(): continue frame.setPixel(xpos + int(b.pos.x), ypos + int(b.pos.y), b.color) + +PlayerEvent = enum.Enum('PlayerEvent', ['ROTATE', 'DROP', 'MOVE_LEFT', + 'MOVE_RIGHT', 'QUIT']) + + class TtrsPlayer(): def __init__(self, playground): pass - def get_move(self, minopos): - return 0 - - def get_drop(self): - return False - - def get_rotate(self): - return False + def get_event(self): + return None - def get_quit(self): - return False class TestTtrsPlayer(TtrsPlayer): def __init__(self, playground): self.playground = playground - self.__move = 0 - self.__drop = False - self.__rotate = False - self.__quit = False + self.__evt = None import pygame self.__pygame = pygame self.screen = pygame.display.set_mode((100, 100)) pygame.display.update() - def __process_events(self): + self.controls = { + pygame.K_a: PlayerEvent.MOVE_LEFT, + pygame.K_d: PlayerEvent.MOVE_RIGHT, + pygame.K_w: PlayerEvent.ROTATE, + pygame.K_s: PlayerEvent.DROP, + pygame.K_ESCAPE: PlayerEvent.QUIT, + } + #self.controls = { + # pygame.K_LEFT: PlayerEvent.MOVE_LEFT, + # pygame.K_RIGHT: PlayerEvent.MOVE_RIGHT, + # pygame.K_UP: PlayerEvent.ROTATE, + # pygame.K_DOWN: PlayerEvent.DROP, + # pygame.K_ESCAPE: PlayerEvent.QUIT, + #} + + def get_event(self): pygame = self.__pygame for event in pygame.event.get(): if event.type == pygame.KEYDOWN: - if event.key == pygame.K_a: - self.__move = -1 - elif event.key == pygame.K_d: - self.__move = 1 - elif event.key == pygame.K_w: - self.__rotate = True - elif event.key == pygame.K_s: - self.__drop = True - elif event.key == pygame.K_ESCAPE: - self.__quit = True - elif event.type == pygame.KEYUP: - if event.key == pygame.K_a or event.key == pygame.K_d: - self.__move = 0 - elif event.key == pygame.K_w: - self.__rotate = False - elif event.key == pygame.K_s: - self.__drop = False - - def get_move(self, minopos): - self.__process_events() - return self.__move - - def get_drop(self): - self.__process_events() - return self.__drop - - def get_rotate(self): - self.__process_events() - return self.__rotate - - def get_quit(self): - self.__process_events() - return self.__quit - - def reset_rotate(self): - pass + return self.controls.get(event.key, None) + return None + -class BalanceTtrsPlayer(TtrsPlayer): +class BalanceTtrsPlayer(TtrsPlayer, threading.Thread): def __init__(self, playground, addr, player_id): self.playground = playground self.player_id = player_id - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect(addr) - - def __update_balance(self): - self.sock.send(b'a') - data = self.sock.recv(4) - p1x, p1y, p2x, p2y = struct.unpack('bbbb', data) - if self.player_id == 0: - self.xbal = p1x - self.ybal = p1y - elif self.player_id == 1: - self.xbal = p2x - self.ybal = p2y - - def reset_rotate(self): - self.__rotate_reset = True - self.__rotate = False - - def get_move(self, minopos): - self.__update_balance() - - #if p1x > 40: - # self.__move = 1 - #elif p1x < -40: - # self.__move = -1 - #elif p1x > -20 and p1x < 20: - # self.__move = 0 + threading.Thread.__init__(self, daemon=True) + self.start() + + def run(self): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(addr) + self.running = True + while self.running: + sock.send(b'a') + data = sock.recv(4) + p0x, p0y, p2x, p2y = struct.unpack('bbbb', data) + if self.player_id == 0: + self.__calc_event(p0x, p0y) + elif self.player_id == 1: + self.__calc_event(p1x, p1y) + time.sleep(0.01) + + def __calc_event(self, xbal, ybal): + if self.ybal > 50: + return PlayerEvent.ROTATE + MAX_Y_AMPLITUDE = 65 bal = self.xbal normbal = (bal + MAX_Y_AMPLITUDE) / (2 * MAX_Y_AMPLITUDE) px = round(normbal * (self.playground.width - 1)) print('player %d balance=%d pos=%d' % (self.player_id, bal, px)) if minopos.x > px: - return -1 + return PlayerEvent.MOVE_LEFT elif minopos.x < px: - return 1 - else: - return 0 - - def get_drop(self): - self.__update_balance() - return (self.ybal < -50) + return PlayerEvent.MOVE_RIGHT - def get_rotate(self): - self.__update_balance() - if self.ybal > 50 and not self.__rotate_reset: - return True - elif self.ybal < 35: - self.__rotate_reset = False - return False - - def get_quit(self): - self.__update_balance() - return (self.xbal == -128 or self.ybal == -128) + if self.ybal < -50: + return PlayerEvent.DROP -class TtrsGame(): - def __init__(self, playground, player): +class TtrsGame(threading.Thread): + def __init__(self, playground, player, rnd=None): self.playground = playground self.player = player + if rnd is None: + self.rnd = random.Random() + else: + self.rnd = rnd self.running = False self.tick_callbacks = [] + threading.Thread.__init__(self, daemon=True) def add_tick_callback(self, cb): self.tick_callbacks.append(cb) + def __call_callbacks(self): + for cb in self.tick_callbacks: + cb(self) + def run(self): self.running = True spawnpos = Point(self.playground.width // 2, -1) @@ -333,18 +300,24 @@ class TtrsGame(): ticks = 0 lastfall = 0 lastmove = 0 + dropping = False + top_row = { Point(x, -1) for x in range(self.playground.height) } while self.running: - for cb in self.tick_callbacks: - cb() + self.__call_callbacks() time.sleep(TICK_TIME) ticks += 1 - if self.player.get_quit(): + evt = self.player.get_event() + if evt == PlayerEvent.QUIT: + self.running = False + break + + if not self.playground.block_points.isdisjoint(top_row): self.running = False break if mino is None: - newminocls = random.choice(Tetrimino.__subclasses__()) + newminocls = self.rnd.choice(Tetrimino.__subclasses__()) mino = newminocls(self.playground, spawnpos) self.playground.minos = {mino} @@ -366,13 +339,11 @@ class TtrsGame(): for i in range(2): for b in to_delete: self.playground.blocks.add(b) - for cb in self.tick_callbacks: - cb() + self.__call_callbacks() time.sleep(0.2) for b in to_delete: self.playground.blocks.remove(b) - for cb in self.tick_callbacks: - cb() + self.__call_callbacks() time.sleep(0.2) to_add = set() @@ -384,7 +355,7 @@ class TtrsGame(): to_add.add(newb) self.playground.blocks.update(to_add) - if ticks - lastfall >= FALL_INTERVAL or self.player.get_drop(): + if ticks - lastfall >= FALL_INTERVAL or evt == PlayerEvent.DROP: lastfall = ticks try: mino.move(Point(0, 1)) @@ -394,60 +365,96 @@ class TtrsGame(): self.playground.minos = set() continue - move = self.player.get_move(mino.pos) - if ticks - lastmove >= MOVE_INTERVAL and move != 0: - lastmove = ticks + if ticks - lastmove >= MOVE_INTERVAL: try: - mino.move(Point(move, 0)) + if evt == PlayerEvent.MOVE_LEFT: + mino.move(Point(-1, 0)) + lastmove = ticks + elif evt == PlayerEvent.MOVE_RIGHT: + mino.move(Point(1, 0)) + lastmove = ticks except InvalidMoveError: pass - if self.player.get_rotate(): + if evt == PlayerEvent.ROTATE: try: mino.rotate() - self.player.reset_rotate() except InvalidMoveError: pass -if __name__ == '__main__': - w = 22 - h = 16 - - dim = blup.frame.FrameDimension(w, h, 256, 3) - frame = blup.frame.Frame(dim) - out = blup.output.getOutput('e3blp:blinkenbunt:2342') - - pg = Playground(10, 16) +def repaint(pg): pg.paint(frame, 0, 0) out.sendFrame(frame) - time.sleep(0.5) - - def repaint(): - pg.paint(frame, 0, 0) - out.sendFrame(frame) - - #player = TestTtrsPlayer(pg) - player = BalanceTtrsPlayer(pg, ('blinkenbunt', 4711), 0) - game = TtrsGame(pg, player) - game.add_tick_callback(repaint) - game.run() - - #t = TetriL(pg, Point(2, 2)) - #pg.minos.add(t) - #pg.paint(frame, 0, 0) - #out.sendFrame(frame) - #time.sleep(0.5) - - #for i in range(10): - # t.rotate(ccw=(i>=5)) - # pg.paint(frame, 0, 0) - # out.sendFrame(frame) - # time.sleep(0.1) - +class GamePlaygroundPainter(): + def __init__(self, output, dimension): + self.output = output + self.games = {} + self.frame = blup.frame.Frame(dimension) + bgcolor = (100, 100, 100) + for x in range(dimension.width): + for y in range(dimension.height): + self.frame.setPixel(x, y, bgcolor) + + def add_game(self, game, xpos, ypos): + game.add_tick_callback(self.repaint) + self.games[game] = (xpos, ypos) + + def repaint(self, game): + xpos, ypos = self.games[game] + game.playground.paint(self.frame, xpos, ypos) + self.output.sendFrame(self.frame) +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Blinkenbunt Tetris!') + parser.add_argument('--players', dest='players', type=int, default=1, + help='number of players') + parser.add_argument('--pygame', dest='pygame', action='store_true', + help='use pygame as input') + parser.add_argument('--balance', dest='balance', type=str, nargs=1, + metavar='HOST:PORT', help='use balance input') + parser.add_argument('--out', dest='out', type=str, metavar='OUTPUT', + default='e3blp', help='blup output specification') + args = parser.parse_args() + + if args.balance is None and not args.pygame: + print('please specify an input method', file=sys.stderr) + sys.exit(1) + + if args.pygame and args.players == 2: + print('pygame input does only support one player', file=sys.stderr) + sys.exit(1) + w = 22 + h = 16 + out = blup.output.getOutput(args.out) + dim = blup.frame.FrameDimension(w, h, 256, 3) + painter = GamePlaygroundPainter(out, dim) + + seed = random.random() + games = [] + def start_game(player_id, xpos): + pg = Playground(10, 16) + if args.pygame: + player = TestTtrsPlayer(pg) + elif args.balance is not None: + host, port = args.balance.split(':') + player = BalanceTtrsPlayer(pg, (host, int(port)), player_id) + rnd = random.Random(seed) + game = TtrsGame(pg, player, rnd) + painter.add_game(game, xpos, 0) + game.start() + games.append(game) + + if args.players == 1: + start_game(0, 6) + elif args.players == 2: + start_game(0, 0) + start_game(1, 12) + + for game in games: + game.join() diff --git a/writebml.py b/writebml.py index abe230d..fc33d2e 100644 --- a/writebml.py +++ b/writebml.py @@ -12,7 +12,7 @@ def writeBml(anim, filename): root.attrib['channels'] = str(anim.dimension.channels) header = ET.Element('header') - for (name, val) in anim.tags: + for (name, val) in anim.tags.items(): elem = ET.Element(name) elem.text = str(val) header.append(elem)