added py-pong
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,67 @@
|
||||||
|
import pygame, pypong
|
||||||
|
from pypong.player import BasicAIPlayer, KeyboardPlayer, MousePlayer
|
||||||
|
|
||||||
|
def run():
|
||||||
|
configuration = {
|
||||||
|
'screen_size': (686,488),
|
||||||
|
'paddle_image': 'assets/paddle.png',
|
||||||
|
'paddle_left_position': 84.,
|
||||||
|
'paddle_right_position': 594.,
|
||||||
|
'paddle_velocity': 6.,
|
||||||
|
'paddle_bounds': (0, 488), # This sets the upper and lower paddle boundary.The original game didn't allow the paddle to touch the edge,
|
||||||
|
'line_image': 'assets/dividing-line.png',
|
||||||
|
'ball_image': 'assets/ball.png',
|
||||||
|
'ball_velocity': 4.,
|
||||||
|
'ball_velocity_bounce_multiplier': 1.105,
|
||||||
|
'ball_velocity_max': 32.,
|
||||||
|
'score_left_position': (141, 30),
|
||||||
|
'score_right_position': (473, 30),
|
||||||
|
'digit_image': 'assets/digit_%i.png',
|
||||||
|
'sound_missed': 'assets/missed-ball.wav',
|
||||||
|
'sound_paddle': 'assets/bounce-paddle.wav',
|
||||||
|
'sound_wall': 'assets/bounce-wall.wav',
|
||||||
|
'sound': True,
|
||||||
|
}
|
||||||
|
pygame.mixer.pre_init(22050, -16, 2, 1024)
|
||||||
|
pygame.init()
|
||||||
|
display_surface = pygame.display.set_mode(configuration['screen_size'])
|
||||||
|
output_surface = display_surface.copy().convert_alpha()
|
||||||
|
output_surface.fill((0,0,0))
|
||||||
|
#~ debug_surface = output_surface.copy()
|
||||||
|
#~ debug_surface.fill((0,0,0,0))
|
||||||
|
debug_surface = None
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
input_state = {'key': None, 'mouse': None}
|
||||||
|
|
||||||
|
# Prepare game
|
||||||
|
player_left = KeyboardPlayer(input_state, pygame.K_w, pygame.K_s)
|
||||||
|
#~ player_right = MousePlayer(input_state)
|
||||||
|
|
||||||
|
#player_left = BasicAIPlayer()
|
||||||
|
player_right = BasicAIPlayer()
|
||||||
|
game = pypong.Game(player_left, player_right, configuration)
|
||||||
|
|
||||||
|
# Main game loop
|
||||||
|
timestamp = 1
|
||||||
|
while game.running:
|
||||||
|
clock.tick(60)
|
||||||
|
now = pygame.time.get_ticks()
|
||||||
|
if timestamp > 0 and timestamp < now:
|
||||||
|
timestamp = now + 5000
|
||||||
|
print clock.get_fps()
|
||||||
|
input_state['key'] = pygame.key.get_pressed()
|
||||||
|
input_state['mouse'] = pygame.mouse.get_pos()
|
||||||
|
game.update()
|
||||||
|
game.draw(output_surface)
|
||||||
|
#~ pygame.surfarray.pixels_alpha(output_surface)[:,::2] = 12
|
||||||
|
display_surface.blit(output_surface, (0,0))
|
||||||
|
if debug_surface:
|
||||||
|
display_surface.blit(debug_surface, (0,0))
|
||||||
|
pygame.display.flip()
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
game.running = False
|
||||||
|
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
|
||||||
|
game.running = False
|
||||||
|
|
||||||
|
if __name__ == '__main__': run()
|
|
@ -0,0 +1,150 @@
|
||||||
|
import pygame, math, random, entity
|
||||||
|
|
||||||
|
def load_image(path):
|
||||||
|
surface = pygame.image.load(path)
|
||||||
|
surface.convert()
|
||||||
|
pygame.surfarray.pixels3d(surface)[:,:,0:1:] = 0
|
||||||
|
return surface
|
||||||
|
|
||||||
|
def line_line_intersect(x1, y1, x2, y2, x3, y3, x4, y4):
|
||||||
|
# Taken from http://paulbourke.net/geometry/lineline2d/
|
||||||
|
# Denominator for ua and ub are the same, so store this calculation
|
||||||
|
d = float((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
|
||||||
|
# n_a and n_b are calculated as seperate values for readability
|
||||||
|
n_a = float((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3))
|
||||||
|
n_b = float((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3))
|
||||||
|
# Make sure there is not a division by zero - this also indicates that
|
||||||
|
# the lines are parallel.
|
||||||
|
# If n_a and n_b were both equal to zero the lines would be on top of each
|
||||||
|
# other (coincidental). This check is not done because it is not
|
||||||
|
# necessary for this implementation (the parallel check accounts for this).
|
||||||
|
if d == 0:
|
||||||
|
return False
|
||||||
|
# Calculate the intermediate fractional point that the lines potentially intersect.
|
||||||
|
ua = n_a / d
|
||||||
|
ub = n_b / d
|
||||||
|
# The fractional point will be between 0 and 1 inclusive if the lines
|
||||||
|
# intersect. If the fractional calculation is larger than 1 or smaller
|
||||||
|
# than 0 the lines would need to be longer to intersect.
|
||||||
|
if ua >= 0. and ua <= 1. and ub >= 0. and ub <= 1.:
|
||||||
|
return [x1 + (ua * (x2 - x1)), y1 + (ua * (y2 - y1))]
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Game(object):
|
||||||
|
def __init__(self, player_left, player_right, configuration):
|
||||||
|
self.player_left = player_left
|
||||||
|
self.player_right = player_right
|
||||||
|
self.configuration = configuration
|
||||||
|
self.background = pygame.Surface(configuration['screen_size'])
|
||||||
|
self.sprites = pygame.sprite.OrderedUpdates()
|
||||||
|
line = entity.Line(load_image(configuration['line_image']), self.sprites)
|
||||||
|
line.rect.topleft = ((configuration['screen_size'][0]-line.rect.width)/2, 0)
|
||||||
|
paddle_image = load_image(configuration['paddle_image'])
|
||||||
|
self.paddle_left = entity.Paddle(configuration['paddle_velocity'], paddle_image, configuration['paddle_bounds'], self.sprites)
|
||||||
|
self.paddle_right = entity.Paddle(configuration['paddle_velocity'], paddle_image, configuration['paddle_bounds'], self.sprites)
|
||||||
|
self.paddle_left.rect.topleft = (self.configuration['paddle_left_position'], (self.configuration['screen_size'][1]-self.paddle_left.rect.height)/2)
|
||||||
|
self.paddle_right.rect.topleft = (self.configuration['paddle_right_position'], (self.configuration['screen_size'][1]-self.paddle_left.rect.height)/2)
|
||||||
|
digit_images = [load_image(configuration['digit_image'] % n) for n in xrange(10)]
|
||||||
|
self.score_left = entity.Score(digit_images, self.sprites)
|
||||||
|
self.score_left.rect.topleft = configuration['score_left_position']
|
||||||
|
self.score_right = entity.Score(digit_images, self.sprites)
|
||||||
|
self.score_right.rect.topleft = configuration['score_right_position']
|
||||||
|
ball_image = load_image(configuration['ball_image'])
|
||||||
|
self.ball = entity.Ball(self.configuration['ball_velocity'], ball_image, self.sprites)
|
||||||
|
self.bounds = pygame.Rect(20, 0, configuration['screen_size'][0]-ball_image.get_width()-20, configuration['screen_size'][1]-ball_image.get_height())
|
||||||
|
self.sound_missed = pygame.mixer.Sound(configuration['sound_missed'])
|
||||||
|
self.sound_paddle = pygame.mixer.Sound(configuration['sound_paddle'])
|
||||||
|
self.sound_wall = pygame.mixer.Sound(configuration['sound_wall'])
|
||||||
|
self.reset_game(random.random()<0.5)
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
def play_sound(self, sound):
|
||||||
|
if self.configuration['sound']:
|
||||||
|
sound.play()
|
||||||
|
|
||||||
|
def reset_game(self, serveLeft=True):
|
||||||
|
y = self.configuration['screen_size'][1] - self.ball.rect.height
|
||||||
|
self.ball.position_x = (self.configuration['screen_size'][0]-self.ball.rect.width)/2.0
|
||||||
|
self.ball.position_y = y * random.random()
|
||||||
|
self.ball.velocity = self.configuration['ball_velocity']
|
||||||
|
a = random.random() * math.pi / 2. - math.pi / 4.
|
||||||
|
self.ball.velocity_vec[0] = self.ball.velocity * math.cos(a)
|
||||||
|
self.ball.velocity_vec[1] = self.ball.velocity * math.sin(a)
|
||||||
|
if random.random() < 0.5:
|
||||||
|
self.ball.velocity_vec[1] = -self.ball.velocity_vec[1]
|
||||||
|
if serveLeft:
|
||||||
|
self.ball.velocity_vec[0] *= -1
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
# Store previous ball position for line-line intersect test later
|
||||||
|
ball_position_x = self.ball.position_x
|
||||||
|
ball_position_y = self.ball.position_y
|
||||||
|
# Update sprites and players
|
||||||
|
self.sprites.update()
|
||||||
|
self.player_left.update(self.paddle_left, self)
|
||||||
|
self.player_right.update(self.paddle_right, self)
|
||||||
|
# Paddle collision check. Could probably just do a line-line intersect but I think I prefer having the pixel-pefect result of a rect-rect intersect test as well.
|
||||||
|
if self.ball.rect.x < self.bounds.centerx:
|
||||||
|
# Left side bullet-through-paper check on ball and paddle
|
||||||
|
if self.ball.velocity_vec[0] < 0:
|
||||||
|
intersect_point = line_line_intersect(
|
||||||
|
self.paddle_left.rect.right, self.paddle_left.rect.top,
|
||||||
|
self.paddle_left.rect.right, self.paddle_left.rect.bottom,
|
||||||
|
ball_position_x-self.ball.rect.width/2, ball_position_y+self.ball.rect.height/2,
|
||||||
|
self.ball.position_x-self.ball.rect.width/2, self.ball.position_y+self.ball.rect.height/2
|
||||||
|
)
|
||||||
|
if intersect_point:
|
||||||
|
self.ball.position_y = intersect_point[1]-self.ball.rect.height/2
|
||||||
|
if intersect_point or (self.paddle_left.rect.colliderect(self.ball.rect) and self.ball.rect.right > self.paddle_left.rect.right):
|
||||||
|
self.ball.position_x = self.paddle_left.rect.right
|
||||||
|
velocity = self.paddle_left.calculate_bounce(min(1,max(0,(self.ball.rect.centery - self.paddle_left.rect.y)/float(self.paddle_left.rect.height))))
|
||||||
|
self.ball.velocity = min(self.configuration['ball_velocity_max'], self.ball.velocity * self.configuration['ball_velocity_bounce_multiplier'])
|
||||||
|
self.ball.velocity_vec[0] = velocity[0] * self.ball.velocity
|
||||||
|
self.ball.velocity_vec[1] = velocity[1] * self.ball.velocity
|
||||||
|
self.player_left.hit()
|
||||||
|
self.play_sound(self.sound_paddle)
|
||||||
|
else:
|
||||||
|
# Right side bullet-through-paper check on ball and paddle.
|
||||||
|
if self.ball.velocity_vec[0] > 0:
|
||||||
|
intersect_point = line_line_intersect(
|
||||||
|
self.paddle_right.rect.left, self.paddle_right.rect.top,
|
||||||
|
self.paddle_right.rect.left, self.paddle_right.rect.bottom,
|
||||||
|
ball_position_x-self.ball.rect.width/2, ball_position_y+self.ball.rect.height/2,
|
||||||
|
self.ball.position_x-self.ball.rect.width/2, self.ball.position_y+self.ball.rect.height/2
|
||||||
|
)
|
||||||
|
if intersect_point:
|
||||||
|
self.ball.position_y = intersect_point[1]-self.ball.rect.height/2
|
||||||
|
if intersect_point or (self.paddle_right.rect.colliderect(self.ball.rect) and self.ball.rect.x < self.paddle_right.rect.x):
|
||||||
|
self.ball.position_x = self.paddle_right.rect.x - self.ball.rect.width
|
||||||
|
velocity = self.paddle_right.calculate_bounce(min(1,max(0,(self.ball.rect.centery - self.paddle_right.rect.y)/float(self.paddle_right.rect.height))))
|
||||||
|
self.ball.velocity = min(self.configuration['ball_velocity_max'], self.ball.velocity * self.configuration['ball_velocity_bounce_multiplier'])
|
||||||
|
self.ball.velocity_vec[0] = -velocity[0] * self.ball.velocity
|
||||||
|
self.ball.velocity_vec[1] = velocity[1] * self.ball.velocity
|
||||||
|
self.player_right.hit()
|
||||||
|
self.play_sound(self.sound_paddle)
|
||||||
|
# Bounds collision check
|
||||||
|
if self.ball.rect.y < self.bounds.top:
|
||||||
|
self.ball.position_y = float(self.bounds.top)
|
||||||
|
self.ball.velocity_vec[1] = -self.ball.velocity_vec[1]
|
||||||
|
self.play_sound(self.sound_wall)
|
||||||
|
elif self.ball.rect.y > self.bounds.bottom:
|
||||||
|
self.ball.position_y = float(self.bounds.bottom)
|
||||||
|
self.ball.velocity_vec[1] = -self.ball.velocity_vec[1]
|
||||||
|
self.play_sound(self.sound_wall)
|
||||||
|
# Check the ball is still in play
|
||||||
|
if self.ball.rect.x < self.bounds.x:
|
||||||
|
self.player_left.lost()
|
||||||
|
self.player_right.won()
|
||||||
|
self.score_right.score += 1
|
||||||
|
self.reset_game(False)
|
||||||
|
self.play_sound(self.sound_missed)
|
||||||
|
if self.ball.rect.x > self.bounds.right:
|
||||||
|
self.player_left.won()
|
||||||
|
self.player_right.lost()
|
||||||
|
self.score_left.score += 1
|
||||||
|
self.reset_game(True)
|
||||||
|
self.play_sound(self.sound_missed)
|
||||||
|
|
||||||
|
def draw(self, display_surface):
|
||||||
|
self.sprites.clear(display_surface, self.background)
|
||||||
|
return self.sprites.draw(display_surface)
|
|
@ -0,0 +1,87 @@
|
||||||
|
import pygame, math
|
||||||
|
from pygame.sprite import Sprite
|
||||||
|
|
||||||
|
class Paddle(Sprite):
|
||||||
|
def __init__(self, velocity, image, bounds_y, *groups):
|
||||||
|
Sprite.__init__(self, *groups)
|
||||||
|
self.image = image
|
||||||
|
self.rect = self.image.get_rect()
|
||||||
|
self.direction = 0
|
||||||
|
self.velocity = velocity
|
||||||
|
self.bounds_y = bounds_y
|
||||||
|
# Like original pong, we break this up into 8 segments from the edge angle (acute_angle) to pi/2 at the center
|
||||||
|
# Changing acute_angle lets us change the extreme edge angle of the paddle.
|
||||||
|
acute_angle = .125
|
||||||
|
# Build the angles from acute_angle to the first 0.5 center value then append the values going from the
|
||||||
|
# second center 0.5 value by using the values we just calculated reversed.
|
||||||
|
angles = [acute_angle + (0.5-acute_angle)/3.0 * n for n in xrange(4)]
|
||||||
|
angles += map(lambda x: 1 + x * -1, reversed(angles))
|
||||||
|
# Final table is the output vector (x,y) of each angle
|
||||||
|
self.bounce_table = [(math.cos(n*math.pi-math.pi/2.0), math.sin(n*math.pi-math.pi/2.0)) for n in angles]
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.rect.y = max(self.bounds_y[0], min(self.bounds_y[1]-self.rect.height, self.rect.y + self.direction * self.velocity))
|
||||||
|
|
||||||
|
def calculate_bounce(self, delta):
|
||||||
|
return self.bounce_table[int(round(delta * (len(self.bounce_table)-1)))]
|
||||||
|
|
||||||
|
class Line(Sprite):
|
||||||
|
def __init__(self, image, *groups):
|
||||||
|
Sprite.__init__(self, *groups)
|
||||||
|
self.image = image
|
||||||
|
self.rect = self.image.get_rect()
|
||||||
|
|
||||||
|
class Ball(Sprite):
|
||||||
|
def __init__(self, velocity, image, *groups):
|
||||||
|
Sprite.__init__(self, *groups)
|
||||||
|
self.velocity = velocity
|
||||||
|
self.image = image
|
||||||
|
self.rect = self.image.get_rect()
|
||||||
|
self.position_vec = [0., 0.]
|
||||||
|
self.velocity_vec = [0., 0.]
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.position_vec[0] += self.velocity_vec[0]
|
||||||
|
self.position_vec[1] += self.velocity_vec[1]
|
||||||
|
self.rect.x = self.position_vec[0]
|
||||||
|
self.rect.y = self.position_vec[1]
|
||||||
|
|
||||||
|
def set_position_x(self, value):
|
||||||
|
self.position_vec[0] = value
|
||||||
|
self.rect.left = value
|
||||||
|
position_x = property(lambda self: self.position_vec[0], set_position_x)
|
||||||
|
|
||||||
|
def set_position_y(self, value):
|
||||||
|
self.position_vec[1] = value
|
||||||
|
self.rect.top = value
|
||||||
|
position_y = property(lambda self: self.position_vec[1], set_position_y)
|
||||||
|
|
||||||
|
class Score(Sprite):
|
||||||
|
def __init__(self, image_list, *groups):
|
||||||
|
Sprite.__init__(self, *groups)
|
||||||
|
self.image_list = image_list
|
||||||
|
self.image = None
|
||||||
|
self.rect = pygame.Rect(0,0,0,0)
|
||||||
|
self.score = 0
|
||||||
|
|
||||||
|
def get_score(self):
|
||||||
|
return self.score_value
|
||||||
|
|
||||||
|
def set_score(self, value):
|
||||||
|
self.score_value = value
|
||||||
|
digit_spacing = 8
|
||||||
|
digit_width = self.image_list[0].get_width()
|
||||||
|
digit_height = self.image_list[0].get_height()
|
||||||
|
values = map(int, reversed(str(self.score_value)))
|
||||||
|
surface_width = len(values) * digit_width + (len(values)-1) * digit_spacing
|
||||||
|
if not self.image or self.image.get_width() < surface_width:
|
||||||
|
self.image = pygame.Surface((surface_width, digit_height))
|
||||||
|
self.image.fill((0,0,0))
|
||||||
|
self.rect.width = self.image.get_width()
|
||||||
|
self.rect.height = self.image.get_height()
|
||||||
|
offset = self.image.get_width()-digit_width
|
||||||
|
for i in values:
|
||||||
|
self.image.blit(self.image_list[i], (offset, 0))
|
||||||
|
offset = offset - (digit_width + digit_spacing)
|
||||||
|
|
||||||
|
score = property(get_score, set_score)
|
|
@ -0,0 +1,81 @@
|
||||||
|
import pygame, random
|
||||||
|
|
||||||
|
class BasicAIPlayer(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.bias = random.random() - 0.5
|
||||||
|
self.hit_count = 0
|
||||||
|
|
||||||
|
def update(self, paddle, game):
|
||||||
|
# Dead simple AI, waits until the ball is on its side of the screen then moves the paddle to intercept.
|
||||||
|
# A bias is used to decide which edge of the paddle is going to be favored.
|
||||||
|
if (paddle.rect.x < game.bounds.centerx and game.ball.rect.x < game.bounds.centerx) or (paddle.rect.x > game.bounds.centerx and game.ball.rect.x > game.bounds.centerx):
|
||||||
|
delta = (paddle.rect.centery + self.bias * paddle.rect.height) - game.ball.rect.centery
|
||||||
|
if abs(delta) > paddle.velocity:
|
||||||
|
if delta > 0:
|
||||||
|
paddle.direction = -1
|
||||||
|
else:
|
||||||
|
paddle.direction = 1
|
||||||
|
else:
|
||||||
|
paddle.direction = 0
|
||||||
|
else:
|
||||||
|
paddle.direction = 0
|
||||||
|
|
||||||
|
def hit(self):
|
||||||
|
self.hit_count += 1
|
||||||
|
if self.hit_count > 6:
|
||||||
|
self.bias = random.random() - 0.5 # Recalculate our bias, this game is going on forever
|
||||||
|
self.hit_count = 0
|
||||||
|
|
||||||
|
def lost(self):
|
||||||
|
# If we lose, randomise the bias again
|
||||||
|
self.bias = random.random() - 0.5
|
||||||
|
|
||||||
|
def won(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class KeyboardPlayer(object):
|
||||||
|
def __init__(self, input_state, up_key=None, down_key=None):
|
||||||
|
self.input_state = input_state
|
||||||
|
self.up_key = up_key
|
||||||
|
self.down_key = down_key
|
||||||
|
|
||||||
|
def update(self, paddle, game):
|
||||||
|
if self.input_state['key'][self.up_key]:
|
||||||
|
paddle.direction = -1
|
||||||
|
elif self.input_state['key'][self.down_key]:
|
||||||
|
paddle.direction = 1
|
||||||
|
else:
|
||||||
|
paddle.direction = 0
|
||||||
|
|
||||||
|
def hit(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def lost(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def won(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MousePlayer(object):
|
||||||
|
def __init__(self, input_state):
|
||||||
|
self.input_state = input_state
|
||||||
|
pygame.mouse.set_visible(False)
|
||||||
|
|
||||||
|
def update(self, paddle, game):
|
||||||
|
centery = paddle.rect.centery/int(paddle.velocity)
|
||||||
|
mousey = self.input_state['mouse'][1]/int(paddle.velocity)
|
||||||
|
if centery > mousey:
|
||||||
|
paddle.direction = -1
|
||||||
|
elif centery < mousey:
|
||||||
|
paddle.direction = 1
|
||||||
|
else:
|
||||||
|
paddle.direction = 0
|
||||||
|
|
||||||
|
def hit(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def lost(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def won(self):
|
||||||
|
pass
|