#~ /*************************************************************************** #~ * pong.py #~ * #~ * Thu Jun 17 12:37:16 2004 #~ * Copyright 2004 Stas #~ * stas@linux.isbeter.nl #~ ****************************************************************************/ # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ## Classic pong game, suitable for children. RCFILE = 1 _PDEBUG = 0# set to 2 to enable the printing of events ## set ONEPLAYER to 1 for single player pong ## set to 0 for two players, one human and one computer ## this works by moving the left goal and bat behind the wall. ## So everything works the same as in multiplayer but it looks like single player ONEPLAYER = 1# how may persons, 1 = 1, 0 = 2 or 1 + computer PCPLAYER = 0# used only in combination with ONEPLAYER = 0 GREEN = (41,241,14)# color used for everything import os,sys,random import pygame from pygame.constants import * from utils import trace_error,MyError,load_image,load_sound,load_music,char2surf,\ font2surf,txtfmt from SpriteUtils import CPSprite,CPGroup,CPinit from CPMenu import MenuItem import Timer class Img: """ Container to store image objects""" pass class Snd: """ Container to store sound objects""" pass class Misc: """ Container to store all kind of stuff""" pass # Options used by the game. # These will be merged with the ones from the rcdic. # When there's a problem with the rcdic we use these as defaults. defaults_dict = {'__name__':'default',\ 'sound':'yes',\ 'computerAI':'easy',\ 'gameplay':'single',\ 'batsize':'72',\ 'batspeed':'12',\ 'ballspeed':'3',\ 'goalsize':'300',\ 'winpoints':'11',\ 'useprofile':'none',\ 'left_keyup':'q',\ 'left_keydown':'a',\ 'right_keyup':'p',\ 'right_keydown':'l'} class Button(MenuItem): def __init__(self,img,pos,data): MenuItem.__init__(self,img,pos,data) class Winner: def __init__(self,side): img = self.load_stuff() if side == 'left': pos = (200,150) else: pos = (500,150) Snd.winner.play() pygame.display.update(Img.screen.blit(img,pos)) def load_stuff(self): return Img.winner class Loser(Winner): def __init__(self,side): Winner.__init__(self,side) def load_stuff(self): return Img.loser class PcPlayer: """The computer player. This will control the bat by adding pygame events to the pygame event queue. """ def __init__(self): """bat must be a Bat class object.""" self.up,self.down = ord(Misc.bat_l.up),ord(Misc.bat_l.down) self.batstarty = Misc.bat_l.rect[1] self.balldirection = Misc.ball.xoffset self.event_dict = {'key':self.up} self.keystate = KEYUP self.AIlist = (0,)*(int(Misc.rc_dic['ballspeed'])-1) + (1,) Misc.bat_l.init_speed = 6 if Misc.rc_dic['computerAI'] == 'hard': self.AIlist = (0,0,1) elif Misc.rc_dic['computerAI'] == 'impossible': self.AIlist = (0,0) def play(self,events): if self.keystate == KEYDOWN: self.keystate = KEYUP events.append(pygame.event.Event(self.keystate,self.event_dict)) return events #the ball is moving away from us and we're not back in the middle elif Misc.ball.xoffset > 0 and \ Misc.bat_l.rect[1] == self.batstarty: return events else: self.keystate = KEYDOWN if random.choice(self.AIlist): return events bally = Misc.ball.rect[1] baty = Misc.bat_l.rect.inflate(0,-4)[1] #print bally,baty if bally > baty: self.event_dict['key'] = self.down else: self.event_dict['key'] = self.up event = pygame.event.Event(self.keystate,self.event_dict) events.append(event) return events class ScoreBoard: def __init__(self,end=11,size=48): try: s = int(Misc.rc_dic['winpoints']) except (TypeError,KeyError): pass else: end = s self.pos_l,self.pos_r = (340,20),(410,20) self.size = size self.end = end#at which score the game ends self.score = (0,0) self.black = pygame.Surface((48,48)) def set_score(self,score,side): if side == 'right': self.score = (self.score[0],self.score[1]+score) else: self.score = (self.score[0]+score,self.score[1]) self.update() def update(self): rects = [] sr = self.black.convert() sr.blit(char2surf(str(self.score[1]),self.size,GREEN),(0,0)) sl = self.black.convert() sl.blit(char2surf(str(self.score[0]),self.size,GREEN),(0,0)) rects.append(Img.screen.blit(sl,(self.pos_l))) rects.append(Img.screen.blit(sr,(self.pos_r))) rects.append(Img.backgr.blit(sl,(self.pos_l))) rects.append(Img.backgr.blit(sr,(self.pos_r))) pygame.display.update(rects) if self.end in self.score: # hack to erase the sprite. Because the position of the sprite is changed # after it's drawn on the screen. So we must erase it's 'old' position. Misc.ball.rect = Misc.ball.oldrect Misc.ball.erase_sprite() pygame.time.wait(3000) if self.score[0] == self.end: Winner('left') Loser('right') else: Winner('right') Loser('left') pygame.time.wait(9500) # End of this game self.score = (0,0) self.update() class Goal: def __init__(self,size=(20,300)): try: s = int(Misc.rc_dic['goalsize']) except (TypeError,KeyError): pass else: size = (20,s) self.image = pygame.Surface(size).convert() self.score = 0 #self.image.fill((255,255,255))# use to display goals (debugging) self.rect = self.image.get_rect() def set_scoreboard(self,side): self.side = side#this should be the other side def move(self,pos): self.rect.move_ip(pos) def scored(self): Snd.goal.play() self.score += 1 Misc.scoreboard.set_score(1,self.side) def get_score(self): return self.score class Ball(CPSprite): def __init__(self,speed=4): CPSprite.__init__(self) try: s = int(Misc.rc_dic['ballspeed']) except (TypeError,KeyError): pass else: speed = s self.image = pygame.Surface((24,24)).convert() pygame.draw.circle(self.image,\ GREEN,\ (12,12),\ 12,\ 0) self.image.set_colorkey(self.image.get_at((0, 0)), RLEACCEL) self.rect = self.image.get_rect() self.xpos,self.ypos = 390,250 self.rect.move_ip(self.xpos,self.ypos) self.xoffset = random.choice((-speed,speed)) self.yoffset = random.choice((-speed,speed)) self.move() def move(self): self.oldrect = self.rect[:4] if self.rect.centerx > 764: Snd.bump.play() self.xoffset = -self.xoffset if self.rect.centerx < 37: Snd.bump.play() self.xoffset = abs(self.xoffset) if self.rect.centery > 464: Snd.bump.play() self.yoffset = -self.yoffset if self.rect.centery < 36: Snd.bump.play() self.yoffset = abs(self.yoffset) self.rect.centerx += self.xoffset self.rect.centery += self.yoffset def update(self,*args): self.move() for obj in (Misc.goal_l,Misc.goal_r): if self.rect.colliderect(obj.rect): obj.scored() return -1 collide = self.rect.colliderect(Misc.bat_r.rect) or \ self.rect.colliderect(Misc.bat_l.rect) if collide: self.xoffset = -self.xoffset Snd.pong.play() class Bat(CPSprite): def __init__(self,action_keys,size=64,speed=12): CPSprite.__init__(self) self.current_key = None#used to fix the "no unicode key in keyup event" pygame bug try: s = int(Misc.rc_dic['batspeed']) bs = int(Misc.rc_dic['batsize']) except (TypeError,KeyError): pass else: size = bs speed = s self.init_speed = speed self.speed = 0 self.size = size self.top,self.bottom = 18,482 self.up,self.down = action_keys[0].upper(),action_keys[1].upper() self.image = pygame.Surface((8,size)) self.image.fill(GREEN) self.rect = self.image.get_rect() def set_position(self,pos): self.rect.move_ip(pos[0],pos[1]) def update(self,*args): """This keeps moving until a keyup event is raised""" #c = None for event in Misc.events: if _PDEBUG: print __name__,event if event.type == KEYDOWN: try: c = event.unicode.upper() except ValueError: pass else: if c == self.up: self.speed = self.init_speed self.speed = -self.speed elif c == self.down: self.speed = self.init_speed self.speed = abs(self.speed) self.current_key = c else: try: c = event.unicode.upper() except Exception,info: if _PDEBUG: print >> sys.stderr,info # this is because the keydown event doesn't define a unicode member c = self.current_key if c == self.up or c == self.down: self.speed = 0 else: if c == self.up or c == self.down: self.speed = 0 newrect = self.rect.move(0,self.speed) if newrect.top > self.top and newrect.bottom < self.bottom: self.rect = newrect class Game(Img,Snd): """ Pong - part of childsplay.py, a suite of educational games for young children. """ def __init__(self,screen,backgr,rc_dic,basepath,libdir): Img.screen = screen Img.backgr = backgr #if _PDEBUG: print "rc_dic",rc_dic # check if the user has set a profile to use in the config file. try: d = defaults_dict.copy()# don't mess up the original n = rc_dic.keys()[0]#get a dict, any dict, we only want to know the #'useprofile' entry which is part of every dict. # But we don't know which names the user use. profile = rc_dic[n]['useprofile'] if profile != 'none': d.update(rc_dic[profile]) Misc.rc_dic = d except Exception,info:# on any error we gonna use the hardcoded version print "Error in config file parsing\n",info trace_error() print "Using hardcoded defaults" Misc.rc_dic = defaults_dict if _PDEBUG: print "config dict",Misc.rc_dic self.basedir = basepath self.libdir = libdir self.datadir = os.path.join(self.libdir,'PongData') self.gamelevels =[((130,70),(130,220),(130,370),),(None)]#positions of the game choices objects self.gameitems = [('single.jpg','multi_person.jpg','multi_pc.jpg')]#image files Misc.actives = CPinit(Img.screen,Img.backgr) self._setup() def _setup(self): """ Set all the stuff we need""" Img.screen.fill((0,0,0)) pygame.display.update() Misc.scoreboard = ScoreBoard() Img.winner = load_image(os.path.join(self.datadir,'winner.jpg'),1) Img.loser = load_image(os.path.join(self.datadir,'loser.jpg'),1) self.skipstart = None# used when the user sets a predefined game play in the config file self.skipssplash = None if Misc.rc_dic['sound'].lower() == 'no': Snd.pong = load_sound(os.path.join(self.datadir,'')) Snd.winner = load_sound(os.path.join(self.datadir,'')) Snd.goal = load_sound(os.path.join(self.datadir,'')) Snd.bump = load_sound(os.path.join(self.datadir,'')) else: Snd.pong = load_sound(os.path.join(self.datadir,'pick.wav')) Snd.winner = load_music(os.path.join(self.datadir,'winner.ogg')) Snd.goal = load_sound(os.path.join(self.datadir,'goal.wav')) Snd.bump = load_sound(os.path.join(self.datadir,'bump.wav')) #set kind of game play # we only check for multi and multipc, anything else is considerd single play # which is the default if Misc.rc_dic['gameplay'] == 'multi': self.restart([[None,'2']]) self.skipstart = 1 elif Misc.rc_dic['gameplay'] == 'multipc': self.restart([[None,'3']]) self.skipstart = 1 def __str__(self): """Must return the original, not translated, title of this game. It's needed by the high score class of childsplay.""" return "Pong" def start(self,level,items): """Try to hit the ball back and defend your goal.""" # When we start the first time (first level) is a menu with three choices. # After this "level" the second level is the kind of game the user has chooses # in the first "level" if level and not self.skipstart:#only the first level is true, the next is none self._get_choice(level,items) return if _PDEBUG: print 'ONEPLAYER,PCPLAYER',ONEPLAYER,PCPLAYER if not self.skipssplash: self._splash_controls() self._draw_field() Misc.scoreboard.update() Misc.goal_r = Goal() Misc.goal_r.set_scoreboard('left') Misc.goal_r.move((774,100)) Misc.goal_l = Goal() Misc.goal_l.set_scoreboard('right') if ONEPLAYER: Misc.goal_l.move((-20,100))# move behind the wall else: Misc.goal_l.move((6,100)) Img.screen.blit(Misc.goal_r.image,Misc.goal_r.rect) Img.screen.blit(Misc.goal_l.image,Misc.goal_l.rect) pygame.display.update() lspeed,rspeed = 8,8 try: u = Misc.rc_dic['right_keyup'] d = Misc.rc_dic['right_keydown'] except (TypeError,KeyError): u,d = 'p','l' Misc.bat_r = Bat(action_keys=(u,d),speed=rspeed)# to keep a reference to the bat, see class Ball Misc.bat_r.set_position((768,220)) try: u = Misc.rc_dic['left_keyup'] d = Misc.rc_dic['left_keydown'] except (TypeError,KeyError): u,d = 'q','a' Misc.bat_l = Bat(action_keys=(u,d),speed=lspeed) if ONEPLAYER: Misc.bat_l.set_position((-20,220)) else: Misc.bat_l.set_position((24,220)) Misc.ball = Ball()# now the computer player can check the ball Misc.actives.add(Misc.ball) if PCPLAYER and not ONEPLAYER:#we play against the pc self.PCplayer = PcPlayer() Misc.actives.add([Misc.bat_r,Misc.bat_l]) r = Misc.actives.draw(Img.screen) pygame.display.update(r) pygame.time.wait(2000) def _draw_field(self): Img.screen.fill((0,0,0)) Img.backgr.fill((0,0,0)) box = pygame.Rect(0,0,800,500).inflate(-32,-32) #box.inflate(-32,-32) pygame.draw.rect(Img.screen,\ GREEN,\ box,\ 4) pygame.draw.line(Img.screen,\ GREEN,\ (398,16),\ (398,484),\ 4) pygame.draw.line(Img.backgr,\ GREEN,\ (398,16),\ (398,484),\ 4) def _splash_controls(self): Img.screen.fill((0,0,0)) txt = _("Use these keys on your keyboard to control the bat.") txtlist = txtfmt([txt],32) #print txtlist s1,spam = font2surf(txtlist[0],48,GREEN) s2,spam = font2surf(txtlist[1],48,GREEN) Img.screen.blit(s1,(120,200)) Img.screen.blit(s2,(120,240)) pygame.display.update() rects = [] fsize = 48 surf = pygame.Surface((60,300)) surf.blit(load_image(os.path.join(self.datadir,'arrow_up.png')),(4,0)) #surf.blit(load_image(os.path.join(self.datadir,'bat.png')),(26,120)) surf.blit(load_image(os.path.join(self.datadir,'arrow_down.png')),(4,240)) surf_r = surf.convert()# copy for the right side surf.blit(char2surf(Misc.rc_dic['left_keyup'].upper(),fsize,GREEN),(16,70)) surf.blit(char2surf(Misc.rc_dic['left_keydown'].upper(),fsize,GREEN),(16,190)) surf_r.blit(char2surf(Misc.rc_dic['right_keyup'].upper(),fsize,GREEN),(16,70)) surf_r.blit(char2surf(Misc.rc_dic['right_keydown'].upper(),fsize,GREEN),(16,190)) rects.append(Img.screen.blit(surf,(40,100))) rects.append(Img.screen.blit(surf_r,(700,100))) pygame.display.update(rects) pygame.time.wait(4000) self.skipssplash = 1 def _get_choice(self,positions,images): """Display the three chooses of game play. This will put the menu items in the actives group which then will be updated in the game loop. When the users picks one the result will be handled by the restart method.""" head = _("Please, choose the game to play:") s = char2surf(head,54,GREEN) pygame.display.update(Img.screen.blit(s,(80,10))) for pos,img,data in map(None,positions,images,('1','2','3')): img = os.path.join(self.datadir,img) obj = Button(load_image(img),pos,data) Misc.actives.add(obj) def restart(self,v): """Used to restart the game when there's a goal. This is also used to set the global vars when ending the first level(menu choices)""" global ONEPLAYER,PCPLAYER if _PDEBUG: print "restart v = ",v obj,val = v[0][0],v[0][1] if val == -1:#scored Misc.actives.empty() self.start(None,None) # Handle the vals from the first level else: if val == '2':# 1 is the default so we don't test it ONEPLAYER = 0 elif val == '3': ONEPLAYER = 0 PCPLAYER = 1 Misc.actives.empty() self.stop = -1 def helptitle(self): return _("Pong") def help(self): text = [_("The aim of the game:"), _("The classic pong game where you must hit a ball with your bat."), _("There are three levels to choose from:"),\ _("Single play - Hit the ball against the wall."),\ _("Multi player against the computer - Try to defeat the computer."),\ _("Multi player - Play against another player."),\ _("In the multiplayer modes, the one who has 11 points wins."),\ " ",\ _("The game has a configuration file called 'pongrc', located in the .childsplay directory of your homedirectory."),\ _("In this file you can set a number of options to change the game play."),\ " ",\ _("Difficulty : 5-8 years")] return text def loop(self,events): item,self.stop,Misc.score = None, 0,0 if PCPLAYER: events = self.PCplayer.play(events)# let the computer make his move. #This will add an event to pygame events. Just like a real player :-) if _PDEBUG == 2 and events: print "events",events Misc.events = []#This is a hack to be able to use keydown AND keyup with #CPSprite stuff for event in events: if event.type == KEYDOWN or \ event.type == KEYUP or\ event.type == MOUSEBUTTONDOWN: item = event Misc.events.append(event) v = Misc.actives.refresh(item)# This will return -1 when there's a score # The menu objects from the first level will return strings ('1','2' or '3') if v: self.restart(v) #pygame.time.wait(1000) return self.stop,Misc.score