Jak działa provably fair w grze Plinko

Oto jak grać w Plinko na Cloudbet oraz jak sprawdzić, że tor każdej kulki jest provably fair.

Jak grać w Plinko

Plinko to gra losowa, w której upuszczasz kulkę na piramidę kołków i obserwujesz, jak odbija się aż do wypłaty. Przed upuszczeniem ustawiasz kwotę zakładu, liczbę rzędów piramidy oraz poziom ryzyka. Im więcej rzędów w grze i im wyższe ryzyko, tym większe możliwe mnożniki — ale tym trudniej je trafić. Po upuszczeniu kulki odbija się ona w lewo lub w prawo od każdego rzędu kołków, aż wyląduje w jednym z koszyków na dole, z których każdy ma swój własny mnożnik. Wiele gier Plinko pozwala upuścić kilka kulek jednocześnie, aby przyspieszyć rozgrywkę.

Cloudbet Originals Plinko

Własna gra Plinko od Cloudbet Originals pozwala wybrać 8, 10, 12 lub 16 rzędów, cztery poziomy ryzyka (Low, Medium, High, Extreme) oraz 1, 5, 10, 20, 50 lub 100 kulek na jedno upuszczenie. Ma RTP na poziomie 99%. Opcjonalna funkcja BOOST (20% dopłaty do zakładu) dodaje specjalne mnożniki kulek 2x, 5x i 10x, podnosząc maksymalny mnożnik do 100 000x — z limitem całkowitej wypłaty wynoszącym 1 000 000 $.

Ogólne wprowadzenie do provably fair — seedy serwera, seedy klienta, nonce, hashowanie i sposób weryfikacji wyniku — znajdziesz w systemie provably fair Cloudbet. Poniżej opisano, jak odnosi się to konkretnie do Plinko.

Jak tor kulki staje się koszykiem

Tor każdej kulki w drodze w dół piramidy to seria odbić od kołków, a każde odbicie jest w lewo lub w prawo.

Diagram pokazujący, jak każde odbicie w grze o weryfikowalnej uczciwości Plinko od Cloudbet jest kodowane jako wybór w lewo lub w prawo.

Ten wybór można zakodować jako „0” (w lewo) lub „1” (w prawo). Tor taki jak „w lewo, w lewo, w prawo” staje się kodem „001”, a podążanie nim sprawia, że kulka ląduje w Koszyku #2.

Przykład pokazujący trasę kulki w grze Plinko zakodowaną jako sekwencję zer i jedynek w systemie o weryfikowalnej uczciwości Cloudbet, kończącą się w koszyku nr 2.

Oto kluczowy wzorzec: tor ląduje w koszyku odpowiadającym liczbie odbić w prawo, plus jeden. Tak więc „100”, „010” i „001” — wszystkie z dokładnie jedną „1” — lądują w Koszyku #2 (1 + 1).

Diagram pokazujący kilka różnych, zgodnych z zasadą weryfikowalnej uczciwości, tras kulek w Plinko, które matematycznie kończą się w tym samym koszyku.

Oznacza to, że losowy tor może zostać wygenerowany i zakodowany w jednym kroku: bierze się losową liczbę 32-bitową, odczytuje jej pierwsze N bitów (gdzie N to liczba rzędów) i liczy, ile z tych bitów to „1”. Dodaje się jeden, i to jest koszyk, w którym ląduje kulka. Koszyk jest obliczany i blokowany na serwerze jeszcze przed rozpoczęciem rundy, ukryty do jej zakończenia, a następnie ujawniany.

Zweryfikuj provably fair swoich gier Plinko

Poniższy kod jest zgodny z implementacją Plinko na Cloudbet. Wprowadź swój Seed serwera, Seed klienta, Nonce, liczbę kulek i liczbę rzędów, zainstaluj jedną zależność i uruchom go.

npm install js-sha3

Po zainstalowaniu zależności wprowadź własne dane do poniższego bloku kodu.

// 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 myNumberOfBalls   = 1;                  // ✍️YOUR_INPUT✍️: Enter your number of balls here
let myNumberOfRows    = 12;                 // ✍️YOUR_INPUT✍️: Enter your number of rows here
const usingCustomData = false;              // Change this to true if you are using your own data

// 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 with a 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); // shake 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: Sample bits to determine ball paths
function pickBuckets(numOfRows, numOfBalls, rng) {
  let bucketIndices = [];
  for (let i = 0; i < numOfBalls; i++) {
    const rawUint32 = rng.next().value;
    let pathArray = rawUint32.toString(2).padStart(32, '0').split("");  // Convert the number into an array of bits
    pathArray.splice(numOfRows);                                        // Select the the first bits for the number of rows
    bucketIndices.push(getBucketIndexFromPath(pathArray));
  }
  return bucketIndices;
}

// Calculates which bucket the ball would land in an array of 1's and 0's where 0 = leftwards, 1 = rightwards
// In principle it counts the amount of times the ball bounces rightwards
function getBucketIndexFromPath(pathArray) {
  let rightCounter = 0;
  for(let i = 0; i < pathArray.length; ++i) {
    if (pathArray[i] === '1') {
      rightCounter += 1;
    } else {
      rightCounter += 0;
    }
  }
  return rightCounter + 1; // Add one so that the left-most bucket is bucket number one
}

// Example usage
(function main() {
  let numOfRows = 12;
  let numOfBalls = 1;
  let nonce = 1;
  if (usingCustomData) { numOfRows = myNumberOfRows; numOfBalls = myNumberOfBalls; nonce = myNonce; }

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = numOfBalls; // Only need to generate one number per ball
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature); 

  const bucketIndices = pickBuckets(numOfRows, numOfBalls, rng);

  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('📋 Number of rows:', numOfRows);
  console.log('🏁 Buckets landed in (Bucket #1 is the leftern-most bucket):', bucketIndices);
})();

Uwaga: aby ujawnić Seed serwera danej gry na Cloudbet, musisz najpierw dokonać rotacji na nowy Seed serwera.

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

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