Scrierea unui joc cu carduri de memorie în Swift

Scrierea unui joc cu carduri de memorie în Swift

Acest articol descrie procesul de creare a unui joc simplu de antrenament de memorie care îmi place foarte mult. Pe lângă faptul că sunt bune în sine, veți învăța puțin mai multe despre cursurile și protocoalele Swift pe măsură ce mergeți. Dar înainte de a începe, să înțelegem jocul în sine.

Amintim: pentru toți cititorii „Habr” - o reducere de 10 de ruble la înscrierea la orice curs Skillbox folosind codul promoțional „Habr”.

Skillbox recomandă: Curs educativ online „Profesie Dezvoltator Java”.

Cum să joci cardul de memorie

Jocul începe cu o demonstrație a unui set de cărți. Se întind cu fața în jos (respectiv, imaginea în jos). Când faceți clic pe oricare, imaginea se deschide pentru câteva secunde.

Sarcina jucătorului este să găsească toate cărțile cu aceleași imagini. Dacă, după deschiderea primei cărți, îl întoarceți pe al doilea și imaginile se potrivesc, ambele cărți rămân deschise. Dacă nu se potrivesc, cărțile sunt închise din nou. Scopul este de a deschide totul.

Structura proiectului

Pentru a crea o versiune simplă a acestui joc, aveți nevoie de următoarele componente:

  • Un controler: GameController.swift.
  • One View: CardCell.swift.
  • Două modele: MemoryGame.swift și Card.swift.
  • Main.storyboard pentru a se asigura că întregul set de componente este disponibil.

Începem cu cea mai simplă componentă a jocului, cărțile.

Card.swift

Modelul cardului va avea trei proprietăți: id pentru a le identifica pe fiecare, o variabilă booleană afișată pentru a specifica starea cardului (ascuns sau deschis) și artworkURL pentru imaginile de pe carduri.

class Card {        
    var id: String    
    var shown: Bool = false    
    var artworkURL: UIImage!
}

Veți avea nevoie și de aceste metode pentru a controla interacțiunea utilizatorului cu hărțile:

Metodă de afișare a unei imagini pe un card. Aici resetăm toate proprietățile la valorile implicite. Pentru id, generăm un id aleatoriu apelând NSUUIS().uuidString.

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

Metodă de comparare a cărților de identitate.

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

Metodă de a crea o copie a fiecărui card - pentru a obține un număr mai mare de identice. Această metodă va returna cardul cu valori similare.

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

Și este nevoie de încă o metodă pentru a amesteca cărțile la început. O vom face o extensie a clasei Array.

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

Și aici este implementarea codului pentru modelul Card cu toate proprietățile și metodele.

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() }
        }
    }
}

Dați-i drumul.

Al doilea model este MemoryGame, aici setăm o grilă 4*4. Modelul va avea proprietăți precum cărți (o matrice de cărți pe o grilă), o matrice cardsShown cu cărți deja deschise și o variabilă booleană isPlaying pentru a urmări starea jocului.

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

De asemenea, trebuie să dezvoltăm metode pentru a controla interacțiunea utilizatorului cu rețeaua.

O metodă care amestecă cărțile într-o grilă.

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

Metoda de creare a unui nou joc. Aici apelăm prima metodă pentru a începe aspectul inițial și a inițializa variabila isPlaying la true.

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

Dacă vrem să repornim jocul, apoi setăm variabila isPlaying la false și eliminăm aspectul inițial al cărților.

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

Metodă de verificare a cardurilor pe care s-a făcut clic. Mai multe despre el mai târziu.

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

O metodă care returnează poziția unui anumit card.

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

Verificarea conformității cardului selectat cu standardul.

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

Această metodă citește ultimul element din matricea **cardsShown** și returnează cardul care nu se potrivește.

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 și GameController.swift

Main.storyboard arată cam așa:

Scrierea unui joc cu carduri de memorie în Swift

Inițial, trebuie să setați noul joc ca viewDidLoad în controler, inclusiv imaginile pentru grilă. În joc, toate acestea vor fi reprezentate de 4*4 collectionView. Dacă nu sunteți încă familiarizat cu collectionView, iată-l puteți obține informațiile de care aveți nevoie.

Vom configura GameController ca controler rădăcină al aplicației. GameController va avea un collectionView pe care îl vom referi ca IBOutlet. O altă referință este la butonul IBAction onStartGame(), acesta este un UIButton, îl puteți vedea în storyboard-ul numit PLAY.

Câteva despre implementarea controlorilor:

  • Mai întâi, inițializam două obiecte principale - grila: game = MemoryGame() și un set de cărți: cards = [Card]().
  • Am setat variabilele inițiale ca viewDidLoad, aceasta este prima metodă care este apelată în timp ce jocul rulează.
  • collectionView este setat la ascuns deoarece toate cărțile sunt ascunse până când utilizatorul apasă pe PLAY.
  • De îndată ce apăsăm PLAY, pornește secțiunea IBAction onStartGame și setăm proprietatea collectionView isHidden la false, astfel încât cărțile să poată deveni vizibile.
  • De fiecare dată când utilizatorul selectează un card, este apelată metoda didSelectItemAt. În metoda pe care o numim didSelectCard pentru a implementa logica principală a jocului.

Iată implementarea finală a 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)
    }
}

Acum să vorbim puțin despre protocoalele importante.

protocoale

Lucrul cu protocoale este nucleul programării Swift. Protocoalele oferă capacitatea de a defini reguli pentru o clasă, o structură sau o enumerare. Acest principiu vă permite să scrieți cod modular și extensibil. De fapt, acesta este un model pe care îl implementăm deja pentru collectionView în GameController. Acum să facem propria noastră versiune. Sintaxa va arăta astfel:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Știm că un protocol ne permite să definim reguli sau instrucțiuni pentru implementarea unei clase, așa că să ne gândim la ce ar trebui să fie acestea. Ai nevoie de patru în total.

  • Începutul jocului: memoryGameDidStart.
  • Trebuie să întoarceți cardul cu fața în jos: memoryGameShowCards.
  • Trebuie să întoarceți cardul cu fața în jos: memoryGameHideCards.
  • Sfârșitul jocului: memoryGameDidEnd.

Toate cele patru metode pot fi implementate pentru clasa principală, care este GameController.

memoryGameDidStart

Când această metodă este rulată, jocul ar trebui să înceapă (utilizatorul apasă pe PLAY). Aici pur și simplu vom reîncărca conținutul apelând collectionView.reloadData(), care va amesteca cardurile.

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

memoryGameShowCards

Numim această metodă din collectionSDViewSelectItemAt. Mai întâi arată cardul selectat. Apoi verifică dacă există o carte nepotrivită în matricea cardsShown (dacă numărul de cardsShown este impar). Dacă există unul, cardul selectat este comparat cu acesta. Dacă imaginile sunt aceleași, ambele cărți sunt adăugate la cardurile afișate și rămân cu fața în sus. Dacă este diferit, cardul lasă cărțile afișate și ambele sunt întoarse cu fața în jos.

memoryGameHideCards

Dacă cardurile nu se potrivesc, această metodă este apelată și imaginile cardului sunt ascunse.

arătat = fals.

memorieGameDidEnd

Când această metodă este apelată, înseamnă că toate cărțile sunt deja dezvăluite și sunt în lista cardsShown: cardsShown.count = cards.count, deci jocul s-a terminat. Metoda este apelată în mod specific după ce am apelat endGame() pentru a seta varul isPlaying la false, după care este afișat mesajul de final de joc. De asemenea, alertController este folosit ca indicator pentru controler. viewDidDisappear este apelat și jocul este resetat.

Iată cum arată totul în 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()
    }
}

Scrierea unui joc cu carduri de memorie în Swift
De fapt, asta-i tot. Puteți folosi acest proiect pentru a vă crea propria versiune a jocului.

Codare fericită!

Skillbox recomandă:

Sursa: www.habr.com

Adauga un comentariu