Challenge

This section will rely heavily on Challenge Events. There will be a lot of calls to retrieve and save the cards, stats, playingField, and currentHand. Player Ids and Challenge Id will constantly be values to reference the rest of the game - these two Ids will allow you to do anything with the challenge.

Create 4 Events:

We're also going to be editing the ChallengeTurnTakenMessage.

Important! Make sure the Cloud Code for these Events is edited in the Challenge Events section of the Cloud Code>Scripts panel NOT the Events section.

challengeInstanceId? When calling Challenge Events you'll automatically get a challengeInstanceId string input value.

action_pullCard

This challenge Event relies on the pullCard module. It adds a random card from the deck into the player's current hand. The player is unable to pull another card from the deck during this round after this Event is executed.

//Load challenge
var chal = Spark.getChallenge(Spark.getData().challengeInstanceId);

//Retrieve player stats
var playerStats = chal.getScriptData("playerStats");
//Retrieve player Id
var pId = Spark.getPlayer().getPlayerId();

if(playerStats[pId].hasPulled === false){

  //Retrieve current hands
  var currentHand = chal.getScriptData("currentHand");

  //Run the sequence to pull a new card
  require("pullCardModule");

  //Player can't pull another card this round
  playerStats[pId].hasPulled = true;

  //Save current hand and player stats
  chal.setScriptData("currentHand", currentHand);
  chal.setScriptData("playerStats", playerStats);
}
else{
  Spark.setScriptError("Error", "Already pulled card this round");
}

action_pushCard

This Event will have a single input value:

This Event will move cards from the currentHand to the playField if the player has enough mana to play the card. Using the unique Id given to the card (for example c1 or c0), we move the card from one object to another. If cards have certain effects, such as charge or direct attack, that will trigger a sequence of code.

//Load the challenge
var chal = Spark.getChallenge(Spark.getData().challengeInstanceId);
//Retrieve player Id
var pId = Spark.getPlayer().getPlayerId();

//Retrieve the slot
var cardName = Spark.getData().name;
//Retrieve the current hand
var currentHand = chal.getScriptData("currentHand");
//Retrieve the current player's hand
var playerHand = currentHand[pId];
//Get the cards name or type
var cardObj = playerHand[cardName];

//Retrieve player stats
var playerStats = chal.getScriptData("playerStats");

//Retrieve Mana
var playerMana = playerStats[pId].currentMana;

if(cardObj == null){
    Spark.setScriptError("result", "Card doesn't exist");
    Spark.exit();
}
//Is there enough mana to spawn the card?
if( playerMana >= cardObj.spawnCost){

  var playerStats = chal.getScriptData("playerStats");
  //New mana
  playerStats[pId].currentMana = playerStats[pId].currentMana - cardObj.spawnCost;

  //Retrieve the playing field
  var playField = chal.getScriptData("playField");


  //Set value in the correct position (playField -> Players ID -> The card's name/number) with correct name
  playField[pId][cardName] = {"type" : cardObj.type, "atk" : cardObj.atk, "hp" : cardObj.hp, "maxHP": cardObj.hp, "canAtk": false } ;

  //Remove that card from current hand
  delete currentHand[pId][cardName];

  //Return script message with card pushed
  Spark.setScriptData("result", "Card " + cardName + " of type " + cardObj.type + " is on the playing field");

  //Save jsons
  chal.setScriptData("currentHand", currentHand);
  chal.setScriptData("playField", playField);
  chal.setScriptData("playerStats", playerStats);
}
else{
  //if there isn't enough mana, send error report through scriptData
  Spark.setScriptData("Error", "Not enough mana");
}

action_playCard

This Event will have 3 input values:

Using this Event, a player can heal a friendly target or attack an enemy opponent or their cards depending on playState and the targetName.

//Load challenge
var chal = Spark.getChallenge(Spark.getData().challengeInstanceId);

//Load data
var playState = Spark.getData().playState;
var cardName = Spark.getData().name;
var targetName = Spark.getData().targetName;

//Retrieve player stats
var playerStats = chal.getScriptData("playerStats");


//Retrieve player Id
var pId = Spark.getPlayer().getPlayerId();

//Determine enemy Id
if(Spark.getPlayer().getPlayerId() === chal.getChallengerId()){
  //If equal to challenger, then Id is challenged[0] Id
  var opponentId = chal.getChallengedPlayerIds()[0];
}
else{
  //If not equal to challenger then other player Id is challenger Id
  var opponentId = chal.getChallengerId();
}

//Load playField
var playField = chal.getScriptData("playField");
//Load player play field
var playerField = playField[pId];
//Load the attacking card
var attackingCard = playerField[cardName];

//If the attacking card isn't null
if(attackingCard == null){
 Spark.setScriptError("Error", "Attacking card does not exist");
 Spark.exit();
}

//Depending on playState of card
if(playState === "attack"){
    if(attackingCard.canAtk === true){
        //Create object for output
        var result = {};

        //If our target is the opponent, damage opponent
        if(targetName === "opponent"){

            //Retrieve the playerHealth object
            var health = playerStats[opponentId].currentHealth;
            //Lower the opponent health points based on card's attack
            playerStats[opponentId].currentHealth = health - attackingCard.atk;
            //Save the health stats
            // chal.setScriptData("playerStats", playerStats);

            //Output result
            result =  cardName + " attacked opponent";

        }
        else{
            //If our target is not an opponent, then we're aiming for another card
            //Retrieve opponent field
            var opponentField = playField[opponentId];
            //Retrieve target card
            var targetCard = opponentField[targetName];

            if(targetCard == null){
            Spark.setScriptError("Error", "Attacking card does not exist");
            Spark.exit();
            }

            //If our attacking card has more attack than the target card hit points
            if(attackingCard.atk >= targetCard.hp){
              delete playField[opponentId][targetName];
              result.case1 = cardName + " destroyed " + targetName;
            }
            else {
              //else if our attacking card cannot kill target card with one hit, lower hit points
              playField[opponentId][targetName].hp = targetCard.hp - attackingCard.atk;
              result.case1 = cardName + " hurt " + targetName;
            }

            //If our attacking card has less hit points than target card, destroy our card
            if(targetCard.atk >= attackingCard.hp){
              delete playField[pId][cardName];
              result.case2 = cardName + " was destroyed by " + targetName;
            }
            else{
              //Else if our attacking card has more hit points than the target card's attack, then lower hit points
              playField[pId][cardName].hp = attackingCard.hp - targetCard.atk;  
              result.case2 = cardName + " was hurt by " + targetName;
            }


        }
    }

    if( playField[pId][cardName] != null){
        //Disallow this card to be able to attack again this round if it still exists
        playField[pId][cardName].canAtk = false;
    }

    //Save playing field
    chal.setScriptData("playField", playField);
    chal.setScriptData("playerStats", playerStats);
    Spark.setScriptData("result", result);
}

else{
  Spark.setScriptError("Error", "Card cannot attack this round")
  Spark.exit();
}

//If instead our playsState was to heal instead of attack, we won't need to load the opponent's playField and just work
//With this player's playField exclusively
if(playState === "heal"){
  if(playField[pId][targetCard] != null){
      //If targetCard has less hit points than max allowed
      if(playField[pId][targetCard].hp <= playField[pId][targetCard].maxHP){
          //Heal card by adding hit points
          playField[pId][targetCard].hp = playField[pId][targetCard].hp + 1;
          //Save playField
          chal.setScriptData("playField", playField);
      }
  }
  else{
      //Stop script if card does not exist
      Spark.setScriptData("Error", "Can't find target card");
      Spark.exit();
  }

}

action_endTurn

A player needs to end their turn to allow:

This also is a challenge Event because it needs to make a reference to the Event, so make sure you edit the Challenge Event Cloud Code.

//Load challenge
var chal = Spark.getChallenge(Spark.getData().challengeInstanceId);
//Retrieve player Id
var pId = Spark.getPlayer().getPlayerId();

//Retrieve player's details and play field
var playerStats = chal.getScriptData("playerStats");
var playField = chal.getScriptData("playField");

//If we have less than 10 mana gems
if(playerStats[pId].overallMana < 10){

    //Add a mana gem
    playerStats[pId].overallMana = playerStats[pId].overallMana + 1;
}


//Current mana will be filled again
playerStats[pId].currentMana = playerStats[pId].overallMana;

//Get all the cards on the player's play field
var cards = Object.keys(playField[pId]);
//Use the allowAtk function on every card
cards.forEach(allowAtk)

//Reset card pulled boolean
playerStats[pId].hasPulled = false;

//Save JSONs
chal.setScriptData("playField", playField);
chal.setScriptData("playerStats", playerStats);

var turnCountVar = chal.getScriptData("turnCount");

//Finish player turn
chal.takeTurn(pId);
//chal.consumeTurn(pId);


function allowAtk(obj){
    //Set the canAtk value to true, to allow the card to attack next turn
    playField[pId][obj].canAtk = true;
}

ChallengeTurnTakenMessage

This is a brilliant message to place a check for player's health and act upon it because it's called after every challenge Event is executed. We'll have three checks:

Each condition will trigger a different response, either the challenged winning, the challenger winning, or a draw.

Global Messages not User Messages! Make sure you attach this Cloud Code logic to the ChallengeTurnTakenMessage under the Global Messages section NOT the User Messages section of the Cloud Code>Scripts panel.

//Load challenge
var chal = Spark.getChallenge(Spark.getData().challenge.challengeId);

//load playerStats
var playerStats = chal.getScriptData("playerStats");

//If challenger reaches 0 or lower health while challenged has more than 0 health, challenged wins
if(playerStats[chal.getChallengerId()].currentHealth <= 0 && playerStats[chal.getChallengedPlayerIds()[0]].currentHealth > 0){
    chal.winChallenge(Spark.loadPlayer(chal.getChallengedPlayerIds()[0]));
}
//If challenged reaches 0 or lower health while challenger has more than 0 health, challenger wins
if(playerStats[chal.getChallengedPlayerIds()[0]].currentHealth <= 0 && playerStats[chal.getChallengerId()].currentHealth > 0){
    chal.winChallenge(Spark.loadPlayer(chal.getChallengerId()));
}
//If both players reach zero or lower health, a draw is in order
if(playerStats[chal.getChallengedPlayerIds()[0]].currentHealth <= 0 && playerStats[chal.getChallengerId()].currentHealth <= 0){
    chal.drawChallenge();
}

Conclusion

In combination these Events and structure can start and end a game the same way Hearthstone works. You can add more cards, more effects, and extra functionality easily because this tutorial was built with modularity and expansion in mind. You can test this using two tabs that have our Test Harness open, then by registering two players and pitting them against each other (both controlled by you).

This concludes the Hearthstone example. You can customize, add, remove, and reinvent this system. This is only one way of achieving this kind of game but by playing around with our components and further understanding the way they work you can achieve brilliant results.

Did this page help you? Please enter your feedback below. For questions about using this part of the platform, please contact support here