Гэйм-дизайн
Тетрис –всем знакомая игра, ее движок состоит из одних лишь двумерных массивов, физика здесь не нужна. Поэтому мы познакомимся со средствами прорисовки 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, все заслуги ему.
ссылка на оригинал статьи http://habrahabr.ru/post/159707/
Добавить комментарий