Skip to content

Hi/Lo Game Responses

This guide explains how to read the scenario field that placeBet returns for a Hi/Lo card game. It also explains how to use the loadConfig data to display card odds and multipliers.

Overview

Hi/Lo is a multi-step wager game. The engine deals the player a card from an infinite deck (13 equally-likely values, Ace low through King high, 4 suits). The player chooses Higher, Lower, or Skip:

  • Higher / Lower: The player guesses the next card's rank. A win multiplies the cashout. A loss forfeits everything.
  • Skip: The player discards the card and receives a new one. The cashout multiplier does not change.

The player can collect at any point between guesses.

The game uses wager features (wagerFeatures). Each round's features multiply the accumulated totalWin. The basegame entry (win: 1) sets the initial stake as the base. Every subsequent feature multiplies from there.

The Hi/Lo Scenario

When placeBet returns a Hi/Lo result, gameResult.scenario contains:

typescript
interface IHiLoScenario {
  round: number;       // Current round (0 = deal, 1+ = guesses)
  card: number;        // Card value: 1 (Ace) through 13 (King)
  suit: string;        // "hearts" | "diamonds" | "clubs" | "spades"
  action?: string;     // "deal" (initial card) or "choose" (skip re-deal choice)
  guess?: string;      // "higher" | "lower" | "skip" - present on guess results
  correct?: boolean;   // Whether the guess was correct - present on higher/lower
  multiplier?: number; // Win multiplier - present on correct guesses
}

The card and suit always represent the drawn card:

  • On deal/choose: the card the player sees and must decide on
  • On win/lose: the next card the engine drew (the result of the guess)
  • On skip: the new card the engine deals after the skip

Game Flow

Step 1: Initial Deal and Player Choice

The first placeBet call deals a card. It presents the player's choices in playerChoice:

typescript
const response = await placeBet({ backendURL, token, stake: 100 });

if (response.success) {
  const { scenario, engineData } = response.result.result;
  const hilo = scenario as IHiLoScenario;

  // hilo.round === 0, hilo.card === 7, hilo.suit === "hearts"
  console.log(`Dealt: ${hilo.card} of ${hilo.suit}`);

  // engineData.playerChoice contains the available actions:
  // [
  //   { feature: "round_1_card_7_higher", count: 1 },
  //   { feature: "round_1_card_7_lower", count: 1 },
  //   { feature: "round_1_card_7_skip", count: 1 },
  // ]
}

Step 2: Player Guesses

The player selects a choice by index, using playerChoiceIndex:

typescript
// Player chooses "higher" (index 0)
const guessResponse = await placeBet({
  backendURL,
  token,
  playerChoiceIndex: 0, // 0 = higher, 1 = lower, 2 = skip
});

if (guessResponse.success) {
  const { scenario, engineData, totalWin } = guessResponse.result.result;
  const hilo = scenario as IHiLoScenario;

  if (hilo.correct) {
    // Won - scenario shows the drawn card
    console.log(`Correct! Drew ${hilo.card} of ${hilo.suit}, multiplier: ${hilo.multiplier}x`);
    console.log(`Cashout: ${totalWin}x`);

    // engineData.playerChoice has choices for the next card
    // engineData.canCollect === true - player can collect or continue
  } else {
    // Lost - scenario shows the card that beat you
    console.log(`Bust! Drew ${hilo.card} of ${hilo.suit}`);
    // engineData.inProgress === false - round is over
  }
}

Skip: New Card, Same Multiplier

When the player skips, the scenario shows the new card. The multiplier does not change:

typescript
// Player chooses "skip" (index 2)
const skipResponse = await placeBet({
  backendURL,
  token,
  playerChoiceIndex: 2,
});

if (skipResponse.success) {
  const { scenario, engineData } = skipResponse.result.result;
  const hilo = scenario as IHiLoScenario;

  // hilo.guess === "skip"
  // hilo.card is the NEW card dealt
  console.log(`New card: ${hilo.card} of ${hilo.suit}`);

  // engineData.playerChoice has higher/lower/skip for the new card
  // totalWin is unchanged
}

Collecting

To cash out, call the collect endpoint instead of placing another bet:

typescript
const collectResponse = await collect({ backendURL, token });
// Player receives totalWin × stake

Loading Game Config

The loadConfig response provides game parameters. It also provides the full odds and multiplier tables for round 1 (house edge) and round 2+ (fair):

typescript
const config = configResponse.result.config;

config.gameType;        // 'hilo'
config.maxRounds;       // Maximum guess rounds (e.g. 25)
config.tiesWin;         // Whether same-value cards count as a win
config.startingCardMin; // Minimum starting card value (e.g. 4)
config.startingCardMax; // Maximum starting card value (e.g. 10)

// Round 1 odds (house edge applied)
config.round1Odds;      // Array of { card, pHigher, pLower, higherMultiplier, lowerMultiplier }

// Round 2+ odds (fair, 100% RTP)
config.fairOdds;        // Same structure, but with fair multipliers

Using the Odds Tables

typescript
interface IHiLoOdds {
  card: number;            // 1 (Ace) through 13 (King)
  pHigher: number;         // Probability of winning a "higher" guess
  pLower: number;          // Probability of winning a "lower" guess
  higherMultiplier: number; // Payout multiplier for correct "higher"
  lowerMultiplier: number;  // Payout multiplier for correct "lower"
}

const round1Odds = config.round1Odds as IHiLoOdds[];
const fairOdds = config.fairOdds as IHiLoOdds[];

// Show odds for current card (e.g. card value 7, round 1)
const odds = round1Odds[6]; // 0-indexed: card 7 is index 6
console.log(`Higher: ${(odds.pHigher * 100).toFixed(1)}% → ${odds.higherMultiplier}x`);
console.log(`Lower: ${(odds.pLower * 100).toFixed(1)}% → ${odds.lowerMultiplier}x`);

// For round 2+, use fairOdds instead
const fair = fairOdds[6];
console.log(`Fair higher: ${fair.higherMultiplier}x`);

How the Player Choice Works

Hi/Lo uses playerChoice feature awards to present the higher/lower/skip options. The playerChoiceIndex in the placeBet request maps to the awards array:

IndexChoiceFeature pattern
0Higherround_{N}_card_{V}_higher
1Lowerround_{N}_card_{V}_lower
2Skipround_{N}_card_{V}_skip

TIP

Not all choices are always available. Extreme cards may have only one direction (for example, Ace with tiesWin: false cannot go lower). Check the length of engineData.playerChoice.

How RTP Works in Hi/Lo

The house edge is taken once, on the first guess

The engine applies the target RTP (for example, 98%) at round 1 only. All subsequent guesses use fair odds (100% RTP).

For every card, the invariant holds: P(win) × multiplier = effective RTP

RoundEffective RTPMultiplier formula
Round 198% (target)0.98 / P(win)
Round 2+100% (fair)1.0 / P(win)

Dynamic Odds: Strategy Does Not Change the EV

The engine computes multipliers per card, based on the probability of winning that guess. Higher probability means lower multiplier. The expected value is identical for every card:

CardGuessP(win)MultiplierEV (Round 1)
AHigher100%0.98x0.98
7Higher53.8%1.82x0.98
KLower100%0.98x0.98
7Lower53.8%1.82x0.98

Skipping does not change the expected value. It changes the variance (risk and reward), not the RTP. A player who skips to Aces wins almost every time. That player gains almost nothing. A player who plays middle cards wins less often. That player gets bigger payouts. The EV is the same either way.

Multiplier rounding

When the game config enables multiplier rounding, the engine snaps multipliers to clean numbers (for example, 1x, 1.5x, 2x). The engine adjusts the win and lose weights automatically. This keeps the target RTP exact.

Feature Structure

Hi/Lo uses a layered feature structure:

basegame (regular feature, deal card, win=1)
  └─ playerChoice → round_1_card_7_higher | _lower | _skip

round_1_card_7_higher (wagerFeature, resolves guess)
  ├─ win entries (per drawn card): multiplier, playerChoice → round_2_card_X_higher | _lower | _skip
  └─ lose entries (per drawn card): win=0, bust

round_1_card_7_skip (wagerFeature, re-deals same round)
  └─ entries (per drawn card): win=1, playerChoice → round_1_card_X_higher | _lower | _skip

round_N_card_V (wagerFeature, choice after skip re-deal)
  └─ single entry: win=1, playerChoice → round_N_card_V_higher | _lower | _skip

Key points:

  • Basegame is a regular feature, not a wager feature. win: 1 sets totalWin = 1× stake.
  • All round features are wager features. Multipliers compound multiplicatively.
  • Win and lose entries each show the drawn card in the scenario. A win shows the card the player beat. A loss shows the card that beat the player.
  • Win entries include playerChoice for the next card. The player does not need another placeBet call.
  • Skip entries include playerChoice for the re-dealt card. The player does not need another placeBet call.
  • Choice features (round_N_card_V) are entered only through skip re-deals. The basegame skips them for the initial card.

Complete Example

typescript
import { login, loadConfig, placeBet, collect } from '@hizi.io/engine-sdk';

interface IHiLoScenario {
  round: number;
  card: number;
  suit: string;
  guess?: string;
  correct?: boolean;
  multiplier?: number;
}

const CARD_LABELS: Record<number, string> = {
  1: 'A', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7',
  8: '8', 9: '9', 10: '10', 11: 'J', 12: 'Q', 13: 'K',
};

// After login and loadConfig...

// Deal initial card
let response = await placeBet({ backendURL, token, stake: 100 });

// Game loop
while (response.success) {
  const { scenario, engineData, totalWin } = response.result.result;
  const hilo = scenario as IHiLoScenario;

  if (hilo.guess && hilo.correct === false) {
    // Bust - show the card that beat the player
    showBust(CARD_LABELS[hilo.card], hilo.suit);
    break;
  }

  if (!engineData.inProgress) {
    // Auto-cashout at max rounds
    showWin(totalWin);
    break;
  }

  // Show current card and choices
  const choices = engineData.playerChoice;
  showCard(CARD_LABELS[hilo.card], hilo.suit);

  if (totalWin > 1) {
    showCashout(totalWin); // Player can collect
  }

  // Wait for player decision
  const decision = await getPlayerDecision(choices);

  if (decision === 'collect') {
    await collect({ backendURL, token });
    showWin(totalWin);
    break;
  }

  // Continue with chosen action
  response = await placeBet({
    backendURL,
    token,
    playerChoiceIndex: decision, // 0=higher, 1=lower, 2=skip
  });
}

Provably Fair Verification

Hi/Lo is a multi-step game. Each guess (higher / lower / skip) draws a fresh card from the RNG. The verify path replays the round. It follows the same playerChoice decisions the player made live.

Request

Each continuation step contributes one entry in actions. The playerChoiceIndex field matches the offered choice (higher / lower / skip).

typescript
import { pfVerify } from '@hizi.io/engine-sdk';

const reply = await pfVerify({
  backendURL,
  token,
  serverSeed: pf.revealedServerSeed,
  clientSeed: pf.clientSeed,
  stake: 100,
  actions: [
    { playerChoiceIndex: 0 },  // higher
    { playerChoiceIndex: 0 },  // higher
    { playerChoiceIndex: 2 },  // skip
    { playerChoiceIndex: 1 },  // lower - round ends here
  ],
});

Reading the response

  • rngData: one int row per drawn card. Compare it against the live pf.rngData array for equality.
  • steps[i]: the IGameResult after guess i. It includes the revealed card in scenario.
  • steps[steps.length - 1].totalWin: equals the live terminal multiplier.

The card sequence in the verified steps[] proves the engine could not have re-drawn a card to alter the outcome.

typescript
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rngOk = deepEqual(rngData, pf.rngData);
const terminalWinOk = steps[steps.length - 1].totalWin === lastLiveResult.totalWin;

Next Steps