You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
15 KiB

#!/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()