Найти тему
ZDG

Пишу 5-килобайтный Тетрис. Часть 4: Места ещё много. Что делать?

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

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

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

Это число 4095. Вот с ним я и буду сравнивать строки стакана.

Если нашлась сплошная строка, то содержимое всех строк, которые находятся выше её, я в цикле скопирую по одной вниз. То есть место сплошной строки займет та, которая была выше, и т.д.

Вместо самой верхней строки будет записана пустая строка 0000100000000001, или 2049.

Пока проверяемая строка остается сплошной (например, сверху скопировалась строка и она опять оказалась сплошная), я остаюсь на ней и повторяю операцию. Затем я перехожу на строку выше, проверяю там, затем еще выше и так далее до самой верхней строки.

Итого мне нужны три вложенных цикла:

  1. Внешний: От нижней до верхней строки...
  2. Внутренний: Пока строка равна 4095...
  3. Совсем внутренний: Копирование всех строк выше текущей

Сделано. Теперь надо, чтобы фигура падала сама. Для этого я пишу функцию, которая будет телом главного игрового цикла – processGame().

Она будет при каждом вызове двигать фигуру на 1 клетку вниз, убирать сплошные линии, и оценивать условие продолжения игры.

Теперь, чтобы всё заработало, нужно, чтобы функция processGame() циклически вызывалась через равные интервалы времени. Раньше для этих целей в JavaScript использовали таймер, сейчас есть специальная функция requestAnimationFrame(), которая сама запустит мою функцию processGame() в тот момент, когда браузер будет готов к перерерисовке графики. Такие моменты будут происходить 60 раз в секунду (это не всегда и не везде, но пока принимаю так).

Получается главный игровой цикл:

  1. Дождаться момента обновления с помощью requestAnimationFrame()
  2. requestAnimationFrame() запустит processGame()
  3. processGame() продвинет фигуру вниз, уберет сплошные линии, в общем сделает всю обработку
  4. Отрисовать "стакан" с актуальным содержимым
  5. Перейти на пункт 1.

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

Также, если двигать фигуру вниз каждый раз, когда срабатывает requestAnimationFrame(), то фигура будет падать очень быстро. Поэтому падать она будет не в каждом кадре, а спустя несколько кадров. Для чего я завел переменную delay, которая будет отсчитывать пропущенные кадры, и переменную speed, которая будет меняться по мере набора очков, чтобы игра постепенно ускорялась.

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

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

В целом игра готова. После сжатия JavaScript-сжимателем размер кода (без HTML) составил 1211 байт, а в несжатом виде 2715 байт.

Рабочую версию можно посмотреть онлайн.

Что дальше?

  • Я не объясняю тут код в подробностях, вместо этого объясняю свои решения. Но как можно заметить, код почти полностью основан на двоичной логике. Об этом я напишу отдельно.
  • Так как у меня в запасе еще почти 4 килобайта, я хочу добавить, во-первых, подсчет очков и показ следующей фигуры, а во-вторых, каких-нибудь красивых графических эффектов. Кое-какие идеи уже есть, надо их проверить.