
この記事では、私が本当に気に入っている単純な記憶訓練ゲームを作成するプロセスについて説明します。 それ自体が良いだけでなく、仕事中にSwiftのクラスとプロトコルについてもう少し学習します。 しかし、始める前に、ゲーム自体を理解しましょう。
「Habr」の読者には、「Habr」プロモーションコードを使用してSkillboxコースに登録すると10,000ルーブルの割引があります。
Skillboxの推奨事項:オンライン教育コース「Profession Java-developer」 。
メモリーカードの遊び方
ゲームは、一連のカードのデモンストレーションから始まります。 彼らは「シャツ」を上に(それぞれ、裏向きに)横になります。 いずれかをクリックすると、画像が数秒間開きます。
プレーヤーのタスクは、同じ写真のすべてのカードを見つけることです。 最初のカードを開いた後、2番目のカードを裏返し、写真が一致した場合、両方のカードが開いたままになります。 一致しない場合、カードは再び閉じられます。 タスクはすべてを開くことです。
プロジェクト構造
このゲームのシンプルなバージョンを作成するには、次のコンポーネントが必要です。
- 1つのコントローラー:GameController.swift。
- 1つのビュー:CardCell.swift。
- 2つのモデル:MemoryGame.swiftとCard.swift。
- Main.storyboard。これにより、コンポーネントのセット全体が使用可能になります。
ゲームの最も単純なコンポーネントであるカードから始めます。
Card.swift
カードモデルには3つのプロパティがあります。それぞれを識別するid、カードの状態(非表示または開いている)を明確にするために表示される論理変数、およびカード上の画像の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 }
IDカードを比較する方法。
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() } } } }
どうぞ
2番目のモデルは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は次のようになります。

最初に、コントローラーで、グリッドのイメージを含むviewDidLoadとして新しいゲームをインストールする必要があります。 ゲームでは、これはすべて4 * 4 collectionViewで表されます。 collectionViewに慣れていない場合は、ここで必要な情報を入手できます 。
GameControllerをアプリケーションのルートコントローラーとして構成します。 GameControllerにはcollectionViewがあり、これをIBOutletとして参照します。 別のリンクはIBAction onStartGame()ボタンへのリンクです。これはUIButtonです。PLAYというストーリーボードで見ることができます。
コントローラーの実装について少し:
- 最初に、2つのメインオブジェクト-グリッド(ゲーム):game = MemoryGame()、およびカードのセット:cards = [Card]()を初期化します。
- 初期変数をviewDidLoadとして設定します。これはゲーム中に呼び出される最初のメソッドです。
- ユーザーがPLAYを押すまで、すべてのマップが非表示になるため、collectionViewを非表示に設定します。
- 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 }
プロトコルを使用すると、クラスを実装するためのルールまたは指示を定義できることがわかっているので、それらがどうあるべきかを考えましょう。 必要なのは4つだけです。
- ゲームの開始:memoryGameDidStart。
- カードを裏返しにする必要があります:memoryGameShowCards。
- カードを裏返すには、memoryGameHideCardsが必要です。
- ゲームの完了:memoryGameDidEnd。
メインクラスに4つのメソッドすべてを実装します。これがGameControllerです。
memoryGameDidStart
このメソッドが起動すると、ゲームが開始されます(ユーザーがPLAYを押します)。 ここでは、collectionView.reloadData()を呼び出してコンテンツをリロードするだけで、マップがシャッフルされます。
func memoryGameDidStart(_ game: MemoryGame) { collectionView.reloadData() }
memoryGameShowCards
collectionSDViewSelectItemAtからこのメソッドを呼び出します。 まず、選択したマップが表示されます。 次に、cardsShown配列に一致しないカードがあるかどうかを確認します(cardsShownの数が奇数の場合)。 ある場合、選択されたカードはそれと比較されます。 写真が同じ場合、両方のカードがCardsShownに追加され、開いたままになります。 異なる場合、カードはカードを表示したままにし、両方が上下逆さまになります。
memoryGameHideCards
カードが一致しない場合、このメソッドが呼び出され、カード画像が非表示になります。
表示= false。
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の推奨事項:
- 実践コース「Mobile Developer PRO」 。
- 応用Python Data Analystオンラインコース。
- 2年間の実践コース「私はPRO Web開発者です。 」