Exploding Kittens Card Game - React, Nodejs and Redis

nabajits21

NabajitS

Posted on May 8, 2023

Exploding Kittens Card Game - React, Nodejs and Redis

Introduction

If your familiar with Redis you might know it is primarily used as a caching database along with a few other use cases.

What this basically means is that, a Redis database acts as a high-speed data store that sits between the client and the main database, providing fast data retrieval compared to other primary databases such as MySQL, MongoDB, etc. This makes getting query results very fast.

Enough about what it is, you can read up more about Redis from their docs.

What this article covers

What this tutorial is about is How to build a Card game using React and Redux Toolkit on the frontend, Nodejs on the backend and Redis as the primary database.
Now, you might be thinking why use Redis as a primary database, and for that question my friend I only have one answer.

meme
On a more serious note, Redis provides many modules such as RedisSearch, RedisJson etc which allows us to extend the core Redis functionality, thus allowing us to use it more like a primary database.

This article is part 1 of a two part series, in this part I'll go over the frontend implementation of the game. So if you are only concerned about the backend implementation you can visit just the second blog.


Prerequisites πŸ–₯️

  • React (Basics)
  • Nodejs (Basics)
  • Typescript (Basics)

Let's get started

If your familiar with game "Exploding KittensπŸ˜ΌπŸ’£" that's great, but my version of the game is different. So, even if you're not familiar with the game it doesn't matter. Here's how my version works.

This will be an online single-player card game that consists of 4 different types of cards

  • Cat card 😼
  • Defuse card πŸ™…β€β™‚οΈ
  • Shuffle card πŸ”€
  • Exploding kitten card πŸ’£

There will be a button to start the game. When the game is started there will be a deck of 5 cards ordered randomly. Each time user clicks on the deck a card is revealed and that card is removed from the deck. A player wins the game once he draws all 5 cards from the deck and there is no card left to draw.

Rules

  • If the card drawn from the deck is a cat card, then the card is removed from the deck.
  • If the card is exploding kitten (bomb) then the player loses the game.
  • If the card is a defusing card, then the card is removed from the deck. This card can be used to defuse one bomb that may come in subsequent cards drawn from the deck.
  • If the card is a shuffle card, then the game is restarted and the deck is filled with 5 cards again.

What our game offers

  • Our game will allow a player to draw a random card from the deck once the game is started.
  • Allow users to create a username to enter the game and create a leaderboard to record how many games they won.
  • We will use Redis as a database to store the points of all the users and NodeJs using typescript for the backend. One game won is equal to one point.

This is part 1 of a 2 part series, this article will cover the frontend implementation of the game and the second part will cover the backend implementation using Redis as the primary database.

Here's the projects folder structure

-
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ components
|   β”‚   β”œβ”€β”€ Navbar.jsx
|   |   β”œβ”€β”€ Login.jsx
|   |   β”œβ”€β”€ Signup.jsx
|   |   β”œβ”€β”€ Highscore.jsx
|   β”‚   └── componentStyle.css
|   β”œβ”€β”€ redux
|        └──slices
|           └── userSlice.js
|   β”œβ”€β”€ App.jsx
|   β”œβ”€β”€ Board.jsx
|   β”œβ”€β”€ Board.css
β”‚   └── main.jsx
β”œβ”€β”€ package.json
└── package-lock.json
Enter fullscreen mode Exit fullscreen mode

The project is setup using vite
After initial setup run

npm run dev
Enter fullscreen mode Exit fullscreen mode

to start the frontend

Before we begin I just want to state that there's a lot of room for optimization and removal of redundant code, I just wanted to get through the frontend as soon as possible.

Let's Begin
So our main game logic lies inside Board.jsx

function Board() {
const [deck, setDeck] = useState([])

const initializeDeck = () => {

const cards = [
 { cardName: 'Cat card', cardTitle: 'first title' },
 { cardName: 'Defuse card', cardTitle: 'second title' },
 { cardName: 'Shuffle card', cardTitle: 'third title' },
 { cardName: 'Exploding kitten card', cardTitle: 'forth title' }
]
const tempDeck = [];

const getRandomInt = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min);
}

for (let i = 0; i < 5; i++) {
   tempDeck.push(cards[getRandomInt(0, cards.length - 1)]);
}    
 return tempDeck;
}

useEffect(() => {
        const tempDeck = initializeDeck();
        setDeck(tempDeck);
}, [])

return (
  <div>
   Board
  <div>
 )
}

export default Board
Enter fullscreen mode Exit fullscreen mode

We create a state variable deck, which will hold the 5 cards for the game. And we create the initializeDeck function which will fill the deck with 5 random cards when the game starts. We extract it out into a function because we will need to reuse it multiple times.
We then call the initializeDeck function inside useEffect and set the deck on initial render.

const [diffuseCardCount, setDiffuseCardCount] = useState(0)
const [currentCard, setCurrentCard] = useState(null)
const [cardIsShowing, setCardIsShowing] = useState(false);
const [gameOver, setGameOver] = useState(false);
const [gameWon, setGameWon] = useState(false);

const handleCardShow = () => {
 const tempDeck = [...deck];
 const currCard = tempDeck[tempDeck.length-1];
 setCurrentCard(currCard)
 setCardIsShowing(true)
 setTimeout(() => {
   if(tempDeck.length == 1 && currCard.cardName != "Shuffle card" 
 && currCard.cardName != "Exploding kitten card"){
    setGameWon(true)
    dispatch(updateScore())
 }
 if(currCard.cardName == "Cat card"){
   //remove card from deck
   tempDeck.pop();
   setDeck(tempDeck);
 }
 else if(currCard.cardName == "Defuse card"){
 setDiffuseCardCount(prev => prev + 1)
 tempDeck.pop();
 setDeck(tempDeck);
 }
 else if(currCard.cardName == "Shuffle card"){
 restartGame() //Restart Game 
}
 else if(currCard.cardName == "Exploding kitten card"){ //exploding kitten card
  if(diffuseCardCount > 0 ){ //if player has any diffuse cards
   setExplodeAction(true);   //Game over
  }
  else{
   setGameOver(true)
 }
}
 setCurrentCard(null); //set currentCard to null after 2.5 secs
 setCardIsShowing(false)
 }, 2500)

}

<div className='board' >
   <div className="container">
     <div className='card-cont' >
      {
       deck && deck.map((card, ind) => (
        <div key={ind} className={`card card-${ind+1}`}>card {ind} 
        </div>
     ))
      }
    </div>

    {currentCard && ( 
              <div className='card active-card'> 
                    {  currentCard.cardName  }
              </div>
      )}

     { !cardIsShowing && <button className='show-btn' onClick= 
        {handleCardShow} >show card</button>
     }  

            <h2>Diffuse Cards Available - {diffuseCardCount}</h2>
        </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Then we introduce 5 more state variables diffuseCardCount to keep track of total diffuse cards drawn ingame(in order to counter the exploding kitten card). And the other 4 are what their names suggest.
Inside the component we map over the deck to display the 5 cards, display the current card on show card button press by running the handleCardShow function. We display the card for 2.5 secs before any action takes place.

const restartGame = () => {
    const tempDeck = initializeDeck();
    setDeck(tempDeck);
    setDiffuseCardCount(0);
    dispatch(fetchHighscore())
    setGameOver(false)
    setGameWon(false)
}

return (
<>
 <Navbar/>
 {
   gameWon ? (
     <div>
       <h1>You Won</h1>
       <button onClick={ restartGame } >Restart</button>
      </div>
        ) : (
    gameOver ? (
       <div>
       <h1>Game Over</h1>
         <button onClick={ restartGame } >Restart</button>
       </div>
        ) : (
   <div className='board' >

    <div className="container">
       (...._Same code as before_)
     </div>

    <Highscore highscore={highscore} />
   </div>
        )
      )
    }
</>
Enter fullscreen mode Exit fullscreen mode

Then, if user wins set gameWon to true and if user losses set gameOver to true and display their respective div's. On click restart button the restartGame function gets called thus reinitializing the deck and resetting all state variables to initial condition.
The Highscore component gets the top 10 user high scores from the Redis database and displays it on the screen.

I won't go over the Redux code you can view it from the repo which I'll link below as it contains a very basic code, there was no need to use Redux for this project. I was just playing around and felt like using Redux.

Here's the link to the codebase - code

πŸ’– πŸ’ͺ πŸ™… 🚩
nabajits21
NabajitS

Posted on May 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related