IT Notes

Тетрис на C++: Статическая модель

Рекомендую сначала ознакомиться с вводной заметкой, посвященной реализации тетриса на C++, если вы еще этого не сделали.

Игровое поле тетриса

Игровое поле тетриса представляет собой простой двумерный массив или матрицу. На C++ для этого можно использовать следующую структуру:

typedef std::vector< std::vector< int > > Matrix;

tetris-game-field

Каждый элемент матрицы, представляющей игровое поле, определяет состояние соответствующего "блока" на некоторой позиции (x; y). Под блоками будем понимать клетки игрового поля. Для самих состояний достаточно иметь всего 3 условных обозначения:

  1. Пустой блок: подойдет значение 0;
  2. Занятый блок: 1, 2, и т.д.;
  3. Блок за пределами игрового поля: -1.

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

Как видно на рисунке слева, каждому числовому обозначению мы можем назначить некоторый цвет. Однако этот аспект не касается Модели, а полностью зависит от Представления, поэтому сейчас мы на нем останавливаться не будем.

Изображенному игровому полю на C++ соответствует следующая матрица:

    Matrix fieldMatrix = {
        { 0, 0, 0, 0, 0 },
        { 0, 0, 0, 0, 0 },
        { 0, 0, 0, 0, 0 },
        { 0, 0, 0, 7, 0 },
        { 0, 0, 0, 7, 7 },
        { 0, 0, 0, 5, 7 },
        { 0, 0, 5, 5, 5 },
    };

Для вывода на экран текущего состояния игрового поля нам требуется лишь доступ к его размерности и элементам. Обратите внимание, что если запрос элементов организовать на манер Представления, то есть по координатам (а не рядам и столбцам), то возникает небольшая путаница. Элемент fieldMatrix[ x ][ y ] на самом деле соответствует элементу из x-го ряда (ходим вверх-вниз) и y-го столбца (ходим влево-вправо). То есть координаты оказываются перепутаны, поэтому для исправления ситуации приходится использовать конструкцию вида fieldMatrix[ y ][ x ].

Игровые элементы тетриса

На этом можно было бы считать, что статическая модель тетриса практически готова, но не так быстро. До сих пор мы не учитывали, что существует еще и "активный элемент", которым может управлять игрок. Организация внутренней системы координат элемента ничем не отличается от того, что мы уже разобрали для игрового поля. Но в отличие от блоков, относящихся к полю, он имеет логику движения. Здесь нам приходится немного забегать вперед и учитывать динамику поведения элементов, поскольку мы должны уметь зафиксировать любое промежуточное состояние игры, чтобы Представление могло корректно провести отрисовку.

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

tetris-item

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

Matrix itemMatrix = {
    { 0, 3, 0 },
    { 0, 3, 0 },
    { 0, 3, 3 },
};

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

// Получение x-координаты в точках для блока с внутренней координатой по оси x
int TetrisItem::getBlockXPoints( int innerXBlocks ) const {
    int innerXPoints = blocksToPoints( innerXBlocks ) + HALF_BLOCK_SIZE;
    int innerXCenterPoints = blocksToPoints( getSizeBlocks() ) / 2;
    return m_xPoints - innerXCenterPoints + innerXPoints;
}

// Получение y-координаты в точках для блока с внутренней координатой по оси y
int TetrisItem::getBlockYPoints( int innerYBlocks ) const {
    int innerYPoints = blocksToPoints( innerYBlocks ) + HALF_BLOCK_SIZE;
    int innerYCenterPoints = blocksToPoints( getSizeBlocks() ) / 2;
    return m_yPoints - innerYCenterPoints + innerYPoints;
}

// Извлечение типа блока по внутренним координатам
int TetrisItem::getBlockType( int innerXBlocks, int innerYBlocks ) const {
    if( innerXBlocks < 0 ||
        getSizeBlocks() <= innerXBlocks ||
        innerYBlocks < 0 ||
        getSizeBlocks() <= innerYBlocks
    ) {
        return 0;
    }

    return m_matrix[ innerYBlocks ][ innerXBlocks ];
}

Заключение

На уровне статики можно считать, что игровое поле и активный элемент живут в параллельных вселенных. Для Представления это не имеет никакого значения, поскольку ему достаточно уметь рисовать блоки по заданным координатам в точках, что мы уже обеспечили. Модель должна сама гарантировать, что состояние игры остается корректным в каждый момент времени. А это достигается путем введения обнаружения столкновений, о чем мы поговорим в следующий раз…

Похожие публикации