React Blackjack game

Posted on

Problem

I started to learn React, and I wrote simple Blackjack game. I just need code review because I don’t know anyone who know React well.

Whole state and logic of my game I have in Game.js component:

import React, { Component } from 'react';
import Hand from './Hand';
import DealersHand from './DealersHand';
import Bet from './Bet';
import cards from '../../data/cards';
import { Redirect } from 'react-router';

class Game extends Component{

    state = {
    gameStarted: false,
    bet: 5,
    deck: [],
    playerCards: [],
    dealerCards: [],
    money: 100,
    playerScore: 0,
    dealerScore: 0,
    playerWin: false,
    dealerWin: false,
    playerName: '',
    draw: false,
    finish: false

}

betLess = () => {
    if(this.state.bet >= 1){
        this.setState(state => ({ bet: state.bet - 5 }));
    }
}
betMore = () => {
    if(this.state.bet <= this.state.money){
        this.setState(state => ({ bet: state.bet + 5 }));
    }
}

letsPlay = () =>{
    this.setState({gameStarted: true})
}

getRandomCards = () =>{

    for(let i = 0; i < 2; i++){
        setTimeout(this.getPlayerCard, 100);

    }
    this.getDealerCard();
}

getDealerCard = () =>{

    const { deck, dealerCards, playerScore, money, bet } = this.state;
    let { dealerScore } = this.state 
    const deckCards = deck.length > 0 ? deck : cards;

    const randomIndex = Math.floor(Math.random() * deckCards.length);
    dealerScore += deckCards[randomIndex].cardValue;
    dealerCards.push(deckCards[randomIndex]);
    deckCards.splice(randomIndex, 1);

    const statesToSet = {
        dealerScore: dealerScore,
        dealerCards: dealerCards,
        deck: deckCards
    };

    if(dealerCards.length === 2){
        if(playerScore !== 21 && dealerScore < 15){
            const randomIndex = Math.floor(Math.random() * deckCards.length);
            dealerScore += deckCards[randomIndex].cardValue;
            dealerCards.push(deckCards[randomIndex]);
            deckCards.splice(randomIndex, 1);
            statesToSet.deck = deckCards;
            statesToSet.dealerCards = dealerCards;
            statesToSet.dealerScore = dealerScore;
            this.setState(statesToSet);
        }

        if(dealerScore === playerScore){
            statesToSet.draw = true;
            statesToSet.money = money + bet;
        } else if(dealerScore === 21){
            statesToSet.dealerWin = true;
            statesToSet.money = money - bet;
        } else if(playerScore === 21){
            statesToSet.playerWin = true;
            statesToSet.money = money + bet * 2;
        } else if(dealerScore > playerScore){
            statesToSet.dealerWin = true;
            statesToSet.money = money - bet;
        }else if(dealerScore < playerScore){
            statesToSet.playerWin = true;
            statesToSet.money = money + bet * 2;
        }else if(dealerScore > 21){
            statesToSet.dealerWin = true;
            statesToSet.money = money - bet;
        }
    }
    if(money === 0){
        this.endGame();
    }
    this.setState(statesToSet);
}

startNewGame = () =>{
    this.setState({
            gameStarted: false,
            deck: [],
            playerCards: [],
            dealerCards: [],
            playerScore: 0,
            dealerScore: 0,
            playerWin: false,
            dealerWin: false,
            draw: false
        });

}

getPlayerCard = () => {

    const { deck, playerCards, bet } = this.state;
    let { playerScore, playerWin, dealerWin, money } = this.state;
    const deckCards = deck.length > 0  ? deck : cards;
    const randomIndex = Math.floor(Math.random() * deckCards.length);
    playerScore += deckCards[randomIndex].cardValue;
    playerCards.push(deckCards[randomIndex]);

    if(playerScore === 21){
        playerWin = true;
        money = money + bet * 2;
    }
    if(playerScore > 21){
        dealerWin = true;
        money = money - bet;
    }

    this.setState({
        deck: deckCards,
        playerCards: playerCards,
        playerScore: playerScore,
        playerWin: playerWin,
        dealerWin: dealerWin,
        money: money
    });

}

endRound = () =>{
    this.getDealerCard();
}

endGame = () =>{
    const { playerName, money } = this.state;
    const highscores = JSON.parse(localStorage.getItem('highscores'));
    highscores.push({name: playerName, score: money});
    localStorage.setItem('highscores', JSON.stringify(highscores))
    this.setState({finish: true});
}

componentDidMount(){

    this.getRandomCards(4);
    const playerName = localStorage.getItem('name');
    this.setState({playerName: playerName})

}

componentDidUpdate(){
    if(this.state.deck.length === 0){
        this.getRandomCards();
    }
}


render(){
    const { gameStarted, bet, money, dealerScore, playerScore, dealerCards, playerCards, playerWin, dealerWin, draw, finish } = this.state;

    if(finish){
        return <Redirect to="/highscores"/>
    }
    if(gameStarted){
        return(
            <div className="col-md-12 col-xs-12">
                <DealersHand cards={dealerCards} />
                <div className="panel">
                    <h3>Dealer's score: {dealerScore}</h3>
                </div>
                <Hand cards={playerCards} />
                {draw || playerWin || dealerWin ? 
                <div className="game-status">
                    {dealerWin ?<div className="info lose">You lost.</div> : null}
                    {playerWin ?<div className="info win"> You won!</div> : null}
                    {draw ? <div className="info draw">Draw.</div> : null}
                    <div>
                        <div className="buttons">
                            <button className="game-button" onClick={this.startNewGame}>Play again</button>
                            <button className="game-button" onClick={this.endGame}>End game and save score</button>
                        </div>
                    </div>
                </div> 
                : null}
                <div className="panel">
                        <h3>Your score: {playerScore}</h3>
                </div>
                <div className="panel">

                    <button className="game-button" onClick={this.getPlayerCard} disabled={playerScore > 20 || playerWin || dealerWin || draw}>Get card</button>
                    <button className="game-button" onClick={this.endRound} disabled={playerScore > 20 || playerWin || dealerWin || draw}>End round</button>
                </div>
            </div>
        )
    }else{
        return(
            <Bet bet={bet} money={money} betLess={this.betLess} betMore={this.betMore} letsPlay={this.letsPlay} />
        )
    }

}
}
export default Game;

GitHub

GitHub Pages

Can anyone tell me what can I do better or what I did wrong?

Solution

The code in componentDidUpdate looks a little odd to me.

if(this.state.deck.length === 0){

You are checking if there aren’t any cards, then drawing some cards. This works because you are initialising the deck within getPlayerCard. What I would do is put the deck initialisation within the startNewGame method instead.

getDealerCard and getPlayerCard are doing a bit too much; you could move the victory conditions to separate methods.

In componentDidMount, this.getRandomCards(4); takes in a parameter but the method doesn’t use any parameters.

You have 3 variables in state which represent the victory state; playerWin, dealerWin, draw. These all cannot be true at the same time. It would be better to have one variable for the victory state; an option would be like an Enum in Typescript

Looking at the code in your repository, there’s a bug in DealersHand.js. You are rendering the second card twice; once in the map function, then again when you make the decision to render it face-down or face-up. Instead, you just need to conditionally render it when the dealer has only 1 card.

{
    cards.length == 1
    &&
    <Card
    key="5"
    className={ "cards card-back" }
    />
}

The link on your ReadMe is also wrong. I used the link you provided in the question. I liked the game, but the lack of cards 2-8 took some adapting.

Leave a Reply

Your email address will not be published. Required fields are marked *