Тетрис представляет собой игру, в которой геометрические фигуры, называемые тетромино (фигура, состоящая из 4-ёх кубиков), падают с верхнего конца поля. Как только тетромино касается основания, оно больше не может двигаться и становится частью основания. Следующее тетромино падает с верхнего конца поля, обычно представляющего собой прямоугольник 10 на 20. Игрок может двигать падающие тетромино горизонтально и поворачивать их на 90 градусов. Цель игры — складывать горизонтальные линии, которые удаляются и приносят очки. Игрок проигрывает, если сложенные тетромино достигают верхнего края поля.
Гэйм-дизайн
Тетрис –всем знакомая игра, ее движок состоит из одних лишь двумерных массивов, физика здесь не нужна. Поэтому мы познакомимся со средствами прорисовки CoreGraphics и создадим игру с нуля. Программисту не нужно будет ничего рисовать – все будет сделано при помощи кода.
Хотя код не будет содержать сложных функций, не будет требовать особых навыков программирования, и все же тетрис представляет собой одну из самых сложных для создания паззл-игр. Пройдя данный урок, вы сможете в дальнейшем больше фокусироваться на логике алгоритма, нежели на средствах исполнения.
Начало
В начале создаем новый проект в Xcode и назовем его: Тетрис.
Создайте новый класс типа UIView и назовите его TetrisBack. Это будет наше игровое поле.
Добавьте в хэдер файл нашего класса переменную – массив: genArray. Выглядеть все должно примерно так:
Перейдем к файлу TetrisBach.m. Метод drawrect предназначен для рисования. Изменим его содержимое:
- (void)drawRect:(CGRect)rect
{
// Код рисования
CGContextRef context = UIGraphicsGetCurrentContext(); //получение контекста
CGContextClearRect(context, rect); // Очистим контекст
for (int i = 0; i < 20; i++) { //Цикл по прохождению массива
for (int j = 0; j < 10; j++) {
if (genArray[i][j] == 0) {
if ((i+j)%2 == 0) {
CGContextSetRGBFillColor(context, 0.321, 0.321, 0.321, 1); //выбор цвета для рисования
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15)); //рисование прямоугольника
} else {
CGContextSetRGBFillColor(context, 0.266, 0.266, 0.266, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
}
}
}
}
Здесь мы выполнили проверку массива genArray и если он пуст- рисуется шахматное поле.
Перейдем к главному файлу ViewController.h (у некоторых может быть RootViewController.h) и сделаем импорт нашему классу TetrisBack.h и создадим ему переменную:
#import "TetrisBack.h"
@interface ViewController : UIViewController {
TetrisBack *tetrisBack;
}
@end
Добавим в функцию viewDidLoad фала ViewController.h следующие строчки:
tetrisBack = [[[TetrisBack alloc] initWithFrame:CGRectMake(0, 0, 200, 320)] autorelease];
[self.view addSubview:tetrisBack];
Таким образом, мы выделили переменной память и добавили наш TetrisBack на экран. Запустим нашу программу:
Поле у нас готово пора добавлять тетрамино.
Логика игры
В тетрисе 7 разных видов тетромино:
Каждое из наших тетромино будет массивом. Но также будет трех мерный массив, который будет содержать в себе все четыре ротации тетромино. Добавим несколько переменных в хэдер ViewController.h:
@interface ViewController : UIViewController {
TetrisBack *tetrisBack;
int currentTetronominoe [4][4][4]; //отвечает за двигающееся тетромино - 4 ротации и матрица 4 на 4
int tetroType; //отвечает за тип тетромино (от 0 до 6)
int currentRotation; //отвечает за текущую ротацию тетрамино
int currentRow; //отвечает за запоминание ряда на котором находится верхняя часть тетромино
int currentColumn; //отвечает за запоминание столбца на котором находится левая часть тетромино
}
Все готово для вывода тетромино на экран.
Создадим функцию addTetrominoes и добавим ее в файл ViewController.m:
-(void)addTetrominoes {
int i = arc4random()%7; //случайный выбор следующего тетромино
switch (i) {
case 0:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 0; currentTetronominoe[0][0][2] = 0; currentTetronominoe[0][0][3] = 0;
currentTetronominoe[0][1][0] = 0; currentTetronominoe[0][1][1] = 1; currentTetronominoe[0][1][2] = 1; currentTetronominoe[0][1][3] = 0;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 1; currentTetronominoe[0][2][2] = 1; currentTetronominoe[0][2][3] = 0;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
tetroType = 0;
currentRotation = 0;
break;
case 1:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 0; currentTetronominoe[0][0][2] = 0; currentTetronominoe[0][0][3] = 0;
currentTetronominoe[0][1][0] = 2; currentTetronominoe[0][1][1] = 2; currentTetronominoe[0][1][2] = 2; currentTetronominoe[0][1][3] = 2;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 0; currentTetronominoe[0][2][2] = 0; currentTetronominoe[0][2][3] = 0;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
currentTetronominoe[1][0][0] = 0; currentTetronominoe[1][0][1] = 0; currentTetronominoe[1][0][2] = 2; currentTetronominoe[1][0][3] = 0;
currentTetronominoe[1][1][0] = 0; currentTetronominoe[1][1][1] = 0; currentTetronominoe[1][1][2] = 2; currentTetronominoe[1][1][3] = 0;
currentTetronominoe[1][2][0] = 0; currentTetronominoe[1][2][1] = 0; currentTetronominoe[1][2][2] = 2; currentTetronominoe[1][2][3] = 0;
currentTetronominoe[1][3][0] = 0; currentTetronominoe[1][3][1] = 0; currentTetronominoe[1][3][2] = 2; currentTetronominoe[1][3][3] = 0;
tetroType = 1;
currentRotation = 0;
break;
case 2:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 0; currentTetronominoe[0][0][2] = 0; currentTetronominoe[0][0][3] = 0;
currentTetronominoe[0][1][0] = 0; currentTetronominoe[0][1][1] = 0; currentTetronominoe[0][1][2] = 3; currentTetronominoe[0][1][3] = 3;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 3; currentTetronominoe[0][2][2] = 3; currentTetronominoe[0][2][3] = 0;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
currentTetronominoe[1][0][0] = 0; currentTetronominoe[1][0][1] = 0; currentTetronominoe[1][0][2] = 3; currentTetronominoe[1][0][3] = 0;
currentTetronominoe[1][1][0] = 0; currentTetronominoe[1][1][1] = 0; currentTetronominoe[1][1][2] = 3; currentTetronominoe[1][1][3] = 3;
currentTetronominoe[1][2][0] = 0; currentTetronominoe[1][2][1] = 0; currentTetronominoe[1][2][2] = 0; currentTetronominoe[1][2][3] = 3;
currentTetronominoe[1][3][0] = 0; currentTetronominoe[1][3][1] = 0; currentTetronominoe[1][3][2] = 0; currentTetronominoe[1][3][3] = 0;
tetroType = 2;
currentRotation = 0;
break;
case 3:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 0; currentTetronominoe[0][0][2] = 0; currentTetronominoe[0][0][3] = 0;
currentTetronominoe[0][1][0] = 0; currentTetronominoe[0][1][1] = 4; currentTetronominoe[0][1][2] = 4; currentTetronominoe[0][1][3] = 0;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 0; currentTetronominoe[0][2][2] = 4; currentTetronominoe[0][2][3] = 4;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
currentTetronominoe[1][0][0] = 0; currentTetronominoe[1][0][1] = 0; currentTetronominoe[1][0][2] = 0; currentTetronominoe[1][0][3] = 4;
currentTetronominoe[1][1][0] = 0; currentTetronominoe[1][1][1] = 0; currentTetronominoe[1][1][2] = 4; currentTetronominoe[1][1][3] = 4;
currentTetronominoe[1][2][0] = 0; currentTetronominoe[1][2][1] = 0; currentTetronominoe[1][2][2] = 4; currentTetronominoe[1][2][3] = 0;
currentTetronominoe[1][3][0] = 0; currentTetronominoe[1][3][1] = 0; currentTetronominoe[1][3][2] = 0; currentTetronominoe[1][3][3] = 0;
tetroType = 3;
currentRotation = 0;
break;
case 4:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 0; currentTetronominoe[0][0][2] = 0; currentTetronominoe[0][0][3] = 5;
currentTetronominoe[0][1][0] = 0; currentTetronominoe[0][1][1] = 5; currentTetronominoe[0][1][2] = 5; currentTetronominoe[0][1][3] = 5;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 0; currentTetronominoe[0][2][2] = 0; currentTetronominoe[0][2][3] = 0;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
currentTetronominoe[1][0][0] = 0; currentTetronominoe[1][0][1] = 5; currentTetronominoe[1][0][2] = 5; currentTetronominoe[1][0][3] = 0;
currentTetronominoe[1][1][0] = 0; currentTetronominoe[1][1][1] = 0; currentTetronominoe[1][1][2] = 5; currentTetronominoe[1][1][3] = 0;
currentTetronominoe[1][2][0] = 0; currentTetronominoe[1][2][1] = 0; currentTetronominoe[1][2][2] = 5; currentTetronominoe[1][2][3] = 0;
currentTetronominoe[1][3][0] = 0; currentTetronominoe[1][3][1] = 0; currentTetronominoe[1][3][2] = 0; currentTetronominoe[1][3][3] = 0;
currentTetronominoe[2][0][0] = 0; currentTetronominoe[2][0][1] = 0; currentTetronominoe[2][0][2] = 0; currentTetronominoe[2][0][3] = 0;
currentTetronominoe[2][1][0] = 0; currentTetronominoe[2][1][1] = 5; currentTetronominoe[2][1][2] = 5; currentTetronominoe[2][1][3] = 5;
currentTetronominoe[2][2][0] = 0; currentTetronominoe[2][2][1] = 5; currentTetronominoe[2][2][2] = 0; currentTetronominoe[2][2][3] = 0;
currentTetronominoe[2][3][0] = 0; currentTetronominoe[2][3][1] = 0; currentTetronominoe[2][3][2] = 0; currentTetronominoe[2][3][3] = 0;
currentTetronominoe[3][0][0] = 0; currentTetronominoe[3][0][1] = 0; currentTetronominoe[3][0][2] = 5; currentTetronominoe[3][0][3] = 0;
currentTetronominoe[3][1][0] = 0; currentTetronominoe[3][1][1] = 0; currentTetronominoe[3][1][2] = 5; currentTetronominoe[3][1][3] = 0;
currentTetronominoe[3][2][0] = 0; currentTetronominoe[3][2][1] = 0; currentTetronominoe[3][2][2] = 5; currentTetronominoe[3][2][3] = 5;
currentTetronominoe[3][3][0] = 0; currentTetronominoe[3][3][1] = 0; currentTetronominoe[3][3][2] = 0; currentTetronominoe[3][3][3] = 0;
tetroType = 4;
currentRotation = 0;
break;
case 5:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 6; currentTetronominoe[0][0][2] = 0; currentTetronominoe[0][0][3] = 0;
currentTetronominoe[0][1][0] = 0; currentTetronominoe[0][1][1] = 6; currentTetronominoe[0][1][2] = 6; currentTetronominoe[0][1][3] = 6;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 0; currentTetronominoe[0][2][2] = 0; currentTetronominoe[0][2][3] = 0;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
currentTetronominoe[1][0][0] = 0; currentTetronominoe[1][0][1] = 0; currentTetronominoe[1][0][2] = 6; currentTetronominoe[1][0][3] = 0;
currentTetronominoe[1][1][0] = 0; currentTetronominoe[1][1][1] = 0; currentTetronominoe[1][1][2] = 6; currentTetronominoe[1][1][3] = 0;
currentTetronominoe[1][2][0] = 0; currentTetronominoe[1][2][1] = 6; currentTetronominoe[1][2][2] = 6; currentTetronominoe[1][2][3] = 0;
currentTetronominoe[1][3][0] = 0; currentTetronominoe[1][3][1] = 0; currentTetronominoe[1][3][2] = 0; currentTetronominoe[1][3][3] = 0;
currentTetronominoe[2][0][0] = 0; currentTetronominoe[2][0][1] = 0; currentTetronominoe[2][0][2] = 0; currentTetronominoe[2][0][3] = 0;
currentTetronominoe[2][1][0] = 0; currentTetronominoe[2][1][1] = 6; currentTetronominoe[2][1][2] = 6; currentTetronominoe[2][1][3] = 6;
currentTetronominoe[2][2][0] = 0; currentTetronominoe[2][2][1] = 0; currentTetronominoe[2][2][2] = 0; currentTetronominoe[2][2][3] = 6;
currentTetronominoe[2][3][0] = 0; currentTetronominoe[2][3][1] = 0; currentTetronominoe[2][3][2] = 0; currentTetronominoe[2][3][3] = 0;
currentTetronominoe[3][0][0] = 0; currentTetronominoe[3][0][1] = 0; currentTetronominoe[3][0][2] = 6; currentTetronominoe[3][0][3] = 6;
currentTetronominoe[3][1][0] = 0; currentTetronominoe[3][1][1] = 0; currentTetronominoe[3][1][2] = 6; currentTetronominoe[3][1][3] = 0;
currentTetronominoe[3][2][0] = 0; currentTetronominoe[3][2][1] = 0; currentTetronominoe[3][2][2] = 6; currentTetronominoe[3][2][3] = 0;
currentTetronominoe[3][3][0] = 0; currentTetronominoe[3][3][1] = 0; currentTetronominoe[3][3][2] = 0; currentTetronominoe[3][3][3] = 0;
tetroType = 5;
currentRotation = 0;
break;
case 6:
currentTetronominoe[0][0][0] = 0; currentTetronominoe[0][0][1] = 0; currentTetronominoe[0][0][2] = 7; currentTetronominoe[0][0][3] = 0;
currentTetronominoe[0][1][0] = 0; currentTetronominoe[0][1][1] = 7; currentTetronominoe[0][1][2] = 7; currentTetronominoe[0][1][3] = 7;
currentTetronominoe[0][2][0] = 0; currentTetronominoe[0][2][1] = 0; currentTetronominoe[0][2][2] = 0; currentTetronominoe[0][2][3] = 0;
currentTetronominoe[0][3][0] = 0; currentTetronominoe[0][3][1] = 0; currentTetronominoe[0][3][2] = 0; currentTetronominoe[0][3][3] = 0;
currentTetronominoe[1][0][0] = 0; currentTetronominoe[1][0][1] = 0; currentTetronominoe[1][0][2] = 7; currentTetronominoe[1][0][3] = 0;
currentTetronominoe[1][1][0] = 0; currentTetronominoe[1][1][1] = 7; currentTetronominoe[1][1][2] = 7; currentTetronominoe[1][1][3] = 0;
currentTetronominoe[1][2][0] = 0; currentTetronominoe[1][2][1] = 0; currentTetronominoe[1][2][2] = 7; currentTetronominoe[1][2][3] = 0;
currentTetronominoe[1][3][0] = 0; currentTetronominoe[1][3][1] = 0; currentTetronominoe[1][3][2] = 0; currentTetronominoe[1][3][3] = 0;
currentTetronominoe[2][0][0] = 0; currentTetronominoe[2][0][1] = 0; currentTetronominoe[2][0][2] = 0; currentTetronominoe[2][0][3] = 0;
currentTetronominoe[2][1][0] = 0; currentTetronominoe[2][1][1] = 7; currentTetronominoe[2][1][2] = 7; currentTetronominoe[2][1][3] = 7;
currentTetronominoe[2][2][0] = 0; currentTetronominoe[2][2][1] = 0; currentTetronominoe[2][2][2] = 7; currentTetronominoe[2][2][3] = 0;
currentTetronominoe[2][3][0] = 0; currentTetronominoe[2][3][1] = 0; currentTetronominoe[2][3][2] = 0; currentTetronominoe[2][3][3] = 0;
currentTetronominoe[3][0][0] = 0; currentTetronominoe[3][0][1] = 0; currentTetronominoe[3][0][2] = 7; currentTetronominoe[3][0][3] = 0;
currentTetronominoe[3][1][0] = 0; currentTetronominoe[3][1][1] = 0; currentTetronominoe[3][1][2] = 7; currentTetronominoe[3][1][3] = 7;
currentTetronominoe[3][2][0] = 0; currentTetronominoe[3][2][1] = 0; currentTetronominoe[3][2][2] = 7; currentTetronominoe[3][2][3] = 0;
currentTetronominoe[3][3][0] = 0; currentTetronominoe[3][3][1] = 0; currentTetronominoe[3][3][2] = 0; currentTetronominoe[3][3][3] = 0;
tetroType = 6;
currentRotation = 0;
break;
default:
break;
}
//проверка на возможность добавления тетромино на поле
if (currentTetronominoe[currentRotation][0][0] == 0 && currentTetronominoe[currentRotation][0][1] == 0 && currentTetronominoe[currentRotation][0][2] == 0 && currentTetronominoe[currentRotation][0][3] == 0) {
currentRow = -1;
currentColumn = 3;
if ([self canBePutOnX:currentRow andY:currentColumn withRot:currentRotation]) {
[self putOnX:currentRow andY:currentColumn];
}
} else {
currentRow = 0;
currentColumn = 3;
if ([self canBePutOnX:currentRow andY:currentColumn withRot:currentRotation]) {
[self putOnX:currentRow andY:currentColumn];
}
}
}
В функции мы определили случайным образом тип тетромино, заполнили массив соответственно его типу и добавили проверку на возможность добавления тетромино на поле. Ведь если поле забито — его не всегда можно добавить. Теперь давайте напишем код проверки. Добавьте в файл ViewController.m метод:
-(BOOL)canBePutOnX:(int)row andY:(int)column withRot:(int)rot{
if (column > 6) {
if (currentTetronominoe[rot][0][3] == 0 && currentTetronominoe[rot][1][3] == 0 && currentTetronominoe[rot][2][3] == 0 && currentTetronominoe[rot][3][3] == 0) {
if (column > 7) {
return NO;
}
} else {
return NO;
}
}
if (column < 0) {
if (currentTetronominoe[rot][0][0] == 0 && currentTetronominoe[rot][1][0] == 0 && currentTetronominoe[rot][2][0] == 0 && currentTetronominoe[rot][3][0] == 0) {
if (currentTetronominoe[rot][0][1] == 0 && currentTetronominoe[rot][1][1] == 0 && currentTetronominoe[rot][2][1] == 0 && currentTetronominoe[rot][3][1] == 0) {
if (column < -2) {
return NO;
}
}else if (column < -1) {
return NO;
}
} else {
return NO;
}
}
if (row > 16) {
if (currentTetronominoe[rot][3][0] == 0 && currentTetronominoe[rot][3][1] == 0 && currentTetronominoe[rot][3][2] == 0 && currentTetronominoe[rot][3][3] == 0) {
if (currentTetronominoe[rot][2][0] == 0 && currentTetronominoe[rot][2][1] == 0 && currentTetronominoe[rot][2][2] == 0 && currentTetronominoe[rot][2][3] == 0) {
if (row > 18) {
return NO;
}
} else if (row > 17) {
return NO;
}
} else {
return NO;
}
}
int yesCount = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (currentTetronominoe[rot][i][j] > 0 && genArray[i+row][j+column] == 0) {
yesCount++;
}
}
}
if (yesCount == 4) {
return YES;
} else {
return NO;
}
}
Код кажется страшным на первый взгляд, но не бойтесь- тут все просто. Объясню на примере первого условия:
if (column > 6) {
if (currentTetronominoe[rot][0][3] == 0 && currentTetronominoe[rot][1][3] == 0 && currentTetronominoe[rot][2][3] == 0 && currentTetronominoe[rot][3][3] == 0) {
if (column > 7) {
return NO;
}
} else {
return NO;
}
}
Если столбец больше 6 (всего у нас 10 столбцов от 0 до 9), то есть 7, 8 или 9 то мы можем добавить тетромино только, если столбец равен 7 и правая часть матрицы тетромино заполнена нулями, то есть ничего не касается сама фигура тетромино. Пример:
Немного мудренее с этой частью:
int yesCount = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (currentTetronominoe[rot][i][j] > 0 && genArray[i+row][j+column] == 0) {
yesCount++;
}
}
}
if (yesCount == 4) {
return YES;
} else {
return NO;
}
Мы выполняем проверку на количество пустых клеточек на нашем поле и клеток больше нуля, то есть заполненных в нашей матрице тетромино. Если это число равно 4 по количеству кубиков в нашем тетромино, то мы можеи его добавить.
Теперь напишем функцию добавления тетромино на наше поле. Все там же в файле ViewController.m:
-(void)putOnX:(int)row andY:(int)column {
//переберем массив поля для добавления массива тетромино
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
genArray[i+row][j+column] += currentTetronominoe[currentRotation][i][j];
}
}
[tetrisBack setNeedsDisplay]; //функция отрисовки UIView
}
Добавим во viewDidLoad:
[self addTetrominoes];
Запускаем программу:
Ура тетромино добавился. Но он черный, нужно добавить цвета. Переходим к файлу TetrisBack.m. Изменим нашу функцию drawRect следующим образом:
[21:27:54] TURKISH: - (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect); // Очистим context
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 10; j++) {
if (genArray[i][j] == 0) {
if ((i+j)%2 == 0) {
CGContextSetRGBFillColor(context, 0.321, 0.321, 0.321, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else {
CGContextSetRGBFillColor(context, 0.266, 0.266, 0.266, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
}
}
else if (genArray[i][j] == 1) {
CGContextSetRGBFillColor(context, 1, 1, 0, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else if (genArray[i][j] == 2) {
CGContextSetRGBFillColor(context, 0, 1, 1, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else if (genArray[i][j] == 3) {
CGContextSetRGBFillColor(context, 0, 1, 0, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else if (genArray[i][j] == 4) {
CGContextSetRGBFillColor(context, 1, 0, 0, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else if (genArray[i][j] == 5) {
CGContextSetRGBFillColor(context, 1, 0.5, 0, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else if (genArray[i][j] == 6) {
CGContextSetRGBFillColor(context, 0, 0, 1, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
} else if (genArray[i][j] == 7) {
CGContextSetRGBFillColor(context, 0.5, 0, 1, 1);
CGContextFillRect(context, CGRectMake(10+j*15+7.5, i*15, 15, 15));
}
}
}
}
Запустим программу теперь:
Тетромино цветное, но оно не двигается. Перейдем к логике движения. Через каждый заданный промежуток времени тетромино двигается вниз, если ему ничего не мешает. В нашем случае прежде чем сдвинуть тетромино на шаг вниз, нужно убрать его из матрицы поля и добавить обратно но уже в новом положении.
Перейдем к ViewController.m и добавим функцию удаления:
-(void)removeFromX:(int)row andY:(int)column {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
genArray[i+row][j+column] -= currentTetronominoe[currentRotation][i][j];
}
}
}
Теперь добавим функцию ticker, которая содержит основную логику игры: поверку на возможность движения тетромино, проверку на заполненные ряды и автоматическое добавление нового тетромино. Код функции для ViewController.m:
-(void)tick:(NSTimer *)timer {
[self removeFromX:currentRow andY:currentColumn]; //удаляем тетромино из матрицы поля
//проверяем можно ли добавить тетромино на ряд ниже
if ([self canBePutOnX:currentRow+1 andY:currentColumn withRot:currentRotation]) {
currentRow += 1;
[self putOnX:currentRow andY:currentColumn];
} else {
//если нельзя
[self putOnX:currentRow andY:currentColumn];
//проверяем на заполненные ряды
for (int i = 0; i < 20; i++) {
int z =0;
for (int j =0; j < 10; j++) {
if (genArray[i][j] > 0) {
z++;
}
if (z == 10) {
//удаляем ряд
for (int g = 0; g < 10; g++) {
genArray[i][g] = 0;
}
//передвигаем часть массива выше удаленного ряда вниз
for (int q = i-1; q > -1; q--) {
for (int w = 0; w < 10; w++) {
genArray[q+1][w]=genArray[q][w];
}
}
}
}
}
//добавляем новое тетромино
[self addTetrominoes];
}
}
Функция готова- осталось задать таймер ее выполнения. Добавим следующий код во viewDidLoad:
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(tick:) userInfo:nil repeats:YES];
Ну вот мы практически добрались до финала. Запусти нашу программу:
Тетромино появляются и падают, но мы еще не прописали управление. Добавим в ViewController.h следующее:
-(IBAction)leftPress:(id)sender;
-(IBAction)rightPress:(id)sender;
-(IBAction)dropPress:(id)sender;
-(IBAction)rotatePress:(id)sender;
Опишем наши функции в ViewController.m:
-(IBAction)leftPress:(id)sender {
[self removeFromX:currentRow andY:currentColumn];
if ([self canBePutOnX:currentRow andY:currentColumn-1 withRot:currentRotation]) {
currentColumn -= 1;
[self putOnX:currentRow andY:currentColumn];
} else {
[self putOnX:currentRow andY:currentColumn];
}
}
-(IBAction)rightPress:(id)sender{
[self removeFromX:currentRow andY:currentColumn];
if ([self canBePutOnX:currentRow andY:currentColumn+1 withRot:currentRotation]) {
currentColumn += 1;
[self putOnX:currentRow andY:currentColumn];
} else {
[self putOnX:currentRow andY:currentColumn];
}
}
-(IBAction)dropPress:(id)sender{
[self removeFromX:currentRow andY:currentColumn];
int dropRow = 0;
for (int i = currentRow; i < 20; i++) {
if ([self canBePutOnX:i andY:currentColumn withRot:currentRotation]) {
dropRow = i;
}
}
//проверка идентичная той что мы делали в функции tick:
if (dropRow != 0) {
currentRow = dropRow;
[self putOnX:currentRow andY:currentColumn];
//проверяем есть ли заполненные ряды
for (int i = 0; i < 20; i++) {
int z =0;
for (int j =0; j < 10; j++) {
if (genArray[i][j] > 0) {
z++;
}
if (z == 10) {
//удаляем ряды
for (int g = 0; g < 10; g++) {
genArray[i][g] = 0;
}
//двигаем массив вниз
for (int q = i-1; q > -1; q--) {
for (int w = 0; w < 10; w++) {
genArray[q+1][w]=genArray[q][w];
}
}
}
}
}
//добавляем новое тетрамино
[self addTetrominoes];
} else {
[self putOnX:currentRow andY:currentColumn];
}
}
-(IBAction)rotatePress:(id)sender {
[self removeFromX:currentRow andY:currentColumn];
if ([self canRotate:(currentRotation + 1)] == 1) {
currentRotation++;
switch (tetroType) {
case 0:
currentRotation = 0;
break;
case 1:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 2:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 3:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 4:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 5:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 6:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
default:
break;
}
[self putOnX:currentRow andY:currentColumn];
} else if ([self canRotate:(currentRotation + 1)] == 2) {
currentRotation++;
switch (tetroType) {
case 0:
currentRotation = 0;
break;
case 1:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 2:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 3:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 4:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 5:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 6:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
default:
break;
}
currentColumn++;
[self putOnX:currentRow andY:currentColumn];
} else if ([self canRotate:(currentRotation + 1)] == 3) {
currentRotation++;
switch (tetroType) {
case 0:
currentRotation = 0;
break;
case 1:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 2:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 3:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 4:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 5:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 6:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
default:
break;
}
currentColumn--;
[self putOnX:currentRow andY:currentColumn];
} else if ([self canRotate:(currentRotation + 1)] == 4) {
currentRotation++;
switch (tetroType) {
case 0:
currentRotation = 0;
break;
case 1:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 2:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 3:
if (currentRotation > 1) {
currentRotation = 0;
}
break;
case 4:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 5:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
case 6:
if (currentRotation > 3) {
currentRotation = 0;
}
break;
default:
break;
}
currentColumn += 2;
[self putOnX:currentRow andY:currentColumn];
} else {
[self putOnX:currentRow andY:currentColumn];
}
}
Добавим кнопки на наш экран во ViewController.xib и привяжем к ним наши функции:
Запустим программу:
Ну вот мы и закончили написание тетриса. Конечно можно добавить очки, управление жестами но это уже детали.
Урок от пользователя SubmarineApps, все заслуги ему.
Автор: SeriiZ