Функция random() вообще не случайна

Я использую random() для генерации случайных координат для монет в простой игровой консоли. Проблема здесь в том, что функция random() не генерирует случайные числа, так как монета находится в одних и тех же координатах каждый раз, когда я загружаю ее. Я использую библиотеку Adafruit GFX с цветным TFT ЖК-дисплеем Adafruit 1.44". Вот мой код (извините, он немного длинный).

//necessary libraries
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>

//colors
#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0 
#define WHITE    0xFFFF

//pins
#define TFT_CS  10
#define TFT_RST  9
#define TFT_DC   8

//joystick variables
const int VRxPin = A1;
const int VRyPin = A0;
const int SWPin  = 7;
bool mov = false;

//data read from joystick pins
int x = 0;
int y = 0;
int SW = 0;

// X and Y coords for entities
int playerx = 50;
int playery = 50;

long coinx;
long coiny;

//game variables
int score = 0;

//defining the tft class
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

//useful text function
void output(char *text, int x, int y, uint16_t color, bool wrap = 0){
  tft.setCursor(x, y);
  tft.setTextColor(color);
  tft.setTextWrap(wrap);
  tft.print(text);
}

//there is probably a better way to do this but im too lazy to find it
void num_output(int text, int x, int y, uint16_t color, bool wrap = 0){
  tft.setCursor(x, y);
  tft.setTextColor(color);
  tft.setTextWrap(wrap);
  tft.print(text);
}

//clears screen
void clear() {
  tft.fillScreen(BLACK);
}

//setup (lcd init & startup screen)
void setup() {
  Serial.begin(9600);
  pinMode(SWPin,INPUT_PULLUP);
  tft.initR(INITR_GREENTAB);
  
  long coinx = random(20, 100);
  long coiny = random(20, 100);
  
  clear();
  output("RAMBUTAN", 40, 60, RED);
  delay(1000);
  clear();
  tft.fillRect(playerx, playery, 5, 5, WHITE);
  tft.fillCircle(coinx, coiny, 5, YELLOW);
}

void loop() {
  //reads data from joystick pins every single tick
  int VRx = analogRead(VRxPin);
  int VRy = analogRead(VRyPin);
  int SW = digitalRead(SWPin);
  
  //converts joystick pin data into directions
  if (VRx > 250 && VRx < 750 && VRy > 250 && VRy < 750) {
    // middle
  } else if (VRx > 511.5 && VRy < 750 && VRy > 240) {
    // right
    playery -= 5;
    mov = true;
  } else if (VRx < 511.5 && VRy < 750 && VRy > 240) {
    // left
    playery += 5;
    mov = true;
  } else if (VRy > 511.5 && VRx < 750 && VRx > 240) {
    // up
    playerx += 5;
    mov = true;
  } else if (VRy < 511.5 && VRx < 750 && VRx > 240) {
    // down
    playerx -= 5;
    mov = true;
  } else {
    mov = false;
  }

  if (mov == true) {
    clear();
    tft.fillRect(playerx, playery, 5, 5, WHITE);
    num_output(score, 10, 10, WHITE);
    tft.fillCircle(coinx, coiny, 5, YELLOW);
    mov = false;
  }
  delay(100);
}

, 👍0

Обсуждение

Вы читали [документацию]? (https://www.arduino.cc/reference/en/language/functions/random-numbers/random/) к функции Arduino random ()? Особенно раздел "Примечания и предупреждения"? Микроконтроллер не может легко генерировать случайные числа без хорошего источника случайности., @chrisl

Большинство псевдослучайных библиотек (например, в python), по крайней мере, кажутся случайными, поэтому я подумал, что могу просто использовать это :/, @jort57

Случайные библиотеки на ПК также будут извлекать случайность/энтропию из некоторых источников. На ПК есть много факторов, которые могут быть использованы для этого. На микроконтроллере ресурсы гораздо более ограничены. Здесь вам нужно выделить для этого аналоговый вход (который может быть или не быть приемлемым для вас)., @chrisl

Это ответ на ваш вопрос? Получение действительно случайного числа в Arduino, @Edgar Bonet


2 ответа


Лучший ответ:

1

Вы правы, random () - это не случайность, а псевдослучайность. Он возвращает последовательные результаты из математической формулы, которая во всех отношениях случайна. Это также предсказуемо, как вы уже видели.

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

Поэтому весь фокус в том, чтобы установить семя на что-то действительно случайное. Одним из распространенных источников случайности, или энтропии, является несвязанный вход АЦП:

randomSeed(analogRead(A3));

Подробнее о функции randomSeed() вы можете прочитать здесь.

,

Это, кажется, работает лучше, но когда я двигаюсь, он автоматически уходит в угол, как и раньше... Я не совсем понимаю, что происходит., @jort57

@jort57 Ты же не вызываешь "randomSeed ()" снова и снова, не так ли? Вы хотите вызвать его только один раз в setup()., @Majenko

"randomSeed(analogRead(A3));" лучше, чем ничего, но, по моему опыту, у него очень мало энтропии (пара битов или около того). Если, конечно, вы не подключите к А3 аппаратный источник энтропии., @Edgar Bonet

Трюк, который сработал для многих моих проектов, требующих случайности, состоит в том, чтобы включить какой-то триггер "запуска". Это может быть ответ на сетевую коммуникацию, кнопка (например, кнопка "go") или какое-то другое событие, которое произойдет в случайный момент времени. После наблюдения триггера "start" вызовите функцию millis() и используйте возвращаемое значение в качестве случайного семени. Вероятно, вы также можете использовать micros() вместо millis (), если триггер запуска может произойти быстро (например, связь по локальной сети)., @GMc

... или выполните " analogRead()` несколько раз подряд и соедините последние несколько битов каждого вызова., @PMF

плавающий pin обычно парит в пределах нескольких уровней чтения после чтения, так что больше того же самого на самом деле не очень помогает со случайностью. Наличие нескольких установленных паттернов лучше, чем 1, но не намного. В отличие от совета @Majenko, вызов randomSeed() в дополнительных неопределенных точках (например, if(analogRead(A0)%2)) на протяжении всего скетча исправит проблему "Эй, я знаю этот паттерн", которую создает PRNG. Только вызов его один раз означает, что может быть 4, или 8, или 13, или около того постоянных и предсказуемых паттернов, повторный посев делает его более похожим на CSPRNG, где прошлый результат не подразумевает будущих результатов., @dandavis

Если у вас есть вход от человека, сети или другого источника, не относящегося к MCU, повторите ввод с помощью micros (), когда этот вход произойдет, например, при нажатии кнопки установления соединения Wi-Fi; поскольку они происходят в непредсказуемое время, они идеально подходят для посева PRNG., @dandavis


0

В качестве альтернативы ответу Маженко я генерирую случайные n-битные числа следующим образом: для каждого из n битов я считываю температуру чипа (что довольно шумно), беру наименее значимый бит показаний и присваиваю его соответствующему биту в этом числе. Результаты достаточно случайны, по крайней мере для моей цели.

,