Случайные значения кажутся не случайными?

Пытаясь сделать простую игру с бактериями, используя WinForm в С#, но бактерии (я использую Panel в настоящее время), похоже, не перемещаются наугад.

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

Как видите, красная Panel перемещается только в верхнем левом углу. Как я могу заставить его перемещаться повсюду равномерно и случайным образом?

Вот мой код:

private Panel _pnlBacteria;     //Panel representing a piece of bacteria
private Random r = new Random();  //For randomly-generated values
private int _prevX;        //Stores the previous X location
private int _prevY;        //Stores the previous Y location

public Form1()
{
    InitializeComponent();
    _pnlBacteria = new Panel();
    /* Get more property assignments to this._pnlBacteria (omitted) */

    //Bacteria start position is also randomly selected
    _prevX = r.Next(50, 300);
    _prevY = r.Next(50, 500);
}

//Timer runs every 100 seconds changing the location of the bacteria
private void TmrMoveBacteria_Tick(object sender, EventArgs e)
{
    int x, y;
    //Get random values for X and Y based on where the bacteria was previously
    //and move randomly within ±10 range. Also it cannot go off the screen.
    do 
    {       
        x = r.Next(_prevX - 10, _prevX + 10);
        y = r.Next(_prevY - 10, _prevY + 10);
    } 
    while ((y <= 0) || (y >= 500) || (x <= 0) || (x >= 300));

    //Save the new location to be used in the next Tick round as previous values
    _prevX = x;
    _prevY = y; 

    //Apply the actual location change to the bacteria panel
    _pnlBacteria.Top = y;
    _pnlBacteria.Left = x;
}

Я попытался сменить +10 на +12, оставив -10 как есть, но теперь это только заставило бактерии двигаться только в нижний правый угол. Я в растерянности. Кто-нибудь может помочь?

Ответы

Ответ 1

Немного другой способ справиться с этим может состоять в том, чтобы выбрать случайное направление и случайное расстояние для путешествия в этом направлении. Это позволит увеличить перемещение по форме, если мы увеличим максимальное расстояние, на которое могут двигаться бактерии, прежде чем менять направление.

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

Вот пример, который вы можете скопировать/вставить в новый проект формы, реализующий эту идею. Вы можете играть с настройками maxDistance (наибольшее расстояние до изменения направления) и stepDistance (расстояние, пройденное на каждой итерации Timer). stepDistance меньше stepDistance приведет к более плавному, но медленному движению. Я сделал его довольно маленьким, поэтому я также уменьшил свойство Interval Timer чтобы ускорить его.

Вы заметите, что я также добавил метод проверки правильности направления, чтобы бактерии не выходили из экрана. Я создал enum для представления направлений, что упростило проверку движения в определенном направлении (т.е. Если значение перечисления содержит "Север", и мы слишком близко к вершине, то это неверное направление - это охватывает "Север" "Северо-Запад" и "Северо-Восток").

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

Надеюсь, что это имеет смысл:

public partial class Form1 : Form
{
    // Program Settings and Controls
    private readonly Panel pnlBacteria;             // Panel representing a piece of bacteria
    private readonly Random random = new Random();  // For randomly-generated values
    private readonly Timer tmrMoveBacteria;         // Timer used for bacteria movement
    private readonly int bacteriaSize = 20;         // Stores the size for our bacteria
    private const int maxDistance = 50;             // The maximum number of moves allowed in the same direction.
    private const int stepDistance = 3;             // The distance to travel on each iteration of the timer. Smaller number is slower and smoother
    private readonly List<int> weightedDistances;   // Contains a weighted list of distances (lower numbers appear more often than higher ones)

    // Bacteria state variables
    private Direction direction;  // Stores the current direction bacteria is moving
    private int distance;         // Stores the distance remaining to travel in current direction

    // Represents possible directions for bacteria to move
    private enum Direction
    {
        North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest
    }

    public Form1()
    {
        InitializeComponent();

        // Initialize our weighted differences array so that 1 is  
        // chosen most often and maxDistance is chosen the least often
        weightedDistances = new List<int>();
        for (var i = 0; i < maxDistance; i++)
        {
            var weight = maxDistance / (i + 1);

            for (var j = 0; j <= weight; j++)
            {
                weightedDistances.Add(i + 1);
            }
        }

        // Give life to the bacteria
        pnlBacteria = new Panel
        {
            BackColor = Color.Red,
            Width = bacteriaSize,
            Height = bacteriaSize,
            Left = random.Next(0, ClientRectangle.Width - bacteriaSize),
            Top = random.Next(0, ClientRectangle.Height - bacteriaSize)
        };
        Controls.Add(pnlBacteria);

        // Start bacteria movement timer
        tmrMoveBacteria = new Timer {Interval = 10};
        tmrMoveBacteria.Tick += TmrMoveBacteria_Tick;
        tmrMoveBacteria.Start();
    }

    /// <summary>
    /// Sets the direction and distance fields to valid values based on the
    /// current bacteria position, direction, and remaining distance
    /// </summary>
    private void UpdateDirectionAndDistance()
    {
        // Get all directions
        var validDirections = Enum.GetValues(typeof(Direction)).Cast<Direction>();

        // Remove invalid directions (based on the bacteria position)
        if (pnlBacteria.Top < bacteriaSize) validDirections = 
            validDirections.Where(dir => !dir.ToString().Contains("North"));

        if (pnlBacteria.Right > ClientRectangle.Width - bacteriaSize) validDirections = 
            validDirections.Where(dir => !dir.ToString().Contains("East"));

        if (pnlBacteria.Left < bacteriaSize) validDirections = 
            validDirections.Where(dir => !dir.ToString().Contains("West"));

        if (pnlBacteria.Bottom > ClientRectangle.Height - bacteriaSize) validDirections = 
            validDirections.Where(dir => !dir.ToString().Contains("South"));

        // If we're supposed to keep on moving in the same 
        // direction and it valid, then we can exit
        if (distance > 0 && validDirections.Contains(direction)) return;

        // If we got here, then we're setting a new direction and distance
        distance = weightedDistances[random.Next(weightedDistances.Count)];
        var directions = validDirections.Where(d => d != direction).ToList();
        direction = directions[random.Next(directions.Count)];
    }

    /// <summary>
    /// Executes on each iteration of the timer, and moves the bacteria
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void TmrMoveBacteria_Tick(object sender, EventArgs e)
    {
        // Ensure direction and distance are valid
        UpdateDirectionAndDistance();

        // Move the bacteria
        var dirStr = direction.ToString();
        if (dirStr.Contains("North")) pnlBacteria.Top -= stepDistance;
        if (dirStr.Contains("East")) pnlBacteria.Left += stepDistance;
        if (dirStr.Contains("South")) pnlBacteria.Top += stepDistance;
        if (dirStr.Contains("West")) pnlBacteria.Left -= stepDistance;

        distance--;
    }
}

Ответ 2

Если вы прочитаете документацию Random.next(int, int), вы увидите, что нижняя граница включена, а верхняя граница является эксклюзивной, поэтому -10 и +11 работают.

Ответ 3

Левый и правый аргументы r.Next являются инклюзивными и эксклюзивными соответственно.

x = r.Next(_prevX - 10, _prevX + 10);
y = r.Next(_prevY - 10, _prevY + 10);

Таким образом, вероятностное пространство этого кода представляет собой квадрат от 19x19 (_prevX,_prevY) до его верхнего левого (_prevX,_prevY). Это означает, что смещение более вероятно в верхнем левом углу.

Если мы используем 11s справа, то вероятностное пространство представляет собой квадрат (_prevX,_prevY) центром в (_prevX,_prevY). Бактерии будут двигаться более равномерно. Его среднее смещение в пределах его квадрата вероятности равно нулю, поэтому нет ни одного благоприятного направления. Но диагональные направления будут предпочтительны, поскольку в среднем диагональное смещение является наибольшим. Это может быть или не быть важным в зависимости от ситуации.

Если мы используем радиус и направление, как объяснил Руфус L, тогда мы можем создать более "естественное" круговое вероятностное пространство. Вот почему это предпочтительный подход.

Ответ 4

Вы должны использовать размер вашей панели. Высота - размер вашего квадрата. То же самое для длины, а не для констант, если размер экрана изменится, ваша игра все равно будет работать.

_prevX = r.Next(bateriaSize, this.Width-bacteriaSize); //this.Width Gets you the of form
_prevY = r.Next(bateriaSize, this.Height-bacteriaSize); //This.Height gets you the height of form

Ответ 5

Проблема в том, что вы перемещаете объект туда и обратно, потому что диапазон прев. -+10;

 x = r.Next(_prevX - 10, _prevX + 10);
 y = r.Next(_prevY - 10, _prevY + 10);

вы должны добавить флаг направления для yval и xval, установить его с определенной вероятностью p (меньше 1/2), когда флаг включен, идти вправо/вверх, когда его выключение идет влево/вниз,

if (forwardx){
    x = r.Next(_prevX , _prevX + 10);
}
else {
     x = r.Next(_prevX - 10, _prevX );
}
if (forwardy){
    y = r.Next(_prevY , _prevY + 10);
}
else{
    y = r.Next(_prevY - 10 , _prevY );
}

forwardx = r.Next(10) == 1; //p = 0.1
forwardy = r.Next(10) == 1;

Удачи!