Функция 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);
}
@jort57, 👍0
Обсуждение2 ответа
Лучший ответ:
Вы правы, 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
В качестве альтернативы ответу Маженко я генерирую случайные n-битные числа следующим образом: для каждого из n битов я считываю температуру чипа (что довольно шумно), беру наименее значимый бит показаний и присваиваю его соответствующему биту в этом числе. Результаты достаточно случайны, по крайней мере для моей цели.
- Условие «если» проблема/вопрос
- Программа счетчик
- Почему моя кнопка всегда возвращается HIGH?
- Arduino Sleep для экономии заряда батареи - Как подключить его к существующему скетчу
- Загрузка Arduino Nano дает ошибку: avrdude: stk500_recv(): programmer is not responding
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
- Что лучше использовать: #define или const int для констант?
- Функции со строковыми параметрами
Вы читали [документацию]? (https://www.arduino.cc/reference/en/language/functions/random-numbers/random/) к функции Arduino
random ()
? Особенно раздел "Примечания и предупреждения"? Микроконтроллер не может легко генерировать случайные числа без хорошего источника случайности., @chrislБольшинство псевдослучайных библиотек (например, в python), по крайней мере, кажутся случайными, поэтому я подумал, что могу просто использовать это :/, @jort57
Случайные библиотеки на ПК также будут извлекать случайность/энтропию из некоторых источников. На ПК есть много факторов, которые могут быть использованы для этого. На микроконтроллере ресурсы гораздо более ограничены. Здесь вам нужно выделить для этого аналоговый вход (который может быть или не быть приемлемым для вас)., @chrisl
Это ответ на ваш вопрос? Получение действительно случайного числа в Arduino, @Edgar Bonet