pygame生死存亡_ 双人算术小游戏

"""pygame生死存亡,两只老鼠掉进熊熊燃烧的大火炉,只有用键盘操作它们碰到正确的算术式子才能挽救它们性命。   
   这是一个双人小游戏,按上下左右,或wasd操作两只老鼠去碰撞正确的算术式子即可。   
   结束逻辑一:设定游戏时间 game_time 为 100秒,超过则游戏结束,通过判断得分高低判定输赢。
   结束逻辑二:不小心被烧死了,则没烧死的为赢,和得分无关。
   通过一个事件,每隔一秒进行一次判断。

"""
__author__ = "李兴球"
__date__ = "2018年12月左右"
import os
import time
import pygame
from pygame.locals import *
from random import randint,choice

class Flame(pygame.sprite.Sprite):
    """火焰类"""
    def __init__(self,images,rat,screen):
        pygame.sprite.Sprite.__init__(self)
        self.images = images                 # 这是已经用pygame.image.load转换成surface后的列表
        self.index = 0                       # 造型的初始索引号
        self.amounts = len(images)           # 造型数量
        self.image = self.images[self.index] # 初始的造型
        self.delay_time = 0.1                # 造型切换延时
        self.begin_time = time.time()
        self.rect = self.image.get_rect()
        self.rect.center = rat.rect.center   # 中心点坐标
        self.rat = rat                       # 它属于某鼠上面火焰
        self.screen = screen
        
    def update(self):
        self.rect.centerx = self.rat.rect.centerx +5         # 中心点x坐标
        self.rect.centery = self.rat.rect.centery-18         # 中心点y坐标
        if time.time() - self.begin_time >= self.delay_time: # 超过延时的时间则换下一个造型
           self.index = self.index + 1
           self.index = self.index % self.amounts
           self.image = self.images[self.index]
           self.begin_time = time.time()                     # 重新设定起始时间           
    
    def draw(self):
        self.screen.blit(self.image,self.rect)

class DynamicBackground(pygame.sprite.Sprite):
    """动态背景类,它是一个角色,画在最下面的,本质就是不断地切换帧图"""
    def __init__(self,images,screen):
        pygame.sprite.Sprite.__init__(self)
        # 帧图列表
        self.images = [pygame.image.load(image).convert_alpha() for image in images]
        self.index = 0
        self.amounts = len(images)
        self.screen = screen
        self.delay_time =  0.1           # 背景切换延时 秒
        self.begin_time = time.time()
        self.y = 600                     # 初始y坐标,它只做上下移动,所以没必要设rect属性
        
    def update(self):
        """更新帧图索引,但不能太快,所以设有延时,同于sleep命令会阻赛进程,所以不能使用它"""
        if time.time() - self.begin_time >= self.delay_time: # 超过延时的时间则换下一个背景图
           self.index = self.index + 1
           self.index = self.index % self.amounts
           self.begin_time = time.time()                     # 重新设定起始时间 
           if self.y > 150 : self.y -= 1                     # 让背景缓慢上升           
        
    def draw(self):
        """ 把当前帧图画到屏幕上"""
        self.screen.blit(self.images[self.index],(0,self.y))

class Arithmetic(pygame.sprite.Sprite):
    """算术题类,每隔一定的时候,会生成一个算术题"""
    def __init__(self,font,group,screen,rat_group):
        pygame.sprite.Sprite.__init__(self)
        self.font = font
        self.group = group
        self.group.add(self)
        self.screen = screen
        self.srceen_width = screen.get_width()
        self.screen_height = screen.get_height()
        self.survival_time = 10            #  生存时间,秒
        self.expression_type = ["+","-","*","//"]
        self.make_question()               # 出题
        self.render_question()             # 渲染题目为图层
        self.begin_time = time.time()      # 诞生的起始时间戳
        self.rat_group = rat_group         # 老鼠组,遍历它们进行矩形碰撞检
        
    def make_question(self):        
        "出题"
        x = randint(-10,10)
        y = randint(1,10)       
        atype = randint(0,3)                #0加,1减,2乘,3整除
    
        right = eval(str(x) + self.expression_type[atype] + str(y))   # 正确的答案 
        wrong = randint(-100,100)           # 错误的答案
        while wrong == right:wrong = randint(-100,100) # 如果错误的答案刚好和正确的相等,那么重新换一个
        self.good = choice([True,False])    # 出错误的题还是出正确的题
        if self.good == True :
            answer = right
        else:
            answer = wrong
        self.expression = str(x) + self.expression_type[atype] + str(y) + "=" + str(answer) 
        
    def render_question(self):
        self.font_image = self.font.render(self.expression,True,(120,10,15))
        self.rect = self.font_image.get_rect()
        self.width = self.rect.w
        self.height = self.rect.h
        "把字渲染到self.image上,只是为了有个背景,也可以不用这一步"
        self.image = pygame.Surface((self.width + 20,self.height+20))
        self.image.set_colorkey((0,0,0))
        
        rect = self.image.get_rect()
        pygame.draw.ellipse(self.image,(0,128,128),rect)  # 在image上画个椭圆      
        self.image.blit(self.font_image,(10,10))          # 在image上再把算术题画上去
         
        "移到随机位置"
        x = randint(self.width,self.srceen_width - self.width)
        y = randint(self.height,self.screen_height - self.height)
        self.rect.center = (x,y)

    def update(self):
        """超过生存时间,则从组中移动,这样就不会被渲染了"""
        if time.time() - self.begin_time > self.survival_time: self.die()
        self.bump_rats_check()
        
    def bump_rats_check(self):
        """碰鼠检测,遍历所有老鼠"""
        for rat in self.rat_group:
            if rat.dead : continue
            if self.rect.colliderect(rat.rect) and self in self.group:
                
                if  self.good : rat.score += 10
                if not self.good : rat.score -= 10
                self.die()
    def die(self):
        self.group.remove(self)
                  
class Rat(pygame.sprite.Sprite):
    def __init__(self,images,keys,screen,initheading,name,background,flame_images):
        pygame.sprite.Sprite.__init__(self)
        "原始图,是向右的"
        self.flame_images = flame_images                                  # 鼠死后要生成火焰对象,这个是传递给此对象的
        self.background = background                                      # 能访问背景
        self.name = name                                                  # 给他们取个名字
        self.keys = keys                                                  # 右,上,左,下
        self.screen = screen        
        self.screen_width = self.screen.get_width()
        self.screen_height = self.screen.get_height()

        self.raw_images = [pygame.image.load(image).convert_alpha() for image in images]
        
        self.right_images = self.raw_images
        self.up_images = [pygame.transform.rotate(image,90) for image in self.raw_images]
        self.left_images = [pygame.transform.rotate(image,180) for image in self.raw_images]
        self.down_images = [pygame.transform.rotate(image,-90) for image in self.raw_images]
        "images列表,右,上,左,下,每个images包括两个造型"
        self.images_list = [self.right_images,self.up_images,self.left_images,self.down_images]                                                         

        self.right_rects = [image.get_rect() for image in self.right_images]  # 向右方向的2个矩形对象
        self.up_rects = [image.get_rect() for image in self.up_images]        # 向上方向的2个矩形对象
        self.left_rects = [image.get_rect() for image in self.left_images]    # 向左方向的2个矩形对象
        self.down_rects = [image.get_rect() for image in self.down_images]    # 向下方向的2个矩形对象
        self.rects_list = [self.right_rects,self.up_rects,self.left_rects,self.down_rects]

        self.heading_index = initheading                               # 朝向索引号,初始朝向, 
        self.costume_index = 0                                         # 造型索引,0为第一个造型,1为第二个造型
        self.costume = self.images_list[self.heading_index][self.costume_index]   # 初始image对象,向右的第一个造型
        self.rect = self.rects_list[self.heading_index][self.costume_index]       # 初始矩形对象        
        self.rect.center = self.screen_width//2,50

        self.xspeed = 0
        self.yspeed = 0

        self.begin_time = time.time()     # 起始时间,用于造型切换

        self.score = 0                    # 接到一个正确的题目就加分,否则减分

        self.dead = False               # 增加 dead属性
        self.aspeed = 0                   # 往下掉的加速度
        self.flame = None                 # 火焰对象
        
    def keys_check(self,allkeys):
        if self.dead : return           # 碰到火苗后死亡,按键失效
        
        if allkeys[self.keys[0]] :         # 右
            self.xspeed = 5
            self.yspeed = 0
            self.heading_index = 0
            
        if allkeys[self.keys[1]] :         # 上
            self.xspeed = 0
            self.yspeed = -5
            self.heading_index  = 1
            
        if allkeys[self.keys[2]] :         # 左  
            self.xspeed = -5
            self.yspeed = 0
            self.heading_index = 2
            
        if allkeys[self.keys[3]] :         # 下
            self.xspeed = 0
            self.yspeed = 5
            self.heading_index = 3

    def update(self):
        self.rect.move_ip(self.xspeed,self.yspeed)
        self.yspeed = self.yspeed + self.aspeed
        self.bump_edge_check()             # 碰到边缘检测
        self.bump_background_check()       # 碰到火苗检测
        if self.flame : self.flame.update()

    def bump_background_check(self):
        if self.rect.y - self.background.y >=0 and not self.dead:
            self.die()
            
    def die(self):
        self.dead = True
        self.xspeed = 0
        self.aspeed = 0.5
        "死后要有一个火焰在它上面燃烧"
        self.flame = Flame(flame_images,self,self.screen)

    def bump_edge_check(self):
        
        if self.rect.right >= self.screen_width : # 到了最右边,要反过来向左移动
            self.xspeed = -5
            self.yspeed = 0
            self.heading_index = 2            
        if self.rect.left <= 0 :                  # 到了最左边,要反过来向右移动
            self.xspeed = 5
            self.yspeed = 0
            self.heading_index = 0        
        if self.rect.top <= 0  :                  # 到了最顶上,要向下移动
            self.xspeed = 0
            self.yspeed =  5
            self.heading_index = 3            
        if self.rect.bottom >= self.screen_height :
            self.xspeed = 0
            self.yspeed = -5
            self.heading_index = 1                # 到了最下,要向上移动       

    def draw(self):
        if time.time() - self.begin_time > 0.1:   # 超过0.1秒,换造型
            oldrect = self.rect
            self.costume_index = 1 -  self.costume_index                                         
            self.costume = self.images_list[self.heading_index][self.costume_index]    
            self.rect = self.rects_list[self.heading_index][self.costume_index]
            self.rect.center = oldrect.center
            self.begin_time = time.time()
        self.screen.blit(self.costume,self.rect)
        if self.flame : self.flame.draw()
        
def check_end_condition(past_time):
    global game_end
    string = None
    if past_time<=0 :
        if not game_end:
           game_end = True
           
           if rat1.score > rat2.score :
              string ="舒克赢了!"
           elif rat1.score < rat2.score:
              string = "贝塔赢了!"
           else:
              string = "平局!"           
    else:
        
        if rat1.dead == True:
            string = "贝塔赢了!"
            game_end == True
        elif rat2.dead == True:                    
            string = "舒克赢了!"
            game_end == True
    #print(string)
    return string

if __name__ == "__main__":

    msyh_font =  "msyh.ttf"
    
    width,height = 960,720
    # 加载素材图像
    flames_list = [os.getcwd() + os.sep + "flames" + os.sep + "0"*(4-len(str(i))) + str(i) + ".png" for i in range(1,13)]
    bg_list = [os.getcwd() + os.sep + "bgframes" + os.sep + str(i) + ".png" for i in  range(13,38)]
    rat_images = ['mouse_right_a.png','mouse_right_b.png']
    rat_images = [os.getcwd() + os.sep + "images" + os.sep + image for image in rat_images]
    rat1_keys = [K_RIGHT,K_UP,K_LEFT,K_DOWN]    # 玩家一按键表  
    rat2_keys = [K_d,K_w,K_a,K_s]               # 玩家二按键表

    pygame.init()
    screen = pygame.display.set_mode((width,height))
    pygame.display.set_caption("生死存亡_双人算术小游戏_作者李兴球,风火轮少儿编程 www.scratch8.net")
    msyh_font = pygame.font.Font(msyh_font,32)
    flame_images = [ pygame.image.load(image).convert_alpha() for image in flames_list]  # 小老鼠死后身上的火焰
    
    expressionEVENT  = USEREVENT + 1
    pygame.time.set_timer(expressionEVENT,5000) #  设定时任务(生成算术表达式)

    endEVENT = USEREVENT + 2
    pygame.time.set_timer(endEVENT,1000)
    
    expression_group = pygame.sprite.Group()
    
    clock = pygame.time.Clock()
    running = True
    
    background = DynamicBackground(bg_list,screen)               # 创建动态背景实例
    rat_group = pygame.sprite.Group()
    rat1 = Rat(rat_images,rat1_keys,screen,0,"舒克",background,flame_images)  # 生成老鼠1,参数为图形列表,按键列表,屏幕,初始方向右
    rat2 = Rat(rat_images,rat2_keys,screen,2,"贝塔",background,flame_images)  # 初始方向左
    rat_group.add(rat1)
    rat_group.add(rat2)

    ret_string = None
    end_image = msyh_font.render(ret_string,True,(255,0,0))
    end_image_rect = end_image.get_rect()
    end_image_rect.center = width//2 , height//2
        
    game_end = False            # 游戏结束的逻辑变量
    game_time = 100             # 游戏时间为100秒
    begin_time = time.time()    # 游戏起始时间
    
    while running:
        past_time = game_time - time.time() + begin_time
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False
                break
            if event.type == endEVENT:               # 每隔一定的时间检测
                ret_string = check_end_condition(past_time)
                if ret_string :
                   end_image = msyh_font.render(ret_string,True,(255,0,0))
                   end_image_rect = end_image.get_rect()
                   end_image_rect.center = width//2 , height//2                   
                   pygame.time.set_timer(endEVENT,100000000)       # 删除此事件,先用这个方法                   
                
            if event.type == expressionEVENT:       # 产生一道算术题
                Arithmetic(msyh_font,expression_group,screen,rat_group)  # 把老鼠组加进去,碰撞检测封装到这个类

        background.update()                              # 背景y坐标不断更新 
        if  ret_string == None:            
            all_keys = pygame.key.get_pressed()          # 所有的按键检测
            rat1.keys_check(all_keys)                    # 对老鼠1进行按键检测
            rat1.update()                                # 更新老鼠1坐标
            rat2.keys_check(all_keys)                    # 对老鼠2进行按键检测
            rat2.update()                                # 更新老鼠2坐标

            expression_group.update()                    # 算术表达式更新,碰撞检测在每个update后
           
        screen.fill((20,20,0))                      # 背景颜色
        background.draw()                           # 动态背景渲染
        if  ret_string == None:
            expression_group.draw(screen)           # 算术题组渲染

        for rat in rat_group:
            rat.draw()                              # 老鼠渲染,不使用rat_group.draw         

        if  ret_string != None: screen.blit(end_image,end_image_rect)
        info = str(rat2.name) + "得分:" + str(rat2.score) + "," + str(rat1.name) + "得分:" + str(rat1.score)
        info = info + " ,游戏剩余时间:" + str(round(past_time))
        pygame.display.set_caption(info)
        pygame.display.update()
        clock.tick(60)
    pygame.quit()