Урок 47. Игра тетрис на Arduino

В этом уроке мы создадим игру «Тетрис». Это известная многим игра «головоломка» изобретённая и написанная советским программистом Алексеем Пажитновым. Первоначальная версия игры была написана для компьютера «Электроника-60» на языке Паскаль. Игра была выпущена 6 июня 1984 г. Мировую известность игра приобрела благодаря её выпуску на портативной консоли GameBoy компании Nintendo

Правила игры «Тетрис»:

Вверху игрового поля появляются случайные геометрические фигуры, которые падают пока не достигнут низа поля или других фигур. Во время падения, фигуры можно сдвигать по горизонтали (влево / вправо), поворачивать на 90° (по часовой стрелке) и ускорять их падение. Таким образом игрок может выбирать место падения фигуры. Если из упавших фигур соберётся горизонтальный ряд без пробелов (пустот), то этот ряд исчезнет, а всё что было над ним опустится. За каждый исчезнувший ряд игрок получает очки. Скорость падения фигур увеличивается с каждым новым уровнем игры. Уровень игры увеличивается через определённое количество появившихся фигур. Игра заканчивается если новая фигура не может появиться вверху игрового поля по причине того что ей мешают уже имеющиеся там фигуры.

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

Нам понадобится:

Для реализации проекта нам необходимо установить библиотеку:

  • iarduino_OLED - графическая библиотека для работы с Trema OLED дисплеями.

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.

Видео:


Схема подключения:

  • Перед подключением модулей, закрепите винтами нейлоновые стойки в отверстиях секций 1, 2, 3, 5 и 6 Trema Set Shield.
  • Установите Trema Set Shield на Arduino Uno.

Остальные модули устанавливаются на Trema Set Shield следующим образом: Trema Кнопки устанавливаются в центр нижних колодок секций 1, 2, 5 и 6, а Trema OLED-дисплей 128x64 устанавливается в верхнюю колодку 3 секции, как это показано на рисунках ниже.

После чего закрепите модули вкрутив через их отверстия нейлоновые винты в установленные ранее нейлоновые стойки (нейлоновые стойки и винты входят в комплектацию Trema Set Shield).

Игра тетрис на Arduino Uno

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

Назначение кнопок:
  • L (Left) смещение фигуры влево.
  • R (Right) смешение фигуры вправо.
  • T (Turn) поворот фигуры на 90° по часовой стрелке.
  • D (Down) сброс фигуры вниз (ускорение падения фигуры).

Код программы:

Код может показаться немного громоздким. Для понимания кода рекомендуем сначала прочитать раздел «алгоритм работы» следующий сразу за кодом. А потом ознакомиться с комментариями в строках самого кода.

#include <iarduino_OLED.h>                                                            // Подключаем библиотеку iarduino_OLED.
iarduino_OLED   myOLED(0x78);                                                         // Объявляем объект myOLED, указывая адрес дисплея на шине I2C: 0x78 (если учитывать бит RW=0).
                                                                                      //
#define         pinBtnL   A0                                                          // Номер вывода к которому подключена кнопка L - сдвиг фигуры влево     (Left ).
#define         pinBtnR   A3                                                          // Номер вывода к которому подключена кнопка R - сдвиг фигуры вправо    (Right).
#define         pinBtnT   11                                                          // Номер вывода к которому подключена кнопка T - поворот фигуры по ч.с. (Turn ).
#define         pinBtnD   6                                                           // Номер вывода к которому подключена кнопка D - опустить фигуру вниз   (Down ).
#define         pinSeed   A1                                                          // Номер аналогового входа значения которого будут использованы для выбора случайной точки входа в последовательность псевдослучайных чисел, для корректной работы функции random().
#define         GAME_OFF  0                                                           // Одно из состояний игры (будет храниться в переменной state).
#define         GAME_ON   1                                                           // Одно из состояний игры (будет храниться в переменной state).
#define         GAME_OVER 2                                                           // Одно из состояний игры (будет храниться в переменной state).
                /* Подключаем шрифты и картинки: */                                   //
extern uint8_t  SmallFontRus[];                                                       // Подключаем шрифт SmallFontRus (шрифт предустановлен в библиотеке iarduino_OLED).
extern uint8_t  MediumFontRus[];                                                      // Подключаем шрифт MediumFontRus (шрифт предустановлен в библиотеке iarduino_OLED).
extern uint8_t  Img_Logo[];                                                           // Подключаем изображение Img_Logo (изображение предустановлено в библиотеке iarduino_OLED).
                /* Определяем настраиваемые значения: */                              //
const  uint16_t sumToNewLevel   = 20;                                                 // Определяем количество фигур которое нужно набрать для перехода на новый уровень.
const  uint16_t startTime       = 1000;                                               // Определяем начальное время в мс с которым фигура опускается на одну клетку игрового стола (начальная скорость игры).
const  uint16_t changeTimeLevel = 10;                                                 // Определяем значение в мс на которое уменьшается время опускания фигуры с каждым новым уровнем (увеличение скорости).
const  uint32_t tmrKeyHold      = 400;                                                // Определяем время в мс после которого нажатая кнопка будет считаться удерживаемой (залипание при нажатии кнопки).
const  uint8_t  scrBoundary     = 68;                                                 // Определяем границу отделяющую левую и правую части экрана.
const  uint8_t  newCubeSize     = 6;                                                  // Определяем размер клеток в пикселях для поля отображения будущих фигур.
const  uint8_t  tableCubeSize   = 3;                                                  // Определяем размер клеток в пикселях для игрового стола.
                /* Объявляем массивы и переменные: */                                 //
const  uint8_t  tableRows       = (         64-3) / (tableCubeSize+1);                // Определяем количество строк    на игровом столе (зависит от размера клеток игрового стола). Количество = (высота экрана     - 3 линии) / (высота клеток игрового стола + 1 разделительная линия).
const  uint8_t  tableCols       = (scrBoundary-4) / (tableCubeSize+1);                // Определяем количество столбцов на игровом столе (зависит от размера клеток игрового стола). Количество = (ширина до границы - 4 линии) / (ширина клеток игрового стола + 1 разделительная линия).
       uint32_t tableArray        [tableRows];                                        // Объявляем  массив клеток игрового стола. Каждый элемент массива - это строки игрового стола, а каждый бит это клетка на игровом столе (0-пуста, 1-закрашена).
       uint8_t  figureArray       [6];                                                // Объявляем  массив для хранения вида движущейся фигуры.    |  Каждый элемент массивов figureArray и figureArrayNew - это строки фигуры, в которых биты 4-0 являются флагами наличия кубиков в столбцах стоки (0-нет, 1-есть).
       uint8_t  figureArrayNew    [6];                                                // Объявляем  массив для хранения вида будущей фигуры.       |  Последний элемент массивов figureArray и figureArrayNew - это флаг запрещающий поворот фигуры (0-разрешено, 1-запрещено).
        int8_t  figurePos         [4];                                                // Объявляем  массив позиции движущейся фигуры на игровом столе (в клетках). [0]: отступ слева, [1]: отступ сверху, [2]: начальный отступ новой фигуры слева, [3]: начальный отступ новой фигуры сверху.
       uint8_t  state;                                                                // Объявляем  переменную для хранения текущего состояния игры: GAME_OFF, GAME_ON, GAME_OVER.
       uint32_t sumfig;                                                               // Объявляем  счётчик количества созданных фигур:  0-4294967296.
       uint8_t  level;                                                                // Объявляем  счётчик текущего уровня игры:        1-99.
       uint32_t points;                                                               // Объявляем  счётчик набранного количества балов: 0-99999.
       uint32_t tmrShift;                                                             // Объявляем  переменную для хранения времени следующего сдвига фигуры вниз.
       bool     valBtnL, valBtnR, valBtnT, valBtnD;                                   // Объявляем  переменные для хранения состояния кнопок.
                /* Объявляем функции: */                                              //
       void     getKeys          (void);                                              // Объявляем  функцию получения состояния всех кнопок в переменные valBtnL, valBtnR, valBtnT, valBtnD.
       void     showWelcome      (void);                                              // Объявляем  функцию прорисовки анимированного приветствия.
       void     showGameScreen   (void);                                              // Объявляем  функцию прорисовки игрового поля.
       void     showFigure       (bool, bool);                                        // Объявляем  функцию прорисовки или затирания фигуры (аргументы: флаг [1-фигура на игровом столе, 0-будущая фигура], флаг [1-прорисовать фигуру, 0-затереть фигуру]).
       void     showTable        (void);                                              // Объявляем  функцию прорисовки всех кубиков в клетках игрового стола.
       bool     createNewFigure  (void);                                              // Объявляем  функцию создания будущей фигуры. Функция возвращает false если вставить ранее созданную будущую фигуру на игровой стол не удается (завершение игры).
       void     turnFigure       (bool, uint8_t=1);                                   // Объявляем  функцию поворота фигуры на 90° по часовой стрелке (аргументы: флаг [1-фигура на игровом столе, 0-будущая фигура], количество поворотов будущей фигуры на 90° по часовой стрелке).
       bool     shiftFigure      (uint8_t);                                           // Объявляем  функцию сдвига фигуры на одну клетку игрового стола. Если возвращает false если сдвиг невозможен. (аргументы: 1 - влево, 2 - вправо, 3 - вниз, иначе - без сдвига).
       bool     checkFigure      (void);                                              // Объявляем  функцию проверки корректности положения фигуры на игровом столе (перед её отрисовкой). Если положение фигуры некорректно, то функция вернёт false.
       bool     checkTable       (void);                                              // Объявляем  функцию проверки полностью заполненных строк на игровом столе (функцию требуется вызывать до создания новой фигуры на игровом столе). Если на игровом столе есть полностью заполненные строки, то функция вернёт true.
       uint8_t  deletTableRows   (void);                                              // Объявляем  функцию удаления полностью заполненных строк на игровом столе (функцию требуется вызывать до создания новой фигуры на игровом столе). Функция возвращает количество удалённых строк с игрового стола.
                                                                                      //
                                                                                      // Примечание: игровой стол   - прямоугольная область которая заполняется перемещаемыми фигурами.
                                                                                      //             будущая фигура - фигура отображаемая справа от игрового стола.
                                                                                      //             новая фигура   - фигура которая только появилась на игровом столе.
void setup(){                                                                         //
    myOLED.begin();                                                                   // Инициируем работу с Trema OLED дисплеем.
    myOLED.autoUpdate(false);                                                         // Запрещаем автоматический вывод данных без обращения к функции myOLED.update().
//  myOLED.setCoding(TXT_UTF8);                                                       // Указываем кодировку текста в скетче. Если на дисплее не отображается Русский алфавит, то раскомментируйте функцию setCoding и замените параметр TXT_UTF8, на TXT_CP866 или TXT_WIN1251.
    pinMode(pinBtnL, INPUT);                                                          // Конфигурируем вывод pinBtnL как вход.
    pinMode(pinBtnR, INPUT);                                                          // Конфигурируем вывод pinBtnR как вход.
    pinMode(pinBtnT, INPUT);                                                          // Конфигурируем вывод pinBtnT как вход.
    pinMode(pinBtnD, INPUT);                                                          // Конфигурируем вывод pinBtnD как вход.
    randomSeed(analogRead(pinSeed)); random(12345);                                   // Выбираем случайную точку входа в последовательность псевдослучайных чисел, для корректной работы функции random().
    randomSeed(analogRead(pinSeed) + random(12345));                                  // Выбираем случайную точку входа в последовательность псевдослучайных чисел, для корректной работы функции random().
                                     random(12345);                                   // Вызываем функцию random() в холостую (т.к. её первое значение после функции randomSeed(...) часто повторяется).
    figurePos[2] = (int8_t(tableCols)-5) / 2;                                         // Определяем начальный отступ новой фигуры слева (количество клеток от левого края игрового стола).
    showWelcome();                                                                    // Выводим анимированное приветствие.
    state=GAME_OFF;                                                                   // Переводим состояние игры в GAME_OFF.
}                                                                                     // 
                                                                                      //
void loop(){                                                                          //
    getKeys();                                                                        // Получаем состояния всех кнопок в переменные valBtnL, valBtnR, valBtnT, valBtnD.
//  Не играем:                                                                        //
    if(state==GAME_OFF){                                                              // Если состояние игры равно GAME_OFF, то ...
        if(valBtnL||valBtnR||valBtnT||valBtnD){                                       // Если нажата любая кнопка, то ...
            state    = GAME_ON;                                                       // Переводим состояние игры в GAME_ON (играем).
            sumfig   = 0;                                                             // Сбрасываем счётчик количества созданных фигур.
            level    = 1;                                                             // Сбрасываем счётчик текущего уровня.
            points   = 0;                                                             // Сбрасываем счётчик набранного количества балов.
            tmrShift = 0;                                                             // Сбрасываем время следующего сдвига вниз фигуры на игровом столе.
            valBtnD  = 0;                                                             // Обнуляем состояние кнопки Down (состояние остальных кнопок обнулится при первом вызове функции getKeys).
            memset(tableArray    , 0, tableRows*4);                                   // Обнуляем массив tableArray заполняя нулями все tableRows его элементов по 4 байта каждый.
            memset(figureArray   , 0, 6          );                                   // Обнуляем массив figureArray заполняя нулями все 6 его элементов.
            memset(figureArrayNew, 0, 6          );                                   // Обнуляем массив figureArrayNew заполняя нулями все 6 его элементов.
            showGameScreen();                                                         // Прорисовываем игровое поле.
            createNewFigure();                                                        // Создаём будущую фигуру. При этом новой фигуры на игровом столе ещё нет.
            createNewFigure();                                                        // Создаём будущую фигуру. Теперь ранее созданная будущая фигура стала новой фигурой на игровом столе.
        }                                                                             //
    }else                                                                             //
//  Играем:                                                                           //
    if(state==GAME_ON){                                                               // Если состояние игры равно GAME_ON, то ...
        if(valBtnL){shiftFigure(1);}                                                  // Если нажата кнопка Left,  то сдвигаем фигуру игрового стола влево на одну клетку.
        if(valBtnR){shiftFigure(2);}                                                  // Если нажата кнопка Right, то сдвигаем фигуру игрового стола вправо на одну клетку.
        if(valBtnT){turnFigure(1);}                                                   // Если нажата кнопка Turn,  то поворачиваем фигуру игрового стола на 90° по часовой стрелке.
        if(valBtnD || (millis() > tmrShift) ){                                        // Если нажата кнопка Down или наступило время очередного сдвига вниз фигуры на игровом столе, то ...
            tmrShift = millis()+(startTime-((level-1)*changeTimeLevel));              // Обновляем время следующего сдвига вниз фигуры на игровом столе.
            if(!shiftFigure(3)){                                                      // Сдвигаем фигуру игрового стола на 1 клетку вниз. Если фигура достигла низа игрового стола или другой фигуры на игровом столе, то ...
                if( checkTable() ){ points+=deletTableRows(); }                       // Проверяем наличие заполненных строк на игровом столе, если они есть, то удаляем заполненные строки, добавляя их количество в переменную points.
                valBtnD = 0;                                                          // Обнуляем состояние кнопки Down (состояние остальных кнопок обнулится при первом вызове функции getKeys).
                level   = sumfig/sumToNewLevel+1;                                     // Определяем текущий уровень игры (он зависит от количества уже выведенных фигур).
                sumfig++;                                                             // Увеличиваем счётчик созданных фигур.
                myOLED.setFont(SmallFontRus);                                         // Указываем шрифт который требуется использовать для вывода цифр и текста.
                myOLED.print( level  , scrBoundary + 8*myOLED.getFontWidth(),  7);    // Выводим текущий уровень в указанную позицию на экране.
                myOLED.print( points , scrBoundary + 5*myOLED.getFontWidth(), 20);    // Выводим текущий уровень в указанную позицию на экране.
                myOLED.update();                                                      // Обновляем информацию на экране OLED дисплея.
                if(!createNewFigure()){state=GAME_OVER;}                              // Создаём будущую фигуру. Если создать фигуру невозможно, то переводим состояние игры в GAME_OVER (игра завершена).
            }                                                                         //
        }                                                                             //
    }else                                                                             //
//  Игра завершена:                                                                   //
    if(state==GAME_OVER){                                                             // Если состояние игры равно GAME_OVER, то ...
        for(uint8_t i=0; i<tableRows; i++){tableArray[i]=0xFFFFFFFF; showTable();}    // Выполняем анимацию построчного заполнения игрового стола сверху вниз.
        for(uint8_t i=0; i<tableRows; i++){tableArray[i]=0;          showTable();}    // Выполняем анимацию построчного очищения   игрового стола сверху вниз.
        myOLED.clrScr();                                                              // Чистим экран OLED дисплея.
        myOLED.setFont(MediumFontRus);                                                // Указываем шрифт который требуется использовать для вывода цифр и текста.
        myOLED.print( F("КОНЕЦ ИГРЫ") , OLED_C, OLED_C);                              // Выводим текст по центру экрана.
        myOLED.update();                                                              // Обновляем информацию на экране OLED дисплея.
        delay(1000);                                                                  // Ждём.
        showWelcome();                                                                // Выводим анимированное приветствие.
        valBtnD = 0;                                                                  // Обнуляем состояние кнопки Down (состояние остальных кнопок обнулится при первом вызове функции getKeys).
        state=GAME_OFF;                                                               // Переводим состояние игры в GAME_OFF.
    }                                                                                 //
}                                                                                     //
                                                                                      //
//  Получение состояния всех кнопок в переменные valBtnL, valBtnR, valBtnT, valBtnD:  // Значения возвращаемые функцией: нет.
void getKeys(void){                                                                   // Аргументы принимаемые функцией: нет.
    static bool     fL=0, fR=0, fT=0;                                                 // Определяем переменные для хранения флагов предыдущего состояния кнопок L,R,T (переменные объявленные со спецификатором static не потеряют свои значения между вызовами функции getKeys).
    static uint32_t tL=0, tR=0;                                                       // Определяем переменные для хранения времени момента нажатия на кнопки L и R   (переменные объявленные со спецификатором static не потеряют свои значения между вызовами функции getKeys).
           uint32_t t = millis();                                                     // Сохраняем время вызова функции getKeys().
    valBtnL=0; if(digitalRead(pinBtnL)){ if(!fL){tL=t; valBtnL=1;}else if(t>tL+tmrKeyHold){valBtnL=1;} fL=1;}else{fL=0;} // Если кнопка L нажата, то присваиваем переменной valBtnL значение 1, но только если кнопка не была нажата ранее (fL==0) или если она нажата дольше tmrKeyHold миллисекунд.
    valBtnR=0; if(digitalRead(pinBtnR)){ if(!fR){tR=t; valBtnR=1;}else if(t>tR+tmrKeyHold){valBtnR=1;} fR=1;}else{fR=0;} // Если кнопка R нажата, то присваиваем переменной valBtnR значение 1, но только если кнопка не была нажата ранее (fR==0) или если она нажата дольше tmrKeyHold миллисекунд.
    valBtnT=0; if(digitalRead(pinBtnT)){ if(!fT){valBtnT=1;} fT=1;}else{fT=0;}        // Если кнопка T нажата, то присваиваем переменной valBtnT значение 1, но только если кнопка не была нажата ранее (fT==0).
               if(digitalRead(pinBtnD)){ valBtnD=1;}                                  // Если кнопка D нажата, то присваиваем переменной valBtnD значение 1. Сброс переменной valBtnD осуществляется в коде цикла loop.
}                                                                                     //
                                                                                      //
//  Прорисовка приветствия:                                                           // Значения возвращаемые функцией: нет.
void showWelcome(){                                                                   // Аргументы принимаемые функцией: нет.
    myOLED.autoUpdate(true);                                                          // Разрешаем автоматический вывод данных без обращения к функции myOLED.update().
    myOLED.clrScr();                                                                  // Чистим экран OLED дисплея.
    myOLED.setFont(MediumFontRus);                                                    // Указываем шрифт который требуется использовать для вывода цифр и текста.
    myOLED.print( F("ТЕТРИС") , OLED_C, OLED_C);                                      // Выводим текст по центру экрана.
    delay(500);                                                                       // Ждём
    myOLED.drawRect( 11,  0,  20,  9, true );   delay(200);                           // 0        Выводим заставку:
    myOLED.drawRect(  0, 54,   9, 63, false);   delay(200);                           // 1        Кубики фигур прорисовываются в следующем порядке ...
    myOLED.drawRect(  0,  0,   9,  9, true );   delay(200);                           // 2
    myOLED.drawRect(118, 11, 127, 20, false);   delay(200);                           // 3
    myOLED.drawRect( 11, 43,  20, 52, false);   delay(200);                           // 4          ###      ##          20A      6B
    myOLED.drawRect(  0, 11,   9, 20, true );   delay(200);                           // 5          #         ##         5         73
    myOLED.drawRect( 96,  0, 105,  9, false);   delay(200);                           // 6             ТЕТРИС   
    myOLED.drawRect(107, 11, 116, 20, false);   delay(200);                           // 7           #        ##          4
    myOLED.drawRect( 22, 54,  31, 63, false);   delay(200);                           // 8          ###       ##         198      
    myOLED.drawRect( 11, 54,  20, 63, false);   delay(200);                           // 9
    myOLED.drawRect( 22,  0,  31,  9, true );   delay(200);                           // A
    myOLED.drawRect(107,  0, 116,  9, false);   delay(200);                           // B
    myOLED.drawImage(Img_Logo, OLED_R, OLED_B); delay(200);                           // Выводим картинку Img_Logo в правом нижнем углу экрана.
    myOLED.invText(true); myOLED.bgText(false);                                       // Инвертируем цвет текста и запрещаем выводить цвет фона.
    myOLED.print( F("ТЕТРИС") , OLED_C, OLED_C);                                      // Выводим текст по центру экрана. Текст будет чёрным, а белый фон не отобразится. Таким образом мы закрасим предыдущий текст.
    myOLED.setFont(SmallFontRus);                                                     // Указываем шрифт который требуется использовать для вывода цифр и текста.
    myOLED.invText(false); myOLED.bgText(true);                                       // Возвращаем нормальный цвет текста и разрешаем выводить цвет фона.
    myOLED.print( F("www.iarduino.ru") , OLED_C, OLED_C);                             // Выводим текст по центру экрана.
    myOLED.autoUpdate(false);                                                         // Запрещаем автоматический вывод данных без обращения к функции myOLED.update().
}                                                                                     //
                                                                                      //
//  Прорисовка игрового поля:                                                         // Значения возвращаемые функцией: нет.
void showGameScreen(void){                                                            // Аргументы принимаемые функцией: нет.
    myOLED.clrScr();                                                                  // Чистим экран OLED дисплея.
    myOLED.setFont(SmallFontRus);                                                     // Указываем шрифт который требуется использовать для вывода цифр и текста.
    myOLED.drawRect(0,0,tableCols*(tableCubeSize+1)+2,tableRows*(tableCubeSize+1)+2); // Выводим рамку игрового стола.
    myOLED.print( F("УРОВЕНЬ:") , scrBoundary, 7 );                                   // Выводим текст в указанную позицию на экране.
    myOLED.print( level );                                                            // Выводим текущий уровень сразу после текста.
    myOLED.print( F("СЧЁТ:")    , scrBoundary, 20);                                   // Выводим текст в указанную позицию на экране.
    myOLED.print( points );                                                           // Выводим текущий счёт сразу после текста.
    myOLED.update();                                                                  // Обновляем информацию на экране OLED дисплея.
}                                                                                     //
                                                                                      //
//  Прорисовка или затирание фигуры:                                                  // Значения возвращаемые функцией: нет.
void showFigure(bool i, bool j){                                                      // Аргументы принимаемые функцией: i - флаг (1-фигура на игровом столе, 0-будущая фигура), j - флаг (1-прорисовать фигуру, 0-затереть фигуру).
    int8_t x0, y0;                                                                    // Объявляем переменные для хранения координат верхнего левого угла массива фигуры в пикселях.
    int8_t x1, y1;                                                                    // Объявляем переменные для хранения координат верхнего левого угла кубика фигуры в пикселях.
    int8_t s = i ? tableCubeSize : newCubeSize;                                       // Определяем размер кубиков фигуры.
    if(i){                                                                            // Если первый аргумент функции равен 1, то ...
    //  Прорисовываем или затираем фигуру на игровом столе:                           //
        x0 = 2+figurePos[0]*(s+1);                                                    // Определяем координату левого угла массива фигуры в пикселях.
        y0 = 2+figurePos[1]*(s+1);                                                    // Определяем координату верхнего угла массива фигуры в пикселях.
        for(uint8_t row=0; row<5; row++){ y1=row*(s+1);                               // Проходим по клеткам фигуры сверху вниз (строкам).
        for(uint8_t col=0; col<5; col++){ x1=col*(s+1);                               // Проходим по клеткам фигуры справа на лево (колонкам).
        if(bitRead(figureArray[row], 4-col)){                                         // Если бит 4-col элемента row массива figureArray равен 1, значит кубик есть, тогда ...
        if(y0+y1>1){                                                                  // Если позиция кубика находится в области игрового стола, тогда ...
            myOLED.drawRect(x0+x1, y0+y1, x0+x1+s-1, y0+y1+s-1, true, j);             // Прорисовываем закрашенный кубик на игровом столе.
        }}}}                                                                          //
        myOLED.update();                                                              // Обновляем информацию на экране OLED дисплея.
    }else{                                                                            // Если первый аргумент функции равен 0, то ...
    //  Прорисовываем или затираем будущую фигуру:                                    //
        x0 = scrBoundary;                                                             // Определяем координату левого угла массива фигуры в пикселях.
        y0 = 30;                                                                      // Определяем координату верхнего угла массива фигуры в пикселях.
        if(j){                                                                        // Если второй аргумент функции равен 1, то ...
        //  Прорисовываем будущую фигуру:                                             //
            for(uint8_t row=0; row<5; row++){ y1=row*(s+1);                           // Проходим по клеткам фигуры сверху вниз (строкам).
            for(uint8_t col=0; col<5; col++){ x1=col*(s+1);                           // Проходим по клеткам фигуры справа на лево (колонкам).
            if(bitRead(figureArrayNew[row], 4-col)){                                  // Если бит 4-col элемента row массива figureArrayNew равен 1, значит кубик есть, тогда ...
                myOLED.drawRect(x0+x1, y0+y1, x0+x1+s-1, y0+y1+s-1);                  // Прорисовываем не закрашенный квадрат белого цвета.
            }}}                                                                       //
        }else{                                                                        // Если второй аргумент функции равен 0, то ...
        //  Затираем место под будущую фигуру:                                        //
            myOLED.drawRect(x0, y0, x0+5*(s+1), y0+5*(s+1), true, 0);                 // Прорисовываем закрашенный квадрат черного цвета покрывая всю площадь предназначенную для вывода будущих фигур.
        }                                                                             //
        myOLED.update();                                                              // Обновляем информацию на экране OLED дисплея.
    }                                                                                 //
}                                                                                     //
                                                                                      //
//  Прорисовка всех кубиков в клетках игрового стола:                                 // Значения возвращаемые функцией: нет.
void showTable(){                                                                     // Аргументы принимаемые функцией: нет.
    int8_t x, y;                                                                      // Объявляем переменные для хранения координат верхнего левого угла клетки игрового стола в пикселях.
    int8_t s = tableCubeSize;                                                         // Определяем размер клеток игрового стола.
    for(uint8_t row=0; row<tableRows; row++){ y=2+row*(s+1);                          // Проходим по клеткам игрового стола сверху вниз (строкам).
    for(uint8_t col=0; col<tableCols; col++){ x=2+(tableCols-col-1)*(s+1);            // Проходим по клеткам игрового стола справа на лево (колонкам).
        myOLED.drawRect(x, y, x+s-1, y+s-1, true, bitRead(tableArray[row], col));     // Прорисовываем кубики в клетках игрового стола.
    }                                                                                 //
    }                                                                                 //
    myOLED.update();                                                                  // Обновляем информацию на экране OLED дисплея.
}                                                                                     //
                                                                                      //
//  Создание будущей фигуры:                                                          // Значения возвращаемые функцией: функция вернёт false если вставить ранее созданную будущую фигуру на игровой стол не удается (завершение игры).
bool createNewFigure(){                                                               // Аргументы принимаемые функцией: нет.
//  Превращаем ранее созданную будущую фигуру в новую фигуру на игровом столе:        // 
    memcpy(figureArray, figureArrayNew, 6);                                           // Копируем ранее созданную будущую фигуру в новую фигуру на игровом столе (копируем все 6 элементов массива figureArrayNew в массив figureArray).
    figurePos[0] = figurePos[2];                                                      // Определяем отсуп  слева  для новой фигуры на игровом столе из начального отступа для новой фигуры.
    figurePos[1] = figurePos[3];                                                      // Определяем отступ сверху для новой фигуры на игровом столе из начального отступа для новой фигуры.
    if(!shiftFigure(0)){return false;}                                                // Выводим новую фигуру на игровой стол и возвращаем false если положение этой фигуры некорректно.
//  Создаём будущую фигуру:                                                           // 
    switch(random(7)){                                                                // Переходим к созданию будущей фигуры, в зависимости от случайного числа со значением от 0 до 6 включительно...
        case 0:                                                                       // Если случайное число равно 0, то создаём фигуру...
            figureArrayNew[0] = B00000;                                               //  
            figureArrayNew[1] = B01000;                                               //  #
            figureArrayNew[2] = B01110;                                               //  ###
            figureArrayNew[3] = B00000;                                               //  
            figureArrayNew[4] = B00000;                                               //  
            figureArrayNew[5] = 0;                                                    // Нет запрета на поворот фигуры.
        break;                                                                        //
        case 1:                                                                       // Если случайное число равно 1, то создаём фигуру...
            figureArrayNew[0] = B00000;                                               //  
            figureArrayNew[1] = B00000;                                               //  
            figureArrayNew[2] = B01110;                                               //  ###
            figureArrayNew[3] = B01000;                                               //  #
            figureArrayNew[4] = B00000;                                               //  
            figureArrayNew[5] = 0;                                                    // Нет запрета на поворот фигуры.
        break;                                                                        //
        case 2:                                                                       // Если случайное число равно 2, то создаём фигуру...
            figureArrayNew[0] = B00000;                                               //  
            figureArrayNew[1] = B00100;                                               //   #
            figureArrayNew[2] = B01110;                                               //  ###
            figureArrayNew[3] = B00000;                                               //  
            figureArrayNew[4] = B00000;                                               //  
            figureArrayNew[5] = 0;                                                    // Нет запрета на поворот фигуры.
        break;                                                                        //
        case 3:                                                                       // Если случайное число равно 3, то создаём фигуру...
            figureArrayNew[0] = B00000;                                               //  
            figureArrayNew[1] = B01100;                                               //  ##
            figureArrayNew[2] = B00110;                                               //   ##
            figureArrayNew[3] = B00000;                                               //  
            figureArrayNew[4] = B00000;                                               //  
            figureArrayNew[5] = 0;                                                    // Нет запрета на поворот фигуры.
        break;                                                                        //
        case 4:                                                                       // Если случайное число равно 4, то создаём фигуру...
            figureArrayNew[0] = B00000;                                               //  
            figureArrayNew[1] = B00110;                                               //   ##
            figureArrayNew[2] = B01100;                                               //  ##
            figureArrayNew[3] = B00000;                                               //  
            figureArrayNew[4] = B00000;                                               //  
            figureArrayNew[5] = 0;                                                    // Нет запрета на поворот фигуры.
        break;                                                                        //
        case 5:                                                                       // Если случайное число равно 5, то создаём фигуру...
            figureArrayNew[0] = B00000;                                               // 
            figureArrayNew[1] = B01100;                                               //  ##
            figureArrayNew[2] = B01100;                                               //  ##
            figureArrayNew[3] = B00000;                                               //  
            figureArrayNew[4] = B00000;                                               //  
            figureArrayNew[5] = 1;                                                    // Есть запрет на поворот фигуры.
        break;                                                                        //
        case 6:                                                                       // Если случайное число равно 6, то создаём фигуру...
            figureArrayNew[0] = B00100;                                               //   #
            figureArrayNew[1] = B00100;                                               //   #
            figureArrayNew[2] = B00100;                                               //   #
            figureArrayNew[3] = B00100;                                               //   #
            figureArrayNew[4] = B00000;                                               //    
            figureArrayNew[5] = 0;                                                    // Нет запрета на поворот фигуры.
        break;                                                                        //
    }                                                                                 //
    turnFigure(0, random(4));                                                         // Поворачиваем созданную будущую фигуру на 90°. Случайное число от 0 до 3 включительно, указывает сколько раз требуется повернуть будущую фигуру.
    return true;                                                                      // Возвращаем true. Новая фигура установлена на игровом столе и будущая фигура создана.
}                                                                                     //
                                                                                      //
//  Поворот фигуры на 90° по часовой стрелке:                                         // Значения возвращаемые функцией: нет.
void turnFigure(bool i, uint8_t j){                                                   // Аргументы принимаемые функцией: i - флаг (1-фигура на игровом столе, 0-будущая фигура), j - количество поворотов на 90° по часовой стрелке (только для будущей фигуры).
    uint8_t figureArrayTemp[5];                                                       // Объявляем временный массив для хранения изначального вида фигуры.
    if(i){                                                                            // Если первый аргумент функции равен 1, то ...
    //  Поворачиваем фигуру на игровом столе:                                         //
        if(figureArray[5]==0){                                                        // Если фигуру на игровом столе не запрещено поворачивать, то ...
            showFigure(1, 0);                                                         // Затираем фигуру на игровом столе.
            memcpy(figureArrayTemp, figureArray, 5);                                  // Копируем 5 элементов массива figureArray в массив figureArrayTemp.
            memset(figureArray, 0, 5);                                                // Обнуляем изображение фигуры в массиве figureArray заполняя нулями 5 его элементов.
            for(uint8_t k=0; k<5; k++){                                               // Проходим по элементам массива figureArrayNew и битам элементов массива figureArrayTemp.
            for(uint8_t l=0; l<5; l++){                                               // Проходим по элементам массива figureArrayTemp и битам элементов массива figureArrayNew.
                bitWrite(figureArray[4-k], l, bitRead(figureArrayTemp[l],k) );        // Заполняем массив figureArrayNew повернутым массивом figureArrayTemp.
            }                                                                         //
            }                                                                         //
            if(!checkFigure()){memcpy(figureArray, figureArrayTemp, 5);}              // Проверяем корректность положения фигуры на игровом столе. Если положение не корректно, то возвращаем начальное значение массива figureArray из figureArrayTemp.
            showFigure(1, 1);                                                         // Прорисовываем фигуру на игровом столе.
        }                                                                             //
    }else{                                                                            // Если первый аргумент функции равен 0, то ...
    //  Поворачиваем будущую фигуру:                                                  //
        showFigure(0, 0);                                                             // Затираем предыдущую будущую фигуру.
        if(figureArrayNew[5]==0){                                                     // Если будущую фигуру не запрещено поворачивать, то ...
            for(uint8_t n=0; n<j; n++){                                               // Выполняем поворот фигуры на 90° указанное число раз.
                memcpy(figureArrayTemp, figureArrayNew, 5);                           // Копируем 5 элементов массива figureArrayNew в массив figureArrayTemp.
                memset(figureArrayNew, 0, 5);                                         // Обнуляем изображение фигуры в массиве figureArrayNew заполняя нулями 5 его элементов.
                for(uint8_t k=0; k<5; k++){                                           // Проходим по элементам массива figureArrayNew и битам элементов массива figureArrayTemp.
                for(uint8_t l=0; l<5; l++){                                           // Проходим по элементам массива figureArrayTemp и битам элементов массива figureArrayNew.
                    bitWrite(figureArrayNew[4-k], l, bitRead(figureArrayTemp[l],k) ); // Заполняем массив figureArrayNew повернутым массивом figureArrayTemp.
                }                                                                     //
                }                                                                     //
            }                                                                         //
        }                                                                             //
        figurePos[3] = 0;                                                             // Начальный отступ новой фигуры сверху равен 0 клеток.
        while(figureArrayNew[ (figurePos[3]*(-1)) ]==0){figurePos[3]--;}              // Если очередная сверху строка клеток будущей фигуры пуста, то уменьшаем начальный отступ сверху для новой фигуры (т.к. отступ может получиться отрицательным, то новая фигура будет сдвинута вверх при появлении на игровом столе).
        showFigure(0, 1);                                                             // Прорисовываем созданную фигуру.
    }                                                                                 //
}                                                                                     //
                                                                                      //
//  Cдвиг фигуры на одну клетку игрового стола:                                       // Значения возвращаемые функцией: Функция вернёт false если сдвиг фигуры невозможен.
bool shiftFigure(uint8_t i){                                                          // Аргументы принимаемые функцией: i = 1 - влево, 2 - вправо, 3 - вниз, иначе - без сдвига.
    switch(i){                                                                        // Переходим к сдвигу фигуры игрового стола на одну клетку, в направлении зависящем от аргумента функции ...
        case 1:                figurePos[0]--;                                        // Если фигуру требуется сдвинуть влево, то уменьшаем отступ фигуры слева на 1 клетку.
            if(checkFigure()){ figurePos[0]++; showFigure(1, 0);                      // Если положение фигуры корректно, то возвращаем отступ фигуры и затираем фигуру на игровом столе.
                               figurePos[0]--; showFigure(1, 1);                      // Опять уменьшаем отступ фигуры слева и прорисовываем фигуру на новом месте игрового стола.
            }else{             figurePos[0]++; return false;}                         // Иначе, если положение фигуры не корректно, то возвращаем отступ фигуры слева и возвращаем false.
        break;                                                                        //
        case 2:                figurePos[0]++;                                        // Если фигуру требуется сдвинуть вправо, то увеличиваем отступ фигуры слева на 1 клетку.
            if(checkFigure()){ figurePos[0]--; showFigure(1, 0);                      // Если положение фигуры корректно, то возвращаем отступ фигуры и затираем фигуру на игровом столе.
                               figurePos[0]++; showFigure(1, 1);                      // Увеличиваем отступ фигуры слева и прорисовываем фигуру на новом месте игрового стола.
            }else{             figurePos[0]--; return false;}                         // Иначе, если положение фигуры не корректно, то возвращаем отступ фигуры слева и возвращаем false.
        break;                                                                        //
        case 3:                figurePos[1]++;                                        // Если фигуру требуется сдвинуть вниз, то увеличиваем отступ фигуры сверху на 1 клетку.
            if(checkFigure()){ figurePos[1]--; showFigure(1, 0);                      // Если положение фигуры корректно, то возвращаем отступ фигуры и затираем фигуру на игровом столе.
                               figurePos[1]++; showFigure(1, 1);                      // Увеличиваем отступ фигуры и прорисовываем фигуру на новом месте игрового стола.
            }else{             figurePos[1]--; return false;}                         // Иначе, если положение фигуры не корректно, то возвращаем отступ фигуры и возвращаем false.
        break;                                                                        //
        default:               showFigure(1, 1); return checkFigure();                // Если фигуру не требуется сдвигать, то прорисовываем фигуру в её текущей позиции игрового стола и возвращаем результат корректности положения фигуры.
        break;                                                                        //
    }                                                                                 //
    return true;                                                                      //
}                                                                                     //
                                                                                      //
//  Проверка корректности положения фигуры на игровом столе:                          // Значения возвращаемые функцией: функция вернёт false если положение фигуры на игровом столе некорректно.
bool checkFigure(){                                                                   // Аргументы принимаемые функцией: нет.
    int8_t x = tableCols-figurePos[0]-1;                                              // Определяем отступ массива фигуры на игровом столе от его левого края в клетках.
    for(uint8_t row=0; row<5; row++){                                                 // Проходим по клеткам фигуры сверху вниз (строкам).
    for(uint8_t col=0; col<5; col++){                                                 // Проходим по клеткам фигуры справа на лево (колонкам).
    if(bitRead(figureArray[row], col)){                                               // Если бит col элемента row массива figureArrayNew равен 1, значит кубик есть, тогда ...
        if(figurePos[1]+row >= tableRows){return false;}                              // Если позиция кубика фигуры превышает количество строк (клеток по вертикали) игрового стола, значит положение фигуры некорректно (фигура вышла за нижнюю границу игрового стола).
        if(x-(4-col) >= tableCols){return false;}                                     // Если позиция кубика фигуры превышает количество столбцов (клеток по горизонтали) игрового стола, значит положение фигуры некорректно (фигура вышла за левую  границу игрового стола).
        if(x-(4-col) <  0        ){return false;}                                     // Если позиция кубика фигуры находится в отрицательном столбце (клетке) игрового стола, значит положение фигуры некорректно (фигура вышла за правую границу игрового стола).
        if(bitRead(tableArray[figurePos[1]+row],x-(4-col))){return false;}            // Если позиция кубика фигуры наложилась на кубик в клетке игрового стола, значит положение фигуры некорректно (фигура наложилась на уже имеющуюся фигуру игрового стола).
    }                                                                                 //
    }                                                                                 //
    }                                                                                 //
    return true;                                                                      //
}                                                                                     //
                                                                                      //
//  Проверка заполненных строк на игровом столе:                                      // Значения возвращаемые функцией: функция вернёт true если на игровом столе есть полностью заполненные строки.
bool checkTable(){                                                                    // Аргументы принимаемые функцией: нет.
//  Добавляем фигуру игрового стола в массив клеток игрового стола:                   //
    for(uint8_t row=0; row<5; row++){                                                 // Проходим по клеткам фигуры сверху вниз (строкам).
    for(uint8_t col=0; col<5; col++){                                                 // Проходим по клеткам фигуры справа на лево (колонкам).
    if(bitRead(figureArray[row], col)){                                               // Если бит col элемента row массива figureArrayNew равен 1, значит кубик есть, тогда ...
        bitSet(tableArray[figurePos[1]+row],tableCols-figurePos[0]-(4-col)-1);        // Устанавливаем в 1 бит массива tableArray соответствующий клетке игрового стола на которой находится кубик фигуры.
    }                                                                                 //
    }                                                                                 //
    }                                                                                 //
//  Проверяем игровой стол на наличие полных строк:                                   //
    uint32_t fullRows = 0;                                                            // Определяем переменную, значение которой будет эталоном для заполненной строки игрового стола.
    for(uint8_t i=0; i<tableCols; i++){bitSet(fullRows,i);}                           // Заполняем переменную fullRows эталонным значением.
    for(uint8_t i=0; i<tableRows; i++){if(tableArray[i]==fullRows){return true;}}     // Проходим по строкам игрового стола (сверху вниз) в поисках совпадений с эталонным значением. Если совпадение найдено - возвращаем true.
    return false;                                                                     // Возвращаем false.
}                                                                                     //
                                                                                      //
//  Удаление заполненных строк с игрового стола:                                      // Значения возвращаемые функцией: функция вернёт количество удалённых строк.
uint8_t deletTableRows(){                                                             // Аргументы принимаемые функцией: нет.
    uint8_t sum=0;                                                                    // Определяем счётчик стёртых строк.
    uint32_t fullRows = 0;                                                            // Определяем переменную, значение которой будет эталоном для заполненной строки игрового стола.
    for(uint8_t i=0; i<tableCols; i++){bitSet(fullRows,i);}                           // Заполняем переменную fullRows эталонным значением.
    for(uint8_t i=0; i<tableRows; i++){if(tableArray[i]==fullRows){tableArray[i]=0; sum++;}} // Проходим по строкам игрового стола (сверху вниз) в поисках совпадений с эталонным значением. Если совпадение найдено - увеличиваем счётчик и обнуляем строку.
    if(sum){                                                                          // Если были обнулены строки игрового стола, то ...
        showTable();                                                                  // Прорисовываем игровой стол с обнулёнными (пустыми) строками.
        delay(500);                                                                   // Вызываем задержку, чтоб пустые строки можно было увидеть
        for(uint8_t i=0; i<tableRows; i++){                                           // Проходим по строкам игрового стола (сверху вниз) в поисках пустых строк.
            if(tableArray[i]==0){                                                     // Если пустая строка обнаружена, то ...
                for(int8_t j=i; j>0; j--){tableArray[j]=tableArray[j-1];}             // Сдвигаем все строки находящиеся выше вниз, на одну строку (клетку).
            }                                                                         //
        }                                                                             //
        showTable();                                                                  // Прорисовываем игровой стол с удалёнными пустыми строками.
    }                                                                                 //
    return sum;                                                                       // Возвращаем количество удалённых пустых строк.
}                                                                                     //

Алгоритм работы:

  • В начале скетча (до кода setup) выполняются следующие действия:
    • Подключаем графическую библиотеку iarduino_OLED для работы с Trema OLED дисплеем.
    • Объявляем объект myOLED указывая адрес дисплея на шине I2C, он должен совпадать с адресом установленным переключателем на обратной стороне платы OLED дисплея.
    • Объявляем константы pinBtnL, pinBtnR, pinBtnT, pinBtnD, pinSeed с указанием номеров выводов Arduino, которые будут задействованы в скетче.
    • Объявляем константы GAME_OFF, GAME_ON, GAME_OVER для удобочитаемости скетча.
    • Подключаем шрифты и картинки предустановленные в библиотеке myOLED.
    • Определяем константы с настраиваемыми значениями. Меняя эти значения можно менять размеры игрового стола, размеры фигур, скорость игры и время «залипания» кнопок.
    • Объявляем массивы и переменные участвующие в работе скетча.
    • Объявляем функции используемые в скетче.
  • В коде setup выполняются следующие действия:
    • Инициируем работу с Trema OLED дисплеем и запрещаем автоматический вывод данных.
    • Указываем кодировку текста в скетче (если требуется).
    • Конфигурируем выводы к которым подключены кнопки.
    • Готовим корректную работу функции random() для генерации псевдослучайных чисел.
    • Выводим анимированное приветствие (текст «Тетрис» с появляющимися фигурами).
    • Переводим состояние игры в GAME_OFF «Не играем» (ждём нажатие любой кнопки).
  • В коде loop сначала выполняется чтение состояний кнопок, после чего выполняется 1 из 3 частей:
    • «Не играем» - эта часть кода ожидает нажатия на любую кнопку. Если любая кнопка будет нажата, будут подготовлены переменные, прорисуется игровое поле и игра перейдёт в состояние GAME_ON «Играем».
    • «Играем» - эта часть кода является основной. Здесь в 18 строках кода реализован весь алгоритм игры. Он более подробно описан ниже.
    • «Игра завершена» - эта часть кода содержит анимацию закраски и очистки игрового стола, вывод текста «КОНЕЦ ИГРЫ», вывод анимированного приветствия и перевод игры в состояние GAME_OFF «Не играем».
  • Весть алгоритм игры полностью реализован в разделе «Играем» который состоит из 4 частей:
    (каждая часть этого раздела заключена в тело оператора if).
    • Первая часть сдвигает фигуру на игровом столе влево. Код в теле оператора if выполняется только при нажатии на кнопку Left. Единственная функция shiftFigure() в теле оператора if, выполняет сдвиг фигуры игрового стола на одну клетку. Параметр функции равный 1 указывает сдвинуть фигуру влево.
    • Вторая часть сдвигает фигуру на игровом столе вправо. Код в теле оператора if выполняется только при нажатии на кнопку Right. Единственная функция shiftFigure() в теле оператора if, выполняет сдвиг фигуры игрового стола на одну клетку. Параметр функции равный 2 указывает сдвинуть фигуру вправо.
    • Третья часть выполняет поворот фигуры на игровом столе. Код в теле оператора if выполняется только при нажатии на кнопку Turn. Единственная функция turnFigure() в теле оператора if, выполняет поворот фигуры на 90°. Первый параметр функции равный 1 указывает что повернуть требуется фигуру на игровом столе.
    • Четвёртая часть выполняет сдвиг фигуры игрового стола на одну клетку вниз. Код в теле оператора if выполняется как от нажатия на кнопку Down, так и по достижении времени tmrShift. Параметр функции равный 3 указывает сдвинуть фигуру вниз. Отпускание кнопки Down не заблокирует выполнение кода в теле оператора if при следующем проходе цикла loop (Если нажать и отпустить кнопку Down, то фигура будет сдвигаться вниз пока не достигнет дна или других фигур).
      Код в теле оператора If выполняет следующие действия:
      • Обновляем время tmrShift для следующего сдвига фигуры вниз на игровом столе.
      • Сдвигаем фигуру игрового стола на 1 клетку вниз. Проверяя не достигла ли фигура дна игрового стола или другой фигуры на игровом столе. Если не достигла, то на этом выполнение данного участка кода будет закончено.
      • Если фигура достигла дна игрового стола или другой фигуры (закончила падение), то выполняются следующие действия:
        • Проверяем наличие заполненных строк игрового стола, если они есть, то они удаляются, со сдвигом всего что находится выше и добавлением бала игроку.
        • Сбрасываем флаг нажатия на кнопку Down (на случай если фигура падала принудительно)
        • Обновляем текущий уровень игры в соответствии со значением счётчика созданных фигур.
        • Увеличиваем счётчик созданных фигур.
        • Выводим номер текущего уровня игры и количество набранных баллов.
        • Создаём будущую фигуру с выводом её изображения в поле справа от игрового стола, а ту фигуру которая ранее находилась в этом поле переносим на верх игрового стола (она станет новой фигурой в игре).
        • Если новую фигуру не удалось разместить на игровом столе (ей мешают другие фигуры), значит игра закончена, переводим состояние игры в GAME_OVER «Игра завершена».
      • Действия перечисленные выше выполняются вызовом функций:
        • shiftFigure(); - выполняет сдвиг фигуры на одну клетку игрового стола и возвращает false если сдвиг невозможен.
        • turnFigure(); - поворачивает фигуру игрового стола на 90°.
        • checkTable(); - проверяет наличие заполненных строк игрового стола, возвращая true или false.
        • deletTableRows(); - удаляет все заполненные строки с игрового стола, возвращая количество удалённых строк.
        • createNewFigure(); - создаёт будущую фигуру а предыдущую будущую фигуру делает новой на игровом столе, возвращая false если не удалось вставить фигуру на игровой стол.

Все строки скетча (кода программы) прокомментированы, так что Вы можете подробнее ознакомиться с кодом прочитав комментарии строк.

Ссылки:

Обсуждение