Memóriakártya-játék írása Swiftben

Memóriakártya-játék írása Swiftben

Ez a cikk egy egyszerű memóriafejlesztő játék létrehozásának folyamatát írja le, amelyet nagyon szeretek. Amellett, hogy önmagában jó, egy kicsit többet is megtudhat a Swift osztályokról és protokollokról. De mielőtt elkezdenénk, értsük meg magát a játékot.

Emlékeztetünk: a "Habr" minden olvasója számára - 10 000 rubel kedvezmény, ha a "Habr" promóciós kóddal bármely Skillbox tanfolyamra jelentkezik.

A Skillbox a következőket ajánlja: Oktató online tanfolyam "Profession Java Developer".

Hogyan kell játszani a memóriakártyával

A játék egy sor kártya bemutatásával kezdődik. Arccal lefelé fekszenek (illetve kép lefelé). Ha bármelyikre kattint, a kép néhány másodpercre megnyílik.

A játékos feladata, hogy megtalálja az összes azonos képpel ellátott kártyát. Ha az első kártya kinyitása után megfordítja a másodikat, és a képek megegyeznek, mindkét kártya nyitva marad. Ha nem egyeznek, a kártyákat újra bezárják. A cél az, hogy mindent kinyissanak.

Projekt szerkezete

A játék egyszerű verziójának létrehozásához a következő összetevőkre van szüksége:

  • Egy vezérlő: GameController.swift.
  • Egy nézet: CardCell.swift.
  • Két modell: MemoryGame.swift és Card.swift.
  • Main.storyboard, hogy biztosítsa, hogy a teljes összetevőkészlet elérhető legyen.

Kezdjük a játék legegyszerűbb összetevőjével, a kártyákkal.

Card.swift

A kártyamodellnek három tulajdonsága lesz: id mindegyik azonosítására, egy logikai változó a kártya állapotának meghatározására (rejtett vagy nyitott), valamint artworkURL a kártyákon lévő képekhez.

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

A következő módszerekre is szüksége lesz a térképekkel való felhasználói interakció szabályozásához:

Kép kártyán való megjelenítésének módja. Itt visszaállítjuk az összes tulajdonságot az alapértelmezettre. Az azonosítóhoz véletlenszerű azonosítót generálunk az NSUUIS().uuidString meghívásával.

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

A személyi igazolványok összehasonlításának módszere.

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

Az egyes kártyák másolatának elkészítésének módja - annak érdekében, hogy minél nagyobb számú egyforma legyen. Ez a módszer hasonló értékű kártyát ad vissza.

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

És még egy módszerre van szükség a kártyák összekeveréséhez az elején. Az Array osztály kiterjesztését tesszük.

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

És itt van a Kártya modell kódjának megvalósítása az összes tulajdonsággal és módszerrel.

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

Menj csak.

A második modell a MemoryGame, itt egy 4*4-es rácsot állítunk be. A modellnek olyan tulajdonságai lesznek, mint a kártyák (kártyatömb egy rácson), a cardsShown tömb már megnyitott kártyákkal, és egy logikai változó isPlaying a játék állapotának nyomon követésére.

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

Módszereket kell kidolgoznunk a felhasználói hálózattal való interakció szabályozására is.

Egy módszer, amely a kártyákat rácsban keveri.

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

Új játék létrehozásának módja. Itt az első módszert hívjuk meg a kezdeti elrendezés elindításához és az isPlaying változó true értékre való inicializálásához.

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

Ha újra akarjuk indítani a játékot, majd az isPlaying változót false értékre állítjuk és eltávolítjuk a kártyák kezdeti elrendezését.

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

A kattintott kártyák ellenőrzésének módja. Bővebben róla később.

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

Olyan módszer, amely visszaadja egy adott kártya pozícióját.

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

A kiválasztott kártya szabványnak való megfelelőségének ellenőrzése.

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

Ez a metódus beolvassa a **cardsShown** tömb utolsó elemét, és visszaadja a nem egyező kártyát.

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 és GameController.swift

A Main.storyboard valahogy így néz ki:

Memóriakártya-játék írása Swiftben

Kezdetben az új játékot viewDidLoad néven kell beállítani a vezérlőben, beleértve a rács képeit is. A játékban mindezt a 4*4 collectionView fogja képviselni. Ha még nem ismeri a collectionView-t, itt van megkaphatja a szükséges információkat.

A GameControllert az alkalmazás gyökérvezérlőjeként konfiguráljuk. A GameControllernek lesz egy gyűjteménynézete, amelyre IBOutletként fogunk hivatkozni. Egy másik hivatkozás az IBAction onStartGame() gombra, ez egy UIButton, a PLAY nevű storyboard-ban láthatod.

Egy kicsit a vezérlők megvalósításáról:

  • Először is inicializálunk két fő objektumot - a rácsot: játék = MemoryGame(), és egy kártyakészletet: cards = [Card]().
  • A kezdeti változókat viewDidLoad néven állítjuk be, ez az első metódus, amely a játék futása közben meghívódik.
  • A collectionView rejtettre van állítva, mert minden kártya el van rejtve, amíg a felhasználó meg nem nyomja a PLAY gombot.
  • Amint megnyomjuk a PLAY gombot, elindul az onStartGame IBAction szekció, és a collectionView isHidden tulajdonságot false értékre állítjuk, hogy a kártyák láthatóvá váljanak.
  • Minden alkalommal, amikor a felhasználó kiválaszt egy kártyát, a didSelectItemAt metódus kerül meghívásra. A metódusban a didSelectCard-ot hívjuk meg a játék fő logikájának megvalósításához.

Íme a GameController végső megvalósítása:

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

Most beszéljünk egy kicsit a fontos protokollokról.

protokollok

A protokollokkal való munka a Swift programozás magja. A protokollok lehetővé teszik egy osztály, struktúra vagy felsorolás szabályainak meghatározását. Ez az elv lehetővé teszi moduláris és bővíthető kód írását. Valójában ez egy olyan minta, amelyet már implementálunk a GameController-ben található collectionView-hoz. Most készítsük el a saját verziónkat. A szintaxis így fog kinézni:

protocol MemoryGameProtocol {
    //protocol definition goes here
}

Tudjuk, hogy egy protokoll lehetővé teszi, hogy szabályokat vagy utasításokat határozzunk meg egy osztály megvalósításához, ezért gondoljuk át, mik legyenek azok. Összesen négyre van szüksége.

  • A játék kezdete: memoryGameDidStart.
  • A kártyát képpel lefelé kell fordítania: memoryGameShowCards.
  • A kártyát képpel lefelé kell fordítania: memoryGameHideCards.
  • A játék vége: memoryGameDidEnd.

Mind a négy metódus megvalósítható a fő osztályhoz, ami a GameController.

memoryGameDidStart

Amikor ez a módszer fut, a játéknak el kell indulnia (a felhasználó megnyomja a PLAY gombot). Itt egyszerűen újratöltjük a tartalmat a collectionView.reloadData() meghívásával, amely megkeveri a kártyákat.

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

memoryGameShowCards

Ezt a metódust a collectionSDViewSelectItemAt gyűjteményből hívjuk. Először a kiválasztott kártyát mutatja. Ezután ellenőrzi, hogy van-e páratlan kártya a cardsShown tömbben (ha a Shown kártyák száma páratlan). Ha van ilyen, a kiválasztott kártya összehasonlításra kerül vele. Ha a képek megegyeznek, mindkét kártya hozzáadódik a Megjelenített kártyákhoz, és képpel felfelé marad. Ha eltér, a kártya a Megjelenített kártyákat hagyja, és mindkettőt képpel lefelé fordítják.

memoryGameHideCards

Ha a kártyák nem egyeznek, akkor ez a metódus meghívásra kerül, és a kártyaképek elrejtésre kerülnek.

mutatott = hamis.

memoryGameDidEnd

Ha ezt a metódust hívjuk, az azt jelenti, hogy az összes kártya már fel van fedve, és benne van a kártyákShown listában: cardsShown.count = kártyák.szám, tehát a játéknak vége. A metódus kifejezetten azután hívódik meg, hogy meghívtuk az endGame()-t, hogy az isPlaying var értéket false értékre állítsuk, ami után megjelenik a játék vége üzenet. Az alertController a vezérlő indikátoraként is szolgál. A viewDidDisappar meghívásra kerül, és a játék alaphelyzetbe áll.

Így néz ki mindez a GameControllerben:

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

Memóriakártya-játék írása Swiftben
Valójában ez minden. Ezzel a projekttel létrehozhatod a játék saját verzióját.

Boldog kódolást!

A Skillbox a következőket ajánlja:

Forrás: will.com

Hozzászólás