So funktioniert Provably Fair bei Mines
So spielst du Mines auf Cloudbet, und so überprüfst du, dass die Minenplatzierung jeder Runde provably fair ist.
So spielst du Mines
Jede Mines-Runde auf Cloudbet basiert auf demselben Provably-Fair-Fundament — Server-Seed, Client-Seed, Nonce und SHA3-256/SHAKE256 —, das ausführlich auf unserer Hub-Seite „Wie Provably Fair funktioniert" beschrieben wird. Diese Seite behandelt, was bei Mines spezifisch ist: wie diese Zufallszahlenfolge zum tatsächlichen Minenraster wird, gegen das du spielst.
Zufallszahlen werden zu einem Minenraster
Du wählst zu Beginn des Spiels die Anzahl der zu platzierenden Minen — wir nennen diese Zahl N. Das 5×5-Mines-Raster lässt sich zu einer einzigen Reihe mit 25 Positionen abflachen, jede mit ihrem eigenen Index, beginnend bei 1:

Wir ziehen eine Zahl aus der Zufallsfolge (auf dieselbe Weise generiert, wie auf der Provably-Fair-Seite beschrieben), um die Position der ersten Mine zu bestimmen:

Müssen weitere Minen platziert werden, ziehen wir eine weitere Zufallszahl und wiederholen den Vorgang — nur stehen jetzt noch 24 verbleibende Felder zur Auswahl:

Das wiederholt sich, bis jede Mine platziert ist. Das vollständige Raster wird dann rekonstruiert und bis zum Ende der Runde geheim gehalten:

Technische Details: Auswahl jeder Minenposition
Vor jeder Ziehung kennen wir P — die Anzahl der minenfreien Positionen, die noch zur Auswahl stehen. P startet bei 25 und sinkt bei jeder platzierten Mine um 1.
Für jede Mine wird aus dem SHAKE256-Output eine zufällige 4-Byte-(32-Bit-)Zahl gezogen, im Bereich von 0 bis 4.294.967.295. Um fair unter P Positionen auszuwählen, wenden wir Rejection Sampling an (das allgemeine Konzept wird auf Wie Provably Fair funktioniert erklärt), und nehmen dann das Ergebnis modulo P, um einen Platzierungsindex zu erhalten:
// Pseudo Code
const placementIndex = randomNumber % P;

Sind weitere Minen übrig, wird die gewählte Position aus der Liste entfernt, P sinkt um 1, und der Vorgang wiederholt sich.
Spielablauf
Das fertige, minenbestückte Raster wird auf dem Server erstellt und verborgen, bevor du deinen ersten Zug machst — es kann danach nicht unbemerkt geändert oder verändert werden, dank des Commitments, das dir bereits mitgeteilt wurde. Jedes Feld, das du auswählst, wird einfach mit diesem verborgenen Raster abgeglichen.
Code zur unabhängigen Verifizierung
Dieses Programm entspricht exakt der Mines-Implementierung auf Cloudbet. Gib deinen Server-Seed, Client-Seed, Nonce und die Anzahl der Minen ein, installiere die eine Abhängigkeit, und führe es selbst aus, um das Ergebnis deines Spiels zu rekonstruieren.
Hinweis: Um den Server-Seed einer abgeschlossenen Runde auf Cloudbet offenzulegen, musst du bereits eine neue Runde gestartet haben (wodurch zu einem neuen Seed-Paar rotiert wird).
Abhängigkeiten:
npm install js-sha3
Der 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);
})();
Jetzt ausprobieren: Teste dein Glück bei Mines
Möchtest du es live erleben? Spiele eine Runde Cloudbet Mines — und überprüfe anschließend deine Ergebnisse mit dem Provably-Fair-Rechner. Es dauert nur wenige Sekunden. Kein technisches Know-how erforderlich.
Du wirst den Prozess in Aktion sehen, deine Seeds prüfen und beobachten, wie das Tool dein Ergebnis von Grund auf rekonstruiert.
