Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a probability algorithm that can be maintained easily?

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?

like image 910
code4j Avatar asked May 31 '13 10:05

code4j


2 Answers

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.

like image 135
Zutty Avatar answered Oct 03 '22 22:10

Zutty


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();
}
like image 36
Peter Lawrey Avatar answered Oct 03 '22 21:10

Peter Lawrey