DocsGames
Hi-Lo
Hi-Lo walks the Blackjack deck one card at a time, and the rules for aces and equal ranks are yours to choose and publish.
There is no Hi-Lo shuffle. In the library, hilo is a second name for the function that shuffles a Blackjack deck.
import { play } from '@galabet/fair';
import { blackjack, hilo } from '@galabet/fair/games';
console.log(hilo === blackjack);
const { result, cursor } = await play({
game: 'hilo',
serverSeed: '5c1f7d3e8a2b4c6d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d',
clientSeed: 'galabet',
nonce: 42,
});
console.log(result.length, cursor);
console.log(result.slice(0, 6).join(' '));
true
52 6
7C KC 3S AC QH 5H
So how the deck gets made is on the Blackjack page and isn't repeated here: the 51 floats, the seven digests behind cursor 6, the decks parameter, the label format, the errors. A port that passes the blackjack vectors has already written Hi-Lo.
The record says game: "hilo", and the game name is part of what a record hash covers, so a hilo record and a blackjack record with identical seeds hold the same 52 labels under different hashes. If you have a Hi-Lo record and want to know whether its deck is genuine, the verifier accepts it as it is.
Rules You Define
The player sees a card and calls the next one higher or lower. Ranks are compared and suits are ignored. That leaves two questions no shuffle can answer. Is an ace above a king or below a two? And what happens when the next card has the same rank?
| Question | Galabet's practice page |
|---|---|
| Ace | High. Order runs 2 up to K, then A |
| Equal rank | A tie, neither a win nor a loss |
| Stakes and payouts | None |
The public deck runs into both early. Its third and fourth cards are 3S then AC: a correct "higher" call with aces high, and a wrong one with aces low, from the same two cards. The first equal pair is 7H then 7D, at positions 12 and 13.
Neither rule is in GFS. Publish yours before anyone plays, the way the demo API lists ace: "high" and equalRank: "tie" under GET /api/demo/games/rules, because a verified deck can't tell a player which of two reasonable tables they were sitting at.
Remaining Ranks and Odds
import { play } from '@galabet/fair';
const ORDER = '23456789TJQKA'; // ace high. For ace low, move the A to the front.
const { result: deck } = await play({
game: 'hilo',
serverSeed: '5c1f7d3e8a2b4c6d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d',
clientSeed: 'galabet',
nonce: 42,
});
const left = new Array(13).fill(4);
const sum = (counts) => counts.reduce((a, b) => a + b, 0);
for (let at = 0; at < 6; at++) {
const rank = ORDER.indexOf(deck[at][0]);
left[rank]--;
const higher = sum(left.slice(rank + 1));
const lower = sum(left.slice(0, rank));
const unseen = 51 - at;
const share = (count) => `${((count / unseen) * 100).toFixed(1)}%`;
console.log(`${deck[at]} higher ${higher} (${share(higher)}) lower ${lower} (${share(lower)}) equal ${left[rank]} of ${unseen}`);
}
7C higher 28 (54.9%) lower 20 (39.2%) equal 3 of 51
KC higher 4 (8.0%) lower 43 (86.0%) equal 3 of 50
3S higher 42 (85.7%) lower 4 (8.2%) equal 3 of 49
AC higher 0 (0.0%) lower 45 (93.8%) equal 3 of 48
QH higher 6 (12.8%) lower 38 (80.9%) equal 3 of 47
5H higher 32 (69.6%) lower 11 (23.9%) equal 3 of 46
With aces high, a fresh deck is even odds only when the card is an 8, and it stops being a fresh deck after one card. Every line above is worked out from the cards already face up and nothing else. That matters on a server. It knows the whole deck, and odds computed from the cards still face down would leak them, so whatever you show the player has to be built the way this loop builds it.
Look at the AC line. With aces high, nothing is higher. Whether you still offer that call is a rule too.
The equal column is where a payout table would get its edge. Three cards in 51 is 5.9%, and counting them as a loss, a push or a win shifts the player's chance on every step by up to that much. This project has no payout ladder for Hi-Lo to show you. The demo's rules list it with settlement: false.
Withholding the Deck
Blackjack has to hide the deck while a hand is live. Hi-Lo has more to lose, since guessing the next card is the entire game and result is every next card in order. It stays on the server until the run ends, and the server seed stays there until rotation.
The practice page at /games/hilo/ doesn't do that. It calculates the full deck in your browser from the public seed and compares ranks there, which is fine for a page with no stakes and would be fatal with them. A server that turns one card per request, records each call and conceals the rest has not been written in this project.
One nonce is one deck, so a run is 51 calls at most. What happens at the last card is yours to decide. The practice page stops and asks for a new deck. And the list of calls is a second history that the seeds can't reproduce, the same as the actions in a Blackjack hand. Keep it with the record.
| Server seed | 5c1f7d3e8a2b4c6d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d (public, for following along only) |
| Client seed, nonce | galabet, 42 |
| Result | 52 × decks labels in the order they are turned |
params | decks, 1 to 8, default 1 |
| Record cursor | 6 for one deck |
| Test vectors | 112 in vectors/gfs-1.0.json, all single deck |
