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:

Diagrama que muestra cómo la cuadrícula de equidad demostrable de Mines de Cloudbet —5×5 casillas etiquetadas de la A a la Y— se aplana en una sola fila de 25 posiciones numeradas para la generación justa de números.

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:

Ejemplo práctico del algoritmo de equidad demostrable de Mines de Cloudbet: un número aleatorio selecciona la posición de la primera mina entre las 25 casillas.

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:

Ejemplo práctico que muestra cómo el algoritmo de equidad demostrable de Mines selecciona la siguiente mina entre las 24 posiciones restantes tras eliminar la primera mina del conjunto.

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:

La cuadrícula final de equidad demostrable de Mines, reconstruida a partir de la fila aplanada una vez que todas las posiciones de las minas se han colocado de forma aleatoria y verificable.

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;

Diagrama de flujo del cálculo de equidad demostrable de Mines de Cloudbet: los bytes aleatorios y el muestreo por rechazo determinan el índice de la posición de cada mina.

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.

Más explicaciones de provably fair por juego:

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