As part of a continuous effort to deepen resources that GameSparks offers to our users, I have produced a full demonstration of a Hearthstone style, ranking, matchmaking and challenge system, showing the inherent flexibility and customisability of the GameSparks platform. To support this, I am writing a detailed description which can be seen below. The full series of Hearthstone tutorials can also be found here.
Consider what is going on behind the scenes of Hearthstone and how that logic would work on the back-end. Think about how each sequence of events are fired whenever a decision is made through the front-end, and how this impacts the back-end. In order to produce a turn-based card game such as this successfully, a well-made and highly capable back-end is most certainly needed. A modular back-end, made from many comprehensive components which work together to bring any idea to life within a short space of time to accommodate for prototyping.
To create the Hearthstone demonstration, I started by adding functionality to existing GameSparks components, preparing them to work in a way fit for the Hearthstone example. The most basic building block of any game and any back-end is the player.
The player had to be initialised in a way that suited the game, to do that I had to give the player a rank, starting at the lowest rank which is rank 25 with 0 stars. I also had to give the player a deck of cards. When the player is created they’re given three basic cards, inputted manually by referring to their string name, in my case being – ‘Archer’, ’Warrior’ and ‘Mage’. The starting deck also contains one rare card that is given to the player randomly from a collection of cards which is stored in my game’s database.
All of this was done in the ‘Registration Response’ which is created when making a ‘Registration Request’. When a player registers for the game, they use the ‘Registration Request’. The platform supplies this and also adds the new player to the player database. By having logic in the response I can reference the newly created player and add values to their database, saving values which were used later down the line. For example, the rank was used to search for appropriate opponents for matchmaking and the deck was used to draw cards to the player’s hand in game.
Using the NoSQL database that utilises MongoDB, I created three collections of cards by tier – Common cards, rare cards and legendary cards. I also created a template used by every card that contains information about the type of card, tier of card, attack rating, hitpoint rating, spawn cost and the effect that card holds. These stats were used when adding a card to deck, pulling the card from the deck to the player’s current hand, placing the card on the playing field and using the card while on the playing field. The way the cards work in this example is completely modular. Across all tiers, cards were referenced and retrieved from the database in the same way and were organised in their documents within their collections.
The GameSparks platform provides a matchmaking system out of the box. All to do was create a match and configure it using a friendly screen with drop down lists and toggle switches. I specified that I wanted to make a match between two players, and added thresholds to find a suitable match. The players called this match by using a ‘Matchmaking Request’ and referencing the match using its shortcode.
In my demonstration I made two matches, one for casual and one for ranked. The casual match looked for any player looking for a game, regardless of level, while ranked matched players based on skill.
Once two players were matched, they received a ‘Match found’ message. This is a brilliant place to run logic that creates a ranked challenge. Basic challenges work in two ways on the GameSparks platform:
- A player can create a public challenge, and other players join that challenge in a fashion similar to a server browser.
- A player can creates a private challenge and invites other players.
The GameSparks platform is designed from the ground up to be flexible and allows you to customize the functionality. In my case, I wanted to create a challenge between the two matched players, so I added some cloud code to the ‘Match Found Message’. In this, I specified that I wanted the first participant, which was the first index in the participants array, to send the ‘Create Challenge Request’. The ability to send any request as or for any player, is yet another powerful combination of features in our platform.
The challenge created by the first participant was a private challenge and so I specified that I wanted the second participant to be invited to this challenge. When the challenge was created by the first participant, a message was sent to the second participant. This was where the magic happened – the second player didn’t need to manually accept the challenge. After writing some cloud code in the ‘Challenge Issued Message’ checks are made for the kind of challenge being issued, and is accepted if it’s the same kind of challenge.
After the invitation is automatically accepted, a ‘Challenge Started Message’ was sent to both players. Once again this message was referenced to both players. One being the challenger, who was the first participant, and the second being the challenged player, who held the first index in the challenged players array. These references were in the form of a string ID which was used to load players using Cloud code. I wrote a check which searched for who received the message. if it was the challenger, then a sequence of code was run which initialised the game.
The cloud code built three JSON objects:
- The current hand of each player, which was an object that contains two objects in it, each for every player referenced by a key which represents each player’s hand. For example, currentHand[playerId][cardName] got the currentHand object, then the player’s hand object, then the card itself.
- The playing field object which had two objects within it similar to current hand.
- The player stats object, similar to the two objects before it, had two objects within it for either player. This contained info about health, current mana, overall mana, whether player pulled a new card and the player was protected by a taunt card meaning they couldn’t be attacked directly.
In the following image you can see how the currentHand and playfield worked. The cards were referenced as c# to keep them unique, every card pulled increments the cardsPulled int which followed the ‘c’. Referencing the card around the playfield consisted of referring to the right playingField or currentHand and the card name, for example playfield[player1Id].c3.
I gave the player the ability to make four calls. These four events referenced the challenge currently taking place using the challenge’s unique Id, similar to the player’s Id used to reference it from anywhere in Cloud code. Whenever one of these events were called, every player in the challenge was conveniently sent an ‘Action Taken message’ which can be used to refresh the game in the front end. Those four events are:
- pullCard: which pulls a card from the player’s deck into the currentHand. This card was constructed by referring to the database document which contained the card by searching its name and tier. Once the card was found, the details for name, attack, hitpoints, spawn cost and effect were constructed in an object and then used to save information about the card.
- pushCard: moves a card from the currentHand to the playField in reference to its unqiue Id, for example c3. The card was constructed as an object inside the playField and information from the previous object in the currentHand was used to make it with a bit of variance. The card had current and max health stats, these kept track of its health and how much it could be healed. The card also had a Boolean to allow it to attack or not. By default, cards were spawned unallowed to attack, but if they had a ‘Charge’ effect, the Boolean was true and they were allowed to attack. Some cards directly attacked the opponent as they spawned, due to their effect. Once the card was moved into the playField object, it retained its unique Id, in this example c3.
- playCard: this event kept things modular. Based on the action, which was a string, a sequence was taken. The action can either be ‘Heal’ or ‘Attack’. Both these cards treated the target differently. The Heal action looked for the card in the player’s own playField while the Attack action looked for cards in the opponent’s playField. The attack target could also be the opponent. If the opponent didn’t have a taunt card to protect it, its protection Boolean in the playerStats object was false, which allowed cards to directly attack it. Depending on the card’s hitpoints and attack, and the target’s hitpoints and attack, it determined which cards walked out of the battle or not.
- endTurn: This event allowed the player to end their turn and refresh their playField and playerStats. So every card could attack the next turn, the player earned a new mana gem, increasing their overall mana pool and regenerated their current mana to the maximum.
The ‘Turn Taken’ message sent to both players could be used to track the health that both players had. If any player’s health reached zero, the other player won. GameSparks comes ready with Won, Lost and Draw messages which I used to either increase or decrease the player’s ranks if the challenge was a ranked one.
All of this information was calculated per event call made and saved in their respectful objects. This was very simple to make, test and debug thanks to GameSpark’s Test Harness.
The Test Harness
The Test Harness allowed me to test every building block after I’ve prototyped it. After adding Cloud code to any request, response or message, I could walk through this code step by step, line by line to determine if my variables were what I expected them to be, whether my functions were doing what I expected them to and whether the results were what I hoped for them to be.
One of the most impressive parts about this, was when I simulated a game, I opened two tabs on my browser to simulate two different players and called the matchmaking event to make a ‘Matchmaking Request’ so every message and response was available for debug from the matchmaking event up until the challenge was created. So to paint the picture:
- I called the matchmaking event for both players, a match was found which triggered the debugger for the ‘Match Found’ message, allowing one participant to create the challenge and invite the other participant.
- This triggered the ‘Challenged Issued’ message, accepting the invitation of the challenge
- When both players were in the challenge, the ‘Challenge Started’ message was sent to both players. These were both debuggable and initialises the game through the Challenger.
By using the GameSparks platform I have been able to seamlessly imitate Hearthstone’s ranking, matchmaking and multiplayer challenge systems. Due to the inherent flexibility and customisability, GameSparks allows developers to take advantage of this single integrated tool in however way they see fit, meaning it can plug into any game or application of your choosing. With the creation of further variant game type tutorials and demonstration, we will continue to show how malleable GameSparks really is.
For more information including further examples of sample code, please see the the full series of tutorials in our documentation pages.