тетрис на java код
Русские Блоги
Реализация игры Тетрис на Java (простая версия)
Эффекты страницы игры следующие:
Логика самой игры Тетрис:
Логика игры в тетрис относительно проста. Он похож на сложенный дом, формы квадратов разные. Однако интерфейс игры Тетрис поровну разделен на несколько строк и несколько столбцов, поэтому суть куба заключается в том, сколько единиц он занимает.
Давайте сначала рассмотрим вопрос о данных. Для интерфейса необходим двумерный массив типа int, который содержит те места, которые должны быть раскрашены, а какие нет; тогда сами квадраты, хотя их формы неоднородны, они могут быть окружены квадратом масштаба 4X4. Следовательно, 16 байтов могут сохранить информацию о блоке,
Примечание. Фактически, данные квадрата также могут быть представлены массивом int, но когда дело доходит до эффективности, побитовые операции выполняются быстрее, чем обычные арифметические операции.
Затем подумайте о следующих конкретных действиях:
(1) Рождение куба. Его рождение требует принципа случайности.Кроме того, как он инициализируется для размещения в верхней части игрового интерфейса?
(2) Квадрат должен падать автоматически.В процессе падения он должен оценить, конфликтует ли он с окружающей средой и может ли он продолжать падать.
(3) Сам блок также может быть деформирован, и деформированный блок имеет другие данные, и метод оценки будет другим. (4) Когда пользователь продолжает нажимать клавишу s, квадрат должен продолжать падать.
Затем идет процесс. Игроки в основном действуют в следующих аспектах:
(1) Работа влево и вправо. Вам нужно прослушать KeyEvent и позволить квадрату перемещаться влево и вправо, пока не достигнет границы.
(2) Деформационная операция. Также послушайте KeyEvent, чтобы блок автоматически деформировался.
(3) Операция опускания. Также послушайте KeyEvent, чтобы ящик упал быстро.
Что касается конца игры, то здесь только одна ситуация, то есть рождение куба, который конфликтует с другими кубиками.
Исходный код выглядит следующим образом:Детали аннотации
Тетрис на JavaScript
Стильный и цветной.
Отменяйте все дела, переносите встречи. Сегодня мы делаем тетрис, в который можно играть в браузере и на который можно потратить весь день.
В чём идея
Правила игры все знают: сверху в двумерный игровой стакан падают фигуры разной формы, составленные из модульных блоков. Внизу блоки соединяются. Если собрать целую горизонтальную линию из блоков, она исчезает, все остальные блоки сдвигаются на ряд ниже.
Наша задача — как можно дольше продержаться, чтобы экран не заполнился и было место, куда падать новым фигурам.
Если вдруг не знаете, как это работает, вот фрагмент с чемпионата мира по тетрису:
Код не мой
Код, который мы разбираем в этом проекте, написал американский разработчик Стивен Ламберт:
В этой статье мы объясним, как этот код работает.
Неожиданная сложность
Самое главное при программировании такой игры — это как-то хранить содержимое игрового экрана и учитывать движение фигур.
Если бы мы писали эту игру на Unreal Engine или Unity, первым интуитивным решением было бы сделать для блоков какую-то сущность типа объекта. У него были бы свойства — например, конфигурация. Может быть, мы бы захотели потом сделать взрывающиеся объекты или объекты с заморозкой, объекты с повышенной скоростью, отравленные объекты или что-то ещё в таком духе.
Но есть нюанс: смысл объекта в том, что он неделимый. А в «Тетрисе» все объекты запросто делятся, когда мы «закрываем линию». У какой-нибудь Т-образной фигуры может запросто пропасть хвостик, а у Z-образной фигуры — нижняя перекладина.
Получается, что фигура в тетрисе выглядит как объект, иногда ведёт себя как объект, но не обладает свойствами объекта. Поэтому объектный подход нам здесь не подходит.
Решение — представить игровое поле в виде двумерного массива нулей и единиц. Ноль означает, что клетка свободна, а единица — что занята какой-то частью фигуры. Хранить и обрабатывать двумерный массив довольно просто, поэтому решение кажется логичным.
Сами фигуры тоже представим в виде двумерного массива из нолей и единиц, но особым образом — в виде квадрата, где единицы отвечают за части фигуры, а ноли — за пустое место:
Если вместо квадрата просто взять фактические размеры фигуры и загнать их в массив, то при вращении они не влезут в исходный массив. А внутри квадрата их можно вращать как угодно — размер массива от этого не изменится:
Получается, что если мы добавим в общий массив с игровым цветом параметр, который отвечает за цвет, то можем рисовать каждую фигуру своим цветом. Так и сделаем.
Подготовка страницы
Игра будет работать на HTML-странице с помощью элемента Canvas — это холст, на котором мы можем рисовать произвольные фигуры через JavaScript.
Возьмём пустую страницу и сразу нарисуем на ней игровое поле. Сразу сделаем чёрный фон, игровое поле поставим по центру, а его рамки сделаем белыми:
Всё остальное сделаем скриптом. Добавим тэг сразу после того, как нарисовали холст, и начнём писать содержимое скрипта.
Заводим переменные и константы
Пока что всё просто:
Генерируем выпадающие фигуры
Первое, что нам понадобится для этого, — функция, которая выдаёт случайное число в заданном диапазоне. По этому числу мы будем выбирать фигуры.
Теперь мы можем создать последовательность из выпадающих фигур. Логика будет такая:
Последний этап в этом блоке — получить из игровой последовательности, которую мы только что сделали, следующую фигуру, которая у нас появится. Мы должны знать, что это за фигура; как она рисуется; откуда она начинает движение. Обратите внимание: на выходе мы получаем не только двумерный массив с фигурой, а ещё и название и её координаты. Название нам нужно для того, чтобы знать, каким цветом рисовать фигуру.
Движение, вращение и установка фигуры на место
В тетрисе мы можем вращать каждую фигуру на 90 градусов по часовой стрелке сколько угодно раз. А так как у нас фигура — это двумерный массив из чисел, то быстро найдём в интернете готовый код для поворота числовой матрицы:
После каждого поворота и при каждой смене позиции нам нужно проверить, а может ли в принципе фигура так двигаться? Если движению или вращению мешают стенки поля или другие фигуры, то нужно сообщить программе, что такое движение делать нельзя. Дальше мы будем делать эту проверку перед тем, как что-то отрисовывать на экране.
Если проверка не прошла, то мы не делаем последнее движение, и фигура просто продолжает падать вниз. Если ей некуда падать и она упёрлась в другие, то нам нужно зафиксировать это в игровом поле. Это значит, что мы записываем в массив, который отвечает за поле, нашу матрицу фигуры, пропуская ноли и записывая только единицы.
Как только фигура встала, нам нужно проверить, получился целый ряд или нет. Если получился — сдвигаем на один ряд вниз всё, что сверху. Такую проверку делаем каждый раз при установке фигуры и начинаем с нижнего ряда, поднимаясь наверх.
Что будет, когда мы проиграем
Когда фигура при окончательной установке вылезает за границы игрового поля, это значит, что мы проиграли. За это у нас отвечает флаг gameOver, и его задача — остановить анимацию игры.
Чтобы было понятно, что игра закончена, выведем надпись GAME OVER! прямо поверх игрового поля:
Обрабатываем нажатия на клавиши
Всё как в обычном тетрисе: стрелки влево и вправо двигают фигуру, стрелка вверх поворачивает её на 90 градусов, а стрелка вниз ускоряет падение.
Единственное, о чём нужно не забыть — после каждого нажатия вызвать проверку, можно ли так двигать фигуру или нет.
Запускаем движения и анимацию
Смысл главного цикла игры такой:
Так как кадры меняются быстро, мы не заметим постоянного очищения и отрисовки. Нам будет казаться, что фигура просто движется вниз и реагирует на наши действия.
Последнее, что нам осталось сделать, — запустить игру:
Готовый результат можно посмотреть на странице с игрой.
Что дальше
У нас есть игра, но нет важных элементов:
Сделаем это в другой версии игры, а пока отменяйте планы, сегодня мы играем в бесконечный тетрис.
Tetris на javascript (в 30+ строк)
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. Если не удалось переместить фигуру вниз — значит она уперлась в предыдущие фигуры или дно. В таком случае проверяем нет ли заполненных строк, и рисуем новую фигуру вверху стакана. Если вверху стакана нарисовать фигуру не удалось — игра закончена!
Как написать свой Тетрис на Java за полчаса
Как написать свой Тетрис на Java за полчаса
В предыдущих статьях этой серии мы уже успели написать сапёра, змейку и десктопный клон игры 2048. Попробуем теперь написать свой Тетрис.
Нам, как обычно, понадобятся:
Прежде чем задавать вопрос в комментариях, не забудьте заглянуть в предыдущие статьи, возможно там на него уже давался ответ. Исходный код готового проекта традиционно можно найти на GitHub.
С чего начать?
Получение данных от пользователя
Код, честно говоря, достаточно капитанский:
1 октября – 30 ноября, Онлайн, Беcплатно
Интерфейсы для клавиатурного и графического модулей
Так как многим не нравится, что я пишу эти модули на 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 нет) делегатов метод, в котором в зависимости от переданного состояния поворота возвращаем разные реальные координаты блоков. В общем-то, можно обойтись и без делегатов, если хранить в каждом элементе отсупы для каждого из режимов поворота.
Наслаждаемся результатом
P.S. Ещё раз напомню, что исходники готового проекта доступны на GitHub.
Хинт для программистов: если зарегистрируетесь на соревнования Huawei Cup, то бесплатно получите доступ к онлайн-школе для участников. Можно прокачаться по разным навыкам и выиграть призы в самом соревновании.
Перейти к регистрации
Java Tetris
last modified July 20, 2020
In this chapter, we create a Tetris game clone in Java Swing. Source code and images can be found at the author’s Github Java-Tetris-Game repository.
Tetris
The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a Russian programmer Alexey Pajitnov in 1985. Since then, Tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the Tetris game.
Figure: Tetrominoes
The development
The tetrominoes are drawn using the Swing painting API. We use the java.util.Timer to create a game cycle. The shapes move on a square by square basis (not pixel by pixel). Mathematically the board in the game is a simple list of numbers.
The game starts immediately after it is launched. We can pause the game by pressing the p key. The space key will drop the Tetris piece immediately to the bottom. The d key will drop the piece one line down. (It can be used to speed up the falling a bit.) The game goes at constant speed, no acceleration is implemented. The score is the number of lines that we have removed.
The Shape class provides information about a Tetris piece.
The coords array holds the actual coordinates of a Tetris piece.
The coordsTable array holds all possible coordinate values of the Tetris pieces. This is a template from which all pieces take their coordiate values.
We put one row of the coordinate values from the coordsTable into the coords array of a Tetris piece. Note the usage of the ordinal() method. In C++, an enum type is essentially an integer. Unlike in C++, Java enums are full classes and the ordinal() method returns the current position of the enum type in the enum object.
Figure: Coordinates
This code rotates a piece to the left. The square does not have to be rotated. That’s why we simply return the reference to the current object. Looking at the previous image will help to understand the rotation.
Finally, we have the Board.java file. This is where the game logic is located.
We have four constants. The BOARD_WIDTH and BOARD_HEIGHT define the size of the board. The PERIOD_INTERVAL constant defines the speed of the game.
Some important variables are initialized. The isFallingFinished determines if the Tetris shape has finished falling and we then need to create a new shape. The isStarted is used to check if the game has started. Likewise, the isPaused is used to check if the game is paused. The numLinesRemoved counts the number of lines that we have removed so far. The curX and curY determine the actual position of the falling Tetris shape.
These lines determine the width and height of a single Tetrominoe square.
We determine the shape at the given coordinates. The shapes are stored in the board array.
We create a new current shape and a new board.
The board is cleared and the new falling piece is initialized.
We create a timer. The timer is executed at PERIOD_INTERVAL intervals, creating a game cycle.
The pause() method pauses or resumes the game. When the game is paused, we display the paused message in the statusbar.
Inside the doDrawing() method, we draw all objects on the board. The painting has two steps.
In the first step we paint all the shapes or remains of the shapes that have been dropped to the bottom of the board. All the squares are remembered in the board array. We access it using the shapeAt() method.
In the second step, we paint the actual falling piece.
If we press the Space key, the piece is dropped to the bottom. We simply try to drop the piece one line down until it reaches the bottom or the top of another fallen Tetris piece. When the Tetris piece finishes falling, the pieceDropped() is called.
In the oneLineDown() method we try to move the falling piece down one line until it is fully dropped.
The pieceDropped() method puts the falling piece into the board array. Once again, the board holds all the squares of the pieces and remains of the pieces that has finished falling. When the piece has finished falling, it is time to check if we can remove some lines off the board. This is the job of the removeFullLines() method. Then we create a new piece, or more precisely, we try to create a new piece.
The newPiece() method creates a new Tetris piece. The piece gets a new random shape. Then we compute the initial curX and curY values. If we cannot move to the initial positions, the game is over—we top out. The timer is stopped and we display Game over string containing the score on the statusbar.
The tryMove() method tries to move the Tetris piece. The method returns false if it has reached the board boundaries or it is adjacent to the already fallen Tetris pieces.
Inside the removeFullLines() method we check if there is any full row among all rows in the board. If there is at least one full line, it is removed. After finding a full line we increase the counter. We move all the lines above the full row one line down. This way we destroy the full line. Notice, that in our Tetris game, we use so called naive gravity. This means that the squares may be left floating above empty gaps.
Every Tetris piece has four squares. Each of the squares is drawn with the drawSquare() method. Tetris pieces have different colours. The left and top sides of a square are drawn with a brighter color. Similarly, the bottom and right sides are drawn with darker colours. This is to simulate a 3D edge.
The game is divided into game cycles. Each cycle udpates the game and redraws the board.
The update() represents one step of the game. The falling piece goes one line down or a new piece is created if the previous one has finished falling.
We get the key code with getKeyCode() method.
With Java 12 switch expressions, we bind key events to methods. For instance, with the Space key we drop down the falling tetris piece.
In the Tetris.java file, we set up the game. We create a board on which we play the game. We create a statusbar.
The score is displayed in a label which is located at the bottom of the board.
The board is created and added to the container. The start() method starts the Tetris game.
Figure: Tetris