用 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 寫記憶卡遊戲
事實上,僅此而已。 您可以使用此項目來創建您自己的遊戲版本。

快樂編碼!

技能箱推薦:

來源: www.habr.com

添加評論