|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import socket
|
|
|
|
import struct
|
|
|
|
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)
|
|
|
|
for newp in newpoints:
|
|
|
|
if ( newp.x >= self.playground.width or newp.x < 0 or
|
|
|
|
newp.y >= self.playground.height):
|
|
|
|
raise InvalidMoveError('out of playground bounds')
|
|
|
|
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(self.width):
|
|
|
|
for y in range(self.height):
|
|
|
|
frame.setPixel(xpos + x, ypos + y, (0, 0, 0))
|
|
|
|
for b in set.union(self.blocks, *[ m.blocks for m in self.minos ]):
|
|
|
|
if not self.contains_points([b.pos]):
|
|
|
|
# don't draw blocks outside the playground area
|
|
|
|
continue
|
|
|
|
frame.setPixel(xpos + int(b.pos.x), ypos + int(b.pos.y), b.color)
|
|
|
|
|
|
|
|
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_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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
class BalanceTtrsPlayer(TtrsPlayer):
|
|
|
|
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
|
|
|
|
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
|
|
|
|
elif minopos.x < px:
|
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def get_drop(self):
|
|
|
|
self.__update_balance()
|
|
|
|
return (self.ybal < -50)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
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, -1)
|
|
|
|
mino = None
|
|
|
|
TICK_TIME = 0.1
|
|
|
|
FALL_INTERVAL = 5
|
|
|
|
MOVE_INTERVAL = 2
|
|
|
|
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.get_quit():
|
|
|
|
self.running = False
|
|
|
|
break
|
|
|
|
|
|
|
|
if mino is None:
|
|
|
|
newminocls = random.choice(Tetrimino.__subclasses__())
|
|
|
|
mino = newminocls(self.playground, spawnpos)
|
|
|
|
self.playground.minos = {mino}
|
|
|
|
|
|
|
|
#print(mino.pos)
|
|
|
|
|
|
|
|
to_remove = set()
|
|
|
|
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):
|
|
|
|
to_remove.add(y)
|
|
|
|
|
|
|
|
if len(to_remove) > 0:
|
|
|
|
to_delete = set()
|
|
|
|
for b in list(self.playground.blocks):
|
|
|
|
if b.pos.y in to_remove:
|
|
|
|
to_delete.add(Block(b.pos, (255, 255, 255)))
|
|
|
|
self.playground.blocks.remove(b)
|
|
|
|
|
|
|
|
for i in range(2):
|
|
|
|
for b in to_delete:
|
|
|
|
self.playground.blocks.add(b)
|
|
|
|
for cb in self.tick_callbacks:
|
|
|
|
cb()
|
|
|
|
time.sleep(0.2)
|
|
|
|
for b in to_delete:
|
|
|
|
self.playground.blocks.remove(b)
|
|
|
|
for cb in self.tick_callbacks:
|
|
|
|
cb()
|
|
|
|
time.sleep(0.2)
|
|
|
|
|
|
|
|
to_add = set()
|
|
|
|
for b in list(self.playground.blocks):
|
|
|
|
o = len(list(filter(lambda y: y > b.pos.y, to_remove)))
|
|
|
|
if o > 0:
|
|
|
|
self.playground.blocks.remove(b)
|
|
|
|
newb = Block(b.pos + Point(0, o), b.color)
|
|
|
|
to_add.add(newb)
|
|
|
|
self.playground.blocks.update(to_add)
|
|
|
|
|
|
|
|
if ticks - lastfall >= FALL_INTERVAL or self.player.get_drop():
|
|
|
|
lastfall = ticks
|
|
|
|
try:
|
|
|
|
mino.move(Point(0, 1))
|
|
|
|
except InvalidMoveError:
|
|
|
|
self.playground.blocks.update(mino.blocks)
|
|
|
|
mino = None
|
|
|
|
self.playground.minos = set()
|
|
|
|
continue
|
|
|
|
|
|
|
|
move = self.player.get_move(mino.pos)
|
|
|
|
if ticks - lastmove >= MOVE_INTERVAL and move != 0:
|
|
|
|
lastmove = ticks
|
|
|
|
try:
|
|
|
|
mino.move(Point(move, 0))
|
|
|
|
except InvalidMoveError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if self.player.get_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)
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|