[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[pygame] pong/tele-tennis with sound!
A two-player game small enough to squeeze onto a mailing list!
With sound effects!
My brain didn't spot the sprite collision-detection stuff
though ... Oh, well ...
cheers,
John.
"""
A clone of the game of pong or tele-tennis.
Requires Python and PyGame:
http://www.python.org/ and http://pygame.seul.org/
Try 'python pypong.py --help' for instructions.
This code is in the public domain.
[Tested on Win98 / Win2K / GNU/Linux (SuSE)]
Version: 0.0.1
Author : John Popplewell
Email : john@johnnypops.demon.co.uk
Web : http://www.johnnypops.demon.co.uk/
"""
import os, sys, getopt, math, random
try:
import Numeric as N
except:
print "This game requires the Numeric module."
sys.exit()
try:
import pygame
except:
print "This game requires the PyGame module."
sys.exit()
from pygame.locals import *
file_name = "pypong"
demo_name = "pyPong"
if not pygame.font:
print 'Warning, fonts disabled'
if not pygame.mixer:
print 'Warning, sound disabled'
LEFT_PLAYER_UP = K_q
LEFT_PLAYER_DOWN = K_a
LEFT_PLAYER_SERVE = K_s
RIGHT_PLAYER_UP = K_p
RIGHT_PLAYER_DOWN = K_l
RIGHT_PLAYER_SERVE = K_k
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
BAT_WIDTH = 12
BAT_HEIGHT = 80
BAT_INDENT = 10
BAT_VELOCITY = 256.0
BAT_ACCELERATION = 300.0
BAT_SPIN_FACTOR = -0.333
BAT_CURVATURE = -8.0
BORDER_THICKNESS = 2
BALL_SIZE = 12
BALL_VELOCITY = 360.0
MAX_BALL_VELOCITY= 460.0
BALL_WOBBLE = BALL_VELOCITY/10.0
SCORE_INDENT = 10
SCORE_POINT_SIZE = 36
TICK_PER_SECOND = 40
SND_STEREO = 0
SND_BITS_PER_SAMPLE = 16
SND_SAMPLES_PER_SEC = 22050
game_font = None
all_sprites = None
visible_sprites = None
def make_tone(frequency, duration, samples_per_sec, bits_per_sample):
samples_per_cycle = int(math.ceil(samples_per_sec/frequency))
total_samples = samples_per_cycle*int(frequency*duration)
samples = N.zeros(total_samples, N.Int16)
amplitude = ((2**bits_per_sample)/2)-1
k = 2.0*math.pi/samples_per_cycle
for i in range(total_samples):
samples[i] = int(amplitude*math.sin(k*(i%samples_per_cycle)))
res = pygame.sndarray.make_sound(samples)
res.set_volume(1.0)
return res
def sign(i):
if i < 0: return -1
if i > 0: return 1
return 0
class ScoreBoard(pygame.sprite.Sprite):
LEFT, CENTER, RIGHT = range(3)
def __init__(self, x, y, align, colour ):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.align = align
self.colour = colour
self.score = 0
self.update_score(0)
visible_sprites.add(self)
def update_score(self, delta):
self.score += delta
if not game_font:
return
self.image = game_font.render("%d"%self.score, 1, self.colour)
self.rect = self.image.get_rect()
self.rect.top = self.y
if self.align == ScoreBoard.LEFT:
self.rect.left = self.x
elif self.align == ScoreBoard.CENTER:
self.rect.centerx = self.x
elif self.align == ScoreBoard.RIGHT:
self.rect.right = self.x
class Bat(pygame.sprite.Sprite):
def __init__(self, x, height_range, colour):
pygame.sprite.Sprite.__init__(self)
self.height_range = height_range
self.rect = pygame.Rect((0,0,BAT_WIDTH,BAT_HEIGHT))
self.image = pygame.Surface((BAT_WIDTH,BAT_HEIGHT))
self.image.fill(colour)
self.p_x = float(x)
self.p_y = float((self.height_range-self.rect.height)/2)
self.dir = 0
self.v_y = 0.0
self.rect.move_ip(round(self.p_x),round(self.p_y))
def update(self,dT):
self.v_y += sign(self.v_y)*BAT_ACCELERATION*dT
self.p_y += self.v_y*dT
if self.p_y <= 0.0:
self.p_y = 0.0
self.v_y = 0.0
if self.p_y+BAT_HEIGHT >= SCREEN_HEIGHT:
self.p_y = SCREEN_HEIGHT-BAT_HEIGHT
self.v_y = 0.0
self.rect.top = round(self.p_y)
def move(self,dir):
self.dir = dir
self.v_y = self.dir*BAT_VELOCITY
def stop(self,dir):
if dir == self.dir:
self.move(0)
class Player:
properties = (
(BAT_INDENT , 0.25),
(SCREEN_WIDTH-BAT_INDENT-BAT_WIDTH, 0.75),
)
controls = (
(LEFT_PLAYER_UP, LEFT_PLAYER_DOWN),
(RIGHT_PLAYER_UP, RIGHT_PLAYER_DOWN),
)
def __init__(self, player_idx, colour, miss_sfx):
self.props = Player.properties[player_idx]
self.controls = Player.controls[player_idx]
self.bat = Bat(self.props[0], SCREEN_HEIGHT, colour)
self.score = ScoreBoard(self.props[1]*SCREEN_WIDTH, SCORE_INDENT, ScoreBoard.RIGHT, colour)
if pygame.mixer:
self.chan = pygame.mixer.Channel(1)
else: self.chan = None
self.miss_sfx = miss_sfx
def inc_score(self):
self.score.update_score(1)
if self.chan: self.chan.play(self.miss_sfx, 0, 200)
def key_down(self, key):
if key == self.controls[0]:
self.bat.move(-1)
return 1
if key == self.controls[1]:
self.bat.move( 1)
return 1
return 0
def key_up(self, key):
if key == self.controls[0]:
self.bat.stop(-1)
return 1
if key == self.controls[1]:
self.bat.stop( 1)
return 1
return 0
class Ball(pygame.sprite.Sprite):
READY, PLAYING = range(2)
def __init__(self, x, y, colour, bat_sfx, wall_sfx, players, out_of_play):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect((0,0,BALL_SIZE,BALL_SIZE))
self.image = pygame.Surface((BALL_SIZE,BALL_SIZE))
self.image.fill(colour)
self.visible = 0
self.gen = random.Random()
self.p_x = float(x)
self.p_y = float(y)
self.v_x = BALL_VELOCITY
self.v_y = self.random_velocity(BALL_VELOCITY)
self.bat_sfx = bat_sfx
self.wall_sfx = wall_sfx
if pygame.mixer:
self.chan = pygame.mixer.Channel(0)
else: self.chan = None
self.players = players
self.out_of_play = out_of_play
self.status = Ball.READY
self.set_rect()
def set_rect(self):
self.rect.left = round(self.p_x)
self.rect.top = round(self.p_y)
def get_bat(self, idx):
return self.players[idx].bat
def update(self,dT):
if self.status == Ball.READY:
return
self.p_x += self.v_x*dT
self.p_y += self.v_y*dT
if self.p_y <= 0.0:
self.p_y = 0.0
self.v_y = -self.v_y
if self.visible:
if self.chan: self.chan.play(self.wall_sfx, 0, 150)
elif self.p_y+BALL_SIZE >= SCREEN_HEIGHT:
self.p_y = SCREEN_HEIGHT-BALL_SIZE
self.v_y = -self.v_y
if self.visible:
if self.chan: self.chan.play(self.wall_sfx, 0, 150)
if self.p_x <= 0.0:
self.show(0)
self.status = Ball.READY
self.out_of_play(1)
elif self.p_x+BALL_SIZE >= SCREEN_WIDTH:
self.show(0)
self.status = Ball.READY
self.out_of_play(0)
elif self.collide_player(0):
pass
elif self.collide_player(1):
pass
self.set_rect()
def collide_player(self, idx):
if self.approaching_bat(idx) and self.rect.colliderect(self.get_bat(idx).rect):
self.align_ball_on_bat(idx)
self.v_x = -self.v_x
self.v_y += self.get_bat(idx).v_y*BAT_SPIN_FACTOR+self.random_velocity(BALL_WOBBLE)
self.v_y += (self.get_bat(idx).rect.centery-self.rect.centery)*BAT_CURVATURE
self.clamp_velocity()
if self.visible:
if self.chan: self.chan.play(self.bat_sfx, 0, 120)
return 1
return 0
def approaching_bat(self, idx):
return ((self.v_x < 0), (self.v_x > 0))[idx]
def align_ball_on_bat(self, idx):
self.p_x = (self.get_bat(idx).rect.right, self.get_bat(idx).rect.left-BALL_SIZE)[idx]
def random_velocity(self, amplitude):
return 2.0*amplitude*self.gen.random()-amplitude
def clamp_velocity(self):
self.v_y = max(min(MAX_BALL_VELOCITY,self.v_y),-MAX_BALL_VELOCITY)
def serve(self, idx):
if self.status != Ball.READY:
return
self.p_x = (0.0, SCREEN_WIDTH-BALL_SIZE)[idx]
self.p_y = (self.get_bat(idx).rect.centery)-BALL_SIZE/2
self.v_x = (1.0,-1.0)[idx]*BALL_VELOCITY
self.v_y = self.random_velocity(BALL_VELOCITY)
self.status = Ball.PLAYING
self.set_rect()
self.show(1)
def show(self, visible):
if self.visible == visible:
return
self.visible = visible
if visible:
visible_sprites.add(self)
else:
visible_sprites.remove(self)
class Game:
def __init__(self,left_colour,right_colour,ball_colour):
if pygame.mixer:
miss_sound = make_tone(131.25, 1.0, SND_SAMPLES_PER_SEC, SND_BITS_PER_SAMPLE)
wall_sound = make_tone(262.5, 1.0, SND_SAMPLES_PER_SEC, SND_BITS_PER_SAMPLE)
bat_sound = make_tone(525.0, 1.0, SND_SAMPLES_PER_SEC, SND_BITS_PER_SAMPLE)
else:
miss_sound = None
wall_sound = None
bat_sound = None
self.players = ( Player(0, left_colour, miss_sound), Player(1, right_colour, miss_sound) )
self.ball = Ball(0, 0, ball_colour, bat_sound, wall_sound, self.players, self.out_of_play)
all_sprites.add((self.players[0].bat, self.players[1].bat, self.ball))
visible_sprites.add((self.players[0].bat, self.players[1].bat))
self.service = -1
def out_of_play(self, player_idx):
self.service = player_idx
self.players[player_idx].inc_score()
def serve(self, player_idx):
if self.service == -1:
self.service = player_idx
if self.service != player_idx:
return
self.ball.serve(player_idx)
def handle_event(self, event):
if event.type is KEYDOWN:
if event.key is K_ESCAPE:
return
if self.players[0].key_down(event.key):
pass
elif self.players[1].key_down(event.key):
pass
elif event.key == LEFT_PLAYER_SERVE:
self.serve(0)
elif event.key == RIGHT_PLAYER_SERVE:
self.serve(1)
elif event.type is KEYUP:
if self.players[0].key_up(event.key):
pass
elif self.players[1].key_up(event.key):
pass
def Usage():
print """python %s.py
[-f|--fullscreen] fullscreen display (640x480)
[-c|--colour|--color] colour paddles and ball
[-q|--quiet] no sound
[-h|--help] this text
Left player controls: Right player controls:
Q - UP P - UP
A - DOWN L - DOWN
S - SERVE K - SERVE
ESC - Quit
A clone of the game of pong or tele-tennis.
The players decide who serves first. Service goes to the winner
of a rally. Players should agree to end the match on a first to
score N basis e.g. first player to score 5 points is the winner.
Have Fun!
This code is in the public domain.
Version: 0.0.1
Author : John Popplewell
Email : john@johnnypops.demon.co.uk
Web : http://www.johnnypops.demon.co.uk/
"""%(file_name,)
def main():
fullscreen = 0
monochrome = 1
try:
opts, args = getopt.getopt(sys.argv[1:], "fcqh", ["fullscreen","colour","color","quiet","help"])
for o, a in opts:
if o in ("-f", "--fullscreen"):
fullscreen = 1
if o in ("-c", "--colour", "--color"):
monochrome = 0
if o in ("-q", "--quiet"):
pygame.mixer = None
if o in ("-h", "--help"):
Usage()
sys.exit()
except getopt.GetoptError:
Usage()
sys.exit()
if pygame.mixer:
pygame.mixer.pre_init(SND_SAMPLES_PER_SEC, -SND_BITS_PER_SAMPLE, SND_STEREO)
pygame.init()
pygame.mouse.set_visible(0)
if fullscreen:
#vidflags = HWSURFACE|DOUBLEBUF|FULLSCREEN
vidflags = FULLSCREEN
else:
vidflags = 0
if monochrome:
BACKGROUND_COLOUR= (0,0,0)
BORDER_COLOUR = (255,255,255)
BALL_COLOUR = (255,255,255)
LEFT_BAT_COLOUR = (255,255,255)
RIGHT_BAT_COLOUR = (255,255,255)
else:
BACKGROUND_COLOUR= (0,0,0)
BORDER_COLOUR = (0,0,255)
BALL_COLOUR = (0,255,255)
LEFT_BAT_COLOUR = (255,0,0)
RIGHT_BAT_COLOUR = (0,255,0)
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), vidflags )
pygame.mouse.set_visible(0)
pygame.display.set_caption(demo_name)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill(BACKGROUND_COLOUR)
if fullscreen:
rc = (0, 0, SCREEN_WIDTH, BORDER_THICKNESS)
pygame.draw.rect(background, BORDER_COLOUR, rc)
rc = (0, SCREEN_HEIGHT-BORDER_THICKNESS, SCREEN_WIDTH, BORDER_THICKNESS)
pygame.draw.rect(background, BORDER_COLOUR, rc)
screen.blit(background, (0, 0))
pygame.display.flip()
global game_font, all_sprites, visible_sprites
if pygame.font:
game_font = pygame.font.Font(None, SCORE_POINT_SIZE)
all_sprites = pygame.sprite.Group()
visible_sprites = pygame.sprite.RenderUpdates()
the_game = Game(LEFT_BAT_COLOUR, RIGHT_BAT_COLOUR, BALL_COLOUR)
clock = pygame.time.Clock()
clock.tick(TICK_PER_SECOND)
quit = 0
while not quit:
for event in pygame.event.get():
if event.type is QUIT:
quit = 1
if event.type is KEYDOWN and event.key is K_ESCAPE:
quit = 1
the_game.handle_event(event)
visible_sprites.clear(screen, background)
all_sprites.update(clock.tick(TICK_PER_SECOND)/1000.0)
dirty = visible_sprites.draw(screen)
pygame.display.update(dirty)
pygame.quit()
if __name__ == '__main__':
main()
# vim: et noai tw=0 ts=4