提高Objective-C中随机性的质量

9

你站在一个地牢里。在你面前,是一群5级书呆子。他们想要你为他们运行一场龙与地下城的游戏。

你进行了几次游戏,你的玩家正在升级,一切都很顺利。不过战斗有点慢。你决定拿出你的+4 Objective-C 戟,并编写一个 iPad 应用程序来自动化战斗中的 NPC 掷骰子。 书呆子们怒视着你。“算法生成的数字,”一个人咆哮道,“是真正随机的空洞模仿!你不能用伪随机的东西玷污我们神圣的游戏!”

你试图说服他用arc4random_uniform()已经足够了……但失败了。书呆子们只满足于真正的随机性。他们把你拘禁起来,而你则拼命抓住你的 MacBook 写一个从 random.org 获取数据的类。

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY/YYYY-MM-dd"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@%@", 
                @"http://www.random.org/files/", 
                [formatter stringFromDate:[NSDate date]],
                @".bin"]];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:
                [NSURLRequest requestWithURL:url] delegate:self];

一旦数据已保存,您可以从下载的字节中生成0-255的随机数。
-(int) nextInt:(int)start end:(int)end
{
    int upperBound = end - start + 1;
    unsigned char result;
    int maxModulo = 255 - (255 % upperBound);
    do {
        NSRange range = {index, sizeof(char)};
        [randos getBytes:&result range:range];
        index += sizeof(char);
    } while (result > maxModulo); //avoid modulo bias
    result = result % upperBound;
    result += start;
    return result;
}

这些书呆子似乎很满意,但是另一个地牢主出现了!他要求你为他自己的目的提供软件副本。问题显而易见——如果你们都使用同一天的random.org数据,你们将得到相同的骰子点数集合!
因此,我的问题如下:我该如何修改random.org数据,使其保留“真正的随机性”,但在程序的每个实例中都是不同的?我可以想象一种解决方案,即从用户那里获取一些(据称是随机的)触摸板移动,就像TrueCrypt一样,但一旦我有了这个常数,我不确定接下来该怎么做。以某种方式使用我的常数对所有数字进行哈希处理?这将产生一个更大的数字;如果我只截断或取模它以得到骰子点数,那么统计学上是否可以?我不知道应采取哪些算法步骤。
5个回答

5
我有一个不同的解决方案,这个方案应该能够满足每个人。每次你想生成新的骰子点数时,请按照以下方式操作:
展示一个进度条,并提示用户摇晃设备。
在等待期间,适当地过滤加速度数据(一些低通IIR应该很好),并寻找具有一定给定幅度的尖峰。使用迟滞计算电平变化。显示一个显示摇晃量的进度条。
同时,将原始加速度数据馈送到适当的加密哈希函数中,例如SHA-2。
当进度条完全到达右侧时,播放掷骰子的声音,并使用哈希函数的输出(256位)生成骰子点数。这对于超过59d20的情况无效。您还可以将哈希函数状态保存为下一个骰子点数的输入。
告诉那些技术宅们的话:骰子点数在算法上是不可预测的。确定骰子点数值的唯一信息是您如何摇动设备,这也适用于真正的骰子。理论上,您可能会以相同的方式两次摇动设备,就像理论上高技能的赌徒可能会掷出他想要的真正的骰子点数一样。
如何使用输出:您有256位的随机数据,并且想要获得骰子点数。
struct bits {
    unsigned data[8];
    unsigned pos;
};

// Get next n bits, or -1 if out of entropy.
int get_bits(struct bits *b, int n);

// Roll an n-sided die, or -1 if out of entropy
int uniform(struct bits *b, int n)
{
    int nbits, x;
    for (nbits = 0; (1 << nbits) < n; ++nbits);
    do {
        x = get_bits(b, nbits);
        if (x < 0)
            return -1;
    } while (x >= n);
    return x + 1;
}

这个函数的工作原理是每次为您的掷骰子切割一些熵位。例如,对于一个d8,您需要切割3位并使用结果。对于一个d20,您需要切割5位,对于d32,如果结果大于20,则需要重新掷骰。如果您在某个给定的掷骰中耗尽了熵(虽然不太可能),那么建议打印“骰子错误”消息,并要求用户为剩余的骰子摇动更多次。
注:除非您掷了很多骰子,否则您将耗尽熵的概率非常低。256位足够用了。即使掷24个d20,耗尽熵的概率也不到1%。

哈!就这样吧。我们掌握了密码学,也感受到了控制输出的感觉。其他答案表明我的玩家们并不真正理解随机数是如何生成的,但为什么要教他们呢?我可以直接让他们开心。 - iameli
1
更好的做法是让用户自己完成这些操作,并使用更好的“随机”数生成器,例如arc4random或SecRandomCopyBytes。系统具有更好和更多的熵源。因此,认为“自制”的解决方案可能比经过专业设计的系统更好,这种想法是不可思议的。 - zaph
@CocoaFu:我认为你没有理解这个应用程序的目的。它有两个目的:1)拥有一个易于解释的可理解的机制,2)产生足够好的数字供RPG使用。认为你需要经过审核的专业设计的PRNG才能在家和几个朋友一起玩RPG是疯狂的。(顺便说一句,大多数RPG玩家的骰子都有相当惊人的偏见。)此外,SHA-2是一个很好的PRNG。 - Dietrich Epp
不要过度设计你的应用程序。嗯,我会称编写一个比提供的随机数生成器差的随机数生成器为过度设计。更多的工作,更差的结果。 - zaph
该应用程序是一款玩具。它在非正式场合作为游戏的一部分使用。随机数生成器机制的性质使得它更加有趣。 - Dietrich Epp
显示剩余2条评论

4

这并不是一个答案,而是一种评论,但它变得很长,下面是内容。

我可以想象出一种解决方案,即从用户获取一些(据称是随机的)触摸板动作,

请注意,arc4random_stir()/dev/urandom 读取,参见手册。因此,它使用“环境”自行生成种子。

或者,购买放射源和盖革计数器,将其连接到USB,并根据计数器的读数生成随机数。核衰变是量子力学随机的。


只需使用arc4random(),来自man页:“在使用arc4random()之前不需要调用arc4random_stir(),因为arc4random()会自动初始化自己。” - zaph

2
生成加密安全的随机字节数组。
int SecRandomCopyBytes (
   SecRandomRef rnd,
   size_t count,
   uint8_t *bytes
);

苹果随机服务参考文档

或者直接使用arc4random()函数,它的随机性足以满足需求,而且会自动从/dev/urandom中获取种子。


由于模数偏差,建议使用arc4random_uniform()。 - Martin-Gilles Lavoie

1

下载bin-files.txt文件并随机选择其中一个条目(例如,使用NSDate timeIntervalSinceNow对txt文件中的条目数取模)。然后下载该文件。

此外,一旦您下载了文件,可以根据不同的随机化器从某个偏移量开始。


0
无论您使用哪种随机方法(我选择 _arc4random_uniform_),在某些时候仍然需要一个骰子计算器。
从方法体中推断标题。这是我使用的:
objective-c
//
//  Dice.m
//  TheeEndThee
//
//  Created by Martin-Gilles Lavoie on 2019-10-19.
//  Copyright © 2019 DoRyu. All rights reserved.
//

#import "Dice.h"

@implementation Dice

+ (NSInteger) roll: (NSUInteger) sides
             times: (NSUInteger) numDices
              plus: (NSInteger) bonus
{
    NSInteger   result  =   bonus;

    while (numDices-- > 0)
    {
        result += (arc4random() % sides) + 1;
    }

    return result;
}

+ (NSInteger) roll100   {   return [self roll: 100 times: 1 plus: 0];   }
+ (NSInteger) roll30    {   return [self roll: 30 times: 1 plus: 0];    }
+ (NSInteger) roll24    {   return [self roll: 24 times: 1 plus: 0];    }
+ (NSInteger) roll20    {   return [self roll: 20 times: 1 plus: 0];    }
+ (NSInteger) roll12    {   return [self roll: 12 times: 1 plus: 0];    }
+ (NSInteger) roll10    {   return [self roll: 10 times: 1 plus: 0];    }
+ (NSInteger) roll8     {   return [self roll: 8 times: 1 plus: 0];     }
+ (NSInteger) roll6     {   return [self roll: 6 times: 1 plus: 0];     }
+ (NSInteger) roll4     {   return [self roll: 4 times: 1 plus: 0];     }
+ (NSInteger) roll2     {   return [self roll: 2 times: 1 plus: 0];     }

+ (NSInteger) rollStat  {   return [self roll: 6 times: 3 plus: 0];     }

+ (NSObject*) compute: (NSObject*) value
{
    NSObject*   result  =   value;

    if ([value isKindOfClass: NSString.class])
    {
        NSString*   expression  =   (id) value;

        //whitespaces and newlines are nod valid dice expressions
        if ([expression rangeOfCharacterFromSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]].location == NSNotFound
        && [expression hasPrefix: @"="])
        {
            expression = [expression substringFromIndex: 1];

            NSInteger   multiplier  =   1;
            NSInteger   addition    =   0;
            NSInteger   faces;

            NSArray<NSString*>*     components  =   [expression componentsSeparatedByString: @"+"];

            if (components.count == 2)
            {
                addition = components.lastObject.integerValue;

                expression = components.firstObject;
            }

            expression  =   expression.lowercaseString;

            components  =   [expression componentsSeparatedByString: @"d"];

            if (components.count == 2)
            {
                multiplier = components.firstObject.integerValue;

                expression = components.lastObject;
            }

            faces = expression.integerValue;

            if (faces > 0 && multiplier > 0)
            {
                result = @([self roll: faces
                                times: multiplier
                                 plus: addition]);

#if DEBUG
                NSLog(@"Dice \"%@\" = %@d%@+%@ = %@",
                      value, @(multiplier), @(faces), @(addition), result);
#endif
            }
            else
            {
                result = nil;
            }
        }
    }

    return result;
}

@end

[Dice compute: expression]将计算以下类型的表达式:

=6.      // expression to roll a 6-sided dice
=1d6     // expression to roll a 6-sided dice
=6+2     // expression to roll a 6-sided dice and add 2
=1d6+2   // expression to roll a 6-sided dice and add 2

语法 {multiplier}{d}faces{+bonus} 可以产生任何给定面数的数字骰子,最后带有奖励。

我的程序使用变量替换操作来处理类似于“1d20 + MOD”之类的事物。在您自己的程序中进行轻松变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接