前言
开始
献上代码
from tkinter.messagebox import showinfo as msg
from pygame.locals import *
import tkinter as tk
import pygame
import random
import time
win = tk.Tk()
win.withdraw()
pygame.init()
def render(surf,blocks):
for x,y in blocks.keys():
renderingf = (x * 40,y * 40)
color = blocks[(x,y)]
pygame.draw.rect(surf,color,Rect(renderingf,(40,40)))
class tetris_block:
def __init__(self):
self.block = {}
self.pos = (0,0)
self.disable = False
def rand_block(self,s,w,h):
O = {(0, 0): (0, 255, 0), (0, 1): (0, 255, 0), (1, 0): (0, 255, 0), (1, 1): (0, 255, 0)}
I = {(0, 0): (0, 0, 255), (1, 0): (0, 0, 255), (2, 0): (0, 0, 255), (3, 0): (0, 0, 255)}
S = {(0, 1): (255, 0, 0), (0, 2): (255, 0, 0), (1, 0): (255, 0, 0), (1, 1): (255, 0, 0)}
Z = {(0, 0): (255, 0, 255), (0, 1): (255, 0, 255), (1, 1): (255, 0, 255), (1, 2): (255, 0, 255)}
L = {(0, 0): (255, 255, 255), (0, 1): (255, 255, 255), (0, 2): (255, 255, 255), (1, 0): (255, 255, 255)}
J = {(0, 0): (255, 255, 0), (0, 1): (255, 255, 0), (0, 2): (255, 255, 0), (1, 2): (255, 255, 0)}
T = {(0, 0): (0, 255, 255), (0, 1): (0, 255, 255), (0, 2): (0, 255, 255), (1, 1): (0, 255, 255)}
self.block = random.choice([O,I,S,Z,L,J,T])
ma = w - 1 - max(map(lambda it:it[1],list(self.block.keys())))
if(ma < 0):
ma = 0
self.move(random.randint(0,ma),0,w,h)
def get_block(self):
res = {}
for x,y in self.block.keys():
res[(x + self.pos[0],y + self.pos[1])] = self.block[(x,y)]
return res
def rotate(self,W,H):
if(self.disable):return
size = [max(map(lambda it:it[0],list(self.block.keys()))),
max(map(lambda it:it[1],list(self.block.keys())))]
rotMat = [[0 for j in range(size[0] + 1)] for i in range(size[1] + 1)]
c = self.block[list(self.block.keys())[0]]
for i in self.block.keys():
rotMat[i[1]][i[0]] = 1
rotMat = rotMat[::-1]
h,w = len(rotMat),len(rotMat[0])
mx,my = w + self.pos[0],h + self.pos[1]
if(mx >= W or my >= H or mx < 0 or my < 0):
return
self.block = {}
for i in range(len(rotMat)):
for j in range(len(rotMat[i])):
if(rotMat[i][j]):
self.block[(i,j)] = c
def intersect(self,rhs):
b1 = self.get_block()
b2 = rhs.get_block()
if(set(b1) & set(b2)):
return True
else:
return False
def any_intersect(self,ls):
for i in ls:
if(self.intersect(i)):
return True
return False
def landed(self,s):
if(self.disable):return True
p = self.pos[1] + max(map(lambda it:it[1],list(self.block.keys()))) + 1
return p > s
def flying(self):
if(self.disable):return False
return self.pos[1] <= 0
def move(self,x,y,w,h):
if(self.disable):return
if(self.pos[0] + x + max(map(lambda it:it[0],list(self.block.keys()))) + 1 > w or self.pos[0] + x < 0):
return
self.pos = (self.pos[0] + x,
self.pos[1] + y)
def removeline(self,l):
if(self.disable):return
new = dict(self.block)
for x,y in self.block:
if(y + self.pos[1] == l):
new.pop((x,y))
proc = lambda it:(it[0],it[0]) if(it[0][1] > l) else (it[0],(it[0][0],it[0][1] + 1))
upd = dict(dict(map(proc,new.items())))
res = {}
for i in upd:
v = new[i]
res[upd[i]] = v
self.block = dict(res)
if(not new):
self.disable = True
def main(size):
scr = pygame.display.set_mode((40 * size[0],40 * size[1]))
blk_list = []
n = 1
speed = 15
s = 0
stop = False
while(1):
if(not stop):
d = {}
for i in blk_list:
d.update(i.get_block())
for i in range(size[1]):
flag = True
for j in range(size[0]):
if((j,i) not in d):
flag = False
if(flag):
s += 5
for j in blk_list:
j.removeline(i)
for i in blk_list[:-1]:
if(i.flying()):
return s
pygame.display.update()
scr.fill((0,0,0))
n += 1
n %= speed
if((not n) and (not stop)):
if((not blk_list) or blk_list[-1].landed(size[1]) or blk_list[-1].any_intersect(blk_list[:-1])):
if(blk_list):
blk_list[-1].move(0,-1,*size)
b = tetris_block()
b.rand_block(size[0],*size)
blk_list.append(b)
else:
blk_list[-1].move(0,1,*size)
for ev in pygame.event.get():
if(ev.type == QUIT):
exit()
elif(ev.type == KEYDOWN and ev.key == K_LEFT and blk_list):
blk_list[-1].move(-1,0,*size)
if(blk_list[-1].any_intersect(blk_list[:-1])):
blk_list[-1].move(1,0,*size)
elif(ev.type == KEYDOWN and ev.key == K_RIGHT and blk_list):
blk_list[-1].move(1,0,*size)
if(blk_list[-1].any_intersect(blk_list[:-1])):
blk_list[-1].move(-1,0,*size)
elif(ev.type == MOUSEBUTTONDOWN):
stop = not stop
elif(ev.type == KEYDOWN and ev.key in [K_LSHIFT,K_RSHIFT] and blk_list):
blk_list[-1].rotate(*size)
elif(ev.type == KEYDOWN and ev.key == K_ESCAPE and stop):
return s
elif(ev.type == KEYDOWN and ev.unicode == ' ' and blk_list and (not stop)):
while(not (blk_list[-1].landed(size[1]) or blk_list[-1].any_intersect(blk_list[:-1]))):
blk_list[-1].move(0,1,*size)
blk_list[-1].move(0,-1,*size)
b = tetris_block()
b.rand_block(size[0],*size)
blk_list.append(b)
if(stop):
img = pygame.image.load('pause.jpg')
img = pygame.transform.scale(img,(40 * size[0],40 * size[1]))
scr.blit(img,(0,0))
else:
for blk in blk_list:
render(scr,blk.get_block())
if(__name__ == '__main__'):
size = [15,15]
while(1):
s = main(size)
msg('GAMEOVER!','GAMEOVER! score:%s'%s)
代码剖析
from tkinter.messagebox import showinfo as msg
from pygame.locals import *
import tkinter as tk
import pygame
import random
import time
win = tk.Tk()
win.withdraw()
pygame.init()
导入库及初始化
def render(surf,blocks):
for x,y in blocks.keys():
renderingf = (x * 40,y * 40)
color = blocks[(x,y)]
pygame.draw.rect(surf,color,Rect(renderingf,(40,40)))
参见《python简易贪吃蛇》中的render
class tetris_block:
def __init__(self):
self.block = {}
self.pos = (0,0)
self.disable = False
def rand_block(self,s,w,h):
O = {(0, 0): (0, 255, 0), (0, 1): (0, 255, 0), (1, 0): (0, 255, 0), (1, 1): (0, 255, 0)}
I = {(0, 0): (0, 0, 255), (1, 0): (0, 0, 255), (2, 0): (0, 0, 255), (3, 0): (0, 0, 255)}
S = {(0, 1): (255, 0, 0), (0, 2): (255, 0, 0), (1, 0): (255, 0, 0), (1, 1): (255, 0, 0)}
Z = {(0, 0): (255, 0, 255), (0, 1): (255, 0, 255), (1, 1): (255, 0, 255), (1, 2): (255, 0, 255)}
L = {(0, 0): (255, 255, 255), (0, 1): (255, 255, 255), (0, 2): (255, 255, 255), (1, 0): (255, 255, 255)}
J = {(0, 0): (255, 255, 0), (0, 1): (255, 255, 0), (0, 2): (255, 255, 0), (1, 2): (255, 255, 0)}
T = {(0, 0): (0, 255, 255), (0, 1): (0, 255, 255), (0, 2): (0, 255, 255), (1, 1): (0, 255, 255)}
self.block = random.choice([O,I,S,Z,L,J,T])
ma = w - 1 - max(map(lambda it:it[1],list(self.block.keys())))
if(ma < 0):
ma = 0
self.move(random.randint(0,ma),0,w,h)
def get_block(self):
res = {}
for x,y in self.block.keys():
res[(x + self.pos[0],y + self.pos[1])] = self.block[(x,y)]
return res
def rotate(self,W,H):
if(self.disable):return
size = [max(map(lambda it:it[0],list(self.block.keys()))),
max(map(lambda it:it[1],list(self.block.keys())))]
rotMat = [[0 for j in range(size[0] + 1)] for i in range(size[1] + 1)]
c = self.block[list(self.block.keys())[0]]
for i in self.block.keys():
rotMat[i[1]][i[0]] = 1
rotMat = rotMat[::-1]
h,w = len(rotMat),len(rotMat[0])
mx,my = w + self.pos[0],h + self.pos[1]
if(mx >= W or my >= H or mx < 0 or my < 0):
return
self.block = {}
for i in range(len(rotMat)):
for j in range(len(rotMat[i])):
if(rotMat[i][j]):
self.block[(i,j)] = c
def intersect(self,rhs):
b1 = self.get_block()
b2 = rhs.get_block()
if(set(b1) & set(b2)):
return True
else:
return False
def any_intersect(self,ls):
for i in ls:
if(self.intersect(i)):
return True
return False
def landed(self,s):
if(self.disable):return True
p = self.pos[1] + max(map(lambda it:it[1],list(self.block.keys()))) + 1
return p > s
def flying(self):
if(self.disable):return False
return self.pos[1] <= 0
def move(self,x,y,w,h):
if(self.disable):return
if(self.pos[0] + x + max(map(lambda it:it[0],list(self.block.keys()))) + 1 > w or self.pos[0] + x < 0):
return
self.pos = (self.pos[0] + x,
self.pos[1] + y)
def removeline(self,l):
if(self.disable):return
new = dict(self.block)
for x,y in self.block:
if(y + self.pos[1] == l):
new.pop((x,y))
proc = lambda it:(it[0],it[0]) if(it[0][1] > l) else (it[0],(it[0][0],it[0][1] + 1))
upd = dict(dict(map(proc,new.items())))
res = {}
for i in upd:
v = new[i]
res[upd[i]] = v
self.block = dict(res)
if(not new):
self.disable = True
分开剖析
def __init__(self):
self.block = {}
self.pos = (0,0)
self.disable = False
初始化函数,tetris_block.block
是方块的原形状(即不算方块位置的),tetris_block.pos
是方块的位置,tetris_block.disable
是指方块是否因为消行而消失,True
为已经消失,反之亦然。
def rand_block(self,s,w,h):
O = {(0, 0): (0, 255, 0), (0, 1): (0, 255, 0), (1, 0): (0, 255, 0), (1, 1): (0, 255, 0)}
I = {(0, 0): (0, 0, 255), (1, 0): (0, 0, 255), (2, 0): (0, 0, 255), (3, 0): (0, 0, 255)}
S = {(0, 1): (255, 0, 0), (0, 2): (255, 0, 0), (1, 0): (255, 0, 0), (1, 1): (255, 0, 0)}
Z = {(0, 0): (255, 0, 255), (0, 1): (255, 0, 255), (1, 1): (255, 0, 255), (1, 2): (255, 0, 255)}
L = {(0, 0): (255, 255, 255), (0, 1): (255, 255, 255), (0, 2): (255, 255, 255), (1, 0): (255, 255, 255)}
J = {(0, 0): (255, 255, 0), (0, 1): (255, 255, 0), (0, 2): (255, 255, 0), (1, 2): (255, 255, 0)}
T = {(0, 0): (0, 255, 255), (0, 1): (0, 255, 255), (0, 2): (0, 255, 255), (1, 1): (0, 255, 255)}
self.block = random.choice([O,I,S,Z,L,J,T])
ma = w - 1 - max(map(lambda it:it[1],list(self.block.keys())))
if(ma < 0):
ma = 0
self.move(random.randint(0,ma),0,w,h)
初始化方块形状及位置。
def get_block(self):
res = {}
for x,y in self.block.keys():
res[(x + self.pos[0],y + self.pos[1])] = self.block[(x,y)]
return res
获取方块形状(即方块原形状加上方块位置)
def rotate(self,W,H):
if(self.disable):return
size = [max(map(lambda it:it[0],list(self.block.keys()))),
max(map(lambda it:it[1],list(self.block.keys())))]
rotMat = [[0 for j in range(size[0] + 1)] for i in range(size[1] + 1)]
c = self.block[list(self.block.keys())[0]]
for i in self.block.keys():
rotMat[i[1]][i[0]] = 1
rotMat = rotMat[::-1]
h,w = len(rotMat),len(rotMat[0])
mx,my = w + self.pos[0],h + self.pos[1]
if(mx >= W or my >= H or mx < 0 or my < 0):
return
self.block = {}
for i in range(len(rotMat)):
for j in range(len(rotMat[i])):
if(rotMat[i][j]):
self.block[(i,j)] = c
方块旋转,见下 ↓ \downarrow ↓
这里使用一种特使的矩阵旋转方法,见下图
另外,方块中小正方形和pygame
中的坐标都是按照 ( x , y ) (x,y) (x,y)的形式存储的,而二维数组中的索引是 ( y , x ) (y,x) (y,x),所以在方块转化的二维数组中的行列本来就是反的,最后不变的将 ( y , x ) (y,x) (y,x)塞到 ( x , y ) (x,y) (x,y)里,就实现了行变列。
def intersect(self,rhs):
b1 = self.get_block()
b2 = rhs.get_block()
if(set(b1) & set(b2)):
return True
else:
return False
检查两个方块是否交叉,将第一个方块转成集合,第二个同样,如果它们中的小正方形坐标有交集,那就交叉了。
def any_intersect(self,ls):
for i in ls:
if(self.intersect(i)):
return True
return False
就是对一堆方块求tetris_block.intersect
(点击跳转)
def landed(self,s):
if(self.disable):return True
p = self.pos[1] + max(map(lambda it:it[1],list(self.block.keys()))) + 1
return p > s
检测方块是否落地
def flying(self):
if(self.disable):return False
return self.pos[1] <= 0
检测是不是已经到顶了
def move(self,x,y,w,h):
if(self.disable):return
if(self.pos[0] + x + max(map(lambda it:it[0],list(self.block.keys()))) + 1 > w or self.pos[0] + x < 0):
return
self.pos = (self.pos[0] + x,
self.pos[1] + y)
在能挪的情况下挪动
def removeline(self,l):
if(self.disable):return
new = dict(self.block)
for x,y in self.block:
if(y + self.pos[1] == l):
new.pop((x,y))
proc = lambda it:(it[0],it[0]) if(it[0][1] > l) else (it[0],(it[0][0],it[0][1] + 1))
upd = dict(dict(map(proc,new.items())))
res = {}
for i in upd:
v = new[i]
res[upd[i]] = v
self.block = dict(res)
if(not new):
self.disable = True
消行,分为两个步骤 ↓ \downarrow ↓
- 去掉方块内的所有在这一行的小正方形
- 该掉下来的掉下来
def main(size):
scr = pygame.display.set_mode((40 * size[0],40 * size[1]))
blk_list = []
n = 1
speed = 15
s = 0
stop = False
while(1):
if(not stop):
d = {}
for i in blk_list:
d.update(i.get_block())
for i in range(size[1]):
flag = True
for j in range(size[0]):
if((j,i) not in d):
flag = False
if(flag):
s += 5
for j in blk_list:
j.removeline(i)
for i in blk_list[:-1]:
if(i.flying()):
return s
pygame.display.update()
scr.fill((0,0,0))
n += 1
n %= speed
if((not n) and (not stop)):
if((not blk_list) or blk_list[-1].landed(size[1]) or blk_list[-1].any_intersect(blk_list[:-1])):
if(blk_list):
blk_list[-1].move(0,-1,*size)
b = tetris_block()
b.rand_block(size[0],*size)
blk_list.append(b)
else:
blk_list[-1].move(0,1,*size)
for ev in pygame.event.get():
if(ev.type == QUIT):
exit()
elif(ev.type == KEYDOWN and ev.key == K_LEFT and blk_list):
blk_list[-1].move(-1,0,*size)
if(blk_list[-1].any_intersect(blk_list[:-1])):
blk_list[-1].move(1,0,*size)
elif(ev.type == KEYDOWN and ev.key == K_RIGHT and blk_list):
blk_list[-1].move(1,0,*size)
if(blk_list[-1].any_intersect(blk_list[:-1])):
blk_list[-1].move(-1,0,*size)
elif(ev.type == MOUSEBUTTONDOWN):
stop = not stop
elif(ev.type == KEYDOWN and ev.key in [K_LSHIFT,K_RSHIFT] and blk_list):
blk_list[-1].rotate(*size)
elif(ev.type == KEYDOWN and ev.key == K_ESCAPE and stop):
return s
elif(ev.type == KEYDOWN and ev.unicode == ' ' and blk_list and (not stop)):
while(not (blk_list[-1].landed(size[1]) or blk_list[-1].any_intersect(blk_list[:-1]))):
blk_list[-1].move(0,1,*size)
blk_list[-1].move(0,-1,*size)
b = tetris_block()
b.rand_block(size[0],*size)
blk_list.append(b)
if(stop):
img = pygame.image.load('pause.jpg')
img = pygame.transform.scale(img,(40 * size[0],40 * size[1]))
scr.blit(img,(0,0))
else:
for blk in blk_list:
render(scr,blk.get_block())
分开剖析
def main(size):
scr = pygame.display.set_mode((40 * size[0],40 * size[1]))
blk_list = []
n = 1
speed = 15
s = 0
stop = False
创建窗口,初始化变量
- 方块列表
- 轮回数
- 速度
- 分数
- 是否暂停
while(1):
if(not stop):
d = {}
for i in blk_list:
d.update(i.get_block())
for i in range(size[1]):
flag = True
for j in range(size[0]):
if((j,i) not in d):
flag = False
if(flag):
s += 5
for j in blk_list:
j.removeline(i)
for i in blk_list[:-1]:
if(i.flying()):
return s
pygame.display.update()
进入游戏循环,处理消行和游戏结束并刷新页面。
pygame.display.update()
scr.fill((0,0,0))
n += 1
n %= speed
if((not n) and (not stop)):
if((not blk_list) or blk_list[-1].landed(size[1]) or blk_list[-1].any_intersect(blk_list[:-1])):
if(blk_list):
blk_list[-1].move(0,-1,*size)
b = tetris_block()
b.rand_block(size[0],*size)
blk_list.append(b)
else:
blk_list[-1].move(0,1,*size)
按照速度将方块向下移动(参见《python简易贪吃蛇》中的方法)
for ev in pygame.event.get():
if(ev.type == QUIT):
exit()
elif(ev.type == KEYDOWN and ev.key == K_LEFT and blk_list):
blk_list[-1].move(-1,0,*size)
if(blk_list[-1].any_intersect(blk_list[:-1])):
blk_list[-1].move(1,0,*size)
elif(ev.type == KEYDOWN and ev.key == K_RIGHT and blk_list):
blk_list[-1].move(1,0,*size)
if(blk_list[-1].any_intersect(blk_list[:-1])):
blk_list[-1].move(-1,0,*size)
elif(ev.type == MOUSEBUTTONDOWN):
stop = not stop
elif(ev.type == KEYDOWN and ev.key in [K_LSHIFT,K_RSHIFT] and blk_list):
blk_list[-1].rotate(*size)
elif(ev.type == KEYDOWN and ev.key == K_ESCAPE and stop):
return s
elif(ev.type == KEYDOWN and ev.unicode == ' ' and blk_list and (not stop)):
while(not (blk_list[-1].landed(size[1]) or blk_list[-1].any_intersect(blk_list[:-1]))):
blk_list[-1].move(0,1,*size)
blk_list[-1].move(0,-1,*size)
b = tetris_block()
b.rand_block(size[0],*size)
blk_list.append(b)
处理事件 ↓ \downarrow ↓
- 点击
x
退出 - 方块向左/向右移动
- 左键开始/暂停
- 暂停时按下
ESC
就再来一局 - 空格快速向下移动
if(stop):
img = pygame.image.load('pause.jpg')
img = pygame.transform.scale(img,(40 * size[0],40 * size[1]))
scr.blit(img,(0,0))
else:
for blk in blk_list:
render(scr,blk.get_block())
如果没有在玩(暂停),显示图片(参见《python简易贪吃蛇》中的暂停图片),否则显示方块
if(__name__ == '__main__'):
size = [15,15]
while(1):
s = main(size)
msg('GAMEOVER!','GAMEOVER! score:%s'%s)
运行显示GAMEOVER
,其中size
是大小(推荐
≤
15
\le15
≤15)
项目github
作者
hit-road
拜拜,下课!
回到顶部