Escribir un xogo de tarxetas de memoria en Swift

Escribir un xogo de tarxetas de memoria en Swift

Este artigo describe o proceso de creación dun xogo sinxelo de adestramento da memoria que me gusta moito. Ademais de ser bo en si mesmo, aprenderás un pouco máis sobre as clases e os protocolos de Swift a medida que vaias. Pero antes de comezar, imos entender o xogo en si.

Recordámolo: para todos os lectores de "Habr" - un desconto de 10 rublos ao inscribirse en calquera curso de Skillbox usando o código promocional "Habr".

Skillbox recomenda: Curso educativo online "Profesional Java Developer".

Como xogar a tarxeta de memoria

O xogo comeza cunha demostración dun conxunto de cartas. Déitanse boca abaixo (respectivamente, imaxe para abaixo). Cando fai clic en calquera, a imaxe ábrese durante uns segundos.

A tarefa do xogador é atopar todas as cartas coas mesmas imaxes. Se despois de abrir a primeira tarxeta, dás a volta á segunda e as imaxes coinciden, ambas as tarxetas permanecen abertas. Se non coinciden, pecharanse de novo as tarxetas. O obxectivo é abrir todo.

Estrutura do proxecto

Para crear unha versión sinxela deste xogo necesitas os seguintes compoñentes:

  • Un controlador: GameController.swift.
  • Unha vista: CardCell.swift.
  • Dous modelos: MemoryGame.swift e Card.swift.
  • Main.storyboard para garantir que todo o conxunto de compoñentes está dispoñible.

Comezamos polo compoñente máis sinxelo do xogo, as cartas.

Tarxeta.swift

O modelo de tarxeta terá tres propiedades: id para identificar cada unha, unha variable booleana mostrada para especificar o estado da tarxeta (oculta ou aberta) e artworkURL para as imaxes das tarxetas.

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

Tamén necesitará estes métodos para controlar a interacción do usuario cos mapas:

Método para mostrar unha imaxe nunha tarxeta. Aquí restablecemos todas as propiedades por defecto. Para id, xeramos un id aleatorio chamando a NSUUIS().uuidString.

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

Método de comparación de DNI.

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

Método para crear unha copia de cada tarxeta - para conseguir un maior número de idénticos. Este método devolverá tarxetas con valores similares.

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

E é necesario un método máis para barallar as cartas ao comezo. Convertémolo nunha extensión da clase Array.

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

E aquí está a implementación do código para o modelo de Tarxeta con todas as propiedades e métodos.

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

Vai adiante

O segundo modelo é MemoryGame, aquí establecemos unha cuadrícula 4*4. O modelo terá propiedades como tarxetas (unha matriz de cartas nunha cuadrícula), unha matriz cardsShown coas tarxetas xa abertas e unha variable booleana isPlaying para seguir o estado do xogo.

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

Tamén necesitamos desenvolver métodos para controlar a interacción do usuario coa rede.

Un método que baralla as cartas nunha grella.

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

Método para crear un novo xogo. Aquí chamamos ao primeiro método para iniciar o deseño inicial e inicializar a variable isPlaying en verdadeiro.

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

Se queremos reiniciar o xogo, despois establecemos a variable isPlaying en false e eliminamos a disposición inicial das cartas.

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

Método para verificar as tarxetas nas que se fixo clic. Máis sobre el máis tarde.

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

Un método que devolve a posición dunha tarxeta específica.

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

Comprobación da conformidade da tarxeta seleccionada coa norma.

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

Este método le o último elemento da matriz **cardsShown** e devolve a tarxeta que non coincide.

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

Main.storyboard parece algo así:

Escribir un xogo de tarxetas de memoria en Swift

Inicialmente, debes configurar o novo xogo como viewDidLoad no controlador, incluídas as imaxes para a grella. No xogo, todo isto estará representado por 4*4 collectionView. Se aínda non estás familiarizado con collectionView, aquí está podes obter a información que necesites.

Configuraremos o GameController como controlador raíz da aplicación. O GameController terá unha collectionView á que faremos referencia como IBOutlet. Outra referencia é ao botón IBAction onStartGame(), este é un UIButton, podes velo no guión gráfico chamado PLAY.

Un pouco sobre a implementación dos controladores:

  • En primeiro lugar, inicializamos dous obxectos principais: a grella: xogo = Xogo de memoria() e un conxunto de tarxetas: tarxetas = [Tarxeta]().
  • Establecemos as variables iniciais como viewDidLoad, este é o primeiro método que se chama mentres o xogo está en execución.
  • collectionView está configurado como oculto porque todas as tarxetas están ocultas ata que o usuario preme REPRODUCIR.
  • En canto prememos PLAY, comeza a sección IBAction onStartGame e establecemos a propiedade collectionView isHidden como false para que as tarxetas poidan facerse visibles.
  • Cada vez que o usuario selecciona unha tarxeta, chámase ao método didSelectItemAt. No método que chamamos didSelectCard para implementar a lóxica do xogo principal.

Aquí está a implementación 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)
    }
}

Agora imos falar un pouco dos protocolos importantes.

Protocolos

Traballar con protocolos é o núcleo da programación de Swift. Os protocolos proporcionan a capacidade de definir regras para unha clase, estrutura ou enumeración. Este principio permítelle escribir código modular e extensible. De feito, este é un patrón que xa estamos implementando para collectionView en GameController. Agora imos facer a nosa propia versión. A sintaxe terá o seguinte aspecto:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Sabemos que un protocolo permítenos definir regras ou instrucións para implementar unha clase, así que pensemos cales deberían ser. Necesitas catro en total.

  • Inicio do xogo: memoryGameDidStart.
  • Debes xirar a tarxeta cara abaixo: memoryGameShowCards.
  • Debes xirar a tarxeta cara abaixo: memoryGameHideCards.
  • Fin do xogo: memoryGameDidEnd.

Os catro métodos pódense implementar para a clase principal, que é GameController.

memoryGameDidStart

Cando se executa este método, o xogo debería comezar (o usuario preme PLAY). Aquí simplemente recargaremos o contido chamando a collectionView.reloadData(), que barallará as tarxetas.

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

MemoryGameShowCards

Chamamos a este método desde collectionSDViewSelectItemAt. Primeiro mostra a tarxeta seleccionada. Despois comproba se hai unha tarxeta non coincidente na matriz cardsShown (se o número de cardsShown é impar). Se hai un, a tarxeta seleccionada compárase con ela. Se as imaxes son iguais, as dúas tarxetas engádense ás tarxetas mostradas e permanecen boca arriba. Se é diferente, a tarxeta deixa as tarxetas mostradas e ambas bótanse boca abaixo.

MemoryGameHideCards

Se as tarxetas non coinciden, chámase a este método e escóndense as imaxes das tarxetas.

mostrado = falso.

memoryGameDidEnd

Cando se chama este método, significa que todas as cartas xa están reveladas e están na lista cardsShown: cardsShown.count = cards.count, polo que o xogo rematou. O método chámase especificamente despois de chamar a endGame() para establecer a isPlaying var como false, despois de que se mostra a mensaxe de finalización do xogo. Tamén alertController úsase como indicador para o controlador. chámase viewDidDisappear e restablece o xogo.

Aquí tes como se ve todo en 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()
    }
}

Escribir un xogo de tarxetas de memoria en Swift
En realidade, iso é todo. Podes usar este proxecto para crear a túa propia versión do xogo.

Feliz codificación!

Skillbox recomenda:

Fonte: www.habr.com

Engadir un comentario