Mines'ta provably fair nasıl çalışır
Cloudbet'te Mines'ı nasıl oynayacağınızı ve her turdaki mayın yerleşimlerinin provably fair olduğunu nasıl kontrol edeceğinizi burada bulabilirsiniz.
Mines nasıl oynanır
Cloudbet'teki her Mines turu, aynı provably fair temelini kullanır — sunucu tohumu, müşteri tohumu, nonce ve SHA3-256/SHAKE256 — bunlar Provably Fair Nasıl Çalışır ana sayfamızda ayrıntılı olarak ele alınmıştır. Bu sayfa Mines'a özgü olanı ele alır: bu rastgele sayı dizisinin, karşı oynadığınız gerçek mayın ızgarasına nasıl dönüştüğünü.
Rastgele sayıları bir mayın ızgarasına dönüştürmek
Oyunun başında yerleştirilecek mayın sayısını seçersiniz — bu sayıya N diyoruz. 5×5'lik Mines ızgarası, her birinin 1'den başlayan kendi indeksine sahip olduğu tek bir 25 pozisyonluk satıra düzleştirilebilir:

İlk mayının konumunu belirlemek için rastgele diziden (Provably Fair sayfasında açıklandığı gibi üretilen) bir sayı örnekleriz:

Daha fazla mayın yerleştirilmesi gerekiyorsa, başka bir rastgele sayı çeker ve tekrarlarız — ama artık seçilecek yalnızca 24 kalan alan vardır:

Bu, her mayın yerleştirilene kadar tekrarlanır. Tam ızgara daha sonra yeniden oluşturulur ve tur sona erene kadar gizli tutulur:

Teknik detaylar: her mayın pozisyonunun seçilmesi
Her seçimden önce P'yi biliriz — seçim yapılabilecek, mayınsız kalan pozisyon sayısını. P, 25'ten başlar ve her mayın yerleştirildiğinde 1 azalır.
Her mayın için SHAKE256 çıktısından rastgele 4 baytlık (32 bit) bir sayı çekilir; bu sayı 0 ile 4.294.967.295 arasında değişir. P pozisyon arasından adil bir seçim yapmak için reddetme örneklemesi uygularız (Provably Fair Nasıl Çalışır sayfasında açıklanan genel kavrama bakın), ardından bir yerleşim indeksi elde etmek için sonucun P'ye göre modunu alırız:
// Pseudo Code
const placementIndex = randomNumber % P;

Daha fazla mayın kalıyorsa, seçilen pozisyon listeden çıkarılır, P bir azalır ve süreç tekrarlanır.
Oynanış
Siz herhangi bir hamle yapmadan önce, mayınlarla dolu son ızgara oluşturulur ve sunucuda gizlenir — sizinle zaten paylaşılmış olan Taahhüt sayesinde, sonrasında fark edilmeden değiştirilemez veya değişiklik yapılamaz. Seçtiğiniz her kare, yalnızca bu gizli ızgaraya göre kontrol edilir.
Bağımsız doğrulama için kod
Bu program, Cloudbet'teki Mines uygulamasıyla birebir aynıdır. Sunucu Tohumunuzu, Müşteri Tohumunuzu, Nonce'unuzu ve mayın sayınızı girin, tek bağımlılığı kurun ve oyununuzun sonucunu yeniden oluşturmak için kendiniz çalıştırın.
Not: Cloudbet'te tamamlanmış bir oyunun Sunucu Tohumunu açığa çıkarmak için, zaten yeni bir tur başlatmış olmanız gerekir (bu, yeni bir tohum çiftine geçiş yapar).
Bağımlılıklar:
npm install js-sha3
Kod:
// 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);
})();
Şimdi deneyin: Mines'ta şansınızı deneyin
Canlı olarak görmek ister misiniz? Bir tur Cloudbet Mines oynayın — ardından sonuçlarınızı provably fair hesaplayıcısıyla doğrulayın. Sadece birkaç saniye sürer. Teknik bilgi gerekmez.
Sürecin işleyişini canlı görebilir, tohumlarınızı inceleyebilir ve aracın sonucunuzu sıfırdan nasıl yeniden oluşturduğunu izleyebilirsiniz.
