Приведенный ниже код представляет собой полный компилируемый пример.
Ваша проблема заключается в отбрасывании битов из генератора случайных чисел. Давайте посмотрим, как можно написать источник случайных пар битов, который не теряет биты. Требуется, чтобы RAND_MAX
имел форму 2 ^ n -1, но идея может быть расширена для поддержки любых RAND_MAX >= 3
.
#include <cassert>
#include <cstdint>
#include <cstdlib>
class RandomBitSource {
int64_t bits = rand();
int64_t bitMask = RAND_MAX;
static_assert((int64_t(RAND_MAX + 1) & RAND_MAX) == 0, "No support for RAND_MAX != 2^(n-1)");
public:
auto get2Bits() {
if (!bitMask) // got 0 bits
bits = rand(), bitMask = RAND_MAX;
else if (bitMask == 1) // got 1 bit
bits = (bits * (RAND_MAX+1)) | rand(), bitMask = (RAND_MAX+1) | RAND_MAX;
assert(bitMask & 3);
bitMask >>= 2;
int result = bits & 3;
bits >>= 2;
return result;
}
};
Затем реализация случайного блуждания могло быть следующим. Обратите внимание, что разделитель '
di git - это функция C ++ 14, что очень удобно.
#include <vector>
using num_t = int;
struct Coord { num_t x, y; };
struct Walk {
std::vector<Coord> points;
num_t min_x = {}, max_x = {}, min_y = {}, max_y = {};
Walk(size_t n) : points(n) {}
};
auto makeWalk(size_t n = 250'000)
{
Walk walk { n };
RandomBitSource src;
num_t x = 0, y = 0;
for (auto& point : walk.points)
{
const int bits = src.get2Bits(), b0 = bits & 1, b1 = bits >> 1;
x = x + (((~b0 & ~b1) & 1) - ((b0 & ~b1) & 1));
y = y + (((~b0 & b1) & 1) - ((b0 & b1) & 1));
if (x < walk.min_x)
walk.min_x = x;
else if (x > walk.max_x)
walk.max_x = x;
if (y < walk.min_y)
walk.min_y = y;
else if (y > walk.max_y)
walk.max_y = y;
point = { x, y };
}
return walk;
}
Приложив немного больше усилий, мы можем превратить это в интерактивное приложение Qt. Нажатие Return генерирует новое изображение.
Изображение просматривается с собственным разрешением экрана, на котором оно отображается, т. Е. Оно отображается в пикселях физического устройства. Изображение не масштабируется. Вместо этого он поворачивается при необходимости, чтобы лучше вписаться в ориентацию экрана (портретная или альбомная). Это для поклонников портретных мониторов :)
#include <QtWidgets>
QImage renderWalk(const Walk& walk, Qt::ScreenOrientation orient)
{
using std::swap;
auto width = walk.max_x - walk.min_x + 3;
auto height = walk.max_y - walk.min_y + 3;
bool const rotated = (width < height) == (orient == Qt::LandscapeOrientation);
if (rotated) swap(width, height);
QImage image(width, height, QPixmap(1, 1).toImage().format());
image.fill(Qt::black);
QPainter p(&image);
if (rotated) {
p.translate(width, 0);
p.rotate(90);
}
p.translate(-walk.min_x, -walk.min_y);
auto constexpr hueStep = 1.0/720.0;
qreal hue = 0;
int const huePeriod = walk.points.size() * hueStep;
int i = 0;
for (auto& point : walk.points) {
if (!i--) {
p.setPen(QColor::fromHsvF(hue, 1.0, 1.0, 0.5));
hue += hueStep;
i = huePeriod;
}
p.drawPoint(point.x, point.y);
}
return image;
}
#include <ctime>
int main(int argc, char* argv[])
{
srand(time(NULL));
QApplication a(argc, argv);
QLabel view;
view.setAlignment(Qt::AlignCenter);
view.setStyleSheet("QLabel {background-color: black;}");
view.show();
auto const refresh = [&view] {
auto *screen = view.screen();
auto orientation = screen->orientation();
auto pixmap = QPixmap::fromImage(renderWalk(makeWalk(), orientation));
pixmap.setDevicePixelRatio(screen->devicePixelRatio());
view.setPixmap(pixmap);
view.resize(view.size().expandedTo(pixmap.size()));
};
refresh();
QShortcut enter(Qt::Key_Return, &view);
enter.setContext(Qt::ApplicationShortcut);
QObject::connect(&enter, &QShortcut::activated, &view, refresh);
return a.exec();
}