|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import time
|
|
|
|
import random
|
|
|
|
import sys
|
|
|
|
import colorsys
|
|
|
|
|
|
|
|
import blup.frame
|
|
|
|
import blup.output
|
|
|
|
|
|
|
|
|
|
|
|
__numbers = {
|
|
|
|
1: [[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0,]],
|
|
|
|
2: [[1,1,1,1,1],[0,0,0,0,1],[1,1,1,1,1],[1,0,0,0,0],[1,1,1,1,1,]],
|
|
|
|
3: [[1,1,1,1,1],[0,0,0,0,1],[0,0,1,1,1],[0,0,0,0,1],[1,1,1,1,1,]],
|
|
|
|
4: [[1,0,0,0,1],[1,0,0,0,1],[1,1,1,1,1],[0,0,0,0,1],[0,0,0,0,1,]],
|
|
|
|
5: [[1,1,1,1,1],[1,0,0,0,0],[1,1,1,1,1],[0,0,0,0,1],[1,1,1,1,1,]],
|
|
|
|
6: [[1,1,1,1,1],[1,0,0,0,0],[1,1,1,1,1],[1,0,0,0,1],[1,1,1,1,1,]],
|
|
|
|
7: [[1,1,1,1,1],[0,0,0,0,1],[0,0,0,0,1],[0,0,0,0,1],[0,0,0,0,1,]],
|
|
|
|
8: [[1,1,1,1,1],[1,0,0,0,1],[1,1,1,1,1],[1,0,0,0,1],[1,1,1,1,1,]],
|
|
|
|
9: [[1,1,1,1,1],[1,0,0,0,1],[1,1,1,1,1],[0,0,0,0,1],[1,1,1,1,1,]],
|
|
|
|
0: [[1,1,1,1,1],[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[1,1,1,1,1,]],
|
|
|
|
}
|
|
|
|
|
|
|
|
class Paddle(object):
|
|
|
|
def __init__(self, playground, xpos, ypos, size):
|
|
|
|
self.__playground = playground
|
|
|
|
self.__xpos = xpos
|
|
|
|
self.__size = size
|
|
|
|
self.__ypos = ypos
|
|
|
|
self.__nextMove = 0
|
|
|
|
|
|
|
|
@property
|
|
|
|
def nextMove(self):
|
|
|
|
return self.__nextMove
|
|
|
|
@nextMove.setter
|
|
|
|
def nextMove(self, value):
|
|
|
|
if value in [-1, 0, 1]:
|
|
|
|
self.__nextMove = value
|
|
|
|
else:
|
|
|
|
raise ValueError('invalid move')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def xpos(self):
|
|
|
|
return self.__xpos
|
|
|
|
@property
|
|
|
|
def ypos(self):
|
|
|
|
return self.__ypos
|
|
|
|
@property
|
|
|
|
def size(self):
|
|
|
|
return self.__size
|
|
|
|
@property
|
|
|
|
def nextMove(self):
|
|
|
|
return self.__nextMove
|
|
|
|
|
|
|
|
def containsPoint(self, x, y):
|
|
|
|
if x == self.__xpos and y in range(self.__ypos, self.__ypos + self.__size):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def nextMoveUp(self):
|
|
|
|
self.__nextMove = -1
|
|
|
|
|
|
|
|
def nextMoveDown(self):
|
|
|
|
self.__nextMove = 1
|
|
|
|
|
|
|
|
def doNextMove(self):
|
|
|
|
if self.__nextMove is not 0:
|
|
|
|
if self.__nextMove == -1 and self.__ypos > 0:
|
|
|
|
self.__ypos -= 1
|
|
|
|
elif self.__nextMove == 1 and self.__ypos + self.__size < self.__playground.height:
|
|
|
|
self.__ypos += 1
|
|
|
|
self.__nextMove = 0
|
|
|
|
|
|
|
|
class Wall(object):
|
|
|
|
HORIZONTAL = 1
|
|
|
|
VERTICAL = 2
|
|
|
|
def __init__(self, orientation):
|
|
|
|
self.orientation = orientation
|
|
|
|
|
|
|
|
class Ball(object):
|
|
|
|
def __init__(self, playground, xpos, ypos, xspeed, yspeed):
|
|
|
|
self.__playground = playground
|
|
|
|
self.__xpos = xpos
|
|
|
|
self.__ypos = ypos
|
|
|
|
self.__xspeed = xspeed
|
|
|
|
self.__yspeed = yspeed
|
|
|
|
self.__hitCallbacks = []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def xpos(self):
|
|
|
|
return self.__xpos
|
|
|
|
@property
|
|
|
|
def ypos(self):
|
|
|
|
return self.__ypos
|
|
|
|
@property
|
|
|
|
def xspeed(self):
|
|
|
|
return self.__xspeed
|
|
|
|
@property
|
|
|
|
def yspeed(self):
|
|
|
|
return self.__yspeed
|
|
|
|
@xpos.setter
|
|
|
|
def xpos(self, value):
|
|
|
|
self.__xpos = value
|
|
|
|
@ypos.setter
|
|
|
|
def ypos(self, value):
|
|
|
|
self.__ypos = value
|
|
|
|
@xspeed.setter
|
|
|
|
def xspeed(self, value):
|
|
|
|
self.__xspeed = value
|
|
|
|
@yspeed.setter
|
|
|
|
def yspeed(self, value):
|
|
|
|
self.__yspeed = value
|
|
|
|
|
|
|
|
def addHitCallback(self, callback):
|
|
|
|
self.__hitCallbacks.append(callback)
|
|
|
|
|
|
|
|
def doHitCallbacks(self, obj):
|
|
|
|
for callback in self.__hitCallbacks:
|
|
|
|
callback(obj)
|
|
|
|
|
|
|
|
def move(self, ignorePaddles=False):
|
|
|
|
if self.__xspeed == 0 and self.__yspeed == 0:
|
|
|
|
return
|
|
|
|
foundpos = False
|
|
|
|
while not foundpos:
|
|
|
|
if self.__xspeed == 0 and self.__yspeed == 0:
|
|
|
|
break
|
|
|
|
newx = self.__xpos + self.__xspeed
|
|
|
|
newy = self.__ypos + self.__yspeed
|
|
|
|
|
|
|
|
newobj = self.__playground.getObjectAtPosition(newx, newy)
|
|
|
|
if isinstance(newobj, Wall):
|
|
|
|
self.doHitCallbacks(newobj)
|
|
|
|
|
|
|
|
# bounce off at horizontal wall
|
|
|
|
if newobj.orientation == Wall.HORIZONTAL:
|
|
|
|
self.__yspeed *= -1
|
|
|
|
else:
|
|
|
|
#self.__xspeed *= -1
|
|
|
|
foundpos = True
|
|
|
|
elif isinstance(newobj, Paddle) and not ignorePaddles:
|
|
|
|
self.doHitCallbacks(newobj)
|
|
|
|
self.__xspeed *= -1
|
|
|
|
|
|
|
|
# bounce off at the paddle
|
|
|
|
if self.__yspeed == 0:
|
|
|
|
if self.__playground.getObjectAtPosition(newobj.xpos, newobj.ypos - 1) is None:
|
|
|
|
self.__yspeed = -1
|
|
|
|
elif self.__plauground.getObjectAtPosition(newobj.xpos, newobj.ypos + 1) is None:
|
|
|
|
self.__yspeed = 1
|
|
|
|
else:
|
|
|
|
if newobj.xpos < self.__xpos:
|
|
|
|
if self.__playground.getObjectAtPosition(self.__xpos - 1, self.__ypos) is None:
|
|
|
|
self.__yspeed *= -1
|
|
|
|
if newobj.xpos > self.__xpos:
|
|
|
|
if self.__playground.getObjectAtPosition(self.__xpos + 1, self.__ypos) is None:
|
|
|
|
self.__yspeed *= -1
|
|
|
|
|
|
|
|
if newobj.nextMove != 0 and random.randint(0, 2) == 0:
|
|
|
|
self.__yspeed += newobj.nextMove
|
|
|
|
elif abs(self.__yspeed) != 1:
|
|
|
|
self.__yspeed = 1 if self.__yspeed > 0 else -1
|
|
|
|
else:
|
|
|
|
adjobj = self.__playground.getObjectAtPosition(newx, self.__ypos)
|
|
|
|
if not ignorePaddles and isinstance(adjobj, Paddle):
|
|
|
|
self.doHitCallbacks(adjobj)
|
|
|
|
self.__xspeed *= -1
|
|
|
|
else:
|
|
|
|
foundpos = True
|
|
|
|
|
|
|
|
|
|
|
|
self.__xpos = newx
|
|
|
|
self.__ypos = newy
|
|
|
|
|
|
|
|
class Playground(object):
|
|
|
|
def __init__(self, width, height, paddlesize=3):
|
|
|
|
self.__width = width
|
|
|
|
self.__height = height
|
|
|
|
|
|
|
|
paddleLeft = Paddle(self, 0, (height - paddlesize)//2, paddlesize)
|
|
|
|
paddleRight = Paddle(self, width - 1, (height - paddlesize)//2, paddlesize)
|
|
|
|
self.__paddles = [paddleLeft, paddleRight]
|
|
|
|
|
|
|
|
self.cancelled = False
|
|
|
|
|
|
|
|
self.__ball = Ball(self, 0, 0, 1, 1)
|
|
|
|
self.__gameTickCallbacks = []
|
|
|
|
self.__newRoundCallbacks = []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def width(self):
|
|
|
|
return self.__width
|
|
|
|
@property
|
|
|
|
def height(self):
|
|
|
|
return self.__height
|
|
|
|
@property
|
|
|
|
def leftPaddle(self):
|
|
|
|
return self.__paddles[0]
|
|
|
|
@property
|
|
|
|
def rightPaddle(self):
|
|
|
|
return self.__paddles[1]
|
|
|
|
@property
|
|
|
|
def ball(self):
|
|
|
|
return self.__ball
|
|
|
|
|
|
|
|
def containsPoint(self, x, y):
|
|
|
|
if x >= 0 and x < self.width and y >= 0 and y < self.height:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def addGameTickCallback(self, callback):
|
|
|
|
self.__gameTickCallbacks.append(callback)
|
|
|
|
print('registered callback',callback)
|
|
|
|
def addNewRoundCallback(self, callback):
|
|
|
|
self.__newRoundCallbacks.append(callback)
|
|
|
|
|
|
|
|
def getObjectAtPosition(self, x, y):
|
|
|
|
if x >= self.__width or x < 0:
|
|
|
|
return Wall(Wall.VERTICAL)
|
|
|
|
elif y >= self.__height or y < 0:
|
|
|
|
return Wall(Wall.HORIZONTAL)
|
|
|
|
elif y == self.__ball.ypos and x == self.__ball.xpos:
|
|
|
|
return self.__ball
|
|
|
|
else:
|
|
|
|
for paddle in self.__paddles:
|
|
|
|
if paddle.containsPoint(x, y):
|
|
|
|
return paddle
|
|
|
|
|
|
|
|
def cancel(self):
|
|
|
|
self.cancelled = True
|
|
|
|
|
|
|
|
def play(self, serve=None):
|
|
|
|
leftPaddle = self.__paddles[0]
|
|
|
|
rightPaddle = self.__paddles[1]
|
|
|
|
ball = self.__ball
|
|
|
|
|
|
|
|
if serve not in self.__paddles:
|
|
|
|
serve = self.__paddles[random.randint(0, 1)]
|
|
|
|
|
|
|
|
ball.ypos = self.__height // 2
|
|
|
|
ball.xpos = self.__width // 2
|
|
|
|
if serve == rightPaddle and self.__width % 2 == 1:
|
|
|
|
ball.xpos += 1
|
|
|
|
|
|
|
|
ball.yspeed = 0
|
|
|
|
if serve == rightPaddle:
|
|
|
|
ball.xspeed = 1
|
|
|
|
else:
|
|
|
|
ball.yspeed = -1
|
|
|
|
|
|
|
|
for callback in self.__newRoundCallbacks:
|
|
|
|
callback()
|
|
|
|
|
|
|
|
ticks = 0
|
|
|
|
while not self.cancelled:
|
|
|
|
ticks += 1
|
|
|
|
time.sleep(0.08)
|
|
|
|
if ticks %2 == 0:
|
|
|
|
ball.move()
|
|
|
|
leftPaddle.doNextMove()
|
|
|
|
rightPaddle.doNextMove()
|
|
|
|
|
|
|
|
if not self.containsPoint(ball.xpos, ball.ypos):
|
|
|
|
break
|
|
|
|
|
|
|
|
for callback in self.__gameTickCallbacks:
|
|
|
|
#print(callback)
|
|
|
|
callback()
|
|
|
|
|
|
|
|
if ball.xpos >= self.width:
|
|
|
|
return leftPaddle
|
|
|
|
elif ball.xpos <= 0:
|
|
|
|
return rightPaddle
|
|
|
|
|
|
|
|
|
|
|
|
def getRandomColor(maxval):
|
|
|
|
return list(map(lambda x: int(round(x*maxval)), colorsys.hsv_to_rgb(random.random(), 1, 1)))
|
|
|
|
#return map(lambda x: int(round(x*maxval)), colorsys.hsv_to_rgb(random.random(), random.random(), random.random()))
|
|
|
|
|
|
|
|
|
|
|
|
class PlaygroundPainter(object):
|
|
|
|
def __init__(self, out, dimension, playground):
|
|
|
|
self.__out = out
|
|
|
|
self.__playground = playground
|
|
|
|
self.__dimension = dimension
|
|
|
|
self.__playground.addGameTickCallback(self.update)
|
|
|
|
self.__playground.ball.addHitCallback(self.ballhit)
|
|
|
|
|
|
|
|
if dimension.channels == 1:
|
|
|
|
self.__ballColor = 1
|
|
|
|
self.__paddleColor = 1
|
|
|
|
else:
|
|
|
|
self.__ballColor = getRandomColor(dimension.depth - 1)
|
|
|
|
self.__leftPaddleColor = getRandomColor(dimension.depth - 1)
|
|
|
|
self.__rightPaddleColor = getRandomColor(dimension.depth - 1)
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
frame = blup.frame.Frame(self.__dimension)
|
|
|
|
#self.__paddleColor = getRandomColor(self.__dimension.depth - 1)
|
|
|
|
|
|
|
|
if self.__dimension.channels == 3:
|
|
|
|
for x in range(self.__dimension.width):
|
|
|
|
for y in range(self.__dimension.height):
|
|
|
|
frame.setPixel(x, y, (0,0,0))
|
|
|
|
|
|
|
|
frame.setPixel(self.__playground.ball.xpos, self.__playground.ball.ypos, self.__ballColor)
|
|
|
|
|
|
|
|
for i in range(self.__playground.leftPaddle.size):
|
|
|
|
frame.setPixel(self.__playground.leftPaddle.xpos, self.__playground.leftPaddle.ypos + i, self.__leftPaddleColor)
|
|
|
|
for i in range(self.__playground.rightPaddle.size):
|
|
|
|
frame.setPixel(self.__playground.rightPaddle.xpos, self.__playground.rightPaddle.ypos + i, self.__rightPaddleColor)
|
|
|
|
|
|
|
|
self.__out.sendFrame(frame)
|
|
|
|
|
|
|
|
def ballhit(self, obj):
|
|
|
|
if self.__dimension.channels == 3:
|
|
|
|
if isinstance(obj, Paddle):
|
|
|
|
self.__ballColor = getRandomColor(self.__dimension.depth - 1)
|
|
|
|
if obj.xpos == 0:
|
|
|
|
self.__leftPaddleColor = self.__ballColor
|
|
|
|
else:
|
|
|
|
self.__rightPaddleColor = self.__ballColor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PongBot(object):
|
|
|
|
def __init__(self, playground, ownPaddle, dullness=0):
|
|
|
|
self.__playground = playground
|
|
|
|
self.__ownPaddle = ownPaddle
|
|
|
|
self.__dullness = dullness
|
|
|
|
self.__playground.addGameTickCallback(self.onGameTick)
|
|
|
|
self.__playground.addNewRoundCallback(self.onNewRound)
|
|
|
|
self.__playground.ball.addHitCallback(self.onHit)
|
|
|
|
self.__hitpoint = None
|
|
|
|
self.__oldHitpoint = None
|
|
|
|
self.__dull = False
|
|
|
|
|
|
|
|
def updateHitpoint(self):
|
|
|
|
origball = self.__playground.ball
|
|
|
|
ball = Ball(self.__playground, origball.xpos, origball.ypos, origball.xspeed, origball.yspeed)
|
|
|
|
ball.move()
|
|
|
|
hitpoint = None
|
|
|
|
while hitpoint is None:
|
|
|
|
if ball.xpos >= self.__playground.width - 1 or ball.xpos <= 0:
|
|
|
|
hitpoint = (ball.xpos, ball.ypos)
|
|
|
|
break
|
|
|
|
|
|
|
|
ball.move(ignorePaddles=True)
|
|
|
|
self.__oldHitpoint = self.__hitpoint
|
|
|
|
self.__hitpoint = hitpoint
|
|
|
|
|
|
|
|
def onHit(self, obj):
|
|
|
|
print('hit!', obj)
|
|
|
|
if isinstance(obj, Paddle) and obj is not self.__ownPaddle:
|
|
|
|
self.updateHitpoint()
|
|
|
|
|
|
|
|
if self.__dullness > random.randint(0, 4):
|
|
|
|
self.__dull = True
|
|
|
|
else:
|
|
|
|
self.__dull = False
|
|
|
|
|
|
|
|
def onNewRound(self):
|
|
|
|
self.updateHitpoint()
|
|
|
|
self.__oldHitpoint = None
|
|
|
|
self.__dull = False
|
|
|
|
|
|
|
|
def onGameTick(self):
|
|
|
|
#self.updateHitpoint()
|
|
|
|
print('hitpoint', self.__hitpoint)
|
|
|
|
(hitx, hity) = self.__hitpoint
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if hitx == self.__ownPaddle.xpos:
|
|
|
|
if abs(self.__playground.ball.xpos - self.__ownPaddle.xpos) < 15:
|
|
|
|
if not self.__dull:
|
|
|
|
if not self.__ownPaddle.containsPoint(hitx, hity):
|
|
|
|
print('moving!!')
|
|
|
|
if self.__ownPaddle.ypos < hity:
|
|
|
|
self.__ownPaddle.nextMoveDown()
|
|
|
|
else:
|
|
|
|
self.__ownPaddle.nextMoveUp()
|
|
|
|
else:
|
|
|
|
if self.__ownPaddle.containsPoint(hitx, hity):
|
|
|
|
print('moving!!')
|
|
|
|
if hity < self.__ownPaddle.size:
|
|
|
|
self.__ownPaddle.nextMoveDown()
|
|
|
|
else:
|
|
|
|
self.__ownPaddle.nextMoveUp()
|
|
|
|
|
|
|
|
elif self.__dull:
|
|
|
|
r = random.randint(-1, 1)
|
|
|
|
if r == -1:
|
|
|
|
self.__ownPaddle.nextMoveUp()
|
|
|
|
elif r == 1:
|
|
|
|
self.__ownPaddle.nextMoveDown()
|
|
|
|
|
|
|
|
|
|
|
|
def displayScore(out, dimension, leftScore, rightScore, delay=0):
|
|
|
|
leftNumber = __numbers[leftScore]
|
|
|
|
rightNumber = __numbers[rightScore]
|
|
|
|
|
|
|
|
frame = blup.frame.Frame(dimension)
|
|
|
|
|
|
|
|
if dimension.channels == 1:
|
|
|
|
dotcolor = 1
|
|
|
|
numcolor = 1
|
|
|
|
else:
|
|
|
|
dotcolor = getRandomColor(dimension.depth - 1)
|
|
|
|
numcolor = getRandomColor(dimension.depth - 1)
|
|
|
|
|
|
|
|
xoffs = (dimension.width - 18) // 2
|
|
|
|
yoffs = (dimension.height - 8) // 2
|
|
|
|
|
|
|
|
frame.setPixel(xoffs + 8, yoffs + 1, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 9, yoffs + 1, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 8, yoffs + 2, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 9, yoffs + 2, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 8, yoffs + 4, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 9, yoffs + 4, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 8, yoffs + 5, dotcolor)
|
|
|
|
frame.setPixel(xoffs + 9, yoffs + 5, dotcolor)
|
|
|
|
|
|
|
|
for x in range(5):
|
|
|
|
for y in range(5):
|
|
|
|
if leftNumber[y][x] == 1:
|
|
|
|
frame.setPixel(xoffs + x+1, yoffs + y+1, numcolor)
|
|
|
|
if rightNumber[y][x] == 1:
|
|
|
|
frame.setPixel(xoffs + x+12, yoffs + y+1, numcolor)
|
|
|
|
|
|
|
|
out.sendFrame(frame)
|
|
|
|
if delay > 0:
|
|
|
|
time.sleep(delay / 1000.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
print('running a \'Bot vs. Bot\' round...')
|
|
|
|
#out = blup.output.getOutput('blp:localhost:42421')
|
|
|
|
#out = blup.output.getOutput('e3blp:bastel0:4242')
|
|
|
|
out = blup.output.getOutput('e3blp:blinkenbunt:4242')
|
|
|
|
#out = blup.output.getOutput('e3blp:bbunt:42429')
|
|
|
|
#dim = blup.frame.FrameDimension(18, 8, 8, 3)
|
|
|
|
dim = blup.frame.FrameDimension(22, 16, 255, 3)
|
|
|
|
|
|
|
|
playground = Playground(22, 16, paddlesize=4)
|
|
|
|
pp = PlaygroundPainter(out, dim, playground)
|
|
|
|
|
|
|
|
leftPaddle = playground.leftPaddle
|
|
|
|
rightPaddle = playground.rightPaddle
|
|
|
|
ball = playground.ball
|
|
|
|
|
|
|
|
leftBot = PongBot(playground, leftPaddle, 1)
|
|
|
|
rightBot = PongBot(playground, rightPaddle, 1)
|
|
|
|
|
|
|
|
print(playground.play())
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
|
|
|
|
|
|
|
|