Escribir un juego de cartas de memoria en Swift



Este artículo describe el proceso de crear un juego de entrenamiento de memoria simple que realmente me gusta. Además de ser bueno en sí mismo, aprenderá un poco más sobre las clases y protocolos de Swift durante el trabajo. Pero antes de comenzar, descubramos el juego en sí.

Le recordamos: para todos los lectores de "Habr": un descuento de 10.000 rublos al registrarse en cualquier curso de Skillbox con el código de promoción "Habr".

Skillbox recomienda: El curso educativo en línea "Profession Java-developer" .

Cómo jugar la tarjeta de memoria


El juego comienza con una demostración de un conjunto de cartas. Se acuestan "camisa" hacia arriba (respectivamente, boca abajo). Cuando hace clic en cualquiera, se abre una imagen durante unos segundos.

La tarea del jugador es encontrar todas las cartas con las mismas imágenes. Si después de abrir la primera carta, le das la vuelta a la segunda y las imágenes coinciden, ambas cartas permanecen abiertas. Si no coinciden, las cartas se vuelven a cerrar. La tarea es abrir todo.

Estructura del proyecto


Para crear una versión simple de este juego necesitas los siguientes componentes:

  • Un controlador: GameController.swift.
  • Una vista: CardCell.swift.
  • Dos modelos: MemoryGame.swift y Card.swift.
  • Main.storyboard para que todo el conjunto de componentes esté disponible.

Comenzamos con el componente más simple del juego, la tarjeta.

Card.swift

El modelo de tarjeta tendrá tres propiedades: identificación para identificar cada una, una variable lógica que se muestra para aclarar el estado de la tarjeta (oculta o abierta) y artworkURL para las imágenes en las tarjetas.

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

También necesitará estos métodos para controlar la interacción del usuario con las tarjetas:

Método para mostrar una imagen en una tarjeta. Aquí restablecemos todas las propiedades a sus valores predeterminados. Para la identificación, genere una identificación aleatoria llamando a NSUUIS (). UuidString.

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

Método para comparar tarjetas de identificación.

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

El método para crear una copia de cada tarjeta es obtener una mayor cantidad de tarjetas idénticas. Este método devolverá la tarjeta con valores similares.

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

Y se necesita otro método para mezclar tarjetas al comienzo. Lo haremos una extensión de la clase Array.
 extension Array { mutating func shuffle() { for _ in 0...self.count { sort { (_,_) in arc4random() < arc4random() } } } } 

Y aquí está la implementación del código para el modelo de Tarjeta con todas las propiedades y 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() } } } } 

Adelante

El segundo modelo es MemoryGame, aquí configuramos la cuadrícula 4 * 4. El modelo tendrá propiedades tales como cartas (una matriz de cartas en la cuadrícula), una matriz de Cartas mostradas con cartas ya abiertas, y el booleano isPlaying para rastrear el estado del juego.

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

También necesitamos desarrollar métodos para controlar la interacción del usuario con la red.

Un método que baraja cartas en una cuadrícula.

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

Método para crear un nuevo juego. Aquí llamamos al primer método para iniciar el diseño inicial e inicializar la variable isPlaying como true.
 func newGame(cardsArray:[Card]) -> [Card] { cards = shuffleCards(cards: cardsArray) isPlaying = true return cards } 

Si queremos reiniciar el juego, establezca la variable isPlaying en falso y elimine el diseño inicial de las tarjetas.

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

Método para la verificación de tarjetas prensadas. Más sobre eso más tarde.

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

Un método que devuelve la posición de una carta específica.

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

Comprobación del cumplimiento de la tarjeta seleccionada con el estándar.

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

Este método lee el último elemento en la matriz ** cardsShown ** y devuelve una tarjeta inapropiada.

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


Main.storyboard se parece a esto:



Inicialmente, en el controlador, debe instalar un nuevo juego como viewDidLoad, incluidas las imágenes para la cuadrícula. En el juego, todo esto estará representado por 4 * 4 collectionView. Si no está familiarizado con collectionView, aquí puede obtener la información necesaria .

Configuraremos GameController como el controlador raíz de la aplicación. Habrá un collectionView en GameController, al que haremos referencia como IBOutlet. Otro enlace es el botón IBAction onStartGame (), este es UIButton, puedes verlo en el guión gráfico llamado PLAY.

Un poco sobre la implementación de controladores:

  • Primero, inicializamos los dos objetos principales: la cuadrícula (juego): juego = MemoryGame (), y en el conjunto de tarjetas: tarjetas = [Tarjeta] ().
  • Establezca las variables iniciales como viewDidLoad, este es el primer método que se llama durante el juego.
  • establece collectionView como oculto, ya que todos los mapas están ocultos hasta que el usuario presiona PLAY.
  • Tan pronto como presionamos PLAY, comienza la sección onStartGame IBAction, y establecemos la propiedad collectionView isHidden en false para que las tarjetas puedan ser visibles.
  • Cada vez que un usuario selecciona una tarjeta, se llama al método didSelectItemAt. En el método, llamamos a didSelectCard para implementar la lógica básica del juego.

Aquí está la implementación final de 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) } } 

Ahora hablemos de algunos protocolos importantes.

Protocolos


Trabajar con protocolos es la base de la programación Swift. Los protocolos proporcionan la capacidad de establecer reglas para una clase, estructura o enumeración. Este principio le permite escribir código modular y extensible. Esta es realmente una plantilla que ya estamos implementando para collectionView en GameController. Ahora hagamos nuestra propia versión. La sintaxis se verá así:

 protocol MemoryGameProtocol { //protocol definition goes here } 

Sabemos que un protocolo le permite definir reglas o instrucciones para implementar una clase, así que pensemos cuáles deberían ser. Solo se necesitan cuatro.

  • Inicio del juego: memoryGameDidStart.
  • Necesita voltear la tarjeta boca abajo: memoryGameShowCards.
  • Necesita voltear la tarjeta boca abajo: memoryGameHideCards.
  • Finalización del juego: memoryGameDidEnd.

Implementamos los cuatro métodos para la clase principal, y este es GameController.

memoryGameDidStart


Cuando se inicia este método, el juego debería comenzar (el usuario presiona PLAY). Aquí, simplemente recargamos el contenido llamando a collectionView.reloadData (), lo que hará que los mapas se barajen.

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

memoryGameShowCards


Llame a este método desde collectionSDViewSelectItemAt. Primero, muestra el mapa seleccionado. Luego, verifica si hay una tarjeta no coincidente en la matriz cardsShown (si el número de cartasShown es impar). Si hay una, la tarjeta seleccionada se compara con ella. Si las imágenes son iguales, ambas tarjetas se agregan a las tarjetas mostradas y permanecen abiertas. Si es diferente, la carta deja las cartas mostradas, y ambas se dan vuelta.

memoryGameHideCards


Si las tarjetas no coinciden, se llama a este método y se ocultan las imágenes de la tarjeta.

se muestra = falso.

memoryGameDidEnd


Cuando se llama a este método, significa que todas las cartas ya están abiertas y están en la lista cardsShown: cardsShown.count = cards.count, por lo que el juego ha terminado. El método se llama específicamente después de que llamamos a endGame () para establecer isPlaying var en false, después de lo cual se muestra un mensaje sobre la finalización del juego. AlertController también se utiliza como indicador para el controlador. Se llama a ViewDidDisappear y se reinicia el juego.

Así es como se ve todo en 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() } } 


Eso es todo Puedes usar este proyecto para crear tu propia versión del juego.

Buena codificación!

Skillbox recomienda:

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


All Articles