Jak działa provably fair w grze Pump

Oto jak grać w Pump na Cloudbet oraz jak sprawdzić, że wadliwa pompka w każdej rundzie jest provably fair.

Jak grać w Pump

Pump to gra typu „graj na przekór szczęściu”: pompujesz coś (w wersji Cloudbet — balonik), co zwiększa mnożnik z każdym pompnięciem. Wypłać się w dowolnym momencie, aby zachować wygraną — jeśli jednak przesadzisz, balonik pęka i tracisz swój zakład. Zanim zaczniesz, zwykle wybierasz poziom trudności, który równoważy to, jak daleko możesz bezpiecznie się posunąć, z tym, jak szybko rośnie mnożnik.

Po postawieniu zakładu naciśnij Pump, aby napompować balonik krok po kroku. Każde udane pompnięcie zwiększa Twoją potencjalną wypłatę. Możesz nacisnąć Wypłać po dowolnym pompnięciu, aby odebrać swój aktualny mnożnik — lub kontynuować pompowanie w poszukiwaniu wyższego. Jeśli Twoje kolejne pompnięcie okaże się tym wadliwym w danej rundzie, balonik pęka natychmiast, a runda kończy się bez wypłaty.

Cloudbet Originals Pump

Własna gra Pump od Cloudbet Originals ma pięć poziomów trudności — Easy, Medium, Hard, Expert i Master — kontrolujących, ile z 25 pompnięć w rundzie jest „wadliwych” (tych, które przebijają balonik). Ma RTP na poziomie 99%, maksymalną wygraną 1 000 000 $ i przyjmuje zakłady od 0,01 $. Tryb Autobet pozwala skonfigurować kwotę zakładu, liczbę pompnięć na rundę, limity zysku/straty oraz liczbę rund do automatycznego rozegrania — ręczna gra („Pump czeka na TWOJE działanie”) jest zawsze dostępna.

Ogólne mechanizmy provably fair — seed serwera, seed klienta, nonce, hashowanie i sposób tworzenia Sygnatury rundy — opisano w przewodniku provably fair Cloudbet. Poniżej opisano, jak ta losowa sekwencja liczb staje się konkretnie wynikiem rundy Pump.

Przekształcanie liczb losowych w układ gry

25 pompek w Pump jest oznaczonych literami od A do Y, dzięki czemu możemy je śledzić:

Diagram przedstawiający 25 pomp w grze Pump, oznaczonych od A do Y, używanych do śledzenia wyboru wadliwej pompy w systemie o weryfikowalnej uczciwości Cloudbet.

Wszystkie pompki zaczynają jako niewadliwe. Korzystając z pierwszej liczby w sekwencji losowej danej gry, wybieramy niewadliwą pompkę według indeksu i czynimy ją wadliwą:

Praktyczny przykład algorytmu o weryfikowalnej uczciwości gry Pump: pierwsza wadliwa pompa jest losowo wybierana spośród wszystkich 25 sprawnych pomp.

Ta pompka jest następnie usuwana z listy niewadliwych pompek, więc kolejny wybór odbywa się już tylko spośród pozostałych:

Diagram pokazujący, jak system o weryfikowalnej uczciwości gry Pump od Cloudbet usuwa wybraną wadliwą pompę z listy przed wylosowaniem kolejnej.

Powtarza się to, aż zostanie osiągnięta liczba wadliwych pompek dla danego poziomu trudności rundy (tutaj pokazano medium = 3):

Praktyczny przykład pokazujący, jak algorytm o weryfikowalnej uczciwości gry Pump wybiera drugą wadliwą pompę spośród pozostałych sprawnych pomp.

Gdy wszystkie wadliwe pompki dla danej rundy zostaną wybrane, liczby użyte do ich wyboru nie są już potrzebne — pozostaje tylko informacja o tym, które pompki są wadliwe:

Diagram pokazujący pełny układ wadliwych pomp w danej rundzie, zanim obliczenia o weryfikowalnej uczciwości Cloudbet uproszczą go do jednej istotnej pompy.

Ponieważ pompki są uruchamiane pojedynczo, w kolejności (A, potem B, potem C…), znaczenie ma tylko pierwsza wadliwa pompka, do której dotrze gracz — wszystko po niej nigdy nie zostaje uruchomione. Układ upraszcza się więc do tej jednej istotnej pompki:

Diagram pokazujący układ wadliwych pomp o weryfikowalnej uczciwości w grze Pump, uproszczony do jednej — pierwszej wadliwej pompy, do której gracz faktycznie by dotarł.

Szczegóły techniczne

Liczbę niewadliwych pompek nazywamy P. Losowe bajty z SHAKE256 są dzielone na 4-bajtowe liczby całkowite bez znaku (od 0 do 2³²−1). Aby uczciwie wybrać, która pompka jest wadliwa, stosujemy próbkowanie odrzucające względem P, a następnie bierzemy resztę (+1) jako indeks niewadliwej pompki, która ma stać się wadliwa. Jeśli potrzeba jeszcze więcej wadliwych pompek, wybrana pompka jest usuwana z listy, P zmniejsza się o 1, a losowana jest nowa liczba — powtarza się to, aż zostanie osiągnięta liczba wadliwych pompek dla danej rundy.

Ostateczny układ wadliwych pompek jest generowany i zapisywany po stronie serwera jeszcze przed rozpoczęciem rundy i nie może zostać później zmieniony bez naruszenia Zobowiązania już udostępnionego graczowi. Każde pompnięcie gracza jest sprawdzane względem tego wcześniej ustalonego układu.

Zweryfikuj to samodzielnie

Napisaliśmy niewielki program w Node.js zgodny z implementacją Pump na Cloudbet — wprowadź swój Seed serwera, Seed klienta, Nonce oraz poziom trudności, aby odtworzyć wynik swojej rundy. (Uwaga: aby ujawnić Seed serwera danej rundy na Cloudbet, musisz już dokonać rotacji na nową parę seedów.)

// 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);
})();

Najpierw zainstaluj jedną zależność:

npm install js-sha3

Więcej wyjaśnień provably fair dla poszczególnych gier:

  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