Tetris 俄罗斯方块游戏
文件建立日期: 2020/03/21
最后修订日期: None
相关软件信息:
win10 | Python 3.7.6 | Numpy 1.18.1 | PySimpleGUI 4.16.0 |
说明:本文请随意引用或更改,只须标示出处及作者,作者不保证内容絶对正确无误,如造成任何后果,请自行负责.
标题: Tetris 俄罗斯方块游戏#
写个游戏玩玩….
目标#
- 不同几何形状的碎片从顶部下降
- 在下降过程中,玩家可以横向移动碎片并旋转它们,直到它们接触到底部或降落在之前放置的棋子上
- 掉落的碎片可以加速,一步一步,或一次到底.
- 游戏的目标是使用棋子水平连成完整的一条线,当一条线完成时,它会消失了,放置在上方的方块下降了一级.
- 完成线会授予积分,并且累积一定数量的分数会使玩家上移一个级别.
- 碎片的速度会随着每个级别的增加而增加.
- 一次清除越多行积分会越高.
- 四个小方块组成的所有连接在一起的不同形状碎片,共有七种.
游戏画面#
代码及说明#
导入的库
import numpy as np import PySimpleGUI as sg import random
- 方块的类建立
方块图形:七种方块以各小块相对坐标来定义.
class Game(): def __init__(self): self.pixel = np.array( [[[[0,0], [0,1], [0,2], [0,3]], [[0,0], [1,0], [2,0], [3,0]], # ____ [[0,0], [0,1], [0,2], [0,3]], [[0,0], [1,0], [2,0], [3,0]]], [[[0,0], [0,1], [1,0], [1,1]], [[0,0], [0,1], [1,0], [1,1]], # 田 [[0,0], [0,1], [1,0], [1,1]], [[0,0], [0,1], [1,0], [1,1]]], [[[0,1], [1,1], [2,1], [2,0]], [[0,0], [1,0], [1,1], [1,2]], # ▁▁│ [[0,1], [0,0], [1,0], [2,0]], [[0,0], [0,1], [0,2], [1,2]]], [[[0,0], [0,1], [1,1], [2,1]], [[1,0], [0,0], [0,1], [0,2]], # │▁▁ [[0,0], [1,0], [2,0], [2,1]], [[1,0], [1,1], [1,2], [0,2]]], [[[0,1], [1,1], [2,1], [1,0]], [[0,0], [0,1], [0,2], [1,1]], # ▁│▁ [[0,0], [1,0], [2,0], [1,1]], [[1,0], [1,1], [1,2], [0,1]]], [[[0,0], [1,0], [1,1], [2,1]], [[1,0], [1,1], [0,1], [0,2]], # ▔│▁ [[0,0], [1,0], [1,1], [2,1]], [[1,0], [1,1], [0,1], [0,2]]], [[[0,1], [1,1], [1,0], [2,0]], [[0,0], [0,1], [1,1], [1,2]], # ▁│▔ [[0,1], [1,1], [1,0], [2,0]], [[0,0], [0,1], [1,1], [1,2]]]])
游戏旗标及参数
包含格数的度及高度,方块的种类数目,游戏起始速度,方块起始位置,方块消去分数,游戏状态 (游戏结束,暂停), 方块是否存在等等旗标及参数.
其中字体大小主要用来定义方块的大小,因为每个小方块都是文字框,只是背景色不一样.
self.width, self.height = 10, 20 self.start_x, self.start_y = 4, 0 self.kind,self.axis, self.timer = 7, 0, 100 self.font, self.background = '微软正黑体 24', 'gray' self.pause, self.stop, self.no_blcok = False, True, True self.block = [] self.lines, self.score, self.level, self.count = 0, 0, 0, 0 self.plus = [0, 100, 200, 400, 800]
游戏记录参数
记录每个位置是否被占用及其颜色,定义每种方块的频色,每种方块出现的机率,以及按键和按钮的对应函数.
self.area = np.zeros((self.height, self.width)) self.cont = np.full((self.height, self.width), 7) self.rate = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 5, 6, 6, 6] self.colors = ['gold', 'blue', 'tomato', 'red', 'green', 'purple', 'brown', self.background] self.func = {'New' :self.new, 'Pause' :self.wait, 'Left:37' :self.left, 'Up:38' :self.rotate, 'Right:39' :self.right, 'Down:40':self.down, 'Escape:27':self.wait, ' ' :self.space}
游戏类的方法及函数
- 游戏起始的展示画面,七种方块旋转动态展示.
def blocks(self): self.count += 1 if self.count == 50: self.count = 0 old_axis = self.axis-1 if self.axis>0 else 3 for kind in range(self.kind): self.block = [kind, old_axis, (kind%2)*5+1, (kind//2)*5+1] self.draw(show=False) self.block = [kind, self.axis, (kind%2)*5+1, (kind//2)*5+1] self.draw() self.axis = self.axis+1 if self.axis<3 else 0
检查方块连线
如果每一列的占用总数等于游戏画面的寛度,代表一连线,每一连线删除并下移上方所有的画面.
按消去的列总数,取得分数,更新总分及游戏等级,并调整方块自动下落的速度.
def check(self): count = 0 if not self.no_block: self.draw(show=False) for y in range(self.height-1, -1, -1): if np.sum(self.area[y]) == self.width: count += 1 self.area[1:y+1], self.area[0] = self.area[0:y], 0 self.cont[1:y+1], self.cont[0] = self.cont[0:y], 7 for z in range(0, y+1): for x in range(self.width): window.find_element(str(x+10*z)).Update( background_color=self.colors[self.cont[z, x]]) if not self.no_block: self.draw() self.score += min(self.plus[count], 999999) self.lines += count self.level = int(self.lines//2) self.timer = int(100-self.level) if self.level<100 else 1 window.find_element('Score').Update(value='{:0>6d}'.format(self.score)) window.find_element('Level').Update(value='{:0>2d}'.format(self.level))
清除画面
更改所有格子的被占用记录为 0, 设置所有格子的颜色为游戏背景色
def clear(self): self.area = np.zeros((self.height, self.width)) for i in range(self.width*self.height): window.find_element(str(i)).Update( background_color=self.background)
方块下降一格
先取得下降一格的位置,再认该位置是否没被占用或出界,如果被占用或出界,不可下移,而且设置该方块不再存在,并设置该方块所有的格子位置被占用.
def down(self): kind, axis, x, y = self.block new_block = [kind, axis, x, y+1] if self.ok(new_block): self.block = new_block return True else: self.no_block = True data = self.get_font(self.block)+self.block[2:] xi, yi = data[:,0], data[:,1] self.area[yi, xi] = 1 return False
显示方块或不显示方块
以改变文本框的方式来达成
def draw(self, show=True): color = self.colors[self.block[0]] if show else self.background for x, y in self.get_font(self.block)+self.block[2:]: window.find_element(str(x+10*y)).Update(background_color=color) self.cont[y, x] = self.colors.index(color)
- 取得方块的图形数据
def get_font(self, block): return self.pixel[block[0], block[1]]
左键左移处理
先取得左移一格的位置,再认该位置是否没被占用或出界,如果被占用或出界,不可左移
def left(self): kind, axis, x, y = self.block new_block = [kind, axis, x-1 if x>0 else x, y] if self.ok(new_block): self.block = new_block return True return False
建立新的方块
新的方块如果被占用,游戏结束
def new_block(self): self.block = [self.random(), 0, int(self.width/2-1), 0] self.draw(self.block) if self.ok(self.block): self.no_block = False self.count = 0 else: self.stop = True sg.popup("Game Over", no_titlebar=True, font=self.font)
- 游戏开始
def new(self): self.pause = False self.no_block = True self.stop = False self.clear()
- 调整方块的位置计算函数
def offset(self, x_limit, x_max, x_min): return x_max-x_limit+1 if x_max>x_limit-1 else x_min if x_min<0 else 0
- 检查方块的位置是否出界或被占用
def ok(self, block): if block[3] >= self.height: return False data = self.get_font(block)+block[2:] if not(np.max(data[:,0])<self.width and np.min(data[:,0])>-1 and np.max(data[:,1])<self.height and np.min(data[:,1])>-1): return False x, y = data[:,0], data[:,1] if np.sum(self.area[y, x]) != 0: return False return True
按提供的样品空间,随机选择方块的类别
该空间类别出现的次数越多,在游戏中出现的机率越高
def random(self): return random.choice(self.rate)
右键右移处理
先取得右移一格的位置,再认该位置是否没被占用或出界,如果被占用或出界,不可右移
def right(self): kind, axis, x, y = self.block new_block = [kind, axis, x+1 if x<self.width-1 else x, y] if self.ok(new_block): self.block = new_block return True return False
上键旋转处理
旋转时,必须考虑方块是否会出界或被占用,所以必须以旋转的方块先调整位置,再确认是否可移到该位置.
def rotate(self): kind, axis, x, y = self.block new_block = [kind, (axis+1)%4, x, y] data=self.get_font(new_block)+[x, y] x_max, x_min = np.max(data[:,0]), np.min(data[:,0]) y_max, y_min = np.max(data[:,1]), np.min(data[:,1]) x_offset = self.offset(self.width, x_max, x_min) y_offset = self.offset(self.height, y_max, y_min) new_block[2:]= [x-x_offset, y-y_offset] if self.ok(new_block): self.block = new_block return True return False
空格键快速下落
连续一步一步的下移,直到会出界或被占用.
def space(self): while True: self.draw(show=False) stop = not self.down() self.draw() if stop: break
- 动作前后更新方块
def update(self, func): self.draw(show=False) func() self.draw()
- 暂停按钮或按键处理
def wait(self): self.pause = not self.pause
- GUI 界面
方块文字框
def T(key, text=None, color='white', size=(None, None)): if text == None: text = ' '*5 return sg.Text(text, key=key, pad=(1,1), size=size, justification='center', font=G.font, background_color=G.background, text_color=color)
一般文字框
def M(text, bg='green'): return sg.Text(text, font=G.font, size=(10, 1), justification='center', background_color=bg, text_color='white')
按钮对象
def B(text, key): return sg.Button(text, font=G.font, size=(10, 1), key=key, bind_return_key=False, focus=False)
游戏类实例化
G = Game()
游戏区配置
layout1 = [[T(str(i+j*G.width), color=G.background) for i in range(G.width)] for j in range(G.height)]
讯息区配置 (分数,等级,游戏说明,按键)
layout2 = [[M('分数')], [T('Score', text='000000', size=(10, 1))], [M('', bg=G.background)], [M('等级')], [T('Level', text='00', size=(10, 1))], [M('', bg=G.background)], [M('左键:左移')], [M('右键:右移')], [M('下键:下移')], [M('上键:旋转')], [M('空格键:落下')], [M('ESC键:暂停')], [M('', bg=G.background)], [M(' ', bg=G.background)], [B('游戏开始', 'New')], [B('游戏暂停', 'Pause')], [B('游戏结束', 'Over')]]
整体游戏 GUI 配置
frame1 = sg.Frame('', layout=layout1, background_color=G.background, border_width=5) frame2 = sg.Frame('', layout=layout2, background_color=G.background, border_width=5, size=(None, G.height*32)) layout = [[frame1, frame2]] window = sg.Window('Tetris 俄罗斯方块', layout=layout, finalize=True, use_default_focus=False, return_keyboard_events=True, background_color=G.background)
事件处理及游戏循环处理
blocks = True while True: event, values = window.read(timeout=10) if event in [None, 'Over']: break elif event in ['New', 'Pause', 'Escape:27']: blocks = False G.func[event]() continue if blocks: G.blocks() continue if G.pause or G.stop: continue if G.no_block: G.new_block() else: if event in G.func: G.update(G.func[event]) G.count += 1 if G.count == G.timer: G.count = 0 G.update(G.down) G.check() window.close()
本作品采用《CC 协议》,转载必须注明作者和本文链接
不用 tkinter 了吗
tkinter 变化多端,不好驾驭,而且一点一滴都要自己来,不像 PySimpleGUI 简单多了,虽然会到很多约束.
另外,在我的 IDLE (Pyscripter) 下,tkinter 一出错,老是要重启. PySimpleGUI 就比较不会有这样的问题.
GUI 只是辅助,简单好用就行,除非要作商用软件.
看起里写的好累呀~