使用 Python 的 PyGame 库写一个俄罗斯方块游戏的逐步指南

很多人学习python,不知道从何学起。
很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。
很多已经做案例的人,却不知道如何去学习更加高深的知识。
那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!
QQ群:1097524789

在这篇教程中,我们会用 Python 的 PyGame 库写一个简单的俄罗斯方块游戏。里面的算法很简单,但对新手可能有一点挑战性。我们不会太关注 PyGame 的内部原理,而更关注游戏的逻辑。如果你懒得阅读整篇文章,你可以简单地复制粘贴文末的代码。

准备工作

  1. Python 3。这可以从 官方网站 下载。
  2. PyGame。根据你正在使用的操作系统,打开命令提示符或者终端,输入 pip install pygame 或 pip3 install pygame 。
  3. Python 的基本知识。如果有需要,可以看看我的其他博文。

你在安装 Python 或者 PyGame 的时候可能会遇到一些问题,但这超出了本文的范围。请参考 StackOverflow :)

我个人在 Mac 上遇到了没办法在屏幕上显示任何东西的问题,安装某些特定版本的 PyGame 可以解决这个问题: pip install pygame==2.0.0.dev4 。

Figure 类

让我们从 Figure 类开始。我们的目标是储存图形的各种类型和它们的旋转结果。我们当然可以通过矩阵旋转来实现,但是这会让问题变得太复杂了。

所以,我们简单地用这样的列表表示图形:

class Figure:
figures = [
[[1, 5, 9, 13], [4, 5, 6, 7]],
[[1, 2, 5, 9], [0, 4, 5, 6], [1, 5, 9, 8], [4, 5, 6, 10]],
[[1, 2, 6, 10], [5, 6, 7, 9], [2, 6, 10, 11], [3, 5, 6, 7]],
[[1, 4, 5, 6], [1, 4, 5, 9], [4, 5, 6, 9], [1, 5, 6, 9]],
[[1, 2, 5, 6]],
]
复制代码

其中,列表第一维度存储图形的类型,第二维度存储它们的旋转结果。每个元素中的数字代表了在 4 × 4 矩阵中填充为实心的位置。例如,[1,5,9,13] 表示一条竖线。为了更好地理解,请参考上面的图片。

作为练习,试着添加一些这里没有的图形,比如 Z 字形。

__init__ 函数如下所示:

class Figure:
...
def __init__(self, x, y):
self.x = x
self.y = y
self.type = random.randint(0, len(self.figures) - 1)
self.color = random.randint(1, len(colors) - 1)
self.rotation = 0
复制代码

在这里,我们随机选择一个形状和颜色。

并且,我们需要能够快速地旋转图形并获得当前的旋转结果,为此我们给出这两个简单的方法:

class Figure:
...
def image(self):
return self.figures[self.type][self.rotation] def rotate(self):
self.rotation = (self.rotation + 1) % len(self.figures[self.type])
复制代码

Tetris 类

我们先用一些变量初始化游戏:

class Tetris:
level = 2
score = 0
state = "start"
field = []
height = 0
width = 0
x = 100
y = 60
zoom = 20
figure = None
复制代码

其中, state 表示我们是否仍在进行游戏; field 表示游戏的场地,为 0 处表示为空,有颜色值则表示此处有图形(除了仍在下落的)。

我们通过下面这个简单的方法来初始化游戏:

class Tetris:
...
def __init__(self, height, width):
self.height = height
self.width = width
for i in range(height):
new_line = []
for j in range(width):
new_line.append(0)
self.field.append(new_line)
复制代码

这会创建一个大小为 height x width 的场地。

创建一个新的图形,并把它定位到坐标 (3, 0) 是很简单的:

class Tetris:
...
def new_figure(self):
self.figure = Figure(3, 0)
复制代码

更有意思的函数是检查目前正在下落的图形是否与已经固定的相交。这种情形可能在图形向左、向右、向下或者旋转时发生。

class Tetris:
...
def intersects(self):
intersection = False
for i in range(4):
for j in range(4):
if i * 4 + j in self.figure.image():
if i + self.figure.y > self.height - 1 or \
j + self.figure.x > self.width - 1 or \
j + self.figure.x < 0 or \
self.field[i + self.figure.y][j + self.figure.x] > 0:
intersection = True
return intersection
复制代码

这很简单:我们遍历并检查当前图形的 4 × 4 矩阵的每个格子,不管它是否超出了游戏的边界或者或者与场地已填充的块重合。我们还检查 self.field[..][..] > 0 ,因为场地的那一块可能有颜色。如果那里是 0,就说明那一块是空的,那就没问题。

有了这个函数,我们就可以检查是否可以移动或旋转图形了。如果它向下移动并且满足相交,那就说明我们已经到底了,所以我们需要 “冻结” 场地上的这个图形:

class Tetris:
...
def freeze(self):
for i in range(4):
for j in range(4):
if i * 4 + j in self.figure.image():
self.field[i + self.figure.y][j + self.figure.x] = self.figure.color
self.break_lines()
self.new_figure()
if self.intersects():
game.state = "gameover"
复制代码

冻结以后,我们需要检查有没有已填满的、需要删除的水平线。然后创建一个新的图形,如果它刚创建就满足相交,那就 Game Over 了 :) 。

检查填满的水平线相当简单直接,但注意,删除水平线需要由下而上地进行:

class Tetris:
...
def break_lines(self):
lines = 0
for i in range(1, self.height):
zeros = 0
for j in range(self.width):
if self.field[i][j] == 0:
zeros += 1
if zeros == 0:
lines += 1
for i1 in range(i, 1, -1):
for j in range(self.width):
self.field[i1][j] = self.field[i1 - 1][j]
self.score += lines ** 2
复制代码

现在,我们还差移动的方法:

class Tetris:
...
def go_space(self):
while not self.intersects():
self.figure.y += 1
self.figure.y -= 1
self.freeze() def go_down(self):
self.figure.y += 1
if self.intersects():
self.figure.y -= 1
self.freeze() def go_side(self, dx):
old_x = self.figure.x
self.figure.x += dx
if self.intersects():
self.figure.x = old_x def rotate(self):
old_rotation = self.figure.rotation
self.figure.rotate()
if self.intersects():
self.figure.rotation = old_rotation
复制代码

如你所见, go_space 方法重复了 go_down 的,但它会一直向下运动直到接触到场景底部或者某个固定的图形。

并且在每个方法中,我们记忆了之前的位置,改变坐标,然后检查是否满足相交。如果有相交,我们就回退到前一个状态。

PyGame 和完整的代码

我们快搞定了!

还剩下一些游戏循环和 PyGame 方面的逻辑。所以,现在一起看看完整的代码吧:

import pygame
import random colors = [
(0, 0, 0),
(120, 37, 179),
(100, 179, 179),
(80, 34, 22),
(80, 134, 22),
(180, 34, 22),
(180, 34, 122),
] class Figure:
x = 0
y = 0 figures = [
[[1, 5, 9, 13], [4, 5, 6, 7]],
[[1, 2, 5, 9], [0, 4, 5, 6], [1, 5, 9, 8], [4, 5, 6, 10]],
[[1, 2, 6, 10], [5, 6, 7, 9], [2, 6, 10, 11], [3, 5, 6, 7]],
[[1, 4, 5, 6], [1, 4, 5, 9], [4, 5, 6, 9], [1, 5, 6, 9]],
[[1, 2, 5, 6]],
] def __init__(self, x, y):
self.x = x
self.y = y
self.type = random.randint(0, len(self.figures) - 1)
self.color = random.randint(1, len(colors) - 1)
self.rotation = 0 def image(self):
return self.figures[self.type][self.rotation] def rotate(self):
self.rotation = (self.rotation + 1) % len(self.figures[self.type]) class Tetris:
level = 2
score = 0
state = "start"
field = []
height = 0
width = 0
x = 100
y = 60
zoom = 20
figure = None def __init__(self, height, width):
self.height = height
self.width = width
for i in range(height):
new_line = []
for j in range(width):
new_line.append(0)
self.field.append(new_line) def new_figure(self):
self.figure = Figure(3, 0) def intersects(self):
intersection = False
for i in range(4):
for j in range(4):
if i * 4 + j in self.figure.image():
if i + self.figure.y > self.height - 1 or \
j + self.figure.x > self.width - 1 or \
j + self.figure.x < 0 or \
self.field[i + self.figure.y][j + self.figure.x] > 0:
intersection = True
return intersection def break_lines(self):
lines = 0
for i in range(1, self.height):
zeros = 0
for j in range(self.width):
if self.field[i][j] == 0:
zeros += 1
if zeros == 0:
lines += 1
for i1 in range(i, 1, -1):
for j in range(self.width):
self.field[i1][j] = self.field[i1 - 1][j]
self.score += lines ** 2 def go_space(self):
while not self.intersects():
self.figure.y += 1
self.figure.y -= 1
self.freeze() def go_down(self):
self.figure.y += 1
if self.intersects():
self.figure.y -= 1
self.freeze() def freeze(self):
for i in range(4):
for j in range(4):
if i * 4 + j in self.figure.image():
self.field[i + self.figure.y][j + self.figure.x] = self.figure.color
self.break_lines()
self.new_figure()
if self.intersects():
game.state = "gameover" def go_side(self, dx):
old_x = self.figure.x
self.figure.x += dx
if self.intersects():
self.figure.x = old_x def rotate(self):
old_rotation = self.figure.rotation
self.figure.rotate()
if self.intersects():
self.figure.rotation = old_rotation # 初始化游戏引擎
pygame.init() # 定义一些颜色
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128) size = (400, 500)
screen = pygame.display.set_mode(size) pygame.display.set_caption("Tetris") # 循环,直到用户点击关闭按钮
done = False
clock = pygame.time.Clock()
fps = 25
game = Tetris(20, 10)
counter = 0 pressing_down = False while not done:
if game.figure is None:
game.new_figure()
counter += 1
if counter > 100000:
counter = 0 if counter % (fps // game.level // 2) == 0 or pressing_down:
if game.state == "start":
game.go_down() for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
game.rotate()
if event.key == pygame.K_DOWN:
pressing_down = True
if event.key == pygame.K_LEFT:
game.go_side(-1)
if event.key == pygame.K_RIGHT:
game.go_side(1)
if event.key == pygame.K_SPACE:
game.go_space()
if event.type == pygame.KEYUP:
if event.key == pygame.K_DOWN:
pressing_down = False screen.fill(WHITE) for i in range(game.height):
for j in range(game.width):
pygame.draw.rect(screen, GRAY, [game.x + game.zoom * j, game.y + game.zoom * i, game.zoom, game.zoom], 1)
if game.field[i][j] > 0:
pygame.draw.rect(screen, colors[game.field[i][j]],
[game.x + game.zoom * j + 1, game.y + game.zoom * i + 1, game.zoom - 2, game.zoom - 1]) if game.figure is not None:
for i in range(4):
for j in range(4):
p = i * 4 + j
if p in game.figure.image():
pygame.draw.rect(screen, colors[game.figure.color],
[game.x + game.zoom * (j + game.figure.x) + 1,
game.y + game.zoom * (i + game.figure.y) + 1,
game.zoom - 2, game.zoom - 2]) font = pygame.font.SysFont('Calibri', 25, True, False)
font1 = pygame.font.SysFont('Calibri', 65, True, False)
text = font.render("Score: " + str(game.score), True, BLACK)
text_game_over = font1.render("Game Over :( ", True, (255, 0, 0)) screen.blit(text, [0, 0])
if game.state == "gameover":
screen.blit(text_game_over, [10, 200]) pygame.display.flip()
clock.tick(fps) pygame.quit()
复制代码

试试复制然后粘贴到一个 py 文件里。运行,然后享受游戏吧! :)

Python 写一个俄罗斯方块游戏的更多相关文章

  1. 【转】shell脚本写的俄罗斯方块游戏

    亲测一个很好玩的shell脚本写的俄罗斯方块游戏,脚本来自互联网 先来讲一下思维流程 一.方块的表示 由于shell不能定义二维数组,所以只能用一维数组表示方块,俄罗斯方块主要可以分为7类,每一类方块 ...

  2. python写一个能变身电光耗子的贪吃蛇

    python写一个不同的贪吃蛇 写这篇文章是因为最近课太多,没有精力去挖洞,记录一下学习中的收获,python那么好玩就写一个大一没有完成的贪吃蛇(主要还是跟课程有关o(╥﹏╥)o,课太多好烦) 第一 ...

  3. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

  4. 十行代码--用python写一个USB病毒 (知乎 DeepWeaver)

    昨天在上厕所的时候突发奇想,当你把usb插进去的时候,能不能自动执行usb上的程序.查了一下,发现只有windows上可以,具体的大家也可以搜索(搜索关键词usb autorun)到.但是,如果我想, ...

  5. [py]python写一个通讯录step by step V3.0

    python写一个通讯录step by step V3.0 参考: http://blog.51cto.com/lovelace/1631831 更新功能: 数据库进行数据存入和读取操作 字典配合函数 ...

  6. 【Python】如何基于Python写一个TCP反向连接后门

    首发安全客 如何基于Python写一个TCP反向连接后门 https://www.anquanke.com/post/id/92401 0x0 介绍 在Linux系统做未授权测试,我们须准备一个安全的 ...

  7. Python写一个自动点餐程序

    Python写一个自动点餐程序 为什么要写这个 公司现在用meican作为点餐渠道,每天规定的时间是早7:00-9:40点餐,有时候我经常容易忘记,或者是在地铁/公交上没办法点餐,所以总是没饭吃,只有 ...

  8. 用python写一个自动化盲注脚本

    前言 当我们进行SQL注入攻击时,当发现无法进行union注入或者报错等注入,那么,就需要考虑盲注了,当我们进行盲注时,需要通过页面的反馈(布尔盲注)或者相应时间(时间盲注),来一个字符一个字符的进行 ...

  9. 利用Python写一个抽奖程序,解密游戏内抽奖的秘密

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 极客挖掘机 PS:如有需要Python学习资料的小伙伴可以加点击下 ...

随机推荐

  1. spring oauth2获取当前登录用户信息。

    使用spring oauth2框架做授权鉴定.想获取当前用户信息怎么办? 我们知道spring oauth2是基于spring security的实现的. spring security可以通过Sec ...

  2. 详解UDP协议

    运输层位于网络层之上,网络层提供了主机之间的逻辑通信:而运输层为运行在不同主机上的应用进程之间提供了逻辑通信.从应用程序角度看,通过逻辑通信,运行不同进程的主机好像直接相连一样.应用进程使用运输层提供 ...

  3. 学会Markdown不仅可以用来编写文档,还可以制作自己的简历,真香!

    程序员的简历要简洁明了,不要太多花哨的修饰,突出重点即可,使用markdown就可以很好的满足写一份简历的需求 Markdown 简历模板 这里我贡献一下我自己的markdown简历模板,简历效果如下 ...

  4. 大厂程序员教你如何学习C++(内附学习资料)

    目前准备面试同学都知道,C++是百度和腾讯的主流开发语言,而java是阿里的主流开发语言. 对于初学者来说,也不用纠结究竟学习c++还是java 其实只要好好掌握好一门即可,另一门即可融会贯通 因为我 ...

  5. 二进制图片blob数据转canvas

    javascript是有操作二进制文件的方法的,在这里就不详述了. 而AJAX的核心XMLHttpRequest也可以获取服务端给的二进制Blob. 可以参考: XMLHttpRequest Leve ...

  6. Shell基本语法---函数

    函数 函数定义 function 函数名 () { 指令... return n } 函数调用及参数传递 function func() { echo "第零个参数:" $ #脚本 ...

  7. Python编程入门(第3版)|百度网盘免费下载|零基础入门学习资料

    百度网盘免费下载:Python编程入门(第3版) 提取码:rsd7 目录  · · · · · · 第1章 编程简介 11.1 Python语言 21.2 Python适合用于做什么 31.3 程序员 ...

  8. DQL_MySQL

    4.DQL(查询数据){SUPER 重点} 4.1DQL (Data Query Language : 数据查询语言) -所有的查询操作: Select 数据库中最核心的语言  create data ...

  9. Spring main方法中怎么调用Dao层和Service层的方法

    在web环境中,一般serviceImpl中的dao之类的数据库连接都由容器启动的时候创建好了,不会报错.但是在main中,没有这个环境,所以需要获取环境: ApplicationContext ct ...

  10. Android系统前台进程,可见进程,服务进程,后台进程,空进程的优先级排序

    1.前台进程 前台进程是Android中最重要的进程,在最后被销毁,是目前正在屏幕上显示的进程和一些系统进程,也就是和用户正在交互的进程. 2.可见进程 可见进程指部分程序界面能够被用户看见,却不在前 ...