Generating Codes for Joining Challenges Using Game Data Service
Introduction
Like Kahoot, sometimes you want your friends to join your game through an easy-to-remember code - players enter a code and the system finds the game their friend has created and adds them to it.
This tutorial shows you how to meet this sort of use case in GameSparks:
- Create a system that generate codes for GameSparks Challenges.
- Give those codes back to the creator of the Challenge.
- Add codes to a Data Type to keep track of codes and their corresponding Challenges.
- Remove codes for recycling.
Here are the steps we'll follow:
- Create a Data Type to save references to the generated codes, which Challenges they are linked to, and the time we created them (in miliseconds).
- Customize the CreateChallengeResponse Cloud Code script.
- Create an Event that uses the generated code to search for games and join them if a game exists.
- Set up a way to remove used codes so future games can use them.
Game Data Service? This tutorial assumes an understanding of the Game Data Service. We strongly recommend that you review the Game Data and the Data Explorer topics before you attempt to follow this tutorial.
Creating a Data Type and Adding Indexes
This step is easy - simply create a Data Type in the Game Data page and add the fields we want to use to query its data. We've called it challengeCodeDataType and we'll add two indexed fields - you'll see that we make references to these in Cloud Code.
How to Create Data Type? See the Game Data and Data Explorer pages for details on how to create and add indexes for a Data Type.
Customizing the CreateChallengeResponse Script
Our first task is to attach a customized Cloud Code script for the CreateChallengeResponse.
The reason we attach this code to the CreateChallengeResponse is because when the player has successfully submitted a CreateChallengeRequest, they receive a valid challengeInstanceId in the response. This is therefore a good place to verify that the player has a valid Challenge. We can then save the id, the generated code for the Challenge, and the timestamp in our Challenge and code Data Type that we called challengeCodeDataType.
1. Go to the Configurator > Cloud Code page.
2. In the Scripts panel, select Responses > CreateChallengeResponse. The Cloud Code editor opens.
3. Add the following code:
//We check if the response has a valid challengeInstanceId to carry on the sequence
if(Spark.getData().challengeInstanceId !== null){
//Load data service
var API = Spark.getGameDataService();
//Get the challenge instance id generated
var challengeID = Spark.getData().challengeInstanceId;
//Generate random code of 000-000 format
var code = randomCode();
//If we did not get a valid code after 1000 attempts then force an error
if(code === null){
Spark.setScriptError("error", "NO VALID CODE CAN BE GENERATED")
} else{
//Create time stamp
var date = new Date();
//Create new entry in Data Type for players to search and join
var entry = API.createItem("challengeCodeDataType", challengeID);
var data = entry.getData();
data.code = code;
data.creationTime = date.getTime();
//Persist entry
var status = entry.persistor().persist().error();
if(status){
//Return the code to the player
Spark.setScriptData("code", code);
} else{
Spark.setScriptError("ERROR", status)
}
}
}
//Function to create number
function randomCode(){
//Create a code, if this code is not valid (used already) keep trying for 1000 times, if none are valid, return null to be used to create an error message back
for(var i = 0; i < 1000; i++){
//Generate two 3 character long ints
var firstThree = ("00" + Math.floor(Math.random() * 1000).toString()).slice(-3);
var secondThree = ("00" + Math.floor(Math.random() * 1000).toString()).slice(-3);
//Combine ints to form game search code
var codeGen = firstThree + "-" + secondThree;
//Create a condition comparing the code
var condition = API.S("code").eq(codeGen);
//Is there any other game using this code right now?
var potentialMatch = API.queryItems("challengeCodeDataType", condition);
//Was there an error trying to connect to data service
if(potentialMatch.error() == null){
//If not, break while loop by returning code
if(potentialMatch.cursor().next() === null){
return codeGen;
}
if(i >= 999){
return null;
}
} else{
Spark.setScriptError("ERROR", "Error retrieving data");
return null;
}
}
}
Join Event
Because our players don't know the challengeInstanceId, the JoinChallengeRequest is not an immediate and viable way for players to join the Challenge. Instead, we need to create a custom Event that uses the generated code to:
- Find our game through searching the Data Type called challengeCodeDataType.
- Retrieve the challengeInstanceId.
- Invoke a JoinChallengeRequest for us through Cloud Code.
1. Create an Event and add a single string Attribute and call it code.
2. Go to Configurator > Cloud Code and in the Scripts select the new Event under Events. The Cloud Code editor opens.
3. Add the following Cloud Code to the Event:
//Get code from input
var code = Spark.getData().code
//Load Data API
var API = Spark.getGameDataService();
//Create condition
var condition = API.S("code").eq(code);
//Attempt to get result
var resultOBJ = API.queryItems("challengeCodeCollection", condition);
//Check for errors
if(resultOBJ.error()){
Spark.setScriptError("ERROR", "Could not retrieve data")
}else{
//Get document
var result = resultOBJ.cursor().next();
}
if(result){
var challengeID = result.getId();
//If game does not exist, return error
if(challengeID == null){
Spark.setScriptError("error", "NO GAME CAN BE FOUND");
}
//If game exists, join it and return response as scriptData
else{
//Create a join challenge request via code
var joinRequest = new SparkRequests.JoinChallengeRequest();
//Set the id of the challenge we wish to join
joinRequest.challengeInstanceId = challengeID;
//The sending happens here at .send()
var joinResponse = joinRequest.Send()
//If error is null or unidentified return the result of the request otherwise return the error
if(joinResponse.error() == null){
Spark.setScriptData("joined", joinResponse.joined);
} else{
Spark.setScriptError("error", joinResponse.error)
}
}
}else{
Spark.setScriptError("ERROR", "No game found");
}
Removing Old Entries for Re-Use
We want to remove old Challenge codes and make them available for re-use in new games. This can be done in a few ways:
- Method A - By having a daily script remove any code older than, say, 24 hours:
- This could be after any length of time, 24 hours is used here as an example.
- A downside to this method is that your code can no longer be used to join that game after the set time has elapsed.
- Method B - By removing the code when a game is concluded:
- A downside to this method is that if the game is never concluded, the code is never re-used.
You can combine both methods if you wish.
Method A
In this first method, we remove Challenge codes every day:
1. Go to Configurator > Cloud Code.
2. On the Scripts panel, select System > Every Day.
3. Add the following Cloud Code to the GS_DAILY script:
//Create a date stamp
var date = new Date();
//Time stamp 24 hours ago by substracting the milliseconds equal to 24 hours
var timestamp = date.getTime() - 86400000;
//Load API
var API = Spark.getGameDataService();
//Create condition
var condition = API.N("creationTime").lt(timeStamp);
//Return results
var resultsOBJ = API.queryItems("challengeCodeDataType", condition);
if(resultsOBJ.error() !== null){
//Get results
var results = resultsOBJ.cursor();
//Delete records
while(results.hasNext()){
results.next().delete();
}
} else{
Spark.setScriptError("ERROR", resultsOBJ.error())
}
Method B
In this second method, we remove the Challenge codes when a game is concluded:
1. Go to Configurator > Cloud Code.
2. On the Scripts panel, select Global Messages > ChallengeWonMessage or Global Messages > ChallengeLostMessage:
- Do NOT add to both messages, because we want the code to run only once.
3. Open a second tab and select Global Messages > ChallengeDrawnMessage:
- Ensure it's the Global messages version to avoid duplication.
4. Add the following Cloud Code to both the Won/Lost and Drawn messages:
//Get challenge instance id
var challengeID = Spark.getData().challenge.challengeId;
//Load API
var API = Spark.getGameDataService();
//Attempt to find entry
var resultOBJ = API.getItem("challengeCodeDataType", challengeID);
//If error
if(resultOBJ.error()){
Spark.setScriptError("ERROR", resultOBJ.error());
} else{
//If entry is retrieved
if(resultOBJ.document()){
//Delete entry
resultOBJ.document().delete();
}
}
Removing Code for Expired or Withdrawn Challenges
If our Challenge is terminated for any reason before starting, we want to remove any reference of it and recycle the code generated for it:
- Add the code for Method B above to each of the following messages and ensure you have the code in the GLOBAL Message versions:
This will ensure that if the Challenge is expired or withdrawn, we make the code available for re-use. It's important to leave the code in the Global version of the message because we only want to run it once. The Global version is the server version of the call that only executes once.