Een geheugenkaartspel schrijven in Swift

Een geheugenkaartspel schrijven in Swift

Dit artikel beschrijft het proces van het maken van een eenvoudig geheugentrainingspel waar ik erg van geniet. Behalve dat het op zichzelf goed is, leer je gaandeweg wat meer over Swift-klassen en -protocollen. Maar voordat we beginnen, laten we het spel zelf begrijpen.

Herinnering: voor alle lezers van "Habr" - een korting van 10 roebel bij inschrijving voor een Skillbox-cursus met behulp van de promotiecode "Habr".

Skillbox beveelt aan: Educatieve online cursus "Beroep Java-ontwikkelaar".

Hoe speel je Memory Card?

Het spel begint met een demonstratie van een set kaarten. Ze liggen met de voorkant naar beneden (respectievelijk afbeelding naar beneden). Wanneer u op iemand klikt, wordt de afbeelding enkele seconden geopend.

De taak van de speler is om alle kaarten met dezelfde afbeeldingen te vinden. Als je na het openen van de eerste kaart de tweede omdraait en de afbeeldingen overeenkomen, blijven beide kaarten open. Als ze niet overeenkomen, worden de kaarten weer gesloten. Het doel is om alles te openen.

Projectstructuur

Om een ​​eenvoudige versie van dit spel te maken, heb je de volgende componenten nodig:

  • Eén controller: GameController.swift.
  • Eén weergave: CardCell.swift.
  • Twee modellen: MemoryGame.swift en Card.swift.
  • Main.storyboard om ervoor te zorgen dat de volledige set componenten beschikbaar is.

We beginnen met het eenvoudigste onderdeel van het spel: de kaarten.

Kaart.snel

Het kaartmodel heeft drie eigenschappen: id om ze allemaal te identificeren, een Booleaanse variabele die wordt weergegeven om de status van de kaart (verborgen of open) te specificeren, en artworkURL voor de afbeeldingen op de kaarten.

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

U hebt deze methoden ook nodig om de gebruikersinteractie met kaarten te beheren:

Methode voor het weergeven van een afbeelding op een kaart. Hier zetten we alle eigenschappen terug naar de standaardwaarden. Voor ID genereren we een willekeurige ID door NSUUIS().uuidString aan te roepen.

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

Methode voor het vergelijken van identiteitskaarten.

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

Methode om van elke kaart een kopie te maken - om een ​​groter aantal identieke exemplaren te krijgen. Deze methode retourneert een kaart met vergelijkbare waarden.

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

En er is nog een methode nodig om de kaarten aan het begin te schudden. We maken er een uitbreiding van de klasse Array van.

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

En hier is de implementatie van de code voor het kaartmodel met alle eigenschappen en 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() }
        }
    }
}

Ga je gang.

Het tweede model is MemoryGame, hier stellen we een 4*4-raster in. Het model heeft eigenschappen zoals kaarten (een reeks kaarten op een raster), een array cardsShown met kaarten die al open zijn, en een Booleaanse variabele isPlaying om de status van het spel bij te houden.

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

We moeten ook methoden ontwikkelen om de interactie van gebruikers met het elektriciteitsnet te controleren.

Een methode waarbij kaarten in een raster worden geschud.

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

Methode voor het maken van een nieuw spel. Hier noemen we de eerste methode om de initiële lay-out te starten en de isPlaying-variabele te initialiseren naar true.

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

Als we het spel opnieuw willen starten, vervolgens stellen we de isPlaying-variabele in op false en verwijderen we de oorspronkelijke lay-out van de kaarten.

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

Methode voor het verifiëren van aangeklikte kaarten. Later meer over hem.

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

Een methode die de positie van een specifieke kaart retourneert.

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

Controleren of de geselecteerde kaart voldoet aan de norm.

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

Deze methode leest het laatste element in de array **cardsShown** en retourneert de niet-overeenkomende kaart.

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

Main.storyboard ziet er ongeveer zo uit:

Een geheugenkaartspel schrijven in Swift

In eerste instantie moet je het nieuwe spel instellen als viewDidLoad in de controller, inclusief de afbeeldingen voor het raster. In het spel wordt dit allemaal weergegeven door 4*4 collectionView. Als u nog niet bekend bent met collectionView, hier is het dan u kunt de informatie krijgen die u nodig heeft.

We zullen de GameController configureren als de rootcontroller van de applicatie. De GameController zal een collectionView hebben waarnaar we zullen verwijzen als een IBOutlet. Een andere verwijzing is naar de IBAction onStartGame()-knop, dit is een UIButton, je kunt deze zien in het storyboard genaamd PLAY.

Iets over de implementatie van controllers:

  • Eerst initialiseren we twee hoofdobjecten: het raster: game = MemoryGame(), en een set kaarten: kaarten = [Card]().
  • We stellen de initiële variabelen in als viewDidLoad, dit is de eerste methode die wordt aangeroepen terwijl het spel draait.
  • collectionView is ingesteld op verborgen omdat alle kaarten verborgen zijn totdat de gebruiker op PLAY drukt.
  • Zodra we op PLAY drukken, start de onStartGame IBAction-sectie en stellen we de eigenschap collectionView isHidden in op false, zodat de kaarten zichtbaar kunnen worden.
  • Elke keer dat de gebruiker een kaart selecteert, wordt de methode didSelectItemAt aangeroepen. In de methode die we didSelectCard noemen, wordt de hoofdspellogica geïmplementeerd.

Hier is de uiteindelijke GameController-implementatie:

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

Laten we het nu even hebben over de belangrijke protocollen.

Protocollen

Het werken met protocollen is de kern van Swift-programmeren. Protocollen bieden de mogelijkheid om regels te definiëren voor een klasse, structuur of opsomming. Met dit principe kunt u modulaire en uitbreidbare code schrijven. In feite is dit een patroon dat we al implementeren voor collectionView in GameController. Laten we nu onze eigen versie maken. De syntaxis ziet er als volgt uit:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

We weten dat we met een protocol regels of instructies kunnen definiëren voor het implementeren van een klasse, dus laten we eens nadenken over wat die zouden moeten zijn. Je hebt er in totaal vier nodig.

  • Begin van het spel: memoryGameDidStart.
  • Je moet de kaart met de afbeelding naar beneden omdraaien: memoryGameShowCards.
  • Je moet de kaart met de afbeelding naar beneden omdraaien: memoryGameHideCards.
  • Einde van het spel: memoryGameDidEnd.

Alle vier de methoden kunnen worden geïmplementeerd voor de hoofdklasse, namelijk GameController.

geheugenGameDidStart

Wanneer deze methode wordt uitgevoerd, zou het spel moeten starten (de gebruiker drukt op PLAY). Hier laden we eenvoudigweg de inhoud opnieuw door collectionView.reloadData() aan te roepen, waardoor de kaarten worden geschud.

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

geheugenGameShowCards

We noemen deze methode vanuit collectionSDViewSelectItemAt. Eerst wordt de geselecteerde kaart weergegeven. Vervolgens wordt gecontroleerd of er een niet-overeenkomende kaart in de array cardsShown zit (als het aantal cardsShown oneven is). Als die er is, wordt de geselecteerde kaart ermee vergeleken. Als de afbeeldingen hetzelfde zijn, worden beide kaarten toegevoegd aan cardsShown en blijven ze open. Als ze verschillend zijn, laat de kaart kaarten getoond en worden beide met de beeldzijde naar beneden omgedraaid.

geheugenSpelVerbergkaarten

Als de kaarten niet overeenkomen, wordt deze methode aangeroepen en worden de kaartafbeeldingen verborgen.

getoond = onwaar.

geheugenGameDidEnd

Wanneer deze methode wordt aangeroepen, betekent dit dat alle kaarten al zijn onthuld en in de cardsShown-lijst staan: cardsShown.count = cards.count, dus het spel is afgelopen. De methode wordt specifiek aangeroepen nadat we endGame() hebben aangeroepen om de isPlaying var op false in te stellen, waarna het bericht over het einde van het spel wordt weergegeven. Ook alertController wordt gebruikt als indicator voor de controller. viewDidDisappear wordt aangeroepen en het spel wordt gereset.

Zo ziet het er allemaal uit in 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()
    }
}

Een geheugenkaartspel schrijven in Swift
Eigenlijk is dat alles. Je kunt dit project gebruiken om je eigen versie van het spel te maken.

Veel codeerplezier!

Skillbox beveelt aan:

Bron: www.habr.com

Voeg een reactie