Как написать тетрис на python

Pygame – самое популярное решение для создания 2D игр на Python: библиотека включает в себя удобные инструменты для рисования, работы с изображениями, видео, спрайтами, шрифтами и звуком, для обработки событий клавиатуры и мыши. Главные преимущества Pygame – легкость обучения и скорость разработки. И хотя Pygame не используется для коммерческой разработки игр, это идеальный вариант для обучения начинающих. Здесь мы рассмотрим создание клона Тетриса. Полный код игры находится здесь.

Pygame не входит в стандартную поставку Python. Для установки достаточно выполнить в cmd команду py -m pip install -U pygame --user. Полный размер пакета – чуть более 8 Мб.

Обзор проекта

Основной экран Тетриса

Основной экран Тетриса

Игровое поле представляет собой прямоугольный «стакан», в который сверху падают фигуры – стилизованные буквы L, S, Z, J, O, I и T.

Буквы-фигуры в Тетрисе

Буквы-фигуры в Тетрисе

Каждая буква состоит из 4 блоков:

Фигуры и варианты поворотов описаны в 2D-списках 5 х 5

Фигуры и варианты поворотов описаны в 2D-списках 5 х 5

Игрок управляет движением фигур вниз – двигает их вправо и влево (но не вверх), поворачивает на 90 градусов, при желании ускоряет падение нажатием/удержанием клавиши или мгновенно сбрасывает фигуры на дно нажатием Enter.

Приземлением считается момент, когда фигура падает на дно стакана или на элемент предыдущих фигур. После этого программа проверяет, вызвало ли приземление полное (без пустот) заполнение ряда элементов. Заполненные ряды (их может быть от 1 до 4 включительно) удаляются; находящиеся над ними элементы перемещаются вниз на столько рядов, сколько было заполнено и удалено; вверху стакана добавляется соответствующее количество пустых рядов. После удаления 10 заполненных рядов происходит переход на следующий уровень, и падение фигур ускоряется.

Все экраны игры

Все экраны игры

Основные параметры игры

Прежде всего импортируем нужные модули:

        import pygame as pg
import random, time, sys
from pygame.locals import *
    

Затем определяем основные константы – кадровую частоту fps, высоту и ширину окна программы, размер базового элемента фигур-букв block (20 х 20 пикселей), параметры стакана, символ для обозначения пустых ячеек на игровом поле:

        fps = 25
window_w, window_h = 600, 500
block, cup_h, cup_w = 20, 20, 10

    

К размеру базового элемента block привязываются остальные параметры игрового поля: ширина и высота стакана, к примеру, равны 10 и 20 блоков соответственно; каждый раз, когда игрок нажимает клавишу или , фигура перемещается на 1 блок в нужную сторону.

Параметры side_freq и down_freq задают время, которое затрачивается на перемещение фигуры в сторону или вниз, если игрок удерживает клавишу нажатой:

        side_freq, down_freq = 0.15, 0.1
    

Для размещения стакана и информационных надписей, а также для конвертации координат нам также понадобятся константы side_margin и top_margin – первая задает дистанцию между правой и левой сторонами окна программы и стаканом; вторая определяет расстояние между верхней границей стакана и окном:

        side_margin = int((window_w - cup_w * block) / 2)
top_margin = window_h - (cup_h * block) - 5

    

Шаблоны и цвет фигур

Поскольку каждую фигуру-букву можно поворачивать на 90 градусов, все возможные варианты поворотов описаны в словаре figures с помощью вложенных списков, элементы которых состоят из строк: символом x отмечены занятые ячейки, o – пустые. Количество вращений зависит от формы буквы: у O, к примеру, будет всего один вариант:

        'O': [['ooooo',
       'ooooo',
       'oxxoo',
       'oxxoo',
       'ooooo']]

    

Поскольку каждая фигура состоит из 4 блоков, размер шаблона должен быть 5 х 5: fig_w, fig_h = 5, 5.

Цвета фигур задаются двумя кортежами: colors и lightcolors. Последний включает чуть более светлые оттенки тех же цветов, что и colors – для создания псевдо 2.5 D эффекта.

FPS и производительность

Pygame немилосердно нагружает процессор: можно столкнуться с ситуацией, когда небольшая игра с простейшей графикой использует CPU на 100% и нагревает достаточно мощный компьютер гораздо сильнее, чем 3D-шутер, написанный не на Python:). Проблема решается созданием объекта pygame.time.Clock(), который вызывается в основном цикле программы с нужной fps – кадровой частотой.

Шрифты

Модуль Pygame поставляется с одним шрифтом – freesansbold.ttf. При этом Pygame способен использовать любые другие шрифты – как установленные в системе, так и используемые только в рамках конкретного проекта. Чтобы получить список всех шрифтов, установленных в системе, достаточно выполнить pygame.font.get_fonts().

Подключить шрифт можно тремя способами:

Если шрифт установлен и находится в папке WindowsFonts, как, например, стандартный Arial – нужно воспользоваться методом pygame.font.SysFont: pygame.font.SysFont('arial', 15).

Если шрифт используется только в проекте – укажите к нему путь в pygame.font.Font('/User/Tetris/game.ttf', 18).

Чтобы не указывать путь, можно поместить шрифт в одну папку с проектом: pygame.font.Font('game.ttf', 18)

Пауза, экран паузы и прозрачность

Пауза в нашей игре возникает при нажатии пробела event.key == K_SPACE. Чтобы показать «неактивность» программы во время паузы, нужно залить игровое поле цветом.

Во время паузы экран заливается полупрозрачным синим цветом

Во время паузы экран заливается полупрозрачным синим цветом

Заливку сплошным цветом реализовать очень просто, но полупрозрачную заставку сделать сложнее – как ни странно, метод draw в Pygame до сих пор не поддерживает эту опцию. Есть несколько способов решения этой проблемы. Мы воспользуемся методом, который предусматривает создание дополнительной поверхности с попиксельным альфа-смешением, и последующую заливку экрана паузы цветом с наложением на поверхность окна игры:

        pause = pg.Surface((600, 500), pg.SRCALPHA)  
pause.fill((0, 0, 255, 127)) 
display_surf.blit(pause, (0, 0))

    

Экран паузы также активируется в случае проигрыша, вместе с сообщением Игра закончена.

Функция main()

Эта функция отвечает за создание нескольких дополнительных глобальных констант, инициализирует модуль Pygame, рисует стартовое окно игры, вызывает запуск Тетриса runTetris() и в случае необходимости отображает сообщение о проигрыше:

        def main():
	global fps_clock, display_surf, basic_font, big_font
	pg.init()
	fps_clock = pg.time.Clock()
	display_surf = pg.display.set_mode((window_w, window_h))
	basic_font = pg.font.Font('freesansbold.ttf', 18)
	big_font = pg.font.Font('freesansbold.ttf', 45)
	pg.display.set_caption('Тетрис Lite')
	showText('Тетрис Lite')
	while True: # начинаем игру
    	runTetris()
    	pauseScreen()
    	showText('Игра закончена')
    

Основной код Тетриса

Код игры располагается в функции runTetris():

        def runTetris():
	cup = emptycup()
	last_move_down = time.time()
	last_side_move = time.time()
	last_fall = time.time()
	going_down = False
	going_left = False
	going_right = False
	points = 0
	level, fall_speed = calcSpeed(points)
	fallingFig = getNewFig()
	nextFig = getNewFig()
    

При запуске вызывается функция рисования пустого стакана emptycup(), а возможности движения влево, вправо и вниз устанавливаются на False:

        	going_down = False
	going_left = False
	going_right = False

    

Эти значения будут изменяться на True во время обработки событий клавиатуры, если будет установлено, что движение в нужном направлении возможно:

        for event in pg.event.get():
    if event.type == KEYUP:
    

Главный цикл игры

Основной цикл обрабатывает все основные события, связанные с генерацией фигур, движением вниз и показом следующей фигуры:

        while True:
    	if fallingFig == None:
        	fallingFig = nextFig
        	nextFig = getNewFig()
        	last_fall = time.time()
        	if not checkPos(cup, fallingFig):
            	return
    	quitGame()

    

После приземления каждой фигуры значение fallingFig устанавливается на None, после чего «следующая фигура» nextFig, уже показанная в превью, становится «падающей» fallingFig. Следующая фигура для превью генерируется функцией getNewFig(). Каждая новая падающая фигура генерируется в позиции, которая расположена чуть выше стакана. Функция checkPos() вернет False, если стакан уже заполнен настолько, что движение вниз невозможно, после чего появится сообщение Игра закончена. Эта же функция checkPos() проверяет, находится ли фигура в границах стакана и не натыкается ли на элементы других фигур.

Управление движением

Обработка всех событий происходит в уже упомянутом цикле:

        for event in pg.event.get():
    if event.type == KEYUP:
    

Цикл обрабатывает паузу и определяет момент, когда пользователь нажимает и отпускает клавиши со стрелками. Если клавиши , и не нажаты, значения соответствующих переменных меняются на False:

                    	elif event.key == K_LEFT:
                	going_left = False
            	elif event.key == K_RIGHT:
                	going_right = False
            	elif event.key == K_DOWN:
                	going_down = False
    

Управление движением фигур происходит в ветке elif event.type == KEYDOWN: если нажата клавиша со стрелкой и функция checkPos() возвращает True, положение фигуры изменяется на один блок в соответствующем направлении:

        if event.key == K_LEFT and checkPos(cup, fallingFig, adjX=-1):
    fallingFig['x'] -= 1
    going_left = True
    going_right = False
    last_side_move = time.time()

    

Если пользователь не отпускает клавишу, следующее перемещение на один блок произойдет в соответствии со значениями side_freq и down_freq. Случай, когда пользователь удерживает клавишу в течение нескольких секунд, мы рассмотрим ниже.

При нажатии происходит вращение фигуры – варианты берутся из словаря figures. Чтобы не получить ошибку IndexError: list index out of range, мы используем конструкцию, которая обнуляет индекс элемента, когда инкремент достигает максимального значения: fallingFig['rotation'] + 1) % len(figures[fallingFig['shape']]. Если функция checkPos() сообщает, что очередное вращение невозможно из-за того, что фигура натыкается на какой-то блок, нужно вернуться к предыдущему варианту из списка:

        if not checkPos(cup, fallingFig):
    fallingFig['rotation'] = (fallingFig['rotation'] - 1) % len(figures[fallingFig['shape']])
    

Для ускорения падения игрок нажимает и удерживает клавишу :

                    	elif event.key == K_DOWN:
                	going_down = True
                	if checkPos(cup, fallingFig, adjY=1):
                    	    fallingFig['y'] += 1
  	                last_move_down = time.time()
    

Если пользователь хочет мгновенно сбросить фигуру на дно, он может нажать Enter. Цикл for здесь определяет максимально низкую свободную позицию в стакане:

                    	elif event.key == K_RETURN:
                	going_down = False
                	going_left = False
                	going_right = False
                	for i in range(1, cup_h):
                    	    if not checkPos(cup, fallingFig, adjY=i):
                      	        break
                	fallingFig['y'] += i - 1
    

Удержание клавиш

Чтобы определить, удерживает ли пользователь клавишу движения, программа использует условия:

        if (going_left or going_right) and time.time() - last_side_move > side_freq:
    

и

        if going_down and time.time() - last_move_down > down_freq and checkPos(cup, fallingFig, adjY=1):
    

В этих условиях программа проверяет, нажимает ли пользователь клавишу дольше, чем 0.15 или 0.1 секунды – в этом случае условие соответствует True, и фигура продолжит движение в заданном направлении. Эти условия избавляют игрока от необходимости многократно нажимать клавиши передвижения – для продолжения движения достаточно их удерживать.

Свободное падение

Если пользователь никак не вмешивается в управление фигурой, движение вниз происходит так:

            	if time.time() - last_fall > fall_speed: # свободное падение фигуры           
        	if not checkPos(cup, fallingFig, adjY=1): # проверка "приземления" фигуры
            	    addToCup(cup, fallingFig) # фигура приземлилась, добавляем ее в содержимое стакана
            	    points += clearCompleted(cup)
            	    level, fall_speed = calcSpeed(points)
            	    fallingFig = None
        	else: # фигура пока не приземлилась, продолжаем движение вниз
                    fallingFig['y'] += 1
                    last_fall = time.time()

    

Отрисовка, обновление окна игры и вывод надписей

Функцию runTetris() завершает набор функций, обеспечивающих отрисовку игрового поля, вывод названия игры, падающей и следующих фигур, а также информационных надписей:

            	display_surf.fill(bg_color)
    	drawTitle()
    	gamecup(cup)
    	drawInfo(points, level)
    	drawnextFig(nextFig)
    	if fallingFig != None:
        	drawFig(fallingFig)
    	pg.display.update()
    	fps_clock.tick(fps)
    

Вспомогательные функции

Функция txtObjects() принимает текст, шрифт и цвет, и с помощью метода render() возвращает готовые объекты Surface (поверхность) и Rect (прямоугольник). Эти объекты в дальнейшем обрабатываются методом blit в функции showText(), выводящей информационные надписи и название игры.

Выход из игры обеспечивает функция stopGame(), в которой используется sys.exit() из импортированного в начале кода модуля sys.

За добавление фигур к содержимому стакана отвечает addToCup():

        def addToCup(cup, fig):
	for x in range(fig_w):
            for y in range(fig_h):
        	if figures[fig['shape']][fig['rotation']][y][x] != empty:
            	    cup[x + fig['x']][y + fig['y']] = fig['color']
    

Пока фигура двигается, ее блоки не принадлежат к содержимому стакана – добавление происходит после приземления. Этот процесс мы рассмотрим чуть ниже.

Генерация и заполнение стакана

Пустой стакан создается функцией emptycup():

        def emptycup():
    cup = []
    for i in range(cup_w):
        cup.append([empty] * cup_h)
    return cup

    

Пустой стакан представляет собой двумерный список, заполненный символами o. Занятые ячейки в дальнейшем принимают значения 0, 1, 2, 3 – в соответствии с индексами цветов фигур в кортеже colors. Так выглядит массив cup после приземления нескольких фигур:

        ['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 2, 2, 1, 1]
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 3, 3, 2, 2, 1, 1]
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 3, 2, 2, 'o', 'o', 'o', 1]
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 3, 3, 2, 2, 0, 2, 1]
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 3, 0, 2, 0, 0, 2, 'o']
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 0, 0, 0, 0, 0, 0, 1, 'o']
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 0, 0, 2, 1, 0, 1, 1]
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 2, 2, 1, 1, 1, 'o']
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 1, 0, 0, 0, 0, 0]
['o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 1, 1, 1, 2, 2, 2, 0, 0]

    

Допустимое и недопустимое положение фигуры в стакане

Допустимое и недопустимое положение фигуры в стакане

Функция checkPos() следит за тем, чтобы падающая фигура оставалась в пределах игрового поля и не накладывалась на предыдущие. На примере слева фигура остается в допустимой области, на примере справа – ошибочно накладывается на предыдущую. Чтобы определить положение фигуры в стакане, нужно суммировать собственные координаты фигуры со «стаканными»:

Собственные координаты – (2, 1), (3, 1), (2, 2), (2, 3).

Стаканные координаты фигуры – (2, 3) на примере слева и (1, 11) на примере справа. Суммирование дает следующие результаты:

(2+2, 1+3), (3+2, 1+3), (2+2, 2+3), (2+2, 3+3) = (4, 4), (5, 4), (4, 5), (4, 6). Значит, фигура находится в пределах стакана и не наталкивается ни на один элемент предыдущих фигур.

На примере слева ситуация обратная:

(2+1, 2+11), (3+1, 2+11), (2+1, 3+11), (2+1, 4+11) = (3, 13), (4, 13), (3, 14), (3, 15) – две последние координаты в массиве cup уже заняты блоками предыдущих фигур. Именно такие ситуации и предотвращают checkPos() вместе с incup():

                	if not incup(x + fig['x'] + adjX, y + fig['y'] + adjY):
                    return False
        	if cup[x + fig['x'] + adjX][y + fig['y'] + adjY] != empty:
            	    return False
    

Удаление заполненных рядов и сдвиг блоков вниз

За обнаружение и удаление заполненных рядов отвечает функция clearCompleted() вместе со вспомогательной isCompleted(). Если isCompleted() возвращает True, программе нужно последовательно переместить вниз все ряды, располагающиеся над удаляемым, после чего заполнить нулевой ряд empty-значениями о:

        
def clearCompleted(cup):
    # Удаление заполенных рядов и сдвиг верхних рядов вниз
    removed_lines = 0
    y = cup_h - 1 
    while y >= 0:
        if isCompleted(cup, y):
           for pushDownY in range(y, 0, -1):
                for x in range(cup_w):
                    cup[x][pushDownY] = cup[x][pushDownY-1]
           for x in range(cup_w):
                cup[x][0] = empty
           removed_lines += 1
        else:
            y -= 1 
    return removed_lines
    

Переменная указывает на удаленный ряд

Переменная указывает на удаленный ряд

Переменная y после удаления одного ряда продолжает указывать на его номер – это нужно для того, чтобы перейти к удалению других заполненных рядов, если они смещаются на место только что удаленного. В случае если других заполненных рядов пока нет, происходит уменьшение у.

Рисование блоков фигур

Каждая фигура состоит из 4 элементов – блоков. Блоки рисует функция drawBlock(), которая получает координаты из convertCoords():

        def drawBlock(block_x, block_y, color, pixelx=None, pixely=None):
    #отрисовка квадратных блоков, из которых состоят фигуры
    if color == empty:
        return
    if pixelx == None and pixely == None:
        pixelx, pixely = convertCoords(block_x, block_y)
    pg.draw.rect(display_surf, colors[color], (pixelx + 1, pixely + 1, block - 1, block - 1), 0, 3)
    pg.draw.rect(display_surf, lightcolors[color], (pixelx + 1, pixely + 1, block - 4, block - 4), 0, 3)
    pg.draw.circle(display_surf, colors[color], (pixelx + block / 2, pixely + block / 2), 5)
    
    

Для рисования блоков используются примитивы rect (прямоугольник) и circle (круг). При желании верхний квадрат можно конвертировать в поверхность (Surface), после чего наложить на эту поверхность изображение или текстовый символ. Функция drawBlock() также используется в drawnextFig() для вывода следующей фигуры справа от игрового поля.

Заключение

Напоминаем, что полный код игры можно скачать здесь. Это полностью функциональный Тетрис с простым интерфейсом. Pygame предоставляет немало дополнительных возможностей для дополнения программы: к примеру, в игру можно добавить звуковые эффекты, диалоговое окно для закрытия, фоновое изображение, запись рекордов в файл. Если какие-то моменты остались неясными – задавайте вопросы в комментариях.

Материалы по теме

  • 🕵 Пишем кейлоггер на Python для Windows за 5 минут
  • 🐍 Создание интерактивных панелей с Streamlit и Python
  • 🐍 Как сделать сайт на Python за 5 минут с помощью SSG-генератора Pelican

import pygame
import random

colors = [
(0, 0, 0),
(120, 37, 179),
(100, 179, 179),
(80, 34, 22),
(80, 134, 22),
(180, 34, 22),
(180, 34, 122),
]

class Figure:
x = 0
y = 0

figures = [
    [[1, 5, 9, 13], [4, 5, 6, 7]],
    [[4, 5, 9, 10], [2, 6, 5, 9]],
    [[6, 7, 9, 10], [1, 5, 6, 10]],
    [[1, 2, 5, 9], [0, 4, 5, 6], [1, 5, 9, 8], [4, 5, 6, 10]],
    [[1, 2, 6, 10], [5, 6, 7, 9], [2, 6, 10, 11], [3, 5, 6, 7]],
    [[1, 4, 5, 6], [1, 4, 5, 9], [4, 5, 6, 9], [1, 5, 6, 9]],
    [[1, 2, 5, 6]],
]

def __init__(self, x, y):
    self.x = x
    self.y = y
    self.type = random.randint(0, len(self.figures) - 1)
    self.color = random.randint(1, len(colors) - 1)
    self.rotation = 0

def image(self):
    return self.figures[self.type][self.rotation]

def rotate(self):
    self.rotation = (self.rotation + 1) % len(self.figures[self.type])

class Tetris:
level = 2
score = 0
state = «start»
field = []
height = 0
width = 0
x = 100
y = 60
zoom = 20
figure = None

def __init__(self, height, width):
    self.height = height
    self.width = width
    self.field = []
    self.score = 0
    self.state = "start"
    for i in range(height):
        new_line = []
        for j in range(width):
            new_line.append(0)
        self.field.append(new_line)

def new_figure(self):
    self.figure = Figure(3, 0)

def intersects(self):
    intersection = False
    for i in range(4):
        for j in range(4):
            if i * 4 + j in self.figure.image():
                if i + self.figure.y > self.height - 1 or 
                        j + self.figure.x > self.width - 1 or 
                        j + self.figure.x < 0 or 
                        self.field[i + self.figure.y][j + self.figure.x] > 0:
                    intersection = True
    return intersection

def break_lines(self):
    lines = 0
    for i in range(1, self.height):
        zeros = 0
        for j in range(self.width):
            if self.field[i][j] == 0:
                zeros += 1
        if zeros == 0:
            lines += 1
            for i1 in range(i, 1, -1):
                for j in range(self.width):
                    self.field[i1][j] = self.field[i1 - 1][j]
    self.score += lines ** 2

def go_space(self):
    while not self.intersects():
        self.figure.y += 1
    self.figure.y -= 1
    self.freeze()

def go_down(self):
    self.figure.y += 1
    if self.intersects():
        self.figure.y -= 1
        self.freeze()

def freeze(self):
    for i in range(4):
        for j in range(4):
            if i * 4 + j in self.figure.image():
                self.field[i + self.figure.y][j + self.figure.x] = self.figure.color
    self.break_lines()
    self.new_figure()
    if self.intersects():
        self.state = "gameover"

def go_side(self, dx):
    old_x = self.figure.x
    self.figure.x += dx
    if self.intersects():
        self.figure.x = old_x

def rotate(self):
    old_rotation = self.figure.rotation
    self.figure.rotate()
    if self.intersects():
        self.figure.rotation = old_rotation

Initialize the game engine

pygame.init()

Define some colors

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)

size = (400, 500)
screen = pygame.display.set_mode(size)

pygame.display.set_caption(«Tetris»)

Loop until the user clicks the close button.

done = False
clock = pygame.time.Clock()
fps = 25
game = Tetris(20, 10)
counter = 0

pressing_down = False

while not done:
if game.figure is None:
game.new_figure()
counter += 1
if counter > 100000:
counter = 0

if counter % (fps // game.level // 2) == 0 or pressing_down:
    if game.state == "start":
        game.go_down()

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        done = True
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_UP:
            game.rotate()
        if event.key == pygame.K_DOWN:
            pressing_down = True
        if event.key == pygame.K_LEFT:
            game.go_side(-1)
        if event.key == pygame.K_RIGHT:
            game.go_side(1)
        if event.key == pygame.K_SPACE:
            game.go_space()
        if event.key == pygame.K_ESCAPE:
            game.__init__(20, 10)

if event.type == pygame.KEYUP:
        if event.key == pygame.K_DOWN:
            pressing_down = False

screen.fill(WHITE)

for i in range(game.height):
    for j in range(game.width):
        pygame.draw.rect(screen, GRAY, [game.x + game.zoom * j, game.y + game.zoom * i, game.zoom, game.zoom], 1)
        if game.field[i][j] > 0:
            pygame.draw.rect(screen, colors[game.field[i][j]],
                             [game.x + game.zoom * j + 1, game.y + game.zoom * i + 1, game.zoom - 2, game.zoom - 1])

if game.figure is not None:
    for i in range(4):
        for j in range(4):
            p = i * 4 + j
            if p in game.figure.image():
                pygame.draw.rect(screen, colors[game.figure.color],
                                 [game.x + game.zoom * (j + game.figure.x) + 1,
                                  game.y + game.zoom * (i + game.figure.y) + 1,
                                  game.zoom - 2, game.zoom - 2])

font = pygame.font.SysFont('Calibri', 25, True, False)
font1 = pygame.font.SysFont('Calibri', 65, True, False)
text = font.render("Score: " + str(game.score), True, BLACK)
text_game_over = font1.render("Game Over", True, (255, 125, 0))
text_game_over1 = font1.render("Press ESC", True, (255, 215, 0))

screen.blit(text, [0, 0])
if game.state == "gameover":
    screen.blit(text_game_over, [20, 200])
    screen.blit(text_game_over1, [25, 265])

pygame.display.flip()
clock.tick(fps)

pygame.quit()

tetris

Показываем и рассказываем, как написать тетрис на Python с помощью модуля Pygame.

Классический тетрис появился в 1984 году — но до сих пор будоражит умы людей со всего мира. В тетрис и по сей день играют миллионы, используя различные гаджеты и вычислительные машины.

Игру любят за четкость, достижимость цели и наглядную «обратную связь». В то же время тетрис полезен: развивает скорость реакции и пространственное мышление, учит концентрироваться на задаче и абстрагироваться от внешних раздражителей, успокаивает нервы и повышает производительность работы мозга.

Знать, как устроена любимая игра — захватывающе. Конструирование тетриса на языке Пайтон — способ испытать себя и усовершенствовать навыки программирования для начинающего кодера.

В конце инструкции — ссылка на тематический видеоурок: просмотр закрепит знания.

Начинаем воплощать тетрис на Пайтоне

Сразу напомним: модуль Pygame изначально не встроен в Python. Если он не установлен, проще это сделать в консоли командой pip install pygame.

Первым делом импортируем нужные библиотеки: pygame, random, copy. Copy понадобится, чтобы разобраться с одной особенностью хранения в Python.

После чего активируем модули Pygame и зададим 2 параметра сетки — I_max и J_max. Сетка строится по такому принципу: число ячеек равно числу точек -1. Можете убедиться в этом, посчитав на рисунке или нарисовав сами:

Задав число точек I_max и J_max 11 и 21, получим 11 и 20 ячеек соответственно.

Добавляем переменные

Зададим 2 переменные с параметрами экрана 300 на 600. После сделаем экран, туда запишем переменные, а также создадим класс Clock(). Он понадобится для работы со временем — с частотой обновления экрана.

Сосчитаем шаг по x и по y для сетки — ширину и высоту ячейки. Делим на I_max -1, J_max -1: область необходимо разделить на число ячеек, а оно как раз на 1 меньше числа точек. Создаем переменную для частоты обновления кадров.

Как сделать тетрис на Python: применяем списки и циклы

Делаем список, который будет хранить внутри себя параметры сетки, отображенные в окне. Добавим по списку на каждом элементе по ширине и на каждом элементе по высоте. Сразу занесем туда 1 — позднее станет понятно, зачем это сделано.

Кроме 1, заполним списки сущностями прямоугольников — rect, а также цветом для каждого из них. В координатах прямоугольников пишем координаты, где они должны находиться. Это можно сосчитать, умножив номер точки на шаг — ширину или высоту ячейки.

Пора запустить бесконечный цикл, заполнить экран для его обновления и нарисовать сетку. Для этого снова пройдемся по элементам сетки и нарисуем каждый квадратик. Третий элемент списка — 2: это цвет. Второй — 1: сущность прямоугольника, созданного ранее. Тип заполнения — 0: когда стоит 1, у прямоугольника нарисована граница шириной 1. Если бы поставили 0, он бы заполнился полностью.

Не забудьте добавить событие нажатия на крестик в игре.

Переходим к деталям

Тетрис — игра про детали. Потому рекомендуем посмотреть на 7 оригинальных фигур тетриса:

Распишем их координаты в списке. Будем придерживаться того, что каждый третий — 0,0 элемент, который в будущем примем за ось вращения.

Создадим список уже не с координатами, а с самими деталями. Для этого делаем список det: каждый элемент будем заполнять 4 прямоугольниками. Координаты берем из списка с координатами и создаем детали в центре экрана сверху.

Посмотрим, чем заполняется список, выведем это в консоль. Видим, что в нем 7 списков, по 4 прямоугольника в каждом — что и хотели получить.

Настроим случайный выбор детали из списка и ее отрисовку в программе. Каждый кадр проводится циклом по 4 прямоугольникам внутри детали. Прямоугольники будут отрисовываться.

Как создать тетрис на Python: проектируем движения

Для создания движения будем брать рандомно выбранную деталь det_choice и менять координаты у 4 прямоугольников на шаг dx и dy.

Для примера напишем 1*dx и посмотрим, что будет. Деталь быстро улетела вправо. Если поменяем на -1 — быстро улетит влево. Будем менять данные параметры с 0 на 1 и -1 при взаимодействии с клавишами стрелки вправо или стрелки влево. Создадим event нажатия на клавишу: если нажата стрелка влево, меняем переменную delta_x на -1. Для правой стрелки аналогично, только +1.

Теперь нужно настроить границы, чтобы за них нельзя было выходить. Для этого зададим условие: если хотя бы одна из деталей на следующей клетке (на следующем шаге) за пределами границ экрана, то delta_x зануляем, движения не произойдет.

Таким же образом сделаем перемещение по y. Для замедления включаем передвижение только каждые 30 обновлений. Для этого будем сохранять число обновлений в переменной.

Добавим вариант ускорения передвижения по y, если хотим резко уронить деталь и не ждать ее медленного падения. Чтобы зажимать клавишу, а не нажимать по кнопке много раз, используем функцию get_pressed(). Если ключ, который нажмет человек, совпадает с pygame.K_DOWN, т.е. стрелкой вниз, число обновлений сразу зададим равным 31.

Настроим границы для y: если на следующем шаге по y хотя бы одна деталь выйдет за нижнюю границу, остановим движение.

Остановили движение — зареспавним еще одну деталь. Для этого закрасим клетки сетки, где осталась фигура, белым цветом — зададим параметр заливки равным 0, а цвет изменим с серого на белый и сделаем новый выбор детали.

Исправляем ошибки

После проверки в игре обнаружен баг — деталь появляется только 7 раз, после этого ничего не происходит. Это связано с хранением информации в Python: меняя координаты деталей, меняем и список — фигуры сразу возникают в самом низу. Чтобы такого не происходило, будем делать копию детали из списка и менять уже координаты копии, а не фигуры.

Теперь их появляется уже больше 7 штук, но они не ставятся друг на друга. Это следует исправить: пропишем условие в границы: если следующая снизу ячейка сетки закрашена, останавливаем движение.

Доделываем игру тетрис на Python

Основные механики готовы, осталось только внедрить пару вещей из оригинальной игры: поворот деталей и снос заполненного ряда.

Начнем с поворота. Его сделаем по стандартной математической формуле поворота на 90 градусов в декартовой системе координат — относительно центра в (0,0). Центр записан в третьей ячейке, поэтому обозначаем как центр второй индекс det_choice[2].

Создадим проверку на контакт с клавишей стрелки вверх — при ее нажатии переменная rotate становится равной True, выполняется условие. Проходимся по элементам квадратика и смещаем их относительно центра вращения, после чего переписываем rotate на False.

Последний штрих — снос заполненного ряда. Для этого запустим цикл, проверяющий, насколько заполнен каждый ряд. Идти будем снизу вверх и слева направо, считая число заполненных ячеек: в этом случае параметр заполнения равен 0. Если же хотя бы одна ячейка не равна 0, можем сказать, что полоска не заполнена — а затем завершить цикл на этом ряду и перейти к проверке следующего.

Если число заполненных ячеек равно числу ячеек в целом, запускаем еще 2 цикла. Сначала ячейки верхнего ряда делаем незакрашенными, а после все ячейки рядов смещаем на ряд вниз, начиная с заполненного.

Надеемся, вы попробовали написать данный код и улучшить его — поверьте, здесь есть потенциал для совершенствования.

Что дальше

Как и обещали, делимся YouTube-видеороликом о том, как сделать игру тетрис на Пайтоне: он поможет пройтись по изученному материалу. Также на канале находится плейлист с бесплатными роликами, посвященными реализации игр на Python. Стоит посмотреть, чтобы вдохновиться на конструирование игр.

Как сделать игру Тетрис на Python. Python создание игр с нуля

Отточить мастерство геймдева и программирования на Питоне также можно на специальных курсах под присмотром опытного преподавателя.

  • Ребятам 10–14 лет подойдет курс изучения языка Пайтон. Проштудировав азы разработки, подросток напишет квест или викторину, запустит чат-бота для мессенджера или игру с функцией стрельбы, бонусами и подсчетом очков.
  • Любителям Майнкрафта 9–13 лет понравится курс по кодингу на Python в Minecraft. Ребенок применит знания в любимой вселенной, написав внутриигровые программы или придумав персонажа с искусственным интеллектом.

Изучая разработку на Питоне любым из методов, ребенок приобретет полезные качества и умения, которые пригодятся в жизни и при построении карьеры в удивительном мире IT.

Полный код программы:

import pygame

import random

import copy

pygame.init()

I_max = 11

J_max = 21

screen_x = 300

screen_y = 600

screen = pygame.display.set_mode((screen_x, screen_y))

clock = pygame.time.Clock()

pygame.display.set_caption(«Tetris Pixel Game»)

dx = screen_x/(I_max — 1)

dy = screen_y/(J_max — 1)

fps = 60

grid = []

for i in range(0, I_max):

grid.append([])

for j in range(0, J_max):

grid[i].append([1])

for i in range(0, I_max):

for j in range(0, J_max):

grid[i][j].append(pygame.Rect(i*dx, j*dy, dx, dy))

grid[i][j].append(pygame.Color(«Gray»))

details = [

[[-2, 0], [-1, 0], [0, 0], [1, 0]],

[[-1, 1], [-1, 0], [0, 0], [1, 0]],

[[1, 1], [-1, 0], [0, 0], [1, 0]],

[[-1, 1], [0, 1], [0, 0], [-1, 0]],

[[1, 0], [1, 1], [0, 0], [-1, 0]],

[[0, 1], [-1, 0], [0, 0], [1, 0]],

[[-1, 1], [0, 1], [0, 0], [1, 0]],

]

det = [[],[],[],[],[],[],[]]

for i in range(0, len(details)):

for j in range(0, 4):

det[i].append(pygame.Rect(details[i][j][0]*dx + dx*(I_max//2), details[i][j][1]*dy, dx, dy))

detail = pygame.Rect(0, 0, dx, dy)

det_choice = copy.deepcopy(random.choice(det))

count = 0

game = True

rotate = False

while game:

delta_x = 0

delta_y = 1

for event in pygame.event.get():

if event.type == pygame.QUIT:

exit()

if event.type == pygame.KEYDOWN:

if event.key == pygame.K_LEFT:

delta_x = -1

elif event.key == pygame.K_RIGHT:

delta_x = 1

elif event.key == pygame.K_UP:

rotate = True

key = pygame.key.get_pressed()

if key[pygame.K_DOWN]:

count = 31 * fps

screen.fill(pygame.Color(«Black»))

for i in range(0, I_max):

for j in range(0, J_max):

pygame.draw.rect(screen, grid[i][j][2], grid[i][j][1], grid[i][j][0])

#границы

for i in range(4):

if ((det_choice[i].x + delta_x * dx < 0) or (det_choice[i].x + delta_x * dx >= screen_x)):

delta_x = 0

if ((det_choice[i].y + dy >= screen_y) or (grid[int(det_choice[i].x//dx)][int(det_choice[i].y//dy) + 1][0] == 0)):

delta_y = 0

for i in range(4):

x = int(det_choice[i].x // dx)

y = int(det_choice[i].y // dy)

grid[x][y][0] = 0 #закрашиваем квадратик

grid[x][y][2] = pygame.Color(«White»)

detail.x = 0

detail.y = 0

det_choice = copy.deepcopy(random.choice(det))

#передвижение по x

for i in range(4):

det_choice[i].x += delta_x*dx

count += fps

#передвижение по y

if count > 30 * fps:

for i in range(4):

det_choice[i].y += delta_y*dy

count = 0

for i in range(4):

detail.x = det_choice[i].x

detail.y = det_choice[i].y

pygame.draw.rect(screen, pygame.Color(«White»), detail)

C = det_choice[2] #центр СК

if rotate == True:

for i in range(4):

x = det_choice[i].y — C.y

y = det_choice[i].x — C.x

det_choice[i].x = C.x — x

det_choice[i].y = C.y + y

rotate = False

for j in range(J_max — 1, -1, -1): #цикл по рядам снизу вверх

count_cells = 0

for i in range(0, I_max): #цикл по столбцам справа налево

if grid[i][j][0] == 0:

count_cells += 1

elif grid[i][j][0] == 1:

break

if count_cells == (I_max — 1):

for l in range(0, I_max):

grid[l][0][0] = 1 #все ячейки первого ряда

for k in range(j, -1, -1):

for l in range(0, I_max):

grid[l][k][0] = grid[l][k-1][0]

pygame.display.flip()

clock.tick(fps)

Subscribe to Tech With Tim!

Starter File

To make our lives a little easier I have I have included a starter file that has the code for some of the more tedious parts of the game. Please copy the code below into your python script before starting. If you’d like an in-depth explanation of the starter file please refer to the video.

The way we will represent our pieces will be using multidimensional lists. Each list will have multiple sub-lists that represent all of the possible rotations of each shape. This will make it much easier to rotate and represent our shapes visually when we eventually draw them to the screen.

import pygame
import random

# creating the data structure for pieces
# setting up global vars
# functions
# - create_grid
# - draw_grid
# - draw_window
# - rotating shape in main
# - setting up the main

"""
10 x 20 square grid
shapes: S, Z, I, O, J, L, T
represented in order by 0 - 6
"""

pygame.font.init()

# GLOBALS VARS
s_width = 800
s_height = 700
play_width = 300  # meaning 300 // 10 = 30 width per block
play_height = 600  # meaning 600 // 20 = 20 height per block
block_size = 30

top_left_x = (s_width - play_width) // 2
top_left_y = s_height - play_height


# SHAPE FORMATS

S = [['.....',
      '......',
      '..00..',
      '.00...',
      '.....'],
     ['.....',
      '..0..',
      '..00.',
      '...0.',
      '.....']]

Z = [['.....',
      '.....',
      '.00..',
      '..00.',
      '.....'],
     ['.....',
      '..0..',
      '.00..',
      '.0...',
      '.....']]

I = [['..0..',
      '..0..',
      '..0..',
      '..0..',
      '.....'],
     ['.....',
      '0000.',
      '.....',
      '.....',
      '.....']]

O = [['.....',
      '.....',
      '.00..',
      '.00..',
      '.....']]

J = [['.....',
      '.0...',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..00.',
      '..0..',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '...0.',
      '.....'],
     ['.....',
      '..0..',
      '..0..',
      '.00..',
      '.....']]

L = [['.....',
      '...0.',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..0..',
      '..0..',
      '..00.',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '.0...',
      '.....'],
     ['.....',
      '.00..',
      '..0..',
      '..0..',
      '.....']]

T = [['.....',
      '..0..',
      '.000.',
      '.....',
      '.....'],
     ['.....',
      '..0..',
      '..00.',
      '..0..',
      '.....'],
     ['.....',
      '.....',
      '.000.',
      '..0..',
      '.....'],
     ['.....',
      '..0..',
      '.00..',
      '..0..',
      '.....']]

shapes = [S, Z, I, O, J, L, T]
shape_colors = [(0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0), (255, 165, 0), (0, 0, 255), (128, 0, 128)]
# index 0 - 6 represent shape


class Piece(object):
    pass

def create_grid(locked_positions={}):
    pass

def convert_shape_format(shape):
    pass

def valid_space(shape, grid):
    pass

def check_lost(positions):
    pass

def get_shape():
    pass

def draw_text_middle(text, size, color, surface):  
    pass
   
def draw_grid(surface, row, col):
    pass

def clear_rows(grid, locked):
    pass

def draw_next_shape(shape, surface):
    pass

def draw_window(surface):
    pass

def main():
    pass

def main_menu():
    pass

main_menu()  # start game

Piece Class

Since we will be creating multiple shapes it makes sense to create a piece class that can store some information about each shape.

class Piece(object):
    rows = 20  # y
    columns = 10  # x
 
    def __init__(self, column, row, shape):
        self.x = column
        self.y = row
        self.shape = shape
        self.color = shape_colors[shapes.index(shape)]
        self.rotation = 0  # number from 0-3

Creating a Grid

The way that we will keep track of pieces in the game is using a grid data structure. We will create a multidimensional list that contains 20 lists of 10 elements (rows and columns). Each element in the lists will be a tuple representing the color of the piece in that current position. This will allow us to draw all of the colored squares quite easily as we can simply loop through the multidimensional list.

The locked position parameter will contain a dictionary of key value pairs where each key is a position of a piece that has already fallen and each value is its color. We will loop through these locked positions and modify our blank grid to show these pieces.

def create_grid(locked_positions={}):
    grid = [[(0,0,0) for x in range(10)] for x in range(20)]
 
    for i in range(len(grid)):
        for j in range(len(grid[i])):
            if (j,i) in locked_positions:
                c = locked_positions[(j,i)]
                grid[i][j] = c
    return grid

Getting a Random Shape

Since we will be dropping shapes down the screen at random we need to generate a random shape. This will be done in the get_shape() function.

def get_shape():
    global shapes, shape_colors
 
    return Piece(5, 0, random.choice(shapes))

Drawing the Grid

I am not going to explain all of the pygame functions and methods I use as if you are familair with pygame you should know them. However, if you’d like to learn more about the basics of pygame click here!

We will simply be calling the function below to draw all of our objects to the screen. In this function we call some functions that we will be coding later.

    surface.fill((0,0,0))
    # Tetris Title
    font = pygame.font.SysFont('comicsans', 60)
    label = font.render('TETRIS', 1, (255,255,255))
 
    surface.blit(label, (top_left_x + play_width / 2 - (label.get_width() / 2), 30))
 
    for i in range(len(grid)):
        for j in range(len(grid[i])):
            pygame.draw.rect(surface, grid[i][j], (top_left_x + j* 30, top_left_y + i * 30, 30, 30), 0)
 
    # draw grid and border
    draw_grid(surface, 20, 10)
    pygame.draw.rect(surface, (255, 0, 0), (top_left_x, top_left_y, play_width, play_height), 5)
    pygame.display.update()

The Game Loop

In every game we have something called a game loop or a main loop. This is what will be running constantly and checking to see if events occur. Our game loop will go inside the main() function.

In this function we will start by defining some variables and then move into the while loop. Inside the while loop we will check for key press events and see if the user wants to exit the game.

When the user presses the up arrow key the piece will rotate. We can do this by simply increasing our shapes rotation attribute to be the next shape in the list we set up at the beginning of the program.

When the user hits the left or right arrow keys we will move accordingly bu changing the x value of our piece.

Finally when the user hist the down arrow key we will move down one square allowing the user to increase the speed at which the shape falls.

def main():
    global grid
 
    locked_positions = {}  # (x,y):(255,0,0)
    grid = create_grid(locked_positions)
 
    change_piece = False
    run = True
    current_piece = get_shape()
    next_piece = get_shape()
    clock = pygame.time.Clock()
    fall_time = 0
 
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
                pygame.display.quit()
                quit()
 
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    current_piece.x -= 1
                    if not valid_space(current_piece, grid):
                        current_piece.x += 1
 
                elif event.key == pygame.K_RIGHT:
                    current_piece.x += 1
                    if not valid_space(current_piece, grid):
                        current_piece.x -= 1
                elif event.key == pygame.K_UP:
                    # rotate shape
                    current_piece.rotation = current_piece.rotation + 1 % len(current_piece.shape)
                    if not valid_space(current_piece, grid):
                        current_piece.rotation = current_piece.rotation - 1 % len(current_piece.shape)
 
                if event.key == pygame.K_DOWN:
                    # move shape down
                    current_piece.y += 1
                    if not valid_space(current_piece, grid):
                        current_piece.y -= 1

        draw_window(win)

We will be adding more to this function in the future.

Setting up The Window

The last thing we need to do for this tutorial is setup the pygame window and give it a caption. This will go at the very end of the program, not within any function.

win = pygame.display.set_mode((s_width, s_height))
pygame.display.set_caption('Tetris')

Testing The Program

If you’d like to test the program you can simply call main() at the very end of the program like so.

Игра Тетрис – одна из самых популярных компьютерных игр. Оригинальная игра была разработана и запрограммирована русским программистом Алексеем Пажитновым в 1985 году. С тех пор Тетрис доступен на почти каждой компьютерной платформе в множестве вариаций.

Создание простой компьютерной игры на PyQt5 – отличный способ повышения навыков программирования.

Тетрисом называется игра-головоломка с падающими блоками. В этой игре, мы имеем 7 разных фигур, называемых так: S-фигура, Z-фигура, T-фигура, L-фигура, фигура-линия, фигура «Г», и квадрат. Каждая из этих фигур формируется с помощью четырёх квадратиков. Фигуры падают вниз на доску. Цель игры Тетрис – перемещать и вращать фигуры так, чтобы их приземлилось как можно больше. Если мы сумеем сформировать ряд, ряд разрушается и мы получаем очки. Мы играем в Тетрис до тех пор, пока не достигнем верха.

Разработка

У нас нет изображений для нашего Тетриса, мы рисуем тетрамино, используя доступное в программном инструментарии PyQt5 инструменты рисования. У каждой компьютерной игры имеется математическая модель. Также и в Тетрисе.

Некоторые идеи, применяющиеся в игре:

  • Мы используем QtCore.QBasicTimer(), чтобы создать игровой цикл.
  • Тетрамино рисуются.
  • Фигуры перемещаются по принципу «кубик за кубиком» (не «пиксель за пикселем»).
  • Математически, доска – это просто список чисел.

Код содержит четыре класса: Tetris, Board, Tetrominoe и Shape. Класс Tetris организовывает игру. Board – это то, где пишется игровая логика. Класс Tetrominoe содержит имена всех частей тетриса и класс Shape содержит код для частей тетриса.

Код игры Тетрис:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys, random
from PyQt5.QtWidgets import QMainWindow, QFrame, QDesktopWidget, QApplication
from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QPainter, QColor


class Tetris(QMainWindow):

    def __init__(self):
        super().__init__()

        self.initUI()


    def initUI(self):

        self.tboard = Board(self)
        self.setCentralWidget(self.tboard)

        self.statusbar = self.statusBar()
        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)

        self.tboard.start()

        self.resize(180, 380)
        self.center()
        self.setWindowTitle('Tetris')
        self.show()


    def center(self):

        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2,
            (screen.height()-size.height())/2)


class Board(QFrame):

    msg2Statusbar = pyqtSignal(str)

    BoardWidth = 10
    BoardHeight = 22
    Speed = 300

    def __init__(self, parent):
        super().__init__(parent)

        self.initBoard()


    def initBoard(self):

        self.timer = QBasicTimer()
        self.isWaitingAfterLine = False

        self.curX = 0
        self.curY = 0
        self.numLinesRemoved = 0
        self.board = []

        self.setFocusPolicy(Qt.StrongFocus)
        self.isStarted = False
        self.isPaused = False
        self.clearBoard()


    def shapeAt(self, x, y):
        return self.board[(y * Board.BoardWidth) + x]


    def setShapeAt(self, x, y, shape):
        self.board[(y * Board.BoardWidth) + x] = shape


    def squareWidth(self):
        return self.contentsRect().width() // Board.BoardWidth


    def squareHeight(self):
        return self.contentsRect().height() // Board.BoardHeight


    def start(self):

        if self.isPaused:
            return

        self.isStarted = True
        self.isWaitingAfterLine = False
        self.numLinesRemoved = 0
        self.clearBoard()

        self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.newPiece()
        self.timer.start(Board.Speed, self)


    def pause(self):

        if not self.isStarted:
            return

        self.isPaused = not self.isPaused

        if self.isPaused:
            self.timer.stop()
            self.msg2Statusbar.emit("paused")

        else:
            self.timer.start(Board.Speed, self)
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

        self.update()


    def paintEvent(self, event):

        painter = QPainter(self)
        rect = self.contentsRect()

        boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()

        for i in range(Board.BoardHeight):
            for j in range(Board.BoardWidth):
                shape = self.shapeAt(j, Board.BoardHeight - i - 1)

                if shape != Tetrominoe.NoShape:
                    self.drawSquare(painter,
                        rect.left() + j * self.squareWidth(),
                        boardTop + i * self.squareHeight(), shape)

        if self.curPiece.shape() != Tetrominoe.NoShape:

            for i in range(4):

                x = self.curX + self.curPiece.x(i)
                y = self.curY - self.curPiece.y(i)
                self.drawSquare(painter, rect.left() + x * self.squareWidth(),
                    boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
                    self.curPiece.shape())


    def keyPressEvent(self, event):

        if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
            super(Board, self).keyPressEvent(event)
            return

        key = event.key()

        if key == Qt.Key_P:
            self.pause()
            return

        if self.isPaused:
            return

        elif key == Qt.Key_Left:
            self.tryMove(self.curPiece, self.curX - 1, self.curY)

        elif key == Qt.Key_Right:
            self.tryMove(self.curPiece, self.curX + 1, self.curY)

        elif key == Qt.Key_Down:
            self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)

        elif key == Qt.Key_Up:
            self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)

        elif key == Qt.Key_Space:
            self.dropDown()

        elif key == Qt.Key_D:
            self.oneLineDown()

        else:
            super(Board, self).keyPressEvent(event)


    def timerEvent(self, event):

        if event.timerId() == self.timer.timerId():

            if self.isWaitingAfterLine:
                self.isWaitingAfterLine = False
                self.newPiece()
            else:
                self.oneLineDown()

        else:
            super(Board, self).timerEvent(event)


    def clearBoard(self):

        for i in range(Board.BoardHeight * Board.BoardWidth):
            self.board.append(Tetrominoe.NoShape)


    def dropDown(self):

        newY = self.curY

        while newY > 0:

            if not self.tryMove(self.curPiece, self.curX, newY - 1):
                break

            newY -= 1

        self.pieceDropped()


    def oneLineDown(self):

        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
            self.pieceDropped()


    def pieceDropped(self):

        for i in range(4):

            x = self.curX + self.curPiece.x(i)
            y = self.curY - self.curPiece.y(i)
            self.setShapeAt(x, y, self.curPiece.shape())

        self.removeFullLines()

        if not self.isWaitingAfterLine:
            self.newPiece()


    def removeFullLines(self):

        numFullLines = 0
        rowsToRemove = []

        for i in range(Board.BoardHeight):

            n = 0
            for j in range(Board.BoardWidth):
                if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                    n = n + 1

            if n == 10:
                rowsToRemove.append(i)

        rowsToRemove.reverse()


        for m in rowsToRemove:

            for k in range(m, Board.BoardHeight):
                for l in range(Board.BoardWidth):
                        self.setShapeAt(l, k, self.shapeAt(l, k + 1))

        numFullLines = numFullLines + len(rowsToRemove)

        if numFullLines > 0:

            self.numLinesRemoved = self.numLinesRemoved + numFullLines
            self.msg2Statusbar.emit(str(self.numLinesRemoved))

            self.isWaitingAfterLine = True
            self.curPiece.setShape(Tetrominoe.NoShape)
            self.update()


    def newPiece(self):

        self.curPiece = Shape()
        self.curPiece.setRandomShape()
        self.curX = Board.BoardWidth // 2 + 1
        self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

        if not self.tryMove(self.curPiece, self.curX, self.curY):

            self.curPiece.setShape(Tetrominoe.NoShape)
            self.timer.stop()
            self.isStarted = False
            self.msg2Statusbar.emit("Game over")



    def tryMove(self, newPiece, newX, newY):

        for i in range(4):

            x = newX + newPiece.x(i)
            y = newY - newPiece.y(i)

            if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
                return False

            if self.shapeAt(x, y) != Tetrominoe.NoShape:
                return False

        self.curPiece = newPiece
        self.curX = newX
        self.curY = newY
        self.update()

        return True


    def drawSquare(self, painter, x, y, shape):

        colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
                      0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]

        color = QColor(colorTable[shape])
        painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
            self.squareHeight() - 2, color)

        painter.setPen(color.lighter())
        painter.drawLine(x, y + self.squareHeight() - 1, x, y)
        painter.drawLine(x, y, x + self.squareWidth() - 1, y)

        painter.setPen(color.darker())
        painter.drawLine(x + 1, y + self.squareHeight() - 1,
            x + self.squareWidth() - 1, y + self.squareHeight() - 1)
        painter.drawLine(x + self.squareWidth() - 1,
            y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)


class Tetrominoe(object):

    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7


class Shape(object):

    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ((0, -1),    (0, 0),     (1, 0),     (1, 1)),
        ((0, -1),    (0, 0),     (0, 1),     (0, 2)),
        ((-1, 0),    (0, 0),     (1, 0),     (0, 1)),
        ((0, 0),     (1, 0),     (0, 1),     (1, 1)),
        ((-1, -1),   (0, -1),    (0, 0),     (0, 1)),
        ((1, -1),    (0, -1),    (0, 0),     (0, 1))
    )

    def __init__(self):

        self.coords = [[0,0] for i in range(4)]
        self.pieceShape = Tetrominoe.NoShape

        self.setShape(Tetrominoe.NoShape)


    def shape(self):
        return self.pieceShape


    def setShape(self, shape):

        table = Shape.coordsTable[shape]

        for i in range(4):
            for j in range(2):
                self.coords[i][j] = table[i][j]

        self.pieceShape = shape


    def setRandomShape(self):
        self.setShape(random.randint(1, 7))


    def x(self, index):
        return self.coords[index][0]


    def y(self, index):
        return self.coords[index][1]


    def setX(self, index, x):
        self.coords[index][0] = x


    def setY(self, index, y):
        self.coords[index][1] = y


    def minX(self):

        m = self.coords[0][0]
        for i in range(4):
            m = min(m, self.coords[i][0])

        return m


    def maxX(self):

        m = self.coords[0][0]
        for i in range(4):
            m = max(m, self.coords[i][0])

        return m


    def minY(self):

        m = self.coords[0][1]
        for i in range(4):
            m = min(m, self.coords[i][1])

        return m


    def maxY(self):

        m = self.coords[0][1]
        for i in range(4):
            m = max(m, self.coords[i][1])

        return m


    def rotateLeft(self):

        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):

            result.setX(i, self.y(i))
            result.setY(i, -self.x(i))

        return result


    def rotateRight(self):

        if self.pieceShape == Tetrominoe.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):

            result.setX(i, -self.y(i))
            result.setY(i, self.x(i))

        return result


if __name__ == '__main__':

    app = QApplication([])
    tetris = Tetris()
    sys.exit(app.exec_())

Игра немного упрощена для более легкого понимания. Игра начинается сразу же после её запуска. Мы можем приостановить игру, нажав клавишу p. Клавиша Space будет немедленно бросать блок тетриса вниз. Игра идёт на постоянной скорости, ускорение не реализуется. Очки – это число линий, который мы удалили.

self.tboard = Board(self)
self.setCentralWidget(self.tboard)

Экземпляр класса Board создаётся и устанавливается центральным виджетом приложения.

self.statusbar = self.statusBar()
self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)

Мы создаём строку состояния, где мы будем отображать сообщения. Мы будем отображать три возможных сообщения: количество уже удалённых линий, сообщение паузы, или сообщение «Игра окончена». msgStatusbar – это пользовательский сигнал, который реализуется в классе Board. showMessage() – это встроенный метод, который отображает сообщение в строке состояния.

self.tboard.start()

Эта строка инициирует игру.

class Board(QFrame):

    msg2Statusbar = pyqtSignal(str)
    ...

Создаётся пользовательский сигнал. msgStatusbar – это сигнал, который срабатывает, когда мы хотим написать сообщение или количество очков в строку состояния.

BoardWidth = 10
BoardHeight = 22
Speed = 300

Это переменные класса Board. BoardWidth и BoardHeight определяют размер доски в блоках. Speed определяет скорость игры. Каждые 300 мс будет начинаться цикл новой игры.

...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...

В методе initBoard() мы инициализируем несколько важных переменных. Переменная self.board – это список чисел от 0 до 7. Она представляет местоположение различных фигур и оставляет фигуры на доске.

def shapeAt(self, x, y):
    return self.board[(y * Board.BoardWidth) + x]

Метод shapeAt() определяет тип фигуры в данном блоке.

def squareWidth(self):
    return self.contentsRect().width() // Board.BoardWidth

Доска может динамически менять размер (например, при изменении размера окна). Как следствие, размер блока может меняться. squareWidth() вычисляет ширину простого квадратика в пикселях и возвращает её. Board.BoardWidth – это размер доски в блоках.

for i in range(Board.BoardHeight):
    for j in range(Board.BoardWidth):
        shape = self.shapeAt(j, Board.BoardHeight - i - 1)

        if shape != Tetrominoe.NoShape:
            self.drawSquare(painter,
                rect.left() + j * self.squareWidth(),
                boardTop + i * self.squareHeight(), shape)

Рисование игры разделяется на два шага. Первым шагом, мы рисуем все фигуры, или оставляем фигуры, которые были сброшены вниз доски. Все квадратики запоминаются в списке переменных self.board. Доступ к переменной получают, используя метод shapeAt().

if self.curPiece.shape() != Tetrominoe.NoShape:

    for i in range(4):

        x = self.curX + self.curPiece.x(i)
        y = self.curY - self.curPiece.y(i)
        self.drawSquare(painter, rect.left() + x * self.squareWidth(),
            boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
            self.curPiece.shape())

Следующий шаг – это рисование упавших вниз частей.

elif key == Qt.Key_Right:
    self.tryMove(self.curPiece, self.curX + 1, self.curY)

В методе keyPressEvent(), мы проверяем нажатые клавиши. Если мы нажали клавишу правой стрелки, мы пробуем передвинуть часть вправо. Мы говорим «пробуем», поскольку часть может быть на правом крае.

elif key == Qt.Key_Up:
    self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)

Клавиша стрелки вверх будет поворачивать падающую часть влево.

elif key == Qt.Key_Space:
    self.dropDown()

Клавиша «Пробел» будет немедленно бросать падающую часть.

elif key == Qt.Key_D:
    self.oneLineDown()

Нажимая клавишу «d», часть спустится вниз на один блок. Это может быть использовано, чтобы слегка ускорить падение части.

def tryMove(self, newPiece, newX, newY):

    for i in range(4):

        x = newX + newPiece.x(i)
        y = newY - newPiece.y(i)

        if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
            return False

        if self.shapeAt(x, y) != Tetrominoe.NoShape:
            return False

    self.curPiece = newPiece
    self.curX = newX
    self.curY = newY
    self.update()
    return True

В методе tryMove(), мы пробуем переместить наши фигуры. Если фигура находится на краю доски или примыкает к некоторой другой части, мы возвращаем значение «Ложь». В противном случае, мы перемещаем текущую падающую часть в новую позицию.

def timerEvent(self, event):

    if event.timerId() == self.timer.timerId():

        if self.isWaitingAfterLine:
            self.isWaitingAfterLine = False
            self.newPiece()
        else:
            self.oneLineDown()

    else:
        super(Board, self).timerEvent(event)

В timerEvent (событии таймера), мы либо создаём новую фигуру после предыдущей, которая упала, либо мы передвигаем падающую часть на одну линию вниз.

def clearBoard(self):

    for i in range(Board.BoardHeight * Board.BoardWidth):
        self.board.append(Tetrominoe.NoShape)

Метод clearBoard() очищает доску путём установки Tetrominoe.Noshape на каждый блок доски.

def removeFullLines(self):

    numFullLines = 0
    rowsToRemove = []

    for i in range(Board.BoardHeight):

        n = 0
        for j in range(Board.BoardWidth):
            if not self.shapeAt(j, i) == Tetrominoe.NoShape:
                n = n + 1

        if n == 10:
            rowsToRemove.append(i)

    rowsToRemove.reverse()


    for m in rowsToRemove:

        for k in range(m, Board.BoardHeight):
            for l in range(Board.BoardWidth):
                    self.setShapeAt(l, k, self.shapeAt(l, k + 1))

    numFullLines = numFullLines + len(rowsToRemove)
    ...

Когда фигура падает, мы вызываем метод removeFullLines(). Мы обнаруживаем все полные линии и удаляем их. Обратите внимание, что мы развернули порядок удаляемых линий. В противном случае, это не будет работать правильно. В нашем случае, мы используем «никакую» гравитацию. Это означает, что части могут парить над пустыми промежутками.

def newPiece(self):

    self.curPiece = Shape()
    self.curPiece.setRandomShape()
    self.curX = Board.BoardWidth // 2 + 1
    self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

    if not self.tryMove(self.curPiece, self.curX, self.curY):

        self.curPiece.setShape(Tetrominoe.NoShape)
        self.timer.stop()
        self.isStarted = False
        self.msg2Statusbar.emit("Game over")

Метод newPiece() случайным образом создаёт новую часть тетриса. Если часть не может прийти в свою начальную позицию, игра заканчивается.

class Tetrominoe(object):
    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7

Класс Tetrominoe содержит в себе имена всех возможных фигур. Мы также имеем NoShape для пустого пространства.

class Shape(object):

    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ...
    )
    ...

Класс Shape хранит информацию о частях тетриса.

Набор coordsTable содержит в себе всевозможные значения координат наших частей тетриса. Это шаблон, из которого все части берут свои значения координат.

self.coords = [[0,0] for i in range(4)]

После создания, мы создаём пустой список координат. Список будет хранить координаты частей тетриса.

Координаты

Изображение выше поможет понять значения координат. Для примера, набор (0, -1), (0, 0), (-1, 0), (-1, -1) представляет S-фигуру. Схема иллюстрирует фигуру.

def rotateLeft(self):

    if self.pieceShape == Tetrominoe.SquareShape:
        return self

    result = Shape()
    result.pieceShape = self.pieceShape

    for i in range(4):

        result.setX(i, self.y(i))
        result.setY(i, -self.x(i))

    return result

Метод rotateLeft() поворачивает часть влево. Квадрат не должен поворачиваться. Вот почему мы просто возвращаем ссылку на текущий объект. Новая часть создаётся и её координаты устанавливаются в одну из повернутых частей.

Тетрис

Это была игра Тетрис в PyQt5 (а также перевод последней части туториала от zetcode).

The Python Tetris Game is a GUI based title matching puzzle game which is very easy to understand and use. Talking about the gameplay it’s all same as the real one.

The user has to manage the random sequence of Tetriminos. This Tetris Game In Python project, I will teach you on How To Make A Tetris Game In Python.

Tetris Python Code : Project Information

Project Name: Tetris Python Code
Language/s Used: Python (GUI) Based
Python version (Recommended): 2.x or 3.x
Database: None
Type: Python App
Developer: IT SOURCECODE
Updates: 0
Tetris Game Code in Python

A Tetris Game Python player has steps to be follow of playing the game, first the player has to move each one sideways and rotate quarter-turns to form a solid horizontal line without leaving gaps.

It disappears whenever such lines are formed. The user can only enter to next level if they cross the specific number by the game rules.

As the game progresses, the Tetriminos falls faster and faster. As a result, the game ends when the stack of Tetriminos reaches to the top of the field and no new Tetriminos are able to enter.

Anyway if you want level up your knowledge in programming especially games in python, try this new article I’ve made for you Code For Game in Python: Python Game Projects With Source Code

This Tetris Game In Python also includes a downloadable Tetris Game Source Code In Python, just find the downloadable source code below and click to start downloading.

To start Create Tetris In Python, makes sure that you have PyCharm IDE installed in you computer.

By the way if you are new to python programming and you don’t know what would be the the Python IDE to use, I have here a list of Best Python IDE for Windows, Linux, Mac OS that will suit for you. I also have here How to Download and Install Latest Version of Python on Windows.

Steps on how to create a Tetris Game Code in Python

Time needed: 5 minutes.

These are the Steps on how to create a Tetris Game Code in Python

  • Step 1: Create a project name.

    First open Pycharm IDE and then create a “project name” after creating a project name click the “create” button.
    Tetris In Python Code Project Name

  • Step 2: Create a python file.

    Second after creating a project name, “right click” your project name and then click “new” after that click the “python file“.
    Tetris In Python Code Python File

  • Step 3: Name your python file.

    Third after creating a python file, Name your python file after that click “enter“.
    Tetris In Python Code Python File Name

  • Step 4: The Actual Code.

    You are free to copy the code given below and download the full source code below.

Code Explanations

1. Installation of Pygame

Code:

pip install pygame

2. Importing PyGame

Code:

import pygame

3. The Code Given Below Is For The Class Game

Code:

    class Game(object):
    def main(self, screen):
        clock = pygame.time.Clock()

        self.matris = Matris()
        
        screen.blit(construct_nightmare(screen.get_size()), (0,0))
        
        matris_border = Surface((MATRIX_WIDTH*BLOCKSIZE+BORDERWIDTH*2, VISIBLE_MATRIX_HEIGHT*BLOCKSIZE+BORDERWIDTH*2))
        matris_border.fill(BORDERCOLOR)
        screen.blit(matris_border, (MATRIS_OFFSET,MATRIS_OFFSET))
        
        self.redraw()

        while True:
            try:
                timepassed = clock.tick(50)
                if self.matris.update((timepassed / 1000.) if not self.matris.paused else 0):
                    self.redraw()
            except GameOver:
                return
      

    def redraw(self):
        if not self.matris.paused:
            self.blit_next_tetromino(self.matris.surface_of_next_tetromino)
            self.blit_info()

            self.matris.draw_surface()

        pygame.display.flip()


    def blit_info(self):
        textcolor = (255, 255, 255)
        font = pygame.font.Font(None, 30)
        width = (WIDTH-(MATRIS_OFFSET+BLOCKSIZE*MATRIX_WIDTH+BORDERWIDTH*2)) - MATRIS_OFFSET*2

        def renderpair(text, val):
            text = font.render(text, True, textcolor)
            val = font.render(str(val), True, textcolor)

            surf = Surface((width, text.get_rect().height + BORDERWIDTH*2), pygame.SRCALPHA, 32)

            surf.blit(text, text.get_rect(top=BORDERWIDTH+10, left=BORDERWIDTH+10))
            surf.blit(val, val.get_rect(top=BORDERWIDTH+10, right=width-(BORDERWIDTH+10)))
            return surf

        scoresurf = renderpair("Score", self.matris.score)
        levelsurf = renderpair("Level", self.matris.level)
        linessurf = renderpair("Lines", self.matris.lines)
        combosurf = renderpair("Combo", "x{}".format(self.matris.combo))

        height = 20 + (levelsurf.get_rect().height + 
                       scoresurf.get_rect().height +
                       linessurf.get_rect().height + 
                       combosurf.get_rect().height )

        area = Surface((width, height))
        area.fill(BORDERCOLOR)
        area.fill(BGCOLOR, Rect(BORDERWIDTH, BORDERWIDTH, width-BORDERWIDTH*2, height-BORDERWIDTH*2))

        area.blit(levelsurf, (0,0))
        area.blit(scoresurf, (0, levelsurf.get_rect().height))
        area.blit(linessurf, (0, levelsurf.get_rect().height + scoresurf.get_rect().height))
        area.blit(combosurf, (0, levelsurf.get_rect().height + scoresurf.get_rect().height + linessurf.get_rect().height))

        screen.blit(area, area.get_rect(bottom=HEIGHT-MATRIS_OFFSET, centerx=TRICKY_CENTERX))


    def blit_next_tetromino(self, tetromino_surf):
        area = Surface((BLOCKSIZE*5, BLOCKSIZE*5))
        area.fill(BORDERCOLOR)
        area.fill(BGCOLOR, Rect(BORDERWIDTH, BORDERWIDTH, BLOCKSIZE*5-BORDERWIDTH*2, BLOCKSIZE*5-BORDERWIDTH*2))

        areasize = area.get_size()[0]
        tetromino_surf_size = tetromino_surf.get_size()[0]
        # ^^ I'm assuming width and height are the same

        center = areasize/2 - tetromino_surf_size/2
        area.blit(tetromino_surf, (center, center))

        screen.blit(area, area.get_rect(top=MATRIS_OFFSET, centerx=TRICKY_CENTERX))

Explanation:

In this class which is declare the following module:

  • main – The main module of the class Game
  • redraw – In this module which is the draw surface
  • blit_info – In this module which is declaring the text color, font and width
  • renderpair – In this module which is displayed the score, level ,lines and combo of the game in changing process of time.
  • blit_next_tetromino – In this module which is declaring the size of the surface

Code:

class Menu(object):
    running = True
    def main(self, screen):
        clock = pygame.time.Clock()
        menu = kezmenu.KezMenu(
            ['Play!', lambda: Game().main(screen)],
            ['Quit', lambda: setattr(self, 'running', False)],
        )
        menu.position = (50, 50)
        menu.enableEffect('enlarge-font-on-focus', font=None, size=60, enlarge_factor=1.2, enlarge_time=0.3)
        menu.color = (255,255,255)
        menu.focus_color = (40, 200, 40)

        nightmare = construct_nightmare(screen.get_size())
        highscoresurf = self.construct_highscoresurf()

        timepassed = clock.tick(30) / 1000.

        while self.running:
            events = pygame.event.get()

            for event in events:
                if event.type == pygame.QUIT:
                    exit()

            menu.update(events, timepassed)

            timepassed = clock.tick(30) / 1000.

            if timepassed > 1: # A game has most likely been played 
                highscoresurf = self.construct_highscoresurf()

            screen.blit(nightmare, (0,0))
            screen.blit(highscoresurf, highscoresurf.get_rect(right=WIDTH-50, bottom=HEIGHT-50))
            menu.draw(screen)
            pygame.display.flip()

    def construct_highscoresurf(self):
        font = pygame.font.Font(None, 50)
        highscore = load_score()
        text = "Highscore: {}".format(highscore)
        return font.render(text, True, (255,255,255))

def construct_nightmare(size):
    surf = Surface(size)

    boxsize = 8
    bordersize = 1
    vals = '1235' # only the lower values, for darker colors and greater fear
    arr = pygame.PixelArray(surf)
    for x in range(0, len(arr), boxsize):
        for y in range(0, len(arr[x]), boxsize):

            color = int(''.join([random.choice(vals) + random.choice(vals) for _ in range(3)]), 16)

            for LX in range(x, x+(boxsize - bordersize)):
                for LY in range(y, y+(boxsize - bordersize)):
                    if LX < len(arr) and LY < len(arr[x]):
                        arr[LX][LY] = color
    del arr
    return surf

Explanation:

In this class which is declare the following module:

  • main – The main module of the class menu that you see in the first page when you run the game.
  • construct_highscoresurf – In this module which is declaring the font design and highscore of the game.
  • construct_nightmare – In this module which is declaring the size of the surface and its conditions.

5. The Code Given Below Is For The Class Game Over

Code:

class GameOver(Exception):
    """Exception used for its control flow properties"""

def get_sound(filename):
    return pygame.mixer.Sound(os.path.join(os.path.dirname(__file__), "resources", filename))

BGCOLOR = (15, 15, 20)
BORDERCOLOR = (140, 140, 140)

BLOCKSIZE = 30
BORDERWIDTH = 10

MATRIS_OFFSET = 20

MATRIX_WIDTH = 10
MATRIX_HEIGHT = 22

LEFT_MARGIN = 340

WIDTH = MATRIX_WIDTH*BLOCKSIZE + BORDERWIDTH*2 + MATRIS_OFFSET*2 + LEFT_MARGIN
HEIGHT = (MATRIX_HEIGHT-2)*BLOCKSIZE + BORDERWIDTH*2 + MATRIS_OFFSET*2

TRICKY_CENTERX = WIDTH-(WIDTH-(MATRIS_OFFSET+BLOCKSIZE*MATRIX_WIDTH+BORDERWIDTH*2))/2

VISIBLE_MATRIX_HEIGHT = MATRIX_HEIGHT - 2

Explanation:

In this class which is declare the following module:

  • get_sound – This module which is getting the sound when you are already game over.

Downloadable Source Code

I have here the list of Best Python Project with Source code free to download for free, I hope this can help you a lot.

Summary

The Tetris In Python Code is written in python programming language, Python is very smooth to research the syntax emphasizes readability and it is able to reduces time ingesting in developing.

Also in this tutorial is the simplest way for the beginners or the student to enhance their logical skills in programming. and also in this game project is the way for the students or beginners in designing and developing games.

Inquiries

If you have any questions or suggestions about Python Tetris Game with Source Code, please feel free to leave a comment below.

24 min read

Apply for Byte Academy’s Coding Bootcamp with ISA options

pygame is a Free and Open Source python programming language library for making multimedia applications like games built on top of the excellent SDL (Simple DirectMedia Layer) library. Simple DirectMedia Layer is a cross-platform development library designed to provide low-level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D. Like SDL, pygame is highly portable and runs on nearly every platform and operating system. Millions of people have downloaded pygame itself, which is a whole lot of bits flying across the interwebs.

pygame.org (the website) welcomes all Python game, art, music, sound, video and multimedia projects.

History of Pygame

Pygame was originally written by Pete Shinners to replace PySDL after its development stalled. It has been a community project since 2000 and is released under the open source free software GNU Lesser General Public License.

Pygame Installation

Pygame requires Python. The best way to install pygame is with the pip tool (which is what python uses to install packages). Note, this comes with python in recent versions. We use the —user flag to tell it to install into the home directory, rather than globally.

Installation on Windows

For Windows, make sure you add Python 3.6.1 in the PATH, from there, you run the below command:

python3 -m pip install -U pygame —user

Installation on Ubuntu

For Ubuntu and many Linux distributions, they have their own Pygame package. Run the below command for these operating systems:

sudo apt-get install python3-pygame

Installation on Mac

There are issues pertaining in installation of Pygame on Mac. But it is available, if you start a virtual environment in your Mac system using the below command:

python3 m virtualenv anenv

Activate the virtual environment:

. ./anenv/bin/activate

The venvdotapp helps the python be a Mac ‘app’, so that the pygame window can get focus

python m pip install venvdotapp
venvdotapp
python m pip install pygame

Features of pygame

  • Silliness built in. Pygame is meant to make things fun. New silliness is added every 3.1415 seconds. Silliness is a fun feature added into pygame package, in order to denote the simplicity in coding a game using pygame.
  • Does not require OpenGL. With many people having broken OpenGL setups, requiring OpenGL exclusively will cut into your user base significantly. Pygame uses either opengl, directx, windib, X11, linux frame buffer, and many other different backends… including an ASCII art backend! OpenGL is often broken on linux systems, and also on windows systems — which is why professional games use multiple backends.
  • Multi-core CPUs can be used easily. With dual-core CPUs common, and 8 core CPUs cheaply available on desktop systems, making use of multi-core CPUs allows you to do more in your game. Selected pygame functions release the dreaded python GIL, which is something you can do from C code.
  • Uses optimized C, and Assembly code for core functions. C code is often 10-20 times faster than python code, and assembly code can easily be 100x or more times faster than python code.
  • Comes with many Operating systems. Just an apt-get, emerge, pkg_add, or yast install away.  No need to mess with installing it outside of your operating systems package manager. Comes with binary installers (and uninstallers) for Windows or MacOS X.
  • Truly portable. Supports Linux (pygame comes with most mainstream linux distributions), Windows (95,98,me,2000,XP,vista, 64bit windows etc), Windows CE, BeOS, MacOS, Mac OS X, FreeBSD, NetBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX.
  • It’s Simple and easy to use. Kids and adults make games with pygame.
  • Does not require a GUI to use all functions. You can use pygame without a monitor — like if you want to use it just to process images, get joystick input, or play sounds.
  • A small amount of code. It does not have hundreds of thousands of lines of code for things you won’t use anyway. The core is kept simple, and extra things like GUI libraries, and effects are developed separately outside of pygame.

Pros of Pygame

  • Easy Python syntax
  • Pygame uses Python as its scripting language. Python is widely considered one of the easiest languages to grasp even for beginners.
  • Very easy to understand
  • The API is very straightforward.
  • Good canvas system
  • Pygame has a drawing system that allows the user to create and draw on an unlimited number of canvases.

Cons of Pygame

  • Pygame does not scale well with large project games, but it works well with small games and hobby projects. For small-scale game development, pygame is a good option.
  • You might have a hard time finding out the basic functions that you require and their workings, but most of the functions in pygame has been incorporated as small apps across. You can get github programs related to Pygame. All those functions and their descriptions will be given in their code.
  • As I have said before, as there is not much description about the functions and their uses, Pygame also lacks the Pygame community and Pygame developers. Those who develop their small-scale applications in Pygame, put up in Github, but the reliability of those developers is still doubtful
  • Most of the game that we play, has a certain amount of Physics, AI, networking and inputs involved. In Pygame, we won’t be having these complexities as it is not supported by Pygame.
  • Pygame uses an old version of SDL, hence it can be said to be outdated. Some of the features used by SDL2 are also not supported in Pygame. But still remember Old is Gold.

Let us build a Tetris game using Pygame

from random import randrange as rand

import pygame, sys

import getpass

# The configuration

cell_size =    18

cols =    10

rows =    22

maxfps =     30

colors = [

(0,   0, 0  ),

(255, 85,  85),

(100, 200, 115),

(120, 108, 245),

(255, 140, 50 ),

(50,  120, 52 ),

(146, 202, 73 ),

(150, 161, 218 ),

(35,  35, 35)

]

# Define the shapes of the single parts

tetris_shapes = [

   [[1, 1, 1],

    [0, 1, 0]],

   [[0, 2, 2],

    [2, 2, 0]],

   [[3, 3, 0],

    [0, 3, 3]],

   [[4, 0, 0],

    [4, 4, 4]],

   [[0, 0, 5],

    [5, 5, 5]],

   [[6, 6, 6, 6]],

   [[7, 7],

    [7, 7]]

]

def rotate_clockwise(shape):

   return [ [ shape[y][x]

   for y in range(len(shape)) ]

   for x in range(len(shape[0]) — 1, -1, -1) ]

def check_collision(board, shape, offset):

   off_x, off_y = offset

   for cy, row in enumerate(shape):

   for cx, cell in enumerate(row):

   try:

   if cell and board[ cy + off_y ][ cx + off_x ]:

   return True

   except IndexError:

   return True

   return False

def remove_row(board, row):

   del board[row]

   return [[0 for i in range(cols)]] + board

def join_matrixes(mat1, mat2, mat2_off):

   off_x, off_y = mat2_off

   for cy, row in enumerate(mat2):

   for cx, val in enumerate(row):

   mat1[cy+off_y-1    ][cx+off_x] += val

   return mat1

def new_board():

   board = [ [ 0 for x in range(cols) ]

   for y in range(rows) ]

   board += [[ 1 for x in range(cols)]]

   return board

class TetrisApp(object):

   def __init__(self):

   pygame.init()

   pygame.key.set_repeat(250,25)

   self.width = cell_size*(cols+6)

   self.height = cell_size*rows

   self.rlim = cell_size*cols

   self.bground_grid = [[ 8 if x%2==y%2 else 0 for x in range(cols)] for y in range(rows)]

   self.default_font =  pygame.font.Font(

   pygame.font.get_default_font(), 12)

   self.screen = pygame.display.set_mode((self.width, self.height))

   pygame.event.set_blocked(pygame.MOUSEMOTION) # We do not need

                                             # mouse movement

                                             # events, so we

                                             # block them.

   self.next_stone = tetris_shapes[rand(len(tetris_shapes))]

   self.init_game()

   def new_stone(self):

   self.stone = self.next_stone[:]

   self.next_stone = tetris_shapes[rand(len(tetris_shapes))]

   self.stone_x = int(cols / 2 — len(self.stone[0])/2)

   self.stone_y = 0

   if check_collision(self.board,

                   self.stone,

                   (self.stone_x, self.stone_y)):

   self.gameover = True

   def init_game(self):

   self.board = new_board()

   self.new_stone()

   self.level = 1

   self.score = 0

   self.lines = 0

   pygame.time.set_timer(pygame.USEREVENT+1, 1000)

   def disp_msg(self, msg, topleft):

   x,y = topleft

   for line in msg.splitlines():

   self.screen.blit(

   self.default_font.render(

   line,

   False,

   (255,255,255),

   (0,0,0)),

   (x,y))

   y+=14

   def center_msg(self, msg):

   for i, line in enumerate(msg.splitlines()):

   msg_image =  self.default_font.render(line, False,

   (255,255,255), (0,0,0))

   msgim_center_x, msgim_center_y = msg_image.get_size()

   msgim_center_x //= 2

   msgim_center_y //= 2

   self.screen.blit(msg_image, (

     self.width // 2-msgim_center_x,

     self.height // 2-msgim_center_y+i*22))

   def draw_matrix(self, matrix, offset):

   off_x, off_y  = offset

   for y, row in enumerate(matrix):

   for x, val in enumerate(row):

   if val:

   pygame.draw.rect(

   self.screen,

   colors[val],

   pygame.Rect(

   (off_x+x) *

     cell_size,

   (off_y+y) *

     cell_size,

   cell_size,

   cell_size),0)

   def add_cl_lines(self, n):

   linescores = [0, 40, 100, 300, 1200]

   self.lines += n

   self.score += linescores[n] * self.level

   if self.lines >= self.level*6:

   self.level += 1

   newdelay = 1000-50*(self.level-1)

   newdelay = 100 if newdelay < 100 else newdelay

   pygame.time.set_timer(pygame.USEREVENT+1, newdelay)

   def move(self, delta_x):

   if not self.gameover and not self.paused:

   new_x = self.stone_x + delta_x

   if new_x < 0:

   new_x = 0

   if new_x > cols — len(self.stone[0]):

   new_x = cols — len(self.stone[0])

   if not check_collision(self.board,

                       self.stone,

                       (new_x, self.stone_y)):

   self.stone_x = new_x

   def quit(self):

   self.center_msg(«Exiting…»)

   pygame.display.update()

   sys.exit()

   def drop(self, manual):

   if not self.gameover and not self.paused:

   self.score += 1 if manual else 0

   self.stone_y += 1

   if check_collision(self.board,

                   self.stone,

                   (self.stone_x, self.stone_y)):

   self.board = join_matrixes(

     self.board,

     self.stone,

     (self.stone_x, self.stone_y))

   self.new_stone()

   cleared_rows = 0

   while True:

   for i, row in enumerate(self.board[:-1]):

   if 0 not in row:

   self.board = remove_row(

     self.board, i)

   cleared_rows += 1

   break

   else:

   break

   self.add_cl_lines(cleared_rows)

   return True

   return False

   def insta_drop(self):

   if not self.gameover and not self.paused:

   while(not self.drop(True)):

   pass

   def rotate_stone(self):

   if not self.gameover and not self.paused:

   new_stone = rotate_clockwise(self.stone)

   if not check_collision(self.board,

                       new_stone,

                       (self.stone_x, self.stone_y)):

   self.stone = new_stone

   def toggle_pause(self):

   self.paused = not self.paused

   def start_game(self):

   if self.gameover:

   self.init_game()

   self.gameover = False

   def run(self):

   key_actions = {

   ‘ESCAPE’:    self.quit,

   ‘LEFT’:    lambda:self.move(-1),

   ‘RIGHT’:    lambda:self.move(+1),

   ‘DOWN’:    lambda:self.drop(True),

   ‘UP’:    self.rotate_stone,

   ‘p’:    self.toggle_pause,

   ‘SPACE’:    self.start_game,

   ‘RETURN’:    self.insta_drop

   }

   self.gameover = False

   self.paused = False

   dont_burn_my_cpu = pygame.time.Clock()

   while 1:

   self.screen.fill((0,0,0))

   if self.gameover:

   self.center_msg(«»»Game Over!nYour score: %d

Press space to continue»»» % self.score)

   else:

   if self.paused:

   self.center_msg(«Paused»)

   else:

   pygame.draw.line(self.screen,

   (255,255,255),

   (self.rlim+1, 0),

   (self.rlim+1, self.height-1))

   self.disp_msg(«Next:», (

   self.rlim+cell_size,

   2))

   self.disp_msg(«Score: %dnnLevel: %d

nLines: %d» % (self.score, self.level, self.lines),

   (self.rlim+cell_size, cell_size*5))

   self.draw_matrix(self.bground_grid, (0,0))

   self.draw_matrix(self.board, (0,0))

   self.draw_matrix(self.stone,

   (self.stone_x, self.stone_y))

   self.draw_matrix(self.next_stone,

   (cols+1,2))

   pygame.display.update()

   for event in pygame.event.get():

   if event.type == pygame.USEREVENT+1:

   self.drop(False)

   elif event.type == pygame.QUIT:

   self.quit()

   elif event.type == pygame.KEYDOWN:

   for key in key_actions:

   if event.key == eval(«pygame.K_»

   +key):

   key_actions[key]()

   dont_burn_my_cpu.tick(maxfps)

if __name__ == ‘__main__’:

   s = getpass.getpass()

   App = TetrisApp()

  App.run()

Snapshots of Tetris Game

build tetris in pygame

Running the Python Tetris program from the Ubuntu terminal

The Tetris game will open in the new window, which will allow us to use the arrow keys of the keyboard

The game continues… So does the fun.

Happy Gaming!

Want to expertise Python? Checkout Intro to Python and Python bootcamp offered by Byte Academy.

Topics:

coding exercises

Python

Programming Tips

Понравилась статья? Поделить с друзьями:
  • Как написать тесты на java
  • Как написать тестовую стратегию
  • Как написать тестовую документацию
  • Как написать тесто слоеное
  • Как написать тестбенч на верилог