Pygame用200行代码实现俄罗斯方块

 更新时间:2023年12月15日 09:10:18   作者:微小冷  
俄罗斯方块的逻辑很简单,就是几个方块组合在一起,然后下落,当其碰到四周的墙壁后便无法移动,若某行被方块所填满,那么就删除这一行,然后此行上面的所有方块下降一行,本文给大家介绍了用Pygame实现俄罗斯方块,文中代码示例介绍的非常详细,需要的朋友可以参考下

逻辑设计

俄罗斯方块的逻辑很简单,就是几个方块组合在一起,然后下落,当其碰到四周的墙壁后便无法移动。若某行被方块所填满,那么就删除这一行,然后此行上面的所有方块下降一行。

为了将这个逻辑代码化,可以用布尔矩阵来表示具体的方块类型,比如长条形方块可用如下矩阵表示。

BLOCK_I = [[0,1,0,0],
           [0,1,0,0],
           [0,1,0,0],
           [0,1,0,0]]

在实际操作时,会经常遇到旋转操作,其基本逻辑是,将第i ii行变成第− i -i−i列,从而旋转逻辑如下

def rotateBlock(block):
    newBlock = [[[] for _ in block] for b in block[0]]
    for i,row in enumerate(block, 1):
        for j,r in enumerate(row, 0):
            newBlock[j][-i] = r
    return newBlock

定义好所有的方块,并将其封入列表BLOCK中,

BLOCK_S = [[0,1,1],
           [1,1,0],
           [0,0,0]]

BLOCK_Z = [[1,1,0],
     [0,1,1],
     [0,0,0]]

# I型方块
BLOCK_I = [[0,1,0,0],
     [0,1,0,0],
     [0,1,0,0],
     [0,1,0,0]],

# O型方块
BLOCK_O = [[1,1],
            [1,1]]
# J型方块
BLOCK_J = [[1,0,0],
     [1,1,1],
     [0,0,0]]

# L型方块
BLOCK_L = [[0,0,1],
     [1,1,1],
     [0,0,0]]

# T型方块
BLOCK_T = [[0,1,0],
     [1,1,1],
     [0,0,0]]

BLOCKS = [BLOCK_S, BLOCK_I, BLOCK_J, BLOCK_L, BLOCK_O, BLOCK_T, BLOCK_Z]

有了这个,就可以随机生成其中某个方块了,这个过程需要引入一点随机性

import random
# 创建一个图形
def newBlock():
    block = random.choice(BLOCKS)
    for _ in range(random.randint(0,4)):
        block = rotateBlock(block)
    return block

挪动逻辑

原理十分简单,但想要写出一个可以玩的游戏,却还需要注意一些细节问题。比如俄罗斯方块在碰到墙壁时的行为;旋转方块的方法。在实现这些具体问题之前,先设置几个常量

SCREEN_WIDTH, SCREEN_HEIGHT = 450, 750
BLOCK_COL_NUM = 10  # 每行的方格数
BLOCK_ROW_NUM = 25  # 每列的方个数
SIZE = 30  # 每个小方格大小
BG_COLOR = (40, 40, 60)  # 背景色
RED = (200, 30, 30)  # 红色,GAME OVER 的字体颜色
WHITE = (255,255,255)   # 白色
BLACK = (0,0,0)         # 黑色
GREEN = (60, 128, 80)   # 绿色
STOP_LST = [[0 for _ in range(BLOCK_COL_NUM)]
               for _ in range(BLOCK_ROW_NUM)]


接下来逐个实现俄罗斯方块的细节,当左右移动方块时,若方块碰到墙壁,则需要一个判定函数,代码如下。其中isR为True时表示向右移动,否则向左移动。其判断逻辑非常直观,只需查看是否超出边界。

# 判断是否可以向左右移动
# isR为True,判断能否向右移动;isR为False,判断能否向左移动
def judgeMoveLR(block, stCol, isR):
    nCol = len(block[0])
    cols = range(nCol-1, -1, -1) if isR else range(nCol)
    for col in cols:
        lstCol = [line[col] for line in block]
        if 1 not in lstCol:
            continue
        if not 0 <= stCol + col < BLOCK_COL_NUM:
            return False
    return True

如果判定成功,则需要移动方块,那么根据其输入左移或者右移,可以构造一个函数

# 相左或者向右移动
def moveLR(block, stCol, key):
    isR = key == pygame.K_RIGHT
    stCol = stCol + 1 if isR else stCol - 1
    if judgeMoveLR(block ,stCol, isR):
        return 1 if isR else -1
    return 0

方块在下落过程中若碰壁,则稍微有些麻烦,因为下方可能已经堆积了一些方块,为此不仅需要知道当前下落方块的信息,还要知道已经堆积的方块的信息。

# 判断是否可以继续下落
def judgeMoveDown(block, stRow, stCol, stopLst):
    # 堆积方块的位置
    stopPosition = list()
    for row, line in enumerate(stopLst):
        for col, b in enumerate(line):
            if b: stopPosition.append((row, col))
    # 判断碰撞
    for row, line in enumerate(block):
        if 1 in line and stRow + row >= BLOCK_ROW_NUM:
            return False
        for col, b in enumerate(line):
            if b and (stRow + row, stCol + col) in stopPosition:
                return False
    return True

最后将三者合在一起,用于判断方块是否可以继续移动

def canMove(block, stRow, stCol, stopLst):
    flag = judgeMoveLR(block, stCol, False)
    flag &= judgeMoveLR(block, stCol, True)
    flag &= judgeMoveDown(block, stRow, stCol, stopLst)
    return flag

消除和堆积

其消除逻辑是,当某行全为1的时候,就把这行删掉,然后在顶端补充全0的列表。

def judgeLines(stopLst):
    # 记录刚刚消除的行数
    i, N, count = 0, len(stopLst), 0
    for i in range(N):
        if 0 not in stopLst[i]:
            line = [0]*len(stopLst.pop(i))
            stopLst.insert(0, line)
            count += 10
    return count

堆积逻辑则相对简单,只需将堆积部位置为1即可。

# 将停止移动的block添加到堆积方块
def addToStopLst(stopLst, block, stRow, stCol):
    for row, line in enumerate(block):
        for col, b in enumerate(line):
            if b: stopLst[stRow + row][stCol + col] = 1

至此,俄罗斯方块的操作逻辑便设计完成,接下来是绘图。

参数逻辑

俄罗斯方块的下落速度可以区分不同关卡,下面是一种用分数决定下落速度的方案,其速度值表示方块下落的延时时间。

def changeSpeed(score):
    scores = [20, 50, 100, 200]
    infos = "12345"
    speeds = [0.5, 0.4, 0.3, 0.2, 0.1]
    ind = bisect(scores, score)
    return infos[ind], speeds[ind]

绘图逻辑

首先是背景绘制,主要包括背景颜色和网格的绘制。

def drawBackground(screen):
    screen.fill(BG_COLOR)
    width = SIZE * BLOCK_COL_NUM
    pygame.draw.line(screen, BLACK, (width, 0), (width, SCREEN_HEIGHT), 4)
    for x in range(BLOCK_COL_NUM):
        pygame.draw.line(screen, BLACK, (x * SIZE, 0), (x * SIZE, SCREEN_HEIGHT), 1)
    for y in range(BLOCK_ROW_NUM):
        pygame.draw.line(screen, BLACK, (0, y * SIZE), (width, y * SIZE), 1)

这里仅采使用了左侧窗口,其右侧将用于存放提示信息,主要包括得分、速度等级以及接下来落下的方块。

def drawRight(screen, score, speedInfo):
    fontPath = r'C:\Windows\Fonts\msyh.ttc'
    font = pygame.font.Font(fontPath, 24)  # 黑体24

    posDct = {
        '得分: ':10, str(score): 50,
        '速度: ':100, speedInfo: 150,
        '下一个:':200
    }
    for k,v in posDct.items():
        msg = font.render(k, True, WHITE)
        screen.blit(msg, (BLOCK_COL_NUM * SIZE + 10, v))

然后是方块绘制函数,顾名思义,drawBlock绘制一个方块,drawBlocks绘制一组方块。

def drawBlock(screen, row, col, stRow, stCol):
    stRow += row * SIZE
    stCol += SIZE * col
    rect = pygame.Rect(stCol, stRow, SIZE, SIZE)
    pygame.draw.rect(screen, GREEN, rect, 0)
    pygame.draw.rect(screen, BLACK, rect, 1)

def drawBlocks(screen, block, stRow, stCol):
    for row, line in enumerate(block):
        for col, b in enumerate(line):
            if not b: continue
            drawBlock(screen, row, col, stRow, stCol)

最后,若游戏失败,则弹出失败信息

def drawGamerOver(screen):
    fontPath = r'C:\Windows\Fonts\msyh.ttc'
    font = pygame.font.Font(fontPath, 72)

    w, h = font.size('GAME OVER')
    msg = font.render('GAME OVER', True, RED)
    screen.blit(msg, ((SCREEN_WIDTH - w) // 2, (SCREEN_HEIGHT - h) // 2))

    # 显示"鼠标点击任意位置,再来一局"
    font = pygame.font.Font(fontPath, 24)
    w, h = font.size('点击任意位置,再来一局')
    msg = font.render('点击任意位置,再来一局', True, RED)
    screen.blit(msg, ((SCREEN_WIDTH - w) // 2, (SCREEN_HEIGHT - h) // 2 + 80))

主程序

主程序内容如下,其中initBlock用于初始化方块。由于大家对俄罗斯方块都很熟悉,且所有调用的子函数均已说明,所以就不再细说其流程了。

def initBlock(nextBlock=None):
    stRow, stCol = -2, 4
    nowBlock = nextBlock if nextBlock else newBlock()
    nextBlock = newBlock()
    return stRow, stCol, nowBlock, nextBlock

def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption('俄罗斯方块')

    # 初始化当前图形的位置,当前图形,以及下一个图形
    stRow, stCol, nowBlock, nextBlock = initBlock()
    last_time = time.time()
    speed, speedInfo = 0.5, '1'
    score, gameOver = 0, False
    stopLst = deepcopy(STOP_LST)
    clock = pygame.time.Clock()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
                    stCol += moveLR(nowBlock, stCol, event.key)
                elif event.key == pygame.K_UP:
                    rotBlock = rotateBlock(nowBlock)
                    if canMove(rotBlock, stRow, stCol, stopLst):
                        nowBlock = rotBlock
                elif event.key == pygame.K_DOWN:
                    # 判断是否可以向下移动,如果碰到底部或者其它的图形就不能移动了
                    while judgeMoveDown(nowBlock, stRow + 1, stCol, stopLst):
                        stRow += 1
            if gameOver:
                stRow, stCol, nowBlock, nextBlock = initBlock()
                stopLst = deepcopy(STOP_LST)
                score = 0
                gameOver = False

        # 判断是否修改当前图形显示的起始行
        if not gameOver and time.time() - last_time > speed:
            last_time = time.time()
            # 可以向下移动的情形
            if judgeMoveDown(nowBlock, stRow + 1, stCol, stopLst):
                stRow += 1
            else:
                # 不可向下移动,则需新建一个block然后下落
                addToStopLst(stopLst, nowBlock, stRow, stCol)
                score += judgeLines(stopLst)
                gameOver =  1 in stopLst[0]             # 第一行中间有1则游戏结束
                speedInfo, speed = changeSpeed(score)   # 调整速度
                stRow, stCol, nowBlock, nextBlock = initBlock(nextBlock)

        drawBackground(screen)
        drawRight(screen, score, speedInfo)
        drawBlocks(screen, nowBlock, stRow*SIZE, stCol*SIZE)    # 当前方块
        drawBlocks(screen, stopLst, 0, 0)                       # 堆积方块
        drawBlocks(screen, nextBlock, 300, 320)                 # 下一个方块

        # 显示游戏结束画面
        if gameOver:
            drawGamerOver(screen)

        pygame.display.update()
        clock.tick(60)  # 通过一定的延时,实现1秒钟能够循环60次

最终游戏效果如下

以上就是Pygame用200行代码实现俄罗斯方块的详细内容,更多关于Pygame俄罗斯方块的资料请关注脚本之家其它相关文章!

相关文章

  • 解析django的csrf跨站请求伪造

    解析django的csrf跨站请求伪造

    本文主要介绍了解析django的csrf跨站请求伪造,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Python的OptionParser模块示例教程

    Python的OptionParser模块示例教程

    这篇文章主要介绍了Python的OptionParser模块教程,本文通过示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-01-01
  • Python制作豆瓣图片的爬虫

    Python制作豆瓣图片的爬虫

    本文给大家分享的是作者制作的爬取豆瓣首页图片的爬虫代码,代码很简单,大家可以参考下思路,希望可以帮到大家
    2017-12-12
  • pycharm出现了pytest模式下如何改回run模式

    pycharm出现了pytest模式下如何改回run模式

    这篇文章主要介绍了pycharm出现了pytest模式下如何改回run模式问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • python的id()函数解密过程

    python的id()函数解密过程

    id()函数在使用过程中很频繁,为此本人对此函数深入研究下,晒出代码和大家分享下,希望对你们有所帮助
    2012-12-12
  • Python面向对象程序设计之类的定义与继承简单示例

    Python面向对象程序设计之类的定义与继承简单示例

    这篇文章主要介绍了Python面向对象程序设计之类的定义与继承,结合完整实例形式分析了Python面向对象程序设计中类的定义、调用、继承及相关操作注意事项,需要的朋友可以参考下
    2019-03-03
  • Django框架之路由用法

    Django框架之路由用法

    这篇文章介绍了Django框架之路由的用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • python自动生成sql语句的脚本

    python自动生成sql语句的脚本

    这篇文章主要介绍了python自动生成sql语句的脚本,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • Python Numpy中数组的集合操作详解

    Python Numpy中数组的集合操作详解

    这篇文章主要为大家详细介绍了Python Numpy中数组的一些集合操作方法,文中的示例代码讲解详细,对我们学习Python有一定帮助,需要的可以参考一下
    2022-08-08
  • Python断言assert的用法代码解析

    Python断言assert的用法代码解析

    这篇文章主要介绍了Python断言assert的用法代码解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02

最新评论