#!/usr/bin/python3 import sys import time import enum import threading import argparse import subprocess import socket import struct import blup.frame import blup.output class Game(object): def __init__(self): self.balance = None self.pygame = False self.output = None pass @property def logo(self): return self._logo @property def args(self): args = [] args += ['--players', str(self.players)] if self.balance is not None: args += ['--balance', self.balance] if self.pygame: args += ['--pygame'] if self.output is not None: args += ['--out', self.output] return args class PongGame(Game): def __init__(self): self.name = 'Pong' self.executable = 'balancep0ng.py' self.players = 0 self._logo = [ [1,1,0,0,0,1,1,0,0,1,0,0,1,0,0,1,1,0], [1,0,1,0,1,0,0,1,0,1,1,0,1,0,1,0,0,0], [1,1,0,0,1,0,0,1,0,1,0,1,1,0,1,0,1,1], [1,0,0,0,1,0,0,1,0,1,0,0,1,0,1,0,0,1], [1,0,0,0,0,1,1,0,0,1,0,0,1,0,0,1,1,0], ] class TetrisGame(Game): def __init__(self): self.name = 'Tetris' self.executable = 'ttrs.py' self.players = 0 self._logo = [ [1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,1,0,0,1,1], [0,1,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,1,0,0], [0,1,0,0,1,1,0,0,1,0,0,1,1,0,0,1,0,0,1,0], [0,1,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,0,1], [0,1,0,0,1,1,0,0,1,0,0,1,0,1,0,1,0,1,1,0], ] arrow_up = [ [0,0,1,0,0], [0,1,1,1,0], [1,1,1,1,1], ] arrow_down = list(reversed(arrow_up)) arrow_left = [ [0,0,1], [0,1,1], [1,1,1], [0,1,1], [0,0,1], ] arrow_right = [list(reversed(x)) for x in arrow_left] go = [ [0,1,0,0,0,1,0], [1,0,0,0,1,0,1], [1,0,1,0,1,0,1], [0,1,0,0,0,1,0], ] select_game = [ [0,1,1,0,1,1,0,1,0,0,1,1,0,1,1,0,1,1,1], [1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,1,0], [0,1,0,0,1,1,0,1,0,0,1,1,0,1,0,0,0,1,0], [0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,1,0], [1,1,0,0,1,1,0,1,1,0,1,1,0,1,1,0,0,1,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,1,1,0,0,0,1,0,0,1,0,0,0,1,0,1,1,0], [0,1,0,0,0,0,1,0,1,0,1,1,0,1,1,0,1,0,0], [0,1,0,1,1,0,1,1,1,0,1,0,1,0,1,0,1,1,0], [0,1,0,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,0], [0,0,1,1,0,0,1,0,1,0,1,0,0,0,1,0,1,1,0], ] player2 = [ [0,1,1,1,0,0,1,1,1,0,0,0,1,1,0], [1,0,0,0,1,0,1,0,0,1,0,1,0,0,1], [0,0,0,1,0,0,1,0,0,1,0,0,0,1,0], [0,0,1,0,0,0,1,1,1,0,0,0,1,0,0], [0,1,0,0,0,0,1,0,0,0,0,0,0,0,0], [1,1,1,1,1,0,1,0,0,0,0,0,1,0,0], ] def copy_bitmap(bitmap, frame, color, xoffs=0, yoffs=0): for x in range(len(bitmap[0])): for y in range(len(bitmap)): if bitmap[y][x] == 1: frame.setPixel(xoffs + x, yoffs + y, color) def create_init_screen(dimension): frame = blup.frame.Frame(dimension) xoffs = (dimension.size()[0]-len(select_game[0]))//2 yoffs = (dimension.size()[1]-len(select_game))//2 color = (0, 255, 0) copy_bitmap(select_game, frame, color, xoffs, yoffs) return frame def create_2player_screen(dimension): frame = blup.frame.Frame(dimension) xoffs = (dimension.size()[0]-len(player2[0]))//2 yoffs = (dimension.size()[1]-len(player2))//2 color = (0, 255, 0) copy_bitmap(player2, frame, color, xoffs, yoffs) return frame def create_menu_frame(dimension, game): frame = blup.frame.Frame(dimension) xoffs = (dimension.size()[0]-len(arrow_up[0]))//2 yoffs = 0 color = (255, 0, 0) copy_bitmap(arrow_up, frame, color, xoffs, yoffs) xoffs = xoffs+len(arrow_up[0])+1 color = (255, 0, 0) copy_bitmap(go, frame, color, xoffs, yoffs) xoffs = 0 yoffs = dimension.size()[1]-len(arrow_right) color = (0, 0, 255) copy_bitmap(arrow_left, frame, color, xoffs, yoffs) xoffs = dimension.size()[0]-len(arrow_right[0]) color = (255, 255, 0) copy_bitmap(arrow_right, frame, color, xoffs, yoffs) xoffs = (dimension.size()[0]-len(game.logo[0]))//2 yoffs = (dimension.size()[1]-len(game.logo))//2 color = (0, 255, 255) copy_bitmap(game.logo, frame, color, xoffs, yoffs) return frame InputEvent = enum.Enum('InputEvent', ['UP', 'DOWN', 'LEFT', 'RIGHT', 'QUIT', 'STEP_ON']) class AbstractInput(): def __init__(self): pass def get_event(self): return None class TestInput(AbstractInput): def __init__(self): self.__evt = None import pygame self.__pygame = pygame self.screen = pygame.display.set_mode((100, 100)) pygame.display.update() self.controls = { pygame.K_a: InputEvent.LEFT, pygame.K_d: InputEvent.RIGHT, pygame.K_w: InputEvent.UP, pygame.K_s: InputEvent.STEP_ON, pygame.K_ESCAPE: InputEvent.QUIT, } def get_event(self): pygame = self.__pygame for event in pygame.event.get(): if event.type == pygame.KEYDOWN: return self.controls.get(event.key, None) return None class BalanceInput(AbstractInput, threading.Thread): def __init__(self, addr, player_id): self.addr = addr self.player_id = player_id self.evt = None self.lastevt = None self.player_present = False threading.Thread.__init__(self, daemon=True) self.start() def run(self): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect(self.addr) except ConnectionRefusedError: print('could not connect to balance server', file=sys.stderr) self.evt = InputEvent.QUIT return self.running = True evt = None oldevt = None lastchange = 0 while self.running: sock.send(b'a') data = sock.recv(4) p0x, p0y, p1x, p1y = struct.unpack('bbbb', data) if self.player_id == 0: xbal, ybal = p0x, p0y elif self.player_id == 1: xbal, ybal = p1x, p1y print('player_id=%d xbal=%d ybal=%d' % (self.player_id, xbal, ybal)) THRESHOLD = 40 MIN_TIMES = { InputEvent.LEFT: 0.05, InputEvent.RIGHT: 0.05, InputEvent.UP: 0.05, InputEvent.DOWN: 0.05, } if self.player_present: if xbal == -128 or ybal == -128: self.evt = InputEvent.QUIT self.player_present = False continue if abs(xbal) < THRESHOLD and abs(ybal) < THRESHOLD: evt = None else: if abs(xbal) < abs(ybal): if ybal > 0: evt = InputEvent.UP else: evt = InputEvent.DOWN else: if xbal > 0: evt = InputEvent.RIGHT else: evt = InputEvent.LEFT if evt != oldevt: lastchange = time.time() oldevt = evt if time.time() - lastchange < MIN_TIMES.get(evt, 0): print('player_id=%d debounce %s' % (self.player_id, evt)) continue if self.lastevt != evt: self.evt = evt self.lastevt = evt print('player_id=%d event=%s' % (self.player_id, self.evt)) else: if xbal != -128 and ybal != -128: self.evt = InputEvent.STEP_ON self.player_present = True continue def get_event(self): evt = self.evt self.evt = None return evt def start_game(game, players): print('Starting', game.name, 'for', players, 'player(s)') game.players = players arg = ['./'+game.executable] + game.args subprocess.call(arg) sys.exit() if __name__ == '__main__': parser = argparse.ArgumentParser(description='Blinkenbunt Game Selection Menu') parser.add_argument('--pygame', dest='pygame', action='store_true', help='use pygame as input') parser.add_argument('--balance', dest='balance', type=str, 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) w = 22 h = 16 out = blup.output.getOutput(args.out) dim = blup.frame.FrameDimension(w, h, 256, 3) if args.pygame: inp1 = TestInput() inp2 = TestInput() elif args.balance is not None: host, port = args.balance.split(':') inp1 = BalanceInput((host, int(port)), 0) inp2 = BalanceInput((host, int(port)), 1) while inp1.get_event() != InputEvent.STEP_ON: time.sleep(0.05) init_frame = create_init_screen(dim) out.sendFrame(init_frame) time.sleep(2) games = Game.__subclasses__() game_idx = 0 old_idx = -1 while True: if old_idx != game_idx: gamecls = games[game_idx] game = gamecls() out.sendFrame(create_menu_frame(dim, game)) old_idx = game_idx evt = inp1.get_event() if evt == InputEvent.LEFT: game_idx -= 1 if game_idx < 0: game_idx = len(games)-1 if evt == InputEvent.RIGHT: game_idx += 1 if game_idx >= len(games): game_idx = 0 if evt == InputEvent.UP: game.output = args.out game.balance = args.balance game.pygame = args.pygame break if evt == InputEvent.QUIT: sys.exit() p2_frame = create_2player_screen(dim) out.sendFrame(p2_frame) wait_time = 3 start = time.time() while time.time() < start+wait_time: if inp2.get_event() == InputEvent.STEP_ON: inp1.running = False inp2.running = False start_game(game, 2) time.sleep(0.1) inp1.running = False inp2.running = False start_game(game, 1)