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