Пишем Тетрис на 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; }; }; }; } |
Исходный код игры можно скачать тут