Comment le provably fair fonctionne dans Plinko

Voici comment jouer à Plinko sur Cloudbet, et comment vérifier que la trajectoire de chaque bille est provably fair.

Comment jouer à Plinko

Plinko est un jeu de hasard où vous laissez tomber une bille dans une pyramide de picots et regardez comment elle rebondit jusqu'à un gain. Avant de la lâcher, vous définissez le montant de votre mise, le nombre de rangées de la pyramide, et un niveau de risque. Plus il y a de rangées en jeu et plus le risque est élevé, plus les multiplicateurs possibles sont importants — mais plus ils sont difficiles à atteindre. Une fois la bille lâchée, elle rebondit à gauche ou à droite sur chaque rangée de picots jusqu'à atterrir dans l'un des casiers en bas, chacun avec son propre multiplicateur. De nombreux jeux Plinko vous permettent de lâcher plusieurs billes à la fois pour un jeu plus rapide.

Cloudbet Originals Plinko

Le Plinko de Cloudbet Originals vous permet de choisir 8, 10, 12 ou 16 rangées, quatre niveaux de risque (Faible, Moyen, Élevé, Extrême), et 1, 5, 10, 20, 50 ou 100 billes par lâcher. Il a un RTP de 99 %. Une fonctionnalité BOOST optionnelle (un supplément de 20 % sur votre mise) ajoute des multiplicateurs de bille spéciaux 2x, 5x et 10x, portant le multiplicateur maximal à 100 000x — plafonné à un gain total de 1 000 000 $.

Pour une introduction générale au provably fair — sources du serveur, sources du client, nonces, hachage, et comment vérifier un résultat — consultez le système provably fair de Cloudbet. Voici comment cela s'applique spécifiquement à Plinko.

Comment la trajectoire d'une bille devient un casier

La trajectoire de chaque bille dans la pyramide est une série de rebonds sur les picots, et chaque rebond est soit vers la gauche, soit vers la droite.

Ce choix peut être encodé comme un « 0 » (gauche) ou un « 1 » (droite). Une trajectoire comme « gauche, gauche, droite » devient le code « 001 », et en la suivant, la bille atterrit dans le Casier n° 2.

Voici le principe clé : une trajectoire atterrit dans le casier correspondant à son nombre de rebonds vers la droite, plus un. Ainsi, « 100 », « 010 » et « 001 » — qui ont tous exactement un « 1 » — atterrissent dans le Casier n° 2 (1 + 1).

Cela signifie qu'une trajectoire aléatoire peut être générée et encodée en une seule étape : prenez un nombre aléatoire de 32 bits, lisez ses N premiers bits (où N est le nombre de rangées), et comptez combien de ces bits sont des « 1 ». Ajoutez un, et voilà le casier où la bille atterrit. Le casier est calculé et verrouillé sur le serveur avant le début de la manche, caché jusqu'à la fin de la manche puis révélé.

Vérifiez l'équité provably fair de vos propres parties de Plinko

Le code ci-dessous correspond exactement à l'implémentation de Plinko chez Cloudbet. Entrez votre Source du serveur, votre Source du client, votre Nonce, le nombre de billes et le nombre de rangées, installez l'unique dépendance, et exécutez-le.

npm install js-sha3

Après avoir installé la dépendance, saisissez vos propres données dans le bloc de code ci-dessous.

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

Remarque : pour révéler la Source du serveur d'une partie sur Cloudbet, vous devez d'abord renouveler la Source du serveur.

Plus d'explications sur les jeux provably fair :

  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