Open In App

Blackjack Game using MEAN Stack

Last Updated : 15 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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



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

Similar Reads