Exploding Kittens Card Game - React, Nodejs and Redis
NabajitS
Posted on May 8, 2023
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.
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
The project is setup using vite
After initial setup run
npm run dev
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
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>
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>
)
)
}
</>
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
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
November 13, 2024