Cách provably fair hoạt động trong Mines
Đây là cách chơi Mines trên Cloudbet, và cách kiểm tra xem vị trí mìn của mỗi ván có phải là provably fair hay không.
Cách chơi Mines
Mỗi ván Mines trên Cloudbet đều sử dụng nền tảng provably fair giống nhau — server seed, client seed, nonce và SHA3-256/SHAKE256 — được trình bày chi tiết trên trang trung tâm Cách Provably Fair Hoạt Động của chúng tôi. Trang này trình bày những gì đặc thù của Mines: chuỗi số ngẫu nhiên đó trở thành lưới mìn thực tế mà bạn chơi cùng như thế nào.
Biến số ngẫu nhiên thành lưới mìn
Bạn chọn số lượng mìn cần đặt khi bắt đầu ván chơi — chúng ta gọi số này là N. Lưới Mines 5×5 có thể được trải phẳng thành một hàng gồm 25 vị trí, mỗi vị trí có chỉ số riêng, bắt đầu từ 1:

Chúng tôi lấy mẫu một số từ chuỗi ngẫu nhiên (được tạo theo cách giống như mô tả trên trang Provably Fair) để xác định vị trí của quả mìn đầu tiên:

Nếu cần đặt thêm mìn, chúng tôi lấy thêm một số ngẫu nhiên khác và lặp lại — nhưng lúc này chỉ còn 24 ô trống để chọn:

Việc này lặp lại cho đến khi tất cả các mìn được đặt xong. Toàn bộ lưới sau đó được xây dựng lại và giữ bí mật cho đến khi ván chơi kết thúc:

Chi tiết kỹ thuật: chọn từng vị trí mìn
Trước mỗi lượt chọn, chúng ta biết P — số vị trí không có mìn còn lại để chọn. P bắt đầu ở 25 và giảm đi 1 mỗi khi một quả mìn được đặt.
Một số ngẫu nhiên 4 byte (32 bit) được lấy từ đầu ra của SHAKE256 cho mỗi quả mìn, có giá trị từ 0 đến 4.294.967.295. Để chọn công bằng trong số P vị trí, chúng tôi áp dụng rejection sampling (xem khái niệm chung được giải thích trên Cách Provably Fair Hoạt Động), sau đó lấy kết quả modulo P để có chỉ số vị trí:
// Pseudo Code
const placementIndex = randomNumber % P;

Nếu còn mìn cần đặt, vị trí đã chọn sẽ được loại khỏi danh sách, P giảm đi 1, và quá trình lặp lại.
Cách chơi
Lưới mìn hoàn chỉnh được xây dựng và giấu kín trên máy chủ trước khi bạn thực hiện lượt đi đầu tiên — nó không thể bị thay đổi hay can thiệp mà không bị phát hiện sau đó, nhờ vào Commitment đã được chia sẻ với bạn từ trước. Mỗi ô bạn chọn chỉ đơn giản là được đối chiếu với lưới ẩn này.
Mã nguồn để kiểm chứng độc lập
Chương trình này chính xác với cách triển khai Mines trên Cloudbet. Nhập Server Seed, Client Seed, Nonce và số lượng mìn của bạn, cài đặt thư viện duy nhất cần thiết, và tự chạy để tái tạo lại kết quả ván chơi của bạn.
Lưu ý: để công bố Server Seed của một ván đã hoàn thành trên Cloudbet, bạn cần đã bắt đầu một ván mới (thao tác này sẽ làm mới sang một cặp seed mới).
Các thư viện cần cài đặt:
npm install js-sha3
Mã nguồn:
// 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);
})();
Thử ngay: kiểm tra vận may của bạn với Mines
Muốn tự mình trải nghiệm? Chơi một ván Cloudbet Mines — sau đó kiểm chứng kết quả bằng công cụ provably fair. Chỉ mất vài giây. Không cần kiến thức kỹ thuật.
Bạn sẽ được tận mắt xem quy trình diễn ra, kiểm tra các seed của mình và quan sát cách công cụ tái tạo lại kết quả từ đầu.
