Пішам гульню «Карткі памяці» на Swift

Пішам гульню «Карткі памяці» на Swift

У гэтым артыкуле апісваецца працэс стварэння простай гульні для трэніроўкі памяці, якая мне вельмі падабаецца. Акрамя таго, што яна сама па сабе добрая, падчас працы вы крыху больш даведаецеся аб класах і пратаколах Swift. Але перш чым пачаць, давайце разбяромся ў самой гульні.

Нагадваем: для ўсіх чытачоў "Хабра" - зніжка 10 000 рублёў пры запісе на любы курс Skillbox па промакодзе "Хабр".

Skillbox рэкамендуе: Адукацыйны анлайн-курс «Прафесія Java-распрацоўшчык».

Як гуляць у Memory Card

Гульня пачынаецца з дэманстрацыі набору картак. Яны ляжаць "кашуляй" уверх (адпаведна, выявай уніз). Калі вы клікаеце па любой, на некалькі секунд адчыняецца малюнак.

Задача гульца - знайсці ўсе карткі з аднолькавымі карцінкамі. Калі пасля адкрыцця першай карты вы пераварочваеце другую і карцінкі супадаюць, абедзве карткі застаюцца адкрытымі. Калі не супадаюць, карткі зноў зачыняюцца. Задача - адкрыць усё.

Структура праекту

Для таго, каб стварыць простую версію гэтай гульні патрэбны наступныя кампаненты:

  • Адзін кантролер (One Controller): GameController.swift.
  • Адзін прагляд (One View): CardCell.swift.
  • Дзве мадэлі (Two Models): MemoryGame.swift and 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.

init(image: UIImage) {        
    self.id = NSUUID().uuidString        
    self.shown = false        
    self.artworkURL = image    
}

Метад для параўнання id карт.

func equals(_ card: Card) -> Bool {
    return (card.id == id)    
}

Метад для стварэння копіі кожнай карткі - Для таго, каб атрымаць большую колькасць аднолькавых. Гэты метад будзе вяртаць card з аналагічнымі значэннямі.

func copy() -> Card {        
    return Card(card: self)    
}
 
init(card: Card) {        
    self.id = card.id        
    self.shown = card.shown        
    self.artworkURL = card.artworkURL    
}

І яшчэ адзін метад патрэбен для мяшання картак на старце. Мы зробім яго пашырэннем класа Array.

extension Array {    
    mutating func shuffle() {        
        for _ in 0...self.count {            
            sort { (_,_) in arc4random() < arc4random() }        
        }   
    }
}

А вось рэалізацыя кода для мадэлі Card са ўсімі ўласцівасцямі і метадамі.

class Card {
    
    var id: String
    var shown: Bool = false
    var artworkURL: UIImage!
    
    static var allCards = [Card]()
 
    init(card: Card) {
        self.id = card.id
        self.shown = card.shown
        self.artworkURL = card.artworkURL
    }
    
    init(image: UIImage) {
        self.id = NSUUID().uuidString
        self.shown = false
        self.artworkURL = image
    }
    
    func equals(_ card: Card) -> Bool {
        return (card.id == id)
    }
    
    func copy() -> Card {
        return Card(card: self)
    }
}
 
extension Array {
    mutating func shuffle() {
        for _ in 0...self.count {
            sort { (_,_) in arc4random() < arc4random() }
        }
    }
}

Ідзем далей.

Другая мадэль – MemoryGame, тут задаем сетку 4*4. У мадэлі будуць такія ўласцівасці, як cards (масіў картак на сетцы), масіў cardsShown з ужо адчыненымі карткамі і лагічная зменная isPlaying для адсочвання статуту гульні.

class MemoryGame {        
    var cards:[Card] = [Card]()    
    var cardsShown:[Card] = [Card]()    
    var isPlaying: Bool = false
}

Нам таксама трэба распрацаваць метады для кіравання ўзаемадзеяння карыстальніка з сеткай.

Метад, які змешвае карткі ў сетцы.

func shuffleCards(cards:[Card]) -> [Card] {       
    var randomCards = cards        
    randomCards.shuffle()                
 
    return randomCards    
}

Метад для стварэння новай гульні. Тут мы выклікаем першы метад для старту пачатковай раскладкі і ініцыялізуем зменную isPlaying як true.

func newGame(cardsArray:[Card]) -> [Card] {       
    cards = shuffleCards(cards: cardsArray)        
    isPlaying = true            
 
    return cards    
}

Калі мы хочам перазапусціць гульню, то ўсталёўваны зменную isPlaying як false і прыбіраны першапачатковую раскладку картак.

func restartGame() {        
    isPlaying = false                
    cards.removeAll()        
    cardsShown.removeAll()    
}

Метад для верыфікацыі націснутых картак. Больш падрабязна пра яго пазней.

func cardAtIndex(_ index: Int) -> Card? {        
    if cards.count > index {            
        return cards[index]        
    } else {            
        return nil        
    }    
}

Метад, які вяртае пазіцыю вызначанай карткі.

func indexForCard(_ card: Card) -> Int? {        
    for index in 0...cards.count-1 {            
        if card === cards[index] {                
            return index            
        }      
    }
        
    return nil    
}

Праверка адпаведнасці абранай картцы эталону.

func unmatchedCardShown() -> Bool {
    return cardsShown.count % 2 != 0
}

Гэты метад чытае апошні элемент у масіве 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 выглядае прыкладна так:

Пішам гульню «Карткі памяці» на Swift

Першапачаткова ў кантролеры трэба ўсталяваць новую гульню як viewDidLoad, уключаючы выявы для сеткі. У гульні ўсё гэта будзе прадстаўлена 4*4 collectionView. Калі вы яшчэ не знаёмыя з collectionView, вось тут можна атрымаць патрэбную інфармацыю.

Мы наладзім GameController у якасці каранёвага кантролера прыкладання. У GameController будзе collectionView, на які мы будзем спасылацца ў якасьці IBOutlet. Яшчэ адна спасылка - на кнопку IBAction onStartGame (), гэта UIButton, яе вы можаце ўбачыць у раскадроўцы пад назвай PLAY.

Трохі аб рэалізацыі кантролераў:

  • Спачатку ініцыялізуем два галоўныя аб'екты — сетку (the grid): game = MemoryGame(), і на набор картак: cards = [Card]().
  • Усталеўваны пачатковыя зменныя як viewDidLoad, гэта першы метад, які выклікаецца падчас працы гульні.
  • collectionView усталёўваны як hidden, паколькі ўсе карты ўтоеныя да таго моманту, пакуль карыстач не націсне PLAY.
  • Як толькі націскаем PLAY, стартуе раздзел onStartGame IBAction, і мы выстаўляем уласцівасць collectionView isHidden як false, каб карткі маглі стаць бачнымі.
  • Кожны раз, калі карыстач выбірае картку, выклікаецца метад didSelectItemAt. У метадзе мы выклікаем didSelectCard для рэалізацыі асноўнай логікі гульні.

Вось фінальная рэалізацыя GameController:

class GameController: UIViewController {
 
    @IBOutlet weak var collectionView: UICollectionView!
    
    let game = MemoryGame()
    var cards = [Card]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        game.delegate = self
        
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.isHidden = true
        
        APIClient.shared.getCardImages { (cardsArray, error) in
            if let _ = error {
                // show alert
            }
            
            self.cards = cardsArray!
            self.setupNewGame()
        }
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        
        if game.isPlaying {
            resetGame()
        }
    }
    
    func setupNewGame() {
        cards = game.newGame(cardsArray: self.cards)
        collectionView.reloadData()
    }
    
    func resetGame() {
        game.restartGame()
        setupNewGame()
    }
    
    @IBAction func onStartGame(_ sender: Any) {
        collectionView.isHidden = false
    }
}
 
// MARK: - CollectionView Delegate Methods
extension GameController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return cards.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath) as! CardCell
        cell.showCard(false, animted: false)
        
        guard let card = game.cardAtIndex(indexPath.item) else { return cell }
        cell.card = card
        
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let cell = collectionView.cellForItem(at: indexPath) as! CardCell
        
        if cell.shown { return }
        game.didSelectCard(cell.card)
        
        collectionView.deselectItem(at: indexPath, animated:true)
    }
}

Цяпер давайце крыху спынімся на важных пратаколах.

пратаколы

Праца з пратаколамі - аснова асноў праграмавання на Swift. Пратаколы даюць магчымасць задаць правілы для класа, структуры ці пералічэнні. Гэты прынцып дазваляе пісаць модульны і які пашыраецца код. Фактычна гэта шаблон, які мы ўжо рэалізуем для collectionView у GameController. Цяпер зробім уласны варыянт. Сінтаксіс будзе выглядаць так:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Мы ведаем, што пратакол дазваляе вызначыць правілы ці інструкцыі для рэалізацыі класа, таму давайце падумаем, якімі яны мусяць быць. Усяго трэба чатыры.

  • Пачатак гульні: memoryGameDidStart.
  • Трэба перавярнуць картку кашуляй уніз: memoryGameShowCards.
  • Трэба перавярнуць картку кашуляй уверх: memoryGameHideCards.
  • Завяршэнне гульні: memoryGameDidEnd.

Усе чатыры метаду рэалізуемы для асноўнага класа, а гэта GameController.

memoryGameDidStart

Калі гэты метад запушчаны, гульня павінна пачацца (карыстальнік націскае PLAY). Тут проста перазагрузім кантэнт, выклікаўшы collectionView.reloadData (), што прывядзе да мяшання карт.

func memoryGameDidStart(_ game: MemoryGame) {
    collectionView.reloadData()
}

memoryGameShowCards

Выклікаем гэты метад з collectionSDViewSelectItemAt. Спачатку ён паказвае выбраную карту. Затым правярае, ці ёсць у масіве cardsShown несупастаўленая карта (калі лік cardsShown няцотны). Калі такая ёсць, абраная карта параўноўваецца з ёю. Калі карцінкі аднолькавыя, абедзве карты дадаюцца да 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()
    }
}

Пішам гульню «Карткі памяці» на Swift
Уласна, вось і ўсё. Вы можаце выкарыстоўваць гэты праект для стварэння ўласнага варыянта гульні.

Удалага кодынгу!

Skillbox рэкамендуе:

Крыніца: habr.com

Дадаць каментар