Skip to main content

News

Topic: Random Movement Generation (Read 2660 times) previous topic - next topic

Random Movement Generation
The character that random movement is attached to
is only moving north, having trouble figuring out why.
Occasionally he'll turn east and animate, but he's still moving only north.

Code: (javascript) [Select]

// Random movement generator
function MoveRandom(name) {
if (DoesPersonExist(name) == true) {
var direct = rng.random(1, 100);
var tiles = rng.random(0, 5);

switch (direct) {
case 1:
   if (direct >= 1 && direct <= 25) {Move(name, tiles, North);}
   break;
case 2:
   if (direct >= 26 && direct <= 50) {Move(name, tiles, South);}
   break;
case 3:
   if (direct >= 51 && direct <= 74) {Move(name, tiles, East);}
   break;
case 4:
   if (direct >= 75 && direct <= 100) {Move(name, tiles, West);}
   break;
   }
  }
}


Replace rng.random() with Math.random() to
test if it was a problem with the number generator,
but got the same results.

. . .

Was also getting a problem with "undefined" being an invalid integer. [Line 83]
However that was fixed, oddly enough, with putting the variable "Tiles"
into here:

Code: (javacript) [Select]
 else {QueuePersonCommand(Name, Tiles, Face, false);} 


How should this even work?
There is no call for number of tiles in the QueuePersonCommand() function, is there?

Script is attached case anyone needs a quick look through.


  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Random Movement Generation
Reply #1
Just a note. This:

Code: (javascript) [Select]
switch (direct) {
case 1:
   if (direct >= 1 && direct <= 25)  //...

Is nonsensical. You already made sure that line 3 runs only when direct == 1, so it can never be 2-25 in those cases. Use switch only when appropriate - you could remove all the switch case code here since your ifs would handle it all to begin with.

In fact, it's what breaking your script! Your switch statement checks for values 1 to 4 and does nothing for everything else. Try this instead:

Code: (javascript) [Select]
function MoveRandom(name) {
if (DoesPersonExist(name) == true) {
  var direct = rng.random(1, 100);
  var tiles = rng.random(0, 5);

  if (direct >= 1 && direct <= 25) {Move(name, tiles, North);}
  else if (direct >= 26 && direct <= 50) {Move(name, tiles, South);}
  //etc...
  • Last Edit: July 14, 2013, 04:21:44 pm by DaVince

Re: Random Movement Generation
Reply #2
Alright, works now. Thanks bro.

So is the lesson I should never use switch if the function requires an if-then condition?

  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Random Movement Generation
Reply #3
The lesson is, think about when you should use it! More often than not, a regular "if" will cut it. In fact, try using if() first before determining if a switch..case is more appropriate.

A good example of when to use it is when you've got a bunch of different fixed values you want to check against (like case 1: case 2: case 3 and stuff). But if you have a range of values to check against, or there are several conditions you want to compare, then it's usually not necessary or even inconvenient.

Example: why would you do this?
Code: (javascript) [Select]
switch(something) {
  case "value":
    if (another_condition_check) {
      stuff();
    }
}

...if you can do the following, which is identical, but shorter and more effective?
Code: (javascript) [Select]
if (something == "value" && another_condition_check) {
  stuff();
}



Edit:
Another lesson is: read your code and follow the logic in your mind of what it does, step by step. In your case:

  • Compare direct == 1.

  • Yeah, it's 1. Now compare direct == between 1 and 25. (Huh? But it's obvious that direct == 1, or we wouldn't be here!)



If you keep following that logic, you'll realize it would never run the code in case 2 and onward:

  • Compare direct == 2.
  • Yeah, it's 2. Now compare direct == between 26 and 50.
  • It's not between 26 and 50, because it's 2. The condition can never be true. The code in the condition never gets a chance to execute.


The other mistake that you made is that you made direct a value between 0 and 100, so it has a 4 in 100 chance to be 1, 2, 3 or 4 to begin with. In all other 96 cases, no code is run at all!
  • Last Edit: July 14, 2013, 05:46:22 pm by DaVince

Re: Random Movement Generation
Reply #4
Understand what happened there and should've made sense, as it's so clear when you explain it.
Clear as day and still flew over my head. Don't know what I was thinking in that switch conditional.

Have a new problem though, similar to the old one, that needs explanation.
Attempted a day by myself to figure this one out alone, but it's driving me up a wall again.

Code: (javascript) [Select]

// Random movement generator
function MoveRandom(name) {
if (DoesPersonExist(name) == true)
{
  var direct = Math.random()*6;
  var tiles = rng.random(0, 4);

  if (direct = 0) {Move(name, tiles, Wait);}
  if (direct = 1) {Move(name, tiles, North);}
  if (direct = 2) {Move(name, tiles, South);}
  if (direct = 3) {Move(name, tiles, East);}
  if (direct = 4) {Move(name, tiles, West);}
}
}


When I use a "==" the code doesn't work at all, but when it's only an "=" it works almost.
Thought "==" was suppose to be used for when comparing values? But it doesn't work in this case.

As for the "=" I don't know if the sign is the problem or Math.random() repeating a pattern,
because the sprite selected for random movement walks North, then South, East, then West, and repeats.
Pretty much meaning he walks in a fixed L-shape.
  • Last Edit: July 17, 2013, 02:39:21 am by Vakinox

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Random Movement Generation
Reply #5
Never do '=' in an if statement!

'==' compares two values it asks the question, "is it equal to?"

Code: (javascript) [Select]

function MoveRandom(name) {
if (DoesPersonExist(name) == true)
{
  var direct = Math.floor(Math.random()*5); // 0 .. 4
  var tiles = Math.floor(rng.random()*4)) // 0 .. 3;

  if (direct == 0) {Move(name, tiles, Wait);}
  if (direct == 1) {Move(name, tiles, North);}
  if (direct == 2) {Move(name, tiles, South);}
  if (direct == 3) {Move(name, tiles, East);}
  if (direct == 4) {Move(name, tiles, West);}
}
}


The fixes I did:
1. Use == instead of = when comparing.
2. Floor your random values. 3.67867678 != 3, you get the idea, ;)
3. Random() does not take two arguments. You must do Math.random() * scale. the random function only returns a number between 0 and 1 exclusive.
4. I took the numbers between 0 and 5, you had one more than your statements.

If you want a random that takes a range, try a uniform distribution:
Code: (javascript) [Select]

function Uniform(a, b)
{
    return a + Math.floor((1 + b - a)*Math.random());
}


And Uniform (0, 4) will return a number between 0 and 4 inclusive.
  • Last Edit: July 17, 2013, 02:45:31 am by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: Random Movement Generation
Reply #6
Is there any way to quickly concise what uniform distribution is in mathematics?
Personal theory from quick google search leads me to believe you're referring to this:
http://en.wikipedia.org/wiki/Uniform_distribution_%28continuous%29
But, have no understanding at all. High school education here.

My guess is it's a way to keep probability distributions, or rng's in this case,
contained to result within a certain ratio. Between two defined numbers.
Would that be correct?

Quote
Never do '=' in an if statement!
'==' compares two values it asks the question, "is it equal to?"


Was using it before, as explained, but the code was broken when it was being used.
Fixed by the implementation of Math.floor() with your help thankfully, which returns whole numbers instead of decimals.
So presumably, the code was trying to compare whole numbers to decimals and returning false,
which was the reason the code didn't work.
Now it makes sense.

. . .

Having a problem with the characters not facing the correct direction when they walk.

Code: (javascript) [Select]

function Move(Name, Tiles, Direction, Face) {

  if (Direction == North) {QueuePersonCommand(Name, Face_north, false);}  //Makes sprite face its proper direction.
  if (Direction == South) {QueuePersonCommand(Name, Face_south, false);}
  if (Direction == East)  {QueuePersonCommand(Name, Face_east, false);}
  if (Direction == West)  {QueuePersonCommand(Name, Face_west, false);}

  if (Direction == Wait && Face == undefined) {QueuePersonCommand(Name, Face_south, false);}
  else {QueuePersonCommand(Name, Tiles, Face, false);}   //If Direction commands sprite to Wait. It simply faces



for (var j = 0; j < Tiles; ++j) {
  for (var i = 0; i < 16; ++i)  { QueuePersonCommand(Name, Direction, false); }
  SetPersonFrame(Name, 0);
}



//Now we set the sprite's facing direction.

if (Face == undefined)  {
  if (Direction == North) {Face = North;}
  if (Direction == South) {Face = South;}
  if (Direction == East)  {Face = East;}
  if (Direction == West)  {Face = West;}
  }

if (Face == North) {QueuePersonCommand(Name, Face_north, false);}  //Makes sprite face its proper direction.
if (Face == South) {QueuePersonCommand(Name, Face_south, false);}
if (Face == East)  {QueuePersonCommand(Name, Face_east, false);}
if (Face == West)  {QueuePersonCommand(Name, Face_west, false);}




This should be fixed by if (Face == undefined) conditionals, right?
Tried putting them before the for-loop, but yet it doesn't fix the situation.
Also attempted putting them inside the for-loop, but it slowed down their movement terribly.

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Random Movement Generation
Reply #7
I got that wrong that's Equilikely distribution. It's very very similar (for large numbers they are the same) it just doesn't add the one.

Equilikely distribution: It basically means the numbers between a and b inclusive have an equal chance of occurring, if the random number generator has itself an equal distribution (which it usually does).

The +1 adds one to the number since Math.floor() always rounds a number down to the nearest whole number, even if it is at 3.999, eg:
Math.floor(3.999) == 3.

The (b - a) determines the range.

EXAMPLE:
The range of Equilikely(4, 10) is 6. Math.random()*6 will return a number between 0 and 6 exclusive. Adding one makes that from 0 to 7 exclusive. Flooring it will keep the numbers as whole numbers, turning it into 0 to 6 inclusive. And then the last step adds the first number, 4, making it between 4 and 10 inclusive like what you asked for. The math is very simple for uniform and equilikely, but you have to stumble upon this algorithm from somewhere. At my university I took a simulation course last term where we went over these things so it's still fresh in my head.

RANT:
There are other distributions other than uniform and equilikely, such as exponential. There are far, far more (poisson, student, normal, lognormal, geometric, bernoulli, erlang, chisquare, etc.) but are almost never used unless you are seriously trying to model certain real-world behaviors. Sometimes a game may need it, other times they may not. MMO's will tend to use those other ones more often to simulate market growth etc.

Equilikely:
Code: (javascript) [Select]

function Equilikely(a, b)
{
    return a + Math.floor((1 + b - a)*Math.random());
}


Uniform:
Code: (javascript) [Select]

function Uniform(a, b)
{
    return a + Math.floor((b - a)*Math.random());
}


Exponential:
Code: (javascript) [Select]

function Exponential(target)
{
    return Math.floor(-target * Math.log(1 - Math.random()));
}


Exponential is interesting, it tries to get random numbers that average to 'target' over a period of time. What it may do is tend to group numbers together. You might get a stream of low numbers or high numbers, but at the end of the day the average of all numbers is that target number. Of course this is a kind of features you'll have to run more than once to be beneficial. It's useful for markets in games where items and prices can come and go and sometimes they are high and sometimes they are low, but they always average out to the number you specify. So you can have a lot of fun with that kind of distribution. It's also great for damage formulas! Imagine a weapon that attacks on average 7 points of damage but can do damage anywhere between 1 and whatever. The math for that would be easy: var damage = Exponential(7).

Question 2:
It slowed their movement because the 'false' on queued commands for facing should be true. That last variable determines if the action should happen instantly or not. Anyways, to get the correct faces you can create a helper function that translates move commands to face commands:
Code: (javascript) [Select]

function FindFace(command)
{
    switch(command) {
        case: COMMAND_MOVE_NORTH: return COMMAND_FACE_NORTH;
        case: COMMAND_MOVE_SOUTH: return COMMAND_FACE_SOUTH;
        case: COMMAND_MOVE_EAST: return COMMAND_FACE_EAST;
        case: COMMAND_MOVE_WEST: return COMMAND_FACE_WEST;
    }
    return null;
}


And use it like so:
Code: (javascript) [Select]

function Move(Name, Tiles, Direction, Face) {
  if (Direction == North) QueuePersonCommand(Name, Face_north, false);
  if (Direction == South) QueuePersonCommand(Name, Face_south, false);
  if (Direction == East) QueuePersonCommand(Name, Face_east, false);
  if (Direction == West) QueuePersonCommand(Name, Face_west, false);

  var face = FindFace(Direction);
  if (face != null) QueuePersonCommand(Name, face, true);

  for (var j = 0; j < Tiles; ++j) {
    for (var i = 0; i < 16; ++i)  { QueuePersonCommand(Name, Direction, false); }
    SetPersonFrame(Name, 0); // this does nothing in your code. The reason: it's not queued. In fact it will happen before the above line.
  }
}


If you use code to help you code you can use less code to code. :P

Notice that SetPersonFrame(Name, 0) will go first before movement. This is because the queue functions do that: queue commands. They don't make the person move right then and there the map engine does this later on down the line. So you can insted of fixing that, change it so you set the frame once:

Code: (javascript) [Select]

function Move(Name, Tiles, Direction, Face) {
  if (Direction == North) QueuePersonCommand(Name, Face_north, false);
  if (Direction == South) QueuePersonCommand(Name, Face_south, false);
  if (Direction == East) QueuePersonCommand(Name, Face_east, false);
  if (Direction == West) QueuePersonCommand(Name, Face_west, false);

  var face = FindFace(Direction);
  if (face != null) QueuePersonCommand(Name, face, true);

  SetPersonFrame(Name, 0); // now it 'feels' like it works better.
  for (var j = 0; j < Tiles; ++j) {
    for (var i = 0; i < 16; ++i)  { QueuePersonCommand(Name, Direction, false); }
  }
}


That all! :)
  • Last Edit: July 17, 2013, 04:08:11 am by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: Random Movement Generation
Reply #8
Thank you for the information, it'll definitely be useful in later scripting projects.
Especially the exponential one.

. . .

Having a weird new error though. Attempted to sort it out to no avail.
For some reason the Sprite with RandomMovement() attached keeps facing north.
At first, I figured it was being caused by:
Code: (javascript) [Select]
 if (Direction == Wait && Face == undefined) {QueuePersonCommand(Name, Face_south, false);} 
else {QueuePersonCommand(Name, Tiles, Face, false);}   //If Direction commands sprite to Wait. It simply faces


However it has nothing to do with Facing North, and logic instead says Faces South when left undefined.
So that definitely couldn't have been it. Continuing on...

Was wanting to add a Wait to the end of the directions during RandomMovement()
Why? The sprite kept changing direction so swiftly and wanted to slow him down.
Take a breather ya know?
So I added:
Code: (javascript) [Select]
 Move(name, Uniform(2, 9), Wait); 


And also attempted adding a Face_direction behind 'Wait' appropriately to each line.
So changed the (face==undefined && Direction == Wait) code in the Move() function to:
Code: (javascript) [Select]
 QueuePersonCommand(Name, SetPersonDirection(Name, GetPersonDirection(Name)), false); 


And now got this weird error:


In no part of my code is the sprite being told to head 'northeast'
so why is it even returning 'northeast' as a direction?

. . .

For reference, here's what the RandomMovement() function looks like now:
Code: (javascript) [Select]
 
function MoveRandom(name) {

if (DoesPersonExist(name) == true) {

  var direct = Uniform(0, 4);
  var tiles = rng.random(1, 4);

  if (direct == 0) {Move(name, tiles, Wait);}
  if (direct == 1) {QueuePersonCommand(name, Face_north, false); Move(name, tiles, North, Face_north); Move(name, Uniform(2, 9), Wait);}
  if (direct == 2) {QueuePersonCommand(name, Face_south, false); Move(name, tiles, South, Face_south); Move(name, Uniform(2, 9), Wait);}
  if (direct == 3) {QueuePersonCommand(name, Face_east, false); Move(name, tiles, East, Face_east); Move(name, Uniform(2, 9), Wait);}
  if (direct == 4) {QueuePersonCommand(name, Face_west, false); Move(name, tiles, West, Face_west); Move(name, Uniform(2, 9), Wait);}
}
}
  • Last Edit: July 18, 2013, 05:56:26 pm by Vakinox

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Random Movement Generation
Reply #9
That should be Equilikely(0, 4) since equilikely is inclusive 0 to 4.

You got that error since you are setting the person's direction to a direction they do not have.
QueuePersonScript takes a direction command in the second argument not a spriteset direction. They may feel similar but are indeed two different things.

You don't have to set the facing of each direction by hand anymore! I already did that. The move code should only look like this:
Code: (javascript) [Select]

function MoveRandom(name) {
    if (!DoesPersonExist(name)) return;

    var direct = Equilikely(0, 4);
    var tiles = Equilikely(1, 4);
    var w_tiles = Equilikely(2, 9);

    if (direct == 0) { Move(name, tiles, Wait);}
    if (direct == 1) { Move(name, tiles, North, Face_north); Move(name, w_tiles, Wait); }
    if (direct == 2) { Move(name, tiles, South, Face_south); Move(name, w_tiles, Wait); }
    if (direct == 3) { Move(name, tiles, East, Face_east); Move(name, w_tiles, Wait); }
    if (direct == 4) { Move(name, tiles, West, Face_west); Move(name, w_tiles, Wait); }
}
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here