#!/usr/bin/env python3 import socket import time import enum import math import collections import blup.frame import blup.output import random class InvalidMoveError(Exception): pass class Point(collections.namedtuple('Point', ['x', 'y'])): __slots__ = () def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def floored(self): return Point(math.floor(self.x), math.floor(self.y)) Block = collections.namedtuple('Block', ['pos', 'color']) class Tetrimino(): def __init__(self, shape, playground, pos): self.shape = shape self.playground = playground self.pos = pos @property def width(self): x = list(map(lambda s: s.pos.x, self.shape)) return max(x) - min(x) @property def height(self): y = list(map(lambda s: s.pos.y, self.shape)) return max(y) - min(y) @property def blocks(self): ret = { Block((self.pos + b.pos).floored(), b.color) for b in self.shape } return ret def __calc_points(self, pos=None, shape=None): if pos is None: pos = self.pos if shape is None: shape = self.shape return { (pos + b.pos).floored() for b in shape } @property def points(self): return self.__calc_points() def __check_collision(self, newpoints): if not self.playground.contains_points(newpoints): raise InvalidMoveError('out of playground bounds') print(self.playground.block_points) print(self.playground.blocks) print('new', newpoints) if not self.playground.block_points.isdisjoint(newpoints): raise InvalidMoveError('new position already occupied') other_mino_points = self.playground.mino_points - self.points if not other_mino_points.isdisjoint(newpoints): raise InvalidMoveError('other Tetrimino at new position') def rotate(self, ccw=False): if ccw: transform = lambda s: Block(Point(s.pos.y, -s.pos.x), s.color) else: transform = lambda s: Block(Point(-s.pos.y, s.pos.x), s.color) newshape = set(map(transform, self.shape)) newpoints = self.__calc_points(shape=newshape) self.__check_collision(newpoints) self.shape = newshape def move(self, m): newpos = self.pos + m newpoints = self.__calc_points(pos=newpos) self.__check_collision(newpoints) self.pos = newpos class TetriL(Tetrimino): def __init__(self, playground, pos): color = (255,165,0) points = [(-1, 1), (-1, 0), (0, 0), (1, 0)] shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class TetriJ(Tetrimino): def __init__(self, playground, pos): color = (0,0,255) points = [(-1, 0), (0, 0), (1, 0), (1, 1)] shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class TetriI(Tetrimino): def __init__(self, playground, pos): color = (0,255,255) points = {(1.5, -0.5), (0.5, -0.5), (-0.5, -0.5), (-1.5, -0.5)} shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class TetriO(Tetrimino): def __init__(self, playground, pos): color = (255,255,0) points = {(-0.5, -0.5), (-0.5, 0.5), (0.5, -0.5), (0.5, 0.5)} shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class TetriS(Tetrimino): def __init__(self, playground, pos): color = (128,255,0) points = {(-1, 0), (0, 0), (0, -1), (1, -1)} shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class TetriZ(Tetrimino): def __init__(self, playground, pos): color = (255,0,0) points = {(-1, 0), (0, 0), (0, 1), (1, 1)} shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class TetriT(Tetrimino): def __init__(self, playground, pos): color = (128,0,128) points = {(-1, 0), (0, 0), (1, 0), (0, 1)} shape = { Block(Point(x, y), color) for (x, y) in points } Tetrimino.__init__(self, shape, playground, pos) class Playground(): def __init__(self, width=10, height=22): self.width = width self.height = height self.blocks = set() self.minos = set() @property def block_points(self): return set([ b.pos for b in self.blocks ]) @property def mino_points(self): return set.union(set(), *[ m.points for m in self.minos ]) def contains_points(self, points): for p in points: if p.x >= self.width or p.y >= self.height or p.x < 0 or p.y < 0: return False return True def paint(self, frame, xpos, ypos): for x in range(frame.dimension.width): for y in range(frame.dimension.height): frame.setPixel(x, y, (0, 0, 0)) for b in set.union(self.blocks, *[ m.blocks for m in self.minos ]): frame.setPixel(xpos + int(b.pos.x), ypos + int(b.pos.y), b.color) class TtrsPlayer(): def __init__(self): self.move = 0 self.drop = False self.rotate = False self.quit = False class TestTtrsPlayer(TtrsPlayer): def __init__(self): self.__move = 0 self.__drop = False self.__rotate = False self.__quit = False import pygame self.__pygame = pygame self.screen = pygame.display.set_mode((100, 100)) pygame.display.update() def __process_events(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 @property def move(self): self.__process_events() return self.__move @property def drop(self): self.__process_events() return self.__drop @property def rotate(self): self.__process_events() return self.__rotate @property def quit(self): self.__process_events() return self.__quit class TtrsGame(): def __init__(self, playground, player): self.playground = playground self.player = player self.running = False self.tick_callbacks = [] def add_tick_callback(self, cb): self.tick_callbacks.append(cb) def run(self): self.running = True spawnpos = Point(self.playground.width // 2, 0) mino = None TICK_TIME = 0.1 FALL_INTERVAL = 5 MOVE_INTERVAL = 1 ticks = 0 lastfall = 0 lastmove = 0 while self.running: for cb in self.tick_callbacks: cb() time.sleep(TICK_TIME) ticks += 1 if self.player.quit: self.running = False break if mino is None: newminocls = random.choice(Tetrimino.__subclasses__()) mino = newminocls(self.playground, spawnpos) self.playground.minos = {mino} for y in range(self.playground.height): row = { Point(x, y) for x in range(self.playground.width) } if row.issubset(self.playground.block_points): for b in list(self.playground.blocks): if b.pos.y == y: self.playground.blocks.remove(b) elif b.pos.y < y: self.playground.blocks.remove(b) newb = Block(b.pos + Point(0, 1), b.color) self.playground.blocks.add(newb) if ticks - lastfall >= FALL_INTERVAL or self.player.drop: lastfall = ticks try: mino.move(Point(0, 1)) except InvalidMoveError: self.playground.blocks.update(mino.blocks) mino = None self.playground.minos = set() continue if ticks - lastmove >= MOVE_INTERVAL and self.player.move != 0: lastmove = ticks try: mino.move(Point(self.player.move, 0)) except InvalidMoveError: pass if self.player.rotate: try: mino.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') pg = Playground(10, 16) pg.paint(frame, 0, 0) out.sendFrame(frame) time.sleep(0.5) def repaint(): pg.paint(frame, 0, 0) out.sendFrame(frame) player = TestTtrsPlayer() 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)