Дело было вечером, делать было нечего (с)..В качестве разминки решил в кои-то веки написать игру. Пришла на ум идея написать давнюю мечту — тетрис. В т.ч. использовать будем более ранние наработки по работе с Hive и Requets оформленные в виде отдельных классов.
Из интересного:
1) Для отображения стакана и фигур используем три массива:
LandMatrix — массив вида ширина/высота — фактически «стакан» с «приземленными» фигурами. Точка пересечения массива — цвет квадратика. Если пусто — значит в точке стакана ничего нет
MoveFig — массив движущейся по стакану фигуры. В момент совпадения низа фигуры с любой не пустой точкой стакана фигура переносится в «стакан»
ShadowRotareFig — вспомогательный массив, используемый для поворота фигуры. В момент когда фигуру необходимо повернуть, она переносится из MoveFig в этот массив, поворачивается, и переносится обратно в MoveFig. Создание отдельного массива для поворота было обусловлено способом хранения фигур — хранится не матрица, а просто массив текущих координат квадратиков из которых составлена фигура.
2) Отрисовка стакана происходит после того, как фигура перенесена в массив стакана. Причины переноса — касание дна стакана.
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) При старте игры назначаем периодический таймер, при выполнении которого фигура движется на линию ниже. При движении выполняется проверка касания дна в стакане.
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) Поворот фигуры, как уже писал выше осуществляется переносом фигуры в матрицу, поворот матрицы и обратный перенос фигуры
// поворачиваем блок
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;
};
};
};
}
Исходный код игры можно скачать тут