Код этой статьи основан на python3.6 и pygame1.9.4.
Тетрис — одна из самых классических игр моего детства.Когда я впервые столкнулся с pygame, я хотел написать тетрис. Но когда я думаю о таких операциях, как поворот, стыковка и стирание, это кажется сложным.Когда я на самом деле закончу его писать, я обнаружу, что всего в нем всего 300 строк кода, так что в этом нет ничего сложного.
Давайте сначала посмотрим на скриншот игры, немного некрасиво, ну художественных способностей у меня нет, но основные функции реализованы и можно играть.
Теперь давайте посмотрим на процесс реализации.
форма
Весь интерфейс Tetris разделен на две части: одна часть представляет собой игровую область слева, а другая часть представляет собой область отображения справа, которая отображает счет, скорость, стиль следующего блока и т. д. Скриншотов выкладывать не буду, просто посмотрите на картинку выше.
Как и Змейка, игровая зона состоит из маленьких квадратиков, чтобы было интуитивно понятно, я специально нарисовал линии сетки.
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()
квадратный
Следующим шагом является определение блока.Форма блока выглядит следующим образом:
Здесь я сделал несколько изменений, потому что максимальная длина квадрата — это длинная полоса, равная 4 сеткам, поэтому я равномерно использую сетку 4 × 4 для ее определения. Это тоже возможно, но позже оказалось неудобным.
Для наглядности определите квадрат непосредственно как двумерный массив, где . означает пустой, а 0 означает сплошной. (Используйте ., чтобы указать, что пустое пространство предназначено для интуитивного просмотра, если вы используете пробел, это будет неясно.) Например, строка I, определенная в квадрате 4 × 4 как
['.0..',
'.0..',
'.0..',
'.0..']
а также
['....',
'....',
'0000',
'....']
Самое сложное в блоке то, что он должен реализовать функцию вращения.Например, I-тип имеет две формы, горизонтальную и вертикальную. Так называемое вращение на поверхности означает поворот блока на 90° по часовой стрелке, но когда мы это делаем на самом деле, нам не нужно добиваться этого эффекта «вращения».
В окончательной реализации эта графика отрисовывается на интерфейсе, и каждый раз, когда мы обновляем, все содержимое интерфейса будет очищаться и перерисовываться, поэтому вращение происходит только при отрисовке текущего квадрата, предыдущая фигура больше не рисуется. , но при вращении рисуется задняя форма.
Например, этот I-тип определяется как фигура 4×4, но на самом деле нужен только 1×4 или 4×1, а остальное место пустует. Это не похоже на Т-тип, Т-тип не является прямоугольником, если он определяется прямоугольником, должны быть 2 пустые позиции. Итак, действительно ли Тип I нужно определять как 4 × 4?
Ответ положительный. Подумайте об этом, если это горизонтальная полоса 4 × 1, после поворота она становится вертикальной полосой 1 × 4, как определить это положение? Это кажется немного сложным. Но если это квадрат 4 × 4, нам нужно только зафиксировать координаты начальной точки (верхний левый угол) без изменений и заменить область 4 × 4 вертикальной полосы непосредственно областью 4 × 4 горизонтальная полоса. Достигает ли она вращения? А расположение легко вычислить.
Еще момент, в некоторых случаях нет возможности повернуть. Например, вертикальная полоса типа I не может вращаться, когда она находится близко к левой и правой границам. У меня есть впечатление об этом, конечно. Но что касается других форм, я не очень уверен.Я искал на Baidu и нашел веб-версию тетрис для игры, и обнаружил, что это невозможно. Например:
Его нельзя повернуть, когда он находится близко к правой границе. Если вам приходится оценивать каждую форму, это действительно раздражает. Начав с определения блока, добиться этого может быть очень просто.Например, строка вертикальной полосы определяется как:
['.0..',
'.0..',
'.0..',
'.0..']
Вертикальная полоса может быть обведена, поэтому, когда она находится в крайнем левом углу, координата оси X равна -1, потому что вертикальная строка слева в определении пуста. Нам нужно только решить, что мы можем вращаться только тогда, когда форма, определяемая блоком (включая пустую часть), полностью находится внутри игровой зоны.
Как я уже говорил, нехорошо определять все как 4 × 4, и в этом причина.Для других форм, таких как Т-образная, такое суждение сделать нельзя. Итак, для таких фигур, как 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
На этом основная функция всего тетриса завершена.
Многие параметры здесь можно настроить, например, если поворот неудобный, вы можете напрямую настроить определение блока, не меняя логику кода.
Рекомендуемые записи в блоге:
- Python: Игра: Змейка
- Python: игра: напишите тральщик точно так же, как на XP
- Python: The Game: реализация Tetris в 300 строк кода
- Python: Игра: Человеко-машинная битва за Гобанг
Отсканируйте код, чтобы подписаться на мою личную учетную запись, и ответьте «Сапёру», чтобы получить исходный код.