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:

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:

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:

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:

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;

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.
