1. Code
  2. Mobile Development
  3. Corona

Crear un juego de poker en Corona: Logica de Juego

En la primera parte de este tutorial, configuramos el proyecto y creamos la interfaz del juego. También creamos e implementamos una función para crear un mazo de cartas. En este segundo tutorial, crearemos la lógica del juego.
Scroll to top

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

En la primera parte de este tutorial, configuramos el proyecto y creamos la interfaz del juego. También creamos e implementamos una función para crear un mazo de cartas. En este segundo tutorial, crearemos la lógica del juego.


Aplicación de muestra

Si desea ejecutar la aplicación de ejemplo de este tutorial, asegúrese de incluir imágenes para los autos como se explicó en el tutorial anterior. No olvide incluir y actualizar la biblioteca de dataSaver mencionada en este tutorial.


1. enableDealButton

Actualice la implementación de la función enableDealButton como se muestra a continuación.

1
2
function enableDealButton()
3
  disableDealButton()
4
  dealButton:addEventListener('tap',doDeal)
5
  instructionsText.text = " "
6
end

Primero llamamos disableDealButton, que elimina los escuchas agregados previamente, y agregamos un escuchador tap, que invoca doDeal. El método addEventListener acepta un evento y una devolución de llamada. Hay una serie de eventos que puede escuchar, según el contexto en el que lo llame.


2. disableDealButton

Como mencioné en la sección anterior, en disableButton eliminamos cualquier escuchas agregados previamente.

1
2
function disableDealButton()
3
  dealButton:removeEventListener('tap',doDeal)
4
end

3. enableBetButtons

En enableBetButtons, agregamos escuchas tap a betMaxButton y betButton, y le damos al jugador algunas instrucciones sobre cómo colocar su apuesta.

1
2
function enableBetButtons()
3
  betMaxButton:addEventListener('tap',betMax)
4
  betButton:addEventListener('tap',bet)
5
  instructionsText.text = "Place your Bet or Bet Max ($15)"
6
end

4. disableBetButtons

Como en disableDealButton, eliminamos cualquier escuchas agregados previamente en disableBetButtons.

1
2
function disableBetButtons()
3
  betMaxButton:removeEventListener('tap',betMax)
4
  betButton:removeEventListener('tap',bet)
5
end

5. enableHoldButtons

En enableHoldButtons, recorramos la tabla de holdButtons y agregamos un escucha de tap a cada botón.

1
2
function enableHoldButtons()
3
  for i=1, #holdButtons do
4
      holdButtons[i]:addEventListener('tap',holdCard)
5
  end
6
end

6. disableHoldButtons

En la función disableHoldButtons, también recorramos la tabla de holdButtons, pero eliminamos los escuchas agregados en lugar de agregar nuevos escuchas.

1
2
function disableHoldButtons()
3
  for i=1, #holdButtons do
4
      holdButtons[i]:removeEventListener('tap',holdCard)
5
  end
6
end

7. generateCard

La implementación de generateCard requiere un poco más de explicación. Primero generamos un número aleatorio de 1 a la longitud de la tabla deck. Luego creamos una tarjeta temporal utilizando el deck["randIndex]..".png" y almacenamos una referencia en tempCard. Lo que hace el deck["randIndex]..".png" es agarrar un elemento aleatorio de la mesa deck, que sería algo como c1 o h5 y le agrega .png. Debido a que Lua es un lenguaje dinámico, podemos agregar nuevas propiedades a los objetos. En este ejemplo, agregamos una propiedad isHolding, que nos dice si el jugador está sosteniendo la carta, una propiedad cardNumber al obtener una subcadena del elemento del deck elegido, y hacemos lo mismo con la propiedad cardSuit. Finalmente, eliminamos el elemento elegido de la tabla deck y devolvemos la matriz.

1
2
function generateCard()
3
  local randIndex = math.random(#deck)
4
  local tempCard = display.newImage(deck[randIndex]..".png")
5
  tempCard.anchorX, tempCard.anchorY = 0,0
6
  tempCard.isHolding = false
7
  tempCard.cardNumber  =  tonumber(string.sub(deck[randIndex],2,3))
8
  tempCard.cardSuit = string.sub(deck[randIndex],1,1)
9
  table.remove(deck,randIndex);
10
  return tempCard;
11
end

8. getCard

En getCard, configuramos cardPosition, que es la coordenada x de la primera carta en la interfaz del juego. Generamos una tarjeta, la agregamos a la tabla playerHand y pasamos una variable cardIndex, que será un número entre 1 y 5, que representa una de las cinco tarjetas. Esto nos permite colocar las cartas en el orden correcto en la mesa playerHand. Establecemos la posición de cada tarjeta utilizando un desplazamiento de (93 * (cardIndex - 1)). Esto significa que las tarjetas están separadas 93 píxeles entre sí.

1
2
function getCard(index)
3
  local cardPosition = 199
4
  local tempCard = generateCard()
5
  playerHand[cardIndex] = tempCard
6
  tempCard.x = cardPosition + (93*(cardIndex-1))
7
  tempCard.y = 257;
8
end

9. holdCard

En holdCard, primero obtenemos una referencia al botón que se presionó usando su propiedad buttonNumber. Esto nos permite verificar si la carta está en la mesa de PlayerHand. Si es así, establecemos isHolding en falso y actualizamos la coordenada y de la tarjeta. Si la tarjeta no está en la tabla PlayerHand, configuramos isHolding en true y actualizamos la coordenada y de la carta. Si el jugador elige sostener la carta, su coordenada y disminuye, lo que significa que la carta se mueve ligeramente hacia arriba.

1
2
function holdCard(event)
3
  local index = event.target.buttonNumber
4
  if (playerHand[index].isHolding == true) then
5
    playerHand[index].isHolding = false
6
    playerHand[index].y = 257
7
  else
8
    playerHand[index].isHolding = true
9
    playerHand[index].y = 200
10
  end
11
end

10. resetCardsYPosition

En resetCardsYPosition, recorremos la tabla  PlayerHand y vemos si alguna de las cartas está siendo retenida. Los que están, vuelven a su posición original utilizando la biblioteca Transition. La biblioteca Transition hace que sea muy fácil mover objetos y interpolar sus propiedades.

1
2
function resetCardsYPosition()
3
  for i=1,#playerHand do
4
    if (playerHand[i].isHolding) then
5
      transition.to(playerHand[i], {time=200,y=257})
6
    end
7
  end
8
end

11. Datos persistentes a través de sesiones

Queremos que nuestro juego pueda conservar valores o datos en las sesiones. Podemos construir una solución nosotros mismos usando la biblioteca io de Corona, pero en este tutorial usaremos una solución de terceros. Arturs Sosins ha creado un pequeño y práctico módulo para la persistencia de datos en las sesiones de juego.

Descargue la biblioteca y agregue los dos archivos que contiene a su proyecto. Para hacer uso de la biblioteca, agregue la siguiente línea en la parte superior de main.lua.

1
2
saver = require("dataSaver")

Para hacer que la biblioteca funcione en nuestro proyecto, necesitamos hacer algunos cambios menores en dataSaver.lua. Abra este archivo y el cambie require "json"  a local json = require "json".

Cambio:

1
2
require "json"

A:

1
2
local json = require "json"

El segundo cambio que debemos hacer es cambiar todas las apariciones de system.ResourceDirectory a system.DocumentsDirectory como se muestra a continuación.

Cambie:

1
2
system.ResourceDirectory

A:

1
2
system.DocumentsDirectory

12. createDataFile

Para configurar un almacén de datos, inserte el siguiente fragmento de código debajo de la función setupTextFields. Asegúrese de incluir la definición de la función, ya que no hemos eliminado esta función en el tutorial anterior.

1
2
function createDataFile()
3
  gameData = saver.loadValue("gameData")
4
  if (gameData == nil) then
5
    gameData = {}
6
    gameData.numberOfCredits = 100
7
    gameData.numberOfGames = 0
8
    creditText.text = "100"
9
    gamesText.text = "0"
10
    saver.saveValue("gameData",gameData)
11
  else
12
    creditText.text = gameData.numberOfCredits
13
    gamesText.text = gameData.numberOfGames
14
   end
15
end

En createDataFile, primero intentamos cargar la clave gameData del protector en la variable gameData. El método loadValue devuelve nil si la clave no existe. Si no existe, inicializamos la tabla gameData, agregamos las propiedades numberOfCredits y numberOfGames, actualizamos los respectivos campos de texto y guardamos la tabla gameData invocando saveValue en saver. Si la clave existe, entonces ya lo hemos hecho y podemos rellenar los campos de texto con los valores correctos.

En el siguiente paso, invocamos la función createDataFile en la función setup como se muestra a continuación.

1
2
function setup()
3
  math.randomseed(os.time())
4
  setupButtons()
5
  setupTextFields()
6
  createDataFile()
7
end

13. betMax

En betMax, comenzamos cargando nuestros datos en gameData. Si el número de créditos es mayor o igual a 15, seguimos adelante y llamamos doDeal. De lo contrario, el jugador no tiene suficientes créditos para apostar el máximo de 15 créditos y le mostramos una alerta al jugador.

1
2
function betMax()
3
  local gameData = saver.loadValue("gameData")
4
  local numberOfCredits = gameData.numberOfCredits
5
  if (numberOfCredits >= 15) then
6
    enableDealButton()
7
    betAmount = 15;
8
    betText.text = betAmount
9
    instructionsText.text = " "
10
    doDeal()
11
  else
12
    local alert = native.showAlert( "Not Enough Credits", "You must have 15 or more Credits to Bet Max", { "OK"})
13
  end
14
end

14. bet

En la función de apuesta bet, habilitamos el botón de reparto y eliminamos el oyente de betMaxButton. Como el jugador está haciendo una apuesta regular, no puede jugar una apuesta máxima al mismo tiempo. Necesitamos verificar si el número de créditos es mayor o igual a 5 para asegurarnos de que el jugador no esté intentando apostar más créditos de los que le quedan. Si betAmount es igual a 15, llamamos a doDeal ya que han apostado la cantidad máxima.

1
2
function bet()
3
  enableDealButton()
4
  betMaxButton:removeEventListener('tap',betMax)
5
  instructionsText.text = " "
6
  local numberOfCredits = tonumber(creditText.text - betAmount)
7
  if (numberOfCredits >= 5) then
8
    betAmount = betAmount + 5
9
    betText.text = betAmount
10
  else
11
    doDeal()
12
  end
13
14
  if (betAmount == 15) then
15
    doDeal()
16
  end
17
end

15. doDeal

La función doDeal coordina el reparto de las cartas. Si es un juego nuevo, repartimos la mano inicial. De lo contrario, repartimos nuevas cartas al jugador.

1
2
function doDeal()
3
  if(isNewGame == true) then
4
    isNewGame = false
5
    dealInitialHand()
6
  else
7
    dealNewCards()
8
  end
9
end

16. dealInitialHand

Deshabilitamos los botones de apuesta en dealInitialHand y habilitamos los botones de espera. Invocamos a getCard cinco veces, lo que genera las cinco cartas iniciales. Luego cargamos gameData, actualizamos los créditos actuales currentCredits, calculamos nuevos créditos newCredits, actualizamos el campo de texto de crédito y guardamos gameData.

1
2
function dealInitialHand()
3
  disableBetButtons()
4
  enableHoldButtons()
5
  for i=1, 5 do
6
    getCard(i)
7
  end
8
  local gameData = saver.loadValue("gameData")
9
  local currentCredits = gameData.numberOfCredits
10
  local newCredits = currentCredits - betAmount
11
  creditText.text = newCredits
12
  gameData.numberOfCredits = newCredits
13
  saver.saveValue("gameData",gameData)
14
end

17. dealNewCards

En dealNewCards, verificamos si el jugador tiene una carta. Si lo son, obtenemos una referencia a la tarjeta actual, llamamos a removeSelf, la configuramos en nil, y obtenemos una nueva tarjeta invocando getCard(i)

1
2
function dealNewCards()
3
  disableDealButton()
4
  disableHoldButtons()
5
  for i = 1, 5 do
6
    if (playerHand[i].isHolding == false) then
7
      local tempCard = playerHand[i]
8
      tempCard:removeSelf()
9
      tempCard = nil
10
      getCard(i)
11
    end
12
  end
13
  resetCardsYPosition()
14
  getHand()
15
end
Cada vez que elimine algo de la pantalla, debe establecerlo en nil para asegurarse de que esté configurado correctamente para la recolección de basura.

18. getHand

La función getHand determina la mano del jugador. Como se puede ver a continuación, la función es bastante complicada. Vamos a romper si vamos a ver qué está pasando. Comience implementando la función getHand como se muestra a continuación.

1
2
function getHand()
3
  table.sort(playerHand, function(a,b) return a.cardNumber < b.cardNumber end)
4
  local frequencies = {}
5
  for i=1, 13 do
6
    table.insert(frequencies,0)
7
  end
8
9
  for i=1,#playerHand do
10
    frequencies[playerHand[i].cardNumber] = frequencies[playerHand[i].cardNumber]  + 1
11
  end
12
13
  local numberOfPairs = 0
14
  local hasThreeOfAKind = false
15
  local hasFourOfAKind = false
16
  local winningHand = "Nothing"
17
  local cashAward = 0
18
  local isStraight = true
19
  local isRoyalStraight = false
20
  local isFlush = true
21
22
  for i=0, #frequencies do
23
    if (frequencies[i] == 2) then
24
      numberOfPairs = numberOfPairs+1
25
    end
26
27
    if (frequencies[i] == 3) then
28
      hasThreeOfAKind = true
29
    end
30
31
    if (frequencies[i] == 4) then
32
      hasFour = true
33
    end
34
  end
35
36
  if (numberOfPairs > 0) then
37
    if(numberOfPairs == 1)then
38
      winningHand = "1 pair"
39
      cashAward = 1 * betAmount
40
    else
41
      winningHand = "2 pair"
42
      cashAward =  2 * betAmount
43
    end
44
  end
45
46
  if (hasThreeOfAKind) then
47
    winningHand = "3 of A Kind"
48
    cashAward = 3 * betAmount
49
  end
50
51
  if (hasFour) then
52
    winningHand = "Four of A Kind"
53
    cashAward = 7 * betAmount
54
  end
55
56
  if (numberOfPairs == 1 and hasThreeOfAKind) then
57
    winningHand = "Full House"
58
    cashAward = 6 * betAmount
59
  end
60
61
  if (playerHand[1].cardNumber == 1 and playerHand[2].cardNumber == 10 and playerHand[3].cardNumber == 11 and playerHand[4].cardNumber == 12 and playerHand[5].cardNumber == 13 )then
62
    isRoyalStraight = true
63
  end
64
65
  for i=1, 4 do
66
    if (playerHand[i].cardNumber+1 ~= playerHand[i+1].cardNumber) then
67
      isStraight = false
68
      break
69
    end
70
  end
71
72
  for i=1, 5 do
73
    if(playerHand[i].cardSuit ~= playerHand[1].cardSuit) then
74
      isFlush = false
75
      break
76
    end
77
  end
78
79
  if (isFlush) then
80
    winningHand = "Flush"
81
    cashAward = 5 * betAmount
82
  end
83
84
  if (isStraight) then
85
    winningHand = "Straight"
86
    cashAward = 4 * betAmount
87
  end
88
89
  if (isRoyalStraight)then
90
    winningHand = "Straight"
91
    cashAward = 4 * betAmount
92
  end
93
94
  if (isFlush and isStraight) then
95
    winningHand = "Straight Flush"
96
    cashAward = 8 * betAmount
97
  end
98
99
  if (isFlush and isRoyalStraight) then
100
    winningHand = "Royal Flush"
101
    cashAward = 9 * betAmount
102
  end
103
104
  awardWinnings(winningHand, cashAward)
105
end

Comenzamos llamando a table.sort en la tabla playerHand. El método sort realiza una clasificación en el lugar, utiliza el operador para determinar si un elemento de la tabla debe aparecer antes o después de otro elemento. Podemos pasar una función de comparación como segundo argumento. En nuestro ejemplo, verificamos si la propiedad cardNumber es menor que la anterior. Si es así, entonces intercambia los dos elementos.

Luego creamos una tabla frequencies de frecuencias, la rellenamos con trece ceros (0), recorramos la tabla playerHand e incrementamos el número de cada índice si el playerHand contiene ese número. Por ejemplo, si tuviera dos tres y tres cinco, entonces la tabla frequencies sería 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0.

En el siguiente paso declaramos y configuramos una serie de variables locales que necesitamos en unos momentos. Luego pasamos por la tabla frequencies y verificamos los números para ver si el índice contiene un 2, lo que significa que tenemos un par, un 3, lo que significa que tenemos tres de un tipo, o un 4, lo que significa que tenemos cuatro de un tipo. A continuación, verificamos el número de pares, tres de una clase y cuatro de una clase, y actualizamos los valores de winningHand y el cashAward en consecuencia.

Para verificar si hay un Royal Straight, necesitamos verificar si la primera carta es igual a un as y las restantes serían diez, Jack, Queen y King. Para verificar si hay un Straight normal, recorramos el PlayerHand y verificamos si cada cardNumber posterior es uno mayor que el anterior. Para verificar si hay Flush, verificamos si todas las claves de cardSuit de la combinación son iguales a la tarjeta de la primera cardSuit.

Al final de getHand, invocamos awardWinnings.


19. awardWinnings

En awardWinnings, le mostramos al jugador qué mano tienen en la mano y actualizamos la configuración de GameData. Guardamos gameData e invocamos newGame con un retraso de tres segundos.

1
2
function awardWinnings(theHand, theAward)
3
  instructionsText.text  = "You got "..theHand
4
  winText.text = theAward
5
  local gameData = saver.loadValue("gameData")
6
  local currentCredits = gameData.numberOfCredits
7
  local currentGames = gameData.numberOfGames
8
  local newCredits = currentCredits + theAward
9
  local newGames = currentGames + 1
10
  gameData.numberOfCredits = newCredits
11
  gameData.numberOfGames = newGames
12
  saver.saveValue("gameData",gameData)
13
  timer.performWithDelay( 3000, newGame,1 )
14
end

20. newGame

En newGame, revisamos y reiniciamos todas las variables, creamos un nuevo mazo de cartas y verificamos si gameData.numberOfCredits es igual a cero. Si es así, entonces el jugador ha gastado todos sus créditos, por lo que les otorgamos 100 créditos más. Finalmente, actualizamos los campos de texto.

1
2
function newGame()
3
  for i=1,#playerHand do
4
    playerHand[i]:removeSelf()
5
    playerHand[i] = nil
6
  end
7
  playerHand = {}
8
  deck = {}
9
  betAmount = 0
10
  isNewGame = true
11
  createDeck()
12
  enableBetButtons()
13
  instructionsText.text = "Place your Bet or Bet Max ($15)"
14
  winText.text = ""
15
  betText.text = ""
16
  local gameData = saver.loadValue("gameData")
17
18
  if (gameData.numberOfCredits == 0)then
19
    gameData.numberOfCredits = 100
20
    saver.saveValue("gameData",gameData)
21
  end
22
23
  creditText.text = gameData.numberOfCredits
24
  gamesText.text = gameData.numberOfGames
25
end

21. Probando el juego

Actualice la función setup invocando la función createDeck como se muestra a continuación y pruebe el resultado final.

1
2
function setup()
3
  math.randomseed(os.time())
4
  createDeck();
5
  setupButtons()
6
  setupTextFields()
7
  createDataFile()
8
end

Conclusión

En este tutorial, creamos un juego de póker divertido e interesante. Aún no implementamos el botón para retirar dinero, pero puedes hacerlo en tu juego. Espero que hayas aprendido algo útil en este tutorial. Deja tus comentarios en los comentarios a continuación.