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



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.



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.



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.



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.



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 SKSpriteNode
s. Los SKSpriteNode
s 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 money10
, money25
, 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 SKSpriteNode
s 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!