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重新引入被覆盖的自带function
Jul 16 Python
Python中random模块生成随机数详解
Mar 10 Python
Python标准库sched模块使用指南
Jul 06 Python
Tensorflow之构建自己的图片数据集TFrecords的方法
Feb 07 Python
python3实现随机数
Jun 25 Python
pandas DataFrame创建方法的方式
Aug 02 Python
python multiprocessing模块用法及原理介绍
Aug 20 Python
详解centos7+django+python3+mysql+阿里云部署项目全流程
Nov 15 Python
Python序列类型的打包和解包实例
Dec 21 Python
python如何输出反斜杠
Jun 18 Python
django rest framework 自定义返回方式
Jul 12 Python
python中字典增加和删除使用方法
Sep 30 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
【动漫杂谈】关于《请在T台上微笑》
2020/03/03 日漫
为百度UE编辑器上传图片添加水印功能
2015/04/16 PHP
php删除数组中重复元素的方法
2015/12/22 PHP
php使用QueryList轻松采集js动态渲染页面方法
2018/09/11 PHP
laravel withCount 统计关联数量的方法
2019/10/10 PHP
jquery.blockUI.js上传滚动等待效果实现思路及代码
2013/03/18 Javascript
JavaScript代码简单实现求杨辉三角给定行的最大值
2013/10/29 Javascript
javascript客户端遍历控件与获取父容器对象示例代码
2014/01/06 Javascript
js实现div闪烁原理及实现代码
2014/06/24 Javascript
Javascript访问器属性实例分析
2014/12/30 Javascript
JavaScript控制两个列表框listbox左右交换数据的方法
2015/03/18 Javascript
JavaScript实现梯形乘法表的方法
2015/04/25 Javascript
详细分析JavaScript变量类型
2015/07/08 Javascript
jQuery zclip插件实现跨浏览器复制功能
2015/11/02 Javascript
JS模拟bootstrap下拉菜单效果实例
2016/06/17 Javascript
JS克隆,属性,数组,对象,函数实例分析
2016/11/26 Javascript
利用jQuery来动态为属性添加或者删除属性的简单方法
2016/12/02 Javascript
简单谈谈axios中的get,post方法
2017/06/25 Javascript
微信小程序支付功能 php后台对接完整代码分享
2018/06/12 Javascript
python生成日历实例解析
2014/08/21 Python
使用Python从有道词典网页获取单词翻译
2016/07/03 Python
Django框架实现的分页demo示例
2019/05/25 Python
flask框架配置mysql数据库操作详解
2019/11/29 Python
在Python 的线程中运行协程的方法
2020/02/24 Python
Django添加bootstrap框架时无法加载静态文件的解决方式
2020/03/27 Python
如何在python中处理配置文件代码实例
2020/09/27 Python
会计专业应届生求职信
2013/11/24 职场文书
幼儿园感恩节活动方案2014
2014/10/11 职场文书
2014年文员工作总结
2014/11/18 职场文书
婚礼新人答谢词
2015/01/04 职场文书
市场营销计划书范文
2015/01/16 职场文书
刑事申诉状范文
2015/05/20 职场文书
毕业答辩开场白范文
2015/05/27 职场文书
邹越演讲观后感
2015/06/15 职场文书
使用pytorch实现线性回归
2021/04/11 Python
分析mysql中一条SQL查询语句是如何执行的
2021/06/21 MySQL