使用 window.crypto.getRandomValues 在 JavaScript 中洗牌扑克牌

2024-03-10

一副扑克牌有 52 张牌,因此52!或大致2^226可能的排列。

现在我想完美地洗牌这样一副牌,具有真正随机的结果和均匀的分布,这样你就可以达到这些可能的排列中的每一个,并且每个排列出现的可能性相同。

为什么这实际上是必要的?

对于游戏来说,也许你并不真正需要完美的随机性,除非有钱可赢。除此之外,人类可能甚至不会察觉到随机性的“差异”。

但如果我没记错的话,如果你使用流行编程语言中常见的混洗函数和 RNG 组件,你通常会得到不超过 32 位的熵,并且2^32状态。因此,你将永远无法达到所有52!洗牌时牌组可能的排列,但仅限于......

0.000000000000000000000000000000000000000000000000000000005324900157%

...可能的排列。这意味着很多可能的游戏could理论上的演奏或模拟永远不会在实践中真正看到。

顺便说一句,如果您不在每次洗牌前重置为默认顺序,而是从上次洗牌的顺序开始,或者在玩完游戏后保持“混乱”并从那里开始洗牌,您可以进一步改善结果。

要求:

因此,为了实现上述目的,据我所知,需要以下三个组件:

  1. 一种良好的洗牌算法,可确保均匀分布。
  2. 具有至少 226 位内部状态的适当 RNG。由于我们使用的是确定性机器,因此我们只能得到 PRNG,也许这应该是 CSPRNG。
  3. 至少具有 226 位熵的随机种子。

解决方案:

现在这可以实现吗?我们有什么?

  1. 费舍尔-耶茨洗牌 https://bost.ocks.org/mike/shuffle/据我所知,会好的。
  2. The 异或移位7 https://github.com/davidbau/xsrand/blob/master/xorshift7.jsRNG 具有超过所需的 226 位内部状态,应该足够了。
  3. Using window.crypto.getRandomValues我们可以生成所需的 226 位熵来用作种子。如果这还不够,我们可以添加更多的熵从其他来源 https://github.com/keybase/more-entropy.

问题:

上述解决方案(以及要求)是否正确?那么在实践中如何在 JavaScript 中使用这些解决方案来实现洗牌呢?如何将这三个组件组合成一个可行的解决方案?

我想我必须更换Math.random在调用 xorshift7 的 Fisher-Yates 洗牌示例中。但是 RNG 输出的值是[0, 1)浮动范围,我需要[1, n]取而代之的是整数范围。当缩放该范围时,我不想失去均匀分布。此外,我想要大约 226 位的随机性。如果我的 RNG 只输出一个Number,难道随机性不是有效地减少到 2^53(或 2^64)位,因为没有更多的输出可能性吗?

为了生成 RNG 的种子,我想做这样的事情:

var randomBytes = generateRandomBytes(226);

function generateRandomBytes(n) {
    var data = new Uint8Array(
        Math.ceil(n / 8)
    );
    window.crypto.getRandomValues(data);

    return data;
}

它是否正确?我不知道我该如何通过randomBytes以任何方式将 RNG 作为种子,我不知道如何修改它以接受这一点。


这是我编写的一个函数,它使用基于来自以下位置的随机字节的 Fisher-Yates 改组window.crypto。由于 Fisher-Yates 要求在不同范围内生成随机数,因此它以 6 位掩码开始(mask=0x3f),但随着所需范围变小(即,每当i是 2 的幂)。

function shuffledeck() {
    var cards = Array("A♣️","2♣️","3♣️","4♣️","5♣️","6♣️","7♣️","8♣️","9♣️","10♣️","J♣️","Q♣️","K♣️",
                      "A♦️","2♦️","3♦️","4♦️","5♦️","6♦️","7♦️","8♦️","9♦️","10♦️","J♦️","Q♦️","K♦️",
                      "A♥️","2♥️","3♥️","4♥️","5♥️","6♥️","7♥️","8♥️","9♥️","10♥️","J♥️","Q♥️","K♥️",
                      "A♠️","2♠️","3♠️","4♠️","5♠️","6♠️","7♠️","8♠️","9♠️","10♠️","J♠️","Q♠️","K♠️");
    var rndbytes = new Uint8Array(100);
    var i, j, r=100, tmp, mask=0x3f;

    /* Fisher-Yates shuffle, using uniform random values from window.crypto */
    for (i=51; i>0; i--) {
        if ((i & (i+1)) == 0) mask >>= 1;
        do {
            /* Fetch random values in 100-byte blocks. (We probably only need to do */
            /* this once.) The `mask` variable extracts the required number of bits */
            /* for efficient discarding of random numbers that are too large. */
            if (r == 100) {
                window.crypto.getRandomValues(rndbytes);
                r = 0;
            }
            j = rndbytes[r++] & mask;
        } while (j > i);

        /* Swap cards[i] and cards[j] */
        tmp = cards[i];
        cards[i] = cards[j];
        cards[j] = tmp;
    }
    return cards;
}

评估window.crypto图书馆确实值得提出自己的问题,但无论如何......

提供的伪随机流window.crypto.getRandomValues()对于任何目的都应该足够随机,但在不同的浏览器中由不同的机制生成。根据一个2013年调查 https://www.cryptolux.org/images/7/7f/RNG_Survey.pdf:

  • Firefox(第 21 节以上)使用NIST SP 800-90 http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-90a.pdf带有 440 位种子。注意:该标准于 2015 年更新,删除了(可能有后门)Dual_EC_DRBG椭圆曲线PRNG算法。

  • IE浏览器(v. 11+) 使用以下支持的算法之一BCryptGen随机 https://msdn.microsoft.com/en-us/library/windows/desktop/aa375458(v=vs.85).aspx(种子长度=?)

  • Safari、Chrome 和 Opera use an ARC4 https://linux.die.net/man/3/arc4random具有 1024 位种子的流密码。


Edit:

一个更干净的解决方案是添加一个通用的shuffle()Javascript 数组原型的方法:

// Add Fisher-Yates shuffle method to Javascript's Array type, using
// window.crypto.getRandomValues as a source of randomness.

if (Uint8Array && window.crypto && window.crypto.getRandomValues) {
    Array.prototype.shuffle = function() {
        var n = this.length;
    
        // If array has <2 items, there is nothing to do
        if (n < 2) return this;
        // Reject arrays with >= 2**31 items
        if (n > 0x7fffffff) throw "ArrayTooLong";
    
        var i, j, r=n*2, tmp, mask;
        // Fetch (2*length) random values
        var rnd_words = new Uint32Array(r);
        // Create a mask to filter these values
        for (i=n, mask=0; i; i>>=1) mask = (mask << 1) | 1;
    
        // Perform Fisher-Yates shuffle
        for (i=n-1; i>0; i--) {
            if ((i & (i+1)) == 0) mask >>= 1;
            do {
                if (r == n*2) {
                    // Refresh random values if all used up
                    window.crypto.getRandomValues(rnd_words);
                    r = 0;
                }
                j = rnd_words[r++] & mask;
            } while (j > i);
            tmp = this[i];
            this[i] = this[j];
            this[j] = tmp;
        }
        return this;
    }
} else throw "Unsupported";

// Example:
deck = [ "A♣️","2♣️","3♣️","4♣️","5♣️","6♣️","7♣️","8♣️","9♣️","10♣️","J♣️","Q♣️","K♣️",
         "A♦️","2♦️","3♦️","4♦️","5♦️","6♦️","7♦️","8♦️","9♦️","10♦️","J♦️","Q♦️","K♦️",
         "A♥️","2♥️","3♥️","4♥️","5♥️","6♥️","7♥️","8♥️","9♥️","10♥️","J♥️","Q♥️","K♥️",
         "A♠️","2♠️","3♠️","4♠️","5♠️","6♠️","7♠️","8♠️","9♠️","10♠️","J♠️","Q♠️","K♠️"];

deck.shuffle();
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 window.crypto.getRandomValues 在 JavaScript 中洗牌扑克牌 的相关文章

随机推荐

  • 在 Perl 中将 UTF8 字符串转换为 ASCII

    我已经尝试了 Google 和 StackOverflow 推荐的 我能找到的 所有内容 包括使用 Encode 我的代码可以工作 但它只使用 UTF8 并且我收到宽字符警告 我知道如何解决这些警告 但我没有将 UTF8 用于其他任何用途
  • 隐藏除前 4 个元素之外的所有元素

    有无穷无尽的元素 我想要做的是隐藏除前 4 个元素之外的所有元素 带有 not 选择器 我想让点击全部可见 这可以用CSS实现吗 ul li li li li li li li li li li must hide li li must h
  • 使用 Base64 图像的 HTML 到 PDF 会抛出 FileNotFoundException

    我正在使用 itextpdf 5 0 6 jar Java 8 当我尝试使用 base64 图像标签导出 html 代码时 出现文件未找到异常 如果我删除图像标签 一切都会很好 我发现了一些关于覆盖图像标签处理器的解决方案 但大多数都是旧的
  • 关闭 Selenium IDE 中新打开的选项卡或窗口

    在 Windows7 和 Firefox 中使用 Selenium IDE 自动单击链接可能会生成新选项卡或新窗口 close 关闭原始窗口或选项卡 而不是新窗口或选项卡 也许如果我有新创建的 ID 我可以选择它然后关闭它 但我不知道如何自
  • 对两个对象数组的数据求和

    我有两个对象数组 我想对具有相同键 在本例中为 id 的对象求和 如果没有匹配键 则只需创建一个新的 如果我是 我很抱歉没有解释清楚 我对 JavaScript Array Object 很陌生 var dataOne id 1 total
  • 将另一个声音添加到 .NET Speech 中

    如何将其他声音添加到 NET Speech 中 我想使用捷克语语音 我找到了一些 sis files Eliska22k sis但我不知道如何使用它 SpeechSynthesizer synth new SpeechSynthesizer
  • Common Lisp 案例和引用元素

    我正在用 CL 编写一个地下城爬行游戏 但在处理案例表单时遇到了问题 两件事情 Common Lisp 抱怨Duplicate keyform QUOTE in CASE statement make instance cl rogue t
  • 如何在 python-flask 中添加自定义字体?

    我尝试过使用 fontface css 样式 但字体没有渲染 还有另一种方法可以使用 python flask 来做到这一点吗 p style font family trial font weight bold Hello p 上面是我的
  • 使 Div 向上滚动时返回到其原始位置

    当您向下和向上滚动时 我有一个带动画的 div 问题是 当我非常快地向上和向下滚动而不让 div 完成其动画时 div 会逐渐从上部屏幕中消失 如果我删除 animate 函数中的 stop 并快速上下滚动 div 会继续执行此操作一段时间
  • 如何在Android Studio上实时查看Sqlite数据库中插入的数据

    你能帮我解决这个问题吗 我正在将值插入到我的 Sqlite 数据库中 如何检查或查看插入的数据 有没有任何工具或其他技术来显示数据 如果您想显示数据Log尝试下面的代码 for Contact cn contacts String log
  • .NET Standard 2.0 无法在 .NET Framework 2.0 中引用

    我收到一个错误 c xxxx csproj 目标为 NETStandard Version v2 0 它无法被面向 NETFramework Version v2 0 的项目引用 WindowsFormsApp1 如何解决 遗憾的是 您无法
  • 用核心运动计算倾斜角

    我的申请有一个记录会话 当用户开始记录会话时 我开始从设备的 CMMotionManager 对象收集数据并将它们存储在 CoreData 上以供稍后处理和呈现 我正在收集的数据包括 GPS 数据 加速度计数据和陀螺仪数据 数据的频率为10
  • 将 webkit 滚动条样式应用于指定元素

    我对以双冒号为前缀的伪元素很陌生 我看到一篇博客文章讨论使用一些仅适用于 webkit 的 css 来设置滚动条的样式 伪元素 CSS 可以应用于单个元素吗 This works by applying style to all scrol
  • 在节点主管中,如何监视目录中的所有内容是否发生更改?

    https github com isaacs node supervisor https github com isaacs node supervisor 我想查看 api 目录及其所有子目录内的所有内容 递归地 我怎样才能做到这一点
  • CouchDB 入门

    我已经在我的 Linux 云服务器上安装了 CouchDB 并且我正在尝试访问 Futon 欢迎屏幕 O Reilly 书中说要转到 127 0 0 1 portnum 但我不在本地主机上工作 它是我的远程服务器 所以我应该能够使用 xxx
  • 使用指定的初始化器初始化数组时出现奇怪的值

    当我初始化下面的数组时 所有输出看起来都正常 除了values 3 因为某些原因values 3 初始化为values 0 values 5 正在输出一个非常大的数字 我的猜测是我正在尝试分配values 0 values 5 在它们被正确
  • Android 中如何获取联系人信息

    我正在开发一个在android平台上编写短信的应用程序 为此 我需要获取 联系人 最近联系人 和 组 请告诉我任何教程或如何执行此操作的代码 当我们单击这三个按钮中的任何一个时 应该会出现联系人以及用于选择多个联系人的复选框 谢谢 这是以编
  • Three.js 如何从向量和常量得到平面?

    在 Three js 中 构造函数数学平面 http threejs org docs api math Plane html需要 2 个输入 法线 Vector3 定义指向原点的平面的法线向量 Constant Float 沿法向量从原点
  • 在 dart angular2 的组件中使用 paper-tabs

    我正在尝试在 dart Angular2 组件中使用聚合物纸标签 但我做不到 下面的代码中我缺少什么 在以下代码中 index html 模板中指定的 paper tabs 工作正常 但 main app html 中的 paper tab
  • 使用 window.crypto.getRandomValues 在 JavaScript 中洗牌扑克牌

    一副扑克牌有 52 张牌 因此52 或大致2 226可能的排列 现在我想完美地洗牌这样一副牌 具有真正随机的结果和均匀的分布 这样你就可以达到这些可能的排列中的每一个 并且每个排列出现的可能性相同 为什么这实际上是必要的 对于游戏来说 也许