diff --git a/tools/game/py-pong/assets/ball.png b/tools/game/py-pong/assets/ball.png new file mode 100644 index 0000000..f80e521 Binary files /dev/null and b/tools/game/py-pong/assets/ball.png differ diff --git a/tools/game/py-pong/assets/bounce-paddle.wav b/tools/game/py-pong/assets/bounce-paddle.wav new file mode 100644 index 0000000..313fd93 Binary files /dev/null and b/tools/game/py-pong/assets/bounce-paddle.wav differ diff --git a/tools/game/py-pong/assets/bounce-wall.wav b/tools/game/py-pong/assets/bounce-wall.wav new file mode 100644 index 0000000..bd9f843 Binary files /dev/null and b/tools/game/py-pong/assets/bounce-wall.wav differ diff --git a/tools/game/py-pong/assets/digit_0.png b/tools/game/py-pong/assets/digit_0.png new file mode 100644 index 0000000..17d1daf Binary files /dev/null and b/tools/game/py-pong/assets/digit_0.png differ diff --git a/tools/game/py-pong/assets/digit_1.png b/tools/game/py-pong/assets/digit_1.png new file mode 100644 index 0000000..69ff195 Binary files /dev/null and b/tools/game/py-pong/assets/digit_1.png differ diff --git a/tools/game/py-pong/assets/digit_2.png b/tools/game/py-pong/assets/digit_2.png new file mode 100644 index 0000000..cdc5776 Binary files /dev/null and b/tools/game/py-pong/assets/digit_2.png differ diff --git a/tools/game/py-pong/assets/digit_3.png b/tools/game/py-pong/assets/digit_3.png new file mode 100644 index 0000000..74df4fe Binary files /dev/null and b/tools/game/py-pong/assets/digit_3.png differ diff --git a/tools/game/py-pong/assets/digit_4.png b/tools/game/py-pong/assets/digit_4.png new file mode 100644 index 0000000..5f5b150 Binary files /dev/null and b/tools/game/py-pong/assets/digit_4.png differ diff --git a/tools/game/py-pong/assets/digit_5.png b/tools/game/py-pong/assets/digit_5.png new file mode 100644 index 0000000..24eaa60 Binary files /dev/null and b/tools/game/py-pong/assets/digit_5.png differ diff --git a/tools/game/py-pong/assets/digit_6.png b/tools/game/py-pong/assets/digit_6.png new file mode 100644 index 0000000..215e7f9 Binary files /dev/null and b/tools/game/py-pong/assets/digit_6.png differ diff --git a/tools/game/py-pong/assets/digit_7.png b/tools/game/py-pong/assets/digit_7.png new file mode 100644 index 0000000..36d679b Binary files /dev/null and b/tools/game/py-pong/assets/digit_7.png differ diff --git a/tools/game/py-pong/assets/digit_8.png b/tools/game/py-pong/assets/digit_8.png new file mode 100644 index 0000000..8f5246f Binary files /dev/null and b/tools/game/py-pong/assets/digit_8.png differ diff --git a/tools/game/py-pong/assets/digit_9.png b/tools/game/py-pong/assets/digit_9.png new file mode 100644 index 0000000..c80204b Binary files /dev/null and b/tools/game/py-pong/assets/digit_9.png differ diff --git a/tools/game/py-pong/assets/dividing-line.png b/tools/game/py-pong/assets/dividing-line.png new file mode 100644 index 0000000..105682b Binary files /dev/null and b/tools/game/py-pong/assets/dividing-line.png differ diff --git a/tools/game/py-pong/assets/logo.png b/tools/game/py-pong/assets/logo.png new file mode 100644 index 0000000..9c1742d Binary files /dev/null and b/tools/game/py-pong/assets/logo.png differ diff --git a/tools/game/py-pong/assets/missed-ball.wav b/tools/game/py-pong/assets/missed-ball.wav new file mode 100644 index 0000000..b012be2 Binary files /dev/null and b/tools/game/py-pong/assets/missed-ball.wav differ diff --git a/tools/game/py-pong/assets/paddle.png b/tools/game/py-pong/assets/paddle.png new file mode 100644 index 0000000..7204eec Binary files /dev/null and b/tools/game/py-pong/assets/paddle.png differ diff --git a/tools/game/py-pong/main.py b/tools/game/py-pong/main.py new file mode 100644 index 0000000..fc0d78d --- /dev/null +++ b/tools/game/py-pong/main.py @@ -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() diff --git a/tools/game/py-pong/pypong/__init__.py b/tools/game/py-pong/pypong/__init__.py new file mode 100644 index 0000000..856f0e5 --- /dev/null +++ b/tools/game/py-pong/pypong/__init__.py @@ -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) diff --git a/tools/game/py-pong/pypong/entity.py b/tools/game/py-pong/pypong/entity.py new file mode 100644 index 0000000..170be57 --- /dev/null +++ b/tools/game/py-pong/pypong/entity.py @@ -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) diff --git a/tools/game/py-pong/pypong/player.py b/tools/game/py-pong/pypong/player.py new file mode 100644 index 0000000..076bd0d --- /dev/null +++ b/tools/game/py-pong/pypong/player.py @@ -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