FelipeRes
Posted on December 4, 2020
Develop a digital card game can be an interesting exercise for architecture software. You don't need to think about complex math algorithms and frame-accurate precision like action games unless you are creating a peculiar card game. I am talking about simple card games like UNO, Blackjack, or games with a complex game design like Magic and Yu-Gi-Oh! You can create a simple mechanic with some rules and have so much fun with a lot of tools, variations, and game modes that can be easily introduced procedurally during the game development. But it will work only if you create good software architecture.
In this series of posts, I will explore a bit of how to create, improve, and maintain a complex card game like Magic, Hearthstone, and other digital games. I am creating my own card game called Hermetica and in the next posts, I will talk more about it. For while, let's get started with some fundamentals.
A card game is essentially a turn-based game with the entities represented by cards. The most that I will show in this post is valid for other turn based games. By overview, we can split the game architecture in theses parts:
Game Core: The main API that control the game rules and players.
Action Request: The "input" of the game that can be queued and processed by the Game Core. It can be made by a player, a lot of players or IA system or automatically created by the game.
Log: The register of each event that happened in the game until the end.
Visual representation: Is about the graphics of the game and UI interaction. The application will run around the Game Core.
In this post, let's get started with the concept of the Game Core.
Game Core is the main part of the card game software. It represents the main entities and the rules of the game. Imagine a game like chess: You have pieces with a moveset, a table with tiles zones, and the rules. The Game Core, in a software approach, doesn't know how the game will be shown to the player. It only offers an application interface to interaction and can represent the entities with poco class.
The core needs to work without any dependency if possible. This is important to maintain portability if your game will be an online or multiplatform game and create unit tests can be easy too.
Game Core API responsibilities:
- Receive the player movement input by a request.
- Check if each movement request is valid.
- Apply the consequences of movement.
- Determinate when a plyers ends the turn and when the game ends.
Let's thinking in a simple card game like UNO and for while, let's forget the special cards. In this game, we have up to 10 players around a deck on a table with cards in their hands. One card is drawn from the deck and positioned on the table. The first player needs to play a card with the same color or the same number and finish your turn to the next player. You probably know the other rules, I am only listing the important information to modeling the game.
Here is the code of poco classes:
public class Card {
public string color;
public int number;
}
public class Player {
public string name;
private List<Card> hand;
}
public class Game {
private List<Player> players;
private List<Card> deck;
private List<Card> stack;
private Player currentPlayer;
}
In your turn, the player automatically draws a card and can do only one movement in your turn that is to put one card face up in the top of a stack of cards. We can give him/her a function.
public class Player {
//...
public void Drawn(Card card){
if(hand.Contains(card)){
hand.Add(card);
}
}
public void Play(Card card){
if(hand.Contains(card)){
hand.Remove(card);
}
}
}
The Game Core needs to know who is the current player and it can avoid the movement. But before, it needs to know how to change the player's turn and which one is the card on top of the stack. Let's write some functions to support the Game Core API:
public class Game {
//...
private void NextPlayer(){
//Tip to circular list
player.AddLast(players.RemoveFirst());
//Assign the current player
currentPlayer = players.First();
//Current player drawn the card automatically the
currentPlayer.Draw(deck.RemoveFirst());
}
//The game needs a easy way to get the card in top of stack
private Card StackCard(){
return stack[stack.Count - 1];
}
//The game needs to know if a card is valid to play
private bool CorrectCard(Card card){
return if(card.number == StackCard().number ||
card.color == StackCard().color);
}
}
The function NextPlayer will runaways after a valid player movement request. And the functions StackCard and CorrectCard are helpers to clean the code and e easy setup. The function that the API uses to expose to application needs to know who is playing, witch card, the current player turn, and the current card on top of the stack:
public class Game {
//...
//This is the public API function to receive a player movement
public void Play(Player player, Card card){
//Check if is the expected player turn
if(player == currentPlayer){
//Check too if the card is valid to play
if(CorrectCard()){
//Do the drawn movement
currentPlayer.Play(card);
//stack receives the card
stack.Add(card);
//wait for the next player movement
NextPlayer();
}
}
}
}
The Game Core needs to be clean and simple and needs to work in itself universe without dependencies. All other setups need to work around it
But it's just the beginning of the journey! Our game core can have a lot of other functionalities like throw exceptions to invalid movements, factory objects to set up the game cards, functions to check game design problems like when the cards over. All these ideas will be worked on in future posts.
Thanks to read!
Posted on December 4, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.