In the previous part of this series, we put our interface together for a Blackjack game and created the deck of cards. In this part of the tutorial, we’ll add the necessary Blackjack game logic. Let’s get started!
17. CreateDataFile()
We need a way to store the player’s money between game sessions, and to do this we’ll use a simple text file. Add the following code beneath the createDeck()
function.
function createDataFile() local path = system.pathForFile( "data.txt", system.DocumentsDirectory ) local fh, errStr = io.open( path, "r" ) -- r means read mode if fh then print("DataFile Exists Already") -- already exists so we just return from this function return else print( "Reason open failed: " .. errStr ) -- display failure message in terminal -- create file because it doesn't exist yet fh = io.open( path, "w" ) -- w means write mode if fh then local money = 500 fh:write(money) else print( "Create file failed!"..errStr ) end io.close(fh) end end
Here we are creating a file named “data.txt” and writing 500 to it. The player will start the game with $500.00. It’s important to make sure you always call io.close()
when you’re finished with your operations.
You can learn more about creating this data file in the documentation on the Corona site.
18. ReadFile()
Now that we have a way to create our data file, we need a method to read its contents. Enter the following beneath the createDataFile()
function you entered in the step above.
function readMoney() local path = system.pathForFile( "data.txt", system.DocumentsDirectory ) local fh, errStr = io.open( path, "r" ) if fh then local theMoney = fh:read( "*n" ) return theMoney else print( "Reason open failed: " .. errStr ) -- display failure message in terminal end io.close(fh) end
We open the file using the same method, then we use read("*n")
to get the value out of the text file. The “*n” means read as a number.
19. SaveMoney()
To complete our file handling operations, we need a way to save. Enter the following beneath the code you entered in the step above.
function saveMoney(money) local path = system.pathForFile( "data.txt", system.DocumentsDirectory ) local fh, errStr = io.open( path, "w" ) if fh then fh:write(money) else print( "Reason open failed: " .. errStr ) -- display failure message in terminal end io.close(fh) end
Here we open the file for writing as denoted by the “w” in the open()
method. We then write money
to the file that was passed in as a parameter.
20. Setting the Initial Balance
We now need to create the initial balance when the game first starts. Add the following to the top of Setup()
.
function Setup() createDataFile(); setupCoins(); setupButtons(); setupTextFields(); setupGroups(); createDeck(); end
If you open the Corona terminal and run the app twice you should see “DataFile Exists Already” printed out to the terminal. I left the print()
messages in the file handling code so you could see the steps and any errors. If all is working well, feel free to remove them.
21. Showing Initial Balance
So now that we have the balance set, let’s show it in our app. Change the following code within the setupTextFields()
function.
function setupTextFields() instructionsText = display.newText( "Place Your Bet", 300, 300, native.systemFont, 30 ); instructionsText:setTextColor( 0,0,0) bankText = display.newText("Your Bank:$ "..readMoney(),10,905,native.systemFont, 30 ); bankText:setTextColor(0,0,0) betText = display.newText("",650,906,native.systemFont,30); betText:setTextColor(0,0,0); end
Note that we are appending the balance onto “Your Bank:$” by calling the readMoney()
function.
22. Betting
Now that we have the money in place we can add the code to our betHandler()
function. We created this function in the previous part of the tutorial, so make sure you add to it instead of redefining it!
local betHandler = function( event ) local theMoney = readMoney(); if event.phase == "began" then local t = event.target if(bet + t.betAmount > theMoney)then print("Trying to bet more than have"); print("Money is"..theMoney); return; else bet = bet + t.betAmount local tempImage = display.newImage("money"..t.betAmount..".png"); local randomX = (math.random() * 150); local randomY = (math.random() * 100); tempImage.x = randomX; tempImage.y = randomY; coinContainer:insert(tempImage); dealBtn.isVisible = true; instructionsText.text = ""; betText.text = "Your Bet: $"..bet; end end end
Here we first read how much money the player has. If they are trying to bet more than they have, the function simply returns. I’ve left the print()
statements in the code to help with debugging. We set a dynamic key, betAmount
, when we set up the money. If they are not trying to bet too much, we add the amount to the bet
variable.
Next we create a tempImage
, generate two random numbers, set the images x
and y
to the random numbers, and finally add the image to the coin container. You’ll notice that we are using "money"..t.betAmount..".png"
for the image URL. Our images for the money are named “money10.png“, “money25.png” and “money50.png“, so all we are doing here is concatenating them together to form the image string.
Lastly, we set the dealBtn
to be visible, clear out the instructionsText
and set the betText
equal to the bet
.
Now we need to add the addListeners()
function to our Setup()
code. Add the following code at the bottom.
function Setup() createDataFile() setupCoins(); setupButtons(); setupTextFields(); setupGroups(); createDeck(); addListeners(); end
If you test the app now, you should be able to bet some money.
23. Getting Hand Values
We need a way to get the hand value of the player’s hand and the dealer’s hand. Enter the following beneath the createDeck()
function.
function getHandValue(theHand) local handValue = 0; local hasAceInHand=false; for i=1,#theHand do local cardsValue = tonumber(string.sub(theHand[i],2,3)); if (cardsValue > 10) then cardsValue = 10; end handValue = handValue + cardsValue; if (cardsValue == 1)then hasAceInHand = true; end end if (hasAceInHand and handValue <= 11)then handValue = handValue + 10; end return handValue; end
We set up a handValue
variable and a hasAceInHand
variable. Next, we loop through theHand
which will either be the playerHand
or the dealerHand
. We create a variable cardsValue
, casting it to a number by getting a substring of the current card. If cardsValue
is greater than 10 we set it to 10. Jacks, Queens, and Kings are represented by 11, 12, and 13. We then add the value to handValue
. If the card’s value is equal to 1 then we know they have an ace in their hand. If they have an ace and their handValue
is less than or equal to 11 we add 10 to it.
24. Deal()
We now have everything in place for a deal, so now we’ll animate the cards to make the game more interesting. This function is quite large because it’s where all of the game’s logic takes place. We’ll divide it into several steps. Add the following code within the deal()
function you created in the first part of this series.
money10.isVisible = false; money25.isVisible = false; money50.isVisible = false; dealBtn.isVisible = false; local randIndex = math.random(#deck) local tempCard = display.newImage("card_front.png",630,55); table.insert(allCards,tempCard); local whichPosition; local whichArray={}; local whichGroup; if(dealTo == "player") then whichArray = playerHand; whichPosition = playerCardsY; whichGroup = playerGroup; else whichArray = dealerHand; whichPosition = dealerCardsY; whichGroup = dealerGroup; end table.insert(whichArray,deck[randIndex]); local xPos = 20+#whichArray*35 transition.to(tempCard, {time=1000,x=xPos,y=whichPosition,onComplete=function() if(dealTo == "dealer" and #dealerHand == 1) then firstDealerCard = deck[randIndex]; dealerGroup:insert(tempCard); else tempCard:removeSelf(); tempCard = display.newImage(deck[randIndex]..".png",xPos-45,whichPosition-60); whichGroup:insert(tempCard); end table.remove(deck,randIndex); if(#dealerHand < 2)then if(dealTo == "player")then dealTo = "dealer" else dealTo = "player" end deal(); end end });
Here we set our money to be invisible, and our deal button to visible. Next we generate a randIndex
from the deck
table. We then generate a new image tempCard
, and insert the tempCard
into the allImages
table. We set up three local variables, whichPosition
, whichArray
, and whichGroup
. We check who is currently being dealt to initialize these variables as appropriate.
We then insert deck[randIndex]
into whichArray
, which is either the playerHand
or the dealerHand
table. Remember that our deck is composed of strings, so deck[randIndex] would be something like “h5“,”d10“.
We set a local variable xPos
equal to 20+#whichArray*35
, which sets it to 20 plus the table’s length + 35. The first time through the table length would be 1, so 20+1*35. The next time through the table length would be 2 so it would be 20+2*35. All of this is to allow us to space our cards evenly along the X axis.
We use coronas transition.to
method to move the tempCard
. When the card completes its transition, we check whether we are dealing to the dealer and if his hand length is 1. If so, we set firstDealerCard
equal to deck[randIndex]
, and insert the card into the dealerGroup
. We need a reference to the dealer’s first card so that we can show it later.
If this was not the dealer’s first card, then we remove the tempCard
, generate a new image by using deck[randIndex]
, and insert it into the appropriate group (player or dealer). The reason we subtract 45 and 60 respectively is because Corona sets the reference point of images to the center by default, and our images are 90 x 120. Consequently, we take half of that.
Lastly we remove the card at position randIndex
from the deck
table and check whether dealerHand
length is less than 2. If it is, we change dealTo
to its opposite (player or dealer) and then deal again.
Finally, you can test the app, bet some money and get the first two cards dealt.
25. Deal() Continued…
Add the following code beneath where we called deal()
in the step above.
if(#dealerHand < 2)then if(dealTo == "player")then dealTo = "dealer" else dealTo = "player" end deal(); elseif(#dealerHand == 2 and #playerHand == 2) then if(getHandValue(playerHand)==21 or getHandValue(dealerHand) == 21)then doGameOver(true); else standBtn.isVisible = true; hitBtn.isVisible = true; end end
Here we are checking if both dealerHand
and playerHand
‘s length are equal to 2. If they are, we’ll check to see if either of their hands are equal to 21. If either of their hands are equal to 21, the game is over. We call doGameOver(true)
which will award winnings and start a new game. The true
parameter is true for blackjack. Otherwise, it will be false.
26. DoGameOver()
The doGameOver()
function awards the winnings and starts a new game. We’ll also be coding this function in several steps. For now we can use it to test the blackjack part. Enter the following code beneath the deal
function.
function doGameOver(hasBlackJack) local playerHandValue = getHandValue(playerHand); local dealerHandValue = getHandValue(dealerHand); local tempCardX = allCards[2].x; local tempCardY= allCards[2].y; allCards[2]:removeSelf(); local tempCard = display.newImage(firstDealerCard..".png",tempCardX-45,tempCardY-60); dealerGroup:insert(tempCard); tempCard:toBack(); if(hasBlackJack)then if(playerHandValue > dealerHandValue)then money = money + bet*1.5; instructionsText.text = "You Got BlackJack!"; winner = "player" else money = money - bet; instructionsText.text = "Dealer got BlackJack!"; winner = "dealer" end end end
Here we get the player’s and the dealer’s hand value. We get a reference to allCards[2]
, x
, and y
, which is the dealer’s first card, and then we remove it from the display. We then generate a tempCard
by using the firstDealerCard
variable we setup earlier. Once again we subtract 45 and 60. We then insert this new card into the dealerGroup
. When we do this, it’s on top of the second card, so we send it to the back by calling toBack()
.
We check to see if hasBlackJack
is true, and if it is we then check whether the player’s hand is greater than the dealer’s hand. If it is, we award some money, set the instructionsText
accordingly, and change winner
to “player“.
27. Initializing the Money
We need to remember to initialize the money
variable before we do anything with it. Add the following within the Setup()
function.
function Setup() createDataFile(); money = readMoney(); setupCoins(); ..... end
28. Testing for Blackjack
We are at the point where we can test to see if the player or dealer has blackjack. We’ll temporarily change some code to test, but we’ll change it back. First, in the deal
function, change the following.
elseif(#dealerHand == 2 and #playerHand == 2) then if(true)then doGameOver(true);
Then within the doGameOver()
function change the first two line like so.
local playerHandValue = 21--getHandValue(playerHand); local dealerHandValue = 18--;getHandValue(dealerHand);
Now go ahead and test the app. You should see that the player gets blackjack.
Now change the first two lines inside the doGameOver
to the following
local playerHandValue = 18--getHandValue(playerHand); local dealerHandValue = 21--;getHandValue(dealerHand);
Now if you test, you should see the dealer has gotten blackjack.
29. Resetting From Testing
Now that we’ve tested, we should set our variables back. Inside the deal
function change the following.
elseif(#dealerHand == 2 and #playerHand == 2) then if(getHandValue(playerHand)==21 or getHandValue(dealerHand) == 21)then doGameOver(true);
Then within the doGameOver()
function, change the first two lines back to their previous state.
local playerHandValue = getHandValue(playerHand); local dealerHandValue = getHandValue(dealerHand);
If you test once more, and neither you nor the dealer gets blackjack, the deal button should become invisible and the hit and stand buttons should become visible.
30. Hit()
We need to allow the player to hit or stand once the first two cards have been dealt. Enter the following within the hit()
function you entered in the previous part of this series.
function hit(event) if("began" == event.phase)then dealTo = "player"; deal(); end end
If you test now, you should be able to hit.
After some quality control testing, you might have noticed that the player can rapidly press the hit button over and over, dealing many cards at once. This isn’t how the game should work. To fix it, we need to add a condition to make sure that they can only hit at the appropriate time. Add the following to the bottom of your variable declarations.
local canBet=true;
Now, change the hit()
function to the following.
function hit(event) if("began" == event.phase)then if(canBet)then dealTo = "player"; deal(); canBet = false; end end end
Within the deal()
function, add the following line of code.
transition.to(tempCard, {time=1000,x=xPos,y=whichPosition,onComplete=function() canBet = true;
Now the player can only hit once, the card has finished being dealt.
31. Stand()
Next we need to allow the player to stand. Add the following within the stand()
function you entered during the previous part of this series.
function stand() playerYields = true; standBtn.isVisible = false; hitBtn.isVisible = false; if(getHandValue(dealerHand) < 17)then dealTo = "dealer" deal(); else doGameOver(false); end end
Here we indicate that the player is “holding.” Set the standBtn
and hitBtn
to invisible. We check to see if the dealer’s hand is less than 17, and if it is we change dealTo
to the dealer and deal. If his hand is not less than 17, then we call doGameOver()
. The dealer has to stand on 17 or greater.
If you test now, the player can get the hand they want and then press stand. There are a couple of problems, however. If the player busts, he can continue drawing cards and the dealing pauses at the dealer. We need the dealer to continue drawing cards until he gets over 17 or over, or until he busts. We will fix these issues when we finish off our deal()
function in the next two steps.
32. Deal() Continued…
Add the following within the deal()
function.
if(getHandValue(playerHand)==21 or getHandValue(dealerHand) == 21)then doGameOver(true); else standBtn.isVisible = true; hitBtn.isVisible = true; end end if( #dealerHand >=3 and(getHandValue(dealerHand) < 17))then deal(); elseif( playerYields and getHandValue(dealerHand)>=17)then standBtn.isVisible = false; hitBtn.isVisible = false; doGameOver(false); end
Here we check to see if the dealerHand
‘s length is greater than or equal to 3 and that the dealer’s hand is less than 17. If his hand is less than 17 he has to draw a card. Otherwise, we check to see if the player has yielded and if the dealer’s hand is greater than or equal to 17. If so, the game is over. It’s possible for dealer to have 17 or greater with the first two cards.
33. Finishing the Deal() Function
Enter the following code within the deal()
function.
if( #dealerHand >=3 and(getHandValue(dealerHand) < 17))then deal(); elseif( playerYields and getHandValue(dealerHand)>=17)then standBtn.isVisible = false; hitBtn.isVisible = false; doGameOver(false); end if(getHandValue(playerHand)>21)then standBtn.isVisible = false; hitBtn.isVisible = false; doGameOver(false); end
If the player hits and draws a card that puts him over 21, the game is over.
34. GameOver() Continued…
In this step we’ll continue coding the gameOver()
function. As of now, the deal function only determines who wins when either the player or dealer has blackjack. We need to handle all of the other possible outcomes. Enter the following within the doGameOver()
function.
else money = money - bet; instructionsText.text = "Dealer got BlackJack!"; winner = "dealer" end else if (playerHandValue > 21)then instructionsText.text = "You Busted!"; money = money - bet; winner = "dealer"; elseif (dealerHandValue > 21)then money = money + bet; instructionsText.text ="Dealer Busts. You Win!"; winner = "player"; elseif (dealerHandValue > playerHandValue)then money = money - bet; instructionsText.text ="You Lose!"; winner = "dealer" elseif (dealerHandValue == playerHandValue)then money = money - bet; instructionsText.text = "Tie - Dealer Wins!"; winner = "tie" elseif (dealerHandValue < playerHandValue)then money = money +bet; instructionsText.text="You Win!"; winner = "player" end end
If you test the code now, you should be able to play a full game. The instruction text will show the outcome. Play a few rounds and make sure everything seems correct. If you really want to test the game throughly by entering different hand values, you can use the same technique we used earlier in this tutorial to test for blackjack.
35. GameOver() Continued…
Enter the following at the bottom of the doGameOver()
function
elseif (dealerHandValue < playerHandValue)then money = money +bet; instructionsText.text="You Win!"; winner = "player" end end if(money < 10)then money = 500 end saveMoney(money)
After each round we should save the player’s money. If their money is less than 10 we’ll consider that bankrupt and reset their money to 500. As an exercise, see if you can get an alert to pop up saying something like “You’ve gone bankrupt, the dealer is awarding you $500.00.”
36. Finishing GameOver()
After each round we move the coins to the winner and then start a new game. Enter the following beneath the code you entered in the step above.
saveMoney(money) local tweenTo; if(winner == "player")then tweenTo = playerCardsY; else tweenTo = dealerCardsY end transition.to(coinContainer, {time=1000,y=tweenTo,onComplete=function() for i=coinContainer.numChildren,1,-1 do local child = coinContainer[i] child:removeSelf() child = nil; end timer.performWithDelay( 2000, newGame); coinContainer.y = 600; end });
Here we see who won the round, and then we animate the coins to them. When the animation completes we remove all the coins from the coinContainer
, and set them to nil since we are done with them. Lastly, we call newGame()
after two seconds, we also reset our coinContainer
position.
37. NewGame()
Enter the following beneath the doGameOver()
function.
function newGame() instructionsText.text = "PLACE YOUR BET"; betText.text = ""; money10.isVisible = true; money25.isVisible = true; money50.isVisible = true; bankText.text = "Your Bank: $"..readMoney() for i=dealerGroup.numChildren,1,-1 do local child = dealerGroup[i] child:removeSelf() child = nil; end for i=playerGroup.numChildren,1,-1 do local child = playerGroup[i] child:removeSelf() child = nil; end dealTo = "player"; playerHand = {}; dealerHand = {}; allCards = {}; createDeck(); firstDealerCard=""; playerYields = false; winner = ""; bet = 0; canBet = true; end
Here we set the money to visible, remove the cards from both the player and the dealer groups, and reset all of our variables.
Conclusion
We’ve coded a fun and interesting blackjack game using the Corona SDK. Thanks for reading, I hope you found this tutorial helpful!