Escrevendo um jogo de cartão de memória no Swift



Este artigo descreve o processo de criação de um jogo simples de treinamento de memória que eu realmente gosto. Além de ser bom por si só, você aprenderá um pouco mais sobre as classes e protocolos Swift durante o trabalho. Mas antes de começar, vamos descobrir o jogo em si.

Lembramos que: para todos os leitores de "Habr" - um desconto de 10.000 rublos ao se inscrever em qualquer curso Skillbox usando o código promocional "Habr".

A Skillbox recomenda: O curso educacional on-line "Profissão Java-developer" .

Como jogar Cartão de Memória


O jogo começa com uma demonstração de um conjunto de cartas. Eles ficam de “camisa” para cima (respectivamente, voltados para baixo). Quando você clica em qualquer, uma imagem é aberta por alguns segundos.

A tarefa do jogador é encontrar todos os cartões com as mesmas fotos. Se, depois de abrir o primeiro cartão, você virar o segundo cartão e as imagens coincidirem, os dois cartões permanecerão abertos. Se não corresponderem, os cartões serão fechados novamente. A tarefa é abrir tudo.

Estrutura do projeto


Para criar uma versão simples deste jogo, você precisa dos seguintes componentes:

  • Um controlador: GameController.swift.
  • Uma visão: CardCell.swift.
  • Dois modelos: MemoryGame.swift e Card.swift.
  • Main.storyboard para que todo o conjunto de componentes esteja disponível.

Começamos com o componente mais simples do jogo, o cartão.

Card.swift

O modelo do cartão terá três propriedades: id para identificar cada uma, uma variável lógica mostrada para esclarecer o status do cartão (oculto ou aberto) e o artworkURL para fotos nos cartões.

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

Você também precisará destes métodos para controlar a interação do usuário com os cartões:

Método para exibir uma imagem em um cartão. Aqui redefinimos todas as propriedades para o padrão. Para identificação, gere uma identificação aleatória chamando NSUUIS (). UuidString.

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

Método para comparar cartões de identificação.

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

O método para criar uma cópia de cada cartão é obter um número maior de cartões idênticos. Este método retornará o cartão com valores semelhantes.

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

E é necessário outro método para misturar as cartas no início. Vamos torná-lo uma extensão da classe Array.
 extension Array { mutating func shuffle() { for _ in 0...self.count { sort { (_,_) in arc4random() < arc4random() } } } } 

E aqui está a implementação de código para o modelo de cartão com todas as propriedades e métodos.

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

Vá em frente.

O segundo modelo é MemoryGame, aqui configuramos a grade 4 * 4. O modelo terá propriedades como cartões (uma matriz de cartas na grade), uma matriz cardsShown com cartas já abertas e o booleano isPlaying para rastrear o status do jogo.

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

Também precisamos desenvolver métodos para controlar a interação do usuário com a grade.

Um método que embaralha as cartas em uma grade.

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

Método para criar um novo jogo. Aqui chamamos o primeiro método para iniciar o layout inicial e inicializar a variável isPlaying como true.
 func newGame(cardsArray:[Card]) -> [Card] { cards = shuffleCards(cards: cardsArray) isPlaying = true return cards } 

Se queremos reiniciar o jogo, defina a variável isPlaying como false e remova o layout inicial dos cartões.

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

Método para verificação de cartões prensados. Mais sobre isso mais tarde.

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

Um método que retorna a posição de um cartão específico.

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

Verificar a conformidade do cartão selecionado com o padrão

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

Este método lê o último elemento na matriz ** cardsShown ** e retorna um cartão inapropriado.

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


Main.storyboard é algo parecido com isto:



Inicialmente, no controlador, você precisa instalar um novo jogo como viewDidLoad, incluindo imagens para a grade. No jogo, tudo isso será representado por 4 * 4 collectionView. Se você não estiver familiarizado com o collectionView, aqui poderá obter as informações necessárias .

Vamos configurar o GameController como o controlador raiz do aplicativo. Haverá um collectionView no GameController, que iremos referenciar como um IBOutlet. Outro link é para o botão IBAction onStartGame (), este é o UIButton, você pode vê-lo no storyboard chamado PLAY.

Um pouco sobre a implementação de controladores:

  • Primeiro, inicializamos os dois objetos principais - a grade (jogo): game = MemoryGame (), e no conjunto de cartas: cards = [Card] ().
  • Defina as variáveis ​​iniciais como viewDidLoad, este é o primeiro método chamado durante o jogo.
  • defina collectionView como oculto, pois todos os mapas ficam ocultos até o usuário pressionar PLAY.
  • Assim que pressionamos PLAY, a seção onStartGame IBAction é iniciada e definimos a propriedade collectionView isHidden como false para que os cartões possam ficar visíveis.
  • Sempre que um usuário seleciona um cartão, o método didSelectItemAt é chamado. No método, chamamos didSelectCard para implementar a lógica básica do jogo.

Aqui está a implementação final do 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) } } 

Agora vamos nos debruçar sobre alguns protocolos importantes.

Protocolos


Trabalhar com protocolos é a base da programação Swift. Os protocolos fornecem a capacidade de definir regras para uma classe, estrutura ou enumeração. Este princípio permite escrever código modular e extensível. Na verdade, este é um modelo que já estamos implementando para collectionView no GameController. Agora vamos criar nossa própria versão. A sintaxe ficará assim:

 protocol MemoryGameProtocol { //protocol definition goes here } 

Sabemos que um protocolo permite que você defina regras ou instruções para implementar uma classe, então vamos pensar sobre o que elas devem ser. Apenas quatro são necessários.

  • Início do jogo: memoryGameDidStart.
  • Precisa virar o cartão com a face para baixo: memoryGameShowCards.
  • Precisa virar o cartão com a face para baixo: memoryGameHideCards.
  • Conclusão do jogo: memoryGameDidEnd.

Implementamos todos os quatro métodos para a classe principal, e este é o GameController.

memoryGameDidStart


Quando esse método é iniciado, o jogo deve começar (o usuário pressiona PLAY). Aqui, simplesmente recarregamos o conteúdo chamando collectionView.reloadData (), o que fará com que os mapas sejam embaralhados.

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

memoryGameShowCards


Chame esse método de collectionSDViewSelectItemAt. Primeiro, mostra o mapa selecionado. Em seguida, verifica se há um cartão incomparável na matriz cardsShown (se o número de cardsShown for ímpar). Se houver um, o cartão selecionado é comparado a ele. Se as fotos forem iguais, os dois cartões serão adicionados ao cardsShown e permanecerão abertos. Se diferente, o cartão deixa cardsShown e os dois viram de cabeça para baixo.

memoryGameHideCards


Se os cartões não corresponderem, esse método será chamado e as imagens do cartão serão ocultadas.

mostrado = falso.

memoryGameDidEnd


Quando esse método é chamado, significa que todas as cartas já estão abertas e estão na lista cardsShown: cardsShown.count = cards.count, para que o jogo termine. O método é chamado especificamente depois que chamamos endGame () para definir isPlaying var como false, após o qual é exibida uma mensagem sobre a conclusão do jogo. O AlertController também é usado como um indicador para o controlador. O ViewDidDisappear é chamado e o jogo é reiniciado.

Veja como tudo fica no 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() } } 


Só isso. Você pode usar este projeto para criar sua própria versão do jogo.

Boa codificação!

A Skillbox recomenda:

Source: https://habr.com/ru/post/pt447598/


All Articles