Så fungerar provably fair i Pump

Så här spelar du Pump på Cloudbet, och så kontrollerar du att varje omgångs trasiga pump är provably fair.

Så spelar du Pump

Pump är ett push-your-luck-spel: du pumpar något (en ballong i Cloudbets version) som ökar en multiplikator för varje pumpning. Ta ut vinsten när som helst för att säkra den — men pumpar du för långt spricker ballongen och du förlorar din insats. Innan du börjar väljer du vanligtvis en svårighetsgrad som balanserar hur långt du säkert kan gå mot hur snabbt multiplikatorn växer.

När du har placerat din insats trycker du på Pump för att blåsa upp ballongen ett steg i taget. Varje lyckad pumpning höjer din potentiella utdelning. Du kan trycka på Ta ut efter valfri pumpning för att ta ut din nuvarande multiplikator — eller fortsätta pumpa för en större. Om din nästa pumpning råkar vara omgångens trasiga spricker ballongen omedelbart och omgången slutar utan utdelning.

Cloudbet Originals Pump

Cloudbets egen Originals Pump har fem svårighetsgrader — Easy, Medium, Hard, Expert och Master — som styr hur många av omgångens 25 pumpningar som är "trasiga" (de som spränger ballongen). Den har en RTP på 99 %, en maxvinst på $1 000 000, och tar emot insatser från $0,01. Autobet-läget låter dig konfigurera insatsbelopp, pumpningar per omgång, vinst-/förlustgränser och antal omgångar att spela automatiskt — manuellt spel ("Pump väntar på DIN input") finns alltid tillgängligt också.

För de allmänna mekanismerna bakom provably fair — server seed, client seed, nonce, hashning och hur Round Signature skapas — se Cloudbets guide till provably fair. Nedan ser du hur den slumpmässiga talföljden specifikt blir en Pump-omgångs resultat.

Att omvandla slumptal till en spellayout

Pumps 25 pumpningar märks A till Y så att vi kan följa dem:

Diagram som visar Pumps 25 pumpar märkta A till Y, som används för att följa Cloudbets bevisbart rättvisa val av trasig pump.

Alla pumpningar börjar som icke-trasiga. Med hjälp av det första talet i spelets slumptalsföljd väljer vi en icke-trasig pumpning efter index och gör den trasig:

Praktiskt exempel på Pumps bevisbart rättvisa algoritm: den första trasiga pumpen väljs slumpmässigt bland alla 25 fungerande pumpar.

Den pumpningen tas sedan bort från listan över icke-trasiga pumpningar, så nästa val bara sker bland det som är kvar:

Diagram som visar hur Cloudbets bevisbart rättvisa Pump-system tar bort en vald trasig pump från listan innan nästa dras.

Detta upprepas tills omgångens svårighetsgrads antal trasiga pumpningar är uppnått (medium = 3 visas här):

Praktiskt exempel som visar hur Pumps bevisbart rättvisa algoritm väljer en andra trasig pump bland de återstående fungerande pumparna.

När alla trasiga pumpningar för omgången väl är valda behövs inte längre talen som användes för att välja dem — kvar är bara vilka pumpningar som är trasiga:

Diagram som visar en omgångs fullständiga layout av trasiga pumpar innan Cloudbets bevisbart rättvisa beräkning förenklar den till den enda pump som spelar roll.

Eftersom pumpningar utlöses en i taget i ordning (A, sedan B, sedan C…) är det bara den första trasiga pumpningen spelaren når som faktiskt spelar roll — allt efter den utlöses aldrig. Layouten förenklas alltså till bara den enda relevanta pumpningen:

Diagram som visar Pumps bevisbart rättvisa layout av trasiga pumpar, förenklad till endast den första trasiga pump som spelaren faktiskt skulle nå.

Tekniska detaljer

Vi kallar antalet icke-trasiga pumpningar P. De slumpmässiga bytesen från SHAKE256 delas upp i 4-byte osignerade heltal (0 till 2³²−1). För att rättvist välja vilken pumpning som är trasig använder vi rejection sampling mot P, och tar sedan resten (+1) som index för den icke-trasiga pumpning som ska göras trasig. Om fler trasiga pumpningar fortfarande behövs tas den valda pumpningen bort från listan, P minskas med 1, och ett nytt tal hämtas — upprepat tills omgångens antal trasiga pumpningar är uppnått.

Den slutgiltiga layouten för trasiga pumpningar genereras och sparas serversidan innan omgången börjar, och kan inte ändras i efterhand utan att bryta mot den Commitment som redan delats med spelaren. Varje pumpning spelaren trycker på kontrolleras mot denna på förhand fastställda layout.

Verifiera det själv

Vi har skrivit ett litet Node.js-program som motsvarar Cloudbets Pump-implementation exakt — ange din Server Seed, Client Seed, Nonce och svårighetsgrad för att återskapa din omgångs resultat. (Obs: för att avslöja en omgångs Server Seed på Cloudbet måste du redan ha roterat till ett nytt seed-par.)

// If you are running this yourself, then you can input your own data here
// You can then compare:
// 1) Cloudbet's provided Commitment to the Commitment outputted here. This proves that the Server Seed was not altered during gameplay.
// 2) Cloudbet's provided game outcome to the game outcome outputted here. This proves that your game's outcome was determined solely by the pre-game seeds and was not altered afterwards.
let myServerSeed      = 'your_server_seed'; // ✍️YOUR_INPUT✍️: Enter your (unhashed) Server Seed here
let myClientSeed      = 'your_client_seed'; // ✍️YOUR_INPUT✍️: Enter your Client Seed here
let myNonce           = 1;                  // ✍️YOUR_INPUT✍️: Enter your Nonce here
let myDifficultyLevel = 'easy';             // ✍️YOUR_INPUT✍️: Enter your difficulty level here (easy, medium, hard, expert, or master)
const usingCustomData = false;              // Change this to true if you are using your own data

// These are the possible difficulty levels (and how many faulty pumps there are per difficulty level)
const faultyPumpsPerDiff = {
  'easy':       1,
  'medium':     3,
  'hard':       5,
  'expert':     9,
  'master':     12
};

// Load libraries
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');

// Step 1: Generate a Server Seed and Commitment
// And generate a Client Seed - either by player input or by the player's browser
function generateServerSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myServerSeed; }
  const commitment = sha3_256(seed);
  return { 'serverSeed': seed, 'commitment': commitment };
}
function getClientSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myClientSeed; }
  return seed;
}

// Step 2: Create a Round Signature with SHA3 using a Server Seed, Client Seed and Nonce 
function createRoundSignature(serverSeed, clientSeed, nonce) {
  return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}

// Step 3: Create a SHAKE256 stream generator from a Round Signature. 
// To simplify this example program, this function doesn't create a stream, but instead a sequence of custom length.
function* createRandomNumberSequence(numbersToGenerate, roundSignature) {
  const numOfBytes = 4 * numbersToGenerate;           // 4 bytes (32 bits) per random number we generate
  const byteStream = shake256.create(8 * numOfBytes); // shaker256 takes bits as input
  byteStream.update(roundSignature);                  // Use the Round Signature to seed the generator
  const buf = Buffer.from(byteStream.digest());       // The Buffer class lets us package every 4 bytes into one 32-bit integer
  for (let i = 0; i < numbersToGenerate; i++) {
    yield buf.readUInt32BE(i * 4);                    // Read buf in 4-byte steps
  }
  throw new Error("No more numbers in the random number sequence! Fix: make 'numbersToGenerate' larger.");
}

// Step 4: Generate indices of the faulty pumps
function pickUniquePositions(numOfFaultyPumps, totalPumps, rng) {
  const available = Array.from({ length: totalPumps }, (_, i) => i);
  const faultyPumps = [];

  for (let i = 0; i < numOfFaultyPumps; ++i) {
    const index = sample(available.length, rng);

    faultyPumps.push(available[index]);
    available.splice(index, 1); // Remove chosen pump from list of non-faulty pumps
  }
  return faultyPumps;
}
// Rejection sampling for fair results
function sample(P, rng) {
  const maxAcceptable = Math.floor(0x100000000 / P) * P;

  // Rejection sampling
  let rand;
  do {
    rand = rng.next().value;
  } while (rand >= maxAcceptable);
  // Modulo operation
  const sampled_number = rand % P;

  return sampled_number;
}

// Example usage
(function main() {
  const totalPumps = 25;
  let difficultyLevel = 'easy';
  let nonce = 1;
  if (usingCustomData) { difficultyLevel = myDifficultyLevel; nonce = myNonce; }

  const numOfFaultyPumps = faultyPumpsPerDiff[difficultyLevel];

  const { serverSeed, commitment } = generateServerSeed();
  const clientSeed = getClientSeed();

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 12 * 2;                 // We have at most 12 faulty pumps, however due to rejection sampling we may need more numbers
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const faultyPumps = pickUniquePositions(numOfFaultyPumps, totalPumps, rng);
  let firstFaultyPump = Math.min(...faultyPumps);  // Select the smallest/first faulty pump
  firstFaultyPump += 1;                            // Increment the index so that it starts with 1, 2, 3, ... instead of 0, 1, 2

  console.log('📝 Commitment (shown before the game):', commitment);
  console.log('🔒 Server Seed (revealed after the game):', serverSeed);
  console.log('🎲 Client Seed:', clientSeed);
  console.log('🔢 Nonce:', nonce);
  console.log('🔁 Round Signature (can be verified after the game):', roundSignature);
  console.log('🎯 Difficulty level:', difficultyLevel);
  console.log('💣 First faulty pump (zero-indexed, i.e., 1, 2, 3, ...):', firstFaultyPump);
})();

Installera det enda beroendet först:

npm install js-sha3

Fler förklaringar av provably fair per spel:

  1. Provably fair Mines
  2. Provably fair Plinko
  3. Provably fair Dice
  4. Provably fair Dice: Golden Ticket
  5. Provably fair Limbo
  6. Provably fair Roulette
  7. Provably fair Keno