Γράψτε ένα παιχνίδι καρτών μνήμης στο Swift

Γράψτε ένα παιχνίδι καρτών μνήμης στο Swift

Αυτό το άρθρο περιγράφει τη διαδικασία δημιουργίας ενός απλού παιχνιδιού εκπαίδευσης μνήμης που μου αρέσει πολύ. Εκτός από το ότι είναι καλό από μόνο του, θα μάθετε περισσότερα για τις τάξεις και τα πρωτόκολλα του Swift καθώς προχωράτε. Αλλά πριν ξεκινήσουμε, ας καταλάβουμε το ίδιο το παιχνίδι.

Υπενθύμιση: για όλους τους αναγνώστες του "Habr" - έκπτωση 10 ρούβλια κατά την εγγραφή σε οποιοδήποτε μάθημα Skillbox χρησιμοποιώντας τον κωδικό προσφοράς "Habr".

Το Skillbox προτείνει: Εκπαιδευτικό διαδικτυακό μάθημα "Επάγγελμα προγραμματιστή Java".

Πώς να παίξετε κάρτα μνήμης

Το παιχνίδι ξεκινά με μια επίδειξη ενός σετ καρτών. Ξαπλώνουν μπρούμυτα (αντίστοιχα, εικόνα προς τα κάτω). Όταν κάνετε κλικ σε οποιοδήποτε, η εικόνα ανοίγει για λίγα δευτερόλεπτα.

Η αποστολή του παίκτη είναι να βρει όλες τις κάρτες με τις ίδιες εικόνες. Εάν, αφού ανοίξετε το πρώτο φύλλο, γυρίσετε το δεύτερο και οι εικόνες ταιριάζουν, και τα δύο φύλλα παραμένουν ανοιχτά. Εάν δεν ταιριάζουν, τα φύλλα κλείνουν ξανά. Στόχος είναι να ανοίξουν τα πάντα.

Δομή έργου

Για να δημιουργήσετε μια απλή έκδοση αυτού του παιχνιδιού χρειάζεστε τα ακόλουθα στοιχεία:

  • One Controller: GameController.swift.
  • One View: CardCell.swift.
  • Δύο μοντέλα: MemoryGame.swift και Card.swift.
  • Main.storyboard για να διασφαλίσετε ότι ολόκληρο το σύνολο των στοιχείων είναι διαθέσιμο.

Ξεκινάμε με το πιο απλό στοιχείο του παιχνιδιού, τις κάρτες.

Card.swift

Το μοντέλο της κάρτας θα έχει τρεις ιδιότητες: id για την αναγνώριση καθεμιάς, μια δυαδική μεταβλητή που εμφανίζεται για να καθορίσει την κατάσταση της κάρτας (κρυφή ή ανοιχτή) και artworkURL για τις εικόνες στις κάρτες.

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

Θα χρειαστείτε επίσης αυτές τις μεθόδους για τον έλεγχο της αλληλεπίδρασης των χρηστών με τους χάρτες:

Μέθοδος εμφάνισης εικόνας σε κάρτα. Εδώ επαναφέρουμε όλες τις ιδιότητες στις προεπιλογές. Για το αναγνωριστικό, δημιουργούμε ένα τυχαίο αναγνωριστικό καλώντας το NSUUIS().uuidString.

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)    
}
 
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. Το μοντέλο θα έχει ιδιότητες όπως κάρτες (μια συστοιχία καρτών σε ένα πλέγμα), έναν πίνακα που εμφανίζεται με κάρτες που έχουν ήδη ανοιχτές και μια μεταβλητή boolean 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(), αυτό είναι ένα κουμπί UIB, μπορείτε να το δείτε στο storyboard που ονομάζεται PLAY.

Λίγα λόγια για την εφαρμογή των ελεγκτών:

  • Αρχικά, αρχικοποιούμε δύο κύρια αντικείμενα - το πλέγμα: παιχνίδι = MemoryGame(), και ένα σύνολο καρτών: κάρτες = [Κάρτα]().
  • Ορίσαμε τις αρχικές μεταβλητές ως viewDidLoad, αυτή είναι η πρώτη μέθοδος που καλείται ενώ εκτελείται το παιχνίδι.
  • Το collectionView έχει οριστεί ως κρυφό επειδή όλες οι κάρτες είναι κρυφές μέχρι ο χρήστης να πατήσει το 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 είναι μονός). Εάν υπάρχει, η επιλεγμένη κάρτα συγκρίνεται με αυτήν. Εάν οι εικόνες είναι ίδιες, και οι δύο κάρτες προστίθενται στις κάρτες Εμφανίζονται και παραμένουν κλειστές. Εάν διαφέρει, η κάρτα αφήνει τις κάρτες Εμφανίζονται και και οι δύο είναι γυρισμένες με την όψη προς τα κάτω.

memoryGameHideCards

Εάν οι κάρτες δεν ταιριάζουν, καλείται αυτή η μέθοδος και οι εικόνες της κάρτας αποκρύπτονται.

φαίνεται = ψευδής.

memoryGameDidEnd

Όταν καλείται αυτή η μέθοδος, σημαίνει ότι όλα τα φύλλα έχουν ήδη αποκαλυφθεί και βρίσκονται στη λίστα καρτώνΕμφανίζονται: 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 προτείνει:

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο