Содержание
Цель задания — улучшить решение, полученное в Задании 2. К вашей игре Matchismo “на совпадение” с игральными картами вы должны добавить вторую карточную игру, Set. При этом нужно продемонстрировать прием полиморфизма объектно-ориентированного программирования для разделения большей части кода с вашей карточной игрой “на совпадение” с игральными картами Matchismo. Кроме того, вы должны продемонстрировать использование segues и Navigation Controller для показа истории “совпадений” и “несовпадений” карт.
Текст Домашнего задания на английском языке доступен на iTunes в пункте “Developing iOS 7 app:Assignment 3″.
На русском языке
Задание выполнялось в Xcode 7 iOS 9. Режим «Size classes» в этом Домашнем задании отключен. Кроме того Autolayout также не использовалась, так как набор карт на игральном столе сложно адаптировать к различным размерам экрана с помощью Autolayout. Только использование специальной «сетки» позволит это сделать, что и будет выполнено в Задании 4.
Код Задания 3 находится на Github.
Пункт 1, 4
1. Добавьте новый MVC к вашему решению Matchismo, полученному на прошлой неделе. Новый MVC должен обеспечить простейший вариант карточной игры “на совпадение” Set. Хорошим решением для этого домашнего задания было бы использование приемов объектно-ориентированного программирования для разделения большей части кода с вашей карточной игрой “на совпадение” с игральными картами Playing Card.
4. Используйте UITabBarController для представления двух игр в вашем UI на различных закладках.
Я сразу покажу к чему мы должны стремиться в первом пункте нашего задания. Добавляем на storyboard из палитры объектов Tab Bar Controller и убираем сопровождающие его View Controllers. Затем дублируем наш Play сards View Controller целиком и получаем после переименования Set Card View Controller, который будет обслуживать нашу будущую игру Set.
Используем такой принцип Объектно-Ориентированного Программирования, как полиморфизм. Для этого сделаем CardGameViewController абстрактным классом, который будет реализовывать принципы игры на совпадения независимо от внешнего вида карт и их характеристик. Вот интерфейс этого класса (.h файл)
Мы видим, что в API этого абстрактного класса имеются методы отложенной инициализации:
— колоды карт createDeck;
— числа карт, анализируемых на совпадение numberOfMatches;
— названия игры gameName;
Кроме того, у нас есть абстрактный метод
— (void)updateCardButton:(UIButton *)cardButton usingCard:(Card *)card;, который определяет внешний вид карты на UI в зависимости от ее характеристик (например, масти и ранга).
У нас еще есть методы
— (NSAttributedString *)textForSingleCard:(Card *)card;
— (NSAttributedString *)attributedCardsDescription:(NSArray *)cards;
, которые возвращают вам описание одной карты и группы карт, объединенных специальным разделителем, ввиде строки с атрибутами NSAttributedString. Это необходимо для размещения результатов анализа совпадения или несовпадения карт на UI.
В реализации абстрактного класса CardGameViewController (.m файл) осуществляется связь со storyboard — там находятся все outlets (набор карт на игральном столе cardButtons) и Actions (кнопка Deal «пересдачи» карт, Action touchCardButton выбора карты)
. . . . . . . . . . . . .
Там же осуществляется обновление пользовательского интерфейса при загрузки коллекции кнопок, имитирующих карты, с помощью очень важного метода updateUI()
Этот же метод вызывается и при выборе очередной карты
Метод updateUI () выполнен так, чтобы с использованием абстрактных методов уйти от конкретного задания характеристик карт и критерия их совпадения.
Давайте реализуем конкретный класс для игры Matchismo c игральными картами. Собственно это частично было сделано в Задании 2, но …
Создадим subclass PlayingCardGameViewController класса CardGameViewController
При реализации этого класса мы должны реализовать абстрактные методы для игры на совпадение с колодой игральных карт. Количество карт, анализируемых на совпадение, равно 2-м ( в Задании сказано ограничиться 2-мя картами для игры Matщchismo):
Внешний вид карт определяется обратной и лицевой стороной карты и используется в методах обновления UI:
Теперь наш конкретный класс для игральных карт и игры Matchismo на совпадение с двумя картами готов, и мы можем его подключать к экранному фрагменту Playing Card на storyboard:
Давайте реализуем конкретный класс для игры Set.
Для этой игры мы создадим subclass SetCardGameViewController класса CardGameViewController:
Этот класс мы в дальнейшем подсоединим к экранному фрагменту Set Card, который будет выглядеть иначе. Но сначала мы должны понять, какими картами играет Set.
Пункты 2, 6
2. Карточная игра Set состоит в том, чтобы позволить пользователю набирать множества (sets) и получать за это очки (то есть эта игра не “пересдает” карты заново при обнаружении sets). Другими словами, она работает точно также, как игра с игральными картами “на совпадение”. Единственное различие заключается в том, что это 3-х карточная игра “на совпадение” и она использует другие карты (“сдает” вам Set карты из полной колоды Set карт).
6. Вместо рисования Set карт в классической форме ( мы сделаем это на следующей неделе) будем использовать три символа ▲ ● ◼ и аттрибуты NSAttributedString, чтобы нарисовать их подходящим образом (то есть соответствующего цвета и текстуры).
Колода карт для игры Set состоит из 81 карты, каждая из которых имеет 4 свойства:
1) цвет (красный, зелёный или фиолетовый).
2) символ (ромб, овал или волна)
3) количество (1, 2 или 3),
4) текстура (монотонная, штрихованная или без текстуры)
Каждая карта встречается в колоде только один раз. Цель игры — набрать как можно больше сетов (Sets).
«Сет» (Set) состоит из трех карт, у которых, каждый из признаков или одинаков для всех карт или различается для всех карт, то есть удовлетворяют все условия:
- все карты имеют то же количество символов или же 3 различных значения;
- все карты имеют тот же символ или же 3 различных символа;
- все карты имеют ту же текстуру или же 3 различных варианта текстуры;
- все карты имеют тот же цвет или же 3 различных цвета.
Давайте начнем с модели Set карты. Вместо масти и ранга у игральной карты, у Set карты будет цвет, тип символа, штриховка и количество символов. Для этих характеристик мы создадим public свойства и их setter:
SetCard.h
SetCard.m
Также как для игральных карт, нам необходимы массивы с допустимыми значениями:
SetCard.h
SetCard.m
Set карту слишком сложно представить с помощью ее свойства
@property NSString *contents, поэтому содержимое contents Set карты лучше сделать nil.
Свойство description связано с выводом карты на консоль (эту информацию мы сможем использовать позже при отладке):
В этом Домашнем Задании вместо рисования Set карт в классической форме (мы сделаем это на следующей неделе), будем использовать три символа ▲ ● ◼ и аттрибуты NSAttributedString, чтобы нарисовать их подходящим образом (то есть соответствующего цвета и текстуры). Заметим, что единственное место в Модели карт игры Set, где используется палитра символов ▲ ● ◼ — это свойство description, которое участвует только в отладке и вывода содержимого Set карты на консоль только в очень специфическом виде.
Метод match определения, составляют ли 3 карты «сэт» (Set), согласно вышеизложенному алгоритму, будет выглядеть таким образом:
Алгоритм для распознавания Set, используемый в методе match, оказался самым простым и компактным. Смысл его состоит в том, что для каждой из 4-х характеристик: символ symbol, цвет color, число символов number, текстура shading, складываются индексы всех 3-х карт и рассчитывается остаток от деления на 3. Если остаток равен 0, например, для характеристики цвет color, то три карты имеют либо одинаковый цвет, либо все цвета разные. А это и составляет Set.
Наконец создаем новый класс SetCardDeck для моделирования колоды карт для игры Set:
Теперь нами созданы Set карта и колода этих карт, логика игры Set полностью соответствует логике той игры на совпадение, которую мы заложили в абстрактный класс CardGameViewController. Мы уже создали subclass SetCardGameViewController класса CardGameViewController и нам осталось только реализовать в нем абстрактные методы для нашей игры Set, связанные с параметрами игры
и с визуальным представлением Set карты по ее характеристикам.
У Set карты нет лицевой и обратной стороны: карты Set ложатся на стол лицевой стороной, а если мы выбрали карту, то она не переворачивается, просто ее лицевая сторона будет подсвечивать специальным фоном
Еще нам необходимо предоставить текст для вывода результатов проверки на совпадение или несовпадения массива из 3-х карт
и текст о статусе единственной карты
Теперь класс SetCardGameViewController готов для использования в экранном фрагменте Set Card на storyboard, но предварительно мы поменяем форму карт, развернем их лицевой стороной и заменим фонт текста на лицевой стороне на Menlo. Кроме того, сделаем фон игрового стола белым. Остальные элементы пользовательского интерфейса оставим неизменными :
Устанавливаем вновь созданный класс SetCardGameViewController в экранном фрагменте Set Card на storyboard:
Мы видим, что у экранного фрагмента Set Card на два ряда больше карт. Как этого добиться, будет обсуждать в следующих пунктах выполнения этого Задания. Давайте посмотрим на работу нашей новой игры Set. Если выбран Set, то карты удаляются из дальнейшей игры, а вам начисляются очки.
Пункт 3, 5
3. Ваша Playing Card игра на совпадение должна продолжать работать также, как и требовалось в предыдущем домашнем задании, за исключением того, что она должна работать только как 2-х карточная игра (вы можете убрать переключатель UISwitch и элемент сегментного управления UISegmentedControl с UI, но оставьте инфраструктуру, поддерживающую 3-х карточную игру “на совпадение”, так как Set — 3-х карточная игра).
5. Просто для того, чтобы показать, что вы знаете как это делать, ваша Set игра должна иметь другое число карт на столе, чем ваша игра Playing Card с игральными картами (любая игра может иметь сколько угодно карт на столе и соотношение сторон карт следует подобрать наиболее благоприятным для вашего UI).
Давайте вначале займемся пунктом 5, и попробуем добавить в игру Set еще два ряда карт (кнопок). Но вначале нам необходимо уяснить одну вещь. Если вы копируете целиком MVC в ваш storyboard (не покомпонентно кусок за куском, а однократно как целую вещь), то все outlets и actions сохранятся (это может быть очень удобно). Даже если вы затем измените класс Controller в скопированном MVC на новый класс, в которым реализованы эти outlets и actions (например, в силу наследования), то outlets и actions будут сохраняться.
Именно копированием экранного фрагмента Playing Card мы получили MVC для новой Set игры, и если вам не нужно добавлять карт в новую игру, то все карты на новом скопированном экранном фрагменте Set Card оказываются уже присоединенными к коллекции outlets. Но нам нужно добавить два ряда карт (кнопок) в нашей игре Set. Вначале мы просто добавляем кнопки дублированием или еще каким-то способом, но они остаются не подсоединенными к коллекции outlets
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
Как нам их подсоединить? Мы делаем это обычным способом с помощью Ассистента Редактора, где слева у нас абстрактный родительский класс CardGameViewController, а справа — экранный фрагмент, у которого установлен конкретный subclass SetCardGameViewController. Делаем CTRL-перетаскивание от кнопки к коллекции outlets cardButtons:
В результате всех подсоединений проверяем кнопки, подсоединенные к коллекции outlets cardButtons. Для этого достаточно направить курсор на маленький кружочек, расположенный слева от коллекции outlets cardButtons.
Мы видим, что подсоединены все кнопки: как кнопки, расположенные на экраном фрагменте Playing Card, так и кнопки на экраном фрагменте Set Card из-за того, что их классы наследуют от абстрактного класса CardGameViewController.
Теперь проверим, что не нарушилась работоспособность нашей первой игры с игральными картами:
Обе игры прекрасно работают вместе: кнопки «пересдача» работают прекрасно, результат совпадения (или несовпадения) отображается в информационной строке и показывается счет.
Пункты 7, 8
7. Обе игры должны где-то показывать счет и позволять “пересдавать” колоду карт.
8. Ваша игра Set должна сообщать о “совпадениях” (“несовпадениях”) точно также, как это требовало Обязательное задание 5 в прошлой домашней работе, но вы должны усовершенствовать эту возможность (использовать NSAttributedString) для того, чтобы сделать это работоспособным как для Set игры, так и для Playing Card игры.
Пункт 7 выполнен.
Для выполнения пункта 8 модифицируем метод updateFlipResult в абстрактном классе CardGameViewController таким образом, чтобы мы смогли подставлять описания карт в зависимости от типа карт, с которыми мы играем.
CardGameViewController.m
Для этого применяется абстрактный метод attributedCardsDescription,
CardGameViewController.m
который реализуется по-разному в конкретных классах
PlayingCardGameViewController.m
SetCardGameViewController.m
Пункт 9
На прошлой неделе было дополнительное задание использовать UISlider для показа истории играемой в настоящее время игры. На этой неделе мы сделаем показ истории Обязательным заданием, но вместо использования UISlider вы должны придумать другое, новое MVC, которое будет отображать историю в UITextView и на которую вы сможете “выезжать” (segue) внутри UINavigationController. Вам нужно показать карты, вовлеченные в каждое “совпадение” или “несовпадение”, а также количество очков, которые вы получили или потеряли в результате (вы уже показывали эту информацию в UILabel для каждой выбранной карты, так что это будет выполнить достаточно просто). Добавьте UIBarButtonItem кнопку “History” на навигационную панель справа, которая будет выполнять “переезд” на новый MVC. Эта возможность должна работать как для Set игры, так и для Playing Card игры.
На storyboard вставляем оба игровых View Controllers (Playing Cards и Set Cards) в Navigation Controller и устанавливаем заголовки. Затем для каждого из игровых View Controllers вытягиваем из Панели Объектов кнопку Bar Button в качестве правой кнопки на панели Navigation Controller и называем ее «History«.
Добавляем новый View Controller и из Палитры Объектов добавляем на него Text View
делаем CTRL-перетягивание от кнопок «History» к новому View Controller и устанавливаем segue типа push. Задаем идентификатор segue “Show history”.
То же самое нужно сделать с экранным фрагментом Set Cards.
В результате получаем на storyboard такую конфигурацию. Для нового View Controller установим заголовок History.
Создадим новый класс HistoryViewController для нового View Controller. Моделью для этого MVC будет массив строк с атрибутами NSArray *flipsHistory, который необходимо отобразить в Text View:
Cоздаем outlet для Text View:
Пишем setter для нашей Модели в случае, если он находится на экране или когда он собирается появиться на экране. В обоих случаях необходимо обновить пользовательский интерфейс с помощью метода updateUI():
Обновление пользовательского интерфейса связано размещением строк из массива нашей Модели:
Массив строк с атрибутами для History View Controller будет формироваться в абстрактном родительском классе CardGameViewController.m
при получении строки с результатом совпадения или несовпадения:
Подготовка «переезда» на History View Controller будет осуществляться в методе prepareForSegue также в абстрактном родительском классе CardGameViewController.m
В результате получим такую историю для карточных игр Playing Cards и Set Cards.
Отличнейший сайт, отличные лекции. Татьяна, спасибо большое!!!