Перейти к содержимому

Уроки · 76

Новая случайность при каждом запуске

Запусти программу с rand::gen_range несколько раз подряд, и случайные числа каждый раз окажутся одними и теми же, в том же порядке. Например, кружок из урока «Случайная точка» при каждом запуске прыгает по одним и тем же местам. Давай разберёмся, почему так бывает и как это поменять.

Компьютер не бросает кубик. Он считает числа по формуле, а формуле нужно число, с которого начать. Это стартовое число называют seed, по-русски «зерно»: с него всё начинается, и из него вырастает весь набор чисел. Пока зерно одно и то же, и числа получаются одни и те же.

Иногда это полезно. Представь, что игра сама создаёт мир: расставляет деревья, горы и озёра, и руками мы их не рисуем. Если взять то же зерно, получится тот же самый мир. С другим зерном получится другой. Так устроены миры в Майнкрафте: зерно можно задать самому.

Зерно задаёт функция rand::srand:

// Задаём зерно. Дальше все числа из gen_range считаются от него.
rand::srand(42);
// Это число всегда будет одним и тем же при зерне 42.
let x = rand::gen_range(0.0, 10.0);

Вызывать её нужно один раз, в самом начале программы, до первого gen_range.

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

// Текущее время в секундах — каждый запуск оно другое. Берём его как зерно.
rand::srand(macroquad::miniquad::date::now() as u64);

date::now() отдаёт текущее время в секундах. as u64 делает из него целое число, потому что srand ждёт именно целое, как в уроке «Целые числа». u64 — ещё один тип целых чисел, он рассчитан на большие значения.

В предыдущих уроках мы такую строку не писали, поэтому там случайный расклад при каждом запуске одинаковый.

Меняй seed и смотри, как меняется расклад. Верни 42 — получишь тот же расклад, что был сначала. Кнопка берёт в качестве зерна текущее время:

src/main.rs
use macroquad::prelude::*;
// Зерно (seed): с него начинается набор случайных чисел.
const SEED: u64 = 42;
// Сколько кружков нарисовать.
const COUNT: usize = 20;
#[macroquad::main("Новая случайность")]
async fn main() {
// Задаём зерно один раз, в самом начале, до первого gen_range.
rand::srand(SEED);
// Список случайных точек — сначала пустой.
let mut points: Vec<(f32, f32)> = Vec::new();
// Заполняем его один раз, при старте программы.
for _ in 0..COUNT {
// Случайная точка: x от 0 до ширины экрана, y от 0 до высоты.
let x = rand::gen_range(0.0, screen_width());
let y = rand::gen_range(0.0, screen_height());
// Кладём точку в список.
points.push((x, y));
}
loop {
clear_background(BLACK);
// Рисуем кружок в каждой точке списка.
for &(x, y) in &points {
draw_circle(x, y, 30.0, YELLOW);
}
next_frame().await;
}
}
  • Случайные числа считаются по формуле. Начинается она с зерна, по-английски seed.
  • rand::srand(SEED) задаёт зерно. Его вызывают один раз, в начале программы, до первого gen_range.
  • Одно и то же зерно даёт один и тот же расклад кружков. Другое зерно даёт другой.
  • date::now() as u64 берёт в качестве зерна текущее время, и каждый запуск получается новым. Кнопка «Из времени» делает то же самое.