Cum funcționează Provably Fair în Limbo

Iată cum se joacă Limbo pe Cloudbet și cum poți verifica dacă fiecare multiplicator este provably fair.

Cum se joacă Limbo

Limbo este un joc de multiplicare rapid și simplu: înainte de fiecare rundă, stabilești un multiplicator țintă — cât de mare vrei să fie câștigul tău. Plasează pariul, apoi este generat un multiplicator rezultat. Dacă acesta atinge sau depășește ținta ta, câștigi multiplicatorul țintă înmulțit cu paria ta. Dacă este sub țintă, pierzi runda.

Cu cât stabilești un multiplicator țintă mai mare, cu atât câștigul potențial este mai mare — dar cu atât este mai puțin probabil ca rezultatul să îl atingă. Îți poți stabili ținta cât de mică sau cât de mare dorești înainte de fiecare pariu.

Cloudbet Originals Limbo

Limbo-ul propriu al Cloudbet Originals are un RTP de 99%, un multiplicator maxim de x1.000.000 și acceptă pariuri de la 0,01 $ la 20.000 $.

Numerele aleatorii ale Limbo sunt generate în același mod verificabil ca orice alt joc Cloudbet Original — vezi sistemul provably fair al Cloudbet pentru modul în care Server Seed-ul, Client Seed-ul și Nonce-ul se combină într-un Round Signature și apoi într-un flux de numere aleatorii prin SHAKE256. Această pagină acoperă ce este specific la Limbo: cum devine acel număr aleatoriu multiplicatorul împotriva căruia concurezi.

Cum este calculat multiplicatorul

Limbo pornește de la un număr aleatoriu de 32 de biți — generat de SHAKE256, distribuit uniform, ceea ce înseamnă că fiecare valoare între 0 și 2³²-1 este la fel de probabilă.

Totuși, aceasta nu este forma de care avem nevoie. Un multiplicator ar trebui să atingă valori mici mult mai des decât valori uriașe — șansele unui 1,01x ar trebui să fie mult mai mari decât șansele unui 100.000x. Așa că numărul aleatoriu este remodelat, pas cu pas, în acea distribuție:

  1. Se folosește rejection sampling, dar ușor — doar pentru a evita extragerea celui mai mare număr posibil, deoarece numărul urmează să fie incrementat cu unu.
  2. Numărul este incrementat cu unu (astfel încât nu este niciodată zero, ceea ce contează pentru împărțirea de la pasul 4).
  3. Este redus la o zecimală între 0 și 1, prin împărțirea la valoarea sa maximă posibilă.
  4. 0,99 (RTP-ul Limbo) este împărțit la acea zecimală — acesta este pasul care transformă o distribuție uniformă într-una în care multiplicatorii mici sunt exponențial mai frecvenți decât cei mari.
  5. Rezultatul este rotunjit în jos la două zecimale.
  6. În final, este „tăiat” la intervalul valid al Limbo: orice valoare sub 1 devine 1 (paria minimă este 1,01, astfel încât rezultatele sub 1 sunt toate pur și simplu o pierdere), iar orice valoare peste 1.000.000 devine 1.000.000 (câștigul maxim al Limbo).

Diagramă de flux care arată cum sistemul cu corectitudine demonstrabilă al jocului Limbo de la Cloudbet transformă pas cu pas un număr aleatoriu de 32 de biți în multiplicatorul final al rundei.

Odată generat, multiplicatorul este fixat și ascuns — nu poate fi schimbat după plasarea pariului tău. Încasarea (sau nu) înainte de a fi atins este ceea ce decide runda.

Verifică-ți propriile jocuri Limbo

Codul de mai jos este fidel implementării Limbo de pe Cloudbet. Introdu Server Seed-ul, Client Seed-ul și Nonce-ul pentru a-ți reproduce propriul rezultat. Apropo: pentru a dezvălui Server Seed-ul unui joc pe Cloudbet, trebuie să începi să folosești un alt Server Seed.

Dependențe:

npm install js-sha3

Cod:

// 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           = 0;                  // ✍️YOUR_INPUT✍️: Enter your Nonce 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 of 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: Draw from the random number sequence to calculate the game result
function getGameResult(rng) {
  let randomInt32 = rng.next().value;               // 'randomInt32' ranges from 0 and 4294967295
  while (randomInt32 == 0xFFFFFFFF) { randomInt32 = rng.next().value; } // This can basically be ignored

  const fraction = (randomInt32 + 1) / 0xFFFFFFFF;  // 'fraction' ranges from 0 to 1
  const inverse = 0.99 / fraction;                  // 'inverse' ranges from 0 to (basically) infinity
  let crashPoint = Math.floor(inverse * 100) / 100; // Cut off everything past the second decimal place
  // We force the crashpoint to range from 1 to 1,000,000
  if (crashPoint < 1.0) {
    crashPoint = 1.0;
  }
  if (crashPoint > 1000000) {
    crashPoint = 1000000;
  }

  return crashPoint;
}

// Example usage
(function main() {
  let nonce = 1;
  if (usingCustomData) { nonce = myNonce; }

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 1 * 2; // We need 1 random number, but there is an improbable case we'll need two
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const value = getGameResult(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('⛓️‍💥 Break Point:', value);
})();

Mai multe ghiduri Provably Fair pentru jocuri:

  1. Provably Fair la Mines
  2. Provably Fair la Pump
  3. Provably Fair la Plinko
  4. Provably Fair la Dice
  5. Provably Fair la Dice: Golden Ticket
  6. Provably Fair la Roulette
  7. Provably Fair la Keno