Open In App

Blackjack Game using MERN Stack

Last Updated : 30 Jan, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In this tutorial, we’ll embark on a journey to create a fully functional Blackjack game using React for the frontend and Node for the backend. This engaging and interactive project will allow you to explore the realms of web development while learning how to implement key features of a classic card game.

Preview of Final Output: Let’s have a look at what our final project will look like:

Screenshot-2024-01-28-110038Prerequisites:

Approach to create Blackjack Game:

  • Backend (Node):
    • Set up a NodeJS server using Express.
    • Create a MongoDB database to store game data.
    • Implement routes for starting a new game, hitting, and standing.
    • Define logic to determine winners and calculate scores.
  • Frontend (React):
    • Develop a responsive UI for the Blackjack game.
    • Connect the frontend to the backend using Axios for API calls.
    • Implement functionalities for hitting, standing, and starting a new game.
    • Display game state, player and dealer hands, and winner messages.

Functionalities of Backend:

  • Importing modules such as Express, Mongoose, cors, and body-parser.
  • Creating an Express app, setting the server port.
  • Middleware is used to Enabling CORS and JSON data parsing.
  • Connecting to a local MongoDB database.
  • Defining Mongoose schemas and models for Card, Player, and Game.
  • Creating a function to generate a shuffled deck.
  • Endpoint (/game/start): Starts a new game, initializes player and dealer cards, calculates scores, and saves the game state.
  • Endpoints (/game/hit and /game/stand): Handle player hits and stands, update game state, and determine the winner.
  • Functions (calculateScore and determineWinner): Calculate hand scores and determine game winner.
  • Starting the server and listening on the specified port (either from the environment variable or defaulting to 5000).

Steps To Create The Backend:

Step 1: Create a new NodeJS project.

npm init -y

Step 2: Install necessary packages such as express, mongoose, cors, and body-parser.

npm install express mongoose cors axios

Step 3: Set up a MongoDB database using MongoDB Compass. Define Mongoose schemas for cards, players, and the Game Logic and Routes In server.js:

Project Structure(Backend):

Screenshot-2024-01-28-111302

The updated dependencies in package.json file of backend will look like:

"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.1.1"
}

Javascript




// blackjack-server/server.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
 
const app = express();
const PORT = process.env.PORT || 5000;
 
app.use(cors());
app.use(bodyParser.json());
 
// Database connection configuration
    useNewUrlParser: true,
    useUnifiedTopology: true,
});
 
const cardSchema = new mongoose.Schema({
    suit: String,
    rank: String,
    value: Number,
});
 
const playerSchema = new mongoose.Schema({
    name: String,
    hand: [cardSchema],
    score: Number,
});
 
const gameSchema = new mongoose.Schema({
    deck: [cardSchema],
    player: playerSchema,
    dealer: playerSchema,
    winner: String, // New field to store the winner
});
 
 
const Card = mongoose.model('Card', cardSchema);
const Player = mongoose.model('Player', playerSchema);
const Game = mongoose.model('Game', gameSchema);
 
// Helper function to create a new deck
function createDeck() {
    const suits =
        ['Hearts', 'Diamonds', 'Clubs', 'Spades'];
    const ranks =
        ['2', '3', '4', '5', '6', '7', '8',
            '9', '10', 'J', 'Q', 'K', 'A'];
 
    const deck = [];
    for (const suit of suits) {
        for (const rank of ranks) {
            const card = new Card({
                suit: suit,
                rank: rank,
                value: rank ===
                    'A' ? 11 : isNaN(rank) ?
                    10 : parseInt(rank),
            });
            deck.push(card);
        }
    }
 
    return deck;
}
 
// Endpoint to start a new game
app.post('/game/start', async (req, res) => {
    try {
        const newDeck = createDeck();
 
        // Shuffle the deck (Fisher-Yates algorithm)
        for (let i = newDeck.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [newDeck[i], newDeck[j]] = [newDeck[j],
            newDeck[i]];
        }
 
        const newGame = new Game({
            deck: newDeck,
            player: { name: 'Player', hand: [],
            score: 0 },
            dealer: { name: 'Dealer', hand: [],
            score: 0 },
        });
 
        // Deal the initial two cards to the player and the dealer
        newGame.player.hand.push(newGame.deck.pop());
        newGame.dealer.hand.push(newGame.deck.pop());
        newGame.player.hand.push(newGame.deck.pop());
        newGame.dealer.hand.push(newGame.deck.pop());
 
        // Update scores
        newGame.player.score = calculateScore(
            newGame.player.hand);
        newGame.dealer.score = calculateScore(
            newGame.dealer.hand);
 
        // Save the new game to the database
        await newGame.save();
 
        res.status(201).json(newGame);
    } catch (error) {
        console.error(error);
        res.status(500).send('Internal Server Error');
    }
});
 
// Endpoint to handle player hits
app.post('/game/hit', async (req, res) => {
    try {
        const gameId = req.body.gameId;
 
        // Fetch the game from the database
        const game = await Game.findById(gameId);
 
        // Draw a card from the deck and add it to the player's hand
        const drawnCard = game.deck.pop();
        game.player.hand.push(drawnCard);
 
        // Update the player's score
        game.player.score = calculateScore(
            game.player.hand);
 
        // Set the winner field
        game.winner = determineWinner(game.player.score,
            game.dealer.score);
 
        // Save the updated game to the database
        await game.save();
 
        res.json({ ...game.toObject(),
            winner: game.winner });
    } catch (error) {
        console.error(error);
        res.status(500).send('Internal Server Error');
    }
});
 
app.post('/game/stand', async (req, res) => {
    try {
        const gameId = req.body.gameId;
 
        // Fetch the game from the database
        const game = await Game.findById(gameId);
 
        // Dealer draws cards until their score is 17 or higher
        while (game.dealer.score < 17) {
            const drawnCard = game.deck.pop();
            game.dealer.hand.push(drawnCard);
            game.dealer.score = calculateScore(
                game.dealer.hand);
        }
 
        // Set the winner field
        game.winner = determineWinner(game.player.score,
            game.dealer.score);
 
        // Save the updated game to the database
        await game.save();
 
        res.json({ ...game.toObject(),
            winner: game.winner });
    } catch (error) {
        console.error(error);
        res.status(500).send('Internal Server Error');
    }
});
 
// Helper function to calculate the score of a hand
function calculateScore(hand) {
    let score = hand.reduce((total, card) =>
        total + card.value, 0);
 
    // Handle Aces (reduce value from 11 to 1 if necessary)
    hand.filter(card => card.rank === 'A')
    .forEach(_ => {
        if (score > 21) {
            score -= 10;
        }
    });
 
    return score;
}
 
// Helper function to determine the winner
function determineWinner(playerScore,
    dealerScore) {
    if (playerScore > 21) {
        return 'Dealer'; // Player busts, dealer wins
    }
 
    if (dealerScore > 21) {
        return 'Player'; // Dealer busts, player wins
    }
 
    if (playerScore > dealerScore) {
        return 'Player'; // Player has a higher score
    } else if (playerScore < dealerScore) {
        return 'Dealer'; // Dealer has a higher score
    } else {
        return 'Draw'; // Scores are equal, it's a draw
    }
}
 
 
// Start the server
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});


Steps to Run the backend:

Step 1: Navigate to blackjack-server root directory.

cd blackjack-server

Step 2: Run the server using following command.

node server.js

Steps To Create The Frontend:

Step 1: Set up a new React app using create-react-app.

npx create-react-app blackjack-frontend

Step 2: Go to the root directory using the following command.

cd blackjack-frontend

Step 3: Install necessary packages.

npm install axios

Project Structure(Frontend):

Screenshot-2024-01-28-111227

The updated dependencies in package.json file of frontend will look like:

"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},

Functionalities of Frontend:

  • The code imports React, Axios (for HTTP requests), and a CSS file for styling.
  • It declares a functional React component named Game, with state variables for managing game state and displaying the winner message.
  • The useEffect hook fetches the initial game state from the backend ( <a href=”http://localhost:5000/game/start” target=”_new” > http://localhost:5000/game/start </a > ) when the component mounts.
  • handleHit: Sends an Axios request to the backend to implement player hitting logic.
  • handleStand: Sends an Axios request to the backend to implement player standing logic.
  • startNewGame: Sends an Axios request to start a new game and clears the winner message.Displays the winner message and starts a new game after a 3-second delay using setTimeout.Uses setTimeout to automatically trigger a new game.Displays a loading message while fetching the game state from the backend.

Javascript




// blackjack-client/src/Game.js
 
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
 
const Game = () => {
    const [gameState, setGameState] = useState(null);
    const [winnerMessage, setWinnerMessage] = useState('');
 
    useEffect(() => {
        // Fetch initial game state when the component mounts
        axios.post('http://localhost:5000/game/start')
            .then(response => setGameState(response.data))
            .catch(error =>
                console.error('Error starting a new game:', error));
    }, []);
 
    const handleHit = () => {
        // Implement logic for the player to hit
        axios.post('http://localhost:5000/game/hit',
            { gameId: gameState._id })
            .then(response => {
                setGameState(response.data);
                checkWinner(response.data.winner);
            })
            .catch(error => console.error('Error hitting:', error));
    };
 
    const handleStand = () => {
        // Implement logic for the player to stand
        axios.post('http://localhost:5000/game/stand',
            { gameId: gameState._id })
            .then(response => {
                setGameState(response.data);
                checkWinner(response.data.winner);
            })
            .catch(error =>
                console.error('Error standing:', error));
    };
 
    const startNewGame = () => {
        // Implement logic to start a new game
        setWinnerMessage(''); // Clear the winner message
        axios.post('http://localhost:5000/game/start')
            .then(response => setGameState(response.data))
            .catch(error =>
                console.error('Error starting a new game:', error));
    };
 
    const checkWinner = (winner) => {
        // Display winner message and start a new game
        setWinnerMessage(`Winner: ${winner}`);
        setTimeout(() => {
            startNewGame();
        }, 3000); // Automatically start a new game after 3 seconds
    };
 
    return (
        <div className="kl">
            {gameState ? (
                <>
                    <h1>Blackjack Game</h1>
                    {winnerMessage && <p className="winner-message">
                        {winnerMessage} </p>}
                    <div className="ma">
 
                        <div className="playerside">
                            <h2>Player Hand:</h2>
                            <ul>
                                {gameState.player.hand.map((card, index) => (
                                    <li key={index}>{card.rank}
                                        of {card.suit}</li>
                                ))}
                            </ul>
                            <p>Score: {gameState.player.score}</p>
                        </div>
                        <div className="dealerside">
                            <h2>Dealer Hand:</h2>
                            <ul>
                                {gameState.dealer.hand.map((card, index) => (
                                    <li key={index}>{card.rank}
                                        of {card.suit}</li>
                                ))}
                            </ul>
                            <p>Score: {gameState.dealer.score}</p>
                        </div>
                    </div>
                    <div className="buttons">
                        <button onClick={handleHit}>Hit</button>
                        <button onClick={handleStand}>Stand</button>
                        <button onClick={startNewGame}>
                            Start New Game
                        </button>
                    </div>
                </>
            ) : (
                <p>Loading...</p>
            )}
        </div>
    );
};
 
export default Game;


CSS




/* blackjack-client/src/App.css */
 
body {
    background-color: #00203F;
    color: #ADEFD1;
    font-family: sans-serif;
}
 
.App {
    text-align: center;
    padding: 20px;
}
 
 
ul {
    list-style-type: none;
    padding: 0;
}
 
button {
    background-color: #ADEFD1;
    color: #00203F;
    border: none;
    padding: 10px 20px;
    margin: 5px;
    font-size: 16px;
    cursor: pointer;
    transition: background-color 0.3s ease;
}
 
button:hover {
    background-color: #00203F;
    color: #ADEFD1;
}
 
.winner-message {
    color: #FF5555;
    font-size: 30px;
    font-weight: bolder;
    margin-top: 20px;
    animation: fadeIn 1s ease;
}
 
@keyframes fadeIn {
    from {
        opacity: 0;
    }
 
    to {
        opacity: 1;
    }
}
 
.ma {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
}
 
h2,
p {
    color: #00203F;
}
 
.playerside {
    padding: 2%;
    background-color: #ADEFD1;
    color: #00203F;
    width: fit-content;
    margin: 2%;
    border-radius: 35px;
}
 
.dealerside {
    width: fit-content;
    margin: 2%;
    border-radius: 35px;
    padding: 2%;
    background-color: #ADEFD1;
    color: #00203F;
}


Steps to Run the frontend:

1. Navigate to blackjack-frontend root directory.

cd blackjack-frontend

2. Run the following command.

npm start

Output:

243-ezgifcom-video-to-gif-converter



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads