Как написать тетрис?

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

В качестве языка программирования далее будет использоваться javascript с небольшими ограничениями. В частности, будет предполагаться что существует функция draw(x,y,color) которая будет рисовать квадрат нужного цвета в заданных координатах, а также что при старте запускается функция init() а потом, для каждого шага запускается функция tick(). Также, есть глобальная переменная key_pressed, хранящая текущую нажатую клавишу. В принципе, это с легкостью можно реализовать как в консоли (node js, teajs), так и в браузере.

С чего начать

Любой, абсолютно любой проект следует начинать с описания данных и use cases (примеров использования).

Данные

  • Шаблоны фигур (7 шт)
  • Игровое поле (постепенно заполняющийся стакан)
  • Текущая фигура в каких-то координатах

Use cases

В данном случае (бизнес-аналитики конечно будут в шоке), скажем что use cases следующие:

  • движение фигуры влево
  • движение фигуры вправо

Основные структуры данных

Начнем с описания шаблонов фигур. Пусть фигура будет представленна двухмерным массивом размером 4x4, в ячейках которого будет 0 или 1, в зависимости от того, есть там блок или нет. Например:

var fig1=[[0,0,0,0],[0,1,0,0],[1,1,1,0],[0,0,0,0]];

На самом деле, конечно же, все немного сложнее. Нам нужно вращать фигуру и для этого проще всего сделать 4 массива с фигурой в разных положениях. А все фигуры засунуть в массив фигур. Итого: массив фигур => массив поворотов => двухмерный массив с блоками.

var FIGURES=[
[// фигура 1
[[0,0,0,0],[0,1,0,0],[1,1,1,0],[0,0,0,0]],
[[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]],
[[0,0,0,0],[1,1,1,0],[0,1,0,0],[0,0,0,0]],
[[0,1,0,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]]
],
[//фигура 2
...
];

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

var FIELD=[];
for (var i=0;i<20;i++) {
FIELD.push([]);
for (var j=0;j<10;j++) FIELD[i].push(0);
}

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

function can_exist(figure,xf,yf)
{
for (var y=0;y<4;y++) {
for (var x=0;x<4;x++) {
if (!figure[y][x]) continue; // если в данной точке нет фигуры, пропускаем
var fldx=x+xf;
var fldy=y+yf;
if (fldx<0 || fldx>=10 || fldy<0 || fldy>=20) return false;// за пределами поля
if (FIELD[fldy][fldx]) return false;// в данных координатах уже есть блок
}
}
return true;
}

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

функция init()

1. инициализация статики (FIGURES)

2. создание пустого игрового поля.

3. gameover=false;

функция work()

function work()
{
if (gameover) return;
if (fig===undefined) { // если текущей фигуры нет
fig=Math.floor(Math.random()*FIGURES.length); // 1.1 выбрать случайную фигуру
figr=0;// Обнуляем поворот фигуры
figx=3;figy=0;//1.2 поместить в верхнюю часть экрана в середину
//1.3 если фигура не может существовать в данных координатах
if (!can_exist(FIGURES[fig][figr],figx,figy)) {
gameover=true; // 1.4 То конец игры (стакан заполнен
return;
}
if (keypressed=="left" || kepyressed=="right") {
//2. если нажата клавиша влево или вправо:
var newfigx=figx+(keypressed=="left")?-1:1;
if (can_exist(FIGURES[fig][figr],newfigx,figy)) figx=newfigx;
}
if (keypressed=="up") {
// 3. если нажата клавиша вверх (поворот)
var newfigr=(figr==3)?0:figr+1;
if (can_exist(FIGURES[fig][newfigr],figx,figy)) figr=newfigr;
}
if (can_exist(FIGURES[fig][figr],figx,figy+1)) { // 4. если можно опустить вниз
figy++;
return;
}
// 5. добавляем фигуру в FIELD
// 6. удаляем фигуру
fig=undefined;
// 7. проверяем, есть ли полностью заполненные строчки
}

Ну вот собственно и всё