Cómo funciona provably fair en Mines
Así se juega a Mines en Cloudbet, y así puedes comprobar que la colocación de minas de cada ronda es provably fair.
Cómo jugar a Mines
Cada ronda de Mines en Cloudbet utiliza la misma base provably fair —semilla del servidor, semilla del cliente, nonce y SHA3-256/SHAKE256— explicada en detalle en nuestra página principal de Cómo funciona Provably Fair. Esta página cubre lo específico de Mines: cómo esa secuencia de números aleatorios se convierte en la cuadrícula de minas real contra la que juegas.
Convertir números aleatorios en una cuadrícula de minas
Eliges el número de minas a colocar al comienzo de la partida; llamamos a este número N. La cuadrícula de Mines de 5×5 se puede desplegar en una sola fila de 25 posiciones, cada una con su propio índice, comenzando en 1:

Tomamos un número de la secuencia aleatoria (generada de la misma forma descrita en la página de Provably Fair) para determinar la posición de la primera mina:

Si hay que colocar más minas, extraemos otro número aleatorio y repetimos el proceso, pero ahora solo quedan 24 espacios disponibles entre los que elegir:

Esto se repite hasta que se coloca cada mina. La cuadrícula completa se reconstruye y se mantiene en secreto hasta que termina la ronda:

Detalles técnicos: selección de cada posición de mina
Antes de cada elección, conocemos P: el número de posiciones libres de minas que quedan por elegir. P comienza en 25 y disminuye en 1 cada vez que se coloca una mina.
Se extrae un número aleatorio de 4 bytes (32 bits) de la salida de SHAKE256 para cada mina, en un rango de 0 a 4 294 967 295. Para elegir de forma justa entre las P posiciones, aplicamos el muestreo por rechazo (consulta el concepto general explicado en Cómo funciona Provably Fair), y luego calculamos el resultado módulo P para obtener un índice de colocación:
// Pseudo Code
const placementIndex = randomNumber % P;

Si quedan más minas por colocar, la posición elegida se elimina de la lista, P disminuye en 1 y el proceso se repite.
Jugabilidad
La cuadrícula final con las minas colocadas se genera y se oculta en el servidor antes de que hagas tu primer movimiento; no se puede cambiar ni alterar sin ser detectado después, gracias al Compromiso que ya se te compartió. Cada casilla que eliges simplemente se comprueba contra esta cuadrícula oculta.
Código para verificación independiente
Este programa es fiel a la implementación de Mines en Cloudbet. Introduce tu Semilla del servidor, Semilla del cliente, Nonce y número de minas, instala la única dependencia y ejécutalo tú mismo para reconstruir el resultado de tu partida.
Nota: para revelar la Semilla del servidor de una partida terminada en Cloudbet, primero debes haber comenzado una nueva ronda (lo que rota a un nuevo par de semillas).
Dependencias:
npm install js-sha3
El código:
// 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);
})();
Pruébalo ahora: pon a prueba tu suerte en Mines
¿Quieres verlo en acción? Juega una ronda de Cloudbet Mines y luego verifica tus resultados con la calculadora provably fair. Toma solo unos segundos. No se necesitan conocimientos técnicos.
Podrás ver el proceso en acción, inspeccionar tus semillas y observar cómo la herramienta reconstruye tu resultado desde cero.
