在Swift上编写存储卡游戏



本文介绍了创建我真正喜欢的简单记忆训练游戏的过程。 除了本身出色外,您还将在工作中学到更多有关Swift类和协议的知识。 但是在开始之前,让我们先弄清楚游戏本身。

我们提醒您: 对于所有“哈勃”读者来说,使用“哈勃”促销代码注册任何Skillbox课程时均可享受10,000卢布的折扣。

Skillbox建议:在线教育课程“专业Java开发人员”

如何玩记忆卡


游戏从一组纸牌的演示开始。 他们躺在“衬衫”上(分别朝下)。 当您单击任何一个时,图像将打开几秒钟。

玩家的任务是找到所有带有相同图片的卡。 如果在打开第一张卡后将您的第二张翻过来并且图片匹配,则两张卡都保持打开状态。 如果它们不匹配,则再次关闭卡。 任务是打开所有内容。

项目结构


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

  • 一个控制器:GameController.swift。
  • 一种视图:CardCell.swift。
  • 两种模型:MemoryGame.swift和Card.swift。
  • Main.storyboard,以便可以使用整个组件集。

我们从游戏中最简单的部分开始,即纸牌。

卡速

证卡模型将具有三个属性:用于标识每个证件的ID,显示的用于阐明证卡状态(隐藏或打开)的逻辑变量,以及证卡上图片的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看起来像这样:



最初,在控制器中,您需要安装一个新游戏作为viewDidLoad,其中包括网格的图像。 在游戏中,所有这些将由4 * 4 collectionView表示。 如果您不熟悉collectionView, 则可以在此处获取必要的信息

我们将GameController配置为应用程序的根控制器。 GameController中将有一个collectionView,我们将其称为IBOutlet。 另一个链接是IBAction onStartGame()按钮,它是UIButton,您可以在名为PLAY的情节提要中看到它。

关于控制器的实现:

  • 首先,我们初始化两个主要对象-网格(游戏):游戏= MemoryGame(),并在卡片组上:卡片= [卡片]()。
  • 将初始变量设置为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。
  • 游戏完成:memoryGameDidEnd。

我们为主类实现了所有四个方法,这就是GameController。

memoryGameDidStart


启动此方法后,游戏应开始(用户按PLAY)。 在这里,我们只是简单地通过调用collectionView.reloadData()重新加载内容,这将导致地图混乱。

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

memoryGameShowCards


从collectionSDViewSelectItemAt调用此方法。 首先,它显示选定的地图。 然后,它检查一下cardsShown数组中是否有不匹配的卡片(如果cardsShown的数目为奇数)。 如果有一张,则将所选卡与其进行比较。 如果图片相同,则将两张卡都添加到cardsShown中并保持打开状态。 如果不同,则卡片离开显示的卡片,并且两边都翻转过来。

memoryGameHideCards


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

显示=假。

memoryGameDidEnd


调用此方法时,这意味着所有卡都已打开并且在cardsShown列表中:cardsShown.count = cards.count,因此游戏结束了。 在调用endGame()将isPlaying var设置为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() } } 


仅此而已。 您可以使用此项目来创建自己的游戏版本。

好的编码!

Skillbox建议:

Source: https://habr.com/ru/post/zh-CN447598/


All Articles