Как сделать тетрис в блокноте

Тетрис на JavaScript

Отме­няй­те все дела, пере­но­си­те встре­чи. Сего­дня мы дела­ем тет­рис, в кото­рый мож­но играть в бра­у­зе­ре и на кото­рый мож­но потра­тить весь день.

В чём идея

Пра­ви­ла игры все зна­ют: свер­ху в дву­мер­ный игро­вой ста­кан пада­ют фигу­ры раз­ной фор­мы, состав­лен­ные из модуль­ных бло­ков. Вни­зу бло­ки соеди­ня­ют­ся. Если собрать целую гори­зон­таль­ную линию из бло­ков, она исче­за­ет, все осталь­ные бло­ки сдви­га­ют­ся на ряд ниже.

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

Если вдруг не зна­е­те, как это рабо­та­ет, вот фраг­мент с чем­пи­о­на­та мира по тетрису:

Код не мой

Код, кото­рый мы раз­би­ра­ем в этом про­ек­те, напи­сал аме­ри­кан­ский раз­ра­бот­чик Сти­вен Ламберт:

В этой ста­тье мы объ­яс­ним, как этот код работает.

Неожиданная сложность

Самое глав­ное при про­грам­ми­ро­ва­нии такой игры — это как-то хра­нить содер­жи­мое игро­во­го экра­на и учи­ты­вать дви­же­ние фигур.

Если бы мы писа­ли эту игру на Unreal Engine или Unity, пер­вым инту­и­тив­ным реше­ни­ем было бы сде­лать для бло­ков какую-то сущ­ность типа объ­ек­та. У него были бы свой­ства — напри­мер, кон­фи­гу­ра­ция. Может быть, мы бы захо­те­ли потом сде­лать взры­ва­ю­щи­е­ся объ­ек­ты или объ­ек­ты с замо­роз­кой, объ­ек­ты с повы­шен­ной ско­ро­стью, отрав­лен­ные объ­ек­ты или что-то ещё в таком духе.

Но есть нюанс: смысл объ­ек­та в том, что он неде­ли­мый. А в «Тет­ри­се» все объ­ек­ты запро­сто делят­ся, когда мы «закры­ва­ем линию». У какой-нибудь Т-образной фигу­ры может запро­сто про­пасть хво­стик, а у Z-образной фигу­ры — ниж­няя перекладина.

Полу­ча­ет­ся, что фигу­ра в тет­ри­се выгля­дит как объ­ект, ино­гда ведёт себя как объ­ект, но не обла­да­ет свой­ства­ми объ­ек­та. Поэто­му объ­ект­ный под­ход нам здесь не подходит.

Реше­ние — пред­ста­вить игро­вое поле в виде дву­мер­но­го мас­си­ва нулей и еди­ниц. Ноль озна­ча­ет, что клет­ка сво­бод­на, а еди­ни­ца — что заня­та какой-то частью фигу­ры. Хра­нить и обра­ба­ты­вать дву­мер­ный мас­сив доволь­но про­сто, поэто­му реше­ние кажет­ся логичным.

Сами фигу­ры тоже пред­ста­вим в виде дву­мер­но­го мас­си­ва из нолей и еди­ниц, но осо­бым обра­зом — в виде квад­ра­та, где еди­ни­цы отве­ча­ют за части фигу­ры, а ноли — за пустое место:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Если вме­сто квад­ра­та про­сто взять фак­ти­че­ские раз­ме­ры фигу­ры и загнать их в мас­сив, то при вра­ще­нии они не вле­зут в исход­ный мас­сив. А внут­ри квад­ра­та их мож­но вра­щать как угод­но — раз­мер мас­си­ва от это­го не изменится:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Полу­ча­ет­ся, что если мы доба­вим в общий мас­сив с игро­вым цве­том пара­метр, кото­рый отве­ча­ет за цвет, то можем рисо­вать каж­дую фигу­ру сво­им цве­том. Так и сделаем.

Подготовка страницы

Игра будет рабо­тать на HTML-странице с помо­щью эле­мен­та Canvas — это холст, на кото­ром мы можем рисо­вать про­из­воль­ные фигу­ры через JavaScript.

Возь­мём пустую стра­ни­цу и сра­зу нари­су­ем на ней игро­вое поле. Сра­зу сде­ла­ем чёр­ный фон, игро­вое поле поста­вим по цен­тру, а его рам­ки сде­ла­ем белыми:

Всё осталь­ное сде­ла­ем скрип­том. Доба­вим тэг сра­зу после того, как нари­со­ва­ли холст, и нач­нём писать содер­жи­мое скрипта.

Заводим переменные и константы

Пока что всё просто:

Генерируем выпадающие фигуры

Пер­вое, что нам пона­до­бит­ся для это­го, — функ­ция, кото­рая выда­ёт слу­чай­ное чис­ло в задан­ном диа­па­зоне. По это­му чис­лу мы будем выби­рать фигуры.

Теперь мы можем создать после­до­ва­тель­ность из выпа­да­ю­щих фигур. Логи­ка будет такая:

Послед­ний этап в этом бло­ке — полу­чить из игро­вой после­до­ва­тель­но­сти, кото­рую мы толь­ко что сде­ла­ли, сле­ду­ю­щую фигу­ру, кото­рая у нас появит­ся. Мы долж­ны знать, что это за фигу­ра; как она рису­ет­ся; отку­да она начи­на­ет дви­же­ние. Обра­ти­те вни­ма­ние: на выхо­де мы полу­ча­ем не толь­ко дву­мер­ный мас­сив с фигу­рой, а ещё и назва­ние и её коор­ди­на­ты. Назва­ние нам нуж­но для того, что­бы знать, каким цве­том рисо­вать фигуру.

Движение, вращение и установка фигуры на место

В тет­ри­се мы можем вра­щать каж­дую фигу­ру на 90 гра­ду­сов по часо­вой стрел­ке сколь­ко угод­но раз. А так как у нас фигу­ра — это дву­мер­ный мас­сив из чисел, то быст­ро най­дём в интер­не­те гото­вый код для пово­ро­та чис­ло­вой матрицы:

После каж­до­го пово­ро­та и при каж­дой смене пози­ции нам нуж­но про­ве­рить, а может ли в прин­ци­пе фигу­ра так дви­гать­ся? Если дви­же­нию или вра­ще­нию меша­ют стен­ки поля или дру­гие фигу­ры, то нуж­но сооб­щить про­грам­ме, что такое дви­же­ние делать нель­зя. Даль­ше мы будем делать эту про­вер­ку перед тем, как что-то отри­со­вы­вать на экране.

Если про­вер­ка не про­шла, то мы не дела­ем послед­нее дви­же­ние, и фигу­ра про­сто про­дол­жа­ет падать вниз. Если ей неку­да падать и она упёр­лась в дру­гие, то нам нуж­но зафик­си­ро­вать это в игро­вом поле. Это зна­чит, что мы запи­сы­ва­ем в мас­сив, кото­рый отве­ча­ет за поле, нашу мат­ри­цу фигу­ры, про­пус­кая ноли и запи­сы­вая толь­ко единицы.

Как толь­ко фигу­ра вста­ла, нам нуж­но про­ве­рить, полу­чил­ся целый ряд или нет. Если полу­чил­ся — сдви­га­ем на один ряд вниз всё, что свер­ху. Такую про­вер­ку дела­ем каж­дый раз при уста­нов­ке фигу­ры и начи­на­ем с ниж­не­го ряда, под­ни­ма­ясь наверх.

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Что будет, когда мы проиграем

Когда фигу­ра при окон­ча­тель­ной уста­нов­ке выле­за­ет за гра­ни­цы игро­во­го поля, это зна­чит, что мы про­иг­ра­ли. За это у нас отве­ча­ет флаг gameOver, и его зада­ча — оста­но­вить ани­ма­цию игры.

Что­бы было понят­но, что игра закон­че­на, выве­дем над­пись GAME OVER! пря­мо поверх игро­во­го поля:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Обрабатываем нажатия на клавиши

Всё как в обыч­ном тет­ри­се: стрел­ки вле­во и впра­во дви­га­ют фигу­ру, стрел­ка вверх пово­ра­чи­ва­ет её на 90 гра­ду­сов, а стрел­ка вниз уско­ря­ет падение.

Един­ствен­ное, о чём нуж­но не забыть — после каж­до­го нажа­тия вызвать про­вер­ку, мож­но ли так дви­гать фигу­ру или нет.

Запускаем движения и анимацию

Смысл глав­но­го цик­ла игры такой:

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

Послед­нее, что нам оста­лось сде­лать, — запу­стить игру:

// старт игры rAF = requestAnimationFrame(loop);

Гото­вый резуль­тат мож­но посмот­реть на стра­ни­це с игрой.

Источник

[Перевод] Как я сделал игру для Блокнота

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Пока читал про необычные решения от инди-разработчиков, наткнулся на золото. Вот вам статья про игру в текстовом редакторе. Арт, анимация, сюжет — все как положено.

Я создал игру And yet it hurt (вероятно, автор хотел сказать it hurts, но мог использовать неправильный вариант намеренно, — прим.).

Все началось в 2017 году с вопроса: «Реально ли сделать игру в Блокноте?» Тогда я только усмехнулся. Прошло три года. Обдумав, как все будет работать, и убедившись, что это реально, я решил сделать эту игру.

Обычно вы жмете на кнопку, и в игре что-то происходит. Жмете А, и Марио прыгает. Все завязано на получении информации и отклике. Игра получает входные данные и выводит свои.

В игре для Блокнота на входе будут изменения, которые вносит пользователь в файл, а на выходе изменения, которые вносит в файл сама игра. Для этого приложение отслеживает время последнего сохранения файла. Если оно изменилось, игра считывает содержимое файла и вносит в него новые данные.

Возникает проблема: Блокнот от Microsoft не проверяет, был ли файл изменен. Пришлось бы сохранять файл, закрывать и открывать его заново. Создать такую игру возможно, но звучит не очень весело. Пришлось искать альтернативу.

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

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Альтернатива

Пришлось искать другой текстовый редактор. Единственным требованием было — автоматическое обновление файла. Хотя чуть позже вы увидите, что я использовал еще одну фичу.

Сначала на ум пришли Notepad++ и Sublime Text. Но они совсем не похожи на Блокнот внешне, очарование проекта развеялось бы окончательно. Плюс, они спрашивают игрока, хотел бы он обновить файл. Это куда лучше, чем закрывать и открывать файл, но все равно отвлекает от геймплея. Я хотел, чтобы файл обновлялся автоматически. Тогда мне на глаза попался Notepad2. Он был почти идеален.

Редактор можно настроить, чтобы он был похож на MS Блокнот, а главное — он проверяет изменения, внесенные в файл. Но также как Notepad++ и Sublime Text, Notepad2 спрашивает игрока, нужно ли изменить файл. К счастью, у редактора открытый код, и я мог отполировать его до совершенства.

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Notepad2 написан на C. Я немного знаком с этим языком, пусть меня и нельзя назвать экспертом. Опытный программист Javascript сможет прочитать и уловить общую суть кода, но понять исходный код Notepad2, чтобы внести необходимые изменения, оказалось не так просто.

Для начала я решил поискать текст из диалогового окна: «Файл был изменен внешней программой. Перезагрузить файл?». Это значение переменной, которая используется в качестве аргумента в функции диалогового окна. И я ее нашел.

Этот код проверяет, не изменилось ли содержимое файла. Если оно изменилось, открывается окно, и программа проверяет, выбрал ли пользователь ответ «Да». Мне нужно было лишь заменить кусок

на TRUE, и программа начала автоматически обновлять файл. Таким образом, я создал рендер на базе ASCII. Осталось создать подходящий движок.

Отрисовка

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Загрузка арта — это просто чтение файла.

Дом используется в качестве фона, поэтому я начал с прорисовки этого изображения на «экране». Экран в данном случае — это home.txt.

Я хотел, чтобы с птичкой можно было работать в таком ключе:

х — номер столбца, y — номер строки. Поэтому разбил экран и птицу на списки строк.

С птицей сделал то же самое. Теперь код, описывающий птицу, должен был перекрывать код про дом. Вот, что мне было нужно:

В коде это выглядит так:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

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

Стало намного лучше:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Анимация

Я начал добавлять больше фишек, например, анимацию:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

Все кадры расположены в одном файле и разделены тегом <>. Тег определяется при чтении и позволяет задать последовательность кадров. Благодаря этому мы получаем классическую анимацию. Создаем таймер и отрисовываем кадры в соответствии с ним.

Также я реализовал вывод печатаемого текста и отобразил отдельно экран, инвентарь и окно для ввода решения. Оставалась одна проблема. Как игра узнает, что был открыт файл? Это и есть вторая фича, о которой я говорил ранее.

В исходном коде Notepad2 я прописал, что файл должен сохраняться сразу после открытия. Затем игра проверяет, не изменилось ли время последнего сохранения. Так она узнает, что файл был открыт, и может его менять.

В итоге я получил фреймворк, в котором можно работать. Самое время создавать игру.

За девять дней разработки (судя по дате создания gif-файлов) я сделал это:

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

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

Программа по умолчанию

Может, возвращать исходные настройки при закрытии игры? Это возможно, но возникнет проблема, если игра вылетит или неожиданно закроется.

Программу по умолчанию можно назначить только от имени администратора. Если вы открываете игру под другой учетной записью, будут использоваться txt-файлы. Если вы открываете файл в обычном Блокноте, игра сообщит, что нужно перетащить файл в открытое окно Блокнота. Либо запустить ее от имени администратора, чтобы она открылась по дабл-клику.

Мотивация

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

Но я все время держал его в голове. Я отладил целый фреймворк, который позволял создать игру в Блокноте, а проект не двигался с мертвой точки. Нужно было доделать его. В 2019 году я не завершил почти ни одного проекта. Разочарование подтолкнуло меня к решению: закончить незаконченное в 2020-м.

И вот она. Я сократил сюжет, дал себе месяц на все (получилось на неделю дольше) и бросился в бой. Еще подал заявку на A MAZE. Awards, соответственно, дедлайн был назначен на 2 февраля. Так появилась мотивация.

Заключение

Я рад, что доделал игру. Удивительно, сколько времени проект просто собирал цифровую пыль, а в итоге хватило месяца. Игру не стоило делать настолько объемной, как я хотел сначала — такой нестандартный проект должен лишь показывать особенности, которые можно в нем реализовать.

Что дальше? Игра в Paint? Игра в Калькуляторе? Вряд ли я их сделаю. Но мне нравится думать об играх, которые используют нетрадиционные платформы.

Источник

Как написать свой Тетрис на Java за полчаса

Как сделать тетрис в блокноте. Смотреть фото Как сделать тетрис в блокноте. Смотреть картинку Как сделать тетрис в блокноте. Картинка про Как сделать тетрис в блокноте. Фото Как сделать тетрис в блокноте

В предыдущих статьях этой серии мы уже успели написать сапёра, змейку и десктопный клон игры 2048. Попробуем теперь написать свой Тетрис.

Нам, как обычно, понадобятся:

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

С чего начать?

Получение данных от пользователя

Код, честно говоря, достаточно капитанский:

Интерфейсы для клавиатурного и графического модулей

Так как многим не нравится, что я пишу эти модули на LWJGL, я решил в статье уделить время только интерфейсам этих классов. Каждый может написать их с помощью той GUI-библиотеки, которая ему нравится (или вообще сделать консольный вариант). Я же по старинке реализовал их на LWJGL, код можно посмотреть здесь в папках graphics/lwjglmodule и keyboard/lwjglmodule.

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

Отлично, мы получили от пользователя данные. Что дальше?

А дальше мы должны эти данные обработать и что-то сделать с игровым полем. Если пользователь сказал сдвинуть фигуру куда-то, то передаём полю, что нужно сдвинуть фигуру в таком-то направлении. Если пользователь сказал, что нужно фигуру повернуть, поворачиваем, и так далее. Кроме этого нельзя забывать, что 1 раз в FRAMES_PER_MOVE (вы же открывали файл с константами?) итераций нам необходимо сдвигать падающую фигурку вниз.

Сюда же добавим проверку на переполнение поля (в Тетрисе игра завершается, когда фигурам некуда падать):

Так, а теперь мы напишем класс для того магического gameField, в который мы всё это передаём, да?

А инициализировать мы их будем так:

А вот теперь мы переходим к классу, который отвечает за хранение информации об игровом поле и её обновление.

Класс GameField

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

Хранить информацию о поле…

…и о падающей фигуре

TpReadableColor — простой enum, содержащий элементы с говорящими названиями (RED, ORANGE и т.п.) и метод, позволяющий получить случайным образом один из этих элементов. Ничего особенного в нём нет, код можно посмотреть тут.

Это все поля, которые нам понадобятся. Как известно, поля любят быть инициализированными.
Сделать это следует в конструкторе.

Конструктор и инициализация полей

А что это там за spawnNewFigure()? Почему инициализация фигуры вынесена в отдельный метод?

На этом с хранением данных мы закончили. Переходим к методам, которые отдают информацию о поле другим классам.

Методы, передающие информацию об игровом поле

Таких метода всего два. Первый возвращает цвет ячейки (для графического модуля):

А второй сообщает, переполнено ли поле (как это происходит, мы разобрали выше):

Методы, обновляющие фигуру и игровое поле

Сдвиг фигуры

Что мы сделали в этом методе? Мы запросили у фигуры ячейки, которые бы она заняла в случае сдвига. А затем для каждой из этих ячеек мы проверяем, не выходит ли она за пределы поля, и не находится ли по её координатам в сетке статичный блок. Если хоть одна ячейка фигуры выходит за пределы или пытается встать на место блока – сдвига не происходит. Coord здесь – класс-оболочка с двумя публичными числовыми полями (x и y координаты).

Поворот фигуры

Логика аналогична сдвигу:

Падение фигуры

Сначала код в точности повторяет предыдущие два метода:

Так как в результате переноса ячеек какая-то линия может заполниться полностью, после каждого добавления ячейки мы проверяем линию, в которую мы её добавили, на полноту:

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

Теперь GameField реализован почти полностью — за исключением геттера для фигуры. Её ведь графическому модулю тоже придётся отрисовывать:

Теперь нам нужно написать алгоритмы, по которым фигура определяет свои координаты в разных состояниях. Да и вообще весь класс фигуры.

Класс фигуры

Реализовать это всё я предлагаю следующим образом – хранить для фигуры (1) «мнимую» координату, такую, что все реальные блоки находятся ниже и правее неё, (2) состояние поворота (их всего 4, после 4-х поворотов фигура всегда возвращается в начальное положение) и (3) маску, которая по первым двум параметрам будет определять положение реальных блоков:

Rotation мод здесь будет выглядеть таким образом:

Соответственно, от самого класса Figure нам нужен только конструктор, инициализирующий поля:

И методы, которыми мы пользовались в GameField следующего вида:

Вдобавок, у фигуры должен быть цвет, чтобы графический модуль мог её отобразить. В тетрисе каждой фигуре соответствует свой цвет, поэтому цвет мы будем запрашивать у формы:

Форма фигуры и маски координат

Чтобы не занимать лишнее место, здесь я приведу реализацию только для двух форм: I-образной и J-образной. Код для остальных фигур принципиально не отличается и выложен на GitHub.

Храним для каждой фигуры маску координат (которая определяет, насколько каждый реальный блок должен отстоять от «мнимой» координаты фигуры) и цвет:

Реализуем методы, которые использовали выше:

Ну а сами маски координат я предлагаю просто захардкодить следующим образом:

Т.е. для каждого объекта enum‘а мы передаём с помощью импровизированных (других в Java нет) делегатов метод, в котором в зависимости от переданного состояния поворота возвращаем разные реальные координаты блоков. В общем-то, можно обойтись и без делегатов, если хранить в каждом элементе отсупы для каждого из режимов поворота.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *