Comment le provably fair fonctionne dans Mines
Voici comment jouer à Mines sur Cloudbet, et comment vérifier que le placement des mines de chaque manche est provably fair.
Comment jouer à Mines
Chaque manche de Mines sur Cloudbet utilise la même base provably fair — source du serveur, source du client, nonce, et SHA3-256/SHAKE256 — détaillée sur notre page centrale Comment fonctionne le provably fair. Cette page couvre ce qui est spécifique à Mines : comment cette séquence de nombres aléatoires devient la grille de mines réelle contre laquelle vous jouez.
Transformer des nombres aléatoires en grille de mines
Vous choisissez le nombre de mines à placer au début de la partie — nous appelons ce nombre N. La grille 5×5 de Mines peut être aplatie en une seule rangée de 25 positions, chacune avec son propre indice, commençant à 1 :

Nous échantillonnons un nombre à partir de la séquence aléatoire (générée de la même façon que décrite sur la page Provably Fair) pour déterminer la position de la première mine :

S'il faut placer d'autres mines, nous tirons un autre nombre aléatoire et répétons l'opération — mais il ne reste alors que 24 emplacements disponibles :

Cela se répète jusqu'à ce que toutes les mines soient placées. La grille complète est ensuite reconstituée et gardée secrète jusqu'à la fin de la manche :

Détails techniques : sélection de chaque position de mine
Avant chaque tirage, nous connaissons P — le nombre de positions sans mine restant à choisir. P commence à 25 et diminue de 1 à chaque mine placée.
Un nombre aléatoire de 4 octets (32 bits) est tiré de la sortie SHAKE256 pour chaque mine, allant de 0 à 4 294 967 295. Pour choisir équitablement parmi les P positions, nous appliquons l'échantillonnage par rejet (voir le concept général expliqué sur Comment fonctionne le provably fair), puis nous prenons le résultat modulo P pour obtenir un indice de placement :
// Pseudo Code
const placementIndex = randomNumber % P;

S'il reste des mines à placer, la position choisie est retirée de la liste, P diminue de 1, et le processus se répète.
Déroulement de la partie
La grille finale remplie de mines est construite et cachée sur le serveur avant que vous ne fassiez un mouvement — elle ne peut pas être modifiée ou altérée sans être détectée par la suite, grâce à l'Engagement déjà partagé avec vous. Chaque case que vous choisissez est simplement comparée à cette grille cachée.
Code pour une vérification indépendante
Ce programme correspond exactement à l'implémentation de Mines chez Cloudbet. Entrez votre Source du serveur, votre Source du client, votre Nonce et le nombre de mines, installez l'unique dépendance, et exécutez-le vous-même pour reconstituer le résultat de votre partie.
Remarque : pour révéler la Source du serveur d'une partie terminée sur Cloudbet, vous devez avoir déjà démarré une nouvelle manche (ce qui renouvelle la paire de sources).
Dépendances :
npm install js-sha3
Le code :
// 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);
})();
Essayez maintenant : tentez votre chance sur Mines
Vous voulez le voir en action ? Jouez une manche de Cloudbet Mines — puis vérifiez vos résultats avec la calculatrice provably fair. Cela ne prend que quelques secondes. Aucune connaissance technique n'est requise.
Vous pourrez voir le processus en action, inspecter vos sources et observer comment l'outil reconstruit votre résultat depuis zéro.
