Example¶
Here is the complete example of using the Scorbit SDK in C, C++, and Python. The example demonstrates:
- Set up the game state and request top scores
- Set game state and commit the updated game state
- Set an active player
- Set the current ball
- Setting and clearing modes
- Get a deeplink for pairing (useful for displaying a QR code during pairing process)
- Request a pairing short code (6 chars) (useful for alphanumeric displays during pairing process)
- Get a deeplink for claiming a player slot (use to assist player claiming at the start of a ball)
- Issue a user unpairing request and handle the unpairing callback.
#include <scorbit_sdk/scorbit_sdk_c.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
// ------------ Dummy functions to simulate game state just to get file compiled --------------
int isGameFinished(int i)
{
return i == 99;
}
int isGameJustStarted(int i)
{
return i == 5;
}
int isGameActive(int i)
{
return i >= 5 && i < 99;
}
int player1Score(int i)
{
if (i == 5)
return 0;
return 1010 + i * 500;
}
int hasPlayer2(void)
{
return 0;
}
int player2Score(void)
{
return 2000;
}
int hasPlayer3(void)
{
return 0;
}
int player3Score(void)
{
return 3000;
}
int hasPlayer4(void)
{
return 0;
}
int player4Score(void)
{
return 4000;
}
int currentPlayer(void)
{
return 1;
}
int currentBall(int i)
{
return i / 33 + 1;
}
int timeToClearModes(void)
{
return 0;
}
int isUnpairTriggeredByUser(void)
{
return 0;
}
// --------------- Example of logger callback ------------------
// This callback will be called in a thread-safe manner, so we don't worry about thread-safety
void loggerCallback(const char *message, sb_log_level_t level, const char *file, int line,
void *userData)
{
(void)userData;
(void)file;
(void)line;
// Get the current time
time_t ct = time(NULL); // Current time in seconds since epoch
struct tm *timeInfo = localtime(&ct); // Convert to local time
// Buffer for formatted time string
char timeStr[30]; // Holds a string like "2024-10-01 12:34:56"
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", timeInfo);
// Determine log level string
const char *levelStr = "UNK"; // default to unknown
switch (level) {
case SB_DEBUG:
levelStr = "DBG";
return; // Skip debug messages
break;
case SB_INFO:
levelStr = "INF";
break;
case SB_WARN:
levelStr = "WRN";
break;
case SB_ERROR:
levelStr = "ERR";
break;
default:
break;
}
// Use printf to print the log message
printf("[%s] [%s] %s\n", timeStr, levelStr, message);
fflush(stdout); // Maybe we should not flush buffer, so it will not slow down the program
}
sb_game_handle_t setup_game_state(void)
{
// Setup device info
sb_device_info_t device_info = {
.provider = "dilshodpinball", // This is required, set to your provider name
.machine_id = 4419,
.game_code_version = "0.1.0", // game version
.hostname = "staging", // Optional, if NULL, it will be production
// UUID is optional, if NULL, will be automatically derived from device's mac address
// However, if there is known uuid attached to the device, set it here:
.uuid = "c7f1fd0b-82f7-5504-8fbe-740c09bc7dab", // dilshodpinball test machine
.serial_number = 0, // If no serial number available, set to 0
};
// Another example with default values:
sb_device_info_t device_info2 = {
.provider = "vscorbitron", // This is required, set to your provider name
.game_code_version = "0.1.0", // game version
.hostname = NULL, // NULL, it will be production, or can set to "production"
.uuid = NULL, // NULL, will be automatically derived from device
.serial_number = 0, // no serial number available, set to 0
};
(void)device_info2;
// Setup encrypted key
const char *encrypted_key = "8qWNpMPeO1AbgcoPSsdeUORGmO/hyB70oyrpFyRlYWbaVx4Kuan0CAGaXZWS3JWdgmPL7p9k3UFTwAp5y16L8O1tYaHLGkW4p/yWmA==";
// Create game state object. Device info will be copied, so it's safe to create it in the stack
return sb_create_game_state2(encrypted_key, &device_info);
}
void top_scores_callback(sb_error_t error, const char *reply, void *user_data)
{
(void)user_data;
switch (error) {
case SB_EC_SUCCESS:
printf("Top scores: %s\n", reply);
break;
case SB_EC_NOT_PAIRED:
printf("Device is not paired\n");
break;
case SB_EC_API_ERROR:
printf("API error: %s\n", reply);
return;
default:
printf("Error: %d\n", error);
break;
}
}
void unpair_callback(sb_error_t error, const char *reply, void *user_data)
{
(void)user_data;
if (error == SB_EC_SUCCESS) {
printf("Unpairing successful\n");
} else {
printf("Unpairing failed: %s\n", reply);
}
}
void shortcode_callback(sb_error_t error, const char *shortcode, void *user_data)
{
(void)user_data;
switch (error) {
case SB_EC_SUCCESS:
printf("Pairing short code: %s\n", shortcode);
break;
case SB_EC_API_ERROR:
printf("API error: %s\n", shortcode);
break;
default:
printf("Error: %d\n", error);
break;
}
}
int main(void)
{
printf("Simple example of Scorbit SDK usage\n");
// Setup logger
sb_add_logger_callback(loggerCallback, NULL);
sb_game_handle_t gs = setup_game_state();
// Request top scores
sb_request_top_scores(gs, 0, &top_scores_callback, NULL);
// Request deep link for pairing. This is useful if machine can display QR code.
printf("Deeplink for pairing %s\n", sb_get_pair_deeplink(gs));
// Alternatively, request short code for pairing which is alphanumeric 6 chars and display it
sb_request_pair_code(gs, &shortcode_callback, NULL);
// Main loop which is typically an infinite loop, but this example runs for 10 cycles
for (int i = 0; i < 100; ++i) {
// Check the auth (networking) status. It's not necessary, just for demo
if (i % 10 == 0) {
sb_auth_status_t status = sb_get_status(gs);
printf("Networking status: %d\n", status);
}
// Next game cycle started. First check if game is finished, because it might happen,
// that in the same cycle one game finished and started new game
if (isGameFinished(i)) {
// This will close current active session and do commit.
sb_set_game_finished(gs);
}
if (isGameJustStarted(i)) {
// This will start new game session with player1 score 0 and current ball 1.
// In the same game cycle before commit it can be set new score, active player, etc.
// So, player1's initial score will be not 0, but the one set in the current cycle
sb_set_game_started(gs);
}
if (isGameActive(i)) {
// Set player1 score, no problem, if it was not changed in the current cycle
sb_set_score(gs, 1, player1Score(i), 0);
if (hasPlayer2()) {
// Set player2 score if player2 is present
sb_set_score(gs, 2, player2Score(), 0);
}
if (hasPlayer3()) {
// Set player3 score if player3 is present
sb_set_score(gs, 3, player3Score(), 0);
}
if (hasPlayer4()) {
// Set player4 score if player4 is present
sb_set_score(gs, 4, player4Score(), 0);
}
// Set active player
sb_set_active_player(gs, currentPlayer());
// Set current ball
sb_set_current_ball(gs, currentBall(i));
// Add/remove game modes:
if (i % 10 == 0) {
sb_add_mode(gs, "MB:Multiball");
} else {
sb_remove_mode(gs, "MB:Multiball");
}
sb_add_mode(gs, "MB:Multiball");
sb_add_mode(gs, "NA:SomeMode");
sb_remove_mode(gs, "NA:AnotherMode");
// Sometimes we might need to clear all modes
if (timeToClearModes()) {
sb_clear_modes(gs);
}
}
// Commit game state at the end of each cycle. This ensures that any changes
// in the game state are captured and sent to the cloud. If no changes occurred,
// the commit will be ignored, avoiding unnecessary uploads.
sb_commit(gs);
usleep(300 * 1000); // Sleep for 300 ms
}
if (isUnpairTriggeredByUser()) {
sb_request_unpair(gs, &unpair_callback, NULL);
}
// Cleanup
sb_destroy_game_state(gs);
printf("Example finished\n");
return 0;
}
#include <scorbit_sdk/scorbit_sdk.h>
#include <iostream>
#include <iomanip>
#include <chrono>
#include <ctime>
#include <thread>
using namespace std;
// ------------ Dummy functions to simulate game state just to get file compiled --------------
bool isGameFinished(int i)
{
return i == 99;
}
bool isGameJustStarted(int i)
{
return i == 5;
}
bool isGameActive(int i)
{
return i >= 5 && i < 99;
}
int player1Score(int i)
{
if (i == 5)
return 0;
return 1000 + i * 500;
}
bool hasPlayer2()
{
return false;
}
int player2Score()
{
return 2000;
}
bool hasPlayer3()
{
return false;
}
int player3Score()
{
return 3000;
}
bool hasPlayer4()
{
return false;
}
int player4Score()
{
return 4000;
}
int currentPlayer()
{
return 1;
}
int currentBall(int i)
{
return i / 33 + 1;
}
bool timeToClearModes()
{
return false;
}
bool isUnpairTriggeredByUser()
{
return false;
}
// --------------- Example of logger callback ------------------
// This callback will be called in a thread-safe manner, so we don't worry about thread-safety
void loggerCallback(const std::string &message, scorbit::LogLevel level, const char *file, int line,
void *userData)
{
(void)userData;
(void)file;
(void)line;
const std::time_t ct = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
// Format currentTime to string like [2024-10-01 12:34:56]
std::cout << '[' << std::put_time(std::localtime(&ct), "%Y-%m-%d %H:%M:%S") << "] [";
switch (level) {
case scorbit::LogLevel::Debug:
std::cout << "DBG";
break;
case scorbit::LogLevel::Info:
std::cout << "INF";
break;
case scorbit::LogLevel::Warn:
std::cout << "WRN";
break;
case scorbit::LogLevel::Error:
std::cout << "ERR";
break;
}
std::cout << "] " << message << '\n';
std::cout.flush(); // Maybe we should not flush buffer, so it will not slow down the program
}
scorbit::GameState setupGameState()
{
scorbit::DeviceInfo info;
info.provider = "dilshodpinball"; // This is required, set to your provider name
info.machineId = 4419; // This is required, set to your machine id
info.hostname = "staging"; // Optional, if not set, it will be "production"
// Another example: info.hostname = "https://api.scorbit.io";
info.gameCodeVersion = "0.1.0"; // game version
// If not set, will be 0, however, it there is serial number attached to the device, set it here
// info.serialNumber = 12345;
// UUID is optional, if not set will be automatically derived from device's mac address
// However, if there is known uuid attached to the device, set it here:
info.uuid = "c7f1fd0b-82f7-5504-8fbe-740c09bc7dab"; // dilshodpinball test machine
// encrypted key is generated by encrypt_tool
std::string encryptedKey = "8qWNpMPeO1AbgcoPSsdeUORGmO/hyB70oyrpFyRlYWbaVx4Kuan0CAGaXZWS3JWdgmPL7p9k3UFTwAp5y16L8O1tYaHLGkW4p/yWmA==";
// Create game state object. Normally, device info will be copied.
// However, it can be moved, because we don't need this struct anymore.
return scorbit::createGameState(encryptedKey, std::move(info));
}
using namespace std::chrono_literals;
int main()
{
cout << "Simple example of Scorbit SDK usage" << endl;
// Setup logger
scorbit::addLoggerCallback(loggerCallback);
// Create game state object
scorbit::GameState gs = setupGameState();
gs.requestPairCode([](scorbit::Error error, const std::string &shortCode) {
if (error == scorbit::Error::Success) {
cout << "Pairing short code: " << shortCode << endl;
} else {
cout << "Error: " << static_cast<int>(error) << endl;
}
});
gs.requestTopScores(0, [](scorbit::Error error, std::string reply) {
switch (error) {
case scorbit::Error::Success:
cout << "Top scores: " << reply << endl;
break;
case scorbit::Error::NotPaired:
cout << "Device is not paired" << endl;
break;
case scorbit::Error::ApiError:
cout << "API error: " << reply << endl;
break;
default:
cout << "Error: " << static_cast<int> (error) << endl;
}
});
// std::this_thread::sleep_for(1000ms);
cout << "Deeplink for pairing " << gs.getPairDeeplink() << endl;
// Main loop which is typically an infinite loop, but this example runs for 10 cycles
for (int i = 0; i < 100; ++i) {
// Check the auth (networking) status. It's not necessary, just for demo
if (i % 10 == 0) {
auto status = gs.getStatus();
cout << "Networking status: " << static_cast<int>(status) << endl;
}
// Next game cycle started. First check if game is finished, because it might happen,
// that in the same cycle one game finished and started new game
if (isGameFinished(i)) {
// This will close current active session and do commit.
gs.setGameFinished();
}
if (i == 50) {
cout << "Deeplink for claiming " << gs.getClaimDeeplink(1) << endl;
}
if (isUnpairTriggeredByUser()) {
// Request unpairing
gs.requestUnpair([](scorbit::Error error, std::string reply) {
switch (error) {
case scorbit::Error::Success:
cout << "Unpairing successful" << endl;
break;
case scorbit::Error::NotPaired:
cout << "Device is not paired" << endl;
break;
case scorbit::Error::ApiError:
cout << "API error: " << reply << endl;
break;
default:
cout << "Error: " << static_cast<int> (error) << endl;
}
});
}
if (isGameJustStarted(i)) {
// This will start new game session with player1 score 0 and current ball 1.
// In the same game cycle before commit it can be set new score, active player, etc.
// So, player1's initial score will be not 0, but the one set in the current cycle
gs.setGameStarted();
}
if (isGameActive(i)) {
// Set player1 score, no problem, if it was not changed in the current cycle
gs.setScore(1, player1Score(i));
if (hasPlayer2()) {
// Set player2 score if player2 is present
gs.setScore(2, player2Score());
}
if (hasPlayer3()) {
// Set player3 score if player3 is present
gs.setScore(3, player3Score());
}
if (hasPlayer4()) {
// Set player4 score if player4 is present
gs.setScore(4, player4Score());
}
// Set active player
gs.setActivePlayer(currentPlayer());
// Set current ball
gs.setCurrentBall(currentBall(i));
// Add/remove game modes:
if (i % 10 == 0) {
gs.addMode("MB:Multiball");
} else {
gs.removeMode("MB:Multiball");
}
gs.addMode("MB:Multiball");
gs.addMode("NA:SomeMode");
gs.removeMode("NA:AnotherMode");
// Sometimes we might need to clear all modes
if (timeToClearModes()) {
gs.clearModes();
}
}
// Commit game state at the end of each cycle. This ensures that any changes
// in the game state are captured and sent to the cloud. If no changes occurred,
// the commit will be ignored, avoiding unnecessary uploads.
std::cout << "Commit cycle " << i << std::endl;
gs.commit();
std::this_thread::sleep_for(200ms);
}
cout << "Example finished" << endl;
return 0;
}
import time
from datetime import datetime
from scorbit import scorbit
# -------- Dummy functions to simulate game state --------
def is_game_finished(i):
return i == 99
def is_game_just_started(i):
return i == 5
def is_game_active(i):
return 5 <= i < 99
def player1_score(i):
return 0 if i == 5 else 1000 + i * 500
def has_player2():
return False
def player2_score():
return 2000
def has_player3():
return False
def player3_score():
return 3000
def has_player4():
return False
def player4_score():
return 4000
def current_player():
return 1
def current_ball(i):
return i // 33 + 1
def time_to_clear_modes():
return False
def is_unpair_triggered_by_user():
return False
# -------- Logger callback --------
def logger_callback(message, level, file, line):
level_str = {scorbit.LogLevel.Debug: "DBG", scorbit.LogLevel.Info: "INF",
scorbit.LogLevel.Warn: "WRN", scorbit.LogLevel.Error: "ERR"}.get(level, "UNK")
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}] [{level_str}] {message}")
def setup_game_state():
info = scorbit.DeviceInfo()
info.provider = "dilshodpinball"
info.machine_id = 4419
info.hostname = "staging" # staging, production, or specific url
info.game_code_version = "0.1.0"
info.uuid = "c7f1fd0b-82f7-5504-8fbe-740c09bc7dab" # don't set it, then it will be generated automatically
# Use encrypt_tool to generate your encrypted key from your private key
encrypted_key = '8qWNpMPeO1AbgcoPSsdeUORGmO/hyB70oyrpFyRlYWbaVx4Kuan0CAGaXZWS3JWdgmPL7p9k3UFTwAp5y16L8O1tYaHLGkW4p/yWmA=='
return scorbit.create_game_state(encrypted_key, info)
def main():
print(f"Simple example of Scorbit SDK {scorbit.__version__} usage")
# Setup logger
scorbit.add_logger_callback(logger_callback)
# Create game state object
gs = setup_game_state()
gs.request_pair_code(lambda error, short_code: print(
f"Pairing short code: {short_code}" if error == 0 else f"Error: {error}"
))
gs.request_top_scores(0, lambda error, reply: print(
f"Top scores: {reply}" if error == 0 else f"Error: {error}"
))
print(f"Deeplink for pairing: {gs.get_pair_deeplink()}")
for i in range(100):
if i % 10 == 0:
print(f"Networking status: {gs.get_status()}")
if is_game_finished(i):
gs.set_game_finished()
if i == 50:
print(f"Deeplink for claiming: {gs.get_claim_deeplink(1)}")
if is_unpair_triggered_by_user():
gs.request_unpair(lambda error, reply: print(
"Unpairing successful" if error == 0 else f"Error: {error}"
))
if is_game_just_started(i):
gs.set_game_started()
if is_game_active(i):
gs.set_score(1, player1_score(i))
if has_player2(): gs.set_score(2, player2_score())
if has_player3(): gs.set_score(3, player3_score())
if has_player4(): gs.set_score(4, player4_score())
gs.set_active_player(current_player())
gs.set_current_ball(current_ball(i))
if i % 10 == 0:
gs.add_mode("MB:Multiball")
else:
gs.remove_mode("MB:Multiball")
if time_to_clear_modes():
gs.clear_modes()
print(f"Commit cycle {i}")
gs.commit()
time.sleep(0.5)
print("Example finished")
if __name__ == "__main__":
main()