Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Build a Poker Game in Corona: Game Logic

$
0
0

In the first part of this tutorial, we set up the project and created the game's interface. We also created and implemented a function to create a deck of cards. In this second tutorial, we will create the game logic.


Sample Application

If you want to run the sample application of this tutorial, make sure to include images for the cars as I explained in the previous tutorial. Don't forget to include and update the dataSaver library mentioned in this tutorial.


1.enableDealButton

Update the implementation of the enableDealButton function as shown below.

function enableDealButton()
  disableDealButton()
  dealButton:addEventListener('tap',doDeal)
  instructionsText.text = " "
end

We first call disableDealButton, which removes any previously added listeners, and add a tap listener, which invokes doDeal. The addEventListener method accepts an event and a callback. There are a number of events you can listen for, depending on the context in which you are calling it.


2.disableDealButton

As I mentioned in the previous section, in disableButton we remove any previously added listeners.

function disableDealButton()
  dealButton:removeEventListener('tap',doDeal)
end

3.enableBetButtons

In enableBetButtons, we add tap listeners to betMaxButton and betButton, and give the player some instructions on how to place their bet.

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

4.disableBetButtons

As in disableDealButton, we remove any previously added listeners in disableBetButtons.

function disableBetButtons()
  betMaxButton:removeEventListener('tap',betMax)
  betButton:removeEventListener('tap',bet)
end

5.enableHoldButtons

In enableHoldButtons, we loop through the holdButtons table and add a tap listener to each button.

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

6.disableHoldButtons

In the disableHoldButtons function, we also loop through the holdButtons table, but we remove previously added listeners instead of adding new listeners.

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

7.generateCard

The implementation of generateCard requires a bit more explanation. We first generate a random number from 1 to the length of the deck table. We then create a temporary card using deck["randIndex]..".png" and store a reference in tempCard. What deck["randIndex]..".png" does, is grab a random element from the deck table, which would be something like c1 or h5 and adds .png to it. Because Lua is a dynamic language, we can add new properties to the objects. In this example, we add a isHolding property, which tells us whether the player is holding the card, a cardNumber property by getting a substring of the chosen deck element, and we do the same for the cardSuit property. Finally, we remove the chosen element from the deck table and return the array.

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

8.getCard

In getCard, we set the cardPosition, which is the x coordinate of the first card in the game's interface. We generate a card, add it to the playerHand table, and pass in a cardIndex variable, which will be a number between 1 and 5, representing one of the five cards. This enables us to position the cards in the proper order in the playerHand table. We set the position of each card by using an offset of (93 * (cardIndex - 1)). This means that the cards are 93 pixels apart from one another.

function getCard(index)
  local cardPosition = 199
  local tempCard = generateCard()
  playerHand[cardIndex] = tempCard
  tempCard.x = cardPosition + (93*(cardIndex-1))
  tempCard.y = 257;
end

9.holdCard

In holdCard, we first obtain a reference to the button that was pressed by using its buttonNumber property. This enables us to check whether the card is in the playerHand table. If it is, we set isHolding to false and update the card's y coordinate. If the card isn't in the playerHand table, we set isHolding to true and update the card's y coordinate. If the player chooses to hold the card, it's y coordinate is decreased, which means the card is moved up slightly.

function holdCard(event)
  local index = event.target.buttonNumber
  if (playerHand[index].isHolding == true) then
    playerHand[index].isHolding = false
    playerHand[index].y = 257
  else
    playerHand[index].isHolding = true
    playerHand[index].y = 200
  end
end

10.resetCardsYPosition

In resetCardsYPosition, we loop through the playerHand table and see if any of the cards are being held. The ones that are, are moved back to their original position using the Transition library. The Transition library makes it very easy to move objects around and tween their properties.

function resetCardsYPosition()
  for i=1,#playerHand do
    if (playerHand[i].isHolding) then
      transition.to(playerHand[i], {time=200,y=257})
    end
  end
end

11. Persisting Data Across Sessions

We want our game to be able to persist values or data across sessions. We can build a solution ourselves using Corona's io library, but in this tutorial we are going to use a third party solution. Arturs Sosins has made a handy little module for persisting data across game sessions.

Download the library and add the two files it contains to your project. To make use of the library, add the following line at the top of main.lua.

saver = require("dataSaver")

To make the library work in our project, we need to make a few minor changes to dataSaver.lua. Open this file and change require "json" to local json = require "json".

Change:

require "json"

To:

local json = require "json"

The second change we need to make is change all the occurrences of system.ResourceDirectory to system.DocumentsDirectory as shown below.

Change:

system.ResourceDirectory

To:

system.DocumentsDirectory

12.createDataFile

To set up a data store, insert the following code snippet below the setupTextFields function. Make sure to include the function definition as we didn't stub this function in the previous tutorial.

function createDataFile()
  gameData = saver.loadValue("gameData")
  if (gameData == nil) then
    gameData = {}
    gameData.numberOfCredits = 100
    gameData.numberOfGames = 0
    creditText.text = "100"
    gamesText.text = "0"
    saver.saveValue("gameData",gameData)
  else
    creditText.text = gameData.numberOfCredits
    gamesText.text = gameData.numberOfGames
   end
end

In createDataFile, we first attempt to load the gameData key from the saver into the gameData variable. The loadValue method returns nil if the key doesn't exist. If it doesn't exist, we initialize the gameData table, add numberOfCredits and numberOfGames properties, update the respective text fields, and save the gameData table by invoking saveValue on saver. If the key does exist, then we've already done this and we can populate the text fields with the correct values.

In the next step, we invoke the createDataFile function in the setup function as shown below.

function setup()
  math.randomseed(os.time())
  setupButtons()
  setupTextFields()
  createDataFile()
end

13.betMax

In betMax, we start by loading our data into gameData. If the number of credits is greater than or equal to 15, we go ahead and call doDeal. Otherwise, the player does not have enough credits to bet the maximum of 15 credits and we show the player an alert.

function betMax()
  local gameData = saver.loadValue("gameData")
  local numberOfCredits = gameData.numberOfCredits
  if (numberOfCredits >= 15) then
    enableDealButton()
    betAmount = 15;
    betText.text = betAmount
    instructionsText.text = " "
    doDeal()
  else
    local alert = native.showAlert( "Not Enough Credits", "You must have 15 or more Credits to Bet Max", { "OK"})
  end
end

14.bet

In the bet function, we enable the deal button and remove the listener from betMaxButton. Since the player is doing a regular bet, she cannot play a maximum bet at the same time. We need to check if the number of credits is greater than or equal to 5 to make sure that the player isn't trying to bet more credits than they have left. If betAmount is equal to 15, we call doDeal as they've bet the maximum amount.

function bet()
  enableDealButton()
  betMaxButton:removeEventListener('tap',betMax)
  instructionsText.text = " "
  local numberOfCredits = tonumber(creditText.text - betAmount)
  if (numberOfCredits >= 5) then
    betAmount = betAmount + 5
    betText.text = betAmount
  else
    doDeal()
  end

  if (betAmount == 15) then
    doDeal()
  end
end

15.doDeal

The doDeal function coordinates the dealing of the cards. If it's a new game, we deal the initial hand. Otherwise, we deal the player new cards.

function doDeal()
  if(isNewGame == true) then
    isNewGame = false
    dealInitialHand()
  else
    dealNewCards()
  end
end

16.dealInitialHand

We disable the bet buttons in dealInitialHand and enable the hold buttons. We invoke getCard five times, which generates the initial five cards. We then load gameData, update currentCredits, calculate newCredits, update credit text field, and save gameData.

function dealInitialHand()
  disableBetButtons()
  enableHoldButtons()
  for i=1, 5 do
    getCard(i)
  end
  local gameData = saver.loadValue("gameData")
  local currentCredits = gameData.numberOfCredits
  local newCredits = currentCredits - betAmount
  creditText.text = newCredits
  gameData.numberOfCredits = newCredits
  saver.saveValue("gameData",gameData)
end

17.dealNewCards

In dealNewCards, we check whether the player is holding a card. If they are, then we get a reference to the current card, call removeSelf, set it to nil, and get a new card by invoking getCard(i).

function dealNewCards()
  disableDealButton()
  disableHoldButtons()
  for i = 1, 5 do
    if (playerHand[i].isHolding == false) then
      local tempCard = playerHand[i]
      tempCard:removeSelf()
      tempCard = nil
      getCard(i)
    end
  end
  resetCardsYPosition()
  getHand()
end
Whenever you remove something from the screen, you should always set it to nil to make sure it is setup for garbage collection properly.

18.getHand

The getHand function determines the player's hand. As you can see below, the function is pretty involved. Let's break if down to see what's going on. Start by implementing the getHand function as shown below.

function getHand()
  table.sort(playerHand, function(a,b) return a.cardNumber < b.cardNumber end)
  local frequencies = {}
  for i=1, 13 do
    table.insert(frequencies,0)
  end

  for i=1,#playerHand do
    frequencies[playerHand[i].cardNumber] = frequencies[playerHand[i].cardNumber]  + 1
  end

  local numberOfPairs = 0
  local hasThreeOfAKind = false
  local hasFourOfAKind = false
  local winningHand = "Nothing"
  local cashAward = 0
  local isStraight = true
  local isRoyalStraight = false
  local isFlush = true

  for i=0, #frequencies do
    if (frequencies[i] == 2) then
      numberOfPairs = numberOfPairs+1
    end

    if (frequencies[i] == 3) then
      hasThreeOfAKind = true
    end

    if (frequencies[i] == 4) then
      hasFour = true
    end
  end

  if (numberOfPairs > 0) then
    if(numberOfPairs == 1)then
      winningHand = "1 pair"
      cashAward = 1 * betAmount
    else
      winningHand = "2 pair"
      cashAward =  2 * betAmount
    end
  end

  if (hasThreeOfAKind) then
    winningHand = "3 of A Kind"
    cashAward = 3 * betAmount
  end

  if (hasFour) then
    winningHand = "Four of A Kind"
    cashAward = 7 * betAmount
  end

  if (numberOfPairs == 1 and hasThreeOfAKind) then
    winningHand = "Full House"
    cashAward = 6 * betAmount
  end

  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
    isRoyalStraight = true
  end

  for i=1, 4 do
    if (playerHand[i].cardNumber+1 ~= playerHand[i+1].cardNumber) then
      isStraight = false
      break
    end
  end

  for i=1, 5 do
    if(playerHand[i].cardSuit ~= playerHand[1].cardSuit) then
      isFlush = false
      break
    end
  end

  if (isFlush) then
    winningHand = "Flush"
    cashAward = 5 * betAmount
  end

  if (isStraight) then
    winningHand = "Straight"
    cashAward = 4 * betAmount
  end

  if (isRoyalStraight)then
    winningHand = "Straight"
    cashAward = 4 * betAmount
  end

  if (isFlush and isStraight) then
    winningHand = "Straight Flush"
    cashAward = 8 * betAmount
  end

  if (isFlush and isRoyalStraight) then
    winningHand = "Royal Flush"
    cashAward = 9 * betAmount
  end

  awardWinnings(winningHand, cashAward)
end

We start by calling table.sort on the playerHand table. The sort method does an in-place sort, it uses the < operator to determine whether an element of the table should come before or after another element. We can pass a comparison function as the second argument. In our example, we check whether the cardNumber property is less than the previous one. If it is, then it swaps the two elements.

We then create a frequencies table, populate it with thirteen zeros (0), loop through the playerHand table, and increment each index's number if the playerHand contains that number. For example, if you had two three's and three fives, then the frequencies table would be 0, 0, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0.

In the next step we declare and set a number of local variables that we need in a few moments. We then loop through the frequencies table and check the numbers to see if the index contains a 2, which means we have a pair, a 3, which means we have three of a kind, or a 4, which means we have four of a kind. Next, we check the number of pairs, three of a kind, and four of a kind, and update the winningHand and cashAward values accordingly.

To check for a Royal Straight, we need to check if the first card is equal to an ace and the remaining cards would be ten, Jack, Queen, and King. To check for a regular Straight we loop through the playerHand and check if each subsequent cardNumber is one greater than the previous. To check for a Flush, we check if all the cards cardSuit keys were equal to the first card's cardSuit key.

At the end of getHand, we invoke awardWinnings.


19.awardWinnings

In awardWinnings, we show the player what hand they have in their hand and update the gameData settings. We save gameData and invoke newGame with a delay of three seconds.

function awardWinnings(theHand, theAward)
  instructionsText.text  = "You got "..theHand
  winText.text = theAward
  local gameData = saver.loadValue("gameData")
  local currentCredits = gameData.numberOfCredits
  local currentGames = gameData.numberOfGames
  local newCredits = currentCredits + theAward
  local newGames = currentGames + 1
  gameData.numberOfCredits = newCredits
  gameData.numberOfGames = newGames
  saver.saveValue("gameData",gameData)
  timer.performWithDelay( 3000, newGame,1 )
end

20.newGame

In newGame, we go through and reset all the variables, create a new deck of cards, and check if gameData.numberOfCredits is equal to zero. If it is, then the player has expended all their credits so we award them 100 more credits. Finally, we update the text fields.

function newGame()
  for i=1,#playerHand do
    playerHand[i]:removeSelf()
    playerHand[i] = nil
  end
  playerHand = {}
  deck = {}
  betAmount = 0
  isNewGame = true
  createDeck()
  enableBetButtons()
  instructionsText.text = "Place your Bet or Bet Max ($15)"
  winText.text = ""
  betText.text = ""
  local gameData = saver.loadValue("gameData")

  if (gameData.numberOfCredits == 0)then
    gameData.numberOfCredits = 100
    saver.saveValue("gameData",gameData)
  end

  creditText.text = gameData.numberOfCredits
  gamesText.text = gameData.numberOfGames
end

21. Testing the Game

Update the setup function by invoking the createDeck function as shown below and test the final result.

function setup()
  math.randomseed(os.time())
  createDeck();
  setupButtons()
  setupTextFields()
  createDataFile()
end

Conclusion

In this tutorial, we created a fun and interesting Poker game. We did not implement the button to cash out yet, but you are free to do so in your game. I hope you learned something useful in this tutorial. Leave your feedback in the comments below.

2014-02-24T12:30:25.725Z2014-02-24T12:30:25.725ZJames Tynerhttp://code.tutsplus.com/tutorials/build-a-poker-game-in-corona-game-logic--mobile-22428

Viewing all articles
Browse latest Browse all 1836

Trending Articles