Advertisement
Corona SDK

Making a Blackjack Game in Corona - Implementing Gameplay

by

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!

Related Posts