Skrive et minnekortspill i Swift

Skrive et minnekortspill i Swift

Denne artikkelen beskriver prosessen med å lage et enkelt minnetreningsspill som jeg virkelig liker. Foruten å være god i seg selv, vil du lære litt mer om Swift-klasser og protokoller etter hvert. Men før vi begynner, la oss forstå selve spillet.

Vi minner om: for alle lesere av "Habr" - en rabatt på 10 000 rubler når du melder deg på et hvilket som helst Skillbox-kurs ved å bruke kampanjekoden "Habr".

Skillbox anbefaler: Pedagogisk nettkurs "Profession Java-utvikler".

Hvordan spille Memory Card

Spillet begynner med en demonstrasjon av et sett med kort. De ligger med forsiden ned (henholdsvis bildet ned). Når du klikker på en, åpnes bildet i noen sekunder.

Spillerens oppgave er å finne alle kortene med de samme bildene. Hvis du, etter å ha åpnet det første kortet, snur det andre og bildene stemmer overens, forblir begge kortene åpne. Hvis de ikke stemmer overens, lukkes kortene igjen. Målet er å åpne alt.

Prosjektstruktur

For å lage en enkel versjon av dette spillet trenger du følgende komponenter:

  • Én kontroller: GameController.swift.
  • One View: CardCell.swift.
  • To modeller: MemoryGame.swift og Card.swift.
  • Main.storyboard for å sikre at hele settet med komponenter er tilgjengelig.

Vi starter med den enkleste komponenten i spillet, kortene.

Card.swift

Kortmodellen vil ha tre egenskaper: id for å identifisere hver enkelt, en boolsk variabel vist for å spesifisere statusen til kortet (skjult eller åpen), og artworkURL for bildene på kortene.

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

Du trenger også disse metodene for å kontrollere brukerinteraksjon med kart:

Metode for å vise et bilde på et kort. Her tilbakestiller vi alle egenskaper til standard. For id genererer vi en tilfeldig id ved å kalle NSUUIS().uuidString.

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

Metode for å sammenligne ID-kort.

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

Metode for å lage en kopi av hvert kort - for å få et større antall identiske. Denne metoden vil returnere kort med lignende verdier.

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

Og en metode til er nødvendig for å blande kortene i starten. Vi vil gjøre det til en utvidelse av Array-klassen.

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

Og her er implementeringen av koden for kortmodellen med alle egenskapene og metodene.

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

Gå videre.

Den andre modellen er MemoryGame, her setter vi et 4*4 rutenett. Modellen vil ha egenskaper som kort (en matrise med kort på et rutenett), en cardsShown-matrise med kort som allerede er åpne, og en boolsk variabel er Playing for å spore statusen til spillet.

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

Vi må også utvikle metoder for å kontrollere brukerinteraksjon med nettet.

En metode som blander kort i et rutenett.

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

Metode for å lage et nytt spill. Her kaller vi den første metoden for å starte den innledende layouten og initialisere isPlaying-variabelen til true.

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

Hvis vi vil starte spillet på nytt, så setter vi isPlaying-variabelen til false og fjerner den opprinnelige layouten til kortene.

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

Metode for å bekrefte klikkede kort. Mer om ham senere.

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

En metode som returnerer posisjonen til et bestemt kort.

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

Kontrollere at det valgte kortet samsvarer med standarden.

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

Denne metoden leser det siste elementet i **cardsShown**-matrisen og returnerer det ikke-matchende kortet.

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

Main.storyboard ser omtrent slik ut:

Skrive et minnekortspill i Swift

Til å begynne med må du sette det nye spillet som viewDidLoad i kontrolleren, inkludert bildene for rutenettet. I spillet vil alt dette bli representert av en 4*4 samlingsvisning. Hvis du ennå ikke er kjent med collectionView, er den her du kan få den informasjonen du trenger.

Vi vil konfigurere GameController som applikasjonens rotkontroller. GameController vil ha en samlingsvisning som vi vil referere til som en IBOutlet. En annen referanse er til IBAction onStartGame()-knappen, dette er en UIButton, du kan se den i storyboardet som heter PLAY.

Litt om implementeringen av kontrollere:

  • Først initialiserer vi to hovedobjekter - rutenettet: spill = MemoryGame(), og et sett med kort: kort = [Card]().
  • Vi setter startvariablene som viewDidLoad, dette er den første metoden som kalles mens spillet kjører.
  • collectionView er satt til skjult fordi alle kortene er skjult til brukeren trykker PLAY.
  • Så snart vi trykker PLAY starter onStartGame IBAction-delen, og vi setter egenskapen collectionView isHidden til false slik at kortene kan bli synlige.
  • Hver gang brukeren velger et kort, kalles didSelectItemAt-metoden. I metoden kaller vi didSelectCard for å implementere hovedspilllogikken.

Her er den endelige GameController-implementeringen:

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

La oss nå snakke litt om de viktige protokollene.

protokoller

Arbeid med protokoller er kjernen i Swift-programmering. Protokoller gir muligheten til å definere regler for en klasse, struktur eller oppregning. Dette prinsippet lar deg skrive modulær og utvidbar kode. Faktisk er dette et mønster som vi allerede implementerer for collectionView i GameController. La oss nå lage vår egen versjon. Syntaksen vil se slik ut:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Vi vet at en protokoll lar oss definere regler eller instruksjoner for implementering av en klasse, så la oss tenke på hva de skal være. Du trenger fire totalt.

  • Spillstart: memoryGameDidStart.
  • Du må snu kortet med forsiden ned: memoryGameShowCards.
  • Du må snu kortet med forsiden ned: memoryGameHideCards.
  • Spillslutt: memoryGameDidEnd.

Alle fire metodene kan implementeres for hovedklassen, som er GameController.

memoryGameDidStart

Når denne metoden kjøres, skal spillet starte (brukeren trykker PLAY). Her laster vi ganske enkelt innholdet på nytt ved å ringe collectionView.reloadData(), som vil blande kortene.

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

memoryGameShowCards

Vi kaller denne metoden fra collectionSDViewSelectItemAt. Først viser den det valgte kortet. Sjekker deretter om det er et uovertruffent kort i kortene som vises (hvis antallet kort som vises er oddetall). Hvis det er en, sammenlignes det valgte kortet med det. Hvis bildene er like, legges begge kortene til kortene som vises og forblir med forsiden opp. Hvis det er forskjellig, etterlater kortet de viste kortene og begge vendes med forsiden ned.

memoryGameHideCards

Hvis kortene ikke stemmer, kalles denne metoden og kortbildene skjules.

vist = usant.

memoryGameDidEnd

Når denne metoden kalles, betyr det at alle kortene allerede er avslørt og er på listen over kort som vises: cardsShown.count = cards.count, så spillet er over. Metoden kalles spesifikt etter at vi har kalt endGame() for å sette isPlaying var til false, hvoretter meldingen om sluttspill vises. Også alertController brukes som en indikator for kontrolleren. viewDidDisappear kalles og spillet tilbakestilles.

Slik ser det ut i 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()
    }
}

Skrive et minnekortspill i Swift
Egentlig er det alt. Du kan bruke dette prosjektet til å lage din egen versjon av spillet.

Lykke til med koding!

Skillbox anbefaler:

Kilde: www.habr.com

Legg til en kommentar