BlackJack Application with JavaScript

8,643 reads

A few weeks ago I wrote an article about calculating the probability of certain outcomes in BlackJack using Python. If you want to read that article, feel free. https://hackernoon.com/gambling-probability-python-dfd3e301b1ad.

The idea was that you could set up a scenario in blackjack by creating a simulated game. Then simulate the outcome of that game a million times, and figure out what the probability is of certain outcomes.

It got me thinking though, how much more useful something like that would be if I created an actual game that would calculate those probabilities in real time. So, I set out to do that. In this post, I’ll talk about some ideas in how to go about creating such an application, if you’ve ever been interested in doing so, as well as some of the logic behind how I made mine work.

If you want to see the final product of my application before reading any further, here’s what I have so far:

https://safe-springs-65895.herokuapp.com/.

The API

To get started, I found this fantastic API for playing cards: https://deckofcardsapi.com/.

It’s a great tool for getting started, and allows you to make several types of API calls depending on your needs. For example, you can either get a brand new deck of cards, or shuffle an existing deck by using a deck ID#. Either way, after getting the deck of cards, you can draw a card from the deck. In theory, it would make sense to make a request that would draw one or two cards each time a player in the game clicks a button to draw a card. For example, the url for the deck shuffle looks like this:

https://deckofcardsapi.com/api/deck/new/shuffle/?deck_count=1

Then, if you wanted to draw a card, you would use:

https://deckofcardsapi.com/api/deck/<<deck_id>>/draw/?count=2

However, I found that for my purposes, I wanted to draw all 52 cards at once, and use the remaining values from the deck to calculate the probabilities I would use later. So when a player starts a new game, I would actually do this:

https://deckofcardsapi.com/api/deck/<<deck_id>>/draw/?count=52

And then using JavaScript, I would pull 2 cards from that array for the dealer, and 2 cards for the player.

A response from the API when drawing a card, would look like this:

{

"success": true,

"cards": [

{

"image": "https://deckofcardsapi.com/static/img/KH.png",

"value": "KING",

"suit": "HEARTS",

"code": "KH"

},

{

"image": "https://deckofcardsapi.com/static/img/8C.png",

"value": "8",

"suit": "CLUBS",

"code": "8C"

}

],

"deck_id":"3p40paa87x90",

"remaining": 50

}

So naturally, with blackjack, I just ignored the suites, since they don’t matter. Then, I went through and changed all the KINGS, QUEENS and JACKS value to 10. Finally for the Ace, my thought on that is that, you always want the value of the Ace to be 11, unless your total hand value is over 21, then you’d want the Ace to be 1, since you can choose either value for the Ace. So initially, when I cycle through my initial cards, I want turn all the ACE values to 11.

With that in mind, I needed to think about the structure of my game. I really only need 3 buttons. One, for a NEW GAME, which will reset all the values, except for the total wins and losses of each player. One for HIT, which will draw a card for the player. And one for STAND, which will check the values, and draw a card for the dealer if needed. At minimum, that’s really all I need. Here’s how I saw the flow of my game:

Initial Values and Arrays:

let allCards = [];

let iterations = 1000000;

let player = [];

let dealer = [];

let dealerValues = [];

let playerValues = [];

var playerSum ;

var dealerSum ;

let winsPlayer = 0;

let winsDealer = 0;

let testDeck = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11];

These are all the values that I’ll be working with in my game.

My thinking is that every time cards a drawn or updated, I’ll create a scenario in the background using testDeck . With testDeck, I’ll recreate the current scenario of card values, and determine certain game outcomes. The iterations value will determine the number of times I’ll test each scenario, currently set to 1 million. player and dealer will contain all the card information for each player. playerValues and dealerValues is an array I’ll push all the current card values to for each player, just to make it easier for me to loop through later. playerSum and dealerSum is what I’ll use to determine current totals for each player and determine winners. winsPlayer and winsDealer is what I’ll use to keep track of the current number of wins for each player. Finally, once I fetch my cards, I’m going to actually set that value as the allCards value. Since the fetched cards will exist inside the newGame function, I need to access them outside of that function.

HTML

My HTML basically looks pretty simple, but I’ll use the newGame , hit and stand IDs to create my onclick functions:

<body>

<div class="navContainer">

<div class="title8" id="newGame">new game</div>

<div class="title11" id="hit">hit</div>

<div class="title12" id="stand">stand</div>

</div>

<div class="title3">Dealer</div>

<div id="dealerCards"></div>

<div class="title2">Player</div>

<div id="playerCards"></div>

<div class="title4" id="dealerSum"></div>

<div class="title5" id="playerSum"></div>



<div id="currentPercent"></div>



<div id="currentBust"></div>



<div class="dealerWins">

<h1>Dealer Wins</h1>

<div id="dealerWins"></div>

</div>

<div class="playerWins">

<h1>Player Wins</h1>

<div id="playerWins"></div>

</div>

</body>

Back to the JavaScript

Here are really the only three onclick functions I’ll need, which I’ll create after setting all the initial values.

$('#newGame').click(function(){

})

$('#hit').click(function(){

})

$('#stand').click(function(){

})

NewGame

After declaring all my initial variables, now I need to set them to 0 or empty. This is because these variables will have values after I push data to them in the newGame, hit and stand functions. I also need to clear the HTML elements innerHTML, since this will be the beginning of a new round.

document.getElementById('dealerWins').innerHTML = winsDealer;

document.getElementById('playerWins').innerHTML = winsPlayer;

document.getElementById('dealerWins').innerHTML = '';

document.getElementById('playerWins').innerHTML = '';

let hit = document.getElementById('hit');

hit.disabled = false;

player = [];

dealer = [];

dealerValues = [];

playerValues = [];

playerSum = 0;

dealerSum = 0;

document.getElementById('dealerCards').innerHTML = "";

document.getElementById('playerCards').innerHTML = "";

document.getElementById('playerSum').innerHTML = "";

document.getElementById('dealerSum').innerHTML = "";

Making my API Calls

Next, I’ll make a request for the deck and cards:

response.json().then(function(data) {

let api = data;

let deckID = api.deck_id;

fetch('https://deckofcardsapi.com/api/deck/'+deckID+'/draw/?count=52').then(function(response) {

if (response.status != 200) {

window.alert("You done messed up");

return;

}

response.json().then(function(data) {

let firstDeal = data.cards;

Next, as I talked about earlier, I need to reset the values of the cards that contain “KING”, “QUEEN”, “JACK” and “ACE”. At the same time, I’ll push those values to my playerValues and dealerValues array. Then, with the cards with correct values, I’ll set the allCards variable to the value of the firstDeal variable, since it sits outside my newGame function.

for (var i = 0; i < firstDeal.length; i++) {

if(firstDeal[i].value == "KING" || firstDeal[i].value == "QUEEN" || firstDeal[i].value == "JACK"){

firstDeal[i].value = 10;

}

if(firstDeal[i].value == "ACE"){

firstDeal[i].value = 11;

}

if(firstDeal[i].value != "KING" && firstDeal[i].value != "QUEEN" && firstDeal[i].value != "JACK" && firstDeal[i].value != "ACE"){

firstDeal[i].value = parseInt(firstDeal[i].value);

}

}

allCards = firstDeal;

Drawing cards for each player

Next, I’ll need to draw cards for each player. I’ll do that by selecting a card from the allCards array, pushing it into either the dealer or player array, and updating both playerValues and dealerValues .

var card1 = allCards[Math.floor(Math.random()*allCards.length)];



let index1 = allCards.indexOf(card1);



allCards.splice(index1,1);



player.push(card1);





var card2 = allCards[Math.floor(Math.random()*allCards.length)];



let index2 = allCards.indexOf(card2);



allCards.splice(index2,1);



player.push(card2);





var card3 = allCards[Math.floor(Math.random()*allCards.length)];



let index3 = allCards.indexOf(card3);



allCards.splice(index3,1);



dealer.push(card3);





var card4 = allCards[Math.floor(Math.random()*allCards.length)];



let index4 = allCards.indexOf(card4);



allCards.splice(index4,1);



dealer.push(card4);

In the below function, I’ll cycle through the player cards, updated the DOM with their current hand, and update the playerValues with the current card values. With a forLoop, I can re-use this function throughout my code, since it doesn’t matter how many cards are currently in the players hand. It just takes all the cards and updates the DOM and playerValues. However, one thing to keep in mind here, is that, before running this function, you need to clear out the current playerValues and clear the player Cards on the DOM. Otherwise it will simply add to the current total. If you already have 3 cards and your value is 15. Then you add one card and re-calculate, it will add the new card to the current three. For example, if your new card is a 2, instead of a total of 17, your total would be 32 (15 + 15 + 2).

for (var i = 0; i < player.length; i++) {

card = `<img style="transform: rotate(${randomIntFromInterval(140,180)}deg);-webkit-box-shadow: 3px 2px 32px 4px rgba(0,0,0,0.88);width:100px;height:120px;" src="${player[i].images.png}" />`;

playerValues.push(player[i].value);

document.getElementById('playerCards').innerHTML += card;

}

This exact same function can also be interchanged for the dealer, but replacing the dealer variables with the current player variables. Since we are in the newGame function, we don’t need to clear the current values, since they are already empty. But when running the function in either the hit or stand function, we would need to clear the values first. For example, we would do this:

document.getElementById('playerCards').innerHTML = "";

playerValues = [];



for (var i = 0; i < player.length; i++) {

card = `<img style="transform: rotate(${randomIntFromInterval(140,180)}deg);-webkit-box-shadow: 3px 2px 32px 4px rgba(0,0,0,0.88);width:100px;height:120px;" src="${player[i].images.png}" />`;

playerValues.push(player[i].value);

document.getElementById('playerCards').innerHTML += card;

}

Now, currently this is pretty much the backbone of my game. I’m also adding functions that calculate the percentages, current wins, etc, but this is really the basic format here. In the hit and stand functions, we would simply need to add conditional statements when running this function. For example, after updating the player cards, dealer cards and values, we would check and see if either player busted, or if a player had won the game. Most of my conditions are simple if statements like:

if (playerSum > 21 && playerValues.includes(11)==true){

// do something

}

In this example, when a player draws, and his current total is over 21, we’ll check to see if the player contains any 11’s (which would be the value we set the ACE card to). If it’s true, we’ll get the index of that card, reset the value to 1 instead of 11, and re-update the current values, sums and the DOM.

And then of course there are conditional statements to determine a winner:

if(playerSum > dealerSum && playerSum <= 21){

}

In this example, if the playerSum is higher than the dealerSum, we’ll determine that the player is the winner, so long as the playerSum is also under 21.

The most time consuming part of the entire game is probably determining busts and wins, since the player and dealer values, sums and percentages would need to be updated at each condition.

I also have this condition that I liked:

while(playerSum > dealerSum && playerSum <= 21){

}

Here, I use a while loop instead of a for loop or if statement. This is inside my stand function. Basically, when a player stands, I want to check the value, and if the dealer value is less than the player value, we’ll just keep hitting for the dealer until he either wins or busts, since the player can no longer hit at that point.

Calculating percentages:

The last part of this for me was what got me started on it in the first place: determining the probability of certain outcomes. For example, I wanted to determine the probability that the dealer’s hand is higher than the player’s.

Since I know three of the cards in play after the initial deal. Basically, this would be a multi part function:

1 Create a simulated deck.

2 remove known cards from the deck.

3 draw a new card and substitute it for the unknown card.

4 If the sum of the new card, plus the known dealer card is higher than the player sum, add 1 to a variable.

5 If the test runs a million times, determine the number of times the outcome puts the dealer hand higher than the players.

Here’s how my function looks, turning pseudo code into actual code:

function determinePercent(p,d){

let percentageOne = 0;

let testDeck2 = testDeck;

let valP = [];

let valD = [];

for (var i = 0; i < player.length; i++) {

valP.push(player[i].value);

}

for (var i = 0; i < dealer.length; i++) {

valD.push(dealer[i].value);

}

p = valP;

d = valD;



for (var i = 0; i < p.length; i++) {

if(testDeck2.includes(p[i])==true){

let index1 = testDeck2.indexOf(p[i]);

testDeck2.splice(index1,1);

}

}

for (var i = 0; i < d.length; i++) {

if(testDeck2.includes(d[i])==true && i!=0){

let index1 = testDeck2.indexOf(d[i]);

testDeck2.splice(index1,1);

}

}



let dealerValues2 = [d.pop()];

function getSum(total, num) {

return total + num;

}

let playerSum2 = p.reduce(getSum);

let dealerSum2 = dealerValues2.reduce(getSum);

console.log(dealerSum2);



for (var i = 0; i < iterations; i++) {

let dealerCard1 = testDeck2[Math.floor(Math.random()*testDeck2.length)];



if(dealerSum2+dealerCard1 > playerSum2){

percentageOne = percentageOne + 1

}

}



let percentageTwo = Math.round((percentageOne/iterations)*100);



return percentageTwo;

}

Now, I can run this function at any point in my code, and update it, determining a new probability. Anyway, Hope this made sense and was at least remotely helpful and entertaining. There’s obviously more detail I could go into on some of this, but to avoid having a post that’s more boring than it already is, I’ll wrap it up here. Feel free to reach out if I made some mistakes, or I can answer any questions.

Let me know how you like the game, or if you have any feedback or suggestions. Thanks!

https://www.linkedin.com/in/ethanjarrell/

Tags