Cách provably fair hoạt động trong Plinko

Đây là cách chơi Plinko trên Cloudbet, và cách kiểm tra xem đường đi của mỗi quả bóng có phải là provably fair hay không.

Cách chơi Plinko

Plinko là một trò chơi may rủi, nơi bạn thả một quả bóng xuống một kim tự tháp các đinh ghim và xem nó nảy đường đến khoản thưởng. Trước khi thả, bạn đặt số tiền cược, số hàng của kim tự tháp và mức độ rủi ro. Càng nhiều hàng và mức rủi ro càng cao, hệ số nhân khả dĩ càng lớn — nhưng càng khó đạt được. Sau khi bạn thả bóng, nó nảy sang trái hoặc phải qua mỗi hàng đinh ghim cho đến khi rơi vào một trong các khay ở phía dưới, mỗi khay có hệ số nhân riêng. Nhiều phiên bản Plinko cho phép bạn thả nhiều bóng cùng lúc để chơi nhanh hơn.

Cloudbet Originals Plinko

Plinko Originals của Cloudbet cho phép bạn chọn 8, 10, 12 hoặc 16 hàng, bốn mức độ rủi ro (Thấp, Trung bình, Cao, Cực cao) và 1, 5, 10, 20, 50 hoặc 100 bóng mỗi lượt thả. Trò chơi có tỷ lệ RTP 99%. Tính năng BOOST tùy chọn (phụ phí 20% trên tiền cược) thêm các hệ số nhân bóng đặc biệt 2x, 5x và 10x, đẩy hệ số nhân tối đa lên đến 100.000x — giới hạn ở mức thắng tổng tối đa 1.000.000 $.

Để có giới thiệu chung về provably fair — server seed, client seed, nonce, hashing, và cách kiểm chứng kết quả — xem hệ thống provably fair của Cloudbet. Dưới đây là cách nó áp dụng cụ thể cho Plinko.

Đường đi của quả bóng trở thành một khay như thế nào

Đường đi của mỗi quả bóng xuống kim tự tháp là một chuỗi các lần nảy khỏi đinh ghim, và mỗi lần nảy đều là sang trái hoặc sang phải.

Sơ đồ cho thấy mỗi lần nảy trong trò chơi công bằng có thể chứng minh Plinko của Cloudbet được mã hóa thành lựa chọn sang trái hoặc sang phải.

Lựa chọn đó có thể được mã hóa thành "0" (sang trái) hoặc "1" (sang phải). Một đường đi như "trái, trái, phải" trở thành mã "001," và đi theo đó quả bóng sẽ rơi vào Khay #2.

Ví dụ cho thấy đường đi của một quả bóng Plinko được mã hóa thành chuỗi số 0 và 1 theo hệ thống công bằng có thể chứng minh của Cloudbet, rơi vào Ô số 2.

Đây là quy luật chính: một đường đi rơi vào khay tương ứng với số lần nảy sang phải của nó, cộng thêm một. Vì vậy "100," "010," và "001" — tất cả đều có đúng một "1" — rơi vào Khay #2 (1 + 1).

Sơ đồ cho thấy nhiều đường đi khác nhau của bóng Plinko theo cơ chế công bằng có thể chứng minh nhưng đều rơi vào cùng một ô theo tính toán.

Điều này có nghĩa là một đường đi ngẫu nhiên có thể được tạo ra và mã hóa trong một bước: lấy một số ngẫu nhiên 32 bit, đọc N bit đầu tiên của nó (trong đó N là số hàng), và đếm xem có bao nhiêu bit trong số đó là "1." Cộng thêm một, đó chính là khay quả bóng sẽ rơi vào. Khay này được tính toán và khóa lại trên máy chủ trước khi vòng chơi bắt đầu, giữ bí mật cho đến khi vòng chơi kết thúc rồi mới được công bố.

Kiểm chứng tính provably fair của các ván Plinko của bạn

Đoạn mã dưới đây chính xác với cách triển khai Plinko của Cloudbet. Nhập Server Seed, Client Seed, Nonce, số lượng bóng và số hàng của bạn, cài đặt thư viện duy nhất cần thiết, và chạy nó.

npm install js-sha3

Sau khi cài đặt thư viện, nhập dữ liệu của riêng bạn vào đoạn mã bên dưới.

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

Lưu ý: để công bố Server Seed của một ván trên Cloudbet, bạn cần làm mới sang một Server Seed mới trước.

Các bài giải thích khác về 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