Создание игры Тетрис средствами CoreGraphics

от автора

Тетрис представляет собой игру, в которой геометрические фигуры, называемые тетромино (фигура, состоящая из 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, все заслуги ему.

ссылка на оригинал статьи http://habrahabr.ru/post/159707/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *