Пишем Тетрис на Flutter
Дело было вечером, делать было нечего (с)..В качестве разминки решил в кои-то веки написать игру. Пришла на ум идея написать давнюю мечту — тетрис. В т.ч. использовать будем более ранние наработки по работе с Hive и Requets оформленные в виде отдельных классов.

Из интересного:
1) Для отображения стакана и фигур используем три массива:
LandMatrix — массив вида ширина/высота — фактически «стакан» с «приземленными» фигурами. Точка пересечения массива — цвет квадратика. Если пусто — значит в точке стакана ничего нет
MoveFig — массив движущейся по стакану фигуры. В момент совпадения низа фигуры с любой не пустой точкой стакана фигура переносится в «стакан»
ShadowRotareFig — вспомогательный массив, используемый для поворота фигуры. В момент когда фигуру необходимо повернуть, она переносится из MoveFig в этот массив, поворачивается, и переносится обратно в MoveFig. Создание отдельного массива для поворота было обусловлено способом хранения фигур — хранится не матрица, а просто массив текущих координат квадратиков из которых составлена фигура.
2) Отрисовка стакана происходит после того, как фигура перенесена в массив стакана. Причины переноса — касание дна стакана.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | List <Widget> MyLandView(){     int poz=0;     return List<Widget>.generate(lh, (int index_h){       return Row(         children:         List<Widget>.generate(lw, (int index_w){           poz++;           Color BoxColor=FigColors[99]??Colors.black;           if (MovedFig.contains(poz)==true){             BoxColor=FigColors[fcurrent]??Colors.black;           };           Coords crd=Poz2Coors(poz);           // если в стакане что-то есть - рисуем           if (LandMatrix[crd.y][crd.x]>0){  // если  клетка не пустая             BoxColor=FigColors[100]??Colors.black;           };           if (LandMatrix[crd.y][crd.x]==101){  //             BoxColor=FigColors[101]??Colors.black;           };           if (BoxColor==FigColors[100]){             //определяем это верхний блок или под ним еще что есть?             bool ground_top=false;               Coords crd_tmp=Poz2Coors(poz-lw);               print("Строчка: ${crd.y},${crd.x}");               print("Строчка вниз: ${crd_tmp.y},${crd_tmp.x}");               if (crd_tmp.y>0) {                 if (LandMatrix[crd_tmp.y][crd_tmp.x] >0) {                   print("-- ниже  есть земля!");                   ground_top = true;                 }               }             return                 Container(                   color: BoxColor,                   height: MediaQuery.of(context).size.height/lh,                   width: MediaQuery.of(context).size.width/lw,                   child:                     Image.asset(                         !ground_top?"lib/images/ground.png":"lib/images/ground_full.png",                       height: 10,                       width: 10,                       fit: BoxFit.fill,                     ),                 );           } else {             return               Padding(                   padding: EdgeInsets.all(1),                   child:                   Container(                     color: BoxColor,                     height: MediaQuery.of(context).size.height / lh - 2,                     width: MediaQuery.of(context).size.width / lw - 2,                     //child: Text("$poz")                   )               );           };         }),       );     });   } | 
3) При старте игры назначаем периодический таймер, при выполнении которого фигура движется на линию ниже. При движении выполняется проверка касания дна в стакане.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |   void StartTimer(context){     timer = Timer.periodic(Duration(seconds: 1), (Timer _) {       DestroyFullLines();       print("Сработал таймер..");       MoveFigDown(context);     });   }   // двигаем фигуру вниз   void MoveFigDown(context){       if (GameOver==true) {return;};       bool move=true;       // проверим,при следующем шаге, ни одна ли из точек не достигнет дна колодца при движении вниз?       for (var i = 0; i < MovedFig.length; i++) {         // проверка дна колодца         if ((MovedFig[i] + lw)>lw*lh){           move=false;         };         // проверка что наезд на другую фигуру         Coords crd=Poz2Coors(MovedFig[i] + lw);         if (MovedFig[i] + lw<=lw*lh) {           if (LandMatrix[crd.y][crd.x] > 0) {             move = false;           };         };       };       if (move==true) {         for (var i = 0; i < MovedFig.length; i++) {           MovedFig[i] = MovedFig[i] + lw;         };       };       // если не можем двигаться вниз, то переносим фигуру в "стакан"       if (move==false){         print("-переносим фигуру в стакан");         for (var i = 0; i < MovedFig.length; i++) {           Coords crd=Poz2Coors(MovedFig[i]);           LandMatrix[crd.y][crd.x]=1;         };         DestroyFullLines();         // создаём новую фигуру         FigureSelections();       };       setState(() {         MovedFig=MovedFig;       });     print("Фигура двинулась вниз: ${MovedFig}");       String audioasset = "lib/sounds/click.mp3";       AssetsAudioPlayer.newPlayer().open(           Audio(audioasset)       );   } | 
4) Поворот фигуры, как уже писал выше осуществляется переносом фигуры в матрицу, поворот матрицы и обратный перенос фигуры
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |   // поворачиваем блок   void RotateFig(context){     print("До поворота: ${MovedFig}");     List <int> OldFig=List<int>.from(MovedFig); //запоминаем фигуру и положение до поворота     // найдем минимальные координаты x и y до поворота     Coords crd_before_rotare=GetStartXYFigure(MovedFig);     print("min_x=${crd_before_rotare.x},min_y=${crd_before_rotare.y}");     // передвинем фигуру в центр     int center_x=4;     int center_y=4;     MovedFig=FigureMove(center_x,center_y,MovedFig);     for (var i = 0; i < MovedFig.length; i++) {       Coords crd=Poz2Coors(MovedFig[i]);       int new_x=(crd.x * cos(3.1415926535897932/2) - crd.y * sin(3.1415926535897932/2)).round();       int new_y=(crd.x * sin(3.1415926535897932/2) + crd.y * cos(3.1415926535897932/2)).round();       MovedFig[i]=Coors2Poz(new_x,new_y);       //print("x=${new_x},y=${new_y}");     };     MovedFig=FigureMove(crd_before_rotare.x-2,crd_before_rotare.y,MovedFig);     print("После поворота: ${MovedFig}");     // проверяем, а можно ли было поворачивать?     if (MovedFig[0]==-1){       MovedFig=OldFig;     }     // проверяем, а нет ли наложения элементов на то что уже в стакане?     for (var i = 0; i < MovedFig.length; i++) {       Coords crd=Poz2Coors(MovedFig[i] + lw);       if (MovedFig[i] + lw<=lw*lh) {         if (LandMatrix[crd.y][crd.x] > 0) {           MovedFig=OldFig;         };       };     };   } | 
Исходный код игры можно скачать тут