用 Swift 编写记忆卡游戏

用 Swift 编写记忆卡游戏

本文描述了创建我非常喜欢的简单记忆训练游戏的过程。 除了本身的优势之外,您还将在学习过程中了解更多有关 Swift 类和协议的知识。 但在开始之前,让我们先了解一下游戏本身。

我们提醒: 对于“Habr”的所有读者 - 使用“Habr”促销代码注册任何 Skillbox 课程可享受 10 卢布的折扣。

技能箱推荐: 教育在线课程 《职业Java开发人员》.

如何玩存储卡

游戏开始时展示一组纸牌。 他们面朝下躺着(分别是图像朝下)。 当您单击任何一个时,图像会打开几秒钟。

玩家的任务是找到所有具有相同图片的卡片。 如果打开第一张卡片后,翻开第二张卡片并且图片匹配,则两张卡片都保持打开状态。 如果不匹配,卡片将再次关闭。 目标是打开一切。

项目结构

要创建该游戏的简单版本,您需要以下组件:

  • 一个控制器:GameController.swift。
  • 一种视图:CardCell.swift。
  • 两个模型:MemoryGame.swift 和 Card.swift。
  • Main.storyboard 确保整套组件可用。

我们从游戏中最简单的组成部分——纸牌开始。

swift卡

卡片模型将具有三个属性:用于标识每个卡片的 id、用于指定卡片状态(隐藏或打开)的布尔变量 shown 以及用于卡片上图像的artworkURL。

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

您还需要这些方法来控制用户与地图的交互:

在卡片上显示图像的方法。 这里我们将所有属性重置为默认值。 对于id,我们通过调用NSUUIS().uuidString生成一个随机id。

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)    
}
 
init(card: Card) {        
    self.id = card.id        
    self.shown = card.shown        
    self.artworkURL = card.artworkURL    
}

一开始还需要另一种洗牌方法。 我们将使其成为 Array 类的扩展。

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

这里是 Card 模型的代码实现,包含所有属性和方法。

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

来吧。

第二种模型是MemoryGame,这里我们设置了4*4的网格。 该模型将具有诸如卡片(网格上的卡片数组)、包含已打开的卡片的 cardsShown 数组以及用于跟踪游戏状态的布尔变量 isPlaying 等属性。

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

我们还需要开发控制用户与网格交互的方法。

一种在网格中洗牌的方法。

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

创建新游戏的方法。 这里我们调用第一个方法来启动初始布局并将 isPlaying 变量初始化为 true。

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

如果我们想重新开始游戏, 然后我们将 isPlaying 变量设置为 false 并删除卡片的初始布局。

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

验证点击卡片的方法。 稍后会详细介绍他。

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

返回特定卡片位置的方法。

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

检查所选卡是否符合标准。

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

此方法读取 **cardsShown** 数组中的最后一个元素并返回不匹配的卡片。

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

Main.storyboard 看起来像这样:

用 Swift 编写记忆卡游戏

最初,您需要在控制器中将新游戏设置为 viewDidLoad,包括网格的图像。 在游戏中,这一切都将由4*4的collectionView来表示。 如果你还不熟悉collectionView,这里是 您可以获得您需要的信息.

我们将把 GameController 配置为应用程序的根控制器。 GameController 将有一个 collectionView,我们将其引用为 IBOutlet。 另一个参考是 IBAction onStartGame() 按钮,这是一个 UIButton,您可以在名为 PLAY 的故事板中看到它。

关于控制器的实现的一些信息:

  • 首先,我们初始化两个主要对象 - 网格:game = MemoryGame() 和一组卡片:cards = [Card]()。
  • 我们将初始变量设置为viewDidLoad,这是游戏运行时调用的第一个方法。
  • collectionView 被设置为隐藏,因为所有卡片都会隐藏,直到用户按下 PLAY。
  • 一旦我们按下 PLAY,onStartGame IBAction 部分就会启动,我们将 collectionView isHidden 属性设置为 false,以便卡片变得可见。
  • 每次用户选择一张卡时,都会调用 didSelectItemAt 方法。 在该方法中我们调用didSelectCard来实现主要的游戏逻辑。

这是最终的 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)
    }
}

现在我们来谈谈重要的协议。

协议

使用协议是 Swift 编程的核心。 协议提供了为类、结构或枚举定义规则的能力。 这一原则允许您编写模块化和可扩展的代码。 事实上,这是我们已经在 GameController 中为 collectionView 实现的模式。 现在让我们制作自己的版本。 语法如下:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

我们知道协议允许我们定义用于实现类的规则或指令,所以让我们考虑一下它们应该是什么。 你总共需要四个。

  • 游戏开始:memoryGameDidStart。
  • 您需要将卡片面朝下:memoryGameShowCards。
  • 您需要将卡片面朝下:memoryGameHideCards。
  • 游戏结束:内存GameDidEnd。

所有四个方法都可以在主类 GameController 中实现。

内存游戏已开始

运行此方法时,游戏应该开始(用户按下“PLAY”)。 在这里,我们只需调用 collectionView.reloadData() 来重新加载内容,这将洗牌。

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

记忆游戏秀卡

我们从 collectionSDViewSelectItemAt 调用此方法。 首先它显示所选的卡。 然后检查cardsShown数组中是否有不匹配的卡片(如果cardShown的数量是奇数)。 如果有,则将所选卡与其进行比较。 如果图片相同,则两张卡片都会添加到 cardsShown 中并保持面朝上。 如果不同,则该卡将保持显示状态,并且将两者都翻面朝下。

记忆游戏隐藏卡

如果卡片不匹配,则调用此方法并隐藏卡片图像。

显示=假。

记忆游戏结束

当这个方法被调用时,意味着所有的牌都已经被揭开并且在cardsShown列表中:cardsShown.count = cards.count,所以游戏结束。 该方法在我们调用 endGame() 将 isPlaying 变量设置为 false 之后专门调用,之后显示游戏结束消息。 AlertController 还用作控制器的指示器。 viewDidDisappear 被调用并且游戏被重置。

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

用 Swift 编写记忆卡游戏
事实上,仅此而已。 您可以使用此项目来创建您自己的游戏版本。

快乐编码!

技能箱推荐:

来源: habr.com

添加评论