Python小游戏之300行代码实现俄罗斯方块


Posted in Python onJanuary 04, 2019

前言

本文代码基于 python3.6 和 pygame1.9.4。

俄罗斯方块是儿时最经典的游戏之一,刚开始接触 pygame 的时候就想写一个俄罗斯方块。但是想到旋转,停靠,消除等操作,感觉好像很难啊,等真正写完了发现,一共也就 300 行代码,并没有什么难的。

先来看一个游戏截图,有点丑,好吧,我没啥美术细胞,但是主体功能都实现了,可以玩起来。

Python小游戏之300行代码实现俄罗斯方块

现在来看一下实现的过程。

外形

俄罗斯方块整个界面分为两部分,一部分是左边的游戏区域,另一部分是右边的显示区域,显示得分、速度、下一个方块样式等。这里就不放截图了,看上图就可以。

游戏区域跟贪吃蛇一样,是由一个个小方格组成的,为了看得直观,我特意画了网格线。

import sys
import pygame
from pygame.locals import *

SIZE = 30 # 每个小方格大小
BLOCK_HEIGHT = 20 # 游戏区高度
BLOCK_WIDTH = 10 # 游戏区宽度
BORDER_WIDTH = 4 # 游戏区边框宽度
BORDER_COLOR = (40, 40, 200) # 游戏区边框颜色
SCREEN_WIDTH = SIZE * (BLOCK_WIDTH + 5) # 游戏屏幕的宽
SCREEN_HEIGHT = SIZE * BLOCK_HEIGHT # 游戏屏幕的高
BG_COLOR = (40, 40, 60) # 背景色
BLACK = (0, 0, 0)


def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)):
 imgText = font.render(text, True, fcolor)
 screen.blit(imgText, (x, y))


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

 font1 = pygame.font.SysFont('SimHei', 24) # 黑体24
 font_pos_x = BLOCK_WIDTH * SIZE + BORDER_WIDTH + 10 # 右侧信息显示区域字体位置的X坐标
 font1_height = int(font1.size('得分')[1])

 score = 0  # 得分

 while True:
 for event in pygame.event.get():
  if event.type == QUIT:
  sys.exit()

 # 填充背景色
 screen.fill(BG_COLOR)
 # 画游戏区域分隔线
 pygame.draw.line(screen, BORDER_COLOR,
    (SIZE * BLOCK_WIDTH + BORDER_WIDTH // 2, 0),
    (SIZE * BLOCK_WIDTH + BORDER_WIDTH // 2, SCREEN_HEIGHT), BORDER_WIDTH)
 # 画网格线 竖线
 for x in range(BLOCK_WIDTH):
  pygame.draw.line(screen, BLACK, (x * SIZE, 0), (x * SIZE, SCREEN_HEIGHT), 1)
 # 画网格线 横线
 for y in range(BLOCK_HEIGHT):
  pygame.draw.line(screen, BLACK, (0, y * SIZE), (BLOCK_WIDTH * SIZE, y * SIZE), 1)

 print_text(screen, font1, font_pos_x, 10, f'得分: ')
 print_text(screen, font1, font_pos_x, 10 + font1_height + 6, f'{score}')
 print_text(screen, font1, font_pos_x, 20 + (font1_height + 6) * 2, f'速度: ')
 print_text(screen, font1, font_pos_x, 20 + (font1_height + 6) * 3, f'{score // 10000}')
 print_text(screen, font1, font_pos_x, 30 + (font1_height + 6) * 4, f'下一个:')

 pygame.display.flip()


if __name__ == '__main__':
 main()

方块

接下来就是要定义方块,方块的形状一共有以下 7 种:

Python小游戏之300行代码实现俄罗斯方块

I 型

Python小游戏之300行代码实现俄罗斯方块

O 型

Python小游戏之300行代码实现俄罗斯方块

T 型

Python小游戏之300行代码实现俄罗斯方块

S 型

Python小游戏之300行代码实现俄罗斯方块

Z 型

Python小游戏之300行代码实现俄罗斯方块

L 型

Python小游戏之300行代码实现俄罗斯方块

J 型

这里我做了多次的更改,因为方块最大的长度是长条形的,为4格,所以我统一用了 4 × 4 的方格来定义。这也是可以的,只是后来发现不方便。

为了直观,直接以一个二维数组来定义方块,其中 . 表示空的, 0 表示实心的。(用 . 表示空是为了看得直观,如果用空格会看不清。)
例如 I 行,以 4 × 4 方格定义为

['.0..',
 '.0..',
 '.0..',
 '.0..']

['....',
 '....',
 '0000',
 '....']

方块最难的是需要实现旋转功能,比如 I 型,就有横和竖两种形态。所谓旋转,表面上看,是把方块顺时针旋转了 90°,但实际做的时候,我们并不需要正真的去实现这个“旋转”的效果。

最终实现的时候,这些图形都是我们画在界面上的,而每一次刷新,界面上所有内容都会被清空重画,所以旋转只是画当前方块的时候不再画之前的形状,而是画旋转后的形状。

比如这个 I 型,定义成了 4 × 4 的形状,但实际上只需要 1 × 4 或 4 × 1 就可以了,其他剩下的地方都是空的。它不像 T 型,T 型不是一个矩形,如果用一个矩形来定义,必然有 2 个位置是空的。那么,I 型真的有必要定义成 4 × 4 吗?

答案是肯定的。想想看,如果是 4 × 1 的一个横条,旋转后变成 1 × 4 的竖条,这个位置怎么确定?好像有点困难。但是如果是 4 × 4 的正方形,我们只需要固定起点坐标(左上角)不变,把竖条的 4 × 4 直接替换掉横条的 4 × 4 区域,是不是就实现旋转了?而且位置很容易计算。

另外一点,在有些情况下是不可以旋转的。比如 I 型的竖条,在紧贴左右边框的时候是不可以旋转的。这点我有印象,可以肯定。但是对于其他的形状,我就不是很确定了,我百度搜了下,找了个网页版的俄罗斯方块玩了下,发现也是不可以的。例如:

Python小游戏之300行代码实现俄罗斯方块

在紧贴右边框的时候是无法旋转的。如果要每一个形状都去判断一下,那实在是太烦了。从方块的定义入手,就可以很简单的实现。

例如竖条行,定义是:

['.0..',
 '.0..',
 '.0..',
 '.0..']

竖条是可以贴边的,所以当它在最左边的时候,X 轴坐标是 -1,这是因为定义中左边一竖排是空的。我们只需判定,当方块所定义的形状(包括空的部分)完全在游戏区域内时才可以旋转。

我之前所说,全都定义成 4 × 4 不好,原因就在这里,对于 T 型等其他形状,无法做这个判定。所以,对于 T 型等形状,我们可以定义成 3 × 3 的格式:

['.0.',
 '000',
 '...']

还有一种情况是无法旋转的,就是旋转后的位置已经被别的方块占了。另外下落,左右移动,都要做这个判断。既然这些是一致的,那么就可以用同一个方法来判断。

先要定义一个 game_area 变量,用于存放整个游戏区域当前的状态:

game_area = [['.'] * BLOCK_WIDTH for _ in range(BLOCK_HEIGHT)]

初始状态全是空的,所以全部用 . 初始化就可以了。

另外,需要一些变量定义当前下落方块的状态

cur_block = None # 当前下落方块
cur_pos_x, cur_pos_y = 0, 0 # 当前下落方块的坐标

方块我们是以二维数组的方式定义的,并且存在空行和空列,如果我们遍历这个二维数组判断其所在的区域在当前游戏区域内是否已经被别的方块所占,这个是可以实现的。我们考虑另外一种情况,一个竖条形,左边一排是空的,这空的一排是可以移出游戏区域的,这个怎么判断?每次左移的时候都去判断一下左边一排全都是空吗?这太麻烦了。并且方块都是固定的,所以这些我们可以提前定义好。最终方块定义如下:

from collections import namedtuple

Point = namedtuple('Point', 'X Y')
Block = namedtuple('Block', 'template start_pos end_pos name next')

# S形方块
S_BLOCK = [Block(['.00',
     '00.',
     '...'], Point(0, 0), Point(2, 1), 'S', 1),
   Block(['0..',
     '00.',
     '.0.'], Point(0, 0), Point(1, 2), 'S', 0)]

方块需要包含两个方法,获取随机一个方块和旋转时获取旋转后的方块

BLOCKS = {'O': O_BLOCK,
   'I': I_BLOCK,
   'Z': Z_BLOCK,
   'T': T_BLOCK,
   'L': L_BLOCK,
   'S': S_BLOCK,
   'J': J_BLOCK}


def get_block():
 block_name = random.choice('OIZTLSJ')
 b = BLOCKS[block_name]
 idx = random.randint(0, len(b) - 1)
 return b[idx]


# 获取旋转后的方块
def get_next_block(block):
 b = BLOCKS[block.name]
 return b[block.next]

判断是否可以旋转,下落,移动的方法也很容易实现了

def _judge(pos_x, pos_y, block):
 nonlocal game_area
 for _i in range(block.start_pos.Y, block.end_pos.Y + 1):
  if pos_y + block.end_pos.Y >= BLOCK_HEIGHT:
   return False
  for _j in range(block.start_pos.X, block.end_pos.X + 1):
   if pos_y + _i >= 0 and block.template[_i][_j] != '.' and game_area[pos_y + _i][pos_x + _j] != '.':
    return False
 return True

停靠

最后一个问题是停靠,当方块下落到底或者遇到别的方块之后,就不能在下落了。我将此称之为“停靠”,有个名字说起来也方便一点。

首先是要判断是否可以停靠,停靠发生之后,就是将当前方块的非空点画到游戏区域上,说白了,就是将cur_block的非空点按对应位置复制到game_area里去。并且计算是否有一排被全部填满了,全部填满则消除。

def _dock():
 nonlocal cur_block, next_block, game_area, cur_pos_x, cur_pos_y, game_over
 for _i in range(cur_block.start_pos.Y, cur_block.end_pos.Y + 1):
  for _j in range(cur_block.start_pos.X, cur_block.end_pos.X + 1):
   if cur_block.template[_i][_j] != '.':
    game_area[cur_pos_y + _i][cur_pos_x + _j] = '0'
 if cur_pos_y + cur_block.start_pos.Y <= 0:
  game_over = True
 else:
  # 计算消除
  remove_idxs = []
  for _i in range(cur_block.start_pos.Y, cur_block.end_pos.Y + 1):
   if all(_x == '0' for _x in game_area[cur_pos_y + _i]):
    remove_idxs.append(cur_pos_y + _i)
  if remove_idxs:
   # 消除
   _i = _j = remove_idxs[-1]
   while _i >= 0:
    while _j in remove_idxs:
     _j -= 1
    if _j < 0:
     game_area[_i] = ['.'] * BLOCK_WIDTH
    else:
     game_area[_i] = game_area[_j]
    _i -= 1
    _j -= 1
  cur_block = next_block
  next_block = blocks.get_block()
  cur_pos_x, cur_pos_y = (BLOCK_WIDTH - cur_block.end_pos.X - 1) // 2, -1 - cur_block.end_pos.Y

至此,整个俄罗斯方块的主体功能就算是完成了。

这里很多参数是可以调的,例如觉得旋转别扭,可以直接调整方块的定义,而无需去改动代码逻辑。

源码下载:http://xiazai.3water.com/201901/yuanma/python-Tetris.rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
python实现通过shelve修改对象实例
Sep 26 Python
一键搞定python连接mysql驱动有关问题(windows版本)
Apr 23 Python
python3使用pandas获取股票数据的方法
Dec 22 Python
Python 支付整合开发包的实现
Jan 23 Python
详解Python函数式编程—高阶函数
Mar 29 Python
Django基础三之视图函数的使用方法
Jul 18 Python
python实现图片压缩代码实例
Aug 12 Python
爬虫代理池Python3WebSpider源代码测试过程解析
Dec 20 Python
python基于三阶贝塞尔曲线的数据平滑算法
Dec 27 Python
pytorch中交叉熵损失(nn.CrossEntropyLoss())的计算过程详解
Jan 02 Python
python torch.utils.data.DataLoader使用方法
Apr 02 Python
pycharm 使用tab跳出正在编辑的括号(){}{}等问题
Feb 26 Python
django主动抛出403异常的方法详解
Jan 04 #Python
pyspark操作MongoDB的方法步骤
Jan 04 #Python
详解Appium+Python之生成html测试报告
Jan 04 #Python
python虚拟环境迁移方法
Jan 03 #Python
对django xadmin自定义菜单的实例详解
Jan 03 #Python
在Python中关于使用os模块遍历目录的实现方法
Jan 03 #Python
Python代码打开本地.mp4格式文件的方法
Jan 03 #Python
You might like
Http 1.1 Etag 与 Last-Modified提高php效率
2008/01/10 PHP
php 连接mysql连接被重置的解决方法
2011/02/15 PHP
php5.2以下版本无json_decode函数的解决方法
2014/05/25 PHP
thinkPHP自动验证、自动添加及表单错误问题分析
2016/10/17 PHP
弹出模态框modal的实现方法及实例
2017/09/19 PHP
Laravel框架使用技巧之使用url()全局函数返回前一个页面的地址方法详解
2020/04/06 PHP
De facto standard 世界上不可思议的事实标准
2010/08/29 Javascript
jquery中ajax调用json数据的使用说明
2011/03/17 Javascript
让低版本浏览器支持input的placeholder属性(js方法)
2013/04/03 Javascript
js实现动态添加、删除行、onkeyup表格求和示例
2013/08/18 Javascript
javascript在IE下trim函数无法使用的解决方法
2014/09/12 Javascript
Jquery弹出层插件ThickBox的使用方法
2014/12/09 Javascript
详解使用Vue.Js结合Jquery Ajax加载数据的两种方式
2017/01/10 Javascript
JavaScript获取URL参数的方法之一
2017/03/24 Javascript
老生常谈ES6中的类
2017/07/31 Javascript
jQuery实现简单的回到顶部totop功能示例
2017/10/16 jQuery
Vue 之孙组件向爷组件通信的实现
2019/04/23 Javascript
django开发教程之利用缓存文件进行页面缓存的方法
2017/11/10 Python
Python统计python文件中代码,注释及空白对应的行数示例【测试可用】
2018/07/25 Python
如何通过雪花算法用Python实现一个简单的发号器
2019/07/03 Python
Python imutils 填充图片周边为黑色的实现
2020/01/19 Python
python实现横向拼接图片
2020/03/23 Python
详解基于Jupyter notebooks采用sklearn库实现多元回归方程编程
2020/03/25 Python
Prometheus开发中间件Exporter过程详解
2020/11/30 Python
CSS3制作皮卡丘动画壁纸的示例
2020/11/02 HTML / CSS
使用SVG实现提示框功能的示例代码
2020/06/05 HTML / CSS
MIXIT官网:俄罗斯最大的化妆品公司之一
2020/01/25 全球购物
中医药大学毕业生自荐信
2013/11/08 职场文书
高校教师思想汇报
2014/01/11 职场文书
纠风工作实施方案
2014/03/15 职场文书
给小学生的新年寄语
2014/04/04 职场文书
甜品店创业计划书
2014/09/21 职场文书
2015年教学工作总结
2015/04/02 职场文书
2015七夕情人节宣传语
2015/07/14 职场文书
Python机器学习之PCA降维算法详解
2021/05/19 Python
利用Python实现Picgo图床工具
2021/11/23 Python