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


源码地址: PyGame俄罗斯方块

逻辑设计

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

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

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

在实际操作时,会经常遇到旋转操作,其基本逻辑是,将第 i i i行变成第 − 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次

最终游戏效果如下

在这里插入图片描述

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微小冷

请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值