Horje
Blackjack Game using MEAN Stack

This is a project to get a thorough understanding of MEAN Stack technologies (MongoDB, Express, Node JS, Angular). This will give you a step-by-step process to create a blackjack game from scratch. This article will discuss Starting a new game, the logic to play it and store it in the database. It will also help you to understand the CRUD operations.

Output Preview: Let us have a look at how the final output will look like.

preview1

OUTPUT PREVIEW OF START GAME PAGE

Prerequisites:

Approach to create Blackjack Game using MEAN Stack:

Backend:

  • Set up a new node.js project
  • Create server.js file to setup the server using express.
  • Create an instance of app and use cors package as a middleware.
  • Create routes folder and setup routes for servicing API requests.
  • Set up mongo DB locally and create a method to connect to the database in server.js file.
  • Create a database and a collection to store and retrieve the game related data.
  • Implement the core logic of game for example – dealing cards, hit action, stand action, calculate scores and determine the winner
  • Test your API using tools like postman.

Frontend:

  • Create a new Angular project.
  • Create various components for game for different functionalities and define HTML and CSS files for all of them.
  • Create a service to set up communication between backend API endpoints and Angular HttpClient.
  • Update the user interface based on the state of game given by backend.
  • Test your frontend application in browser.

Steps to create the project:

Step 1: Create the main folder for the project using the following command.

mkdir black-jack

Step 2: Initialize the node.js project.

npm init -y

Step 3: Install the required dependencies

npm install express cors mongoose

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.3",
"mongoose": "^8.2.1"
}

Project Structure(Backend):

Screenshot-2024-03-12-042848

OUTPUT IMAGE OF PROJECT STRUCTURE FOR BACKEND

Example: Create the required files as seen on the folder structure and add the following codes.

Node
// server.js

const express = require('express');
const mongoose = require('mongoose');
const gameRoutes = require('./routes/gameRoutes');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());
app.use('/api/game', gameRoutes);

mongoose.connect('mongodb://localhost:27017/blackjack', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    family: 4,
})
    .then(() => console.log('MongoDB Connected'))
    .catch(err => console.log(err));

const PORT = 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Node
// gameController.js

const {
    dealCard,
    calculateHandValue,
    isBlackjack,
} = require("../utils/gameUtils");

const Game = require("../models/gameModels");

exports.getAllGames = async (req, res) => {
    try {
        const games = await Game.find();
        res.json(games);
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};

exports.createGame = async (req, res) => {
    const existingGame = await Game.findOne({
        playerHand: [],
        status: "ongoing",
    });
    if (existingGame) {
        return res.status(200).json(existingGame);
    }
    const game = new Game({
        playerHand: [],
        dealerHand: [],
        playerTotal: 0,
        dealerTotal: 0,
        winner: "",
        status: "ongoing",
    });

    try {
        const newGame = await game.save();
        res.status(201).json(newGame);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
};

exports.getGameById = async (req, res) => {
    try {
        const game = await Game.findById(req.params.id);
        if (game == null) {
            return res.status(404)
                .json({ message: "Game not found" });
        }
        res.json(game);
    } catch (err) {
        res.status(500).json({ message: err.message });
    }
};

exports.dealCards = async (req, res) => {
    try {
        const game = await Game.findById(req.params.id);
        game.playerHand.push(dealCard());
        game.dealerHand.push(dealCard());
        game.playerHand.push(dealCard());
        game.dealerHand.push(dealCard());

        game.playerTotal = calculateHandValue(game.playerHand);
        game.dealerTotal = calculateHandValue(game.dealerHand);

        if (isBlackjack(game.playerHand) && !isBlackjack(game.dealerHand)) {
            game.status = "player_wins";
            game.winner = "Player";
        } else if (isBlackjack(game.dealerHand) && !isBlackjack(game.playerHand)) {
            game.status = "dealer_wins";
            game.winner = "Dealer";
        } else if (isBlackjack(game.dealerHand) && isBlackjack(game.playerHand)) {
            game.status = "tie";
            game.winner = "Tie";
        }

        await game.save();
        res.json(game);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
};

exports.updateGame = async (req, res) => {
    try {
        const { id } = req.params;
        const updates = req.body;

        const game = await Game.findById(id);

        if (!game) {
            return res.status(404)
                .json({ message: "Game not found" });
        }

        if (game.status === "ended") {
            return res
                .status(400)
                .json({ message: "Cannot update a game that has already ended" });
        }

        if (game.playerHand.length > 0 && "playerHand" in updates) {
            return res
                .status(400)
                .json({ message: "Cannot change player hand after dealing" });
        }
        const updatedGame = await Game.findByIdAndUpdate(id, updates);
        res.json(updatedGame);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
};

exports.hitAction = async (req, res) => {
    try {
        const { id } = req.params;

        const game = await Game.findById(id);

        if (!game) {
            return res.status(404)
                .json({ message: "Game not found" });
        }

        if (game.status !== "ongoing") {
            return res.status(400)
                .json({ message: "Game is not in progress" });
        }

        const newCard = dealCard();

        while (
            game.playerHand.includes(newCard) ||
            game.dealerHand.includes(newCard)
        ) {
            newCard = dealCard();
        }
        game.playerHand.push(newCard);

        game.playerTotal = calculateHandValue(game.playerHand);

        if (game.playerTotal > 21) {
            game.status = "player_busted";
            game.winner = "Dealer";
        } else if (game.playerTotal == 21) {
            game.status = "player_wins";
            game.winner = "Player";
        }

        await game.save();

        res.json(game);
    } catch (err) {
        console.error(err);
        res.status(500).json({ message: "Internal Server Error" });
    }
};

exports.standAction = async (req, res) => {
    try {
        const { id } = req.params;
        console.log("Stand action", id);
        const game = await Game.findById(id);

        if (!game) {
            return res.status(404)
                .json({ message: "Game not found" });
        }

        if (game.status !== "ongoing") {
            return res.status(400).json({ message: "Game is not in progress" });
        }

        game.status = "player_stands";
        while (calculateHandValue(game.dealerHand) < 17) {
            let newCard = dealCard();

            while (
                game.playerHand.includes(newCard) ||
                game.dealerHand.includes(newCard)
            ) { }
            game.dealerHand.push(newCard);
        }
        game.dealerTotal = calculateHandValue(game.dealerHand);

        const playerTotal = calculateHandValue(game.playerHand);
        const dealerTotal = calculateHandValue(game.dealerHand);

        if (dealerTotal > 21 || playerTotal > dealerTotal) {
            game.status = "player_wins";
            game.winner = "Player";
        } else if (playerTotal < dealerTotal) {
            game.status = "dealer_wins";
            game.winner = "Dealer";
        } else {
            game.status = "tie";
            game.winner = "Tie";
        }

        await game.save();

        res.json(game);
    } catch (err) {
        console.error(err);
        res.status(500).json({ message: "Internal Server Error" });
    }
};

exports.endGame = async (req, res) => {
    try {
        const { id } = req.params;

        const game = await Game.findById(id);

        if (!game) {
            return res.status(404).json({ message: "Game not found" });
        }
        const playerTotal = calculateHandValue(game.playerHand);
        const dealerTotal = calculateHandValue(game.dealerHand);

        if (playerTotal <= 21 && playerTotal > dealerTotal) {
            game.winner = "Player";
        } else if (dealerTotal <= 21 && playerTotal < dealerTotal) {
            game.winner = "Dealer";
        } else if (playerTotal > 21 && dealerTotal <= 21) {
            game.winner = "Dealer";
        } else if (dealerTotal > 21 && playerTotal <= 21) {
            game.winner = "Player";
        } else {
            game.winner = "Tie";
        }

        game.status = "ended";

        const updatedGame = await game.save();

        res.json(updatedGame);
    } catch (err) {
        res.status(400).json({ message: err.message });
    }
};

exports.deleteGame = async (req, res) => {
    try {
        const { id } = req.params;
        await Game.findByIdAndDelete(id);
        res.json({ msg: "Game deleted" });
    } catch (err) {
        console.error(err.message);
        res.status(500).send("Server Error");
    }
};
Node
// gameRoutes.js

const express = require('express');
const router = express.Router();
const gameController = require('../controller/gameController');

router.get('/', gameController.getAllGames);

router.post('/create', gameController.createGame);

router.get('/:id', gameController.getGameById);

router.post('/deal/:id', gameController.dealCards);

router.put('/update/:id', gameController.updateGame);

router.put('/end/:id', gameController.endGame);

router.post('/hit/:id', gameController.hitAction);

router.post('/stand/:id', gameController.standAction);

router.delete('/delete/:id', gameController.deleteGame);

module.exports = router;
Node
// gameUtils.js

const cardValues = {
    '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
    'J': 10, 'Q': 10, 'K': 10, 'A': 11
};

function dealCard() {
    const suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades'];
    const ranks = Object.keys(cardValues);
    const suit = suits[Math.floor(Math.random() * suits.length)];
    const rank = ranks[Math.floor(Math.random() * ranks.length)];
    return { rank, suit, value: cardValues[rank] };
}

function calculateHandValue(hand) {
    let total = 0;
    let numAces = 0;

    for (const card of hand) {
        total += card.value;
        if (card.rank === 'A') numAces++;
    }

    while (total > 21 && numAces > 0) {
        total -= 10;
        numAces--;
    }

    return total;
}

function isBlackjack(hand) {
    return hand.length === 2 && calculateHandValue(hand) === 21;
}

module.exports = { dealCard, calculateHandValue, isBlackjack };
Node
// gameModels.js

const mongoose = require('mongoose');

const gameSchema = new mongoose.Schema({
    playerHand: [{ rank: String, suit: String, value: Number }],
    dealerHand: [{ rank: String, suit: String, value: Number }],
    playerTotal: { type: Number, default: 0 },
    dealerTotal: { type: Number, default: 0 },
    winner: String,
    status: { type: String, default: 'ongoing' }
});

module.exports = mongoose.model('Game', gameSchema);

To start the backend run the following command.

nodemon server.js

Step 4: Install the angular CLI

npm install -g @angular/cli

Step 5: Create a new angular project using the following command.

ng new frontend

Step 6: Create components in angular for different functionality

ng generate component <component-name>

Step 7: Create service for communication between backend and frontend

ng generate service <service-name>

Step 8: Create model for game matching to the game schema we have created in mongo DB

ng generate interface <model-name>

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

"dependencies": {
"@angular/animations": "^17.0.0",
"@angular/cdk": "^17.2.2",
"@angular/common": "^17.0.0",
"@angular/compiler": "^17.0.0",
"@angular/core": "^17.0.0",
"@angular/forms": "^17.0.0",
"@angular/material": "^17.2.2",
"@angular/platform-browser": "^17.0.0",
"@angular/platform-browser-dynamic": "^17.0.0",
"@angular/platform-server": "^17.0.0",
"@angular/router": "^17.0.0",
"@angular/ssr": "^17.0.10",
"express": "^4.18.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
}

Project Structure(Frontend):

project-structure

OUTPUT GIF OF PROJECT STRUCTURE FOR FRONTEND


Example: Create the required files s seen in folder structure and add the following codes.

HTML
<!-- game-list.component.html -->

<a href="/create" class="link-button">Start new game</a>
<div *ngIf="games.length > 0; else noGames" class="game-list">
    <h1 class="game-list-title">List of Games</h1>
    <table class="game-table">
        <thead>
            <tr>
                <th>Player Hand</th>
                <th>Dealer Hand</th>
                <th>Player Total</th>
                <th>Dealer Total</th>
                <th>Winner</th>
                <th>Status</th>
                <th style="text-align: center">Actions</th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let game of games" class="game-row">
                <td *ngIf="game.playerHand.length > 0 || 
                           game.status !== 'ongoing'" class="player-hand">
                    <div *ngFor="let player of game.playerHand">
                        {{ player.rank }} of {{ player.suit }}
                    </div>
                </td>
                <td *ngIf="game.playerHand.length > 0 || game.status !== 'ongoing'" 
                    class="dealer-hand">
                    <div *ngFor="let dealer of game.dealerHand">
                        {{ dealer.rank }} of {{ dealer.suit }}
                    </div>
                </td>
                <td *ngIf="game.playerHand.length > 0 || game.status !== 'ongoing'" 
                    class="player-total">
                    {{ game.playerTotal }}
                </td>
                <td *ngIf="game.playerHand.length > 0 || game.status !== 'ongoing'" 
                    class="dealer-total">
                    {{ game.dealerTotal }}
                </td>
                <td *ngIf="game.playerHand.length > 0 || game.status !== 'ongoing'" 
                    class="winner">
                    {{ game.winner }}
                </td>
                <td *ngIf="game.playerHand.length > 0 || game.status !== 'ongoing'" 
                    class="status">
                    {{ game.status }}
                </td>
                <td *ngIf="game.playerHand.length > 0 || game.status !== 'ongoing'" 
                    class="actions"
                    style="text-align: center">
                    <button (click)="getGameById(game._id)" class="get-button">
                        Get
                    </button>
                    <button (click)="updateGame(game._id)" [disabled]="game.status === 'ended'"
                            class="update-button"
                        style="background-color: green">
                        Update
                    </button>
                    <button (click)="confirmDelete(game._id)" style="background-color: rgb(181, 57, 57)">
                        Delete
                    </button>
                </td>
            </tr>
        </tbody>
    </table>
</div>
<ng-template #noGames>
    <div class="noGames">
        <p>No games available</p>
    </div>
</ng-template>
HTML
<!-- game-dialog.component.html -->

<div *ngIf="!isUpdateDialog" class="game-details-dialog">
    <h2>Game Details</h2>
    <table class="game-details-table">
        <tr>
            <th>Player Hand</th>
            <td>
                <div *ngFor="let player of data.playerHand" class="card">
                    {{ player.rank }} of {{ player.suit }}
                </div>
            </td>
        </tr>
        <tr>
            <th>Dealer Hand</th>
            <td>
                <div *ngFor="let dealer of data.dealerHand" class="card">
                    {{ dealer.rank }} of {{ dealer.suit }}
                </div>
            </td>
        </tr>
        <tr>
            <th>Player Total</th>
            <td>{{ data.playerTotal }}</td>
        </tr>
        <tr>
            <th>Dealer Total</th>
            <td>{{ data.dealerTotal }}</td>
        </tr>
        <tr>
            <th>Winner</th>
            <td>{{ data.winner }}</td>
        </tr>
        <tr>
            <th>Status</th>
            <td>{{ data.status }}</td>
        </tr>
    </table>
    <button mat-button class="close-button" (click)="closeDialog()">Close</button>
</div>
<div *ngIf="isUpdateDialog" class="update-game-dialog">
    <h2>Update Game</h2>
    <table class="update-game-table">
        <tr>
            <th>Player Hand</th>
            <td>
                <div *ngFor="let player of data.game.playerHand" class="card">
                    {{ player.rank }} of {{ player.suit }}
                </div>
            </td>
        </tr>
        <tr>
            <th>Dealer Hand</th>
            <td>
                <div *ngFor="let dealer of data.game.dealerHand" class="card">
                    {{ dealer.rank }} of {{ dealer.suit }}
                </div>
            </td>
        </tr>
        <tr>
            <th>Player Total</th>
            <td>{{ data.game.playerTotal }}</td>
        </tr>
        <tr>
            <th>Dealer Total</th>
            <td>{{ data.game.dealerTotal }}</td>
        </tr>
        <tr>
            <th>Winner</th>
            <td>{{ data.game.winner }}</td>
        </tr>
        <tr>
            <th>Status</th>
            <td>{{ data.game.status }}</td>
        </tr>
    </table>
    <div class="dialog-buttons">
        <button mat-button class="play-button" (click)="playGame(data.game._id)">
            Continue Game
        </button>
        <button mat-button class="close-button" (click)="closeDialog()">
            Close
        </button>
    </div>
</div>
HTML
<!-- start-game.component.html -->

<div class="start-game">
  <h2>Player Hand</h2>
  <app-hand
    [isPlayer]="true"
    [playerHand]="game.playerHand"
    class="hand"
  ></app-hand>

  <h2>Dealer Hand</h2>
  <app-hand
    [isPlayer]="false"
    [dealerHand]="game.dealerHand"
    class="hand"
  ></app-hand>
</div>
<div class="button-container">
  <button
    (click)="dealCards(game._id)"
    [disabled]="game.playerHand.length > 0"
    class="action-button"
    style="margin-left: 2vmax"
  >
    Deal Cards
  </button>
  <button
    (click)="hitAction(game._id)"
    [disabled]="game.status !== 'ongoing'"
    class="action-button"
  >
    Hit
  </button>
  <button
    (click)="standAction(game._id)"
    [disabled]="game.status !== 'ongoing'"
    class="action-button"
  >
    Stand
  </button>
  <button (click)="checkStart()" 
  class="action-button">Start new Game</button>
  <button (click)="endGame(game._id)" 
  class="action-button">End Game</button>
  <button (click)="homePage()" 
  class="action-button">Back to Home page</button>
</div>
HTML
<!-- hand.component.html -->

<div class="hand-container">
    <h2>Deck of Cards</h2>
    <div class="card-container" *ngIf="playerHand">
        <ng-container *ngFor="let card of playerHand">
            <div class="card">
                Card {{ card.rank }} of {{ card.suit }}
            </div>
        </ng-container>
        <div *ngIf="isPlayer" class="total-score">
            <span>Total Score:
                {{ calculateScore(playerHand) }}</span>
        </div>
    </div>

    <div class="card-container" *ngIf="dealerHand">
        <ng-container *ngFor="let card of dealerHand">
            <div class="card">
                Card {{ card.rank }} of
                {{ card.suit }}
            </div>
        </ng-container>
        <div *ngIf="!isPlayer" class="total-score">
            <span>Total Score:
                {{ calculateScore(dealerHand) }}</span>
        </div>
    </div>
</div>
HTML
<!-- winner-dialog.component.html -->

<div class="winner-dialog">
    <h1>{{ data.message }}</h1>
    <button mat-button class="close-button"
         (click)="closeDialog()">Close</button>
</div>
HTML
<!-- app.component.html -->

<div class="container">
  <h1>GFG Blackjack Game</h1>
</div>
<router-outlet></router-outlet>
CSS
/* game-list.component.css */

.game-list {
    margin-top: 20px;
    margin-left: 8%;
}

.game-list-title {
    margin-bottom: 2vmax;
    text-align: center;
    font-weight: bold;
    box-sizing: border-box;
    box-shadow: #777;
}

.game-table {
    width: 90%;
    border-collapse: collapse;
    border-radius: 5px;
}

.game-table th,
.game-table td {
    padding: 8px;
    text-align: left;
    border: 1px solid black;
}

.game-table th {
    background-color: #f2f2f2;
}

.game-table tbody tr:hover {
    background-color: #f5f5f5;
}

.game-table button {
    padding: 5px 10px;
    border: none;
    background-color: #0056b3;
    color: #fff;
    cursor: pointer;
    border-radius: 5px;
}

.game-table button[disabled] {
    background-color: #cccccc;
    cursor: not-allowed;
}

.actions button {
    margin-right: 5px;
    margin-bottom: 1vmax;
}

.link-button {
    display: inline-block;
    padding: 8px 16px;
    background-color: #1b599a;
    color: #fff;
    text-decoration: none;
    border-radius: 4px;
    cursor: pointer;
    margin-left: 8%;
    margin-top: 3%;
}

button:disabled {
    background-color: rgb(124, 170, 124);
    color: black;
    cursor: not-allowed;
    opacity: 0.5;
}

.noGames {
    width: 82%;
    margin-top: 20px;
    padding: 10px;
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
    border-radius: 4px;
    margin-left: 8%;
    margin-right: 8%;
}

.noGames p {
    text-align: center;
    margin: 0;
}
CSS
/* game-dialog.component.css */

.game-details-dialog,
.update-game-dialog {
    background-color: #fff;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.game-details-table,
.update-game-table {
    width: 100%;
    margin-bottom: 20px;
}

.game-details-table th,
.game-details-table td,
.update-game-table th,
.update-game-table td {
    padding: 10px;
    border-bottom: 1px solid #ddd;
}

.card {
    margin-bottom: 5px;
}

.close-button,
.play-button {
    padding: 10px 20px;
    margin-right: 10px;
    border: none;
    border-radius: 5px;
    background-color: #0056b3;
    color: #fff;
    cursor: pointer;
    float: right;
    margin-bottom: 1vmax;
}

.dialog-buttons {
    text-align: right;
}

h2 {
    text-align: center;
}
CSS
/* start-game.component.css */

h2 {
    margin-left: 12%;
    margin-top: 1.5vmax;
}

.hand {
    margin-bottom: 20px;
}

.action-button {
    padding: 10px 20px;
    margin-right: 10px;
    border: none;
    border-radius: 5px;
    background-color: #0056b3;
    color: #fff;
    cursor: pointer;
}

.button-container {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    margin-top: -16vmax;
    margin-left: 30%;
}

button {
    margin-bottom: 1.5vmax;
}

button:disabled {
    background-color: #95b1ce;
    color: black;
    cursor: not-allowed;
    opacity: 0.5;
}
CSS
/* hand.component.css */

.hand-container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    border: 1px solid lightgrey;
    border-radius: 10px;
    margin: 2vmax;
    margin-left: 12%;
    width: 20%;
    text-align: center;
}

.card-container {
    flex-wrap: 1;
}

.card {
    font-size: 1.1rem;
    padding-left: 1.5vmax;
    padding-right: 1.5vmax;
    padding-bottom: 1vmax;
}

.total-score {
    padding: 1.5vmax;
    border-top: 1px solid lightgrey;
    border-radius: 10px;
    font-weight: bold;
    text-transform: uppercase;
    font-size: 1.2rem;
}

h2 {
    padding-top: 1.5vmax;
    padding-left: 1.5vmax;
    padding-right: 1.5vmax;
    padding-bottom: 0;
    margin-bottom: 1.5vmax;
    text-decoration: underline;
}
CSS
/* winner-dialog.component.css */

h1 {
    color: green;
    font-weight: bold;
    font-family: cursive;
    font-size: 3rem;
    text-align: center;
    padding-top: 2vmax;
}

.winner-dialog {
    background-color: rgb(174, 213, 174);
}

.close-button {
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    background-color: green;
    color: #fff;
    cursor: pointer;
    float: right;
}
CSS
/* app.component.css */

.container {
    display: flex;
    flex-direction: column;
    align-items: center;
    background-color: darkslategrey;
    color: white;
    text-decoration: underline;
}

h1 {
    font-weight: bold;
    padding: 1.2vmax;
}
JavaScript
// game-list.component.ts

import { Component, OnInit } from '@angular/core';
import { Game } from '../../models/game';
import { GameService } from '../../services/game.service';
import { MatDialog } from '@angular/material/dialog';
import { GameDialogComponent } from '../game-dialog/game-dialog.component';
import { CommonModule } from '@angular/common';
import { Router } from '@angular/router';

@Component({
    selector: 'app-game-list',
    standalone: true,
    imports: [GameDialogComponent, CommonModule],
    templateUrl: './game-list.component.html',
    styleUrl: './game-list.component.css'
})
export class GameListComponent implements OnInit {
    games: Game[] = [];
    game: Game | undefined;
    constructor(private gameService: GameService,
        private dialog: MatDialog,
         private router: Router) { }

    ngOnInit(): void {
        this.getAllGames();
    }

    getAllGames(): void {
        this.gameService.getAllGames().subscribe(
            (data: Game[]) => {
                this.games = data;
            },
            (error) => {
                console.error('Error fetching games:', error);
            }
        );
    }

    getGameById(id: string): void {
        this.gameService.getGameById(id).subscribe(
            (data: Game) => {
                this.game = data;
                this.openDialog(this.game);
            },
            (error) => {
                console.error('Error fetching game:', error);
            }
        );
    }

    openDialog(game: Game): void {
        const dialogRef = this.dialog.open(GameDialogComponent, {
            width: '30%',
            data: game
        });

        dialogRef.afterClosed().subscribe(() => {
            console.log('The dialog was closed');
        });
    }

    updateGame(id: string): void {
        this.gameService.updateGame(id).subscribe(
            (data: Game) => {
                this.game = data;
                this.openUpdateDialog(this.game);
            },
            (error) => {
                console.error('Error updating game:', error);
            }
        );
    }

    openUpdateDialog(game: Game): void {
        const dialogRef = this.dialog.open(GameDialogComponent, {
            width: '30%',
            data: {
                game: game,
                type: 'update'
            }
        });

        dialogRef.afterClosed().subscribe(() => {
            console.log('The dialog was closed');
        });
    }

    endGame(id: string): void {
        this.gameService.endGame(id).subscribe(
            (data: Game) => {
                console.log('End Game:', data);
            },
            (error) => {
                console.error('Error in ending game:', error);
            }
        );
    }

    confirmDelete(gameId: string): void {
        const confirmDelete = window.confirm
            ('Are you sure you want to delete this game?');
        if (confirmDelete) {
            this.deleteGame(gameId);
        }
    }

    deleteGame(id: string): void {
        this.gameService.deleteGame(id).subscribe(
            (data: Game) => {
                console.log('Game Deleted:', data);
                this.gameService.getAllGames().subscribe(
                    (data: Game[]) => {
                        this.games = data;
                    },
                    (error) => {
                        console.error('Error fetching games:', error);
                    }
                );
            },
            (error) => {
                console.error('Error in deleting game:', error);
            }
        );
    }
}
JavaScript
// game-dialog.component.ts

import { CommonModule } from '@angular/common';
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';
@Component({
    selector: 'app-game-dialog',
    standalone: true,
    imports: [CommonModule],
    templateUrl: './game-dialog.component.html',
    styleUrl: './game-dialog.component.css'
})
export class GameDialogComponent {
    isUpdateDialog: boolean = false;
    constructor(@Inject(MAT_DIALOG_DATA) public data: any,
        private dialogRef: MatDialogRef<GameDialogComponent>,
        private router: Router) { }

    ngOnInit(): void {
        this.isUpdateDialog = this.data.type === 'update';
    }
    closeDialog(): void {
        this.dialogRef.close();
    }

    playGame(id: string): void {
        this.router.navigate(["/create"], { queryParams: { id } });
        this.dialogRef.close({ action: 'play' });
    }
}
JavaScript
// start-game.component.ts

import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { HandComponent } from '../hand/hand.component';
import { GameService } from '../../services/game.service';
import { Game } from '../../models/game';
import { ActivatedRoute, Router } from '@angular/router';
import { skip, take } from 'rxjs';
import { WinnerDialogComponent } from '../winner-dialog/winner-dialog.component';

@Component({
    selector: 'app-start-game',
    standalone: true,
    imports: [FormsModule, CommonModule, HandComponent, MatDialogModule],
    templateUrl: './start-game.component.html',
    styleUrl: './start-game.component.css',
})
export class StartGameComponent {
    game: Game = {
        _id: '',
        playerHand: [],
        dealerHand: [],
        playerTotal: 0,
        dealerTotal: 0,
        winner: '',
        status: 'ongoing',
    };
    gameId: string = '';
    startAfresh: boolean = false;
    constructor(
        private dialog: MatDialog,
        private gameService: GameService,
        private router: Router,
        private route: ActivatedRoute
    ) { }

    ngOnInit(): void {
        this.startNewGame();
    }
    checkStart(): void {
        this.startAfresh = true;
        this.startNewGame();
    }
    startNewGame(): void {
        this.route.queryParams.subscribe((params) => {
            console.log(params);
            this.gameId = params['id'];

            if (this.gameId && this.gameId != '' && !this.startAfresh) {
                console.log(this.gameId);
                this.gameService.getGameById(this.gameId).subscribe(
                    (data: Game) => {
                        this.game = data;
                    },
                    (error) => {
                        console.error('Error fetching game:', error);
                    }
                );
            } else {
                console.log('Starting game from scratch');
                this.gameService.createGame().subscribe(
                    (data: Game) => {
                        console.log('New game started:', data);
                        this.game = data;
                    },
                    (error: any) => {
                        console.error('Error starting new game:', error);
                    }
                );
            }
        });
    }
    dealCards(id: string): void {
        this.gameService.dealCards(id).subscribe(
            (data: Game) => {
                console.log('Cards dealt:', data);
                this.game = data;
                if (this.game.winner !== '') {
                    this.openWinnerDialog(this.game.winner);
                }
            },
            (error) => {
                console.error('Error dealing cards:', error);
            }
        );
    }

    hitAction(id: string): void {
        this.gameService.hitAction(id).subscribe(
            (data: Game) => {
                console.log('Hit Action:', data);
                this.game = data;
                if (this.game.winner !== '') {
                    this.openWinnerDialog(this.game.winner);
                }
            },
            (error) => {
                console.error('Error in hit action:', error);
            }
        );
    }

    standAction(id: string): void {
        this.gameService.standAction(id).subscribe(
            (data: Game) => {
                console.log('Stand Action:', data);
                this.game = data;
                if (this.game.winner !== '') {
                    this.openWinnerDialog(this.game.winner);
                }
            },
            (error) => {
                console.error('Error in stand action:', error);
            }
        );
    }

    homePage(): void {
        this.router.navigate(['/']);
    }
    endGame(id: string): void {
        this.gameService.endGame(id).subscribe(
            (data: Game) => {
                console.log('End Game:', data);
                if (this.game.winner !== '') {
                    this.openWinnerDialog(this.game.winner);
                }
                this.router.navigate(['/']);
            },
            (error) => {
                console.error('Error in ending game:', error);
            }
        );
    }
    openWinnerDialog(winner: string): void {
        if (winner !== 'Tie') {
            this.dialog.open(WinnerDialogComponent, {
                width: '30%',
                data: { message: `${winner} wins!` },
            });
        } else {
            this.dialog.open(WinnerDialogComponent, {
                width: '20%',
                height: '50%',
                data: { message: `${winner}` },
            });
        }
    }
}
JavaScript
// hand.component.ts

import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { Card } from '../../models/card';

@Component({
    selector: 'app-hand',
    standalone: true,
    imports: [CommonModule],
    templateUrl: './hand.component.html',
    styleUrl: './hand.component.css'
})
export class HandComponent {
    @Input() playerHand?: Card[] = [];
    @Input() isPlayer: boolean = true;
    @Input() dealerHand?: Card[] = [];

    constructor() { }

    calculateScore(hand: Card[]): number {
        let total = 0;
        let numAces = 0;

        for (const card of hand) {
            total += card.value;
            if (card.rank === 'A') numAces++;
        }

        while (total > 21 && numAces > 0) {
            total -= 10;
            numAces--;
        }

        return total;
    }
}
JavaScript
// winner-dialog.component.ts

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';

@Component({
    selector: 'app-winner-dialog',
    standalone: true,
    imports: [],
    templateUrl: './winner-dialog.component.html',
    styleUrl: './winner-dialog.component.css'
})
export class WinnerDialogComponent {
    constructor(@Inject(MAT_DIALOG_DATA) public data: any,
     private dialogRef: MatDialogRef<WinnerDialogComponent>,
      private router: Router) { }
    closeDialog(): void {
        this.dialogRef.close();
        this.router.navigate(["/"]);
    }
}
JavaScript
// winner-dialog.component.ts

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';

@Component({
    selector: 'app-winner-dialog',
    standalone: true,
    imports: [],
    templateUrl: './winner-dialog.component.html',
    styleUrl: './winner-dialog.component.css'
})
export class WinnerDialogComponent {
    constructor(@Inject(MAT_DIALOG_DATA) public data: any,
        private dialogRef: MatDialogRef<WinnerDialogComponent>,
        private router: Router) { }
    closeDialog(): void {
        this.dialogRef.close();
        this.router.navigate(["/"]);
    }
}
JavaScript
// app.routes.ts

import { Routes } from '@angular/router';
import { GameListComponent }
    from './components/game-list/game-list.component';
import { StartGameComponent }
    from './components/start-game/start-game.component';

export const routes: Routes = [
    { path: '', component: GameListComponent },
    { path: 'create', component: StartGameComponent },
    { path: ':id', component: GameListComponent },
    { path: 'deal/:id', component: StartGameComponent },
    { path: 'update/:id', component: GameListComponent },
    { path: 'hit/:id', component: StartGameComponent },
    { path: 'stand/:id', component: StartGameComponent },
    { path: 'end/:id', component: StartGameComponent }
];
JavaScript
// app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { StartGameComponent } from './components/start-game/start-game.component';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { GameListComponent } from './components/game-list/game-list.component';
import { MatDialogModule } from '@angular/material/dialog';

@NgModule({
    declarations: [
        AppComponent,
        GameListComponent,
        StartGameComponent
    ],
    imports: [
        BrowserModule,
        HttpClientModule,
        CommonModule,
        FormsModule,
        MatDialogModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

To start the frontend run the following command.

ng serve

Output:

output-(1)

OUTPUT GIF FOR BLACKJACK GAME PROJECT USING MEAN STACK




Reffered: https://www.geeksforgeeks.org


Dev Scripter

Related
Multiplayer Tic Tac Toe Game using React &amp; Node Multiplayer Tic Tac Toe Game using React &amp; Node
Blogging Platform using MERN Stack Blogging Platform using MERN Stack
Canonicalization vs Redirection vs Pagination in SEO Canonicalization vs Redirection vs Pagination in SEO
How to Use the coeftest() Function in R How to Use the coeftest() Function in R
Design a Online Grocery Website in HTML CSS &amp; JavaScript Design a Online Grocery Website in HTML CSS &amp; JavaScript

Type:
Geek
Category:
Coding
Sub Category:
Tutorial
Uploaded by:
Admin
Views:
11