Escriure un joc de targetes de memòria a Swift

Escriure un joc de targetes de memòria a Swift

Aquest article descriu el procés de creació d'un joc d'entrenament de memòria senzill que m'agrada molt. A més de ser bo en si mateix, aprendreu una mica més sobre les classes i els protocols de Swift a mesura que aneu. Però abans de començar, entenem el joc en si.

Recordem: per a tots els lectors de "Habr": un descompte de 10 rubles en inscriure's a qualsevol curs de Skillbox amb el codi promocional "Habr".

Skillbox recomana: Curs educatiu en línia "Desenvolupador Java professional".

Com jugar a la targeta de memòria

El joc comença amb una demostració d'un joc de cartes. Es troben boca avall (respectivament, imatge avall). Quan feu clic a qualsevol, la imatge s'obre durant uns segons.

La tasca del jugador és trobar totes les cartes amb les mateixes imatges. Si després d'obrir la primera targeta, gireu la segona i les imatges coincideixen, ambdues cartes romandran obertes. Si no coincideixen, les cartes es tornen a tancar. L'objectiu és obrir-ho tot.

Estructura del projecte

Per crear una versió senzilla d'aquest joc, necessiteu els components següents:

  • Un controlador: GameController.swift.
  • Una vista: CardCell.swift.
  • Dos models: MemoryGame.swift i Card.swift.
  • Main.storyboard per garantir que tot el conjunt de components estigui disponible.

Comencem amb el component més senzill del joc, les cartes.

Card.swift

El model de targeta tindrà tres propietats: id per identificar cadascuna, una variable booleana mostrada per especificar l'estat de la targeta (ocult o oberta) i artworkURL per a les imatges de les targetes.

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

També necessitareu aquests mètodes per controlar la interacció de l'usuari amb els mapes:

Mètode per mostrar una imatge en una targeta. Aquí restablirem totes les propietats per defecte. Per a l'identificador, generem un identificador aleatori cridant a NSUUIS().uuidString.

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

Mètode de comparació de DNI.

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

Mètode per crear una còpia de cada targeta - per aconseguir un major nombre d'idèntics. Aquest mètode retornarà una targeta amb valors similars.

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

I cal un mètode més per barrejar les cartes al principi. La convertirem en una extensió de la classe Array.

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

I aquí teniu la implementació del codi per al model Card amb totes les propietats i mètodes.

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

Segueix endavant

El segon model és MemoryGame, aquí establim una graella de 4*4. El model tindrà propietats com ara cartes (una matriu de cartes en una graella), una matriu cardsShown amb cartes ja obertes i una variable booleana isPlaying per fer un seguiment de l'estat del joc.

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

També hem de desenvolupar mètodes per controlar la interacció de l'usuari amb la xarxa.

Un mètode que barreja cartes en una graella.

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

Mètode per crear un nou joc. Aquí anomenem el primer mètode per iniciar el disseny inicial i inicialitzar la variable isPlaying a true.

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

Si volem reiniciar el joc, després establim la variable isPlaying a false i eliminem la disposició inicial de les cartes.

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

Mètode per verificar les targetes clicades. Més sobre ell més endavant.

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

Un mètode que retorna la posició d'una targeta específica.

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

Comprovació de la conformitat de la targeta seleccionada amb la norma.

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

Aquest mètode llegeix l'últim element de la matriu **cardsShown** i retorna la targeta que no coincideix.

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

Main.storyboard té un aspecte semblant a això:

Escriure un joc de targetes de memòria a Swift

Inicialment, heu de configurar el nou joc com a viewDidLoad al controlador, incloses les imatges per a la graella. Al joc, tot això estarà representat per 4*4 collectionView. Si encara no esteu familiaritzat amb collectionView, aquí el teniu podeu obtenir la informació que necessiteu.

Configurarem el GameController com a controlador arrel de l'aplicació. El GameController tindrà una collectionView a la qual farem referència com a IBOutlet. Una altra referència és al botó IBAction onStartGame(), aquest és un UIButton, el podeu veure al guió gràfic anomenat PLAY.

Una mica sobre la implementació dels controladors:

  • Primer, inicialitzem dos objectes principals: la quadrícula: joc = Joc de memòria() i un conjunt de cartes: targetes = [Targeta]().
  • Establem les variables inicials com viewDidLoad, aquest és el primer mètode que es crida mentre el joc s'executa.
  • collectionView està configurat com a amagat perquè totes les cartes estan amagades fins que l'usuari prem PLAY.
  • Tan bon punt premem PLAY, s'inicia la secció IBAction onStartGame i establim la propietat collectionView isHidden en false perquè les cartes puguin ser visibles.
  • Cada vegada que l'usuari selecciona una targeta, es crida al mètode didSelectItemAt. En el mètode que anomenem didSelectCard per implementar la lògica principal del joc.

Aquí teniu la implementació final de GameController:

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

Ara parlem una mica dels protocols importants.

Protocols

Treballar amb protocols és el nucli de la programació Swift. Els protocols ofereixen la capacitat de definir regles per a una classe, estructura o enumeració. Aquest principi us permet escriure codi modular i extensible. De fet, aquest és un patró que ja estem implementant per a collectionView a GameController. Ara fem la nostra pròpia versió. La sintaxi serà així:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Sabem que un protocol ens permet definir regles o instruccions per implementar una classe, així que pensem quines haurien de ser. Necessites quatre en total.

  • Inici del joc: memoryGameDidStart.
  • Heu de girar la targeta cap avall: memoryGameShowCards.
  • Heu de girar la targeta cap avall: memoryGameHideCards.
  • Final del joc: memoryGameDidEnd.

Els quatre mètodes es poden implementar per a la classe principal, que és GameController.

memoryGameDidStart

Quan s'executa aquest mètode, el joc hauria de començar (l'usuari prem PLAY). Aquí simplement tornarem a carregar el contingut cridant a collectionView.reloadData(), que barrejarà les targetes.

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

MemoryGameShowCards

Anomenem aquest mètode des de la col·lecció SDViewSelectItemAt. Primer mostra la targeta seleccionada. A continuació, comprova si hi ha una targeta no coincident a la matriu cardsShown (si el nombre de cardsShown és senar). Si n'hi ha una, es compara la targeta seleccionada amb ella. Si les imatges són les mateixes, les dues cartes s'afegeixen a les targetes Mostrades i es mantenen boca amunt. Si és diferent, la carta deixa cartes Mostrades i totes dues es tornen cap avall.

memoryGameHideCards

Si les targetes no coincideixen, s'anomena aquest mètode i s'amaguen les imatges de les targetes.

mostrat = fals.

memoryGameDidEnd

Quan es crida aquest mètode, vol dir que totes les cartes ja estan revelades i estan a la llista cardsShown: cardsShown.count = cards.count, de manera que el joc s'ha acabat. El mètode es crida específicament després d'haver cridat endGame() per establir la variable isPlaying a false, després de la qual cosa es mostra el missatge de finalització del joc. També alertController s'utilitza com a indicador per al controlador. Es crida a viewDidDisappear i es restableix el joc.

A continuació es mostra com es veu tot a 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()
    }
}

Escriure un joc de targetes de memòria a Swift
De fet, això és tot. Podeu utilitzar aquest projecte per crear la vostra pròpia versió del joc.

Feliç codificació!

Skillbox recomana:

Font: www.habr.com

Afegeix comentari