You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python开发俄罗斯方块时方块顶部卡顿重复生成问题求助

俄罗斯方块碰撞检测问题:方块卡顶重复生成无法堆叠

我用Python结合Pygame开发俄罗斯方块,添加方块间碰撞检测后,出现方块卡在顶部并重复生成的问题(如图所示)。当前方块可触底、触边停止,但无法实现方块堆叠效果,调整边界参数要么问题依旧,要么方块失去碰撞检测直接落到底部。

方块卡顶示意图

原完整代码

import pygame
import random

# Define Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
CYAN = (0, 255, 255)

# Define shapes
SHAPES = [
    [[0, 1, 1, 1],
     [0, 0, 1, 0]],
    [[0, 0, 2, 2],
     [0, 2, 2, 0]],
    [[0, 3, 3, 0],
     [0, 0, 3, 3]],
    [[0, 4, 0, 0],
     [0, 4, 4, 4]],
    [[0, 0, 5, 0],
     [0, 5, 5, 5]],
    [[0, 6, 6],
     [0, 6, 6]],
    [[0, 7, 0, 0],
     [0, 7, 7, 7]]
]


# Define shape class
class Shape:
    def __init__(self, shape):
        self.shape = shape
        self.color = random.choice([RED, BLUE, GREEN, YELLOW, ORANGE, CYAN])
        self.x = (WINDOW_WIDTH // 2) // block_size - len(self.shape[0]) // 2
        self.y = 1

    def draw(self, screen, block_size):
        for i in range(len(self.shape)):
            for j in range(len(self.shape[0])):
                if self.shape[i][j] != 0:
                    pygame.draw.rect(screen, self.color, (self.x * block_size + j * block_size, self.y * block_size + i * block_size, block_size, block_size))
                    pygame.draw.rect(screen, 'white', (self.x * block_size + j * block_size, self.y * block_size + i * block_size, block_size, block_size), 1)

    def move_down(self):
        self.y += 1

    def move_up(self):
        self.y -= 1

    def collides_with(self, other):
        shape_width = len(self.shape[0])
        shape_height = len(self.shape)
        for i in range(shape_height):
            for j in range(shape_width):
                if self.shape[i][j] != 0:
                    x_cor, y_cor = self.x + j, self.y + i
                    if y_cor >= len(other.shape) or x_cor < 0 or x_cor >= len(other.shape[0]) \
                            or (other.shape[y_cor][x_cor] != 0):
                        return True
        return False


# Initialize Pygame
pygame.init()

# Set up window
WINDOW_WIDTH = 220
WINDOW_HEIGHT = 500
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Tetris')

# Set up clock
clock = pygame.time.Clock()

# Set up game variables
block_size = 20
shapes = []
current_shape = Shape(random.choice(SHAPES))

# Game loop
while True:
    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] and current_shape.x > 0:
        current_shape.x -= 1
    if keys[pygame.K_RIGHT] and current_shape.x + len(current_shape.shape[0]) < 200 / block_size:
        current_shape.x += 1

    # Move current shape down
    current_shape.move_down()

    for shape in shapes:
        if current_shape.collides_with(shape):
            current_shape.move_up()
            if current_shape.y <= 0:
                pygame.quit()
                quit()
            else:
                shapes.append(current_shape)
                current_shape = Shape(random.choice(SHAPES))
            break

    # Check if current shape has collided with another shape or reached the bottom
    if current_shape.y + len(current_shape.shape) >= 24:
        shapes.append(current_shape)
        current_shape = Shape(random.choice(SHAPES))

    # Draw current shape and all other shapes on screen
    screen.fill('dark gray')

    for x in range(1, 10):
        for y in range(1, 24):
            # First creates square with gray color. Second draws black border for square
            pygame.draw.rect(screen, 'light gray', (10 * x * 2, 10 * y * 2, 20, 20), 0)
            pygame.draw.rect(screen, 'black', (10 * x * 2, 10 * y * 2, 20, 20), 1)

    for shape in shapes:
        shape.draw(screen, block_size)

    current_shape.draw(screen, block_size)
    pygame.display.update()

    # Tick clock
    clock.tick(10)

核心碰撞检测代码(原错误版本)

def collides_with(self, other):
    shape_width = len(self.shape[0])
    shape_height = len(self.shape)
    for i in range(shape_height):
        for j in range(shape_width):
            if self.shape[i][j] != 0:
                x_cor, y_cor = self.x + j, self.y + i
                if y_cor >= len(other.shape) or x_cor < 0 or x_cor >= len(other.shape[0]) \
                        or (other.shape[y_cor][x_cor] != 0):
                    return True
    return False

for shape in shapes:
    if current_shape.collides_with(shape):
        current_shape.move_up()
        if current_shape.y <= 0:
            pygame.quit()
            quit()
        else:
            shapes.append(current_shape)
            current_shape = Shape(random.choice(SHAPES))
        break

问题根源与修正方案

核心问题

  1. 碰撞检测逻辑错误:原方法拿当前形状和单个已落地形状的小矩阵对比,而非整个游戏场的全局网格,导致碰撞判断完全失效
  2. 落地判断流程混乱:先移动再检测碰撞,之后又单独判断触底,逻辑重复且冲突
  3. 新形状生成无校验:刚生成的形状未检查是否与已落地方块碰撞,直接触发卡顶重复生成

修正后完整代码

import pygame
import random

# Define Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
CYAN = (0, 255, 255)

# Define shapes
SHAPES = [
    [[0, 1, 1, 1],
     [0, 0, 1, 0]],
    [[0, 0, 2, 2],
     [0, 2, 2, 0]],
    [[0, 3, 3, 0],
     [0, 0, 3, 3]],
    [[0, 4, 0, 0],
     [0, 4, 4, 4]],
    [[0, 0, 5, 0],
     [0, 5, 5, 5]],
    [[0, 6, 6],
     [0, 6, 6]],
    [[0, 7, 0, 0],
     [0, 7, 7, 7]]
]


# Define shape class
class Shape:
    def __init__(self, shape, grid_width):
        self.shape = shape
        self.color = random.choice([RED, BLUE, GREEN, YELLOW, ORANGE, CYAN])
        self.x = (grid_width // 2) - len(self.shape[0]) // 2
        self.y = 0

    def draw(self, screen, block_size):
        for i in range(len(self.shape)):
            for j in range(len(self.shape[0])):
                if self.shape[i][j] != 0:
                    pygame.draw.rect(screen, self.color, (self.x * block_size + j * block_size, self.y * block_size + i * block_size, block_size, block_size))
                    pygame.draw.rect(screen, 'white', (self.x * block_size + j * block_size, self.y * block_size + i * block_size, block_size, block_size), 1)

    def move_down(self):
        self.y += 1

    def check_collision(self, game_grid, grid_width, grid_height):
        for i in range(len(self.shape)):
            for j in range(len(self.shape[0])):
                if self.shape[i][j] != 0:
                    x = self.x + j
                    y = self.y + i
                    # 检查边界与已落地方块碰撞
                    if x < 0 or x >= grid_width or y >= grid_height or (y >= 0 and game_grid[y][x] != 0):
                        return True
        return False


# Initialize Pygame
pygame.init()

# Set up window
WINDOW_WIDTH = 220
WINDOW_HEIGHT = 500
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('Tetris')

# Set up clock
clock = pygame.time.Clock()

# Set up game variables
block_size = 20
grid_width = 10
grid_height = 24
# 全局游戏网格:0为空,非0为对应颜色的方块
game_grid = [[0 for _ in range(grid_width)] for _ in range(grid_height)]
current_shape = Shape(random.choice(SHAPES), grid_width)

# Game loop
while True:
    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    keys = pygame.key.get_pressed()
    # 左右移动:先移动再校验碰撞,碰撞则回退
    if keys[pygame.K_LEFT]:
        current_shape.x -= 1
        if current_shape.check_collision(game_grid, grid_width, grid_height):
            current_shape.x += 1
    if keys[pygame.K_RIGHT]:
        current_shape.x += 1
        if current_shape.check_collision(game_grid, grid_width, grid_height):
            current_shape.x -= 1

    # 自动下落逻辑
    current_shape.move_down()
    if current_shape.check_collision(game_grid, grid_width, grid_height):
        current_shape.move_up()
        # 将当前形状写入全局网格
        for i in range(len(current_shape.shape)):
            for j in range(len(current_shape.shape[0])):
                if current_shape.shape[i][j] != 0:
                    x = current_shape.x + j
                    y = current_shape.y + i
                    if y < 0:
                        # 方块超出顶部,游戏结束
                        pygame.quit()
                        quit()
                    game_grid[y][x] = current_shape.color
        # 生成新形状并校验初始碰撞
        current_shape = Shape(random.choice(SHAPES), grid_width)
        if current_shape.check_collision(game_grid, grid_width, grid_height):
            pygame.quit()
            quit()

    # 绘制游戏内容
    screen.fill('dark gray')

    # 绘制网格背景
    for x in range(grid_width):
        for y in range(grid_height):
            pygame.draw.rect(screen, 'light gray', (x * block_size, y * block_size, block_size, block_size), 0)
            pygame.draw.rect(screen, 'black', (x * block_size, y * block_size, block_size, block_size), 1)

    # 绘制已落地方块
    for y in range(grid_height):
        for x in range(grid_width):
            if game_grid[y][x] != 0:
                pygame.draw.rect(screen, game_grid[y][x], (x * block_size, y * block_size, block_size, block_size))
                pygame.draw.rect(screen, 'white', (x * block_size, y * block_size, block_size, block_size), 1)

    # 绘制当前形状
    current_shape.draw(screen, block_size)

    pygame.display.update()
    clock.tick(10)

关键修正点

  1. 新增全局游戏网格:用二维数组记录每个位置的方块状态,碰撞检测基于全局网格更准确
  2. 重写碰撞检测方法:check_collision统一处理边界碰撞和已落地方块的碰撞
  3. 优化下落流程:先移动再检测碰撞,碰撞则回退并将方块写入网格,再生成新形状
  4. 新形状初始校验:生成新形状后立即检查碰撞,避免刚生成就卡顶触发游戏结束

内容的提问来源于stack exchange,提问作者Theo 17

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:04:55