Ein Memory Cards-Spiel in Swift schreiben

Ein Memory Cards-Spiel in Swift schreiben

Dieser Artikel beschreibt den Prozess der Erstellung eines einfachen Gedächtnistrainingsspiels, das mir wirklich gefällt. Abgesehen davon, dass es an sich gut ist, erfahren Sie im Laufe der Zeit auch etwas mehr über Swift-Klassen und -Protokolle. Aber bevor wir beginnen, wollen wir das Spiel selbst verstehen.

Erinnerung: für alle Leser von „Habr“ – ein Rabatt von 10 Rubel bei der Anmeldung zu einem beliebigen Skillbox-Kurs mit dem Aktionscode „Habr“.

Skillbox empfiehlt: Pädagogischer Online-Kurs „Beruf Java-Entwickler“.

So spielen Sie eine Speicherkarte

Das Spiel beginnt mit der Demonstration eines Kartensatzes. Sie liegen mit dem Gesicht nach unten (bzw. mit dem Bild nach unten). Wenn Sie auf eines klicken, wird das Bild für einige Sekunden geöffnet.

Die Aufgabe des Spielers besteht darin, alle Karten mit den gleichen Bildern zu finden. Wenn man nach dem Öffnen der ersten Karte die zweite umdreht und die Bilder übereinstimmen, bleiben beide Karten offen. Bei Nichtübereinstimmung werden die Karten wieder geschlossen. Ziel ist es, alles zu öffnen.

Projektstruktur

Um eine einfache Version dieses Spiels zu erstellen, benötigen Sie folgende Komponenten:

  • Ein Controller: GameController.swift.
  • Eine Ansicht: CardCell.swift.
  • Zwei Modelle: MemoryGame.swift und Card.swift.
  • Main.storyboard um sicherzustellen, dass der gesamte Satz an Komponenten verfügbar ist.

Wir beginnen mit der einfachsten Komponente des Spiels, den Karten.

Card.swift

Das Kartenmodell verfügt über drei Eigenschaften: id zur Identifizierung jedes einzelnen, eine boolesche Variable, die angezeigt wird, um den Status der Karte (versteckt oder offen) anzugeben, und ArtworkURL für die Bilder auf den Karten.

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

Sie benötigen außerdem diese Methoden, um die Benutzerinteraktion mit Karten zu steuern:

Verfahren zum Anzeigen eines Bildes auf einer Karte. Hier setzen wir alle Eigenschaften auf die Standardeinstellungen zurück. Für die ID generieren wir eine zufällige ID, indem wir NSUUIS().uuidString aufrufen.

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

Methode zum Vergleich von Ausweisen.

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

Methode zum Erstellen einer Kopie jeder Karte - um eine größere Anzahl identischer Exemplare zu erhalten. Diese Methode gibt eine Karte mit ähnlichen Werten zurück.

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

Und es bedarf noch einer weiteren Methode, um die Karten zu Beginn zu mischen. Wir machen daraus eine Erweiterung der Array-Klasse.

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

Und hier ist die Implementierung des Codes für das Kartenmodell mit allen Eigenschaften und Methoden.

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

Gehen Sie voran.

Das zweite Modell ist MemoryGame, hier legen wir ein 4*4-Raster fest. Das Modell verfügt über Eigenschaften wie „Cards“ (eine Anordnung von Karten in einem Raster), ein „cardsShown“-Array mit bereits geöffneten Karten und eine boolesche Variable „isPlaying“, um den Status des Spiels zu verfolgen.

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

Wir müssen auch Methoden entwickeln, um die Benutzerinteraktion mit dem Grid zu steuern.

Eine Methode, die Karten in einem Raster mischt.

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

Methode zum Erstellen eines neuen Spiels. Hier rufen wir die erste Methode auf, um das anfängliche Layout zu starten und die Variable isPlaying auf true zu initialisieren.

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

Wenn wir das Spiel neu starten wollen, Dann setzen wir die Variable isPlaying auf false und entfernen das ursprüngliche Layout der Karten.

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

Methode zur Überprüfung angeklickter Karten. Mehr über ihn später.

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

Eine Methode, die die Position einer bestimmten Karte zurückgibt.

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

Überprüfung der Konformität der ausgewählten Karte mit dem Standard.

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

Diese Methode liest das letzte Element im Array **cardsShown** und gibt die nicht passende Karte zurück.

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 und GameController.swift

Main.storyboard sieht in etwa so aus:

Ein Memory Cards-Spiel in Swift schreiben

Zunächst müssen Sie das neue Spiel im Controller als viewDidLoad festlegen, einschließlich der Bilder für das Raster. Im Spiel wird all dies durch 4*4 CollectionView dargestellt. Wenn Sie mit „collectionView“ noch nicht vertraut sind, finden Sie es hier Sie können die Informationen erhalten, die Sie benötigen.

Wir werden den GameController als Root-Controller der Anwendung konfigurieren. Der GameController verfügt über eine CollectionView, auf die wir als IBOutlet verweisen. Ein weiterer Verweis bezieht sich auf die IBAction onStartGame()-Schaltfläche. Dies ist eine UIButton, Sie können sie im Storyboard mit dem Namen PLAY sehen.

Ein wenig über die Implementierung von Controllern:

  • Zuerst initialisieren wir zwei Hauptobjekte – das Raster: game = MemoryGame() und einen Satz Karten: Cards = [Card]().
  • Wir setzen die Anfangsvariablen auf viewDidLoad, dies ist die erste Methode, die aufgerufen wird, während das Spiel läuft.
  • „collectionView“ ist auf „hidden“ gesetzt, da alle Karten ausgeblendet sind, bis der Benutzer auf „PLAY“ drückt.
  • Sobald wir PLAY drücken, startet der onStartGame IBAction-Abschnitt und wir setzen die Eigenschaft „collectionView isHidden“ auf „false“, damit die Karten sichtbar werden können.
  • Jedes Mal, wenn der Benutzer eine Karte auswählt, wird die Methode didSelectItemAt aufgerufen. In der Methode rufen wir didSelectCard auf, um die Hauptspiellogik zu implementieren.

Hier ist die endgültige GameController-Implementierung:

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

Lassen Sie uns nun ein wenig über die wichtigen Protokolle sprechen.

Protokolle

Die Arbeit mit Protokollen ist der Kern der Swift-Programmierung. Protokolle bieten die Möglichkeit, Regeln für eine Klasse, Struktur oder Aufzählung zu definieren. Dieses Prinzip ermöglicht es Ihnen, modularen und erweiterbaren Code zu schreiben. Tatsächlich ist dies ein Muster, das wir bereits für CollectionView in GameController implementieren. Jetzt erstellen wir unsere eigene Version. Die Syntax sieht folgendermaßen aus:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Wir wissen, dass wir mit einem Protokoll Regeln oder Anweisungen für die Implementierung einer Klasse definieren können. Denken wir also darüber nach, wie diese aussehen sollten. Sie benötigen insgesamt vier.

  • Spielstart: MemoryGameDidStart.
  • Sie müssen die Karte umdrehen: MemoryGameShowCards.
  • Sie müssen die Karte umdrehen: MemoryGameHideCards.
  • Spielende: MemoryGameDidEnd.

Alle vier Methoden können für die Hauptklasse GameController implementiert werden.

MemoryGameDidStart

Wenn diese Methode ausgeführt wird, sollte das Spiel starten (der Benutzer drückt PLAY). Hier laden wir einfach den Inhalt neu, indem wir „collectionView.reloadData()“ aufrufen, wodurch die Karten gemischt werden.

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

MemoryGameShowCards

Wir rufen diese Methode von „collectionSDViewSelectItemAt“ auf. Zuerst wird die ausgewählte Karte angezeigt. Anschließend wird überprüft, ob sich im Array „cardsShown“ eine nicht übereinstimmende Karte befindet (wenn die Anzahl der „cardsShown“ ungerade ist). Ist eine vorhanden, wird die ausgewählte Karte damit verglichen. Sind die Bilder gleich, werden beide Karten zu den angezeigten Karten hinzugefügt und bleiben offen liegen. Wenn sie unterschiedlich sind, werden die Karten nicht angezeigt und beide werden umgedreht.

MemoryGameHideCards

Wenn die Karten nicht übereinstimmen, wird diese Methode aufgerufen und die Kartenbilder werden ausgeblendet.

angezeigt = falsch.

MemoryGameDidEnd

Wenn diese Methode aufgerufen wird, bedeutet dies, dass alle Karten bereits aufgedeckt sind und sich in der CardsShown-Liste befinden: CardsShown.count = Cards.count, das Spiel ist also beendet. Die Methode wird insbesondere aufgerufen, nachdem wir endGame() aufgerufen haben, um die Variable isPlaying auf false zu setzen, woraufhin die Meldung über das Ende des Spiels angezeigt wird. AlertController wird auch als Indikator für den Controller verwendet. viewDidDisappear wird aufgerufen und das Spiel wird zurückgesetzt.

So sieht alles im GameController aus:

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

Ein Memory Cards-Spiel in Swift schreiben
Eigentlich ist das alles. Mit diesem Projekt können Sie Ihre eigene Version des Spiels erstellen.

Viel Spaß beim Codieren!

Skillbox empfiehlt:

Source: habr.com

Kommentar hinzufügen