# -*- coding: utf-8 -*-

# Copyright (c) 2007 Stas Zykiewicz <stas.zytkiewicz@gmail.com>
#
#           pong.py
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 3 of the GNU General Public License
# as published by the Free Software Foundation.  A copy of this license should
# be included in the file GPL-3.
#
# 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.u_up,self.u_down = unicode(Misc.bat_l.up), unicode(Misc.bat_l.down)
        self.batstarty = Misc.bat_l.rect[1]
        self.balldirection = Misc.ball.xoffset
        self.event_dict = {'key':self.up,'unicode':self.u_up}
        self.keystate = KEYUP
        self.counter = 15# about 0.5 second (clockrate chidsplay 30/sec)
        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.counter > 0: 
            self.counter -= 1
            return events
        else:
            self.counter = 15
        if self.keystate is 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  
        elif self.keystate is KEYUP:
            self.keystate = KEYDOWN
        if self.keystate is KEYUP and 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
            self.event_dict['unicode'] = self.u_down
        else:
            self.event_dict['key'] = self.up
            self.event_dict['unicode'] = self.u_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,info:
                    print info
                    return
                else:
                    if c == self.up:
                        self.speed = self.init_speed
                        self.speed = -self.speed
                        self.current_key = c
                    elif c == self.down:
                        self.speed = self.init_speed
                        self.speed = abs(self.speed)
                        self.current_key = c
            elif event.type == KEYUP and self.current_key:
                self.speed = 0
                self.current_key = None
        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,cpg):
        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.cpg = cpg
        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()
        fs = 36
        Misc.scoreboard = ScoreBoard(size=fs)
        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.PCplayer = None
        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 = unicode(Misc.rc_dic['right_keyup'])
            d = unicode(Misc.rc_dic['right_keydown'])
        except (TypeError,KeyError):
            u,d = u'p',u'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 = unicode(Misc.rc_dic['left_keyup'])
            d = unicode(Misc.rc_dic['left_keydown'])
        except (TypeError,KeyError):
            u,d = u'q',u'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],26)
        #print txtlist
        s1,spam = font2surf(txtlist[0],36,GREEN)
        s2,spam = font2surf(txtlist[1],36,GREEN)
        Img.screen.blit(s1,(100,200))
        Img.screen.blit(s2,(100,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 choices 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,48,GREEN)
        pygame.display.update(Img.screen.blit(s,(80,4)))
        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
