Python俄罗斯方块

完整代码

import pygame, sys
import random

# 绘制方块和背景的函数
def draw_block():
    y, x = initial_position  # 获取当前方块的初始位置
    for row, column in select_block:  # 遍历当前选择的方块
        row += y  # 更新y坐标
        column += x  # 更新x坐标
        # 800 - row * 40 反转了y坐标
        pygame.draw.rect(screen, (255, 165, 0), (column * 40, 800 - row * 40, 38, 38))  # 绘制动态方块(橙色)
    for row in range(1, 21):  # 遍历背景网格的每一行
        for column in range(0, 10):  # 遍历每一列
            pygame.draw.rect(screen, (200, 200, 200), (column * 40, 800 - row * 40, 40, 40), 1)  # 绘制网格线
            if backgroud[row][column] == 1:  # 如果该位置被占用
                pygame.draw.rect(screen, (0, 0, 255), (column * 40, 800 - row * 40, 38, 38))  # 绘制静态方块(蓝色)

# 方块下落的函数
def block_down():
    global select_block  # 声明select_block为全局变量
    y, x = initial_position  # 获取初始位置,initial_position = [21, 5]
    y -= 1  # y坐标减1,表示方块向下移动一个单位
    for row, column in select_block:  # 调节方块下落的位置
        row += y  # 更新方块的y坐标
        column += x  # 更新方块的x坐标
        if backgroud[row][column]:  # 注意!!!!!检查新位置是否已被占用
            break  # 如果被占用,停止下落
    else:
        initial_position.clear()  # 刷新,对初始位置进行更新
        initial_position.extend([y, x])  # 更新初始位置为新的坐标,第一个值表示row,第二个值表示column
        return  # 结束当前函数 block_down 的执行,并返回到调用 block_down 的地方,不再执行后续的代码

    # 在方块下落时,如方块无法再继续向下移动,则将当前方块的位置标记为占用,并将其固定在背景网格中
    y, x = initial_position  # 获取方块的当前初始位置
    for row, column in select_block:  # 对当前选择的方块
        row += y
        column += x
        backgroud[row][column] = 1  # 将背景网格对应位置标记为1,表示占用

    complete_row = []  # 用来存储静态的block,即完成一行后,将静态block存储在数组中
    # 然后通过删除数组元素达到消除方块的效果
    # 处理和检测背景网格中是不是有完整的行
    for row in range(1, 21):  # 遍历背景网格的每一行(1到20行)
        if 0 not in backgroud[row]:  # 如果该行没有0,说明已被填满
            complete_row.append(row)  # 如果一行是完整的将其行号row添加到complete_row中
    for row in complete_row:  # 遍历所有已完成的行
        backgroud.pop(row)  # 将完整的行移除
        backgroud.append([0 for _ in range(10)])  # 在网格底部添加一个新空行,保持网格大小不变

    score[0] += len(complete_row)  # 更新分数,增加消除的行数
    pygame.display.set_caption('你现在的分数是 ' + str(score[0]) + '分')  # 更新窗口标题显示当前分数

    initial_position.clear()  # 清空初始位置列表
    select_block = list(random.choice(all_block))  # 随机选择一个新的方块形状
    initial_position.extend([20, 5])  # 将[20,5]添加到initial_position中,[20,5]代表下落的初始坐标

    y, x = initial_position  # 获取新方块的初始坐标
    for row, column in select_block:  # 用于确定方块的偏移量
        row += y
        column += x
        if backgroud[row][column] == 1:  # 判断静态block对应位置是否为1,如果是说明当前方块与背景中已有方块重叠
            gameover.append(1)  # 如果重叠,将1添加到gameover列表中,表示游戏结束

# 旋转方块的函数
def rotate():
    y, x = initial_position  # 获取当前方块的初始位置
    # 计算旋转后的位置,旋转90度
    rotating_position = [(-colum, row) for row, colum in select_block]
    for row, colum in rotating_position:  # 遍历旋转后的每个单元
        row += y  # 更新y坐标
        colum += x  # 更新x坐标
        if colum < 0 or colum > 9 or backgroud[row][colum]:  # 检查是否越界或与背景重叠
            break  # 如果有问题,停止旋转
    else:
        select_block.clear()  # 清空当前选择的方块
        select_block.extend(rotating_position)  # 更新为旋转后的方块

# 移动方块的函数,参数d表示方向(-1左移,1右移)
def move(d):
    y, x = initial_position  # 获取当前方块的初始位置
    x += d  # 更新x坐标,根据方向d移动
    for row, column in select_block:  # 遍历当前选择的方块
        row += y  # 更新y坐标
        column += x  # 更新x坐标
        if column < 0 or column > 9 or backgroud[row][column]:  # 检查是否越界或与背景重叠
            break  # 如果有问题,停止移动
    else:
        initial_position.clear()  # 清空初始位置列表
        initial_position.extend([y, x])  # 更新初始位置为新的坐标

# 主程序入口
if __name__ == "__main__":
    # 初始化Pygame
    pygame.init()
    # 定义所有可能的方块形状,每种形状由4个单元组成
    all_block = [
        [[0, 0], [0, 1], [0, 2], [0, 3]],  # 直线形状
        [[0, 0], [0, 1], [1, 0], [1, 1]],  # 正方形形状
        [[0, 0], [1, 0], [0, 1], [0, 2]],  # L形状
        [[0, 0], [1, 0], [0, -1], [0, -2]],  # 反L形状
        [[0, 0], [0, 1], [1, 1], [1, 2]],  # Z形状
        [[0, 0], [0, -1], [1, -1], [1, -2]],  # 反Z形状
        [[0, 0], [0, 1], [0, -1], [1, 0]],  # T形状
    ]

    # 初始化背景网格,23行10列,所有位置初始化为0
    backgroud = [[0 for column in range(10)] for row in range(23)]
    backgroud[0] = [1 for colum in range(10)]  # 将第0行全部设置为1,表示底部边界
    # 设置全局变量
    initial_position = [21, 5]  # 初始位置设置在第21行,第5列
    times = 0  # 初始化计时器
    score = [0]  # 初始化分数列表,用于记录分数
    gameover = []  # 游戏结束标志列表

    press = False  # 按键加速标志
    running = True  # 游戏是否运行

    screen = pygame.display.set_mode((400, 800))  # 设置游戏窗口大小为400x800像素
    select_block = list(random.choice(all_block))  # 随机选择一个初始方块形状

    # 游戏主循环
    while running:
        screen.fill((255, 255, 255))  # 将屏幕填充为白色背景
        for event in pygame.event.get():  # 处理所有事件
            if event.type == pygame.QUIT:  # 如果点击关闭按钮
                sys.exit()  # 退出程序
        # for event in pygame.event.get():
        #     if event.type == pygame.QUIT:  # 如果点击关闭按钮,退出游戏
        #         running = False
        #         break
            if event.type == pygame.KEYDOWN:  # 如果有键被按下
                if event.key == pygame.K_DOWN:  # 如果按下的是向下键
                    press = True  # 设置按键加速标志为True
                elif event.key == pygame.K_UP:  # 如果按下的是向上键
                    rotate()  # 调用旋转函数
                elif event.key == pygame.K_RIGHT:  # 如果按下的是向右键
                    move(1)  # 向右移动
                elif event.key == pygame.K_LEFT:  # 如果按下的是向左键
                    move(-1)  # 向左移动
            if event.type == pygame.KEYUP:  # 如果有键被释放
                if event.key == pygame.K_DOWN:  # 如果释放的是向下键
                    press = False  # 取消按键加速标志

        if times % 60 == 0:  # 每60帧调用一次block_down函数
            block_down()
        times += 1  # 增加计时器
        if press:  # 如果按键加速标志为True
            times += 100  # 快速增加计时器,加速下落
        if gameover:  # 如果游戏结束
            sys.exit()  # 退出程序
        draw_block()  # 调用绘制函数
        pygame.time.Clock().tick(400)  # 设置游戏帧率为400帧每秒

        # 创建字体对象
        font = pygame.font.Font(None, 36)
        # 创建显示文本
        text = font.render("Score: " + str(score[0]), True, (0, 0, 0))
        # 在屏幕上显示文本
        screen.blit(text, (10, 10))
        # 更新显示
        pygame.display.update()

        pygame.display.flip()  # 刷新整个显示
运行截图

注意:

一、本游戏代码在 draw_block() 函数中,绘制方块的位置 使用 (column * 40, 800 - row * 40)800 - row * 40 将 y 坐标反转,以符合游戏的视觉效果,使得 y 轴的坐标是从屏幕顶部到底部逐渐减小的
二、本游戏代码的坐标列表中,[a, b],a表示行row(即y坐标),b表示列column(即x坐标)

疑问

一、

def block_down():
    global select_block  # 声明select_block为全局变量
    y, x = initial_position  # 获取初始位置,initial_position = [21, 5]
    y -= 1  # y坐标减1,表示方块向下移动一个单位
    for row, column in select_block:  # 遍历当前选择的方块
        row += y  # 更新方块的y坐标
        column += x  # 更新方块的x坐标
        if backgroud[row][column]:  # 检查新位置是否已被占用
            break  # 如果被占用,停止下落
    else:
        initial_position.clear()  # 清空初始位置列表
        initial_position.extend([y, x])  # 更新初始位置为新的坐标
        return  # 结束函数,继续下一个循环

这段代码和

def block_down():
    global select_block  # 声明select_block为全局变量
    y, x = initial_position  # 获取初始位置,initial_position = [21, 5]
    y -= 1  # y坐标减1,表示方块向下移动一个单位
    for row, column in select_block:  # 遍历当前选择的方块
        row += y  # 更新方块的y坐标
        column += x  # 更新方块的x坐标
        if backgroud[row][column]:  # 检查新位置是否已被占用
            break  # 如果被占用,停止下落
        else:
            initial_position.clear()  # 清空初始位置列表
            initial_position.extend([y, x])  # 更新初始位置为新的坐标
            return  # 结束函数,继续下一个循环

有什么区别?
解答:
第一段代码

  1. for循环遍历完所有方块位置时,如果没有触发break,即所有新位置都没有被占用,才会执行else块的内容。
  2. else块更新initial_position为新的位置 [y, x]
    第二段代码
  3. for循环的每次迭代中,代码检查当前方块的新位置是否被占用。
  4. 如果未被占用(即else块被执行),initial_position就会立即更新,并且函数会返回,跳过剩余的方块检查。
    关键区别
  • 第一段代码:只有在for循环遍历完所有方块,并且没有发现占用位置时,才会更新initial_position。它确保所有方块的位置都是安全的。
  • 第二段代码:一旦发现一个未占用的位置,就立即更新initial_position并返回,可能忽略了其他方块的位置检查。这可能导致initial_position被错误地更新到一个部分有效的位置。

二、

initial_position.extend([y, x])  # 更新初始位置为新的坐标

这个为什么更新为了新的坐标?改变的不是row和column吗?这里输入的是y, x,那row += y 和column += x 的意义在哪?是不是只有y-=1这一个改变,x不会变?
解答:

  1. y, x = initial_positioninitial_position 包含当前方块的初始位置,例如 [21, 5],其中 y=21x=5
  2. y -= 1:这一步将 y 减 1,表示方块整体向下移动一个单位。假设新的 y 变成了 20
  3. for row, column in select_blockselect_block 包含当前方块的相对坐标(例如,形状或结构)。每个方块由相对 rowcolumn 表示,rowcolumn 是相对于 initial_position 的偏移量。
    • row += y:更新每个方块的 y 坐标,实际表示方块在网格中的纵向位置。
    • column += x:更新每个方块的 x 坐标,实际表示方块在网格中的横向位置。
      更新 initial_position 的目的不是为了改变每个方块的 rowcolumn,而是更新方块整体的初始位置,也就是新的 y, x 坐标。这是为了记录整个方块向下移动后的新位置,便于下次移动时继续以这个新位置作为基准。
  • row += ycolumn += x:这些操作的作用是计算每个方块在网格中的绝对位置,以便检查这些位置是否被占用。
  • rowcolumn 是相对位置,通过加上 yx,我们得到每个方块在整个游戏网格中的实际位置。
    总结
  • y -= 1 是唯一改变的,表示方块向下移动。
  • x 不变,意味着方块只在纵向移动,横向位置保持不变。
  • row += ycolumn += x 的操作是为了计算方块在网格中的实际位置,而不是改变方块的形状或结构。
  • initial_position.extend([y, x]) 更新的是方块整体的初始位置,以便下一次移动操作可以基于这个新位置。

三、score和gameover为什么要设置成列表,直接设置成变量可以吗?
在这段代码中,scoregameover 被设置成列表是为了在函数内部对它们进行修改,并且这些修改能够在函数外部反映出来。由于Python中的变量是按引用传递的,当传递一个列表或其他可变对象给一个函数时,函数内部对这个对象的修改会影响到外部的对象。这使得代码能够在函数内部修改scoregameover,并在函数外部保持这些修改。
如果直接将scoregameover设置为普通的变量(比如整数或布尔值),这些值在函数内部修改时不会影响到外部的变量,因为整数和布尔值是不可变的,函数内部的修改只会作用在函数的局部变量上。

score = 0  # 普通变量
gameover = False  # 普通变量

在函数内部修改scoregameover时,实际上是修改了它们的副本,而不是全局变量本身:

def some_function():
    score += 10  # 只会修改函数内部的局部变量 score
    gameover = True  # 只会修改函数内部的局部变量 gameover

这样,外部的scoregameover不会被修改。
相比之下,当使用列表时:

score = [0]  # 列表
gameover = []  # 列表

函数内部对列表内容的修改会直接影响到外部的变量:

def some_function():
    score[0] += 10  # 直接修改列表中的值,影响全局变量
    gameover.append(True)  # 直接修改列表内容,影响全局变量

这就确保了函数内部的修改能够保留在全局状态中。
因此,在这种情况下,使用列表是一种简便的方式来确保scoregameover在整个程序中都能被正确更新和反映。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容