Cómo funciona provably fair en Plinko
Así se juega a Plinko en Cloudbet, y así puedes comprobar que el recorrido de cada bola es provably fair.
Cómo jugar a Plinko
Plinko es un juego de azar en el que sueltas una bola por una pirámide de clavijas y observas cómo rebota hasta un premio. Antes de soltarla, fijas tu apuesta, cuántas filas tiene la pirámide y un nivel de riesgo. Cuantas más filas haya en juego y mayor sea el riesgo, mayores serán los multiplicadores posibles, pero más difícil será alcanzarlos. Al soltar la bola, esta rebota a izquierda o derecha en cada fila de clavijas hasta caer en uno de los cubos de la parte inferior, cada uno con su propio multiplicador. Muchas versiones de Plinko permiten soltar varias bolas a la vez para jugar más rápido.
Plinko de Cloudbet Originals
El Plinko propio de Cloudbet Originals te permite elegir 8, 10, 12 o 16 filas, cuatro niveles de riesgo (Bajo, Medio, Alto, Extremo) y 1, 5, 10, 20, 50 o 100 bolas por tirada. Tiene un RTP del 99 %. Una función opcional BOOST (un recargo del 20 % sobre tu apuesta) añade multiplicadores especiales de bola de 2x, 5x y 10x, elevando el multiplicador máximo a 100 000x, con un pago total máximo de 1 000 000 $.
Para una introducción general a provably fair —semillas del servidor, semillas del cliente, nonces, hashing y cómo verificar un resultado—, consulta el sistema de imparcialidad comprobable de Cloudbet. A continuación se explica cómo se aplica específicamente a Plinko.
Cómo el recorrido de una bola se convierte en un cubo
El recorrido de cada bola por la pirámide es una serie de rebotes en clavijas, y cada rebote es hacia la izquierda o hacia la derecha.

Esa elección se puede codificar como «0» (izquierda) o «1» (derecha). Un recorrido como «izquierda, izquierda, derecha» se convierte en el código «001», y siguiendo ese camino la bola cae en el Cubo #2.

Aquí está el patrón clave: un recorrido cae en el cubo que corresponde a su número de rebotes hacia la derecha, más uno. Por eso «100», «010» y «001» —todos con exactamente un «1»— caen en el Cubo #2 (1 + 1).

Esto significa que un recorrido aleatorio se puede generar y codificar en un solo paso: se toma un número aleatorio de 32 bits, se leen sus primeros N bits (donde N es el número de filas) y se cuenta cuántos de esos bits son «1». Se suma uno, y ese es el cubo en el que cae la bola. El cubo se calcula y se fija en el servidor antes de que empiece la ronda, permaneciendo oculto hasta que esta termina y se revela.
Verifica la imparcialidad comprobable de tus propias partidas de Plinko
El código de abajo es fiel a la implementación de Plinko de Cloudbet. Introduce tu Semilla del servidor, Semilla del cliente, Nonce, número de bolas y número de filas, instala la única dependencia y ejecútalo.
npm install js-sha3
Tras instalar la dependencia, introduce tus propios datos en el siguiente bloque de código.
// 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: para revelar la Semilla del servidor de una partida en Cloudbet, primero debes rotar a una nueva Semilla del servidor.
