Hi folks! I want to show you a game I made with React. It’s a popular russian card-game - Fool (or Durak). I created this project for fun and some practice with React and CSS. You can see the source code and result here.
And also I want to tell you how I organized a game logic and which design patterns I used in this.
Gameplay
In this game, the game process can be divided into several basic steps.
- deal cards
- choose the first attacker
- discard a card (attack)
- discard a card (defend)
- take cards if you cannot beat
- remove cards from the table if the attack is over
As you can see, each of these steps is an action - ‘to deal...’, ‘to choose...’, ‘to discard...’ And these actions are ordered. And the game process dependents on the result of this actions. Also some actions have options (for example, ‘discard a card’).
Command pattern
This pattern is ideal in this case. With this pattern we implement the logic of the actions in different classes, instead of different functions as usual. These classes will share the same "public" interface. As usually it is the ‘apply’ (or ‘execute’) method, which returns a result of the command.
For example:
class SetFirstAttackerCommand {
errors = [];
apply() {
const attacker = this.setByTrump();
return {
success: this.errors.length === 0,
attacker,
}
}
setByTrump() {
// here the logic how to choose the first attacker by trump
}
}
class TakeOpenedCardsCommand {
errors = [];
apply() {
this.moveCardsToPlayer();
return {
success: this.errors.length === 0
}
}
moveCardsToPlayer() {
// here the logic how to move cards to the player’s hand
}
}
Our next step will be creating instances of these command. Then we will call the apply method, which calls the ‘setByTrump’ и ‘moveCardsToPlayer’, which contain all main logic for our commands.
This is one of my favorite pattern)
Queue Data Structure
I used a queue (FIFO) as the command container. When a new command is added, a new queue node is created and a link to that node is stored in the current last node. Then at the beginning of each frame we check if there are any commands in the queue. If there are, we apply the first command. If its result has completed status, then this command will be removed.
Strategy pattern
This pattern is excellent for actions like ‘discard a card’, when behavior depends on some factor. In our case the factor is the player.
If the current player is an attacker, we need to check if there are open cards on the table and the like.
If the current player is a defender, the result of the command will depend on the rank of the card.
Here is an example strategy:
class DiscardCardStrategy {
strategy = null;
constructor(gambler) {
this.gambler = gambler;
this.strategy = gambler.isAttacker ? DiscardCardStrategy.AttackerStrategy : DiscardCardStrategy.DefenderStrategy;
}
discard(card, openedCards) {
this.strategy({ card, openedCards });
}
static DefenderStrategy({ card, openedCards }) {
// behavior logic
}
static AttackerStrategy({ card, openedCards }) {
// behavior logic
}
}
One of my favorite pattern too)
After all
You know, I had fun when I was making this game. I think it's important sometimes to do something just like that - for the soul. In my case, it reminds me why I decided to become a programmer )))
Top comments (0)