Stanford CS 193P iOS 7 2014 — Задание 3. Set. (Objective-C)

 Screen Shot 2015-11-02 at 7.31.19 PM

Содержание

Цель задания — улучшить решение, полученное в Задании 2. К вашей игре Matchismo “на совпадение” с игральными картами  вы должны добавить вторую карточную игру, Set. При этом нужно продемонстрировать прием полиморфизма объектно-ориентированного программирования для разделения большей части кода с вашей карточной игрой “на совпадение” с игральными картами Matchismo. Кроме того, вы должны продемонстрировать использование segues и Navigation Controller для показа истории “совпадений” и “несовпадений” карт.

Текст Домашнего задания на английском языке доступен на  iTunes в пункте  “Developing iOS 7 app:Assignment 3″

На русском языке

Задание 3 Set fall 2013.pdf

 Задание выполнялось в 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.
Screen Shot 2015-10-31 at 10.23.58 PM
Используем такой принцип Объектно-Ориентированного Программирования, как полиморфизм. Для этого сделаем CardGameViewController абстрактным классом, который будет реализовывать принципы игры на совпадения независимо от внешнего вида карт и их характеристик.  Вот интерфейс этого класса (.h файл)
Screen Shot 2015-10-31 at 10.33.49 PM
Мы видим, что в 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 выбора карты)
Screen Shot 2015-10-31 at 11.01.18 PM
. . . . . . . . . . . . .

Screen Shot 2015-10-31 at 11.03.13 PM
Там же осуществляется обновление пользовательского интерфейса при загрузки коллекции кнопок, имитирующих карты, с помощью очень важного метода updateUI()
Screen Shot 2015-11-01 at 1.50.00 PM
Этот же метод вызывается и при выборе очередной карты
Screen Shot 2015-11-01 at 1.57.04 PM
Метод updateUI () выполнен так, чтобы с использованием абстрактных методов уйти от конкретного задания характеристик карт и критерия их совпадения.
Screen Shot 2015-11-01 at 2.39.51 PM
Давайте реализуем конкретный класс для игры Matchismo c игральными картами. Собственно это частично было сделано в Задании 2, но …
Создадим subclass PlayingCardGameViewController класса CardGameViewController
Screen Shot 2015-11-01 at 2.43.08 PM
При реализации этого класса мы должны реализовать абстрактные методы для игры на совпадение с колодой игральных карт. Количество карт, анализируемых на совпадение, равно 2-м ( в Задании сказано ограничиться 2-мя картами для игры Matщchismo):
Screen Shot 2015-11-01 at 2.49.12 PM
Внешний вид карт определяется обратной и лицевой стороной карты и используется в методах обновления UI:
Screen Shot 2015-11-01 at 3.08.24 PM
Теперь наш конкретный класс для игральных карт и игры Matchismo на совпадение с двумя картами готов, и мы можем его подключать к экранному фрагменту Playing Card на storyboard:
Screen Shot 2015-11-01 at 3.12.11 PM
Давайте реализуем конкретный класс для игры Set.
Для этой игры мы создадим subclass SetCardGameViewController класса CardGameViewController:
Screen Shot 2015-11-01 at 3.17.53 PM
Этот класс мы в дальнейшем подсоединим к экранному фрагменту 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 различных цвета.

Screen Shot 2015-11-01 at 3.44.28 PM
Давайте начнем с модели Set карты. Вместо масти и ранга у игральной карты, у Set карты будет цвет, тип символа, штриховка и количество символов. Для этих характеристик мы создадим public свойства и их setter:
SetCard.h
Screen Shot 2015-11-01 at 4.10.50 PM
SetCard.m
Screen Shot 2015-11-01 at 4.21.12 PM
Также как для игральных карт, нам необходимы массивы с допустимыми значениями:
SetCard.h
Screen Shot 2015-11-01 at 4.44.18 PM
SetCard.m
Screen Shot 2015-11-01 at 4.47.56 PM
Set карту слишком сложно представить с помощью ее свойства  
@property NSString *contents, поэтому содержимое contents Set карты лучше сделать nil.
Свойство description связано с выводом карты на консоль (эту информацию мы сможем использовать позже при отладке):

Screen Shot 2015-11-02 at 9.35.13 AM
В этом Домашнем Задании вместо рисования Set карт в классической форме (мы сделаем это на следующей неделе), будем использовать три символа ▲ ● ◼ и аттрибуты NSAttributedString, чтобы нарисовать их подходящим образом (то есть соответствующего цвета и текстуры). Заметим, что единственное место в Модели карт игры Set, где используется палитра символов ▲ ● ◼ — это свойство description, которое участвует только в отладке и вывода содержимого Set карты на консоль только в очень специфическом виде.
Метод match определения, составляют ли 3 карты «сэт» (Set), согласно вышеизложенному алгоритму, будет выглядеть таким образом:
Screen Shot 2015-11-01 at 6.21.43 PM
Алгоритм для распознавания Set, используемый в методе match, оказался самым простым и компактным. Смысл его состоит в том, что для каждой из 4-х характеристик: символ symbol, цвет color, число символов number, текстура shading, складываются индексы всех 3-х карт и рассчитывается остаток от деления на 3. Если остаток равен 0, например, для характеристики цвет color, то три карты имеют либо одинаковый цвет, либо все цвета разные.  А это и составляет Set.

Наконец создаем новый класс SetCardDeck для моделирования колоды карт для игры Set:
Screen Shot 2015-11-01 at 6.26.43 PM
Screen Shot 2015-11-01 at 6.28.33 PM
Теперь нами созданы Set карта и колода этих карт, логика игры Set полностью соответствует логике той игры на совпадение, которую мы заложили в абстрактный класс CardGameViewController. Мы уже создали subclass SetCardGameViewController класса CardGameViewController и нам осталось только реализовать в нем абстрактные методы для нашей игры Set, связанные с параметрами игры

Screen Shot 2015-11-01 at 6.40.50 PM

и с визуальным представлением Set карты по ее характеристикам.

Screen Shot 2015-11-01 at 6.51.24 PM

У Set карты нет лицевой и обратной стороны: карты Set ложатся на стол лицевой стороной, а если мы выбрали карту, то она не переворачивается, просто ее лицевая сторона будет подсвечивать специальным фоном
Screen Shot 2015-11-01 at 6.59.04 PM
Еще нам необходимо предоставить текст для вывода результатов проверки на совпадение или несовпадения массива из 3-х карт
Screen Shot 2015-11-01 at 7.03.14 PM
и текст о статусе единственной карты
Screen Shot 2015-11-01 at 7.12.52 PM
Теперь класс SetCardGameViewController готов для использования в экранном фрагменте Set Card на storyboard, но предварительно мы поменяем форму карт, развернем их лицевой стороной и заменим фонт текста на лицевой стороне на Menlo. Кроме того, сделаем фон игрового стола белым. Остальные элементы пользовательского интерфейса оставим неизменными :

Screen Shot 2015-11-01 at 7.19.12 PM

Устанавливаем вновь созданный класс SetCardGameViewController в экранном фрагменте Set Card на storyboard:

Screen Shot 2015-11-01 at 7.25.13 PM

Мы видим, что у экранного фрагмента Set Card на два ряда больше карт. Как этого добиться, будет обсуждать в следующих пунктах выполнения этого Задания. Давайте посмотрим на работу нашей новой игры Set. Если выбран Set, то карты удаляются из дальнейшей игры, а вам начисляются очки.

Screen Shot 2015-11-01 at 7.29.31 PM

Пункт 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:
Screen Shot 2015-11-01 at 10.01.48 PM
В результате всех подсоединений проверяем кнопки, подсоединенные к коллекции outlets cardButtons. Для этого достаточно направить курсор на маленький кружочек, расположенный слева от  коллекции outlets cardButtons.
Screen Shot 2015-11-01 at 10.07.36 PM
Мы видим, что подсоединены все кнопки: как кнопки, расположенные на экраном фрагменте Playing Card, так и кнопки на экраном фрагменте Set Card из-за того, что их классы наследуют от абстрактного класса CardGameViewController.
Теперь проверим, что не нарушилась работоспособность нашей первой игры с игральными картами:
Screen Shot 2015-11-02 at 10.24.26 AM
Обе игры прекрасно работают вместе: кнопки «пересдача» работают прекрасно, результат совпадения (или несовпадения) отображается в информационной строке и показывается счет.

Пункты 7, 8

7. Обе игры должны где-то показывать счет и позволять “пересдавать” колоду карт.

8. Ваша игра Set должна сообщать о “совпадениях” (“несовпадениях”) точно также, как это требовало Обязательное задание 5 в прошлой домашней работе, но вы должны усовершенствовать эту возможность (использовать NSAttributedString) для того, чтобы сделать это работоспособным как для Set игры, так и для Playing Card игры.

Пункт 7 выполнен.

Для выполнения пункта 8 модифицируем метод updateFlipResult в абстрактном классе  CardGameViewController таким образом, чтобы мы смогли подставлять описания карт в зависимости от типа карт, с которыми мы играем. 
CardGameViewController.m
Screen Shot 2015-11-02 at 10.37.56 AM
Для этого применяется абстрактный метод attributedCardsDescription,
CardGameViewController.m
Screen Shot 2015-11-02 at 11.03.30 AM
который реализуется по-разному в конкретных классах
PlayingCardGameViewController.m
Screen Shot 2015-11-02 at 11.05.16 AM
SetCardGameViewController.m
Screen Shot 2015-11-02 at 11.08.04 AM

Пункт 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«. 

Screen Shot 2015-11-02 at 1.25.36 PM

Добавляем новый View Controller и из Палитры Объектов добавляем на него Text View

Screen Shot 2015-11-02 at 1.43.25 PM

делаем CTRL-перетягивание от кнопок «History» к новому View Controller и устанавливаем segue типа push. Задаем идентификатор segue “Show history”.
Screen Shot 2015-11-02 at 1.39.29 PM
То же самое нужно сделать с экранным фрагментом Set Cards.
Screen Shot 2015-11-02 at 2.02.26 PM
В результате получаем на storyboard такую конфигурацию. Для нового View Controller установим заголовок History.
Screen Shot 2015-11-02 at 11.35.37 AM
Создадим новый класс HistoryViewController для нового View Controller. Моделью для этого MVC будет массив строк с атрибутами NSArray *flipsHistory, который необходимо отобразить в Text View:
Screen Shot 2015-11-02 at 2.39.38 PM
Cоздаем outlet для Text View:
Screen Shot 2015-11-02 at 2.44.35 PM
Пишем setter для нашей Модели в случае, если он находится на экране или когда он собирается появиться на экране. В обоих случаях необходимо обновить пользовательский интерфейс с помощью метода updateUI():
Screen Shot 2015-11-02 at 2.51.35 PM
Обновление пользовательского интерфейса связано размещением строк из массива нашей Модели:
Screen Shot 2015-11-02 at 3.09.04 PM
Массив строк с атрибутами для History View Controller будет формироваться в абстрактном родительском классе CardGameViewController.m
Screen Shot 2015-11-02 at 3.13.13 PM
при получении строки с результатом совпадения или несовпадения:
Screen Shot 2015-11-02 at 3.20.42 PM

Подготовка «переезда» на History View Controller будет осуществляться в методе prepareForSegue также в абстрактном родительском классе CardGameViewController.m

Screen Shot 2015-11-02 at 3.27.33 PM
В результате получим такую историю для карточных игр Playing Cards и Set Cards.

Screen Shot 2015-11-02 at 3.31.57 PM

Один комментарий к “Stanford CS 193P iOS 7 2014 — Задание 3. Set. (Objective-C)

  1. Отличнейший сайт, отличные лекции. Татьяна, спасибо большое!!!

Обсуждение закрыто.