Как написать тетрис на javascript
в коде игры где-то есть баг, иногда игра завершается преждевременно, надо будет поискать внимательней. ну, или нашедшему — приз!
Современная версия формата разметки HTML5 в связке c CSS и Javascript превращается не просто в массовый инструмент разработки, на котором можно писать игры из. нуля строк кода, но, того и гляди, «HTML-программисты» потеснят классических 🙂 По крайней мере, большинство детских курсов по программированию построены именно на этой связке.
Удобно то, что вся логическая разметка, будь то форма приложения или канва для вывода графики, делается обычным HTML, для оформления и вёрстки идеально подходят стили CSS, а в JS есть средства для удобного доступа к любому узлу документа, высокоуровневые средства программирования и готовые таймеры для реализации анимации. К тому же, в JS нет в чистом виде обычно непонятных начинающим «классов» с запутанными отношениями между ними, но есть произвольное конструирование объектов.
Шагая в ногу со временем, давайте и мы реализуем на HTML+Javascript что-нибудь законченное, например, классический Тетрис примерно так, как он описан в Википедии.
За разметку будет отвечать обычный HTML-файл с именем index.html , предусмотрим вёрстку в 2 колонки с идентификаторами tetrisleft и tetrisright и 2 графичеких канвы — tetriscanvas для вывода основного игрового поля и figurecanvas для отдельной отрисовки следующей фигуры. Для вывода информационных элементов игры также предусмотрим стилевой класс info .
Вот файлы разметки и стиля, которые можно поместить в одну папку.
Файл index.html
Файл tetrisstyle.css
Из HTML-кода видно, что мы подключили целых три яваскрипт-файла. Файл canvas.js будет отвечать за обработку основного игрового поля, controller.js добавит к приложению обработчик событий (в нашем случае — только нажатия клавиш), а game.js займётся основной логикой игры.
Все эти файлы поместим во вложенную папку с именем js .
Файл canvas.js просто получит ссылку на основную канву и займётся отрисовкой игрового поля по данным, содержащимся в массиве board файла game.js . У него будет собственная частота перерисовки канвы, равная 50 миллисекундам.
Файл js/canvas.js
Файл controller.js устроен ещё проще — там прописываются коды нужных клавиш и ставится обработчик события нажатия клавиши в документе.
Стрелки влево и вправо будут двигать фигурку в соответствующих направлениях, стрелка вверх — вращать, а стрелка вниз или пробел — ускорять падение. Паузу можно будет удобно вызвать нажатием клавиши Escape, а возобновить игру — повторным нажатием Escape или кликом по кнопке «OK» в появившемся окне «Pause» (в Javascript уже есть модальное окно window.alert , так что программировать задержки нам не придётся).
Файл js/controller.js
Сама игра, конечно же, будет подлиннее, ровно в 200 строк, но комментарии помогут вам без проблем понять её.
Файл js/game.js
Разумеется, нетрудно прикрутить прикрутить сюда, например, сохранение рекорда в Cookie-файле, предусмотреть увеличение скорости игры в зависимости от количества убранных линий или набранных очков (уменьшать переменную interval в game.js ) и т.д.
Также, кроме паузы по нажатию Esc, мы не предусмотрели каких-либо остановок в игре, по завершении текущей игры немедленно начинается новая. В общем, проект есть куда развивать.
Так как традиционная для тетриса фоновая мелодия «Коробейники» займёт 99% объёма архива, выкладывать сюда никакой архив с проектом не будем, а просто сошлёмся на звуковые файлы. Формат звука .ogg , думаю, удобнее всего, чтобы «шло» и на «Андроиде»:
- файл sound/music.ogg, фоновая музыка «Коробейники»;
- файл sound/pop.ogg, звук убирания линии.
Все остальные файлы проекта — выше по тексту 🙂
UPD: добавил под «стаканом» кнопки, дублирующие действие клавиш. Там всего 5 строчек кода в файле .html , апдейтить статью ради этого лень, смотрите изменения в исходнике страницы с игрой. Ну и с бесклавиатурных устройств, наверное, можно так играть 🙂
Ну, чем хуже оригинала? 🙂 Кстати, чёрно-белый оригинал от 1986 года у меня сработал под Windows7, только таймер там, видимо, абсолютный, из-за чего слишком быстро падают фигурки. Но из-под эмулятора DOS должно всё быть идеально.
Источник статьи: http://blog.kislenko.net/show.php?id=1770
Тетрис на JavaScript
Отменяйте все дела, переносите встречи. Сегодня мы делаем тетрис, в который можно играть в браузере и на который можно потратить весь день.
В чём идея
Правила игры все знают: сверху в двумерный игровой стакан падают фигуры разной формы, составленные из модульных блоков. Внизу блоки соединяются. Если собрать целую горизонтальную линию из блоков, она исчезает, все остальные блоки сдвигаются на ряд ниже.
Наша задача — как можно дольше продержаться, чтобы экран не заполнился и было место, куда падать новым фигурам.
Если вдруг не знаете, как это работает, вот фрагмент с чемпионата мира по тетрису:
Код не мой
Код, который мы разбираем в этом проекте, написал американский разработчик Стивен Ламберт:
В этой статье мы объясним, как этот код работает.
Неожиданная сложность
Самое главное при программировании такой игры — это как-то хранить содержимое игрового экрана и учитывать движение фигур.
Если бы мы писали эту игру на Unreal Engine или Unity, первым интуитивным решением было бы сделать для блоков какую-то сущность типа объекта. У него были бы свойства — например, конфигурация. Может быть, мы бы захотели потом сделать взрывающиеся объекты или объекты с заморозкой, объекты с повышенной скоростью, отравленные объекты или что-то ещё в таком духе.
Но есть нюанс: смысл объекта в том, что он неделимый. А в «Тетрисе» все объекты запросто делятся, когда мы «закрываем линию». У какой-нибудь Т-образной фигуры может запросто пропасть хвостик, а у Z-образной фигуры — нижняя перекладина.
Получается, что фигура в тетрисе выглядит как объект, иногда ведёт себя как объект, но не обладает свойствами объекта. Поэтому объектный подход нам здесь не подходит.
Решение — представить игровое поле в виде двумерного массива нулей и единиц. Ноль означает, что клетка свободна, а единица — что занята какой-то частью фигуры. Хранить и обрабатывать двумерный массив довольно просто, поэтому решение кажется логичным.
Сами фигуры тоже представим в виде двумерного массива из нолей и единиц, но особым образом — в виде квадрата, где единицы отвечают за части фигуры, а ноли — за пустое место:
Если вместо квадрата просто взять фактические размеры фигуры и загнать их в массив, то при вращении они не влезут в исходный массив. А внутри квадрата их можно вращать как угодно — размер массива от этого не изменится:
Получается, что если мы добавим в общий массив с игровым цветом параметр, который отвечает за цвет, то можем рисовать каждую фигуру своим цветом. Так и сделаем.
Подготовка страницы
Игра будет работать на HTML-странице с помощью элемента Canvas — это холст, на котором мы можем рисовать произвольные фигуры через JavaScript.
Возьмём пустую страницу и сразу нарисуем на ней игровое поле. Сразу сделаем чёрный фон, игровое поле поставим по центру, а его рамки сделаем белыми:
Всё остальное сделаем скриптом. Добавим тэг сразу после того, как нарисовали холст, и начнём писать содержимое скрипта.
Заводим переменные и константы
- делаем массив для игрового поля и заполняем его;
- делаем массивы, которые хранят наши фигуры и их цвета;
- в отдельном массиве будем хранить новые фигуры, которые появятся следующими;
- делаем флаг остановки игры. Пока он не сработает — можно играть.
Генерируем выпадающие фигуры
Первое, что нам понадобится для этого, — функция, которая выдаёт случайное число в заданном диапазоне. По этому числу мы будем выбирать фигуры.
Теперь мы можем создать последовательность из выпадающих фигур. Логика будет такая:
- Задаём обычную последовательность доступных фигур.
- Случайным образом забираем оттуда фигуру и помещаем в игровую последовательность.
- Так делаем до тех пор, пока от обычной последовательности ничего не останется.
- У нас получилась случайная игровая последовательность фигур, с которыми мы будем работать дальше.
Последний этап в этом блоке — получить из игровой последовательности, которую мы только что сделали, следующую фигуру, которая у нас появится. Мы должны знать, что это за фигура; как она рисуется; откуда она начинает движение. Обратите внимание: на выходе мы получаем не только двумерный массив с фигурой, а ещё и название и её координаты. Название нам нужно для того, чтобы знать, каким цветом рисовать фигуру.
Движение, вращение и установка фигуры на место
В тетрисе мы можем вращать каждую фигуру на 90 градусов по часовой стрелке сколько угодно раз. А так как у нас фигура — это двумерный массив из чисел, то быстро найдём в интернете готовый код для поворота числовой матрицы:
После каждого поворота и при каждой смене позиции нам нужно проверить, а может ли в принципе фигура так двигаться? Если движению или вращению мешают стенки поля или другие фигуры, то нужно сообщить программе, что такое движение делать нельзя. Дальше мы будем делать эту проверку перед тем, как что-то отрисовывать на экране.
Если проверка не прошла, то мы не делаем последнее движение, и фигура просто продолжает падать вниз. Если ей некуда падать и она упёрлась в другие, то нам нужно зафиксировать это в игровом поле. Это значит, что мы записываем в массив, который отвечает за поле, нашу матрицу фигуры, пропуская ноли и записывая только единицы.
Как только фигура встала, нам нужно проверить, получился целый ряд или нет. Если получился — сдвигаем на один ряд вниз всё, что сверху. Такую проверку делаем каждый раз при установке фигуры и начинаем с нижнего ряда, поднимаясь наверх.
Что будет, когда мы проиграем
Когда фигура при окончательной установке вылезает за границы игрового поля, это значит, что мы проиграли. За это у нас отвечает флаг gameOver, и его задача — остановить анимацию игры.
Чтобы было понятно, что игра закончена, выведем надпись GAME OVER! прямо поверх игрового поля:
Обрабатываем нажатия на клавиши
Всё как в обычном тетрисе: стрелки влево и вправо двигают фигуру, стрелка вверх поворачивает её на 90 градусов, а стрелка вниз ускоряет падение.
Единственное, о чём нужно не забыть — после каждого нажатия вызвать проверку, можно ли так двигать фигуру или нет.
Запускаем движения и анимацию
Смысл главного цикла игры такой:
- на каждом кадре мы очищаем игровое поле и отрисовываем его заново с учётом упавших фигур;
- рисуем текущую фигуру в том месте, где она находится в данный момент.
Так как кадры меняются быстро, мы не заметим постоянного очищения и отрисовки. Нам будет казаться, что фигура просто движется вниз и реагирует на наши действия.
Последнее, что нам осталось сделать, — запустить игру:
// старт игры rAF = requestAnimationFrame(loop);
Готовый результат можно посмотреть на странице с игрой.
Источник статьи: http://thecode.media/tetris/
Tetris на javascript (в 30+ строк)
Решил поддержать тему!
Тетрис в 30+ строк js кода.
- Знает все фигуры тетриса
- Управление с клавиатуры
- ВВЕРХ — фигурки крутятся по часовой стрелке
- ВНИЗ — ускорить падение
- Скорость падания постепенно увеличивается
- Очки подсчитываются
ссылка на jsfiddle
Принцип работы.
1. Получаем фигурки
Все фигурки хранятся в переменной fs=«1111:01|01|01|01*011|110:010|011|001*. » в виде строки. Чтобы получить массив фигур — делаем split(‘*’), далее в каждой фигуре есть от 1-го (для «палки») до 4-х (для L, Г и «пирамидки») состояния (чтобы их можно было переворачивать) — они разделены «:» — соответственно чтобы получить одно состояние — split(‘:’). Допустим получили «пирамидку» — «010|111», здесь делаем split(‘|’) — и получаем уже конечный двумерный массив для одного состояния одной фигуры. «0»- пустое пространство (не нужно отрисовывать), «1» — нужно рисовать.
2. Всё перемещение фигур делается двумя функциями — «стереть фигуру» и «попытаться построить».
При любых перемещениях вправо-влево или вниз, или даже переворот фигуры — сначала стираем текущую отображаемую фигуру, потом пытаемся построить фигуру на новом месте — если при постройке какой-то из квадратиков вылазит за край «стакана», или попадает на место, где уже есть заполненный квадратик от предыдущих фигур — стираем «активную фигуру», и стоим ее на предыдущем месте.
3. Перемещения делаются с клавиатуры. По таймеру просто вызывается функция, которая обрабатывает нажатия с клавиатуры с кодом кнопки ВНИЗ. При каждом вызове таймаута — время до вызова уменьшается.
4. Если не удалось переместить фигуру вниз — значит она уперлась в предыдущие фигуры или дно. В таком случае проверяем нет ли заполненных строк, и рисуем новую фигуру вверху стакана. Если вверху стакана нарисовать фигуру не удалось — игра закончена!
Источник статьи: http://habr.com/ru/post/202628/
Как написать свой Тетрис на Java за полчаса
В предыдущих статьях этой серии мы уже успели написать сапёра, змейку и десктопный клон игры 2048. Попробуем теперь написать свой Тетрис.
Нам, как обычно, понадобятся:
- 30 минут свободного времени;
- Настроенная рабочая среда, т.е. JDK и IDE (например, Eclipse);
- Библиотека LWJGL (версии 2.x.x) для работы с графикой (опционально). Обратите внимание, что для LWJGL версий выше 3 потребуется написать код, отличающийся от того, что приведён в статье;
- Спрайты, т.е. картинки плиток всех возможных состояний (пустая, и со степенями двойки до 2048). Можно нарисовать самому, или скачать использовавшиеся при написании статьи.
Прежде чем задавать вопрос в комментариях, не забудьте заглянуть в предыдущие статьи, возможно там на него уже давался ответ. Исходный код готового проекта традиционно можно найти на GitHub.
С чего начать?
Начать стоит с главного управляющего класса, который в нашем проекте находится выше остальных по уровню абстракции. Вообще отличный совет – в начале работы всегда пишите код вида if (getKeyPressed()) doSomething() , так вы быстро определите фронт работ.
Это наш main() . Он ничем принципиально не отличается от тех, что мы писали в предыдущих статьях – мы всё так же инициализируем поля и, пока игра не закончится, осуществляем по очереди: ввод пользовательских данных ( input() ), основные игровые действия ( logic() ) и вызов метода отрисовки у графического модуля ( graphicsModule.draw() ), в который передаём текущее игровое поле ( gameField ). Из нового разве что метод sync – метод, который должен будет гарантировать нам определённую частоту выполнения итераций. С его помощью мы сможем задать скорость падения фигуры в клетках-в-секунду.
Вы могли заметить, что в коде использована константа FPS . Все константы удобно определять в классе с public static final полями. Полный список констант, который нам потребуется в ходе разработки, можно посмотреть в классе Constants на GitHub.
Оставим пока инициализацию полей на потом (мы же ещё не знаем, какие нам вообще понадобятся поля). Разберёмся сначала с input() и logic() .
Получение данных от пользователя
Код, честно говоря, достаточно капитанский:
Все данные от ввода мы просто сохраняем в соответствующие поля, действия на основе них будет выполнять метод logic() .
Старт 5 октября, 8 месяцев, Онлайн, От 71 200 до 76 000 ₽
Теперь уже потихоньку становится понятно, что нам необходимо. Во-первых, нам нужны клавиатурный и графический модули. Во-вторых, нужно как-то хранить направление, которое игрок выбрал для сдвига. Вторая задача решается просто – создадим enum с тремя состояниями: AWAITING, LEFT, RIGHT . Зачем нужен AWAITING ? Чтобы хранить информацию о том, что сдвиг не требуется (использования в программе null следует всеми силами избегать). Перейдём к интерфейсам.
Интерфейсы для клавиатурного и графического модулей
Так как многим не нравится, что я пишу эти модули на LWJGL, я решил в статье уделить время только интерфейсам этих классов. Каждый может написать их с помощью той GUI-библиотеки, которая ему нравится (или вообще сделать консольный вариант). Я же по старинке реализовал их на LWJGL, код можно посмотреть здесь в папках graphics/lwjglmodule и keyboard/lwjglmodule.
Интерфейсы же, после добавления в них всех упомянутых выше методов, будут выглядеть следующим образом:
Отлично, мы получили от пользователя данные. Что дальше?
А дальше мы должны эти данные обработать и что-то сделать с игровым полем. Если пользователь сказал сдвинуть фигуру куда-то, то передаём полю, что нужно сдвинуть фигуру в таком-то направлении. Если пользователь сказал, что нужно фигуру повернуть, поворачиваем, и так далее. Кроме этого нельзя забывать, что 1 раз в FRAMES_PER_MOVE (вы же открывали файл с константами?) итераций нам необходимо сдвигать падающую фигурку вниз.
Сюда же добавим проверку на переполнение поля (в Тетрисе игра завершается, когда фигурам некуда падать):
Так, а теперь мы напишем класс для того магического gameField, в который мы всё это передаём, да?
Не совсем. Сначала мы пропишем поля класса Main и метод initFields() , чтобы совсем с ним закончить. Вот все поля, которые мы использовали:
А инициализировать мы их будем так:
Если вы решили не использовать LWJGL и написали свои классы, реализующие GraphicsModule и KeyboardHandleModule , то здесь нужно указать их конструкторы вместо, соответственно new LwjglGraphicsModule() и new LwjglKeyboardHandleModule() .
А вот теперь мы переходим к классу, который отвечает за хранение информации об игровом поле и её обновление.
Класс GameField
Этот класс должен, во-первых, хранить информацию о поле и о падающей фигуре, а во-вторых, содержать методы для их обновления, и получения о них информации – кроме тех, которые мы уже использовали, необходимо написать метод, возвращающий цвет ячейки по координатам, чтобы графический модуль мог отрисовать поле.
Хранить информацию о поле…
…и о падающей фигуре
TpReadableColor — простой enum, содержащий элементы с говорящими названиями (RED, ORANGE и т.п.) и метод, позволяющий получить случайным образом один из этих элементов. Ничего особенного в нём нет, код можно посмотреть тут.
Это все поля, которые нам понадобятся. Как известно, поля любят быть инициализированными.
Сделать это следует в конструкторе.
Конструктор и инициализация полей
«Что это за OFFSET_TOP ?» – спросите вы. OFFSET_TOP это количество неотображаемых ячеек сверху, в которых создаются падающие фигуры. Если фигуре не сможет «выпасть» из этого пространства, и хоть одна ячеек theField выше уровня COUNT_CELLS_Y будет заполнена, это будет обозначать, что поле переполнено и пользователь проиграл, поэтому OFFSET_TOP должен быть строго больше нуля.
Далее в конструкторе стоит заполнить массив theField значениями константы EMPTINESS_COLOR , а countFilledCellsInLine – нулями (второе в Java не требуется, при инициализации массива все int‘ы равны 0). Или можно создать несколько слоёв уже заполненных ячейкам — на GitHub вы можете увидеть реализацию именно второго варианта.
А что это там за spawnNewFigure()? Почему инициализация фигуры вынесена в отдельный метод?
Вы правильно догадались, spawnNewFigure() действительно инициализирует поле figure . А в отдельный метод это вынесено, потому что нам придётся делать инициализацию каждый раз, когда будет создаваться новая фигура.
На этом с хранением данных мы закончили. Переходим к методам, которые отдают информацию о поле другим классам.
Методы, передающие информацию об игровом поле
Таких метода всего два. Первый возвращает цвет ячейки (для графического модуля):
А второй сообщает, переполнено ли поле (как это происходит, мы разобрали выше):
Методы, обновляющие фигуру и игровое поле
Начнём реализовывать методы, которые мы вызывали из Main.logic() .
Сдвиг фигуры
За это отвечает метод tryShiftFigure() . В комментариях к его вызову из Main было сказано, что он «пробует сдвинуть фигуру». Почему пробует? Потому что если фигура находится вплотную к стене, а пользователь пытается её сдвинуть в направлении этой стены, никакого сдвига в реальности происходить не должно. Так же нельзя сдвинуть фигуру в статические ячейки на поле.
Что мы сделали в этом методе? Мы запросили у фигуры ячейки, которые бы она заняла в случае сдвига. А затем для каждой из этих ячеек мы проверяем, не выходит ли она за пределы поля, и не находится ли по её координатам в сетке статичный блок. Если хоть одна ячейка фигуры выходит за пределы или пытается встать на место блока – сдвига не происходит. Coord здесь – класс-оболочка с двумя публичными числовыми полями (x и y координаты).
Поворот фигуры
Падение фигуры
Сначала код в точности повторяет предыдущие два метода:
Однако теперь, в случае, если фигура дальше падать не может, нам необходимо перенести её ячейки («кубики») в theField , т.е. в разряд статичных блоков, после чего создать новую фигуру:
Так как в результате переноса ячеек какая-то линия может заполниться полностью, после каждого добавления ячейки мы проверяем линию, в которую мы её добавили, на полноту:
Этот метод возвращает истину, если линию удалось уничтожить. После добавления всех кирпичиков фигуры в сетку (и удаления всех заполненных линий), мы, при необходимости, запускаем метод, который сдвигает на место пустых линий непустые:
Теперь GameField реализован почти полностью — за исключением геттера для фигуры. Её ведь графическому модулю тоже придётся отрисовывать:
Теперь нам нужно написать алгоритмы, по которым фигура определяет свои координаты в разных состояниях. Да и вообще весь класс фигуры.
Класс фигуры
Реализовать это всё я предлагаю следующим образом – хранить для фигуры (1) «мнимую» координату, такую, что все реальные блоки находятся ниже и правее неё, (2) состояние поворота (их всего 4, после 4-х поворотов фигура всегда возвращается в начальное положение) и (3) маску, которая по первым двум параметрам будет определять положение реальных блоков:
Rotation мод здесь будет выглядеть таким образом:
Соответственно, от самого класса Figure нам нужен только конструктор, инициализирующий поля:
И методы, которыми мы пользовались в GameField следующего вида:
Вдобавок, у фигуры должен быть цвет, чтобы графический модуль мог её отобразить. В тетрисе каждой фигуре соответствует свой цвет, поэтому цвет мы будем запрашивать у формы:
Форма фигуры и маски координат
Чтобы не занимать лишнее место, здесь я приведу реализацию только для двух форм: I-образной и J-образной. Код для остальных фигур принципиально не отличается и выложен на GitHub.
Храним для каждой фигуры маску координат (которая определяет, насколько каждый реальный блок должен отстоять от «мнимой» координаты фигуры) и цвет:
Реализуем методы, которые использовали выше:
Ну а сами маски координат я предлагаю просто захардкодить следующим образом:
Т.е. для каждого объекта enum‘а мы передаём с помощью импровизированных (других в Java нет) делегатов метод, в котором в зависимости от переданного состояния поворота возвращаем разные реальные координаты блоков. В общем-то, можно обойтись и без делегатов, если хранить в каждом элементе отсупы для каждого из режимов поворота.
Наслаждаемся результатом
P.S. Ещё раз напомню, что исходники готового проекта доступны на GitHub.
Хинт для программистов: если зарегистрируетесь на соревнования Huawei Honor Cup, бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.
Перейти к регистрации
Источник статьи: http://tproger.ru/articles/java-30-mins-tetris/