Advertisement
  1. Code
  2. iOS SDK

Crea un Juego de Blackjack en Swift 3 y Spritekit

Scroll to top
Read Time: 20 min

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

En este tutorial crearás un juego de blackjack en SpriteKit usando Swift 3. Aprenderás sobre implementar toque, crear animaciones visuales, y muchos otros conceptos que serán útiles cuando construyas un juego SpriteKit.

1. Creando el Proyecto e Importando Recursos

Abre Xcode y elige Create a new Xcode project o elige New > Project... desde el menú File. Asegúrate de que iOS está seleccionado y elige la plantilla Game.

new_projectnew_projectnew_project

Después, elige lo que desees para el Product Name, Organization Name, y Organization Identifier. Asegúrate de que Language está establecido a Swift, Game Technology está establecido a SpriteKit, y Devices está establecido a iPad.

project_optionsproject_optionsproject_options

Especifica una ubicación para guardar los archivos de proyecto y da clic en Create.

Importando las Clases Helper

Descarga el repositorio GitHub para este proyecto. Dentro de este verás una carpeta classes. Abre esta carpeta y arrastra todos los archivos a la carpeta que tiene el mismo nombre o como hayas nombrado a tu proyecto, por ejemplo, blackjack. Asegúrate de que Copy items if needed está marcado a´si como el objetivo principal en la lista de objetivos.

File options with Copy items if needed box checkedFile options with Copy items if needed box checkedFile options with Copy items if needed box checked

Importando las Imágenes

También dentro del repositorio tutorial GitHub está una carpeta llamada tutorial images. Dentro del navegador de proyecto, abre Assets.xcassets y arrastra las imágenes a la barra lateral. Xcode creará automáticamente atlases de textura desde estas imágenes.

Tutorial images folder in GitHubTutorial images folder in GitHubTutorial images folder in GitHub

2. Configurando el Proyecto

Dentro del navegador de proyecto hay dos archivos que puedes borrar (Gamescene.sks y Actions.sks). Borra estos dos archivos y selecciona Mover A Papelera. Estos archivos son usados por el editor de escena integrado de Xcode, el cuál puede ser usado para diseñar visualmente tus proyectos. Estaremos creando todo a través de código, así que estos archivos no son necesarios.

Abre GameViewController.swift, borra sus contenidos, y reemplázalos con lo siguiente.

1
import UIKit
2
import SpriteKit
3
4
class GameViewController: UIViewController {
5
    
6
    override func viewDidLoad() {
7
        super.viewDidLoad()
8
        
9
        let scene = GameScene(size:CGSize(width: 768, height: 1024))
10
        let skView = self.view as! SKView
11
        skView.showsFPS = false
12
        skView.showsNodeCount = false
13
        skView.ignoresSiblingOrder = false
14
        scene.scaleMode = .aspectFill
15
        skView.presentScene(scene)
16
    }
17
    
18
    override var prefersStatusBarHidden: Bool {
19
        return true
20
    }
21
}

La clase GameViewController hereda de UIViewController y tendrá un SKView como su vista. Dentro del método viewDidLoad, degradamos la propiedad view a una instancia SKView, usando el operador de tipo as!, y configuramos la vista.

Si ejecutaras este proyecto cuando lo creaste, notarías texto en la parte inferior derecha de la pantalla. Para esto son las propiedades showsFPS y showsNodeCount, mostrando los cuadros por segundo a los que el juego se está ejecutando y el número de SKNodes visibles en la escena. No necesitamos esta información, así que los establecemos a false.

La propiedad ignoreSiblingOrder es usada para determinar el orden de dibujo de los SKNodes dentro del juego. Establecemos este a false aquí porque necesitamos que nuestros SKNodes se dibujen en el orden en que son agregados a la escena.

Por último, establecemos el modo de escala a .aspectFill, el cuál causará que el contenido de la escena se escale para llenar la pantalla entera. Después invocamos el método presentScene(_:) en el skView que presenta o "muestra" la escena.

Después, borra todo en GameScene.swift y reemplázalo con lo siguiente.

1
import SpriteKit
2
import GameplayKit
3
4
class GameScene: SKScene {
5
    
6
    override func didMove(to view: SKView) {
7
    }
8
   
9
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {    
10
    }
11
}

Ahora puedes probar el proyecto, y deberías ver una pantalla negra vacía. En el siguiente paso comenzaremos agregando contenido a nuestra escena.

3. Variables y Constantes

Ingresa el siguiente código al inicio de la clase GameScene justo debajo de donde GameScene hereda de SKScene.

1
class GameScene: SKScene {
2
    let moneyContainer = SKSpriteNode(color: .clear, size: CGSize(width:250, height: 150))
3
    let dealBtn = SKSpriteNode(imageNamed: "deal_btn")
4
    let hitBtn = SKSpriteNode(imageNamed: "hit_btn")
5
    let standBtn = SKSpriteNode(imageNamed: "stand_btn")
6
    let money10 = Money(moneyValue: .ten)
7
    let money25 = Money(moneyValue: .twentyFive)
8
    let money50 = Money(moneyValue: .fifty)
9
    let instructionText = SKLabelNode(text: "Place your bet")
10
11
   

Aquí estamos creando un número de SKSpriteNodes. Los SKSpriteNodes son usados para crear un nodo de color, o más comúnmente de un SKTexture, el cuál es más frecuentemente una imagen. Usamos el inicializador de conveniencia init(color:size:) para crear un  nodo transparente coloreado moneyContainer. El moneyContainer será usado para retener el dinero que el jugador apuesta, y al final de cada ronda lo animaremos moviéndolo hacia quien haya ganado el juego. Colocar todo el dinero en este solo nodo hace más fácil animar todo el dinero de una vez.

Después, creamos las contantes dealBtn, hitBtn, y standBtn. Como los nombres sugieren, estos serán usados en el juego para repartir, ir y quedarse respectivamente. Estamos usando el inicializador de conveniencia init(imageNamed:), el cuál toma como parámetro el nombre de la imagen sin una extensión.

Después creamos las tres constantes money10money25, y money50, que son del tipo Money. Money es es una clase personalizada que extiende a SKSpriteNode y dependiendo del tipo de moneyValue pasado como parámetro crea uno de tres tipos diferentes de dinero. El parámetro moneyValue es de tipo MoneyValue, que es un enum. Echa un vistazo a la clase Money en el repositorio GitHub para ver cómo funciona todo esto.

Por último creamos un SKLabelNode usando el inicializador de conveniencia init(text:) que toma como parámetro el texto a ser mostrado dentro de la etiqueta.

4. Implementando setupTable

Agrega lo siguiente debajo de la función didMove(to:).

1
func setupTable(){
2
    let table = SKSpriteNode(imageNamed: "table")
3
    addChild(table)
4
    table.position = CGPoint(x: size.width/2, y: size.height/2)
5
    table.zPosition = -1
6
    addChild(moneyContainer)
7
    moneyContainer.anchorPoint = CGPoint(x:0, y:0)
8
    moneyContainer.position = CGPoint(x:size.width/2 - 125, y:size.height/2)
9
    instructionText.fontColor = UIColor.black
10
    addChild(instructionText)
11
    instructionText.position = CGPoint(x: size.width/2, y: 400)
12
}
13
    

Aquí inicializamos una constante table y la agregamos a la escena usando addChild(_:) que toma como un parámetro al nodo a agregar a la escena. Establecemos la position de table dentro de la escena y establecemos su zPosition a -1. La propiedad zPosition controla el orden en que los nodos son dibujados. El número más bajo es dibujado primero, con números más altos siendo dibujados en orden. Debido a que necesitamos la table debajo de todo lo demás, establecemos su zPosition a -1. Esto asegura que es dibujado antes que cualquier otro nodo.

También agregamos el moneyContainer y instructionText a la escena. Establecemos el fontColor del instructionText a negro (el valor por defecto es blanco).

Actualiza didMove(to:) a lo siguiente.

1
override func didMove(to view: SKView) {
2
        setupTable()
3
}

El método didMove(to:) es llamado inmediatamente después de que la escena es presentada por la vista. Generalmente, aquí es donde harás la configuración para tu escena y creas tus recursos. Si pruebas ahora, deberías ver que table e instructionText han sido agregados a la escena. El moneyContainer está ahí también pero no puedes verlo porque lo creamos con un color transparente.

5. Implementando setupMoney

Agrega lo siguiente debajo del método setupTable.

1
func setupMoney(){
2
        addChild(money10)
3
        money10.position = CGPoint(x: 75, y: 40)
4
        
5
        addChild(money25)
6
        money25.position = CGPoint(x:130, y:40)
7
        
8
        addChild(money50)
9
        money50.position = CGPoint(x: 185, y:40)
10
}

Aquí simplemente agregamos las instancias de dinero y establecemos su posición. Invoca este método dentro de didMove(to:).

1
override func didMove(to view: SKView) {
2
    setupTable()
3
    setupMoney()
4
}
5

6. Implementando setupButtons

Agrega lo siguiente debajo del método setupMoney que creaste en el paso anterior.

1
func setupButtons(){
2
    dealBtn.name = "dealBtn"
3
    addChild(dealBtn)
4
    dealBtn.position = CGPoint(x:300, y:40)
5
        
6
    hitBtn.name = "hitBtn"
7
    addChild(hitBtn)
8
    hitBtn.position = CGPoint(x:450, y:40)
9
    hitBtn.isHidden = true
10
        
11
    standBtn.name = "standBtn"
12
    addChild(standBtn)
13
    standBtn.position = CGPoint(x:600, y:40)
14
    standBtn.isHidden = true
15
}

Como hicimos con los dineros en el paso anterior, agregamos los botones y establecemos sus posiciones. Aquí usamos la propiedad name para que podamos identificar cada botón a través de código. También establecemos hitBtn y standBtn para estar ocultos, o invisibles, estableciendo la propiedad isHidden a true.

Ahora invoca este método dentro de didMove(to:).

1
override func didMove(to view: SKView) {
2
    setupTable()
3
    setupMoney()
4
    setupButtons()
5
}

Si ejecutas la aplicación ahora, deberías ver que las instancias de dinero y botones han sido agregadas a la escena.

7. Implementando touchesBegan

Necesitamos implementar el método touchesBegan(_:with:) para poder saber cuando los objetos en la escena han sido tocados. Este método es llamado cuando uno o más dedos han tocado la pantalla. Agrega lo siguiente dentro de touchesBegan.

1
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
2
    guard let touch = touches.first else {
3
        return
4
    }
5
            
6
    let touchLocation = touch.location(in: self)
7
    let touchedNode = self.atPoint(touchLocation)
8
            
9
    if(touchedNode.name == "money"){
10
        let money = touchedNode as! Money
11
        bet(betAmount: money.getValue())
12
    }
13
}

La propiedad multiTouchEnabled de la la vista de la escena está establecida a false por defecto, lo que significa que la vista solo recibe el primer toque de una secuencia multi-táctil. Con esta propiedad deshabilitada, puedes recoger el toque usando la primera propiedad first computada de los toques establecidos ya que solo hay un objeto en el conjunto.

Podemos obtener la touchLocation dentro de la escena por la propiedad location del toque. Podemos entonces descifrar cuál nodo fue tocado invocando atPoint(_:) y pasando la touchLocation.

Revisamos si la propiedad name de touchNode es igual a "money", y si lo es sabemos que han tocado sobre una de las tres instancias de dinero. Inicializamos una constante money degradando el touchedNode a Money, y después llamamos el método bet invocando al método getValue() en la constante money.

8. Implementando bet

Ingresa lo siguiente debajo de la función setupButtons que creaste en el paso de arriba.

1
func bet(betAmount: MoneyValue ){
2
    if(betAmount.rawValue > player1.bank.getBalance()){
3
        print("Trying to bet more than have");
4
        return
5
    }else{
6
        pot.addMoney(amount: betAmount.rawValue)
7
        let tempMoney = Money(moneyValue: betAmount)
8
        tempMoney.anchorPoint = CGPoint(x:0, y:0)
9
        moneyContainer.addChild(tempMoney)
10
        tempMoney.position = CGPoint(x:CGFloat(arc4random_uniform(UInt32(moneyContainer.size.width - tempMoney.size.width))), y:CGFloat(arc4random_uniform(UInt32(moneyContainer.size.height - tempMoney.size.height))))
11
         dealBtn.isHidden = false;
12
    }
13
}

Primero nos aseguramos de que el jugador no está intentando más dinero del que tiene, y si lo hacen simplemente regresamos de la función. De otro modo, agregamos el betAmount al pot, creamos una constante tempMoney, establecemos su anchorPoint a (0,0), y lo agregamos al moneyContainer. Después establecemos su position y ocultamos el dealBtn estableciendo su propiedad isHidden a false.

Los SKSpriteNodes tienen una propiedad anchorPoint que tiene por defecto (0.5,0.5). El sistema de coordenadas coloca (0,0) en la parte inferior izquierda y (1,1) en la parte superior derecha. Cambiarías esta propiedad de su valor por defecto si estuvieras rotando el SKSpriteNode y quisieras rotarlo alrededor de un punto diferente. Por ejemplo, si cambiaras la propiedad anchorPoint a (0,0) entonces el SKSpriteNode rotaría desde tu esquina inferior izquierda. Frecuentemente cambiarás esta propiedad para ayudar con el posicionamiento, como lo tenemos aquí.

Necesitamos crear una instancia de las clases Pot y Player para que este código funcione. Agrega lo siguiente junto con las otras contantes y variables.

1
let pot = Pot()
2
let player1 = Player(hand: Hand(),bank: Bank())

Si tu pruebas ahora puedes presionar cualquiera de los dineros y agregarlo al moneyContainer.

9. Implementando deal

Agrega lo siguiente junto con el resto de tus constantes y variables.

1
let dealer = Dealer(hand: Hand())
2
var allCards = [Card]()
3
let dealerCardsY = 930 // Y position of dealer cards

4
let playerCardsY = 200 // Y position of player cards

5
var currentPlayerType:GenericPlayer = Player(hand: Hand(),bank: Bank())
6
let deck = Deck()

El arreglo allCards será usado para retener todas las cartas dentro del juego. Esto hará sencillo ciclar a través de ellas y quitarlas de la escena todo en un intento. Las constantes dealerCardsY and playerCardsY son las posiciones de las cartas en el eje y. Esto nos ayudará cuando coloquemos nuevas cartas. El currentPlayerType es usado para indicar a quién repartir después. Será igual a dealer o player1.

Dentro de didMove(to:), agrega lo siguiente.

1
override func didMove(to view: SKView) {
2
    setupTable()
3
    setupMoney()
4
    setupButtons()
5
    currentPlayerType = player1
6
}

En el código anterior, inicializamos currentPlayerType a una instancia sin nombre de la clase Player. Aquí lo establecemos a player1.

Necesitamos crear un nuevo mazo de cartas antes de implementar el método deal. Ingresa lo siguiente dentro de setupTable.

1
func setupTable(){
2
    let table = SKSpriteNode(imageNamed: "table")
3
    addChild(table)
4
    table.position = CGPoint(x: size.width/2, y: size.height/2)
5
    table.zPosition = -1
6
    addChild(moneyContainer)
7
    moneyContainer.anchorPoint = CGPoint(x:0, y:0)
8
    moneyContainer.position = CGPoint(x:size.width/2 - 125, y:size.height/2)
9
    instructionText.fontColor = UIColor.black
10
    addChild(instructionText)
11
    instructionText.position = CGPoint(x: size.width/2, y: 400)
12
    deck.new()
13
}

Ahora podemos implementar la función deal. Agrega lo siguiente debajo del método bet.

1
func deal() {
2
    instructionText.text = ""
3
    money10.isHidden = true;
4
    money25.isHidden = true;
5
    money50.isHidden = true;
6
    dealBtn.isHidden = true;
7
    standBtn.isHidden = false
8
    hitBtn.isHidden = false
9
    let tempCard = Card(suit: "card_front", value: 0)
10
    tempCard.position = CGPoint(x:630, y:980)
11
    addChild(tempCard)
12
    tempCard.zPosition = 100
13
        
14
    let newCard = deck.getTopCard()
15
    var whichPosition = playerCardsY
16
    var whichHand = player1.hand
17
    if(self.currentPlayerType is Player){
18
        whichHand = player1.hand
19
        whichPosition = playerCardsY;
20
    } else {
21
        whichHand = dealer.hand
22
        whichPosition = dealerCardsY;
23
    }
24
        
25
    whichHand.addCard(card: newCard)
26
    let xPos = 50 + (whichHand.getLength()*35)
27
    let moveCard = SKAction.move(to: CGPoint(x:xPos, y: whichPosition),duration: 1.0)
28
    tempCard.run(moveCard, completion: { [unowned self] in
29
    self.player1.setCanBet(canBet: true)
30
    if(self.currentPlayerType is Dealer && self.dealer.hand.getLength() == 1){
31
        self.dealer.setFirstCard(card: newCard)
32
        self.allCards.append(tempCard)
33
        tempCard.zPosition = 0
34
    } else {
35
        tempCard.removeFromParent()
36
        self.allCards.append(newCard)
37
        self.addChild(newCard)
38
        newCard.position = CGPoint( x: xPos, y: whichPosition)
39
        newCard.zPosition = 100
40
    }
41
    if(self.dealer.hand.getLength() < 2){
42
        if(self.currentPlayerType is Player){
43
            self.currentPlayerType = self.dealer
44
        }else{
45
            self.currentPlayerType = self.player1
46
        }
47
        self.deal()
48
    }else if (self.dealer.hand.getLength() == 2 && self.player1.hand.getLength() == 2) {
49
        if(self.player1.hand.getValue() == 21 || self.dealer.hand.getValue() == 21){
50
            self.doGameOver(hasBlackJack: true)
51
        } else {
52
            self.standBtn.isHidden = false;
53
            self.hitBtn.isHidden = false;
54
        }
55
    }
56
            
57
    if(self.dealer.hand.getLength() >= 3 && self.dealer.hand.getValue() < 17){
58
        self.deal();
59
    } else if(self.player1.isYeilding() && self.dealer.hand.getValue() >= 17){
60
        self.standBtn.isHidden = true
61
        self.hitBtn.isHidden = true
62
        self.doGameOver(hasBlackJack: false)
63
    }
64
    if(self.player1.hand.getValue() > 21){
65
        self.standBtn.isHidden = true;
66
        self.hitBtn.isHidden = true;
67
        self.doGameOver(hasBlackJack: false);
68
    }
69
            
70
    })
71
}

Este método es bastante grande, pero necesario para implementar la lógica de repartición. Tomémoslo paso a paso. Inicializamos una constante tempCard a una instancia de Card, establecemos su posición, y la agregamos a la escena. Necesitamos esta carta dibujada en una posición zPosition mayor a 0, debido a que la primera carta del repartidor necesita estar en 0. Establecemos esto a un número arbitrario---100 funcionará. También creamos una constante newCard invocando el método getTopCard() de deck.

Después, inicializamos dos variables, wichPosition y whichHand, y después ejecutamos alguna lógica para determinar sus valores finales. Después agregamos newCard a la mano apropiada (ya sea el jugador o del repartidor). La constante xPos determina la posición x final de la carta una vez que se ha terminado de animar.

La clase SKAction tiene un número de métodos de clase que puedes llamar para cambiar las propiedades de un nodo tal como posición, escala, y rotación. Aquí llamamos al método move(to:duration:), el cuál moverá el nodo de una posición a otra. Sin embargo, para ejecutar en realidad la SKAction, tienes que invocar el método run(_:) de un nodo y pásalo al SKAction como un parámetro. Aquí, sin embargo, estamos invocando el método run(_:completion:), el cuál causará que el código se ejecute dentro del cierre de término después de que la acción complete ejecución.

Después de que la acción se ejecute hasta el término, permitimos al jugador apostar invocando setCanBet(canBet:) en la instancia de player1. Después revisamos si el currentPlayerType es una instancia de Dealer, y revisamos que el dealer solo tenga una carta invocando hand.getLength(). Si este es el caso, establecemos la primera carta del dealer, que necesitaremos al final del juego.

Debido a que la primera carta del dealer siempre está boca a bajo hasta el final del juego, necesitamos una referencia para la primera carta para que podamos mostrarla después. Agregamos esta carta al arreglo allCards para que podamos removerla después, y después establecemos su propiedad zPositon a 0 ya que necesitamos esta carta debajo de las otras cartas. (Recuerda que las otras cartas tienen z-position 100.)

Si el currentPlayerType no es una instancia de Dealer, y la longitud de la mano no es igual a 1, entonces quitamos la tempCard y ponemos la newCard en la misma posición, asegurándonos de establecer su zPosition a 100.

De acuerdo a las reglas del blackjack, tanto el repartidor como el jugador tienen dos cartas para comenzar el juego. Aquí estamos revisando cuál es el currentPlayerType y cambiándolo al opuesto. Debido a que el repartidor tiene menos de dos cartas, invocamos a la función deal de nuevo. De otro modo, revisamos que tanto el dealer como el player1 tengan dos cartas, y si este es el caso, revisamos para ver si cualquier tiene cartas con un valor total de 21---una mano ganadora. Si cualquiera de los dos tiene 21 entonces el juego termina porque uno de ellos ha obtenido blackjack. Si ninguno de los dos tiene 21 entonces mostramos los botones standBtn y hitBtn y el juego continua.

Las reglas del blackjack establecen que el dealer debe quedarse en 17 o mayor. Las siguientes pocas líneas de código revisan si el valor de la mano del raprtidor es menos de 17 y si es así invoca al método deal. Si es 17 o mayor entonces el juego termina. Por último, si el valor de la mano del player1 es mayor que 21 entonces el juego termina porque se han pasado.

¡Esto fue mucha lógica que recorrer! Si cualquier cosa es poco clara, solo léela de nuevo y toma tu tiempo para entenderla.

Después, necesitamos implementar el método gameover.

Necesitamos poder decir cuando el usuario ha presionado el botón repartir. Agrega el siguiente código al método touchesBegan(_:with:).

1
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
2
    guard let touch = touches.first else {
3
        return
4
    }
5
        
6
    let touchLocation = touch.location(in: self)
7
    let touchedNode = self.atPoint(touchLocation)
8
        
9
    if(touchedNode.name == "money"){
10
        let money = touchedNode as! Money
11
        bet(betAmount: money.getValue())
12
    }
13
        
14
    if(touchedNode.name == "dealBtn"){
15
        deal()
16
    }
17
}

10. Implementando doGameOver

Después, ingresa lo siguiente debajo del método deal que creaste en el paso de arriba.

1
func doGameOver(hasBlackJack: Bool){
2
    hitBtn.isHidden = true
3
    standBtn.isHidden = true
4
    let tempCardX = allCards[1].position.x
5
    let tempCardY = allCards[1].position.y
6
    let tempCard = dealer.getFirstCard()
7
    addChild(tempCard)
8
    allCards.append(tempCard)
9
    tempCard.position = CGPoint(x:tempCardX,y:tempCardY)
10
    tempCard.zPosition = 0
11
    var winner:GenericPlayer = player1
12
        
13
    if(hasBlackJack){
14
        if(player1.hand.getValue() > dealer.hand.getValue()){
15
            //Add to players Bank Here (pot value * 1.5)

16
            instructionText.text = "You Got BlackJack!";
17
            moveMoneyContainer(position: playerCardsY)
18
        }else{
19
            //Subtract from players bank here

20
            instructionText.text = "Dealer got BlackJack!";
21
            moveMoneyContainer(position: dealerCardsY)
22
        }
23
        return    
24
    }
25
        
26
    if (player1.hand.getValue() > 21){
27
        instructionText.text = "You Busted!"
28
        //Subtract from players bank

29
        winner = dealer
30
    }else if (dealer.hand.getValue() > 21){
31
        //Add to players bank

32
        instructionText.text = "Dealer Busts. You Win!"
33
        winner = player1
34
    }else if (dealer.hand.getValue() > player1.hand.getValue()){
35
        //Subtract from players bank

36
        instructionText.text = "You Lose!"
37
        winner = dealer
38
    }else if (dealer.hand.getValue() == player1.hand.getValue()){
39
         //Subtract from players bank

40
        instructionText.text = "Tie - Dealer Wins!"
41
        winner = dealer
42
    }else if (dealer.hand.getValue() < player1.hand.getValue()){
43
        //Add to players bank

44
        instructionText.text="You Win!";
45
        winner = player1
46
    }
47
        
48
    if(winner is Player){
49
        moveMoneyContainer(position: playerCardsY)
50
    }else{
51
        moveMoneyContainer(position: dealerCardsY)
52
    }
53
}

Obtenemos la posición x y y de la primer carta en el arreglo allCards, que es la primer carta del repartidor. Después instanciamos una constante tempCard invocando getFirstCard sobre el repartidor. ¿Recuerdas que establecimos esta Card anteriormente en el método deal? Aquí lo agregamos a la escena, establecemos su posición usando las constantes tempCardX y tempCardY, y establecemos tu zPosition a 0 para que estuviera debajo de las otras cartas.

Necesitamos saber quién ganó el juego, así que inicializamos una variable winner estableciéndola igual a player1, aunque esto podría cambiar dependiendo si el dealer en realidad ganó el juego.

Después recorremos alguna lógica para determinar quién ganó el juego. Si el parámetro hasBlackjack fuera verdadero, entonces deducimos quien ganó y devolvemos de la función. De otro modo, continuamos con la lógica para deducir quién ganó el juego. No voy a recorrer paso a paso esta lógica ya que debería ser clara de entender. Sin importar quién ganó, invocamos moveMoneyContainer(position:), el cuál toma como parámetro la posición a la cuál mover el contenedor de dinero. Esto será la posición y de las cartas del dealer o del player1.

11. Implementando moveMoneyContainer

Ingresa el siguiente código debajo del método doGameOver.

1
 func moveMoneyContainer(position: Int){
2
    let moveMoneyContainer = SKAction.moveTo(y: CGFloat(position), duration: 3.0)
3
    moneyContainer.run(moveMoneyContainer, completion: { [unowned self] in
4
            self.resetMoneyContainer()
5
    });
6
}

El método moveMoneyContainer(position:) mueve el moneyContainer a quien haya ganado el juego, ya sea el jugador o el repartidor. Cuando la SKAction se completa, invocamos resetMoneyContainer.

12. Implementando resetContainer

El método resetMoneyContainer remueve todos los dineros invocando el método removeAllChildren(), reestablece el moneyContainer a su posición original, e invoca a newGame.

1
func resetMoneyContainer(){
2
    moneyContainer.removeAllChildren()
3
    moneyContainer.position.y = size.height/2
4
    newGame()
5
}

13. Implementando newGame

Agrega lo siguiente debajo del método resetMoneyContainer que implementaste en el paso anterior.

1
func newGame(){
2
    currentPlayerType = player1
3
    deck.new()
4
    instructionText.text = "PLACE YOUR BET";
5
    money10.isHidden = false;
6
    money25.isHidden = false;
7
    money50.isHidden = false;
8
    dealBtn.isHidden = false
9
    player1.hand.reset()
10
    dealer.hand.reset()
11
    player1.setYielding(yields: false)
12
        
13
    for card in allCards{
14
        card.removeFromParent()
15
    }
16
    allCards.removeAll()
17
    }

Aquí reiniciamos todas las variables necesarias y removemos todas las cartas de la escena ciclando a través del arreglo allCards e invocando removeFromParent() sobre cada elemento.

14. Implementando hitBtn y standBtn

Todo lo que resta para completar nuestro juego es implementar los toques en los botones hitBtn y standBtn. Ingresa lo siguiente dentro del método touchesBegan(_:with:).

1
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
2
    guard let touch = touches.first else {
3
        return
4
    }
5
        
6
    let touchLocation = touch.location(in: self)
7
    let touchedNode = self.atPoint(touchLocation)
8
        
9
    if(touchedNode.name == "money"){
10
        let money = touchedNode as! Money
11
        bet(betAmount: money.getValue())
12
    }
13
        
14
    if(touchedNode.name == "dealBtn"){
15
        deal()
16
    }
17
        
18
    if(touchedNode.name == "hitBtn"){
19
        hit()
20
    }
21
        
22
    if(touchedNode.name == "standBtn"){
23
        stand()
24
    }
25
}

Y ahora implementaremos los métodos llamados en el manejador de evento. Ingresa los siguientes dos métodos debajo del método newGame.

1
 func hit(){
2
    if(player1.getCanBet()){
3
        currentPlayerType = player1
4
        deal()
5
        player1.setCanBet(canBet: false)
6
    }
7
}
8
9
func stand(){
10
    player1.setYielding(yields: true)
11
    standBtn.isHidden = true
12
    hitBtn.isHidden = true
13
    if(dealer.hand.getValue() < 17){
14
        currentPlayerType = dealer
15
        deal();
16
    }else{
17
        doGameOver(hasBlackJack: false)
18
    }
19
}

Dentro del método hit, nos aseguramos de que ese jugador puede apostar, y si ese es el caso, establecemos el currentPlayerType a player1, y después invocamos el método deal y evitamos que el jugador siga apostando.

Dentro del método stand, invocamos setYielding sobre player1, pasando true. Después revisamos si el valor de la mano del repartidor es menos a 17, y si ese es el caso llamamos a deal, y si el valor de la mano del repartidor es 17 o mayor significa que el juego ha terminado.

Ahora puedes probar el juego completado.

Conclusión

Este fue un tutorial largo con mucha lógica escondida en el método deal. No implementamos usando el Pot y agregando en sustraer dinero del banco del jugador. ¿Por qué no intentas eso como un ejercicio para terminar la aplicación?

Ahora tienes un juego blackjack del cuál estar orgulloso. Gracias por leer, y espero que encuentres este tutorial útil. Mientras estás aquí, ¡revisa algunos de nuestros otros cursos y tutoriales sobre programar aplicaciones con Swift y SpriteKit!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.