Building a Blackjack Game with JavaScript
We've covered a lot in our journey through JavaScript together, and now it's time to put everything we've learned into practice with a fun final project: building a simple Blackjack game using the Deck of Cards API.
We'll create this game using what we've learned about JavaScript, including loops, control flows, asynchronous code, destructuring, modules, and working with APIs.
Ready? Let's dive in!
What is Blackjack?
Before we start coding, let's quickly review how Blackjack works so we're all on the same page. Blackjack, also known as 21, is a popular card game whose goal is to beat the dealer by having a hand value closest to 21 without going over.
Basic Rules:
Card Values:
- Number cards (2-10) are worth their face value.
- Face cards (Jack, Queen, King) are worth 10 points.
- Aces can be worth 1 or 11 points, depending on what benefits the hand more.
Game Play:
- Both the player and the dealer start with two cards.
- The player’s cards are both visible, but only one of the dealer’s cards is visible.
- The player can choose to "hit" (take another card) or "stand" (keep their current hand).
- If the player’s hand value exceeds 21, they "bust" and lose the game.
- Once the player stands, the dealer reveals their hidden card and must hit until their hand value is at least 17.
- The winner is determined based on whose hand value is closest to 21 without exceeding it. If the dealer busts, the player wins.
Alright, now that we know the rules, let's set up our project!
Setting Up the Project
First, let's set up our project folder. We'll keep things organized by splitting our code into multiple files.
Here's the basic structure:
blackjack/ ├── index.html ├── styles.css ├── script.js ├── deck.js ├── game.js
HTML
Let's start with our HTML file, index.html
. This will include the basic structure of our game.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Blackjack Game</title> <link rel="stylesheet" href="styles.css"> </head> <body> <h1>Blackjack Game</h1> <div id="game"> <div id="player-hand" class="hand"> <h2>Player's Hand</h2> <div id="player-cards" class="cards"></div> <p id="player-score"></p> </div> <div id="dealer-hand" class="hand"> <h2>Dealer's Hand</h2> <div id="dealer-cards" class="cards"></div> <p id="dealer-score"></p> </div> <div id="controls"> <button id="hit-button">Hit</button> <button id="stand-button">Stand</button> <button id="new-game-button">New Game</button> </div> <p id="result"></p> </div> <script type="module" src="script.js"></script> </body> </html>
CSS
Now, let's add some basic styles in styles.css
to make our game look more modern and responsive.
body { font-family: Arial, sans-serif; text-align: center; background-color: #2e7d32; color: white; margin: 0; padding: 0; display: flex; flex-direction: column; align-items: center; min-height: 100vh; } h1 { margin-top: 20px; } #game { flex: 1; display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 600px; } .hand { margin: 20px; border: 2px solid white; padding: 10px; display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 300px; } .cards { display: flex; flex-wrap: wrap; justify-content: center; } .cards img { margin: 5px; } #controls { position: fixed; bottom: 20px; width: 100%; display: flex; justify-content: center; gap: 10px; } button { margin: 10px; padding: 10px 20px; font-size: 16px; cursor: pointer; border: none; border-radius: 5px; background-color: #388e3c; color: white; transition: background-color 0.3s; } button:hover { background-color: #1b5e20; } button:disabled { background-color: #9e9e9e; } #result { font-size: 20px; margin-top: 20px; }
## JavaScript Time!
In the following few sections, we will break apart different functions and tie them together to build up our game.
Deck Module
Let's create the deck.js
file, which will handle interactions with the Deck of Cards API.
// deck.js const apiUrl = 'https://deckofcardsapi.com/api/deck'; export async function createDeck() { const response = await fetch(`${apiUrl}/new/shuffle/?deck_count=1`); const data = await response.json(); return data.deck_id; } export async function drawCards(deckId, count) { const response = await fetch(`${apiUrl}/${deckId}/draw/?count=${count}`); const data = await response.json(); return data.cards; }
createDeck
function: This function makes an API call to create a new shuffled deck and returns the deck ID.drawCards
function: This function takes a deck ID and a count of cards to draw, makes an API call to draw that number of cards from the deck, and returns the drawn cards.
Game Logic
Next, we'll create the game.js
file, which will contain the game logic for our Blackjack game.
// game.js import { createDeck, drawCards } from './deck.js'; let deckId; let playerHand = []; let dealerHand = []; let playerScore = 0; let dealerScore = 0; let gameOver = false; export async function startNewGame() { deckId = await createDeck(); playerHand = await drawCards(deckId, 2); dealerHand = await drawCards(deckId, 2); gameOver = false; updateScores(); updateUI(); document.getElementById('result').innerText = ''; }
Variables:
deckId
: Stores the ID of the current deck.playerHand
anddealerHand
: Arrays storing the player's and dealer's cards.playerScore
anddealerScore
: Variables to keep track of the scores.gameOver
: A flag to check if the game is over.
startNewGame
function: Initializes a new game by creating a deck and drawing two cards each for the player and dealer. It resets the game state and updates the UI.
export async function hit() { if (!gameOver) { const newCard = await drawCards(deckId, 1); playerHand.push(newCard[0]); updateScores(); updateUI(); checkForBust(); } }
hit
function: Draws a new card for the player, updates the score and UI, and checks if the player has busted.
export async function stand() { if (!gameOver) { gameOver = true; while (dealerScore < 17) { const newCard = await drawCards(deckId, 1); dealerHand.push(newCard[0]); updateScores(); updateUI(); if (dealerScore >= 17) { checkForWinner(); } } } }
stand
function: Simulates the dealer's turn, drawing cards until the dealer's score is at least 17, and then determines the winner.
function updateScores() { playerScore = calculateScore(playerHand); dealerScore = calculateScore(dealerHand); document.getElementById('player-score').innerText = `Score: ${playerScore}`; document.getElementById('dealer-score').innerText = `Score: ${dealerScore}`; } function calculateScore(hand) { let score = 0; let hasAce = false; hand.forEach(card => { if (card.value === 'ACE') { hasAce = true; score += 11; } else if (['KING', 'QUEEN', 'JACK'].includes(card.value)) { score += 10; } else { score += parseInt(card.value); } }); if (score > 21 && hasAce) { score -= 10; } return score; }
updateScores
function: Calculates and updates the scores of the player and dealer.calculateScore
function: Calculates the score for a given hand, considering Aces as either 1 or 11.
function updateUI() { const playerCardsDiv = document.getElementById('player-cards'); const dealerCardsDiv = document.getElementById('dealer-cards'); playerCardsDiv.innerHTML = ''; dealerCardsDiv.innerHTML = ''; playerHand.forEach(card => { const img = document.createElement('img'); img.src = card.image; playerCardsDiv.appendChild(img); }); dealerHand.forEach(card => { const img = document.createElement('img'); img.src = card.image; dealerCardsDiv.appendChild(img); }); }
updateUI
function: Updates the UI to reflect the current state of the player's and dealer's hands.
function checkForBust() { if (playerScore > 21) { document.getElementById('result').innerText = 'You bust! Dealer wins!'; gameOver = true; } } function checkForWinner() { if (dealerScore > 21) { document.getElementById('result').innerText = 'Dealer busts! You win!'; } else if (dealerScore >= playerScore) { document.getElementById('result').innerText = 'Dealer wins!'; } else { document.getElementById('result').innerText = 'You win!'; } }
checkForBust
function: Checks if the player has busted (score > 21) and updates the result if they have.checkForWinner
function: Determines the winner based on the final scores and updates the result.
Main Script
Finally, let's create the main script, script.js
, to tie everything together.
// script.js import { startNewGame, hit, stand } from './game.js'; document.getElementById('new-game-button').addEventListener('click', startNewGame); document.getElementById('hit-button').addEventListener('click', hit); document.getElementById('stand-button').addEventListener('click', stand); startNewGame();
- This script imports the functions from
game.js
and binds the UI buttons to these functions. - When the user clicks the "New Game", "Hit", or "Stand" buttons, the corresponding function is called to handle the action.
- The game starts automatically when the page loads by calling
startNewGame()
.
Running the Game
As we covered in the modules section, you'll need a local server to run code with modules.
One of the easiest ways to do this is using the Live Server extension in Visual Studio Code (VS Code). Here's how to set it up:
Install Live Server Extension:
- Open VS Code.
- Go to the Extensions view by clicking the Extensions icon in the Activity Bar on the side of the window.
- Search for "Live Server" and install the extension by Ritwick Dey.
Running the Server:
- Open your project folder in VS Code.
- Right-click on your
index.html
file (or whichever file is your main HTML file) in the Explorer view. - Select "Open with Live Server".
This will start a local server and open your project in a new browser tab. The URL will typically be something like http://127.0.0.1:5500/index.html
. Now, your modules should work perfectly!
Complete Files
If you followed along you should have a working project that looks like this.
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Blackjack Game</title> <link rel="stylesheet" href="styles.css"> </head> <body> <h1>Blackjack Game</h1> <div id="game"> <div id="player-hand" class="hand"> <h2>Player's Hand</h2> <div id="player-cards" class="cards"></div> <p id="player-score"></p> </div> <div id="dealer-hand" class="hand"> <h2>Dealer's Hand</h2> <div id="dealer-cards" class="cards"></div> <p id="dealer-score"></p> </div> <div id="controls"> <button id="hit-button">Hit</button> <button id="stand-button">Stand</button> <button id="new-game-button">New Game</button> </div> <p id="result"></p> </div> <script type="module" src="script.js"></script> </body> </html>
styles.css
body { font-family: Arial, sans-serif; text-align: center; background-color: #2e7d32; color: white; margin: 0; padding: 0; display: flex; flex-direction: column; align-items: center; min-height: 100vh; } h1 { margin-top: 20px; } #game { flex: 1; display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 600px; } .hand { margin: 20px; border: 2px solid white; padding: 10px; display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 300px; } .cards { display: flex; flex-wrap: wrap; justify-content: center; } .cards img { margin: 5px; } #controls { position: fixed; bottom: 20px; width: 100%; display: flex; justify-content: center; gap: 10px; } button { margin: 10px; padding: 10px 20px; font-size: 16px; cursor: pointer; border: none; border-radius: 5px; background-color: #388e3c; color: white; transition: background-color 0.3s; } button:hover { background-color: #1b5e20; } button:disabled { background-color: #9e9e9e; } #result { font-size: 20px; margin-top: 20px; }
deck.js
const apiUrl = 'https://deckofcardsapi.com/api/deck'; export async function createDeck() { const response = await fetch(`${apiUrl}/new/shuffle/?deck_count=1`); const data = await response.json(); return data.deck_id; } export async function drawCards(deckId, count) { const response = await fetch(`${apiUrl}/${deckId}/draw/?count=${count}`); const data = await response.json(); return data.cards; }
game.js
import { createDeck, drawCards } from './deck.js'; let deckId; let playerHand = []; let dealerHand = []; let playerScore = 0; let dealerScore = 0; let gameOver = false; export async function startNewGame() { deckId = await createDeck(); playerHand = await drawCards(deckId, 2); dealerHand = await drawCards(deckId, 2); gameOver = false; updateScores(); updateUI(); document.getElementById('result').innerText = ''; } export async function hit() { if (!gameOver) { const newCard = await drawCards(deckId, 1); playerHand.push(newCard[0]); updateScores(); updateUI(); checkForBust(); } } export async function stand() { if (!gameOver) { gameOver = true; while (dealerScore < 17) { const newCard = await drawCards(deckId, 1); dealerHand.push(newCard[0]); updateScores(); updateUI(); if (dealerScore >= 17) { checkForWinner(); } } } } function updateScores() { playerScore = calculateScore(playerHand); dealerScore = calculateScore(dealerHand); document.getElementById('player-score').innerText = `Score: ${playerScore}`; document.getElementById('dealer-score').innerText = `Score: ${dealerScore}`; } function calculateScore(hand) { let score = 0; let hasAce = false; hand.forEach(card => { if (card.value === 'ACE') { hasAce = true; score += 11; } else if (['KING', 'QUEEN', 'JACK'].includes(card.value)) { score += 10; } else { score += parseInt(card.value); } }); if (score > 21 && hasAce) { score -= 10; } return score; } function updateUI() { const playerCardsDiv = document.getElementById('player-cards'); const dealerCardsDiv = document.getElementById('dealer-cards'); playerCardsDiv.innerHTML = ''; dealerCardsDiv.innerHTML = ''; playerHand.forEach(card => { const img = document.createElement('img'); img.src = card.image; playerCardsDiv.appendChild(img); }); dealerHand.forEach(card => { const img = document.createElement('img'); img.src = card.image; dealerCardsDiv.appendChild(img); }); } function checkForBust() { if (playerScore > 21) { document.getElementById('result').innerText = 'You bust! Dealer wins!'; gameOver = true; } } function checkForWinner() { if (dealerScore > 21) { document.getElementById('result').innerText = 'Dealer busts! You win!'; } else if (dealerScore >= playerScore) { document.getElementById('result').innerText = 'Dealer wins!'; } else { document.getElementById('result').innerText = 'You win!'; } }
script.js
import { startNewGame, hit, stand } from './game.js'; document.getElementById('new-game-button').addEventListener('click', startNewGame); document.getElementById('hit-button').addEventListener('click', hit); document.getElementById('stand-button').addEventListener('click', stand); startNewGame();
Congrats! You've built a pretty complex Blackjack game using JavaScript. This project is a great way to practice your skills and see how different concepts come together in a real-world application.
Feel free to expand on this project by adding more features, such as betting or whatever else you think could add flavor. The best way to learn is by building real projects without tutorials and knowing what you need as you go.
Thanks for sticking with me through this JavaScript journey. Keep coding, and have fun!