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

в 20:15, , рубрики: ip camera, ipad приложение, iphone, метки: , ,

Тетрис представляет собой игру, в которой геометрические фигуры, называемые тетромино (фигура, состоящая из 4-ёх кубиков), падают с верхнего конца поля. Как только тетромино касается основания, оно больше не может двигаться и становится частью основания. Следующее тетромино падает с верхнего конца поля, обычно представляющего собой прямоугольник 10 на 20. Игрок может двигать падающие тетромино горизонтально и поворачивать их на 90 градусов. Цель игры — складывать горизонтальные линии, которые удаляются и приносят очки. Игрок проигрывает, если сложенные тетромино достигают верхнего края поля.

Гэйм-дизайн

Тетрис –всем знакомая игра, ее движок состоит из одних лишь двумерных массивов, физика здесь не нужна. Поэтому мы познакомимся со средствами прорисовки CoreGraphics и создадим игру с нуля. Программисту не нужно будет ничего рисовать – все будет сделано при помощи кода.
Хотя код не будет содержать сложных функций, не будет требовать особых навыков программирования, и все же тетрис представляет собой одну из самых сложных для создания паззл-игр. Пройдя данный урок, вы сможете в дальнейшем больше фокусироваться на логике алгоритма, нежели на средствах исполнения.

Начало

В начале создаем новый проект в Xcode и назовем его: Тетрис.
Создайте новый класс типа UIView и назовите его TetrisBack. Это будет наше игровое поле.

Добавьте в хэдер файл нашего класса переменную – массив: genArray. Выглядеть все должно примерно так:

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

Перейдем к файлу 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 на экран. Запустим нашу программу:

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

Поле у нас готово пора добавлять тетрамино.

Логика игры

В тетрисе 7 разных видов тетромино:

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

Каждое из наших тетромино будет массивом. Но также будет трех мерный массив, который будет содержать в себе все четыре ротации тетромино. Добавим несколько переменных в хэдер 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 и правая часть матрицы тетромино заполнена нулями, то есть ничего не касается сама фигура тетромино. Пример:

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

Немного мудренее с этой частью:

 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];

Запускаем программу:

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

Ура тетромино добавился. Но он черный, нужно добавить цвета. Переходим к файлу 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));
            }
        }
    }
}

Запустим программу теперь:

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

Тетромино цветное, но оно не двигается. Перейдем к логике движения. Через каждый заданный промежуток времени тетромино двигается вниз, если ему ничего не мешает. В нашем случае прежде чем сдвинуть тетромино на шаг вниз, нужно убрать его из матрицы поля и добавить обратно но уже в новом положении.
Перейдем к 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];

Ну вот мы практически добрались до финала. Запусти нашу программу:

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

Тетромино появляются и падают, но мы еще не прописали управление. Добавим в 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 и привяжем к ним наши функции:

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

Запустим программу:

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

Ну вот мы и закончили написание тетриса. Конечно можно добавить очки, управление жестами но это уже детали.

Урок от пользователя SubmarineApps, все заслуги ему.

Автор: SeriiZ

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js