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进行基础的函数式编程的教程
Mar 31 Python
在Python中操作文件之read()方法的使用教程
May 24 Python
全面了解Python环境配置及项目建立
Jun 30 Python
你所不知道的Python奇技淫巧13招【实用】
Dec 14 Python
Django 前后台的数据传递的方法
Aug 08 Python
Python 加密的实例详解
Oct 09 Python
漂亮的Django Markdown富文本app插件的实现
Jan 02 Python
numpy ndarray 取出满足特定条件的某些行实例
Dec 05 Python
python生成任意频率正弦波方式
Feb 25 Python
Python Merge函数原理及用法解析
Sep 16 Python
PyCharm 2020.2下配置Anaconda环境的方法步骤
Sep 23 Python
python实现学员管理系统(面向对象版)
Jun 05 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
php+mysql写的简单留言本实例代码
2008/07/25 PHP
PHP获取指定时间段之间的 年,月,天,时,分,秒
2016/06/05 PHP
PHP使用FFmpeg获取视频播放总时长与码率等信息
2016/09/13 PHP
用PHP的反射实现委托模式的讲解
2019/03/22 PHP
JS高级笔记
2011/07/13 Javascript
jQuery中bind()方法用法实例
2015/01/19 Javascript
JavaScript创建闭包的两种方式的优劣与区别分析
2015/06/22 Javascript
Webpack 实现 Node.js 代码热替换
2015/10/22 Javascript
Angular2从搭建环境到开发步骤详解
2016/10/17 Javascript
微信小程序授权获取用户详细信息openid的实例详解
2017/09/20 Javascript
layui中使用jquery控制radio选中事件的示例代码
2018/08/15 jQuery
详解vue2.0模拟后台json数据
2019/05/16 Javascript
layui table设置某一行的字体颜色方法
2019/09/05 Javascript
JS实现鼠标移动拖尾
2020/12/27 Javascript
python3.3使用tkinter开发猜数字游戏示例
2014/03/14 Python
Python中matplotlib中文乱码解决办法
2017/05/12 Python
浅谈python内置变量-reversed(seq)
2017/06/21 Python
老生常谈Python startswith()函数与endswith函数
2017/09/08 Python
Python拼接微信好友头像大图的实现方法
2018/08/01 Python
Python+OpenCV+pyQt5录制双目摄像头视频的实例
2019/06/28 Python
使用批处理脚本自动生成并上传NuGet包(操作方法)
2019/11/19 Python
Python中低维数组填充高维数组的实现
2019/12/02 Python
Django框架配置mysql数据库实现过程
2020/04/22 Python
python如何对链表操作
2020/10/10 Python
详解python算法常用技巧与内置库
2020/10/17 Python
Python实现Kerberos用户的增删改查操作
2020/12/14 Python
丝芙兰美国官网:SEPHORA美国
2016/08/03 全球购物
新西兰便宜隐形眼镜购买网站:QUICKLENS New Zealand
2019/03/02 全球购物
J2EE面试题
2016/03/14 面试题
《爱如茉莉》教后反思
2014/04/12 职场文书
村当支部个人对照检查材料思想汇报
2014/10/06 职场文书
音乐教师求职信范文
2015/03/20 职场文书
教师评职称工作总结2015
2015/04/20 职场文书
毕业班工作总结
2015/08/10 职场文书
演讲开头怎么书写?
2019/08/06 职场文书
gtx1650怎么样 gtx1650显卡相当于什么级别
2022/04/08 数码科技