2

EDIT: This is a different question to How do I generate random integers within a specific range in Java? because I requested the solution uses the methods

public int getRandomNumber(int start,int end)
{
int normal = Greenfoot.getRandomNumber(end-start+1);
return normal+start;
}

and

getRandomNumber(int, int)

Good afternoon. I've been working on a project in Greenfoot, with Java, where the main character is on a 7x7 screen with 3 coins. I have written this code:

public class FirstLevel extends World
{
public int getRandomNumber(int start,int end)
{
   int normal = Greenfoot.getRandomNumber(end-start+1);
   return normal+start;
}

/**
 * Constructor for objects of class FirstLevel.
 * 
 */
public FirstLevel()
{    
    // Create a new world with 600x400 cells with a cell size of 1x1 pixels.
    super(9, 9, 60); 
    MainCharacter theDuckDude = new MainCharacter ();
   addObject(theDuckDude, 4, 4);
    coin coin1 = new coin();
    coin coin2 = new coin();
    coin coin3 = new coin();
   addObject(coin1, getRandomNumber(1, 7), getRandomNumber(1, 7));
   addObject(coin2, getRandomNumber(1, 7), getRandomNumber(1, 7));
   addObject(coin3, getRandomNumber(1, 7), getRandomNumber(1, 7));
}
}

So, as you can see, the three coins and duck character will appear on the screen in random locations. Here is a snippet of the code from my theDuckDude character:

Actor actor = getOneIntersectingObject(coin.class);
getWorld().removeObject(actor);

Obviously, this code shows that when my theDuckDude character touches the coin actor, the coin is removed. As you have probably seen, this poses quite a problem: if theDuckDude and a coin actor are generated on the same square, the game won't work properly: enter image description here

So, is there a way I can generate these random ints, using the

public int getRandomNumber(int start,int end)
{
   int normal = Greenfoot.getRandomNumber(end-start+1);
   return normal+start;
}

and

getRandomNumber(1, 7)

methods, how can I make it generate a random location that excludes 5 and 5 together?

Community
  • 1
  • 1
  • 1
    did you ever consider testing that they are the same and if they are generate another pair? –  Mar 08 '17 at 18:25
  • 3
    Reopened as this is a 2d problem, and not statistically obvious. – Bathsheba Mar 08 '17 at 18:46
  • 3
    Can the coins be placed on the same square? – matt Mar 08 '17 at 19:07
  • I would do something is a loop, 1, Generate the point. 2, check if it's occupied. 3, if no, out of loop. otherwise, generate it again. – antonio081014 Mar 08 '17 at 19:28
  • @matt yes im actually trying to figure that out. –  Mar 09 '17 at 07:20
  • The simplest way would be to generate the two random numbers, then check the board for anything. If there is something, then get two more random numbers and try again. Provided you don't have too many objects on the board this is only a little bit wasteful. – matt Mar 09 '17 at 09:32

4 Answers4

3

There are 49 visible squares, but 48 valid squares. Pick an integer from 0 to 47 inclusive, add 1 to exclude the middle square, and then derive x and y from there.

static final int ROWS = 7;
static final int COLS = 7;
static final int EXCLUDE_X = 5;
static final int EXCLUDE_Y = 5;

int index = ThreadLocalRandom.current().nextInt(ROWS * COLS - 1); // range: [0, 47]
if (index >= ((EXCLUDE_Y * ROWS) + EXCLUDE_X)) {        // offset the center square
  index++;
}
int x = (index % COLS);
int y = (index / ROWS);

I've stuck to ThreadLocalRandom here, but you could easily reword the same thing to work with your Greenfoot helpers.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
2

There are basically two ways, really.

The simple way is, you pick the two random integers. If you hit the duckDude position, you pick again. And again until you get a different position:

    int x;
    int y;
    do {
        x = getRandomNumber(1, 7);
        y = getRandomNumber(1, 7);
    } while (x == duckDudeX && y == duckDudeY);
    addObject(character, x, y);

It works. What is not so satisfactory about it is that even though it will eventually terminate, we know no upper bound on the number of iterations it will take.

The other option is you exclude the forbidden square already when picking the square. This is also the basic idea in a couple of the other answers. My version uses a list of valid square coordinates, so it requires a bit of initialization:

List<Pair> validPairs = new ArrayList<>(49);

    for (int x = 1; x <= 7; x++) {
        for (int y = 1; y <= 7; y++) {
            validPairs.add(new Pair(x, y));
        }
    }
    validPairs.remove(new Pair(duckDudeX, duckDudeY));

Now picking the square for a coin is straightforward:

    Pair where = validPairs.get(getRandomNumber(0, validPairs.size() - 1));
    addObject(character, where.getX(), where.getY());

The auxiliary Pair class adds some lines to your project, though:

public class Pair {
    private int x;
    private int y;

    public Pair(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Pair other = (Pair) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }

}

Edit: I was hoping you would fill in the missing parts yourself and also correct if anything didn’t agree with your code. In my code I have:

static final int duckDudeX = 4;
static final int duckDudeY = 4;

It wasn’t clear from your question and code whether (4, 4) or (5, 5) was the forbidden square. You may of course just fill in 4 or 5 wherever my code says duckDudeX or duckDudeY. If it were my code, I’d insist on declaring either variables or constants. You know best whether constants are appropriate, and you can probably also find better names.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
0

If I understand you correctly, position 5,5 is just an example and you are looking for an algorithm that generates distinct random numbers within a given range.

Usually, you would shuffle an array of indexes in beforehand and than retrieve the shuffled indexes one by one. However, if you want to pick indexes manually in between, this will not work (For example: generate a random coin -> remove a specific coin -> generate another random coin).

A solution to this problem is to implement an alternative version of the Fisher-Yates shuffle algorithm:

static void shuffle(int[] ar) {         
    Random rnd = ThreadLocalRandom.current();
    for (int i = ar.length - 1; i > 0; i--){
        int index = rnd.nextInt(i + 1);

        int a = ar[index];
        ar[index] = ar[i];
        ar[i] = a;
    }
}

As you can see, once an array item is shuffled to the end of the array, it will not chnage its position anymore. Therefore, we can remove the loop and retrieve the shuffled items or indexes one by one instead getting the same result:

public class Shuffler { 
    private final int indexCount;
    private final int[] indexes;
    private final int[] indexIndexes;
    private int nextIndexI; 

    public Shuffler(int indexCount){
        this.indexCount = indexCount;
        indexes = new int[indexCount];
        indexIndexes = new int[indexCount];
        for(int i = 0; i < indexCount; i++){
            indexes[i] = i;
            indexIndexes[i] = i;
        }
        nextIndexI = indexCount - 1;
    }

    public int nextIndex(){         
        if(nextIndexI == -1){
            return -1;
        }

        Random rnd = ThreadLocalRandom.current();       
        int i = rnd.nextInt(nextIndexI + 1);

        swap(i, nextIndexI);

        return indexes[nextIndexI--];       
    }

    public boolean pickIndex(int index){        
        if(0 > index || index >= indexCount || indexIndexes[index] > nextIndexI){
            return false;
        }

        swap(indexIndexes[index], nextIndexI);      
        nextIndexI--;

        return true;        
    }

    public boolean reinsertIndex(int index){        
        if(0 > index || index >= indexCount || indexIndexes[index] <= nextIndexI){
            return false;
        }

        nextIndexI++;       
        swap(indexIndexes[index], nextIndexI);  

        return true;    
    }

    private void swap(int i1, int i2){
        indexIndexes[indexes[i1]] = i2;
        indexIndexes[indexes[i2]] = i1;

        int tmp = indexes[i1];
        indexes[i1] = indexes[i2];
        indexes[i2] = tmp;  
    }

} 

This modification offers the opportunity to introduce a method which enables the user to pick and reinsert specific indexes in between (the helper array indexIndexes is used by these two methods and makes a fast lookup of the actual indexes possible).

Using the shuffler is easy:

//demo
static final int ROWS = 7;
static final int COLS = 7;

Shuffler shuffler = new Shuffler(ROWS*COLS);        
for(int i = 0; i < ROWS*COLS; i++){
    int index = shuffler.nextIndex();
    int x = index % COLS;
    int y = index / ROWS;
    System.out.println(x + " " + y);
}

You can of course replace the lines:

Random rnd = ThreadLocalRandom.current();       
int i = rnd.nextInt(nextIndexI + 1);

with your own getRandomNumber() method.

Calculator
  • 2,769
  • 1
  • 13
  • 18
0

The answer to this question is quite simple, but be careful!, there's something you missed. Observe the code below.

public class FirstLevel extends World
{
public int getRandomNumber(int start,int end)
{
   int normal = Greenfoot.getRandomNumber(end-start+1);
   return normal+start;
}

public FirstLevel()
{    

    super(9, 9, 60); 
    int coin1x = 0;
    int coin2x = 0;
    int coin3x = 0;
    int coin1y = 0;
    int coin2y = 0;
    int coin3y = 0;
    MainCharacter theDuckDude = new MainCharacter ();
   addObject(theDuckDude, 4, 4);
    coin coin1 = new coin();
    coin coin2 = new coin();
    coin coin3 = new coin();
coin1x = getRandomNumber(1, 9);
coin1y = getRandomNumber(1, 9);
coin2x = getRandomNumber(1, 9);
coin2y = getRandomNumber(1, 9);
coin3x = getRandomNumber(1, 9);
coin3y = getRandomNumber(1, 9);

while (coin1x == 4 && coin1y == 4)
{
    coin1x = getRandomNumber(1, 9);
    coin1y = getRandomNumber(1, 9);

}
while (coin2x == 4 && coin2y == 4)
{
    coin2x = getRandomNumber(1, 9);
    coin2y = getRandomNumber(1, 9);
    while (coin2y == coin1y && coin2x == coin1x);
    { coin2x = getRandomNumber(1, 9);
      coin2y = getRandomNumber(1, 9);
    }
    while (coin2y == coin3y && coin2x == coin3x);
    {
        coin2x = getRandomNumber(1, 9);
        coin2y = getRandomNumber(1, 9);
    }
    while (coin1x == coin3x && coin1y == coin3y);
    {
        coin3x = getRandomNumber(1, 9);
        coin3y = getRandomNumber(1, 0);
    }
}
while (coin3x == 4 && coin3y == 4)
{
    coin3x = getRandomNumber(1, 9);
    coin3y = getRandomNumber(1, 9);
}


int x;
int y;
addObject(coin1, coin1x, coin1y);
   addObject(coin2, coin2x, coin2y);
   addObject(coin3, coin3x, coin3y);

Notice this code has two new things you missed. First, this code will check if the coins are placed on the 4, 4 square:

while (coin1x == 4 && coin1y == 4)
{
coin1x = getRandomNumber(1, 9);
coin1y = getRandomNumber(1, 9);

}

But also notice the code will check if two coins are on the same square:

while (coin2y == coin1y && coin2x == coin1x);
{ coin2x = getRandomNumber(1, 9);
  coin2y = getRandomNumber(1, 9);
}