INTEGRATION GUIDE

This guide will walk you through integrating your game with the Genome API using KODE, enabling you to manage matches, players, and tournament information.

Check out our GitHub page for examples and the latest information.

Prerequisites

  • Node.js: Ensure you have Node.js installed on your system.

  • API secret: You need a Genome personal secret to interact with the API.

  • Game Server: You should have a server running your game, which can communicate with the Genome API.

  • Basic knowledge of Node.js: Familiarity with JavaScript, Node.js, and asynchronous programming is recommended.

Setup

1

Project Setup

Create a new directory for your project and navigate into it.

mkdir game-integration
cd game-integration
npm init -y
2

Install Dependencies

Install the required dependencies.

npm install --save socket.io-client jsonwebtoken dotenv zod
npm install --save-dev typescript ts-node nodemon
3

Setup Build and Run Scripts

Create a tsconfig.json file in root of project.

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "rootDir": "./src",
    "outDir": "./dist",
    "noEmitOnError": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictNullChecks": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

Replace default scripts and main definition inside package.json file with two commands:

"main": "dist/app.js",
"scripts": {
  "build": "tsc",
  "start": "node ./dist/app.js",
  "dev": "nodemon ./src/app.ts"
}
4

Environment Variables

Create a .env file to store your environment variables securely.

# .env
GAME_API_URL=ws://localhost:2593
PLATFORM_URL=http://localhost:2594
GAME_ID=game_id_2
GAME_SECRET=game-1-secret
5

Load and Validate Environments

Using zod we can validate the evironment. Create a src/environment.ts file and copy next content to it.

import { config } from 'dotenv';
import { z } from 'zod';
config();
export const Env = z
    .object({
        GAME_API_URL: z.string().url(),
        PLATFORM_URL: z.string().url(),
        GAME_ID: z.string(),
        GAME_SECRET: z.string(),
    }
    .parse(process.env);
6

Load Models and Validators

To simplify interaction and integration we provide ready to use zod validators and type-guards for all message. Create a src/models.ts file to store the next code.

Example Code
import { z } from 'zod';
import { Env } from './environment';
export const GameId = z.literal(Env.GAME_ID);
export type GameId = z.infer<typeof GameId>;
export const PlayerId = z.string();
export type PlayerId = z.infer<typeof PlayerId>;
export const MatchId = z.string();
export type MatchId = z.infer<typeof MatchId>;
export const TournamentId = z.string();
export type TournamentId = z.infer<typeof TournamentId>;
export const AuthTokenPayload = z.object({
    gameId: GameId,
});
export type AuthTokenPayload = z.infer<typeof AuthTokenPayload>;
export const TeamScore = z.object({
    players: PlayerId.array(),
    scores: z.number().array(),
});
export type TeamScore = z.infer<typeof TeamScore>;
export const GameEndedEvent = z.object({
    matchId: MatchId,
    tournamentId: z.string().optional(),
    winners: z.record(z.number(), TeamScore),
});
export type GameEndedEvent = z.infer<typeof GameEndedEvent>;
export const GameStartedEvent = z.object({
    matchId: MatchId,
    tournamentId: TournamentId.optional(),
    timestamp: z.coerce.number().int(),
});
export type GameStartedEvent = z.infer<typeof GameStartedEvent>;
export const LinkTokenPayload = z.object({
    gameId: GameId,
    playerId: PlayerId,
    rating: z.coerce.number().optional(),
});
export type LinkTokenPayload = z.infer<typeof LinkTokenPayload>;
export const Team = z.object({
    players: PlayerId.array(),
});
export type Team = z.infer<typeof Team>;
export const StartGamePayload = z.object({
    matchId: MatchId,
    tournamentId: TournamentId.optional(),
    backUrl: z.string().url().optional(),
    rivals: Team.array(),
});
export type StartGamePayload = z.infer<typeof StartGamePayload>;
export const StartGameEvent = z.object({
    games: StartGamePayload.array(),
});
export type StartGameEvent = z.infer<typeof StartGameEvent>;

Establishing a WebSocket Connection

This chapter focuses on establishing a WebSocket connection with the Genome API, which is crucial for real-time communication and receiving updates.

1

Prepare Authentication Helper

In order to pass handshake authentication, we need to sign authentication message with our GAME_SECRET. Create a src/auth.ts file and copy the next snippet.

import jwt from 'jsonwebtoken';
import { Env } from './environment';
export const signPayload = <T extends {}>(payload: T) => {
    return jwt.sign(payload, Env.GAME_SECRET, { algorithm: 'HS256' });
};
2

Establish WebSocket Connection

Create the main file at src/app.ts and copy next code there.

import { io } from 'socket.io-client';
import { signPayload } from './auth';
import { Env } from './environment';
import { AuthTokenPayload } from './models';
async function main() {
    const client = io(Env.GAME_API_URL, {
        auth: {
            token: signPayload(AuthTokenPayload.parse({ gameId: Env.GAME_ID })),
        },
    });
    client.on('connect', () => {
        console.log(`Connected to Game API`);
    });
    client.on('startGame', (_payload) => {
        throw new Error("not implemented yet")
    });
    client.on('disconnect', (reason) => {
        console.log(`Disconnected with reason: ${reason}`);
    });
}
main();

Explanation

  1. signPayload() function generates a JWT signature for arbitrary payload using the game secret configured in .env file and loaded by config().

  2. io() creates SocketIO connection with server using GAME_API_URL environment variable and signed auth payload unique gameId

Handle API Events

In this part we explain how to handle the game start and game end events.

Replace content in main.ts with handler of game start and commitment of result logic.

For demonstration purposes, in the example code, we’re using a dummy implementation based on artificial delays, but you need to create your own game client to create matches and wait for results.

Example Code
client.on('startGame', async (payload: unknown) => {
    const parsedPayload = StartGameEvent.parse(payload);
    console.log('start game', parsedPayload.games);
    await Promise.all(
        parsedPayload.games.map(async (game) => {
            // Dummy 1s wait for emulate game starting mechanics
            await sleep(1000);
            // Notify ZeroSum API about started game
            await client.emit(
                'gameStarted',
                GameStartedEvent.parse({
                    matchId: game.matchId,
                    tournamentId: game.tournamentId,
                    timestamp: toUnix(Date.now()),
                } satisfies GameStartedEvent),
            );
            // Dummy 1s wait for emulate game finishing mechanics
            await sleep(1000);
            // Construct random array of winners
            const winners = game.rivals.map(({ players }) => ({
                players,
                scores: players.map(() => Math.floor(Math.random() * 10000)),
            }));
            // Sort winners accordingly random scores
            winners.sort(
                (a, b) =>
                    b.scores.reduce((total, next) => total + next) -
                    a.scores.reduce((total, next) => total + next),
            );
            // Notify ZeroSum API about game ended
            await client.emit(
                'gameEnded',
                GameEndedEvent.parse({
                    winners,
                    matchId: game.matchId,
                } satisfies GameEndedEvent),
            );
        }),
    );
});

Start Game

  1. Wait for astartGame event.

  2. parsedPayload.games.map(async (game) => { Loop though all games and proceed with a match for each game:

    1. Create a match in the game (we’re just waiting 1 second: await sleep(1000))

    2. Emit gameStarted event to our api

await client.emit(
    'gameStarted',
    GameStartedEvent.parse({
        matchId: game.matchId,
        tournamentId: game.tournamentId,
        timestamp: toUnix(Date.now()),
    } satisfies GameStartedEvent),
);

End Game

  1. Wait for results (we’re just waiting 1 second and create random scores and winners)

  2. Emit gameEnded event to our api

await client.emit(
    'gameEnded',
    GameEndedEvent.parse({
        winners,
        matchId: game.matchId,
    } satisfies GameEndedEvent),
);

Linking Players

Game events operate with player IDs linked with the platform by your game server. Follow the steps below to link a user with the platform.

1

Create Object

Create LinkTokenPayloadobject with playerId is equal to your in-game identifier:

const payload = LinkTokenPayload.parse({
    gameId: Env.GAME_ID,
    playerId: <PLAYER_INGAME_ID>
} satisfies LinkTokenPayload)
2

Sign Payload

Sign payload with game secret:

const token = signPayload(payload)
3

Redirect

Ask player to visit linkage url with autenticated browser on Genome platform.

redirect(`${Env.PLATFORM_URL}/?token=${token}`)

Last updated