Ни одна компьютерная игра не может похвастаться таким количеством копий и вариаций, как «Тетрис». Её история началась в 1984 году, когда русский программист Алексей Пажитнов, работавший тогда в Академии наук СССР, воодушевившись идеей детской головоломки «Пентомино», на компьютере «Электроника-60» написал первую версию. Вскоре 27-килобайтная игра была переписана для IBM PC будущим программистом Google Вадимом Герасимовым, и в 1985 году появилась ее цветная версия. С тех пор популярность «Тетриса» стала набирать обороты.
«Тетрис», несмотря на свой возраст, продолжает вполне успешно существовать на современных компьютерах и другой электронике. Как одна из первых и самых простых видеоигр и в нынешнее время популярна и приносит деньги своему создателю. На сегодняшний день у «Тетриса» более 100 млн. продаж, в том числе в виде приложений для смартфонов и планшетов. Вряд ли Candy Crush и другие игровые хиты были бы созданы, если бы не «Тетрис», который стал одной из самых успешных кроссплатформенных игровых линеек.
Итак, принцип работы заключается в выводе случайных фигурок, которые падают сверху в прямоугольный стакан шириной 10 и высотой 20 клеток. В полёте игрок может поворачивать фигурку на 90° и двигать её по горизонтали. Также можно «сбрасывать» фигурку, то есть ускорять её падение, когда уже решено, куда фигурка должна упасть. Фигурка летит до тех пор, пока не наткнётся на другую фигурку либо на дно стакана. Если при этом заполнился горизонтальный ряд из 10 клеток, он пропадает и всё, что выше него, опускается на одну клетку. Дополнительно показывается фигурка, которая будет следовать после текущей — это подсказка, которая позволяет игроку планировать действия. Темп игры постепенно увеличивается. Игра заканчивается, когда новая фигурка не может поместиться в стакан. Игрок получает очки за каждый заполненный ряд, поэтому его задача — заполнять ряды, не заполняя сам стакан (по вертикали) как можно дольше, чтобы таким образом получить как можно больше очков.
Программа предназначена для развлечения играющих, совершенствования их пространственной координации и развития логического мышления. Она может применяться в качестве игровой на разных типах персональных компьютеров. Популярность «Тетриса», объясняется его абстрактностью. Игра простая, поэтому играть в нее могут практически все, вне зависимости от возраста или способностей.
При разработке программы для игры «Тетрис» был использован объектно-ориентированный язык программирования C#. Графическое отображение было реализовано с помощью графических возможностей языка Visual C#.
Инструментальный набор Windows Forms предоставляет типы, необходимые для построения графических пользовательских интерфейсов для настольных компьютеров, создания специализированных элементов управления, управления ресурсами (например, строками и значками) и выполнения других задач, возникающих при программировании для пользовательских компьютеров. Также выбор разработки приложения с помощью Windows Forms не случаен и обусловлен дополнительными графическими преимуществами, что делают игру красочной и интересной. Также имеется и дополнительный API по имени GDI+, который предоставляет дополнительные типы, позволяющие программисту генерировать двухмерную графику, взаимодействовать с сетевыми принтерами и обрабатывать графические данные.
В созданном приложении можно познакомиться с моделью программирования Windows Forms, поработать с интегрированными конструкторами Visual Studio 2022, опробовать различные элементы управления WindowsForms и получить общее впечатление о графическом программировании с помощью GDI+.
Игровое поле представлено в виде двумерного массива map, размерностью 26*16. Рассмотрим элемент матрицы map[i][j] — данный элемент хранит цвет квадрата. Размер каждого такого квадрата — 32*32. Изначально все элементы матрицы map хранят белый цвет. В ходе игры при падении фигуры, матрица map будет изменяться. Массив how_much используется для хранения количества элементов в строках матрицы map, которые хранят цвета отличные от белого, то есть how_much[i] — количество элементов в строке i матрицы map. Использоваться данный массив будет для определения заполнения строки и последующего её уничтожения.
Фигура генерируется случайным образом (её расположение, тип, цвет). Для её реализации был создан отдельный класс Figure. Рассмотрим этот класс подробнее.
Класс Figure состоит из матрицы figure, координаты левого верхнего угла матрицы figure, цвета фигуры, её типа и размеры.
Любая фигура представима в виде матрицы из единиц (значит, что на данном месте находится часть фигуры) и нулей (то есть пустое пространство). Далее каждая часть фигуры будет представляться в виде круга заданного цвета, вписанного в квадрат размером 32*32, пустое пространство – белый квадрат, размер которого 32*32.
Шаблоны всех типов фигур расположены в матрице types_of_figures, их размеры в матрице size_of_figures.
Конструктор класса содержит параметры: координата левого верхнего угла, тип фигуры, цвет, а также матрицу map. Происходит присваивание соответствующих переменных, вызывается функция generation с параметром матрицы map.
Функция generation по типу фигуры устанавливает размеры матрицы figure и заполняет её по шаблону данного типа. Далее, если возможно, выполняется поворот фигуры на 90 градусов случайное количество раз, реализованное в функции swap.
Функция swap на входе получает карту игрового поля, т.е. матрицу map. Затем создаётся новая матрица, равная исходной. Новая матрица поворачивается на 90 градусов вправо. Затем проверяется, может ли полученная фигура с такой матрицей существовать на игровом поле. Это проверка происходит в функции check_swap. Описание данной функции можно найти ниже. Если проверка возвращает истину, то мы производим поворот матрицы figure на 90 градусов, меняем местами размерности матрицы.
Функция check_swap получает карту игрового поля и матрицу новой фигуры. Проверка происходит по следующему алгоритму:
- если хотя бы одна часть матрицы выходит за границы игрового поля, возвращается ложь;
- если матрица новой фигуры пересекается с матрицей игрового поля в тех местах, где в матрице фигуры находится единица, а в матрице игрового поля цвет, отличный от белого, возвращается ложь;
- если ни одно из этих условий не выполнилось, возвращается истина.
Фигура может двигаться вниз, влево и вправо. Соответственно реализованы функции down, left, right. В этих функция проверяется возможность такого сдвига, и, если он возможен, изменяем координату левого верхнего угла. Проверка производится в функции can_move.
Функция can_move получает карту игрового поля, и направление, в котором мы хотим двигать фигуру (-1 — влево, 0 — вниз, 1— вправо). Далее проверяется, не выйдет ли фигура за границы игрового поля и не встретит ли она препятствия в виде уже упавших фигур. Если оба условия выполнились, функция возвращает истину. Важно отметить, что если не возможно движение вниз, это означает, что дальнейшее движение фигуры невозможно, и она становиться частью игрового поля.
Для прорисовки фигуры реализована функция Paint. Она получает объект, на котором и будет производиться прорисовка, переменную типа PainEventArgs, а также булевскую переменную, которая показывает какую именно фигуру надо рисовать (текущую либо следующую), в зависимости от чего выбираются координаты. Прорисовка происходит следующим образом: если на figure[i][j] ==1, тогда рисуется круг, вписанный в квадрат. Координата верхнего левого угла, которая равна: у текущей фигуры — (координата Х левого верхнего угла матрицы figure + j)*32 , (координата Y левого верхнего угла матрицы figure + i)*32, у следующей — (18 + j)*32 , (18 + i)*32. Размер квадрата — 32*32.
Функция check_init вызывается при создании фигуры. Она проверяет, может ли данная фигура находится на игровом поле. Проверка происходит следующим образом: если есть пересечения с части фигуры с уже упавшими фигурами, функция возвращает ложь. Иначе возвращает истину.
Функция unreal вызывается, когда фигура не может двигаться вниз. Она получает на входе карту игрового поля map, и массив how_much. Элементы матрицы figure равные 1 добавляются в соответствующие места матрицы map (устанавливается цвет) и массива how_much (добавляется количество элементов равных 1 в нужную строчку).
Функция real возвращает истину, если фигура может двигаться.
На этом описание класса Figure закончено. Вернёмся к реализации игрового процесса. В игре присутствуют две переменные класса Figure: текущая и следующая. В игре присутствует таймер. Он отвечает за скорость перерисовки, т.е. за скорость движения фигуры. В реализации игры есть текущий уровень, количество уничтоженных линий, набранные очки. За эти данные отвечают переменные типа int. Для их визуализации используем Label. Все надписи находятся в groupbox, что делает интерфейс понятнее для пользователя. На начальных стадиях программы задаются размеры формы, надписей, их шрифты, расположение и т.д.
Функция Timer_Tick вызывается с каждым “тиком” таймера. В ней проверяется, не пора ли повысить уровень (уровень увеличивается после каждой 1000 набранных очков). При повышении уровня увеличивается скорость движения фигур (уменьшение интервала таймера). Вызывается функция сдвига текущей фигуры вниз, а затем перерисовка формы.
Функция Program_Paint - функция перерисовки формы. В ней задаётся текст надписей, рисуется карта игрового поля (уже упавшие фигуры). Рисование игрового поля происходит следующим образом: если map[i][j] — цвет, отличный от белого, рисуется круг этого цвета. Координата этого круга (координата левого верхнего угла квадрата 32*32 в который он вписан) соответствует точке с координатой (32*j, 32*i). Если же цвет — белый, рисуем белый квадрат.
Затем следует прорисовка текущей и следующей фигур. Если текущая фигура больше не может двигаться, тогда:
1. Увеличение количества очков за счёт упавшей фигуры.
2. Вызов метода unreal для текущей фигуры, для изменения игрового поля.
3. Делаем текущую фигуру равной следующей, создаём новую следующую фигуру.
4. Проверяем текущую фигуру с помощью метода check_init.
Если результат функции ложь (т.е. разместить её невозможно) вызываем функцию game_over. Иначе проверяем, нет ли заполненных строк. Данная проверка происходит следующим образом: каждый элемент массива how_much равный 15 удаляется, а также строка матрицы map, соответствующая этому элементу удаляется. Затем в начало массиваhow_much вставляется элемент равный 0, а в начало матрицу map — строка все элементы которой имеют белый цвет. Проигрывается мелодия уничтожения строки, начисляются очки за уничтоженный ряд. Количество начисляемых очков зависит от уровня. Увеличивается количество уничтоженных линий.
Функция game_over вызывается по окончанию игры. В ней вызывается новая форма Game_over, которой передаются набранные очки, количество уничтоженных линий, текущий уровень. Форма игры закрывается. Происходит переход к 3 этапу.
Функция Form1_KeyDown по нажатию определённой клавиши вызывает функции: сдвига текущей фигуры влево, вправо, ускорение вниз, поворота фигуры на 90 градусов вправо, вызов паузы и снятие с паузы.
При нажатии стрелки влево вызывается метод left текущей фигуры, при нажатии стрелки вправо — right, при нажатии стрелки вверх — swap. Ускорение вниз происходит при нажатии стрелки вниз, при этом уменьшается интервал таймера, за счёт чего и происходит ускорение движения. Кнопка P отвечает за вызов и снятие паузы. Во время паузы таймер останавливается, и стрелки не реагируют на нажатия.
Функция Form1_KeyUp вызывается при прекращении нажатия на одну из кнопок. В данной игре рассматривается лишь случай, когда прекращается нажатие стрелки вниз. Скорость падения становится обычной за счёт увеличения интервала таймера.
При запуске приложения пользователь видит окно, оформление и размеры которого могут изменяться непосредственно программистом в процессе создания программы и в соответствии с его вкусами, предпочтениями и дизайнерскими идеями (рисунок 1):
Рисунок 1 – Начальная страница игры
После нажатия кнопки «New Game» переходим на окно номер 2, которое предоставляет возможность начать сам процесс игры (рисунок 2).
Рисунок 2 – Начальный момент игры
Рисунок 3 демонстрирует промежуточный этап игры, из которого видно разнообразие цветовой гаммы выпадаемых фигур, уровень игры и количество набранных очков.
Рисунок 3 – Промежуточный результат
Спустя некоторое время игрок получит результат своих действий (рисунок 4), который означает завершение игры с выводом основных достижений (счёт, уровень, количество собранных линий).
Рисунок 4 – Финальное окно
В юности каждый все время ищет оппонентов — с кем сразиться, на кого возложить вину, кого уличить в ошибках. Молодой человек ко всему относится как к игре с нулевой суммой. Это шахматная установка — и она сильно сдерживает.
В «Тетрисе» же вы сражаетесь со временем, с бесконечным потоком фигурок, падающих сверху. Вы бросаете вызов самому себе. Тут некого винить. Эта игра, как и жизнь, протекает внутри вас. В ней нет плохих, которые причиняют вам страдания. Нет абсолютно правильных или неправильных решений. И ваш счет может расти до бесконечности, если вы заставляете себя работать. Так и в жизни: наш результат может расти быстро или медленно в зависимости от того, прилагаемых усилий.
В некоторых играх, чем дольше вы играете, тем труднее играть. В «Тетрисе» все не так. Эта игра никак не меняется с самого первого кубика и до тех пор, пока на экране не закончится место. Единственное, что меняется — это скорость. Если играть в «Тетрис» на минимальной скорости всю жизнь, никогда не проиграете. Единственный враг — это усталость. Но алгоритм победы в «Тетрисе» совсем не сложный. В «Тетрисе» мы чаще бросаем вызов самим себе. На каком-то этапе нас уже не устраивает убирать по одному ряду за раз. Мы хотим убрать сразу четыре (что и называется «Тетрис»). Наша жизнь становится быстрее. Время идет быстрее. Возможно один из способов, как способ овладеть мастерством жизни — как и игры в «Тетрис» — это научиться играть с той же степенью самоконтроля на высоких скоростях. Не подвергать риску свои цели, невзирая на темп вашего движения, а контролировать себя, свое поведение, свое время.