📜 ⬆️ ⬇️

We write the game "Memory Cards" on Swift



This article describes the process of creating a simple memory training game that I really like. In addition to being good in itself, you will learn a little more about Swift classes and protocols while working. But before we begin, let's understand the game itself.

We remind: for all readers of "Habr" - a discount of 10,000 rubles when writing to any Skillbox course on the promotional code "Habr".

Skillbox recommends: Online Profession Java Developer Educational Course.

How to play Memory Card


The game begins with a demonstration of a set of cards. They are "shirt" up (respectively, the image down). When you click on any, an image opens for a few seconds.
')
The task of the player is to find all the cards with the same pictures. If, after opening the first card, you turn the second one and the pictures are the same, both cards remain open. If they do not match, the cards close again. The challenge is to discover everything.

Project structure


In order to create a simple version of this game you need the following components:


We start with the simplest component of the game, the card.

Card.swift

The card model will have three properties: id to identify each, the logical variable shown to clarify the status of the card (hidden or open) and artworkURL for pictures on the cards.

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

You will also need these methods to manage user interaction with maps:

Method to display the image on the card. Here we reset all properties to default. For id, generate a random id by calling NSUUIS (). UuidString.

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

Method for comparing id cards.

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

Method to create a copy of each card - in order to get a greater number of the same. This method will return a card with similar values.

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

And one more method is needed to shuffle the cards at the start. We will make it an extension of the Array class.
 extension Array { mutating func shuffle() { for _ in 0...self.count { sort { (_,_) in arc4random() < arc4random() } } } } 

But the implementation of the code for the Card model with all the properties and methods.

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

Go ahead.

The second model is MemoryGame, here we set the 4 * 4 grid. The model will have such properties as cards (an array of cards on the grid), an array of cardsShown with already opened cards and a logical variable isPlaying to track the status of the game.

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

We also need to develop methods for managing user interaction with the grid.

A method that shuffles the cards in the grid.

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

Method to create a new game. Here we call the first method to start the initial layout and initialize the isPlaying variable as true.
 func newGame(cardsArray:[Card]) -> [Card] { cards = shuffleCards(cards: cardsArray) isPlaying = true return cards } 

If we want to restart the game, then we set the isPlaying variable to false and remove the initial layout of the cards.

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

Method for verification of pressed cards. More about him later.

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

Method that returns the position of a particular card.

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

Verification of compliance with the selected card standard.

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

This method reads the last element in the ** cardsShown ** array and returns an inappropriate card.

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


Main.storyboard looks like this:



Initially, the controller needs to install a new game as viewDidLoad, including images for the grid. In the game, all this will be represented by 4 * 4 collectionView. If you are not familiar with collectionView, you can get the necessary information here .

We will configure the GameController as the root controller of the application. In GameController there will be a collectionView, which we will refer to as an IBOutlet. Another link is to the IBAction onStartGame () button, this is a UIButton, you can see it in the storyboard called PLAY.

A little bit about the implementation of controllers:


Here is the final implementation of 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) } } 

Now let's dwell on the important protocols.

Protocols


Working with protocols is the basis of Swift programming. Protocols provide the ability to set rules for a class, structure, or enumeration. This principle allows you to write modular and extensible code. In fact, this is a template that we will already implement for collection View in GameController. Now we will make our own version. The syntax will look like this:

 protocol MemoryGameProtocol { //protocol definition goes here } 

We know that the protocol allows you to define rules or instructions for the implementation of a class, so let's think about what they should be. All you need is four.


All four methods are implemented for the main class, and this is GameController.

memoryGameDidStart


When this method is started, the game should start (the user presses PLAY). Here we simply reload the content by calling collectionView.reloadData (), which will cause the maps to shuffle.

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

memoryGameShowCards


Call this method from collectionSDViewSelectItemAt. First, it shows the selected map. Then it checks if there is an unmatched card in the cardsShown array (if the number of cardsShown is odd). If there is one, the selected card is compared with it. If the pictures are the same, both cards are added to cardsShown and remain open. If different, the card leaves cardsShown, and both are turned upside down.

memoryGameHideCards


If the cards do not match, this method is called, and the pictures of the cards are hidden.

shown = false.

memoryGameDidEnd


When this method is called, it means that all cards are already open and are on the list of cardsShown: cardsShown.count = cards.count, so the game is over. The method is invoked specifically after we called endGame () to set isPlaying var to false, after which the game completion message is displayed. Also alertController is used as an indicator for the controller. The viewDidDisappear is called and the game is reset.

Here is how it all looks 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() } } 


Actually, that's all. You can use this project to create your own version of the game.

Good coding!

Skillbox recommends:

Source: https://habr.com/ru/post/447598/


All Articles