pygame 俄罗斯方块

作者: lovetianyats | 来源:发表于2017-08-27 11:05 被阅读422次

    本期,我们开发一个大家都非常熟悉的俄罗斯方块游戏
    想起俄罗斯方块,都是慢慢的回忆~
    闲话少说,开始正文

    游戏设计

    本次我们开发俄罗斯方块,pygame的基本结构我们就不讲了,不清楚的同学可以参考之前的文章,另外就是一些通用的函数比如:show_text 用于显示文字的函数, draw_grids 用于画出网格的函数等等。

    俄罗斯方块游戏主要需要处理好三个的问题

    • 画背景
    • 游戏的主逻辑
    • 满行消除逻辑
    • 生成新的骨牌

    难度从易到难

    背景(包括方块)

    • 如何画出背景网格在之前的贪吃蛇游戏中已经有了,再次不再赘述
    • 我们用一个 screen_color_matrix 一个全局变量保存已经固定的小方块,其中每一行、每一列都对应我们屏幕的网格中的一个小方块,如果为None,说明此处还没有方块;如果不为None,存储的是方块的颜色,函数如下
    # 画出已经固定的网格点
    def draw_matrix():
        for i, row in zip(range(GRID_NUM_HEIGHT), screen_color_matrix):
            for j, color in zip(range(GRID_NUM_WIDTH), row):
                if color is not None:
                    pygame.draw.rect(screen, color,
                                (j * GRID_WIDTH, i * GRID_WIDTH,
                                 GRID_WIDTH, GRID_WIDTH))
                    pygame.draw.rect(screen, WHITE,
                                (j * GRID_WIDTH, i * GRID_WIDTH,
                                 GRID_WIDTH, GRID_WIDTH), 2)
    

    游戏的主逻辑

    俄罗斯方块本身操作简单,自然主逻辑也不复杂。

    • 游戏中只需要检测是否有上下左右键和空格键被按下,左右和下键用于控制移动,上键我们用于旋转,空格键我们用于快速落下骨牌
    • 每次骨牌落下时,我们就重新生成一个新的骨牌
    • 每次骨牌移动的时候,我们都会去判断是否有满行,如果有满行,我们就消除满行并且加分
    • 最后就是需要更新屏幕,这个步骤在之前的游戏中都有,主要就是更新背景及当前的骨牌和分数

    OK,看一下我们的函数,大家可以先忽略用于生成新的骨牌的类CubeShape,以及满行消除逻辑函数remove_full_line

    running = True
    gameover = True
    counter = 0
    live_cube = None
    while running:
        clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if gameover:
                    gameover = False
                    live_cube = CubeShape()
                    break
                if event.key == pygame.K_LEFT:
                    live_cube.left()
                elif event.key == pygame.K_RIGHT:
                    live_cube.right()
                elif event.key == pygame.K_DOWN:
                    live_cube.down()
                elif event.key == pygame.K_UP:
                    live_cube.rotate()
                elif event.key == pygame.K_SPACE:
                    while live_cube.down() == True:
                        pass
                remove_full_line()
    
        # level 是为了方便游戏的难度,level 越高 FPS // level 的值越小
        # 这样屏幕刷新的就越快,难度就越大
        if gameover is False and counter % (FPS // level) == 0:
            # down 表示下移骨牌,返回False表示下移不成功,可能超过了屏幕或者和之前固定的
            # 小方块冲突了
            if live_cube.down() == False:
                for cube in live_cube.get_all_gridpos():
                    screen_color_matrix[cube[0]][cube[1]] = live_cube.color
                live_cube = CubeShape()
                if live_cube.conflict(live_cube.center):
                    gameover = True
                    score = 0
                    live_cube = None
                    screen_color_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]
            # 消除满行
            remove_full_line()
        counter += 1
        # 更新屏幕
        screen.fill(BLACK)
        draw_grids()
        draw_matrix()
        draw_score()
        if live_cube is not None:
            live_cube.draw()
        if gameover:
            show_welcome(screen)
        pygame.display.update()
    

    好的,这里我们将要进入此游戏最复杂的地方

    骨牌类

    骨牌类主要就是为了生成新的骨牌,代码量也就100行

    • 首先分析一下骨牌类,骨牌一共有七种:'I', 'J', 'L', 'O', 'S', 'T', 'Z',可以参考下图

      俄罗斯方块骨牌类型
      各个字母对应哪个图形,相信大家都可以看出来 o~
    • 我们知道每个骨牌除了'O'型的之外,其他图形的转动都会改变。转动图形的时候必须要有一个中心点,不然转动时就会出现奇怪的现象。我们将哪个中心点的位置记为(0, 0),第一个零表示行,第二个零表示列,没向左一个那么列减一,没向下一行行加一,具体的意思大家看下图一看便知


      T型骨牌

      OK, 这样我们将一种骨牌的一个角度记为一个元组列表,如我们记上图为[(0, -1), (0, 0), (0, 1), (-1, 0)],这样上面的T行一共可以有四个方向,就用四个这样的列表表示。

    我们用一个字典SHAPES_WITH_DIR表示所有的图形的各个相对位置

    SHAPES = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
    I = [[(0, -1), (0, 0), (0, 1), (0, 2)],
         [(-1, 0), (0, 0), (1, 0), (2, 0)]]
    J = [[(-2, 0), (-1, 0), (0, 0), (0, -1)],
         [(-1, 0), (0, 0), (0, 1), (0, 2)],
         [(0, 1), (0, 0), (1, 0), (2, 0)],
         [(0, -2), (0, -1), (0, 0), (1, 0)]]
    L = [[(-2, 0), (-1, 0), (0, 0), (0, 1)],
         [(1, 0), (0, 0), (0, 1), (0, 2)],
         [(0, -1), (0, 0), (1, 0), (2, 0)],
         [(0, -2), (0, -1), (0, 0), (-1, 0)]]
    O = [[(0, 0), (0, 1), (1, 0), (1, 1)]]
    S = [[(-1, 0), (0, 0), (0, 1), (1, 1)],
         [(1, -1), (1, 0), (0, 0), (0, 1)]]
    T = [[(0, -1), (0, 0), (0, 1), (-1, 0)],
         [(-1, 0), (0, 0), (1, 0), (0, 1)],
         [(0, -1), (0, 0), (0, 1), (1, 0)],
         [(-1, 0), (0, 0), (1, 0), (0, -1)]]
    Z = [[(0, -1), (0, 0), (1, 0), (1, 1)],
         [(-1, 0), (0, 0), (0, -1), (1, -1)]]
    SHAPES_WITH_DIR = {
        'I': I, 'J': J, 'L': L, 'O': O, 'S': S, 'T': T, 'Z': Z
    }
    

    这样我们每次更新骨牌位置的时候,只需要更新骨牌的中心就可以了,而形状只需要根据上面列表中的相对位置,再加上骨牌中心的实际位置(相对位置为(0, 0))画出骨牌的实际形状。

    • 骨牌生成有一下几点注意:
      • 生成骨牌的时候,我们随机选择一个骨牌类型。
      • 初始时,我们将中心的坐标置于屏幕的中上方。
      • 为了好看,我们在游戏的最前面定义了好多颜色,每次我们就随机选择一种颜色。
      • 同样,每次生成骨牌我们都随机选择一个方向的骨牌。这样我们骨牌类的初始化函数就像下面的那样
    def __init__(self):
        self.shape = self.SHAPES[random.randint(0, len(self.SHAPES) - 1)]
        # 骨牌所在的行列
        self.center = (2, GRID_NUM_WIDTH // 2)
        self.dir = random.randint(0, len(self.SHAPES_WITH_DIR[self.shape]) - 1)
        self.color = CUBE_COLORS[random.randint(0, len(CUBE_COLORS) - 1)]
    
    • 为了方便,我们写一个函数用于将图形的相对位置转化为屏幕中的绝对位置,其实就是用相对位置加上屏幕中心点所在的位置就好了
    def get_all_gridpos(self, center=None):
        curr_shape = self.SHAPES_WITH_DIR[self.shape][self.dir]
        if center is None:
            center = [self.center[0], self.center[1]]
    
        return [(cube[0] + center[0], cube[1] + center[1])
                for cube in curr_shape]
    
    • 好了,有了这个函数,我们就方便我们画出我们的骨牌了
    def draw(self):
        for cube in self.get_all_gridpos():
            pygame.draw.rect(screen, self.color,
                             (cube[1] * GRID_WIDTH, cube[0] * GRID_WIDTH,
                              GRID_WIDTH, GRID_WIDTH))
            pygame.draw.rect(screen, WHITE,
                             (cube[1] * GRID_WIDTH, cube[0] * GRID_WIDTH,
                              GRID_WIDTH, GRID_WIDTH),
                             1)
    

    pygame.draw.rect和我们之前文章中使用的一样,就是一个画出方块的函数,我们使用了两个rect函数,下面那个是为了画一个边框,使得我们的方块好看一点~~

    • 到这里,我们就差骨牌移动的函数了,要移动骨牌我们就必须要判断每次移动是否合法,即是否会超出屏幕之外,或者和之前的小方块冲突了
    def conflict(self, center):
        for cube in self.get_all_gridpos(center):
            # 超出屏幕之外,说明不合法
            if cube[0] < 0 or cube[1] < 0 or cube[0] >= GRID_NUM_HEIGHT or\
                    cube[1] >= GRID_NUM_WIDTH:
                return True
    
            # 不为None,说明之前已经有小方块存在了,也不合法
            if screen_color_matrix[cube[0]][cube[1]] is not None:
                return True
    
        return False
    
    • 有了这个函数之后,我们移动骨牌之前就只需要判断一下移动是否合法就好了,移动或者转动的逻辑都非常简单
    def rotate(self):
        # 选择下一个方向
        new_dir = self.dir + 1
        new_dir %= len(self.SHAPES_WITH_DIR[self.shape])
        # 这里需要保存一下先前的方向,因为我们不知道转动是否合法
        old_dir = self.dir
        self.dir = new_dir
        if self.conflict(self.center):
            self.dir = old_dir
            return False
    
    def down(self):
        # import pdb; pdb.set_trace()
        center = (self.center[0] + 1, self.center[1])
        if self.conflict(center):
            return False
    
        self.center = center
        return True
    
    def left(self):
        center = (self.center[0], self.center[1] - 1)
        if self.conflict(center):
            return False
        self.center = center
        return True
    
    def right(self):
        center = (self.center[0], self.center[1] + 1)
        if self.conflict(center):
            return False
        self.center = center
        return True
    

    好了,最后我们看一下,我们消除满行的函数,这个函数没有什么好说的,只要每一行的方块都不为None就好了(也就是screen_color_matrix中对应位置),如果有为None的,我们就将整行复制到新的屏幕矩阵中就好了~~

    def remove_full_line():
        # 这里我们用的是python3, 所以这里声明一下
        global screen_color_matrix
        global score
        global level
        new_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]
        index = GRID_NUM_HEIGHT - 1
        n_full_line = 0
        for i in range(GRID_NUM_HEIGHT - 1, -1, -1):
            is_full = True
            for j in range(GRID_NUM_WIDTH):
                if screen_color_matrix[i][j] is None:
                    is_full = False
                    continue
            if not is_full:
                new_matrix[index] = screen_color_matrix[i]
                index -= 1
            else:
                n_full_line += 1
        score += n_full_line
        level = score // 20 + 1
        screen_color_matrix = new_matrix
    

    至此,我们大功告成! 看一下效果~


    完整的代码可以去我的 github 看,点击这里进入GitHub。
    如果这篇文章对您有帮助,记得点个赞!
    您的支持是我继续创作的动力~~~

    相关文章

      网友评论

      本文标题:pygame 俄罗斯方块

      本文链接:https://www.haomeiwen.com/subject/dsebdxtx.html