几天前我第一次做第一版的时候本打算用矩阵来模拟积木的旋转和碰撞,但最后做出了一堆bug。这一次我用了最简单粗暴的方法:纯粹面向对象编程。游戏的大部分功能都实现了,但是一次消除多个不连续的行时还是会出bug。全部代码都放在这里了,希望哪位大佬能给我点提示吧。 我是必须要及时止损了。
#-*-coding: utf-8 -*-
import pygame, random, time, sys, copy
import numpy as np
# 用 (x, y) 坐标表示每个小方块相对于“旋转中心”的位置
# 1-4表示顺时针旋转
L1 = [(0,1),(0,0),(0,-1),(1,-1)]
L2 = [(0,0), (-1,0), (-1,-1), (1,0)]
L3 = [(0,0), (-1,1), (0,1), (0,-1)]
L4 = [(0,0), (-1,0), (1,0), (1,1)]
J1 = [(0,0), (0,1), (0,-1), (-1,-1)]
J2 = [(0,0), (-1,1), (-1,0), (1,0)]
J3 = [(0,0), (0,1), (1,1), (0,-1)]
J4 = [(0,0), (-1,0), (1,0), (1,-1)]
O = [(0,0), (1,0), (0,-1), (1,-1)]
T1 = [(0,0), (-1,0), (1,0), (0,1)]
T2 = [(0,0), (0,1), (1,0), (0,-1)]
T3 = [(0,0), (-1,0), (0,-1), (1,0)]
T4 = [(0,0), (0,-1), (0,1), (-1,0)]
I1 = [(0,0), (0,1), (0,-1), (0,-2)]
I2 = [(0,0), (-2,0), (-1,0), (1,0)]
Z1 = [(0,0), (-1,1), (0,1), (1,0)]
Z2 = [(0,0), (0,-1), (1,0), (1,1)]
S1 = [(0,0), (-1,0), (0,1), (1,1)]
S2 = [(0,0), (0,1), (1,0), (1,-1)]
class Timer:
def __init__(self, interval):
self.interval = interval
self.begin_time = time.time()
def release(self):
current_time = time.time()
if current_time - self.begin_time >= self.interval:
self.begin_time = current_time
return True
else:
return False
class BuildingBlock:
def __init__(self, screen, size, blocks):
self.screen = screen
self.size = size
self.shape = random.choice([J1, L1, T1, Z1, S1, O, I1])
self.other_blocks = blocks
# 积木出现的(轴心)位置,底边接触屏幕顶端
self.axis = pygame.Rect(self.size * 4, self.size * (min([y for x,y in self.shape]) - 1), self.size, self.size)
# 四个小方块
self.rects = [pygame.Rect(left * self.size + self.axis.x, -top * self.size + self.axis.y, self.size, self.size) for left, top in self.shape]
# 边界
self.h_lim = self.screen.get_width()
self.v_lim = self.screen.get_height()
# 三个变量表示是否与其他积木相撞
self.left_collision = False
self.right_collision = False
self.bottom_collision = False
self.colour = random.choice(["blue", "green", "pink", "purple", "yellow", "red", "orange"])
self.movable = True
def move(self, direction):
self.collide_other_blocks()
if direction == "up":
self.rotate()
# 向非碰撞方向移动时,碰撞布尔变量归零
elif direction == "left":
if self.check_horizional_collision() == "left collision" or self.left_collision:return
self.right_collision = False
self.axis.x -= self.size
elif direction == "right":
if self.check_horizional_collision() == "right collision" or self.right_collision:return
self.left_collision = False
self.axis.x += self.size
elif direction == "down":
if self.check_vertical_collision() or self.bottom_collision:return
self.left_collision, self.right_collision = False, False
self.axis.y += self.size
# 更新小方块的位置
self.update_rects()
self.collide_other_blocks()
# 最笨的方法就是最好的方法
def rotate(self):
if self.shape == L1:self.shape = L2
elif self.shape == L2:self.shape = L3
elif self.shape == L3:self.shape = L4
elif self.shape == L4:self.shape = L1
elif self.shape == J1:self.shape = J2
elif self.shape == J2:self.shape = J3
elif self.shape == J3:self.shape = J4
elif self.shape == J4:self.shape = J1
elif self.shape == T1:self.shape = T2
elif self.shape == T2:self.shape = T3
elif self.shape == T3:self.shape = T4
elif self.shape == T4:self.shape = T1
elif self.shape == Z1:self.shape = Z2
elif self.shape == Z2:self.shape = Z1
elif self.shape == S1:self.shape = S2
elif self.shape == S2:self.shape = S1
elif self.shape == I1:self.shape = I2
elif self.shape == I2:self.shape = I1
elif self.shape == O:pass
def update_rects(self):
for i in range(len(self.rects)):
x = self.shape[i][0] * self.size + self.axis.x
y = self.shape[i][1] * self.size * -1 + self.axis.y
self.rects[i].topleft = (x, y)
# 如果积木越过边界,递归更新位置
for rect in self.rects:
if rect.left < 0:
self.axis.x += self.size
self.update_rects()
break
elif rect.right > self.h_lim:
self.axis.x -= self.size
self.update_rects()
break
elif rect.bottom > self.v_lim:
self.axis.y -= self.size
self.update_rects()
break
# 检测与屏幕边界的碰撞
def check_vertical_collision(self):
for rect in self.rects:
if rect.bottom == self.v_lim:
self.movable = False
return True
def check_horizional_collision(self):
for rect in self.rects:
if rect.left == 0:
return "left collision"
elif rect.right == self.h_lim:
return "right collision"
# 检测与其他积木的碰撞
def collide_other_blocks(self):
for rect in self.rects:
# 自身以外的其他积木
for block in self.other_blocks[:-1]:
for m in block.rects:
if rect.right == m.left and rect.top == m.top:
self.right_collision = True
if rect.left == m.right and rect.top == m.top:
self.left_collision = True
if rect.bottom == m.top and rect.left == m.left:
self.movable = False
self.bottom_collision = True
# print(self.left_collision, self.bottom_collision)
def draw(self):
for rect in self.rects:
# 由白色边框环绕的实心矩形
pygame.draw.rect(self.screen, self.colour, rect, 0)
pygame.draw.rect(self.screen, "white", rect, 2)
class Tetris:
def __init__(self):
# 如果不先将pygame初始化,则字体无法初始化
pygame.init()
self.cols = 10
self.rows = 18
self.unit = 40
self.window = pygame.display.set_mode((self.cols * self.unit, self.rows * self.unit))
pygame.display.set_caption("俄罗斯方块")
self.blocks = []
# 正在移动状态的积木
self.current_block = BuildingBlock(self.window, self.unit, self.blocks)
self.blocks.append(self.current_block)
# 分数
self.score = 0
# 计时器
self.timer = Timer(0.5)
self.finish = False
# 暂停
self.timeout = False
def run(self):
while True:
self.check_events()
if self.timeout:continue
if not self.finish:
self.update()
self.draw()
def check_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.current_block.move("left")
elif event.key == pygame.K_RIGHT:
self.current_block.move("right")
elif event.key == pygame.K_UP:
self.current_block.move("up")
elif event.key == pygame.K_DOWN:
self.current_block.move("down")
# 按T键暂停
elif event.key == pygame.K_t:
self.timeout = not self.timeout
# 消除连线上的小方块
def emit_rows(self):
# 本次是否有方块消除?
# 将所有小方块按行数存入列表
# warning: 不能用[[]]*self.cols, python会自动用append填满所有子列表
rows = [[] for i in range(self.rows)]
for block in self.blocks:
for rect in block.rects:
i = rect.y // self.unit
rows[i].append(rect)
# 要消除的层
layers = []
for index, row in enumerate(rows):
if len(row) == self.cols:
layers.append(index)
# 在for循环里更改列表会影响迭代结果,所以要用复制的列表取代原列表
for block in self.blocks:
rects = copy.copy(block.rects)
for rect in block.rects:
if rect.y//self.unit in layers:
rects.remove(rect)
block.rects = rects
# 更新分数
self.score += self.cols * len(layers)
# 将消除层上方的方块下移(反正它们也不用再调用move()了)
if layers:
self.update_all_rects(layers)
def update_all_rects(self, layers):
for block in self.blocks:
for rect in block.rects:
# 如果方块在被消除的最低行之上
if rect.top//self.unit < max(layers):
rect.top += len(layers) * self.unit
def show_score(self):
font = pygame.font.SysFont("FangSong", 30)
text = font.render(f"{self.score}", True, "white", None)
self.window.blit(text, (self.window.get_width() - 50, 10))
def game_over(self):
# 可移动方块触底之后,新方块创建之前检测游戏是否结束
for rect in self.blocks[-1].rects:
if rect.bottom == 0:
return True
def show_over(self):
font = pygame.font.SysFont("HeiSim", 100)
text = font.render("Game Over", True, "white", None)
self.window.blit(text, (20, self.window.get_height()/2 - 50))
def update(self):
if self.timer.release():
self.current_block.move("down")
if self.current_block.movable == False:
if self.game_over():
self.finish = True
return
self.emit_rows()
# 积木触底,创建新积木
self.current_block = BuildingBlock(self.window, self.unit, self.blocks)
self.blocks.append(self.current_block)
def draw(self):
self.window.fill("black")
for b in self.blocks:
b.draw()
self.show_score()
if self.finish:self.show_over()
pygame.display.flip()
game = Tetris()
game.run()
轻映录屏 2022-07-05 20-56-10