Question

Supposed I want to create a game. At the start of the game, the player will pick a monster.

It's easy to picks the monster fairly.

// get all monsters with equal chance
public Monster getMonsterFair(){
    Monster[] monsters = {new GoldMonster(), new SilverMonster(), new BronzeMonster()};
    int winIndex = random.nextInt(monsters.length);
    return monsters[winIndex];
}

And picks the monster unfairly.

// get monsters with unequal chance
public Monster getMonsterUnFair(){
    double r = Math.random();
    // about 10% to win the gold one
    if (r < 0.1){
        return new GoldMonster();
    }
    // about 30% to winthe silver one
    else if ( r < 0.1 + 0.2){
        return new SilverMonster();
    }
    // about 70% to win the bronze one
    else {
        return new BronzeMonster();
    }   
}

The problem is that, when I add a new monster to the game, I have to edit the if-else. Or I change the chance of winning GoldMonster to 0.2, I have to change all 0.1 into 0.2 .It's ugly, and not easily maintained.

// get monsters with unequal change & special monster
public Monster getMonsterSpecial(){
    double r = Math.random();
    // about 10% to win the gold one
    if (r < 0.1){
        return new GoldMonster();
    }
    // about 30% to win the silver one
    else if ( r < 0.1 + 0.2){
        return new SilverMonster();
    }
    // about 50% to win the special one
    else if ( r < 0.1 + 0.2 + 0.2){
        return new SpecialMonster();
    }
    // about 50% to win the bronze one
    else {
        return new BronzeMonster();
    }
}

How can this probability algorithm can be refactored so that the codes can be maintained easily when new monster is added and the chances of winning monsters are adjusted?

Était-ce utile?

La solution

Basically what @Egor Skriptunoff said. This should scale easily. You could use a collection of Class<Monster> if you didn't want to use an enum.

enum Monster {
    GOLD(1),
    SILVER(3),
    BRONZE(6) // pseudo probabilities

    private int weight;
    // constructor etc..
}

public Monster getMonsterSpecial() {
    List<Monster> monsters = new ArrayList<>();

    for(Monster monsterType : Monster.values()) {
        monsters.addAll(Collections.nCopies(monsterType.getWeight(), monsterType)); 
    }

    int winIndex = random.nextInt(monsters.length);
    return monsters.get(winIndex);
}

You could perhaps make the enum Monsters plural, and have it point to a Class<? extends Monster> if you still want to instantiate monster classes. I just tried to make the example clearer.

Autres conseils

I would uses a total weight which increases with each monster added.

private final Random rand = new Random();

public Monster getMonsterSpecial() {
    int weight = rand.nextInt(1+2+2+5);
    if ((weight -= 1) < 0) return new GoldMonster();
    if ((weight -= 2) < 0) return new SilverMonster();
    if ((weight -= 2) < 0) return new SpecialMonster();
    // 50% chance of bronze
    return new BronzeMonster();
}

This is based off of Peter's answer, just more maintainable. All you have to do is add a new monster to the array and add the weight to the total weight - this can easily be extended to happen during runtime if you wish (thus, never mind making code changes, you don't even need to restart the program to add a monster (assuming the rest of your program allows this)).

Monster class:

Have an int weight variable for each monster.

If the weights are 1,2 and 7, the respective probabilities will be 10%, 20% and 70% (calculated as 100*x/(1+2+7)).

Globals:

Random rand = new Random();
int totalMonsterWeight;
Monster[] monsters; // set this up somewhere

Global weight initialization:

totalMonsterWeight = 0;
for (Monster monster: monsters)
  totalMonsterWeight += monster.getWeight();

Get-monster function:

public Monster getMonster()
{
  int weight = rand.nextInt(totalMonsterWeight);
  for (Monster monster: monsters)
    if ((weight -= monster.getWeight()) < 0)
      return monster.getClass().newInstance();
}

The above is a lazy way (probably not the best way) to return a new instance during each call. The right way is probably using a Factory pattern.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top