Создание и управление двумя независимыми последовательностями случайных чисел

У меня возникли проблемы с созданием двух независимых случайных последовательностей с помощью функций rand и srand. Подробности ниже, любая помощь будет принята с благодарностью.

Я работаю над игрой-головоломкой для iPhone и обычно для генерации случайных чисел использую функцию arc4. Однако для многопользовательского режима я хочу, чтобы у обоих игроков были одни и те же фигуры на протяжении всей игры, и единственный способ, которым я могу это контролировать, — иметь две повторяющиеся случайные последовательности. Если я отправлю семена на другое устройство, игры будут идентичными. Однако, когда я использую rand и srand и пытаюсь переключиться на другое семя, последовательность начинается с нуля, мне каким-то образом нужно инициализировать две независимые последовательности, сгенерированные с помощью семени.

Спасибо за ваши ответы


person Kaan Dedeoglu    schedule 18.02.2012    source источник


Ответы (6)


Криптографически плохие PRNG, такие как rand(), работают, возвращая предыдущий результат обратно в определенную математическую процедуру.

Чтобы продолжить последовательность с того места, где она была прервана, все, что вам нужно сделать, это сохранить последнее сгенерированное число и использовать его в качестве начального числа:

srand(time(0));
int player1_rand_num = rand();
NSLog(@"Player 1: %d, %d, %d", rand(), rand(), rand());
srand(7);
int player2_rand_num = rand();
NSLog(@"Player 2: %d, %d, %d", rand(), rand(), rand());

// Re-seed Player 1 sequence
srand(player1_rand_num);
// Displays the same "random" numbers as the first NSLog
NSLog(@"Player 1 again: %ld, %ld, %ld", rand(), rand(), rand());
// and so on...

Функция random() генерирует более качественные случайные числа и имеет отдельную пару функций, initstate() и setstate(), которые дадут вам состояние генератора. Вы можете сохранить состояние и передать его в setstate(), чтобы возобновить последовательность с того места, где вы остановились. Я направляю вас на man 3 random для деталей.

person jscs    schedule 18.02.2012
comment
Это не может быть правдой. В противном случае любой сможет воссоздать вашу последовательность, просто заполнив один из выходов. Это открыло бы двери для огромных дыр в безопасности в системах шифрования. Я даже пытался запустить ваш код и после повторного заполнения получил разные значения. - person Tim Rupe; 18.02.2012
comment
@Tim: То, что я сказал, верно для rand(), и именно поэтому вы не используете его для криптографии. Я обычно заменял использование OP rand() на лучшее (но все же не криптографически отличное) random(), пренебрегая тем фактом, что у него есть отдельные функции initstate() и setstate() для выполнения того, о чем я говорю. - person jscs; 18.02.2012
comment
Ах, я не знал, что это работает для старого rand(). Я всегда избегал этого, как чумы. - person Tim Rupe; 19.02.2012
comment
Не по теме, но именно этот недостаток делает rand() идеальным для модульных тестов, не связанных с криптографией. - person Costique; 19.02.2012

Во-первых, как уже указывали другие, вы должны использовать random() вместо rand(). Во-вторых, хотя ваш одноэлементный подход может работать для вас, вы могли бы решить свою проблему проще и ИМХО более элегантно, используя setstate(3). См. Использование setstate(3) не дает ожидаемых последовательность случайных чисел для примера того, как переключаться между двумя состояниями случайных чисел.

person Mojo66    schedule 26.02.2014
comment
Круто, хотя проблема давно решена, я обязательно посмотрю на setstate. Для реализации я использовал синглтон (оглядываясь назад, в котором совершенно нет необходимости), тем не менее, он будет работать при использовании в качестве экземпляра. - person Kaan Dedeoglu; 26.02.2014

Спасибо за советы, вот как я все это реализовал. Я создал одноэлементный класс с двумя переменными экземпляра — seed1 и seed2 — в любое время, когда я хочу получить число от первого генератора, я использую метод генератор1, то же самое для метода генератора2. Семя 1/2 мгновенно устанавливается на новое сгенерированное число каждый раз, поэтому я могу просто продолжить с того места, на котором остановился. В заключение Джош Касуэлл дал мне всю необходимую информацию. Проверьте код, если вам когда-нибудь понадобится что-то подобное. Объект инициализируется сидами 1 и 1, но во время игры они заменяются некоторыми другими числами, общими для обоих устройств.

@implementation RandomNumberGenerator

@synthesize seed1,seed2;

static RandomNumberGenerator *sharedGenerator = nil;

+(RandomNumberGenerator *) sharedInstance
{
if(!sharedGenerator) {
    sharedGenerator = [[RandomNumberGenerator alloc] initWithSeed1:1 andSeed2:1];
}

return sharedGenerator;
}

-(id) initWithSeed1:(int) seedOne andSeed2:(int) seedTwo{
self = [super init];
if (self)
{
    seed1 = seedOne;
    seed2 = seedTwo;
}
return self;
}

-(int) generator1{
srand(seed1);
int j = rand();
seed1 = j;
return abs(j);

}

-(int) generator2 {
srand(seed2);
int k = rand();
seed2 = k;
return abs(k);
}

-(int) giveRandom {
//return abs(arc4random());
return abs(arc4random());
}


@end
person Kaan Dedeoglu    schedule 20.02.2012

Вы запустили генератор случайных чисел?

srand( myIdenticalSeedValueForBothPartners );

См. этот вопрос или здесь [справочник по C++].

Если вам не нужно вызывать rand() много тысяч раз:

int nthRandBasedOnSeed( int seed, int count ) {

   srand( seed );

   int result;
   while( 0 < count-- ) {

     result = rand();

   }

   return result;

}
person SteAp    schedule 18.02.2012
comment
да, я запускаю случайный генератор с помощью srand, давайте предположим, что я запускаю его с помощью 1, т.е. srand(1), и я хочу, чтобы мой другой генератор имел семя 2-srand(2), чего я пытаюсь достичь, так это иметь возможность переключаться между семенем 1 и семенем 2 столько раз, сколько я хочу во время игры, и продолжать оба с того места, где оно осталось. Однако, когда вы меняете семя через srand() и отправляете rand(), он начинает последовательность с нуля, я почему-то хочу продолжить с того места, где она остановилась. - person Kaan Dedeoglu; 18.02.2012

В качестве альтернативы вы можете рассмотреть возможность отправки с начальным числом «счетчика». Этот счетчик будет просто указывать, где вы находитесь в посеянной серии, и будет увеличиваться каждый раз, когда вы генерируете случайное число с этим начальным числом. Этот подход дает вам гибкость использования любого генератора случайных чисел, который вам нравится, и сводит общение к минимуму.

int playerSeed = 12345;
int playerRndCount = 0;

int generateRandomNumber() {
    playerRndCount++;
    return rand();
}

void synchSeed(seed, count) {
    srand(seed);
    for (int i=0;  i<count;  i++)
        generateRandumNumber();
}
person aLevelOfIndirection    schedule 19.02.2012

Некоторые библиотеки генераторов случайных чисел позволяют сохранять состояние генератора. Таким образом, вы можете восстановить его позже и продолжить уже выполняемую последовательность. Один из них, о котором я знаю, называется RandomLib, и его можно найти на SourceForge.

Другой вариант — сохранить начальное значение и подсчитать, сколько раз вы извлекли значение из генератора после заполнения. Позже, когда вы захотите продолжить, повторно засейте исходное семя и соберите такое же количество. Это, вероятно, не лучший метод, но он должен работать нормально, если не делать много.

person Tim Rupe    schedule 18.02.2012
comment
спасибо, это выглядит хорошо, есть идеи, буду ли я по-прежнему отправлять приложение, если я использую стороннюю библиотеку, подобную этой? - person Kaan Dedeoglu; 18.02.2012
comment
Все должно быть в порядке, лицензия довольно открытая: opensource.org/licenses/MIT - person Tim Rupe; 18.02.2012