Schreift e Memory Card Spill a Swift

Schreift e Memory Card Spill a Swift

Dësen Artikel beschreift de Prozess fir en einfacht Memory Training Spill ze kreéieren dat ech wierklech gär hunn. Nieft gutt u sech selwer, léiert Dir e bësse méi iwwer Swift Klassen a Protokoller wéi Dir gitt. Awer ier mer ufänken, loosst eis d'Spill selwer verstoen.

Mir erënneren Iech: fir all Habr Lieser - eng Remise vun 10 Rubel wann Dir Iech an all Skillbox Cours aschreift mat dem Habr Promo Code.

Skillbox recommandéiert: Online pädagogesch Cours "Beruff Java Entwéckler".

Wéi Memory Card ze spillen

D'Spill fänkt mat enger Demonstratioun vun enger Rei vu Kaarten un. Si leien Gesiicht erof (respektiv Bild erof). Wann Dir op eng klickt, mécht d'Bild fir e puer Sekonnen op.

D'Aufgab vum Spiller ass all Kaarte mat de selwechte Biller ze fannen. Wann Dir, nodeems Dir déi éischt Kaart opgemaach hutt, déi zweet ëmdréit an d'Biller passen, bleiwen déi zwou Kaarten op. Wann se net Match, d'Kaarte sinn zougemaach erëm. D'Zil ass alles opzemaachen.

Projet Struktur

Fir eng einfach Versioun vun dësem Spill ze kreéieren braucht Dir déi folgend Komponenten:

  • One Controller: GameController.swift.
  • One View: CardCell.swift.
  • Zwee Modeller: MemoryGame.swift an Card.swift.
  • Main.storyboard fir sécherzestellen datt de ganze Set vu Komponenten verfügbar ass.

Mir fänken mat der einfachst Komponent vum Spill, d'Kaarte.

Card.swift

De Kaartemodell wäert dräi Eegeschaften hunn: ID fir all eenzel z'identifizéieren, eng boolesch Variabel gewisen fir de Status vun der Kaart ze spezifizéieren (verstoppt oder oppen), an ArtworkURL fir d'Biller op de Kaarten.

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

Dir braucht och dës Methoden fir d'Benotzerinteraktioun mat Kaarten ze kontrolléieren:

Method fir e Bild op enger Kaart ze weisen. Hei setzen mir all Properties op Standard zréck. Fir ID Generéiere mir eng zoufälleg ID vun engem Opruff NSUUIS ().uuidString.

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

Method fir d'Vergläiche vun ID Kaarten.

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

Method fir eng Kopie vun all Kaart ze schafen - fir eng méi grouss Zuel vun identesch ze kréien. Dës Method gëtt Kaart mat ähnleche Wäerter zréck.

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

An nach eng Method ass néideg fir d'Kaarte am Ufank ze shuffle. Mir maachen et eng Ausdehnung vun der Array Klass.

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

An hei ass d'Ëmsetzung vum Code fir de Card Modell mat allen Eegeschaften a Methoden.

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-Supporter waren.

Den zweete Modell ass MemoryGame, hei setzen mir e 4 * 4 Gitter. De Modell wäert Eegeschafte wéi Kaarten hunn (eng Array vu Kaarten op engem Gitter), e CardsShown Array mat Kaarte scho op, an eng boolesch Variabel ass Playing fir de Status vum Spill ze verfolgen.

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

Mir mussen och Methoden entwéckelen fir d'Benotzerinteraktioun mam Gitter ze kontrolléieren.

Eng Method déi Kaarten an engem Gitter mëscht.

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

Method fir eng nei Spill schafen. Hei nenne mir déi éischt Method fir den initialen Layout unzefänken an d'isPlaying Variabel op richteg ze initialiséieren.

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

Wa mir d'Spill wëllen nei starten, dann setzen mir d'isPlaying Variabel op falsch an läschen den initialen Layout vun de Kaarten.

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

Method fir geklickt Kaarten z'iwwerpréiwen. Méi iwwer hien méi spéit.

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

Eng Method déi d'Positioun vun enger spezifescher Kaart zréckkënnt.

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

Iwwerpréift d'Konformitéit vun der gewielter Kaart mam Standard.

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

Dës Method liest de leschten Element an der **cardsShown** Array a gëtt déi net passende Kaart zréck.

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

Main.storyboard gesäit sou eppes aus:

Schreift e Memory Card Spill a Swift

Am Ufank musst Dir dat neit Spill als viewDidLoad am Controller setzen, och d'Biller fir d'Gitter. Am Spill gëtt all dëst duerch 4 * 4 CollectionView vertruede ginn. Wann Dir nach net mat CollectionView vertraut sidd, hei ass et Dir kënnt d'Informatioun kréien déi Dir braucht.

Mir konfiguréieren de GameController als Root Controller vun der Applikatioun. De GameController wäert eng CollectionView hunn déi mir als IBOutlet referenzéieren. Eng aner Referenz ass op den IBAction onStartGame () Knäppchen, dëst ass en UIButton, Dir kënnt et am Storyboard gesinn PLAY genannt.

E bëssen iwwer d'Ëmsetzung vu Controller:

  • Als éischt initialiséieren mir zwee Haaptobjekter - d'Gitter: Spill = MemoryGame (), an eng Rei vu Kaarten: Kaarten = [Card] ().
  • Mir setzen déi initial Variabelen als viewDidLoad, dëst ass déi éischt Method déi genannt gëtt wärend d'Spill leeft.
  • collectionView ass op verstoppt gesat well all Kaarte verstoppt sinn bis de Benotzer PLAY dréckt.
  • Soubal mir op PLAY drécken, fänkt d'onStartGame IBAction Sektioun un, a mir setzen d'CollectionView isHidden Propriétéit op falsch sou datt d'Kaarte siichtbar kënne ginn.
  • All Kéier wann de Benotzer eng Kaart wielt, gëtt d'didSelectItemAt Method genannt. An der Method mir ruffen didSelectCard der Haaptrei Logik ëmsetzen.

Hei ass déi lescht GameController Implementatioun:

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

Loosst eis elo e bëssen iwwer déi wichteg Protokoller schwätzen.

Protokoller

Mat Protokoller schaffen ass de Kär vun der Swift Programméierung. Protokoller bidden d'Fäegkeet Regele fir eng Klass, Struktur oder Opzielung ze definéieren. Dëse Prinzip erlaabt Iech modulare an erweiterbare Code ze schreiwen. Tatsächlech ass dëst e Muster dat mir scho fir CollectionView am GameController implementéieren. Loosst eis elo eis eege Versioun maachen. D'Syntax wäert esou ausgesinn:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Mir wëssen datt e Protokoll eis erlaabt Reegelen oder Instruktioune fir d'Ëmsetzung vun enger Klass ze definéieren, also loosst eis nodenken wat se solle sinn. Dir braucht véier am Ganzen.

  • Spillstart: memoryGameDidStart.
  • Dir musst d'Kaart ëmdréinen: MemoryGameShowCards.
  • Dir musst d'Kaart Gesiicht erof: MemoryGameHideCards.
  • Spill Enn: memoryGameDidEnd.

All véier Methode kënne fir d'Haaptklass ëmgesat ginn, dat ass GameController.

memoryGameDidStart

Wann dës Method leeft, soll d'Spill ufänken (de Benotzer dréckt op PLAY). Hei wäerte mir den Inhalt einfach nei lueden andeems Dir collectionView.reloadData () rufft, wat d'Kaarte gemëscht.

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

memoryGameShowCards

Mir ruffen dës Method aus collectionSDViewSelectItemAt. Éischt weist et déi gewielte Kaart. Dann iwwerpréift fir ze kucken ob et eng oniwwertraff Kaart am CardsShown Array ass (wann d'Zuel vun de CardsShown komesch ass). Wann et een ass, gëtt déi gewielte Kaart domat verglach. Wann d'Biller déi selwecht sinn, ginn zwou Kaarten op d'Kaarte bäigefüügt a bleiwen Gesiicht erop. Wann anescht, verléisst d'Kaart d'KaarteShown a béid ginn d'Gesiicht erof.

memoryGameHideCards

Wann d'Kaarte net passen, gëtt dës Method genannt an d'Kaart Biller sinn verstoppt.

ugewisen = falsch.

memoryGameDidEnd

Wann dës Method genannt gëtt, heescht et, datt all Kaarte schonn opgedeckt sinn an an der CardsShown Lëscht sinn: cardsShown.count = cards.count, sou datt d'Spill eriwwer ass. D'Method gëtt speziell genannt nodeems mir endGame genannt hunn () fir den isPlaying var op falsch ze setzen, duerno gëtt de Spill Enn Message gewisen. Och alertController gëtt als Indikator fir de Controller benotzt. viewDidDisappear gëtt genannt an d'Spill gëtt zréckgesat.

Hei ass wéi et alles am GameController ausgesäit:

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

Schreift e Memory Card Spill a Swift
Eigentlech ass dat alles. Dir kënnt dëse Projet benotze fir Är eege Versioun vum Spill ze kreéieren.

Glécklech Kodéierung!

Skillbox recommandéiert:

Source: will.com

Setzt e Commentaire