forked from Blinkenbunt/blup
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.
462 lines
15 KiB
462 lines
15 KiB
#!/usr/bin/python |
|
|
|
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.__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 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 True: |
|
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() |
|
|
|
|
|
|