Come funziona il provably fair in Plinko

Ecco come giocare a Plinko su Cloudbet, e come verificare che il percorso di ogni pallina sia provably fair.

Come giocare a Plinko

Plinko è un gioco d'azzardo in cui lasci cadere una pallina lungo una piramide di pioli e osservi come rimbalza fino a un risultato. Prima di lasciarla cadere, imposti l'importo della scommessa, il numero di righe della piramide e un livello di rischio. Più righe sono in gioco e più alto è il rischio, maggiori sono i possibili moltiplicatori — ma più difficili da raggiungere. Una volta lasciata cadere la pallina, rimbalza a sinistra o a destra a ogni riga di pioli finché non atterra in uno dei contenitori lungo il fondo, ciascuno con il proprio moltiplicatore. Molti giochi Plinko ti permettono di lasciare cadere più palline contemporaneamente per una giocata più veloce.

Cloudbet Originals Plinko

Il Plinko degli Originals di Cloudbet ti permette di scegliere 8, 10, 12 o 16 righe, quattro livelli di rischio (Basso, Medio, Alto, Estremo) e 1, 5, 10, 20, 50 o 100 palline per lancio. Ha un RTP del 99%. Una funzione BOOST opzionale (un sovrapprezzo del 20% sulla tua scommessa) aggiunge moltiplicatori speciali di pallina 2x, 5x e 10x, portando il moltiplicatore massimo a 100.000x — con un limite di vincita totale di 1.000.000 $.

Per un'introduzione generale al provably fair — server seed, client seed, nonce, hashing e come verificare un risultato — consulta il sistema di equità dimostrabile di Cloudbet. Di seguito trovi come si applica in modo specifico a Plinko.

Come il percorso di una pallina diventa un contenitore

Il percorso di ogni pallina lungo la piramide è una serie di rimbalzi contro i pioli, e ogni rimbalzo è verso sinistra o verso destra.

Quella scelta può essere codificata come uno "0" (verso sinistra) o un "1" (verso destra). Un percorso come "sinistra, sinistra, destra" diventa il codice "001", e seguirlo fa atterrare la pallina nel Contenitore #2.

Ecco lo schema chiave: un percorso atterra nel contenitore corrispondente al suo numero di rimbalzi verso destra, più uno. Quindi "100", "010" e "001" — tutti con esattamente un "1" — atterrano nel Contenitore #2 (1 + 1).

Questo significa che un percorso casuale può essere generato e codificato in un solo passaggio: si prende un numero casuale a 32 bit, si leggono i suoi primi N bit (dove N è il numero di righe) e si conta quanti di quei bit sono "1". Si aggiunge uno, e quello è il contenitore in cui atterra la pallina. Il contenitore viene calcolato e bloccato sul server prima dell'inizio del round, nascosto fino alla fine del round e poi rivelato.

Verifica il provably fair delle tue partite a Plinko

Il codice qui sotto è fedele all'implementazione di Plinko su Cloudbet. Inserisci il tuo Server Seed, Client Seed, Nonce, numero di palline e numero di righe, installa l'unica dipendenza necessaria ed eseguilo.

npm install js-sha3

Dopo aver installato la dipendenza, inserisci i tuoi dati nel blocco di codice qui sotto.

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

Nota: per rivelare il Server Seed di una partita su Cloudbet, devi prima ruotare verso un nuovo Server Seed.

Altre spiegazioni sul provably fair per i vari giochi:

  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