Ця стаття описує процес створення простої гри для тренування пам'яті, яка мені дуже подобається. Крім того, що вона сама по собі хороша, під час роботи ви трохи більше дізнаєтеся про класи та протоколи Swift. Але перш ніж почати, давайте розберемося у самій грі.
Нагадуємо:для всіх читачів "Хабра" - знижка 10 000 рублів при записі на будь-який курс Skillbox за промокодом "Хабр".
Гра починається з демонстрації набору карток. Вони лежать "сорочкою" вгору (відповідно зображенням вниз). Коли ви клацаєте по будь-якій, на кілька секунд відкривається зображення.
Завдання гравця – знайти всі картки з однаковими картинками. Якщо після відкриття першої карти ви перевертаєте другу та картинки збігаються, обидві картки залишаються відкритими. Якщо не збігаються, картки знову закриваються. Завдання – відкрити все.
структура проекту
Для того, щоб створити просту версію цієї гри, потрібні наступні компоненти:
Один контролер (One Controller): GameController.swift.
Один перегляд (One View): CardCell.swift.
Дві моделі (Two Models): MemoryGame.swift та Card.swift.
Main.storyboard для того, щоб весь набір компонентів був у наявності.
Починаємо з найпростішого компонента гри, картки.
Card.swift
У моделі картки буде три властивості: id для ідентифікації кожної, логічна змінна shown для уточнення статусу карти (прихована або відкрита) та artworkURL для картинок на картках.
class Card {
var id: String
var shown: Bool = false
var artworkURL: UIImage!
}
Також будуть потрібні ці методи для керування взаємодією користувача з картками:
Метод виведення зображення на карту. Тут ми скидаємо всі властивості на дефолтні. Для id генеруємо довільний id шляхом виклику NSUUIS().uuidString.
Друга модель - MemoryGame, тут задаємо сітку 4*4. У моделі будуть такі властивості, як cards (масив карток на сітці), масив cardsShown з вже відкритими картками та логічна змінна isPlaying для відстеження статусу гри.
class MemoryGame {
var cards:[Card] = [Card]()
var cardsShown:[Card] = [Card]()
var isPlaying: Bool = false
}
Нам також потрібно розробити методи управління взаємодії користувача з сіткою.
Цей метод читає останній елемент у масиві **cardsShown** та повертає невідповідну картку.
func didSelectCard(_ card: Card?) {
guard let card = card else { return }
if unmatchedCardShown() {
let unmatched = unmatchedCard()!
if card.equals(unmatched) {
cardsShown.append(card)
} else {
let secondCard = cardsShown.removeLast()
}
} else {
cardsShown.append(card)
}
if cardsShown.count == cards.count {
endGame()
}
}
Main.storyboard та GameController.swift
Main.storyboard виглядає приблизно так:
Спочатку в контролері потрібно встановити нову гру як viewDidLoad, включаючи зображення для сітки. У грі все це буде представлено 4*4 collectionView. Якщо ви ще не знайомі з collectionView, ось тут можна отримати потрібну інформацію.
Ми налаштуємо GameController як кореневий контролер програми. У GameController буде collectionView, на який ми будемо посилатися як IBOutlet. Ще одне посилання - на кнопку IBAction onStartGame(), це UIButton, її ви можете побачити в розкадруванні під назвою PLAY.
Трохи про реалізацію контролерів:
Спочатку ініціалізуємо два основних об'єкта - сітку (the grid): game = MemoryGame (), і на набір карток: cards = [Card] ().
Встановлюємо початкові змінні як viewDidLoad, це перший метод, який викликається в процесі роботи гри.
collectionView встановлюємо як hidden, оскільки всі карти приховані доти, доки користувач не натисне PLAY.
Як тільки натискаємо PLAY, стартує розділ наStartGame IBAction, і ми виставляємо властивість collectionView isHidden як false, щоб картки могли стати видимими.
Щоразу, коли користувач вибирає картку, викликається метод didSelectItemAt. У методі ми викликаємо didSelectCard для реалізації основної логіки гри.
Тепер давайте трохи зупинимося на важливих протоколах.
протоколи
Робота з протоколами – основа основ програмування на Swift. Протоколи дають можливість встановити правила для класу, структури або перерахування. Цей принцип дозволяє писати модульний код, що розширюється. Фактично це шаблон, який ми вже реалізуємо для collectionView GameController. Тепер зробимо свій варіант. Синтаксис виглядатиме так:
protocol MemoryGameProtocol {
//protocol definition goes here
}
Ми знаємо, що протокол дозволяє визначити правила чи інструкції для реалізації класу, тому подумаємо, якими вони мають бути. Усього потрібно чотири.
Початок гри: memoryGameDidStart.
Потрібно перевернути картку сорочкою вниз: memoryGameShowCards.
Потрібно перевернути картку сорочкою вгору: memoryGameHideCards.
Завершення гри: memoryGameDidEnd.
Усі чотири методи реалізуємо для основного класу, а це GameController.
memoryGameDidStart
Коли цей метод запущено, гра повинна розпочатися (користувач натискає на PLAY). Тут просто перезавантажимо контент, викликавши collectionView.reloadData(), що призведе до перемішування карток.
Викликаємо цей метод з collectionSDViewSelectItemAt. Спочатку він показує вибрану картку. Потім перевіряє, чи є в масиві cardsShown непоставлена карта (якщо число cardsShown непарне). Якщо така є, вибрана карта порівнюється із нею. Якщо картинки однакові, обидві картки додаються до картокзображення і залишаються відкритими. Якщо різні, картка йде з карток, і обидві перевертаються сорочкою вгору.
memoryGameHideCards
Якщо карти не відповідають одна одній, викликається цей метод, і картинки карток ховаються.
shown=false.
memoryGameDidEnd
Коли цей метод викликається, означає, що всі карти вже відкриті і знаходяться в списку cardsShown: cardsShown.count = cards.count, так що гра закінчена. Метод викликається спеціально після того, як ми викликали endGame (), щоб встановити isPlaying var в false, після чого показується повідомлення про завершення гри. Також alertController використовується як індикатор для контролера. Викликається viewDidDisappear і гра скидається.
Ось як все це виглядає в GameController:
extension GameController: MemoryGameProtocol {
func memoryGameDidStart(_ game: MemoryGame) {
collectionView.reloadData()
}
func memoryGame(_ game: MemoryGame, showCards cards: [Card]) {
for card in cards {
guard let index = game.indexForCard(card)
else { continue
}
let cell = collectionView.cellForItem(
at: IndexPath(item: index, section:0)
) as! CardCell
cell.showCard(true, animted: true)
}
}
func memoryGame(_ game: MemoryGame, hideCards cards: [Card]) {
for card in cards {
guard let index = game.indexForCard(card)
else { continue
}
let cell = collectionView.cellForItem(
at: IndexPath(item: index, section:0)
) as! CardCell
cell.showCard(false, animted: true)
}
}
func memoryGameDidEnd(_ game: MemoryGame) {
let alertController = UIAlertController(
title: defaultAlertTitle,
message: defaultAlertMessage,
preferredStyle: .alert
)
let cancelAction = UIAlertAction(
title: "Nah", style: .cancel) {
[weak self] (action) in
self?.collectionView.isHidden = true
}
let playAgainAction = UIAlertAction(
title: "Dale!", style: .default) {
[weak self] (action) in
self?.collectionView.isHidden = true
self?.resetGame()
}
alertController.addAction(cancelAction)
alertController.addAction(playAgainAction)
self.present(alertController, animated: true) { }
resetGame()
}
}
Власне, ось і все. Цей проект можна використовувати для створення власного варіанта гри.