كتابة لعبة بطاقة الذاكرة على سويفت



توضح هذه المقالة عملية إنشاء لعبة تدريب بسيطة على الذاكرة أحبها حقًا. إلى جانب كونك جيدًا في حد ذاته ، ستتعلم المزيد عن فئات Swift وبروتوكولاتها أثناء العمل. ولكن قبل أن تبدأ ، دعونا معرفة اللعبة نفسها.

نذكرك: لجميع قراء "Habr" - خصم بقيمة 10،000 روبل عند التسجيل في أي دورة تدريبية في Skillbox باستخدام الرمز "Habr" الترويجي.

توصي Skillbox بما يلي: الدورة التعليمية عبر الإنترنت "Profession Java-developer" .

كيف تلعب بطاقة الذاكرة


تبدأ اللعبة بعرض مجموعة من البطاقات. إنهم يكذبون "قميص" لأعلى (على التوالي ، وجهه لأسفل). عند النقر فوق أي ، تفتح الصورة لبضع ثوان.

مهمة اللاعب هي العثور على جميع البطاقات مع الصور نفسها. إذا قمت بعد فتح البطاقة الأولى بتسليم البطاقة الثانية وتطابقت الصور ، فستظل كلتا الفتحتين مفتوحة. إذا كانت غير متطابقة ، يتم إغلاق البطاقات مرة أخرى. المهمة هي فتح كل شيء.

هيكل المشروع


لإنشاء نسخة بسيطة من هذه اللعبة ، تحتاج إلى المكونات التالية:

  • وحدة تحكم واحدة: GameController.swift.
  • رأي واحد: CardCell.swift.
  • نموذجين: MemoryGame.swift و Card.swift.
  • Main.storyboard بحيث تتوفر مجموعة كاملة من المكونات.

نبدأ بأبسط مكونات اللعبة ، البطاقة.

Card.swift

سيكون لنموذج البطاقة ثلاث خصائص: معرف لتعريف كل منها ، متغير منطقي معروض لتوضيح حالة البطاقة (مخفية أو مفتوحة) و artworkURL للصور الموجودة على البطاقات.

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

ستحتاج أيضًا إلى هذه الطرق للتحكم في تفاعل المستخدم مع البطاقات:

طريقة لعرض صورة على البطاقة. نحن هنا إعادة تعيين جميع الخصائص إلى الافتراضي. للمعرف ، قم بإنشاء معرف عشوائي عن طريق استدعاء NSUUIS (). UuidString.

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

وهنا هو تطبيق رمز لنموذج البطاقة مع جميع الخصائص والأساليب.

 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. سيكون للنموذج خصائص مثل البطاقات (مجموعة من البطاقات على الشبكة) ، ومجموعة من البطاقات التي بها بطاقات مفتوحة بالفعل ، والمنطق المنطقي 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 كصحيح.
 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 ، والذي سنشير إليه على أنه IBOutlet. هناك رابط آخر هو زر IBAction onStartGame () ، هذا هو UIButton ، يمكنك رؤيته في لوحة العمل المسماة PLAY.

قليلا عن تنفيذ وحدات التحكم:

  • أولاً ، نقوم بتهيئة الكائنين الرئيسيين - الشبكة (اللعبة): game = MemoryGame () ، وعلى مجموعة البطاقات: cards = [Card] ().
  • عيّن المتغيرات الأولية كـ viewDidLoad ، وهذه هي الطريقة الأولى التي يتم استدعاؤها أثناء اللعبة.
  • تعيين collectionView مخفي ، حيث يتم إخفاء جميع الخرائط حتى يضغط المستخدم على PLAY.
  • بمجرد أن نضغط على PLAY ، يبدأ قسم onStartGame IBAction ، وقمنا بتعيين خاصية View 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) } } 

الآن دعونا نتناول بعض البروتوكولات الهامة.

بروتوكولات


العمل مع البروتوكولات هو أساس البرمجة سويفت. توفر البروتوكولات القدرة على تعيين قواعد لفئة أو بنية أو تعداد. هذا المبدأ يسمح لك بكتابة كود معياري وقابل للتمديد. هذا بالفعل قالب نطبقه بالفعل لـ collectionView في GameController. الآن دعونا نجعل نسختنا الخاصة. سيبدو بناء الجملة كما يلي:

 protocol MemoryGameProtocol { //protocol definition goes here } 

نحن نعلم أن البروتوكول يسمح لك بتحديد القواعد أو التعليمات لتطبيق الفصل ، لذلك دعونا نفكر فيما ينبغي أن تكون عليه. هناك حاجة فقط أربعة.

  • بداية اللعبة: memoryGameDidStart.
  • تحتاج إلى قلب البطاقة وجهه لأسفل: memoryGameShowCards.
  • تحتاج إلى قلب البطاقة وجهه لأسفل: memoryGameHideCards.
  • إكمال اللعبة: memoryGameDidEnd.

نحن ننفذ جميع الطرق الأربعة للفئة الرئيسية ، وهذا هو GameController.

memoryGameDidStart


عند بدء تشغيل هذه الطريقة ، يجب أن تبدأ اللعبة (يضغط المستخدم PLAY). هنا ، نقوم ببساطة بإعادة تحميل المحتوى عن طريق استدعاء collectionView.reloadData () ، مما يؤدي إلى تبديل الخرائط.

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

memoryGameShowCards


استدعاء هذه الطريقة من collectionSDViewSelectItemAt. أولاً ، تُظهر الخريطة المحددة. ثم يتحقق لمعرفة ما إذا كانت هناك بطاقة لا مثيل لها في مجموعة البطاقات الموضحة (إذا كان عدد البطاقات معروضاً). إذا كان هناك واحد ، تتم مقارنة البطاقة المحددة بها. إذا كانت الصور هي نفسها ، تتم إضافة كلتا البطاقات إلى البطاقات المعروضة وتبقى مفتوحة. إذا كان الأمر مختلفًا ، فتترك البطاقة البطاقات المعروضة ، ويقلب الوجهان رأسًا على عقب.

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/ar447598/


All Articles