Skip to content

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()