Viết trò chơi thẻ nhớ trong Swift

Viết trò chơi thẻ nhớ trong Swift

Bài viết này mô tả quá trình tạo ra một trò chơi rèn luyện trí nhớ đơn giản mà tôi rất thích. Bên cạnh việc bản thân nó đã tốt, bạn sẽ tìm hiểu thêm một chút về các lớp và giao thức Swift trong quá trình học. Nhưng trước khi bắt đầu, chúng ta hãy hiểu bản thân trò chơi.

Chúng tôi nhắc nhở: cho tất cả độc giả của "Habr" - giảm giá 10 rúp khi đăng ký bất kỳ khóa học Skillbox nào bằng mã khuyến mại "Habr".

Hộp kỹ năng khuyến nghị: Khóa học giáo dục trực tuyến "Nghề Java Developer".

Cách chơi thẻ nhớ

Trò chơi bắt đầu bằng việc trình diễn một bộ thẻ. Họ nằm úp mặt (tương ứng, hình ảnh úp xuống). Khi bạn nhấp vào bất kỳ cái nào, hình ảnh sẽ mở ra trong vài giây.

Nhiệm vụ của người chơi là tìm tất cả các thẻ có hình giống nhau. Nếu sau khi mở thẻ đầu tiên, bạn lật thẻ thứ hai và các hình ảnh giống nhau thì cả hai thẻ vẫn mở. Nếu chúng không khớp, các thẻ sẽ bị đóng lại. Mục tiêu là để mở mọi thứ.

Cấu trúc dự án

Để tạo một phiên bản đơn giản của trò chơi này, bạn cần có các thành phần sau:

  • Một bộ điều khiển: GameController.swift.
  • Một chế độ xem: CardCell.swift.
  • Hai mô hình: MemoryGame.swift và Card.swift.
  • Main.storyboard để đảm bảo rằng toàn bộ bộ thành phần đều có sẵn.

Chúng ta bắt đầu với thành phần đơn giản nhất của trò chơi, các lá bài.

Thẻ.swift

Mô hình thẻ sẽ có ba thuộc tính: id để xác định từng thuộc tính, một biến boolean được hiển thị để chỉ định trạng thái của thẻ (ẩn hoặc mở) và tác phẩm nghệ thuậtURL cho hình ảnh trên thẻ.

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

Bạn cũng sẽ cần những phương pháp này để kiểm soát sự tương tác của người dùng với bản đồ:

Phương pháp hiển thị hình ảnh trên thẻ. Ở đây chúng tôi đặt lại tất cả các thuộc tính về mặc định. Đối với id, chúng tôi tạo một id ngẫu nhiên bằng cách gọi NSUUIS().uuidString.

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

Phương pháp so sánh thẻ ID.

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

Phương pháp tạo bản sao của từng thẻ - để có được số lượng lớn hơn những cái giống hệt nhau. Phương pháp này sẽ trả về thẻ có giá trị tương tự.

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

Và cần thêm một phương pháp nữa là xáo bài ngay từ đầu. Chúng ta sẽ biến nó thành một phần mở rộng của lớp Array.

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

Và đây là phần triển khai code cho model Card với đầy đủ các thuộc tính và phương thức.

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

Đi về phía trước.

Mô hình thứ hai là MemoryGame, ở đây chúng tôi đặt lưới 4*4. Mô hình sẽ có các thuộc tính như thẻ (một mảng thẻ trên lưới), mảng thẻHiển thị với các thẻ đã mở và biến boolean isPlaying để theo dõi trạng thái của trò chơi.

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

Chúng ta cũng cần phát triển các phương pháp để kiểm soát sự tương tác của người dùng với lưới.

Một phương pháp xáo trộn các thẻ trong một lưới.

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

Phương pháp tạo trò chơi mới. Ở đây chúng ta gọi phương thức đầu tiên để bắt đầu bố cục ban đầu và khởi tạo biến isPlaying thành true.

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

Nếu chúng ta muốn bắt đầu lại trò chơi, sau đó chúng tôi đặt biến isPlaying thành false và xóa bố cục ban đầu của thẻ.

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

Phương pháp xác minh thẻ đã nhấp. Thông tin thêm về anh ấy sau.

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

Một phương thức trả về vị trí của một thẻ cụ thể.

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

Kiểm tra sự tuân thủ của thẻ đã chọn với tiêu chuẩn.

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

Phương thức này đọc phần tử cuối cùng trong mảng **cardsShown** và trả về thẻ không khớp.

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 và GameController.swift

Main.storyboard trông giống như thế này:

Viết trò chơi thẻ nhớ trong Swift

Ban đầu, bạn cần đặt trò chơi mới là viewDidLoad trong bộ điều khiển, bao gồm cả hình ảnh cho lưới. Trong trò chơi, tất cả những điều này sẽ được thể hiện bằng bộ sưu tập 4*4. Nếu bạn chưa quen với CollectionView thì đây bạn có thể nhận được thông tin bạn cần.

Chúng tôi sẽ định cấu hình GameController làm bộ điều khiển gốc của ứng dụng. GameController sẽ có một CollectionView mà chúng ta sẽ tham chiếu dưới dạng IBOutlet. Một tài liệu tham khảo khác là nút IBAction onStartGame(), đây là UIButton, bạn có thể thấy nó trong bảng phân cảnh có tên PLAY.

Một chút về việc thực hiện bộ điều khiển:

  • Đầu tiên, chúng ta khởi tạo hai đối tượng chính - lưới: game = MemoryGame() và một bộ thẻ: cards = [Card]().
  • Chúng tôi đặt các biến ban đầu là viewDidLoad, đây là phương thức đầu tiên được gọi khi trò chơi đang chạy.
  • CollectionView được đặt thành ẩn vì tất cả các thẻ đều bị ẩn cho đến khi người dùng nhấn PLAY.
  • Ngay sau khi chúng ta nhấn PLAY, phần IBAction onStartGame sẽ bắt đầu và chúng ta đặt thuộc tính CollectionView isHidden thành false để các thẻ có thể hiển thị.
  • Mỗi lần người dùng chọn một thẻ, phương thức didSelectItemAt sẽ được gọi. Trong phương thức này, chúng tôi gọi didSelectCard để triển khai logic trò chơi chính.

Đây là cách triển khai GameController cuối cùng:

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

Bây giờ hãy nói một chút về các giao thức quan trọng.

Các giao thức

Làm việc với các giao thức là cốt lõi của lập trình Swift. Các giao thức cung cấp khả năng xác định các quy tắc cho một lớp, cấu trúc hoặc bảng liệt kê. Nguyên tắc này cho phép bạn viết mã mô-đun và có thể mở rộng. Trên thực tế, đây là mẫu mà chúng tôi đã triển khai cho CollectionView trong GameController. Bây giờ hãy tạo phiên bản của riêng chúng ta. Cú pháp sẽ trông như thế này:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Chúng ta biết rằng một giao thức cho phép chúng ta xác định các quy tắc hoặc hướng dẫn để triển khai một lớp, vì vậy hãy suy nghĩ xem chúng nên như thế nào. Tổng cộng bạn cần bốn cái.

  • Bắt đầu trò chơi: MemoryGameDidStart.
  • Bạn cần úp lá bài úp xuống: MemoryGameShowCards.
  • Bạn cần úp thẻ úp xuống: MemoryGameHideCards.
  • Trò chơi kết thúc: MemoryGameDidEnd.

Tất cả bốn phương thức đều có thể được triển khai cho lớp chính, đó là GameController.

bộ nhớGameDidStart

Khi phương thức này được chạy, trò chơi sẽ bắt đầu (người dùng nhấn PLAY). Ở đây chúng ta chỉ cần tải lại nội dung bằng cách gọi CollectionView.reloadData(), thao tác này sẽ xáo trộn các thẻ.

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

bộ nhớGameShowThẻ

Chúng tôi gọi phương thức này từ bộ sưu tậpSDViewSelectItemAt. Đầu tiên nó hiển thị thẻ đã chọn. Sau đó kiểm tra xem có thẻ nào chưa khớp trong mảng cardsShown hay không (nếu số lượng thẻShown là số lẻ). Nếu có, thẻ đã chọn sẽ được so sánh với thẻ đó. Nếu các hình ảnh giống nhau, cả hai thẻ sẽ được thêm vào thẻ Hiển thị và vẫn ngửa. Nếu khác nhau, thẻ để lại thẻ Hiển thị và cả hai đều úp xuống.

bộ nhớTrò chơiẨnThẻ

Nếu các thẻ không khớp, phương thức này được gọi và hình ảnh thẻ sẽ bị ẩn.

hiển thị = sai.

bộ nhớGameDidEnd

Khi phương thức này được gọi, điều đó có nghĩa là tất cả các thẻ đã được tiết lộ và nằm trong danh sách thẻHiển thị: cardsShown.count = cards.count, vậy là trò chơi kết thúc. Phương thức này được gọi cụ thể sau khi chúng ta gọi endGame() để đặt biến isPlaying thành false, sau đó thông báo kết thúc trò chơi sẽ được hiển thị. Ngoài ra, AlertController còn được sử dụng làm chỉ báo cho bộ điều khiển. viewDidDisappear được gọi và trò chơi được đặt lại.

Đây là những gì nó trông giống như trong 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()
    }
}

Viết trò chơi thẻ nhớ trong Swift
Trên thực tế, đó là tất cả. Bạn có thể sử dụng dự án này để tạo phiên bản trò chơi của riêng mình.

Chúc mừng mã hóa!

Hộp kỹ năng khuyến nghị:

Nguồn: www.habr.com

Thêm một lời nhận xét