Cum funcționează Provably Fair în Mines

Iată cum se joacă Mines pe Cloudbet și cum poți verifica dacă plasarea minelor din fiecare rundă este provably fair.

Cum se joacă Mines

Fiecare rundă de Mines pe Cloudbet folosește aceeași bază provably fair — Server Seed, Client Seed, Nonce și SHA3-256/SHAKE256 — descrisă în detaliu pe pagina noastră principală despre cum funcționează Provably Fair. Această pagină acoperă ce este specific la Mines: cum devine acea secvență de numere aleatorii grila reală de mine împotriva căreia joci.

Transformarea numerelor aleatorii într-o grilă de mine

Alegi numărul de mine de plasat la începutul jocului — numim acest număr N. Grila Mines 5×5 poate fi „aplatizată” într-un singur rând de 25 de poziții, fiecare cu propriul index, începând de la 1:

Diagramă care arată cum grila cu corectitudine demonstrabilă a jocului Mines de la Cloudbet — 25 de căsuțe 5×5 etichetate de la A la Y — este aplatizată într-un singur rând de 25 de poziții numerotate pentru generarea corectă a numerelor.

Eșantionăm un număr din secvența aleatorie (generată în același mod descris pe pagina Provably Fair) pentru a determina poziția primei mine:

Exemplu practic al algoritmului cu corectitudine demonstrabilă al jocului Mines de la Cloudbet: un număr aleatoriu selectează poziția primei mine dintre toate cele 25 de căsuțe.

Dacă mai trebuie plasate mine, tragem un alt număr aleatoriu și repetăm procesul — dar acum mai sunt disponibile doar 24 de poziții rămase:

Exemplu practic care arată cum algoritmul cu corectitudine demonstrabilă al jocului Mines selectează următoarea mină dintre cele 24 de poziții rămase, după ce prima mină a fost eliminată din lista disponibilă.

Acest lucru se repetă până când toate minele sunt plasate. Grila completă este apoi reconstruită și păstrată secretă până la finalul rundei:

Grila finală cu corectitudine demonstrabilă a jocului Mines, reconstruită din rândul aplatizat după ce fiecare poziție a minelor a fost plasată aleatoriu și verificabil.

Detalii tehnice: selectarea fiecărei poziții de mină

Înainte de fiecare alegere, cunoaștem P — numărul de poziții fără mine rămase din care putem alege. P începe de la 25 și scade cu 1 de fiecare dată când o mină este plasată.

Pentru fiecare mină se extrage un număr aleatoriu de 4 octeți (32 de biți) din ieșirea SHAKE256, cuprins între 0 și 4.294.967.295. Pentru a alege corect dintre cele P poziții, aplicăm rejection sampling (vezi conceptul general explicat la Cum funcționează Provably Fair), apoi luăm rezultatul modulo P pentru a obține un index de plasare:

// Pseudo Code
const placementIndex = randomNumber % P;

Diagramă de flux a calculului cu corectitudine demonstrabilă al jocului Mines de la Cloudbet: octeții aleatorii și eșantionarea prin respingere determină indexul poziției fiecărei mine.

Dacă mai rămân mine de plasat, poziția aleasă este eliminată din listă, P scade cu 1, iar procesul se repetă.

Desfășurarea jocului

Grila finală cu minele plasate este construită și ascunsă pe server înainte să faci vreo mișcare — nu poate fi schimbată sau modificată nedetectat ulterior, datorită Commitment-ului deja transmis. Fiecare pătrat pe care îl alegi este pur și simplu verificat în raport cu această grilă ascunsă.

Cod pentru verificare independentă

Acest program este fidel implementării Mines de pe Cloudbet. Introdu Server Seed-ul, Client Seed-ul, Nonce-ul și numărul de mine, instalează singura dependență și rulează-l tu însuți pentru a reconstitui rezultatul jocului tău.

Notă: pentru a dezvălui Server Seed-ul unui joc încheiat pe Cloudbet, trebuie să fi început deja o rundă nouă (ceea ce rotește către o nouă pereche de semințe).

Dependențe:

npm install js-sha3

Codul:

// 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 myNumberOfMines   = 3;                  // ✍️YOUR_INPUT✍️: Enter your number of mines 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); // shaker256 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: Generate the mine locations
function pickUniquePositions(mineCount, totalTiles, rng) {
  const available = Array.from({ length: totalTiles }, (_, i) => i);
  let result = [];

  for (let i = 0; i < mineCount; ++i) {
    const index = sample(available.length, rng);

    result.push(available[index]);
    available.splice(index, 1);         // Remove chosen position from list of available positions
  }
  result.sort((a, b) => a - b);         // Sort the mine positions from smallest to largest
  result = result.map(num => num + 1);  // Increment positions by one so that they start with 1, 2, 3, ...
  return result;
}
// Rejection sampling for fair results
function sample(P, rng) {
  const maxAcceptable = Math.floor(0x100000000 / P) * P;

  // Rejection sampling
  let rand;
  do {
    rand = rng.next().value;
  } while (rand >= maxAcceptable);
  // Modulo operation
  const sampled_number = rand % P;

  return sampled_number;
}

// Example usage
(function main() {
  const gridSize = 25;
  let mineCount = 3;
  let nonce = 1;
  if (usingCustomData) { mineCount = myNumberOfMines; nonce = myNonce; }

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

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 24 * 2; // There are max. 24 mines, however due to rejection sampling we may need more random numbers
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const minePositions = pickUniquePositions(mineCount, gridSize, 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('💣 Mine Positions (1 = top-left square, 5 = top-right square, 25 = bottom-right square):', minePositions);
})();

Încearcă acum: testează-ți norocul la Mines

Vrei să vezi live? Joacă o rundă de Cloudbet Mines — apoi verifică-ți rezultatele cu calculatorul provably fair. Durează câteva secunde. Nu ai nevoie de cunoștințe tehnice.

Vei vedea procesul în acțiune, îți vei inspecta semințele și vei urmări cum instrumentul reconstruiește rezultatul de la zero.

Mai multe ghiduri Provably Fair pentru jocuri:

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