Crear un juego de poker en Corona: Logica de Juego
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
|
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.



