Bạn từng nghi ngờ "Vòng quay này có gian lận không?" khi quay 10 lần đều ra cùng một ô? Câu hỏi rất chính đáng — và bài viết này sẽ "bóc tách" toàn bộ thuật toán đằng sau, giúp bạn tự kiểm chứng được tính công bằng.
🎲 Khái niệm "ngẫu nhiên" trong máy tính
Sự thật bất ngờ: máy tính KHÔNG thể sinh số thực sự ngẫu nhiên. Tất cả "random" mà chúng ta dùng đều là pseudo-random (giả ngẫu nhiên) — sinh ra từ một thuật toán deterministic, có vẻ ngẫu nhiên với con người, nhưng nếu biết "seed" ban đầu, có thể dự đoán toàn bộ chuỗi.
Có 2 loại generator chính:
- PRNG (Pseudo-Random): Nhanh, dùng Mersenne Twister hoặc Linear Congruential. Dễ bị "predict". Ví dụ:
Math.random()trong JavaScript. - CSPRNG (Cryptographically Secure): Chậm hơn nhưng không thể đoán trước ngay cả với hàng nghìn mẫu. Dùng cho mã hóa banking, password, JWT token. Ví dụ:
crypto.getRandomValues().
⚠️ Tại sao Math.random() KHÔNG đủ tốt?
JavaScript Math.random() trên Chrome dùng thuật toán xorshift128+. Một researcher đã chứng minh: chỉ cần quan sát 5 giá trị Math.random() liên tiếp, có thể tính ngược "internal state" và dự đoán mọi giá trị tiếp theo trong vài mili-giây.
Điều này KHÔNG quan trọng nếu bạn đang… random màu nền nút. Nhưng với vòng quay trao voucher 1 triệu, lì xì Tết, livestream giveaway — Math.random() là tử huyệt bảo mật.
✅ Giải pháp: crypto.getRandomValues()
Trên vongquaymayman.fit, mỗi lần bạn nhấn QUAY, code chạy đại khái như sau:
function pickRandomItem(items) {
const buf = new Uint32Array(1);
crypto.getRandomValues(buf); // CSPRNG, không đoán được
const idx = buf[0] % items.length; // Modulo lấy index
return items[idx];
}crypto.getRandomValues() được hỗ trợ bởi tất cả browser hiện đại (Chrome, Safari, Firefox, Edge từ 2014). Nó sử dụng entropy từ:
- Tiếng ồn nhiệt CPU
- Timing của các interrupt phần cứng
- Mouse movement, keyboard timing
- Hardware RNG (Intel RDRAND) nếu có
🔬 Cách bạn TỰ kiểm tra
Mở Developer Tools (F12) trên trang chủ → tab Console, paste đoạn này:
// Sinh 10000 số random với crypto, đếm phân bố vào 10 ô
const counts = new Array(10).fill(0);
const buf = new Uint32Array(10000);
crypto.getRandomValues(buf);
buf.forEach(n => counts[n % 10]++);
console.table(counts);Kết quả: 10 ô sẽ có giá trị xấp xỉ 1000 ± 50 — phân bố đều hoàn hảo. Bạn có thể chạy 100 lần đều cho kết quả tương tự ⇒ thuật toán không bias bất kỳ ô nào.
🐛 Cảnh báo: Modulo Bias
Một lỗi thường gặp: nếu danh sách có số mục không chia hết cho 2^32, modulo sẽ tạo bias rất nhỏ (khoảng 0.0000001%). Với 10 mục thì hoàn toàn không cảm nhận được, nhưng nếu bạn làm cryptographic giveaway lớn, code chuẩn nên dùng rejection sampling:
function unbiasedRandomIndex(n) {
const max = Math.floor(0xFFFFFFFF / n) * n;
const buf = new Uint32Array(1);
do {
crypto.getRandomValues(buf);
} while (buf[0] >= max);
return buf[0] % n;
}❓ "Tại sao 10 lần em quay đều ra Phở?"
Đây là Gambler's Fallacy — não người luôn cảm thấy có "pattern" trong dãy ngẫu nhiên. Thực tế, xác suất 10 lần liên tiếp ra cùng 1 ô (trong 10 ô) là (1/10)^10 ≈ 10^-10, tức 1 trong 10 tỷ lần. Bạn có thể thấy trong vài trăm lần quay đời mình → cảm giác "bất công" sinh ra.
Để giảm hiệu ứng này, dùng tính năng "Tự xóa người trúng" trong tab Cài đặt — sau khi trúng, ô đó biến mất, đảm bảo các ô khác sẽ trúng dần.
🛡️ Cam kết minh bạch của VQMM
- Code 100% client-side — không có server can thiệp. Bạn có thể tắt mạng vẫn quay được.
- Không có "weighted slice" ngầm — mọi ô đều xác suất bằng nhau.
- Dùng crypto.getRandomValues() — kiểm chứng được trong DevTools.
- Mã nguồn công khai — xem trên vongquaymayman.fit tab Network → app.js.
👉 Đã hiểu rõ thuật toán, hãy yên tâm tạo vòng quay của bạn. Đọc thêm: Lịch sử vòng quay 2500 năm · Vòng quay trong livestream · Mẹo tùy chỉnh vòng quay đẹp.