Save State Functionality
The Save State functionality is crucial for persisting the player's progress in a game.
This ensures that players can continue their game from where they left off, with all their achievements and progress intact.
The following documentation outlines the methods available for saving and retrieving user states.
Method: saveUserState(state: any): Promise<boolean>
The saveUserState method is used to save the current state of the game.
The state can include various aspects of the game, such as the player's position, inventory, quest progress, score, unlocked levels, or even assets like images.
This function must be called whenever there is any progress in the game, ensuring that the player's data is consistently up-to-date.
Parameters
state: any
An object representing the current state of the game.
The state can be structured in any way that suits the needs of the game, but it typically includes details like the player's position, inventory, quest progress, and more.
Returns
Promise<boolean>
Returns a promise that resolves totrueif the state was successfully saved or rejects with an error if the state could not be saved.
Example
Game State Examples
const game1State1 = {
playerPosition: { x: 100, y: 200 },
inventory: ['sword', 'shield'],
questProgress: {
currentQuest: 'Save the Princess',
questStatus: 'in-progress'
}
};
const game2state2 = {
score: 2000,
level: 6,
progress: {
section: 3
},
unlockedLevels: [1, 2, 3, 4, 5, 6]
};
const game3state3 = '[{
"userId": "716ee8fefaead757",
"gameId": "c56d8777-2a7d-4138-8709-d3a9654d2dff",
"index": "0",
"data": "{\"items\":[{\"index\":\"2\",\"itemId\":\"716ee8fefaead757_1716180977\"},{\"index\":\"4\",\"itemId\":\"716ee8fefaead757_1716183448\"}],\"challenges\":[],\"acquiredItems\":[],\"gold\":8000,\"ticket\":5240,\"lastChallengeSeenTime\":[],\"lastExploredSeenTime\":1717044369}"
}]';
const userState = saveUserState(gameAssetState);
userState.then((info) => {
console.log("State Saved: ", info);
}).catch((error) => {
console.log("Unable to save the user state: ", error);
});
Method: GetUserState(userId: string): Promise<any>
The GetUserState method retrieves all saved data for a specific user. This method should be used when you need to load all the user’s saved states. However, if you only need a specific part of the saved state, it is more efficient to use the GetUserStateByIndex method.
Parameters
userId: string
A unique identifier for the user whose state you wish to retrieve.
Returns
Promise<any>
Returns a promise that resolves with the entire user state if successful, or rejects with an error if the data could not be retrieved.
Example
const userId = "nostra-user-001";
const userState = GetUserState(userId);
userState.then((info) => {
console.log("User State: ", info);
}).catch((error) => {
console.log("Unable to get user data: ", error);
});
Method: GetUserStateByIndex(userId: string, index: string) : Promise<any>
The getUserStateByIndex method is used to retrieve a specific part of the saved user state by providing the index. This method is efficient when only a certain portion of the user state is needed, rather than retrieving all the user data.
Parameters
-
userId: string: A unique identifier for the user whose state you wish to retrieve. -
index: string: The specific index or key of the data that you wish to retrieve from the user's saved state.
Returns
Promise<any>: Returns a promise that resolves with the specified part of the user state if successful, or rejects with an error if the data could not be retrieved.
const userId = "nostra-user-001";
const index = "default";
const userState = GetUserStateByIndex(userId, index);
userState.then((info) => {
console.log("User State by Index: ", info);
}).catch((error) => {
console.log("Unable to get user data by index: ", error);
});
Method: saveUserAsset(userId: string, gameId: string, assetObject: Object)
The saveUserAsset method can be used to save in game assets such as any card designed by the player inside the game. It can later be used to drive the same player to do better on top of it or push other players to show their creativity.
Example
const assetObject = {
type: "IMAGE",
format: "png", // optional if link provided
orientation: "PORTRAIT",
bytearray: /* image in bytearray format */
};
const userAsset = saveUserAsset(userId, gameId, assetObject);
userAsset.then((info) => {
console.log("Asset Saved: ", info);
}).catch((error) => {
console.log("Unable to save the user asset: ", error);
});
uploadUrl and cdnUrl of the saved game assets. These URLs can also be used while calling the saveUserState function if needed, in order to store them as part of the user's state.
Best practices
-
Frequent saves: Ensure that saveUserState is called frequently to avoid any loss of progress due to unexpected events like crashes or power outages.
-
Efficient retrieval: Use GetUserStateByIndex when you need specific data, as it is more efficient than retrieving the entire user state.
-
Error handling: Always include error handling when working with promises to manage scenarios where saving or retrieving data might fail.
For local testing, refer to the local.js file and use its code to run the game in a local environment. However, please ensure that this file is removed before sharing the build for testing.
window.saveUserState = function saveUserState(state) {
return new Promise(async (resolve, reject) => {
//validate state. It is supposed to be an array of objects
if (!Array.isArray(state) || state.length === 0) {
reject("Invalid state: state must be an array of objects.");
return;
}
const envValue = "stage";
const saveGameStateApi = async (state) => {
const baseUrl =
envValue === "stage"
? "https://leaderboard-staging.glance.inmobi.com"
: "https://leaderboard.api.glance.inmobi.com";
console.log("Game state to be saved:", state);
// Call the API to save the game state
try {
const response = await fetch(`${baseUrl}/api/v1/externalgames/save`, {
method: "PATCH",
body: JSON.stringify(state),
headers: {
"Content-Type": "application/json",
"X-Api-Key":
window.apiKey || "30aedfec48ddd7c42cb8cd855b431a774a0d6b17",
},
});
if (response.ok) {
const result = await response.json();
console.log("Game update response", result);
return true;
} else {
console.error("Game update failed with status", response.status);
return false;
}
} catch (err) {
console.error("Game update failed with error", err);
return false;
}
};
console.log("User state saved and stored locally:", state);
try {
console.log("Initiating saveGameStateApi");
const isSaved = await saveGameStateApi(state);
if (isSaved) {
resolve(true);
} else {
reject("Failed to save game state.");
}
} catch (error) {
reject("Failed to save user state with error:" + error);
}
});
};
window.saveUserAsset = function saveUserAsset(userId, gameId, asset) {
return new Promise(async (resolve, reject) => {
if (typeof userId !== "string" || userId === "") {
reject("Invalid userId: userId must be a non-empty string.");
return;
}
if (typeof asset !== "object" || asset === null) {
reject("Invalid asset: Asset must be an object.");
return;
}
const byteArrayToBase64 = (byteArray) => {
let binary = "";
const len = byteArray.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(byteArray[i]);
}
return window.btoa(binary);
};
const checkImageDimensions = (base64) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
if (img.width <= 1440 && img.height <= 2560) {
resolve(true); // Correct dimensions
} else {
reject(
`Invalid image dimensions: Expected 1440x2560, got ${img.width}x${img.height}.`
);
}
};
img.onerror = () =>
reject("Failed to load the image for dimension check.");
img.src = `data:image/png;base64,${base64}`; // Convert byte array to base64 data URL
});
};
const checkFileSize = (byteArray) => {
return new Promise((resolve, reject) => {
const fileSizeInKB = byteArray.byteLength / 1024; // Convert bytes to KB
if (fileSizeInKB <= 850) {
resolve(true); // File size is within limit
console.log("File size is within limit");
} else {
reject(
`File size exceeds 800 KB limit. Actual size: ${fileSizeInKB.toFixed(
2
)} KB`
);
}
});
};
const createImageUploadLink = async (userId, gameId) => {
//store timestamp
const timestamp = new Date().getTime();
const baseUrl = "https://livegaming-staging.glance.inmobi.com";
const response = await fetch(
`${baseUrl}/api/v1/livesdk/video/upload/url/SDA/${userId}_${gameId}_${timestamp}.png?gameId=${gameId}`,
{
method: "GET",
headers: {
"x-api-key":
"1030ed0aff4f570f138c518486a258474f862fb12f4af27e95a4de3fd15a3f5c",
"Content-Type": "application/json",
},
}
);
return response.json();
};
const uploadImage = async (uploadUrl, dataURL) => {
const response = await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Type": "application/octet-stream",
},
body: dataURL,
});
console.log(window.btoa(dataURL), "dataURL bytestring");
if (response.ok) {
return { success: true, message: "Upload successful." };
} else {
throw new Error(`Failed to upload image: ${response.statusText}`);
}
};
console.log("User asset to be saved:", asset);
console.log("User asset saved and stored locally:", asset);
try {
const base64Image = byteArrayToBase64(asset.bytearray);
await checkFileSize(asset.bytearray);
await checkImageDimensions(base64Image);
const uploadLink = await createImageUploadLink(userId, gameId);
const uploadResponse = await uploadImage(
uploadLink.uploadUrl,
asset.bytearray
);
console.log("Upload response:", uploadLink);
// check if response status is 200
if (uploadResponse.success) {
//return uploadlink and resolve promise
resolve(uploadLink);
console.log("promise resolved", uploadLink);
} else {
reject("Failed to save user asset.");
}
} catch (error) {
reject("Failed to save user asset with error:" + error);
}
});
};
window.GetUserState = function getUserState(userId, gameId) {
return new Promise((resolve, reject) => {
if (typeof userId !== "string" || userId === "") {
reject("Invalid userId: userId must be a non-empty string.");
return;
}
const envValue = window._ENV_GAME_;
const baseUrl = "https://leaderboard-staging.glance.inmobi.com";
const getUserStateApi = async (userId, gameId) => {
try {
const response = await fetch(
`${baseUrl}/api/v1/externalgames/getAllData?userId=${userId}&gameId=${gameId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Api-Key":
window.apiKey || "30aedfec48ddd7c42cb8cd855b431a774a0d6b17",
},
}
);
if (!response.ok) {
console.error(`Request failed with status: ${response.status}`);
return null;
}
const text = await response.text();
console.log("Response text:", text);
if (!text) {
console.error("Response body is empty");
return null;
}
try {
const result = JSON.parse(text);
console.log("Game state retrieved:", result);
return result;
} catch (jsonError) {
console.error("Failed to parse JSON:", jsonError);
return null;
}
} catch (err) {
console.error("Game state retrieval failed with error", err);
return null;
}
};
const savedState = getUserStateApi(userId, gameId);
if (savedState) {
console.log(`Retrieved user state from storage:`, savedState);
resolve(savedState);
} else {
reject("No user state found for the given userId.");
}
});
};
window.GetUserStateByIndex = function getUserStateByIndex(
userId,
gameId,
index
) {
return new Promise((resolve, reject) => {
if (typeof userId !== "string" || userId === "") {
reject("Invalid userId: userId must be a non-empty string.");
return;
}
if (typeof index !== "string" || index === "") {
reject("Invalid index: index must be a non-empty string.");
return;
}
// Retrieve the user state from local storage
const envValue = window._ENV_GAME_;
const baseUrl = "https://leaderboard-staging.glance.inmobi.com";
const getUserStateByIndexApi = async (userId, index) => {
try {
const response = await fetch(
`${baseUrl}/api/v1/externalgames/getData?gameId=${gameId}&userId=${userId}&index=${index}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Api-Key":
window.apiKey || "30aedfec48ddd7c42cb8cd855b431a774a0d6b17",
},
}
);
if (!response.ok) {
console.error(`Request failed with status: ${response.status}`);
return null;
}
const text = await response.text();
console.log("Response text:", text);
if (!text) {
console.error("Response body is empty");
return null;
}
try {
const result = JSON.parse(text);
console.log("Game state retrieved:", result);
return result;
} catch (jsonError) {
console.error("Failed to parse JSON:", jsonError);
return null;
}
} catch (err) {
console.error("Game state retrieval failed with error", err);
return null;
}
};
const savedState = getUserStateByIndexApi(userId, index);
if (savedState) {
console.log(`Retrieved user state from storage:`, savedState);
resolve(savedState);
} else {
reject("No user state found for the given userId and index.");
}
});
};