Écrire un jeu de cartes mémoire dans Swift

Écrire un jeu de cartes mémoire dans Swift

Cet article décrit le processus de création d'un jeu simple d'entraînement de mémoire que j'apprécie vraiment. En plus d'être bon en soi, vous en apprendrez un peu plus sur les classes et les protocoles Swift au fur et à mesure. Mais avant de commencer, comprenons le jeu lui-même.

Nous rappelons: pour tous les lecteurs de "Habr" - une remise de 10 000 roubles lors de l'inscription à n'importe quel cours Skillbox en utilisant le code promotionnel "Habr".

Skillbox vous recommande : Cours éducatif en ligne "Développeur Java Métier".

Comment jouer à la carte mémoire

Le jeu commence par une démonstration d'un jeu de cartes. Ils se couchent face contre terre (respectivement, image vers le bas). Lorsque vous cliquez sur l'un d'entre eux, l'image s'ouvre pendant quelques secondes.

La tâche du joueur est de trouver toutes les cartes avec les mêmes images. Si, après avoir ouvert la première carte, vous retournez la seconde et que les images correspondent, les deux cartes restent ouvertes. Si elles ne correspondent pas, les cartes sont refermées. Le but est de tout ouvrir.

Structure du projet

Pour créer une version simple de ce jeu, vous avez besoin des composants suivants :

  • Un contrôleur : GameController.swift.
  • Une vue : CardCell.swift.
  • Deux modèles : MemoryGame.swift et Card.swift.
  • Main.storyboard pour garantir que l’ensemble des composants est disponible.

Nous commençons par l’élément le plus simple du jeu, les cartes.

Carte.swift

Le modèle de carte aura trois propriétés : un identifiant pour identifier chacun d'entre eux, une variable booléenne affichée pour spécifier l'état de la carte (cachée ou ouverte) et une artworkURL pour les images sur les cartes.

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

Vous aurez également besoin de ces méthodes pour contrôler l'interaction des utilisateurs avec les cartes :

Méthode d'affichage d'une image sur une carte. Ici, nous réinitialisons toutes les propriétés par défaut. Pour l'identifiant, nous générons un identifiant aléatoire en appelant NSUUIS().uuidString.

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

Méthode de comparaison des cartes d'identité.

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

Méthode pour créer une copie de chaque carte - afin d'en obtenir un plus grand nombre d'identiques. Cette méthode renverra une carte avec des valeurs similaires.

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

Et il faut encore une méthode pour mélanger les cartes au départ. Nous allons en faire une extension de la classe Array.

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

Et voici l'implémentation du code du modèle Card avec toutes les propriétés et méthodes.

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

Allez-y.

Le deuxième modèle est MemoryGame, ici nous définissons une grille 4*4. Le modèle aura des propriétés telles que Cards (un tableau de cartes sur une grille), un tableau CardsShown avec des cartes déjà ouvertes et une variable booléenne isPlaying pour suivre l'état du jeu.

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

Nous devons également développer des méthodes pour contrôler l’interaction des utilisateurs avec la grille.

Une méthode qui mélange les cartes dans une grille.

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

Méthode pour créer un nouveau jeu. Ici, nous appelons la première méthode pour démarrer la mise en page initiale et initialiser la variable isPlaying à true.

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

Si nous voulons redémarrer le jeu, puis nous définissons la variable isPlaying sur false et supprimons la disposition initiale des cartes.

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

Méthode de vérification des cartes cliquées. Nous en parlerons plus tard.

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

Une méthode qui renvoie la position d'une carte spécifique.

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

Vérification de la conformité de la carte sélectionnée à la norme.

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

Cette méthode lit le dernier élément du tableau **cardsShown** et renvoie la carte qui ne correspond pas.

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

Main.storyboard ressemble à ceci :

Écrire un jeu de cartes mémoire dans Swift

Initialement, vous devez définir le nouveau jeu comme viewDidLoad dans le contrôleur, y compris les images de la grille. Dans le jeu, tout cela sera représenté par 4*4 collectionView. Si vous n'êtes pas encore familier avec collectionView, le voici vous pouvez obtenir les informations dont vous avez besoin.

Nous allons configurer le GameController comme contrôleur racine de l'application. Le GameController aura une collectionView que nous référencerons comme un IBOutlet. Une autre référence concerne le bouton IBAction onStartGame(), il s'agit d'un UIButton, vous pouvez le voir dans le storyboard appelé PLAY.

Un peu sur l'implémentation des contrôleurs :

  • Tout d'abord, nous initialisons deux objets principaux - la grille : game = MemoryGame() et un jeu de cartes : cards = [Card]().
  • Nous définissons les variables initiales comme viewDidLoad, c'est la première méthode appelée pendant l'exécution du jeu.
  • collectionView est défini sur caché car toutes les cartes sont masquées jusqu'à ce que l'utilisateur appuie sur PLAY.
  • Dès que nous appuyons sur PLAY, la section onStartGame IBAction démarre et nous définissons la propriété collectionView isHidden sur false afin que les cartes puissent devenir visibles.
  • Chaque fois que l'utilisateur sélectionne une carte, la méthode didSelectItemAt est appelée. Dans la méthode, nous appelons didSelectCard pour implémenter la logique principale du jeu.

Voici l'implémentation finale 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)
    }
}

Parlons maintenant un peu des protocoles importants.

Protocoles

Travailler avec des protocoles est au cœur de la programmation Swift. Les protocoles offrent la possibilité de définir des règles pour une classe, une structure ou une énumération. Ce principe permet d'écrire du code modulaire et extensible. En fait, il s'agit d'un modèle que nous implémentons déjà pour collectionView dans GameController. Créons maintenant notre propre version. La syntaxe ressemblera à ceci :

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Nous savons qu'un protocole nous permet de définir des règles ou des instructions pour implémenter une classe, réfléchissons donc à ce qu'elles devraient être. Il vous en faut quatre au total.

  • Début du jeu : memoryGameDidStart.
  • Vous devez retourner la carte face cachée : memoryGameShowCards.
  • Vous devez retourner la carte face cachée : memoryGameHideCards.
  • Fin du jeu : memoryGameDidEnd.

Les quatre méthodes peuvent être implémentées pour la classe principale, GameController.

mémoireJeuDidStart

Lorsque cette méthode est exécutée, le jeu devrait démarrer (l'utilisateur appuie sur PLAY). Ici, nous allons simplement recharger le contenu en appelant collectionView.reloadData(), ce qui mélangera les cartes.

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

mémoireGameShowCards

Nous appelons cette méthode depuis collectionSDViewSelectItemAt. Tout d'abord, il montre la carte sélectionnée. Vérifie ensuite s'il y a une carte sans correspondance dans le tableau CardsShown (si le nombre de CardsShown est impair). S'il y en a une, la carte sélectionnée lui est comparée. Si les images sont les mêmes, les deux cartes sont ajoutées aux cartes affichées et restent face visible. Si elle est différente, la carte laisse les cartes affichées et les deux sont retournées face cachée.

mémoireGameHideCards

Si les cartes ne correspondent pas, cette méthode est appelée et les images des cartes sont masquées.

montré = faux.

mémoireJeuDidEnd

Lorsque cette méthode est appelée, cela signifie que toutes les cartes sont déjà révélées et se trouvent dans la liste cardsShown : cardsShown.count = cards.count, donc la partie est terminée. La méthode est appelée spécifiquement après que nous ayons appelé endGame() pour définir la variable isPlaying sur false, après quoi le message de fin de jeu s'affiche. alertController est également utilisé comme indicateur pour le contrôleur. viewDidDislessly est appelé et le jeu est réinitialisé.

Voici à quoi tout cela ressemble dans 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()
    }
}

Écrire un jeu de cartes mémoire dans Swift
En fait, c'est tout. Vous pouvez utiliser ce projet pour créer votre propre version du jeu.

Bon codage !

Skillbox vous recommande :

Source: habr.com

Ajouter un commentaire