views:

316

answers:

7

I've created a 2 dimensional 'grid' for a game world I'm creating in Java. I'm having a problem with the 'wander' mode algorithm I've created.

I noticed a problem that the objects would seem to favor the bottom right corner of the grid. I changed the algorithm and thought it was fixed.

Today, while stress testing, I noticed that the problem was not fixed, and now wandering objects favor the top left corner of the grid, but it takes longer for them to wander in that area.

The algorithm works by: 1. Getting the current values of the person's position 2. Putting all the squares in 3 block radius into a linked list 3. Randomize the list 4. Choose a random value from the list 5. Setting that values point as the next target

Here's a code snippet:

 Point personAt = this.getTopLeftPoint();
    personAt = Game.getGame().screenToGame(personAt);
    LinkedList<Point> thisSet = new LinkedList<Point>();
    for (int x = personAt.x - 2; x < personAt.x + 3; x++) {
 for (int y = personAt.y - 2; y < personAt.y + 3; y++) {
     if (!(x == personAt.x && y == personAt.y)) {
  //TODO: Clean up the next line of code.
  if (x > 0 && y > 0 && !Board.getBoard().getSquare(x, y).getSquareType().equals(SquareType.path)) {
      if (!Game.getGame().getMap().blocked(x, y)) {
   thisSet.add(new Point(x, y));
      }
  }
     }
 }
    }
    Collections.shuffle(thisSet);
    //Random random = new Random();
    //Integer randomNo = random.nextInt(thisSet.size());
    setNextTarget(thisSet.get(0));

Is there anything I'm missing here?

I am confused as to why they still end up within the top left quarter of the grid.

EDIT: I completely removed the Random object, as suggested by Don. Still end up with the same result.

EDIT: Updated code to fix one part of the issue, so now only the square the person is currently on is ignored, rather that all squares on the current X or Y co ordinate. As suggested by rsp.

+6  A: 

Only initialize random once. You're ruining the randomness by reinstantiating the random generator over and over.

Preferably, use a factory for the entire application to create a random singleton.

public class RandomFactory
{
    private Random random;
    public Random getRandom()
    {
       if( random == null ){ random = new Random(); }

       return random;
    }
}
Stefan Kendall
I removed the need for the Random object as suggested by Don. Still the same result.Thank you for your input though, I shall remember this and put it on my todo list for the whole project. Factories are good :)
Relequestual
+2  A: 

Assuming the shuffle() method is unbiased, there's no reason why you should need to randomly choose an element after shuffling, you could simply choose the first element. In other words, replace this:

Collections.shuffle(thisSet);
Random random = new Random();
Integer randomNo = random.nextInt(thisSet.size());
setNextTarget(thisSet.get(randomNo));

with:

Collections.shuffle(thisSet);
setNextTarget(thisSet.get(0));
Don
Yes, I agree, however as I do not know if it is or not, I thought I would randomise the selection too.
Relequestual
I would hope Collectiosns.shuffle would be unbiased, it's built into the Java API!
R. Bemrose
I did as suggested, and no change.
Relequestual
+8  A: 

A few points, some of which have been made in other answers:

  • As ever, only create a single instance of Random(). I suspected this would be part of the answer - very few questions about random numbers on Stack Overflow aren't to do with this :)
  • Why are you shuffling the set and taking a random element? Just pick one random element.
  • One point of asymmetry: you're checking the x > 0 and y > 0 (not >= 0, by the way?) but you're not checking whether x and y are within the upper bounds of the board. if Board.getSquare() copes with that, do you really need the > 0 checks?
Jon Skeet
You are right on all points here. Thanks.I have removed the need for the Random object, and use the shuffle on its own.Regarding your final point, will note for cleanup. This is my first real project :)
Relequestual
Board.getSquare(), does not cope with that, but it really really should. Thanks for the comment.
Relequestual
+1  A: 

as others have said, you should only ever instantiate a Random object once and then reuse it, and the shuffle() call should mean you don't need to use Random at all. But as for the root cause of your bug, it looks like every time it is called it initializes at this.getTopLeftPoint(), so I don't see how it wouldn't always go to the top left. I would think you'd want to instantiate personAt at their actual location, not at the top left point.

Brian Schroth
ah. I should have made it clear that, 1. The method is within the Person class.2. getTopLeftPoint(), is the method which returns the persons current position.
Relequestual
+1  A: 

You say you put all surrounding points in the list but your code excludes the horizontal and vertical lines, so your objects tend to move in diagonal direction only, which might explain a preference for upper left.

Btw, your conditionals can be placed a bit more readable saving some unnecessary execution in the process:

for (int x = personAt.x - 2; x < personAt.x + 3; x++) {
  if (x > 0 && x != personAt.x) {
    for (int y = personAt.y - 2; y < personAt.y + 3; y++) {
      if (y > 0 && y != personAt.y) {

         if (!Game.getGame().getMap().blocked(x, y) &&
               !Board.getBoard().getSquare(x,y).getSquareType().equals(SquareType.path)) {

            thisSet.add(new Point(x, y));
         }
      }
   }
}
rsp
Can you explain to me how my code is excluding horizontal and vertical lines? I didn't realise it did, however after just testing it now, I see you are right.
Relequestual
Dave Hinton
You are attempting to exclude the current point but your two != tests are actually being ORed not ANDed and thus exclude crosshairs aimed at the current point.
Loren Pechtel
rsp
Thank you @Dave @Loren and @rsp. I couldn't understand why, but I changed the code to your suggestion and it fixed that issue. However... the MAIN problem, which was this question, still remains. The people all wander to the to left in the end... :S
Relequestual
You start with `.getTopLeftPoint()` of the person. 1) if your person occupies a space like 2x2 this would mean that your random square is around -3,-3 to 1,1 relative to the center of the person leading to a walk left and up. 2) you transform using `.screenToGame()` you might want to try if a `.gameToScreen()` yields the original point. 3) you might check the origins (aka 0,0 point) between the screen, game and board coordinates.
rsp
Thank you for your reply again rsp.1. The person occupies a 1x1 square.2. When it says Game, it actually means the Board. all it does it times or divide the values by 30, and I have used it all through the game. If it was wrong, lots of other things would fail, id think.I hope my comments don't come across as unappreciative, because I really REALLY appreciate your time and help. :)
Relequestual
Ok, if I understand correctly the scale of game and board differs by a factor of 30? You use the same x,y coordinates to check the squaretype on the board, and to check the blocked status on the game. One of those checks might be over the right / lower edge depending on the board size thereby blocking that direction.
rsp
Yes, the scale between the screen display and the game board. (Board is an object in Game). I use the same x and y to check the squaretype and blocked status. I see your point, but I know this is not the case, because both the type and blocked status are displayed graphically on each re draw of the game.
Relequestual
When I'm stuck like you seem to be, I step through the code in a visual debugger and see if I understand and can explain all steps and see if there is something that catches my eye as unexpected. (This inclused checking variable values after ecery step.)
rsp
Now you say that, it's what I've done for about an hour from 9 or so. Think I have worked out what it is. Will explain if it works
Relequestual
A: 

What is the order of the list of Person objects (that you are running this code over)? If you iterate through them all in some pre-set order then you could be introducing a bias there because the results of the first one will have an effect on the second (and so on).

You might try shuffling the list of people before processing them.

Dolphin
How would the result of the first one effect the second one?
Relequestual
I might be reading too much into the snippet given, but I am assuming that setNextTarget could change the results of of blocked(x,y) if the player moves into/out of the block around another player.
Dolphin
people can move into each other. if they are on a square, it is not considered blocked.
Relequestual
+3  A: 

It's hard to find any problem in that code, but you're calling a lot of methods outside that code. Starting with getTopLeftPoint(), my first suspect, are you sure that you are using the same reference for the coordinates. If getTopLeftPoint() returns the leftmost, topmost point but setNextTarget sets the middle point, we have the problem!

Next: screenToGame() also changes the coordinate... could also be the source of the problem.

Is that if part correct

!Board.getBoard().getSquare(x, y).getSquareType().equals(SquareType.path)

you can only walk a square that is NOT a path? (nothing to do with the problem...)

Another: blocked() not sure what that realy does. If all squares dwonside and to the right of the person get blocked as it walks, he will end up in the top left corner.

The last, almost same as first: setNextTarget() same coordinate reference as screenToGame()?

Summary: better create some kind of unit test to test only the random wandering algorithm without all these "noise".
I got nice results doing so (nothing blocked, no path)... Person visit each corner almost the same number of times.

One more point: I would prefer to use a Random instead of shuffling all the list to get just one element of it...

Carlos Heuberger
You raise very good points here.getTopLeftPoint() returns the screen co ordinate of the person, divided by 30 using screenToGame(). setNextTarget() uses the Game co-ordinates rather than screen, meaning each square counts as 1 value, starting from 0 in the top left most square.The path thing, is regarding the actual square type rather than a persons path, referring to the path into the building.Blocked method works when doing general path finding, so cant see any issue there.Did you find my project's svn? I will indeed try as you suggest, strip out the noise.Thanks for your input!
Relequestual
SVN? no, have you posted it?
Carlos Heuberger
no. I just though you may have somehow randomly found it.Anyway, I took your advice, filtered stuff out, and it made no difference.Decided it was time for some hardcore debugging! Found this reason (I think) and will update this question when I have fixed it.
Relequestual
I was confused as to how you got nice results. Guess you wrote out a quick interface or something.
Relequestual
I copyed your code in a method, created the missing classes and methods (eclipse helped a lot): getTopLeftPoint() returns (x,y); screenToGame() does nothing; getSquareType() only returns path if position is invalid (outsid of limits); blocked() always returns false; setNextTarget just sets (x,y) and accumulates some statistics. Finally I wrote a little loop to call the calculation.
Carlos Heuberger
Here you can see the code I wrote: http://simu.wikidot.com/local--files/java:java/game.zip
Carlos Heuberger
I fixed it. You guided me in the right direction. Needed to use the debugger in order to see my problem. The code was fine, when and where it was called from was the problem! :) Will upload a quick video tomorrow
Relequestual
Thank you for your help again. You are clearly someone with expertise and put some time into looking into this for me.
Relequestual
No problem, I liked the idea and now I've a "whole" game framework based on that code to let one or more "Person" walk around and do some histograms (as I don't have enough to do at work...:--))
Carlos Heuberger
Thats rather impressive! Cheers again for your help!
Relequestual