Задание 2 Stanford CS 193P Fall 2017. Игра Set. Решение обязательных пунктов.

Содержание

Цель задания состоит в том, чтобы дать вам возможность создать свое первое приложение полностью с “нуля” и самостоятельно. Оно достаточно похоже на Задание 1, которое помогло обрести вам уверенность, но достаточно отличающееся, чтобы дать вам полную свободу для накопления опыта!

Текст Домашнего задания на английском языке доступен на  iTunes в пункте “Programming: Project 2: Set″. На русском языке вы можете прочитать текст Задания 2 здесь:, а скачать здесь:

Задание 2 Игра Set iOS 11.pdf

Начинаем выполнять Задание 2 c NewProject в Xcode.

Правила игры Set:

SET INSTRUCTIONS - RUSSIAN.pdf

Для решения Задания 2 необходимо ознакомиться с Лекциями 1 — 6.

Решение данного Задания 2 находится на Github.

ОБСУЖДЕНИЕ МАТЕРИАЛОВ курса «Разработка iOS приложений с Swift» проводится на private форуме на Piazza. Делиться своими решениями и задавать вопросы можно там.
Для регистрации вам необходимо пройти по ссылке:
http://piazza.com/moscow_physical_engineering_institute_bestkora.com/spring2017/mf141
и набрать private  код mf141.

Пункты 1 и 2 обязательные

1. Реализуйте игру Set в версии соло (для одного игрока).

2. Разместите на экране по крайней мере 24 карты игры Set. В Set все карты всегда лежат “лицом” вверх.

 

Начинаем новый проект File-> New -> Project и выбираем шаблон Single View App. Мы не будем использовать файлы LaunchScreen.storyboard и AppDelegate.swift. И я опять размещу их в папке Supporting Files. Я сделала это исключительно из тех соображений, чтобы они не привлекали мое внимание и в результате получаем следующие два файлы — ViewController.swift и Main.storyboard:

Далее следуем подсказке № 1:

Подсказка 1

Вы можете использовать тот же самый механизм расположения UI, который мы использовали в Concentration (то есть Stack Views). В начальной стадии игры некоторые кнопки начинают с того, что не показывают карту (так как в начале у нас только 12 карт, но вы должны иметь достаточно места для размещения  24 карт) , а в более поздней стадии игры будут кнопки, представляющие совпавшие карты, которые не могут быть заменены. Мы будем обращаться с этим также, как обращались в Concentration (совпавшей и удаленной) картой (то есть кнопка существует, но она не видна пользователю).

Размещаем на storyboard одну карту (кнопку UIButton) будущей игры Set «лицевой» стороной вверх, сделав ее фон белым и поместив вертикально символы, которые станут потом в нашей игре основными. Для того, чтобы разместить на storyboard вертикальные символы, устанавливаем свойство Line Break кнопки UIButton в Character Wrap, а переносим на другую строку с помощью Alt/Option + Enter:

Мы хотим сделать нашу карту похожей на настоящую карту с закругленными краями, кроме то, нам придется управлять показом этой карты, если пользователь выбрал ее (selected), если она входит в число 3-х совпавших (matched)  или не совпавших (dismatched) карт. Для этого мы воспользуемся подсказкой № 6:

Подсказка 6

Вы можете показать выбор (selection), используя цвет фона backgroundColor кнопки UIButton, если хотите, но UIKit также знает, как разместить границу вокруг любого UIView (включая UIButton) с помощью следующего кода (который будет рисовать границу шириной 3 points голубым цветом, например):

button.layer.borderWidth = 3.0

button.layer.borderColor = UIColor.blue.cgColor

Мы будем подсвечивать границы кнопки разными цветами в зависимости от того, является ли она выбранной (selected), одной из 3-х совпавших (matched) или не совпавших (dismatched). Для этого мы создадим пользовательский класс BorderButton: с тремя основными свойствами : цветом границы borderColor, шириной границы borderWidth, радиус закругленных углов cornerRadius:

В Инспекторе Идентичности мы установим нашей кнопке класс BorderButton:

и так как свойства кнопки borderColor, borderWidth, cornerRadius являются @IBInspectable,то мы их видим в Инспекторе Атрибутов — там показаны значения, выставленные в классе BorderButton по умолчанию и именно поэтому у нас граница кнопки имеет ярко салатовый цвет. Мы можем регулировать эти параметры и я сделаю границу кнопки прозрачной:

Разместим 24 кнопки-карты простым копированием и подсоединим к нашей «сетке» карт @IBOutlet cardButtons:

Мы добавили также кнопки управления и метки, которые нам понадобятся в будущем. Заметим, что кнопки также являются пользовательскими кнопками, которые обслужите класс BorderButton:

Пункт 3 обязательный

При старте сдайте только 12 карт. Они могут появиться где угодно на экране (то есть необязательно их выравнивать по верху или по низу экрана или как-то еще; при старте они могут быть рассеяны, если хотите), но они не должны перекрываться.

Давайте распечатаем на кнопках-картах их индексы  и подсветим первые 12 карт оранжевым цветом, то есть карт с индексами в диапазоне 0.. <12:

Мы видим, что индексы карт не обязательно следуют в порядке слева направо сверху вниз. То есть обязательный пункт 3 предлагает не заботится о том, что первые 12 карт могут располагаться в произвольном месте, лишь бы они не перекрывали друг друга. Об этом же говорит подсказка № 3:

Подсказка 3

Заметьте, вам не требуется выравнивать карты, когда их меньше, чем максимальное количество карт, которые можно показать, так что случайное позиционирование элементов Outlet Collection не является проблемой на этой неделе. Мы исправим это на следующей неделе с помощью улучшенной UI архитектуры.

И тем не менее отладку Задания 2 легче вести, если кнопки-карты располагаются в правильном порядке  — слева направо сверху вниз :

Этого можно довольно просто добиться, если «подвязывать» кнопки одну за другой с помощью CTRL-перетягивания к @IBOutlet cardButtons в правильном порядке.

Теперь займемся Моделью игры Set.

Пункты 5 и 6 обязательные

5. Разрешите пользователю выбирать карты касанием для того, чтобы попытаться составить Set. На ваше усмотрение, как показывать “выбор”  в вашем UI. Некоторые идеи того, как это можно сделать представлены ниже в подсказках. Также обеспечьте возможность переход из состояние “выбрано” (selected) в состояние “не выбрано”(deselected) (но только когда 1 или 2 (не 3) карты выбраны в данный момент).

6. После того, как выбраны 3 карты, вы должны дать пользователю индикацию, совпали ли эти 3 карты или нет (согласно правилам игры Set). Вы можете сделать это с помощью цвета или как хотите, но пользователю должно быть понятно, совпали эти 3 карты или нет…

При построении Модели мы будем принимать во внимание следующие подсказки:

Подсказка 16 и 17

16. Было бы хорошо иметь MVC дизайн, который бы не привязывал жестко специальные имена цветов и фигур (типа diamond (ромб) или oval (овал) или green (зеленый) или striped (штрихованный)) к именам свойств в коде нашей Model. Как видите, в этом домашнем Задании (где мы используем ▲●■ вместо стандартных фигур и “затенение” (shading) вместо  “штриховки” (striping) и т.д. ) цвета, фигуры и т.д. действительно являются UI концепцией и не должны иметь ничего общего с Model.

17. На следующей неделе мы совсем не будем использовать строки с атрибутами, но если вы корректно сконструировали Model на этой неделе, то ваша Model не потребует изменения даже строчки кода. Подумайте, как сделать так, чтобы ваша Model просто имела правильный API для оповещения о том, что происходит в вашей игре, а не делала бы каких-то предположений о том, как игра представляется пользователю.

Карта в игре Set имеет 4 характеристики: количество символом number, цвет символов color, тип символа (или фигура) shape и наполнение fill. У каждой из этих характеристик есть 3 варианта значений v1, v2, v3, v4 . Карта Set в нашем приложении моделируется структурой struct SetCard :

Как видите, у нас получилась карта  Set полностью абстрактной, мы не привязаны ни к каким характеристикам ее визуализации. Кроме того структура SetCard является  полностью неизменяемой (immutable) (так как в ней одни только константы lets и вычисляемые переменные vars типа read- only).

В структуре  struct SetCard есть static var isSet, которая отвечает на вопрос о том, составляет ли набор из 3-х карт Set или нет. Нашу структура SetCard реализует протокол Equatable, это даст нам возможность в дальнейшем воспользоваться при реализации игры Set такими удобными методами массива Array как index(of:) и contains(). Но они работают только с теми массивами Arrays, элементы которых реализуют протокол Equatable (как это делают, например, Int и String).

Колода Set карт моделируется с помощью структуры struct SetCardDeck и полностью повторяет логику построения колоды игральных карт, которую профессор демонстрировал на Лекции 5:

С помощью изменяемой функции draw() мы можем вытянуть случайную карту из колоды Set карт. Расширение extension Int взято из игры Concentration, которая обсуждалась на Лекции 1 и Лекции 2.

Логика игры Set в нашем приложении моделируется структурой struct SetGame. Единственная действительная функциональность нашей Model состоит в выборе (selecting) карт с тем, чтобы попытаться обеспечить их совпадение, и сдаче 3-х новых карт по требованию (потому что это фундаментальная концепция игры Set).

Поэтому основным методом в ней, также как и в приложении Concentration в struct Concentration, является изменяемый метод:


 mutating func chooseCard(at index: Int)


Но в отличие от игры  Concentration, в которой мы отслеживали состояния “лицом вверх” (faceUp) и “совпадение” (isMatched), легче отслеживать список всех выбранных (selected) карт или всех уже совпавших карт в структуре struct SetGame, чем иметь Bool переменную var  в нашей структуре данных для SetCard.

У игры Set есть список карт, которые находятся в игре, у нее есть несколько выбранных (selected) карт, она знает, совпадают (match) или нет выбранные (selected) в настоящий момент карты, у нее есть колода карт, из которой карты сдаются (deal), и, возможно, она хочет отслеживать, какие карты уже совпали (matched). В ней действительно очень много чего. API вашей Model должно представлять все эти концепции очень ясно. В нашей структуре SetGame для этой цели созданы массивы:

Их названия говорят сами за себя:

  • cardsOnTable — карты, находящиеся в данный момент на игровом столе
  • cardsSelected  — выбранные карты, их не может быть больше 3-х
  •  cardsTryMatched  — 3 карты для испытания на Set
  •  cardsRemoved — карты, составившие Set, и удаленные из игры

Кроме того, у нас есть колода deck, состоящая из 81 Set карты :

и метод получения 3-х случайных карт [SetCard]? из колоды take3FromDeck, если они так еще остались:

Есть возможность сдачи 3-х случайных карт из колоды, то есть размещения их на игровом столе, с помощью метода deal3( ):

Есть метод replaceOrRemove3Cards, с помощью которого можно заменить 3 карты cardsTryMatched на игровом столе, которые предназначены для испытания на Set, на любые 3 случайные карты из колоды. Если в колоде не остается карт для замены, то эти 3 карты удаляются с игрового стола :

Есть очень интересная вычисляемая переменная isSet — это Optional<Bool>,  она работает (то есть возвращает true или false) только тогда, когда в массиве cardsTryMatched находятся 3 карты для испытания на Set. Если вы устанавливаете переменную isSet в true или false, то три выбранные карты «перемещаются» из массива выбранных карт cardsSelected в массив cardsTryMatched испытания на Set, а если присваиваете значение nil, то массив cardsTryMatched очищается.

Но вся логика игры Set находится в методе chooseCard(at index: Int), который следует нескольким обязательным пунктам Задания 2.

Пункты 7 и 8 обязательные

7. Когда выбрана новая карта и есть уже 3 выбранных (selected) и не совпавших Set карты, сделайте эти 3 не совпавших карты не выбранными (deselected), а новую карту выбранной (selected).

8. Согласно правилам игры Set, когда выбрана новая карта и есть уже 3 совпавших (matching) и выбранных (selected) Set карты, замените эти 3 совпавших (matching) Set карты новыми из колоды в 81 Set >карту (опять, смотрите правила игры Set и что собой представляет колода Set карт). Если колода пуста, то совпавшие (matching) Set карты не могут быть заменены, но они могут быть скрыты (hidden) в вашем UI. Если вновь выбранная карта является одной из 3-х совпавших (matching) Set карт, то никакие карты не должны быть выбранными (selected) (так как вновь выбранная карта либо будет заменена, либо будет больше невидима на UI).

Давайте посмотрим, как выглядит метод chooseCard(at index: Int):

Центральная и главная часть метода — выбор 3-х карт, которые мы должны проверить на Set с помощью static метода isSet структуры SetCard:

if cardsSelected.count == 2, !cardsSelected.contains(cardChoosen){
                cardsSelected += [cardChoosen]
                isSet = SetCard.isSet(cards: cardsSelected)
} else {
               cardsSelected.inOut(element: cardChoosen)
}

В любом случае переменная isSet устанавливается в false или в true, а в этом случае все 3 выбранные карты удаляются из массива cardsSelected выбранных карт и перемещаются в массив карт cardsTryMatched, которые мы должны испытывать на Set, и мы сразу же сможем в дальнейшем отобразить их на UI соответствующим образом:

var isSet: Bool? {
     get {
             guard cardsTryMatched.count == 3 else {return nil}
            return SetCard.isSet(cards: cardsTryMatched)
     }
     set {
            if newValue != nil {
                   cardsTryMatched = cardsSelected
                   cardsSelected.removeAll()
            } else {
                   cardsTryMatched.removeAll()
            }
     }
}

Пункт 11 обязательный

Вместо рисования Set карт в классической форме (мы будем это делать на следующей неделе), мы будем использовать эти 3 символ ▲ ● ■ и использовать атрибуты в NSAttributedString для соответствующего их рисования (то есть цвета и затенение (shading)). И таким образом, ваши карты могут быть просто кнопками UIButtons. Смотри подсказки с предложениями о том, как показывать различные варианты Set карт.

Посмотрим, какие нам предлагают подсказки.

Подсказка 8

Если вы хотите закрасить (fill) символ в NSAttributedString, то используйте NSAttributedStringKey.strokeWidth с отрицательным числом.

Подсказка 9

Для того, чтобы Set карта выглядела “заштрихованной” (“striped”), просто используйте NSAttributedStringKey.foregroundColor с 15% alpha (создается с помощью UIColor метода withAlphaComponent>). Цвет foregroundColor со 100% alpha может быть использован для “закрашенной“ (“filled”) карты и позитивное значение strokeWidth для “пустой” (“outline”) карты.

Подсказка 10

Помимо указанных выше двух атрибутов NSAttributedStringKeys, вам еще может понадобиться только NSAttributedStringKey.strokeColor>.

Подсказка 11

Вы можете использовать какие хотите цвета для вашего UI (то есть вы не обязаны использовать “стандартные” цвета игры Set).

Подсказка 12

кнопок с вашими Set картами. У некоторых шрифтов эти три фигуры (▲●■) могут иметь различный размер. Похоже, что у systemFont размеры всех фигур одинаковые.

Мы создадим класс SetCardButton, для кнопки, представляющей Set карту на нашем UI, он наследует от класса BorderButton, так как нам придется использовать границу для выделения Set карты в различных обстоятельствах, и у него есть свойство setCard, которое является Optional SetCard? :

Установив это свойство извне, мы можем настроить внешний вид кнопки с помощью метода updateButton(), сделав заголовок кнопки строкой с атрибутами attributedString, установив цвет фона backgroundColor, цвет рамки  borderColor, возможность взаимодействия с кнопкой isEnable:

Если свойство setCard не равно nil, то есть действительно установлена какая-то Set карта, то мы определяем строку с атрибутами attributedString как NSAttributedString с помощью private вспомогательного метода 

setAttributedString (card: SetCard) -> NSAttributedString

а затем устанавливаем ее в качестве заголовок кнопки помощью метода:

 setAttributedTitle (attributedString, for: .normal)

Попутно устанавливаем цвет фона Set карты в белый и делаем доступной кнопку для пользователя.

Если свойство setCard не равно nil, то делаем кнопку прозрачной, все заголовки ее устанавливаем в nil и делаем недоступной пользователю:

Значение nil для свойства setCard очень удобно, если карта должна оставаться на UI, но должна быть невидима и недоступна для пользователя. Именно такая ситуация складывается в конце игры Set, когда карт в колоде не остается, замены совпавших карт не происходит и они должны быть убраны с игрового стола.

Ключевым методом, формирующим UI нашей Set карты, является метод setAttributedString (card: ), который в качестве аргумента берет нашу Set карту. Set карта в нашем приложении моделируется структурой struct SetCard со следующими свойствами:

Все свойства имеют ТИП Variant, который является перечислением enum:

Для отображения этих характеристик Set карты на UI у нас будут 4 массива:

Массив colors для 3-х значений свойства color карты SetCard, массивы alphas и strokeWidths — для 3-х значений свойства fill карты SetCard, массив symbols — для 3-х значений свойства shape, 3 значения свойства number отображаются просто количеством символов (1, 2 или 3) в строке с атрибутами.

Наш вспомогательная функция 

setAttributedString(card: SetCard) -> NSAttributedString

которая берет в качестве аргумента Set карту card ТИПА SetCard и, используя массивы symbolscolors, alphas и strokeWidths, возвращает строку с атрибутами NSAttributedString, соответствующую этой карте. Давайте посмотрим, как эта функция работает :

Имея в распоряжении Set карту card и ее свойство card.shape, мы определяем, какой символ symbol будет отображаться на нашей карте, то есть какой элемент массива

symbols = [«●», «▲», «■»]

нам следует взять, исходя из значения свойства card.shape. Свойство card.shape имеет ТИП перечисления enum Variant с rawValues 1,2,3, и мы могли бы использовать его rawValue для доступа к элементу массива symbols, но чтобы каждый раз не писать card.shape.rawValue — 1, мы наделили наше перечисление Variant вычисляемой переменной idx :

Пользуясь свойством idx нашего перечисления card.shape мы можем очень легко определить требуемый символ symbol :

let symbol = symbols [card.shape.idx]

Нам нужно сформировать строку, отображаемую на нашей Set карте, состоящую из card.number символов, разделенных некоторым разделителем separator, который будет зависеть от того, в каком режиме мы находимся —  в портретном или ландшафтном. В портретном режиме наши символы будут располагаться вертикально и, следовательно, разделителем separator будет символ «\n» перехода на другую строку, а в ландшафтном — горизонтально и, следовательно, разделителем separator будет просто пробел » « :

Для удобства формирования строки String из произвольного числа заданных символов с разделителем separator мы разместим в расширении extension строки String функцию: 

func join(n: Int, with separator:String )-> String

у которой два аргумента: n — число повторений исходной строки и separator — разделитель, на выходе этой функции — строка String.

Вот реализация функции join (n: , with separator:) :

Она нам поможет сформировать строку symbolsString для нашей Set карты:

Далее нашу Set карту нужно наделить атрибутами: шириной обводки символов  strokeWidth, прозрачностью alpha и цветом color, которые определяются свойствами color (цвет) и fill (заполнение) нашей Set карты card и также являются также перечислениями enum Variant с rawValues 1,2,3, и мы опять будем использовать их вычисляемое свойство idx для доступа к массиву прозрачностей alphas, к массиву различных вариантов ширины линии обводки strokeWidths и массиву цветов colors:

Имея строку symbolsString и атрибуты attributes, нам очень просто сформировать и вернуть строку с атрибутами:

Портретный или ландшафтный режим мы определяем с помощью вычисляемой переменной var c именем verticalSizeClass:

именно она участвует в определении разделителя separator :

Она должна изменяться при изменении границ bounds нашей копки SetCardButton, например, при повороте нашего устройства. Следовательно, мы должны обновить кнопку в методе layoutSubviews() :

Это даст возможность иметь различное адаптивное расположение символов на кнопке-карте в портретном и ландшафтном режимах:

У класса SetCardButton есть также метод setBorderColor установки цвета границы кнопки, который, конечно, нам необходим при индикации выбранных (selected) Set карт; карт, представляющих собой Set, и карт, которые мы испытывали на Set и которые ему не удовлетворяют:

Теперь обратимся к ViewController. Он будет выглядеть очень похоже на тот, что был в игре Concentration:

Также есть переменная var game,  которая представляет собой Model игры Set, есть Action touchCard (_ sender: SetCardButton) который срабатывает при клике на кнопке-карте и вызывает метод game.chooseCard (at:), принадлежащий Model, есть также метод  updateViewFromModel(), который в нашем случае представляет собой обновление по данным Model не только кнопок-карт, но и заголовков кнопок и меток, но все же его главной частью является метод  updateButtonsFromModel(), который обновляет кнопки-карты и который в нашем случае намного интереснее аналогичного метода в игре Concentration:

Мы устанавливаем цвета для индикации карт с помощью структуры struct Colors:

Запускаем приложение, и смотрим, что будет происходить, если мы кликаем сначала на одну карту, затем еще на одну и, наконец, на третью карту:

Мы получили Set — об этом говорит подсветка границ совпавших карт бирюзовым цветом, изменившийся счет Score и текст «СОВПАДЕНИЕ» в информационной метке внизу. Кроме того, пользователь видит все 3 карты, принадлежащие Set. На следующем шаге совпавшие карты будут заменены на новые карты из колоды и удалены с игрового стола, если в колоде карт не окажется, то замены карт не произойдет, а соответствующие совпавшим картам кнопки будут невидимы и недоступны для пользователя.  Игра Set продолжится в новой конфигурации карт :

Если мы не получили Set, то карты НЕ удаляются и НЕ заменяются на новые на следующем шаге и игра начинается сначала при той же конфигурации.

Еще раз вернемся к нашему пользовательскому классу  SetCardButton, представляющему Set карту в виде кнопки на нашем UI. У него 5 public переменных, которые мы можем устанавливать извне:

Массивы symbolscolors , alphas и strokeWidths задают UI Set кнопки по умолчанию, все эти массивы могут быть установлены извне, например, из ViewController, что полностью поменяет UI ваших Set кнопок:

Ваш UI изменится:

Пункт 4 обязательный

Вам необходимо также иметь кнопку “Deal 3 More Cards” (Сдай еще 3 карты) (согласно правилам игры Set).

Пункт 9 обязательный

Когда кнопка “Deal 3 More Cards” (Сдай еще 3 карты) нажата, то  либо a) происходит замена выбранных карт, если они совпали, либо b) добавляются 3 карты в игру.

Пункт 10 обязательный

Кнопка “Deal 3 More Cards” (Сдай еще 3 карты) должна быть недоступна, если a) больше нет карт в Set колоде или  b) больше нет мести на UI, чтобы принять еще 3 карты (заметьте, что всегда есть место для размещения еще 3-х карт, если выбранные в данный момент карты совпали (match), так как они заменяются).

Кнопка “Deal 3+» » сдает » 3 карты из колоды если есть место на UI для их добавления:

Кнопка “Deal 3+» становится недоступной, если в колоде нет больше карт, или если отсутствует место на UI:

Поэтому если колода пуста, то кнопка  “Deal 3+» будет выглядеть так:

Подсказка 20

Внимательно проверяй “конец игры.” Когда колода Set карт исчерпалась, успешно совпавшие карты не подлежат замене новыми картами. Эти НЕ-заменяемые совпавшие карты не могут появляться на UI (иначе пользователи могут попытаться найти совпадение их с другими картами!). По этой причине API вашей Model должен выявлять, какие карты уже успешно совпали.

В нашей Модели есть массив cardsRemoved уже совпавших и удаленных с игрового стола карт:

Мы обнаруживаем ( или не обнаруживаем) Set в Модели сразу же, как только выбрана 3-ая карта в добавок к остальным 2-м. Мы списываем массив выбранных 3х карт cardsSelected  в массив карт cardsTryMatched для тестирования на Set :

и можем показывать их на UI с помощью рамки определенного цвета:

Мы получим экран одного из 2-х видов: бирюзовый цвет рамки — Set, черный цвет рамки — НЕ Set:

Продолжить игру мы сможем только, если выберем новую карту, не принадлежащую ни уже совпавшим и убранным с игорного стола картам cardsRemoved, ни картам cardsTryMatched попавшим на испытание на Set :

Далее анализируя Optional переменную isSet, мы смотрим, принималось ли решение о Set на предыдущем шаге. Если да, то в случае Set мы либо заменяем совпавшие карты cardsTryMatched на новые карты из колоды, либо нет, но в любом случае переводим совпавшие карты в карты cardsRemoved, удаленные с игорного стола. Если Set не обнаружен, то с картами ничего, они остаются в игре. В обоих этих случаях  мы устанавливаем переменную isSet в nil, тем самым освобождая массив карт  cardsTryMatched, собранных для испытания на Set:

Таким образом, Set карта проходит следующий путь: из колоды deck попадает на игорный стол  cardsOnTable, затем в выбранные карты selectedCards, затем в массив карт cardsTryMatched, собранных для испытания на Set, и в случае успешных испытаний в совпавшие и удаленные с игорного стола карты cardsRemoved:

Для того, чтобы быстро дойти до конца игры Set, мы воспользуемся дополнительным пунктом 3.

Пункт 3 дополнительный

Если вы напишете алгоритм определения Sets, то сможете добавить “мошенническую” кнопку для того, чтобы испытывающий трудности пользователь смог использовать ее для нахождения Set!

В Модели в структуре SetGame пишем алгоритм нахождения всех Sets для карт, лежащих на игровом столе — это будет переменная var hints, которая содержит массив Sets, то есть массивы индексов карт, составляющих Set:

И на нашем UI размещаем  “мошенническую” кнопку, на которой показывается количество Sets для карт, лежащих на игровом столе:

Но это не все, при нажатии на кнопку «Hint» у вас последовательно будут подсвечиваться на очень короткое время кнопки, составляющие Set, а на информационной метке появится сообщение о номере подсвечиваемого Set:

Пункт 12 обязательный

Используйте метод, который берет в качестве аргумента замыкание как значимую часть вашего решения. Вы не сможете использовать тот, который показан на Лекции.

При определении Set в структуре SetCard мы использовали метод reduce, требующий замыканий:

Для индикации карт, составляющих Set, мы использовали в ViewController метод forEach и метод Timer.scheduledTimer(withTimeInterval: Constants.flashTime, repeats: false), требующие замыканий в качестве аргументов :

Пункт 13 обязательный

Используйте перечисление enum как значимую часть вашего решения.

В Модели Set карты в структуре SetCard мы использовали enum для представления 4-х характеристик:

Пункт 14 обязательный

Добавьте осмысленное расширение extension к некоторым структурам данных как значимую часть вашего решения. Вы не можете использовать то, которое было показано на Лекции.

В Модели игры SetGame мы использовали расширения массива Array c Equatable элементами:

На Swift форуме есть интересная статья «Conditional Conformance in the Standard Library» на этот счет.

Пункт 15 обязательный

Ваш UI должен иметь прекрасно расположенные UI элементы и хорошо выглядеть ( по крайней мере в портретном режиме, желательно также и в ландшафтном режиме, хотя это не обязательно) на любом iPhone 7 или старше. Это означает, что вам следует использовать несколько простых приемов работы с Autolayout, включая Stack Views.

Используем приемов работы с Autolayout, включая  Stack Views, точно такие же как и для Задания 1 и так, как показывал профессор на Лекции 2. Но у нас есть возможность улучшить вид нашей игры Set в ландшафтном режиме за счет расположения символов игры Set не по вертикали, а по горизонтали:

Этого мы добились использованием в класс SetCardButton переменной var verticalSizeClass, которая меняется, например, при повороте нашего устройства. Для этого мы обновляем нашу кнопку SetCardButton в методе layoutSubviews (), о котором профессор рассказывает на Лекции 6.

Код находится на Github.

Продолжение следует…

Задание 2 Stanford CS 193P Fall 2017. Игра Set. Решение обязательных пунктов.: 2 комментария

  1. В методе takeThreeFromDeck, если в колоде осталось две карты, то метод берет эти две карты, пытается взять третью, не может, затем возвращает nil. И две карты бесследно пропадают, потому что уже были взяты, как я понял?

    • В игре Set НИКОГДА в колоде не остается НИ 2 карты, НИ 1 карта: всего в колоде 81 карта, мы начинаем с 12 карт и добавляем по 3 карты каждый раз. Так что нет смысла городить дополнительные проверки.

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

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