import math
import sys
import pygame
from pygame.constants import K_w, K_s
from utils.vec2d import vec2d
from utils import percent_round_int
import base
class Ball(pygame.sprite.Sprite):
def __init__(self, radius, speed,
pos_init, SCREEN_WIDTH, SCREEN_HEIGHT):
pygame.sprite.Sprite.__init__(self)
self.radius = radius
self.speed = speed
self.pos = vec2d(pos_init)
self.vel = vec2d((speed, -1.0*speed))
self.SCREEN_HEIGHT = SCREEN_HEIGHT
self.SCREEN_WIDTH = SCREEN_WIDTH
image = pygame.Surface((radius*2, radius*2))
image.fill((0, 0, 0, 0))
image.set_colorkey((0,0,0))
pygame.draw.circle(
image,
(255, 255, 255),
(radius, radius),
radius,
0
)
self.image = image
self.rect = self.image.get_rect()
self.rect.center = pos_init
def update(self, agentPlayer, cpuPlayer, dt):
if self.pos.y-self.radius <= 0:
self.vel.y *= -0.99
self.pos.y += 1.0
if self.pos.y+self.radius >= self.SCREEN_HEIGHT:
self.vel.y *= -0.99
self.pos.y -= 1.0
if self.pos.x <= agentPlayer.pos.x+agentPlayer.rect_width:
if pygame.sprite.collide_rect(self, agentPlayer):
self.vel.x = -1*(self.vel.x + self.speed*0.05)
self.vel.y += agentPlayer.vel.y*0.01
self.pos.x += self.radius
if self.pos.x >= cpuPlayer.pos.x-cpuPlayer.rect_width:
if pygame.sprite.collide_rect(self, cpuPlayer):
self.vel.x = -1*(self.vel.x + self.speed*0.05)
self.vel.y += cpuPlayer.vel.y*0.006
self.pos.x -= self.radius
self.pos.x += self.vel.x * dt
self.pos.y += self.vel.y * dt
self.rect.center = (self.pos.x, self.pos.y)
class Player(pygame.sprite.Sprite):
def __init__(self, speed, rect_width, rect_height,
pos_init, SCREEN_WIDTH, SCREEN_HEIGHT):
pygame.sprite.Sprite.__init__(self)
self.speed = speed
self.pos = vec2d(pos_init)
self.vel = vec2d((0, 0))
self.rect_height = rect_height
self.rect_width = rect_width
self.SCREEN_HEIGHT = SCREEN_HEIGHT
self.SCREEN_WIDTH = SCREEN_WIDTH
image = pygame.Surface((rect_width, rect_height))
image.fill((0, 0, 0, 0))
image.set_colorkey((0,0,0))
pygame.draw.rect(
image,
(255, 255, 255),
(0, 0, rect_width, rect_height),
0
)
self.image = image
self.rect = self.image.get_rect()
self.rect.center = pos_init
def update(self, dy, dt):
self.vel.y += dy*dt
self.vel.y *= 0.9
self.pos.y += self.vel.y
if self.pos.y-self.rect_height/2 <= 0:
self.pos.y = self.rect_height/2
self.vel.y = 0.0
if self.pos.y+self.rect_height/2 >= self.SCREEN_HEIGHT:
self.pos.y = self.SCREEN_HEIGHT-self.rect_height/2
self.vel.y = 0.0
self.rect.center = (self.pos.x, self.pos.y)
def updateCpu(self, ball, dt):
dy = 0.0
if ball.vel.x >= 0 and ball.pos.x >= self.SCREEN_WIDTH/2:
dy = self.speed
if self.pos.y > ball.pos.y:
dy = -1.0*dy
else:
dy = 1.0 * self.speed/4.0
if self.pos.y > self.SCREEN_HEIGHT/2.0:
dy = -1.0 * self.speed/4.0
if self.pos.y-self.rect_height/2 <= 0:
self.pos.y = self.rect_height/2
self.vel.y = 0.0
if self.pos.y+self.rect_height/2 >= self.SCREEN_HEIGHT:
self.pos.y = self.SCREEN_HEIGHT-self.rect_height/2
self.vel.y = 0.0
self.pos.y += dy*dt
self.rect.center = (self.pos.x, self.pos.y)
[docs]class Pong(base.Game):
"""
Loosely based on code from marti1125's `pong game`_.
.. _pong game: https://github.com/marti1125/pong/
Parameters
----------
width : int
Screen width.
height : int
Screen height, recommended to be same dimension as width.
MAX_SCORE : int (default: 11)
The max number of points the agent or cpu need to score to cause a terminal state.
"""
def __init__(self, width=64, height=48, MAX_SCORE=11):
actions = {
"up": K_w,
"down": K_s
}
base.Game.__init__(self, width, height, actions=actions)
#the %'s come from original values, wanted to keep same ratio when you
#increase the resolution.
self.ball_radius = percent_round_int(height, 0.03)
self.players_speed = 0.22*height
self.cpu_speed = 0.7*height
self.ball_speed = 0.75*height
self.paddle_width = percent_round_int(width, 0.023)
self.paddle_height = percent_round_int(height, 0.15)
self.paddle_dist_to_wall = percent_round_int(width, 0.0625)
self.MAX_SCORE = MAX_SCORE
self.dy = 0.0
self.score_sum = 0.0 #need to deal with 11 on either side winning
self.score_counts = {
"agent": 0.0,
"cpu": 0.0
}
def _handle_player_events(self):
self.dy = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
key = event.key
if key == self.actions['up']:
self.dy -= self.players_speed
if key == self.actions['down']:
self.dy += self.players_speed
[docs] def getGameState(self):
"""
Gets a non-visual state representation of the game.
Returns
-------
dict
* player y position.
* players velocity.
* cpu y position.
* ball x position.
* ball y position.
* ball x velocity.
* ball y velocity.
See code for structure.
"""
state = {
"player_y": self.agentPlayer.pos.y,
"player_velocity": self.agentPlayer.vel.y,
"cpu_y": self.cpuPlayer.pos.y,
"ball_x": self.ball.pos.x,
"ball_y": self.ball.pos.y,
"ball_velocity_x": self.ball.pos.x,
"ball_velocity_y": self.ball.pos.y
}
return state
def getScore(self):
return self.score_sum
def game_over(self):
#pong used 11 as max score
return (self.score_counts['agent'] == self.MAX_SCORE) or (self.score_counts['cpu'] == self.MAX_SCORE)
def init(self):
self.score_counts = {
"agent": 0.0,
"cpu": 0.0
}
self.score_sum = 0.0
self.ball = Ball(
self.ball_radius,
self.ball_speed,
(self.width/2, self.height/2),
self.width,
self.height
)
self.agentPlayer = Player(
self.players_speed,
self.paddle_width,
self.paddle_height,
(self.paddle_dist_to_wall, self.height/2),
self.width,
self.height)
self.cpuPlayer = Player(
self.cpu_speed,
self.paddle_width,
self.paddle_height,
(self.width-self.paddle_dist_to_wall, self.height/2),
self.width,
self.height)
self.players_group = pygame.sprite.Group()
self.players_group.add( self.agentPlayer )
self.players_group.add( self.cpuPlayer )
self.ball_group = pygame.sprite.Group()
self.ball_group.add( self.ball )
def _reset_ball(self, direction):
self.ball.pos.x = self.width/2 #move it to the center
#we go in the same direction that they lost in but at starting vel.
self.ball.vel.x = self.ball_speed*direction
self.ball.vel.y = (self.rng.random_sample()*self.ball_speed)-self.ball_speed
def step(self, dt):
dt /= 1000.0
self.screen.fill((0,0,0))
self._handle_player_events()
#doesnt make sense to have this, but include if needed.
self.score_sum += self.rewards["tick"]
#logic
if self.ball.pos.x <= 0:
self.score_sum += self.rewards["negative"]
self.score_counts["cpu"] += 1.0
self._reset_ball(-1)
if self.ball.pos.x >= self.width:
self.score_sum += self.rewards["positive"]
self.score_counts["agent"] += 1.0
self._reset_ball(1)
#winning
if self.score_counts['agent'] == self.MAX_SCORE:
self.score_sum += self.rewards["win"]
#losing
if self.score_counts['cpu'] == self.MAX_SCORE:
self.score_sum += self.rewards["loss"]
self.ball.update(self.agentPlayer, self.cpuPlayer, dt)
self.agentPlayer.update(self.dy, dt)
self.cpuPlayer.updateCpu(self.ball, dt)
self.players_group.draw(self.screen)
self.ball_group.draw(self.screen)
if __name__ == "__main__":
import numpy as np
pygame.init()
game = Pong(width=256, height=200)
game.screen = pygame.display.set_mode( game.getScreenDims(), 0, 32)
game.clock = pygame.time.Clock()
game.rng = np.random.RandomState(24)
game.init()
while True:
dt = game.clock.tick_busy_loop(60)
game.step(dt)
pygame.display.update()