Skryf 'n geheuekaartspeletjie in Swift

Skryf 'n geheuekaartspeletjie in Swift

Hierdie artikel beskryf die proses om 'n eenvoudige geheue-opleidingspeletjie te skep waarvan ek baie hou. Behalwe dat dit op sigself goed is, sal jy 'n bietjie meer leer oor Swift-klasse en -protokolle soos jy gaan. Maar voor ons begin, laat ons die spel self verstaan.

Ons herinner: vir alle lesers van "Habr" - 'n afslag van 10 000 roebels wanneer u inskryf vir enige Skillbox-kursus met behulp van die "Habr"-promosiekode.

Skillbox beveel aan: Opvoedkundige aanlyn kursus "Beroep Java Ontwikkelaar".

Hoe om Memory Card te speel

Die speletjie begin met 'n demonstrasie van 'n stel kaarte. Hulle lê met die gesig na onder (onderskeidelik, beeld af). Wanneer jy op enige een klik, maak die prent vir 'n paar sekondes oop.

Die speler se taak is om alle kaarte met dieselfde prente te vind. As jy, nadat jy die eerste kaart oopgemaak het, die tweede omdraai en die prente pas, bly albei kaarte oop. As hulle nie ooreenstem nie, word die kaarte weer toegemaak. Die doel is om alles oop te maak.

Projekstruktuur

Om 'n eenvoudige weergawe van hierdie speletjie te skep, benodig jy die volgende komponente:

  • Een beheerder: GameController.swift.
  • Een aansig: CardCell.swift.
  • Twee modelle: MemoryGame.swift en Card.swift.
  • Main.storyboard om te verseker dat die hele stel komponente beskikbaar is.

Ons begin met die eenvoudigste komponent van die spel, die kaarte.

Card.swift

Die kaartmodel sal drie eienskappe hê: ID om elkeen te identifiseer, 'n Boolese veranderlike wat gewys word om die status van die kaart (versteek of oop) te spesifiseer, en kunswerkURL vir die beelde op die kaarte.

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

Jy sal ook hierdie metodes nodig hê om gebruikersinteraksie met kaarte te beheer:

Metode om 'n prent op 'n kaart te vertoon. Hier stel ons alle eienskappe terug na verstek. Vir id genereer ons 'n ewekansige id deur NSUUIS().uuidString te roep.

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

Metode om ID-kaarte te vergelyk.

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

Metode om 'n kopie van elke kaart te skep - om 'n groter aantal identiese te kry. Hierdie metode sal kaart met soortgelyke waardes terugstuur.

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

En nog een metode is nodig om die kaarte aan die begin te skommel. Ons sal dit 'n uitbreiding van die Array-klas maak.

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

En hier is die implementering van die kode vir die kaartmodel met al die eienskappe en metodes.

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

Beweeg aan.

Die tweede model is MemoryGame, hier stel ons 'n 4*4-rooster in. Die model sal eienskappe hê soos kaarte ('n verskeidenheid kaarte op 'n rooster), 'n kaarte-getoon-skikking met kaarte wat reeds oop is, en 'n Boolese veranderlike is Speel om die status van die speletjie na te spoor.

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

Ons moet ook metodes ontwikkel om gebruikersinteraksie met die rooster te beheer.

'n Metode wat kaarte in 'n rooster skommel.

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

Metode om 'n nuwe speletjie te skep. Hier noem ons die eerste metode om die aanvanklike uitleg te begin en die isPlaying-veranderlike inisialiseer na waar.

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

As ons die speletjie wil herbegin, dan stel ons die isPlaying-veranderlike op vals en verwyder die aanvanklike uitleg van die kaarte.

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

Metode om geklikte kaarte te verifieer. Meer oor hom later.

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

'n Metode wat die posisie van 'n spesifieke kaart terugstuur.

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

Kontroleer die nakoming van die gekose kaart met die standaard.

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

Hierdie metode lees die laaste element in die **cardsShown**-skikking en gee die nie-ooreenstemmende kaart terug.

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 lyk so iets:

Skryf 'n geheuekaartspeletjie in Swift

Aanvanklik moet jy die nuwe speletjie as viewDidLoad in die beheerder stel, insluitend die beelde vir die rooster. In die speletjie sal dit alles verteenwoordig word deur 'n 4*4-versamelingView. As jy nog nie vertroud is met collectionView nie, hier is dit jy kan die inligting kry wat jy nodig het.

Ons sal die GameController opstel as die toepassing se wortelbeheerder. Die GameController sal 'n versamelingView hê wat ons as 'n IBOoutlet sal verwys. Nog 'n verwysing is na die IBAction onStartGame() knoppie, dit is 'n UIButton, jy kan dit sien in die storiebord genaamd PLAY.

'n Bietjie oor die implementering van beheerders:

  • Eerstens inisialiseer ons twee hoofobjekte - die rooster: spel = MemoryGame(), en 'n stel kaarte: kaarte = [Card]().
  • Ons stel die aanvanklike veranderlikes as viewDidLoad, dit is die eerste metode wat genoem word terwyl die speletjie aan die gang is.
  • collectionView is op versteek gestel omdat alle kaarte versteek is totdat die gebruiker PLAY druk.
  • Sodra ons PLAY druk, begin die onStartGame IBAction-afdeling, en ons stel die collectionView isHidden-eienskap op vals sodat die kaarte sigbaar kan word.
  • Elke keer as die gebruiker 'n kaart kies, word die didSelectItemAt-metode genoem. In die metode wat ons didSelectCard noem om die hoofspellogika te implementeer.

Hier is die finale GameController-implementering:

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

Kom ons praat nou bietjie oor die belangrike protokolle.

protokolle

Werk met protokolle is die kern van Swift-programmering. Protokolle bied die vermoë om reëls vir 'n klas, struktuur of opsomming te definieer. Hierdie beginsel laat jou toe om modulêre en uitbreidbare kode te skryf. Trouens, dit is 'n patroon wat ons reeds vir collectionView in GameController implementeer. Kom ons maak nou ons eie weergawe. Die sintaksis sal soos volg lyk:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Ons weet dat 'n protokol ons toelaat om reëls of instruksies vir die implementering van 'n klas te definieer, so kom ons dink oor wat dit moet wees. Jy benodig altesaam vier.

  • Speletjie begin: memoryGameDidStart.
  • Jy moet die kaart met die gesig na onder draai: memoryGameShowCards.
  • Jy moet die kaart met die gesig na onder draai: memoryGameHideCards.
  • Speleinde: memoryGameDidEnd.

Al vier metodes kan geïmplementeer word vir die hoofklas, wat GameController is.

memoryGameHetStart

Wanneer hierdie metode uitgevoer word, moet die speletjie begin (die gebruiker druk PLAY). Hier sal ons eenvoudig die inhoud herlaai deur collectionView.reloadData() te skakel, wat die kaarte sal skommel.

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

memoryGameShowCards

Ons noem hierdie metode vanaf collectionSDViewSelectItemAt. Eerstens wys dit die gekose kaart. Kontroleer dan of daar 'n ongeëwenaarde kaart in die kaarte-getoon-skikking is (as die aantal kaarte wat gewys word, vreemd is). As daar een is, word die gekose kaart daarmee vergelyk. As die prente dieselfde is, word albei kaarte by kaarte gevoeg wat gewys word en bly gesig na bo. Indien anders, laat die kaart kaarte wat gewys word en albei word met die gesig na onder gedraai.

memoryGameHideCards

As die kaarte nie ooreenstem nie, word hierdie metode genoem en die kaartbeelde word versteek.

getoon = onwaar.

memoryGameDidEnd

Wanneer hierdie metode genoem word, beteken dit dat alle kaarte reeds geopenbaar is en in die kaarteGetoonde lys is: cardsShown.count = cards.count, so die speletjie is verby. Die metode word spesifiek genoem nadat ons endGame() geroep het om die isPlaying var op vals te stel, waarna die speletjie-eindboodskap gewys word. AlertController word ook gebruik as 'n aanwyser vir die kontroleerder. viewDidDisappear word geroep en die speletjie word teruggestel.

Hier is hoe dit alles lyk 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()
    }
}

Skryf 'n geheuekaartspeletjie in Swift
Eintlik is dit al. Jy kan hierdie projek gebruik om jou eie weergawe van die speletjie te skep.

Gelukkige kodering!

Skillbox beveel aan:

Bron: will.com

Voeg 'n opmerking