Como o provably fair funciona no Plinko
Veja como jogar Plinko na Cloudbet, e como verificar se o caminho de cada bolinha é provably fair.
Como jogar Plinko
Plinko é um jogo de sorte em que você solta uma bolinha em uma pirâmide de pinos e observa ela quicar até um prêmio. Antes de soltar, você define o valor da sua aposta, quantas fileiras a pirâmide tem, e um nível de risco. Quanto mais fileiras em jogo e quanto maior o risco, maiores os multiplicadores possíveis — mas mais difíceis de alcançar. Assim que você solta a bolinha, ela quica para a esquerda ou para a direita em cada fileira de pinos até cair em um dos compartimentos na parte de baixo, cada um com seu próprio multiplicador. Muitos jogos de Plinko permitem soltar várias bolinhas de uma vez para um jogo mais rápido.
Cloudbet Originals Plinko
O Plinko Original da Cloudbet permite escolher 8, 10, 12 ou 16 fileiras, quatro níveis de risco (Baixo, Médio, Alto, Extremo), e 1, 5, 10, 20, 50 ou 100 bolinhas por soltura. Tem RTP de 99%. Um recurso opcional de BOOST (uma sobretaxa de 20% na sua aposta) adiciona multiplicadores especiais de bolinha de 2x, 5x e 10x, elevando o multiplicador máximo a 100.000x — limitado a um prêmio total de $1.000.000.
Para uma introdução geral ao provably fair — seeds do servidor, seeds do cliente, nonces, hashing, e como verificar um resultado — veja o sistema provably fair da Cloudbet. Abaixo está como isso se aplica especificamente ao Plinko.
Como o caminho de uma bolinha se transforma em um compartimento
O caminho de cada bolinha ao descer pela pirâmide é uma série de quiques nos pinos, e cada quique é para a esquerda ou para a direita.

Essa escolha pode ser codificada como "0" (esquerda) ou "1" (direita). Um caminho como "esquerda, esquerda, direita" se torna o código "001", e segui-lo faz a bolinha cair no Compartimento nº 2.

Aqui está o padrão-chave: um caminho cai no compartimento correspondente ao seu número de quiques para a direita, mais um. Então "100", "010" e "001" — todos com exatamente um "1" — caem no Compartimento nº 2 (1 + 1).

Isso significa que um caminho aleatório pode ser gerado e codificado em uma única etapa: pegue um número aleatório de 32 bits, leia seus primeiros N bits (onde N é o número de fileiras), e conte quantos desses bits são "1". Some um, e esse é o compartimento onde a bolinha cai. O compartimento é calculado e travado no servidor antes do início da rodada, oculto até que a rodada termine e então revelado.
Verifique o provably fair dos seus próprios jogos de Plinko
O código abaixo é fiel à implementação de Plinko na Cloudbet. Insira sua Seed do Servidor, Seed do Cliente, Nonce, número de bolinhas e número de fileiras, instale a única dependência e execute-o.
npm install js-sha3
Depois de instalar a dependência, insira seus próprios dados no bloco de código abaixo.
// 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);
})();
Observação: para revelar a Seed do Servidor de um jogo na Cloudbet, você precisa primeiro rotacionar para uma nova Seed do Servidor.
