Att skriva ett minneskortspel i Swift

Att skriva ett minneskortspel i Swift

Den här artikeln beskriver processen att skapa ett enkelt minnesträningsspel som jag verkligen gillar. Förutom att vara bra i sig kommer du att lära dig lite mer om Swift-klasser och protokoll allt eftersom. Men innan vi börjar, låt oss förstå själva spelet.

Påminnelse: för alla läsare av "Habr" - en rabatt på 10 000 rubel när du anmäler dig till någon Skillbox-kurs med hjälp av "Habr"-kampanjkoden.

Skillbox rekommenderar: Pedagogisk onlinekurs "Yrke Java-utvecklare".

Hur man spelar Memory Card

Spelet börjar med en demonstration av en uppsättning kort. De ligger med framsidan nedåt (respektive bild nedåt). När du klickar på någon öppnas bilden i några sekunder.

Spelarens uppgift är att hitta alla kort med samma bilder. Om du, efter att ha öppnat det första kortet, vänder på det andra och bilderna matchar, förblir båda korten öppna. Om de inte stämmer överens stängs korten igen. Målet är att öppna allt.

Projektets struktur

För att skapa en enkel version av detta spel behöver du följande komponenter:

  • En styrenhet: GameController.swift.
  • One View: CardCell.swift.
  • Två modeller: MemoryGame.swift och Card.swift.
  • Main.storyboard för att säkerställa att hela uppsättningen av komponenter är tillgänglig.

Vi börjar med den enklaste komponenten i spelet, korten.

Card.swift

Kortmodellen kommer att ha tre egenskaper: id för att identifiera var och en, en boolesk variabel som visas för att specificera kortets status (dold eller öppen) och artworkURL för bilderna på korten.

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

Du behöver också dessa metoder för att kontrollera användarinteraktion med kartor:

Metod för att visa en bild på ett kort. Här återställer vi alla egenskaper till standard. För id genererar vi ett slumpmässigt id genom att anropa NSUUIS().uuidString.

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

Metod för att jämföra ID-kort.

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

Metod för att skapa en kopia av varje kort - för att få ett större antal identiska. Denna metod kommer att returnera kort med liknande värden.

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

Och ytterligare en metod behövs för att blanda korten i början. Vi kommer att göra det till en förlängning av Array-klassen.

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

Och här är implementeringen av koden för kortmodellen med alla egenskaper och metoder.

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

Varsågod.

Den andra modellen är MemoryGame, här sätter vi ett 4*4 rutnät. Modellen kommer att ha egenskaper som kort (en array av kort på ett rutnät), en cardsShown-array med kort som redan är öppna och en boolesk variabel är Spelar för att spåra spelets status.

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

Vi behöver också utveckla metoder för att styra användarinteraktion med nätet.

En metod som blandar kort i ett rutnät.

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

Metod för att skapa ett nytt spel. Här kallar vi den första metoden för att starta den initiala layouten och initiera variabeln isPlaying till true.

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

Om vi ​​vill starta om spelet, sedan ställer vi in ​​variabeln isPlaying till false och tar bort den initiala layouten för korten.

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

Metod för att verifiera klickade kort. Mer om honom senare.

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

En metod som returnerar positionen för ett specifikt kort.

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

Kontrollera att det valda kortet överensstämmer med standarden.

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

Denna metod läser det sista elementet i **cardsShown**-matrisen och returnerar det icke-matchande 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 och GameController.swift

Main.storyboard ser ut ungefär så här:

Att skriva ett minneskortspel i Swift

Till en början måste du ställa in det nya spelet som viewDidLoad i kontrollern, inklusive bilderna för rutnätet. I spelet kommer allt detta att representeras av 4*4 collectionView. Om du ännu inte är bekant med collectionView, här är den du kan få den information du behöver.

Vi kommer att konfigurera GameController som applikationens rotkontroller. GameController kommer att ha en collectionView som vi kommer att referera till som en IBOutlet. En annan referens är till knappen IBAction onStartGame(), detta är en UIButton, du kan se den i storyboarden som heter PLAY.

Lite om implementeringen av kontroller:

  • Först initierar vi två huvudobjekt - rutnätet: spel = MemoryGame(), och en uppsättning kort: kort = [Card]().
  • Vi ställer in de initiala variablerna som viewDidLoad, detta är den första metoden som anropas medan spelet körs.
  • collectionView är inställt på dold eftersom alla kort är dolda tills användaren trycker på SPELA.
  • Så fort vi trycker på SPELA startar avsnittet onStartGame IBAction och vi ställer in egenskapen collectionView isHidden på false så att korten kan bli synliga.
  • Varje gång användaren väljer ett kort anropas didSelectItemAt-metoden. I metoden kallar vi didSelectCard för att implementera huvudspelets logik.

Här är den sista 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)
    }
}

Låt oss nu prata lite om de viktiga protokollen.

protokoll

Att arbeta med protokoll är kärnan i Swift-programmering. Protokoll ger möjlighet att definiera regler för en klass, struktur eller uppräkning. Denna princip låter dig skriva modulär och utbyggbar kod. I själva verket är detta ett mönster som vi redan implementerar för collectionView i GameController. Låt oss nu göra vår egen version. Syntaxen kommer att se ut så här:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Vi vet att ett protokoll låter dig definiera regler eller instruktioner för att implementera en klass, så låt oss fundera på vad de ska vara. Du behöver fyra totalt.

  • Spelstart: memoryGameDidStart.
  • Du måste vända kortet med framsidan nedåt: memoryGameShowCards.
  • Du måste vända kortet med framsidan nedåt: memoryGameHideCards.
  • Spelslut: memoryGameDidEnd.

Alla fyra metoderna kan implementeras för huvudklassen, som är GameController.

memoryGameDidStart

När denna metod körs bör spelet starta (användaren trycker på SPELA). Här laddar vi helt enkelt om innehållet genom att anropa collectionView.reloadData(), vilket kommer att blanda korten.

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

memoryGameShowCards

Vi kallar denna metod från collectionSDViewSelectItemAt. Först visar det det valda kortet. Kontrollerar sedan om det finns ett omatchat kort i arrayen CardsShown (om antalet kort som visas är udda). Om det finns ett, jämförs det valda kortet med det. Om bilderna är desamma läggs båda korten till korten som visas och förblir med framsidan uppåt. Om olika, lämnar kortet Visade kort och båda vänds nedåt.

memoryGameHideCards

Om korten inte matchar, anropas denna metod och kortbilderna döljs.

visat = falskt.

memoryGameDidEnd

När den här metoden anropas betyder det att alla kort redan är avslöjade och finns i listan över kort som visas: cardsShown.count = cards.count, så spelet är över. Metoden anropas specifikt efter att vi har anropat endGame() för att ställa in isPlaying var till false, varefter spelets slutmeddelande visas. Även alertController används som en indikator för styrenheten. viewDidDisappear anropas och spelet återställs.

Så här 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()
    }
}

Att skriva ett minneskortspel i Swift
Det är faktiskt allt. Du kan använda det här projektet för att skapa din egen version av spelet.

Glad kodning!

Skillbox rekommenderar:

Källa: will.com

Lägg en kommentar