【问题标题】:How to write a probability algorithm that can be maintained easily?如何编写一个易于维护的概率算法?
【发布时间】:2013-05-31 10:27:15
【问题描述】:

假设我想创建一个游戏。游戏开始时,玩家会选择一个怪物。

公平挑选怪物很容易。

// 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];
}

并且不公平地挑选怪物。

// 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();
    }   
}

问题是,当我在游戏中添加新怪物时,我必须编辑 if-else。 或者我把赢得GoldMonster的几率改成0.2,我得把0.1全改成0.2 .它很丑,而且不容易维护。

// 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();
    }
}

如何重构这个概率算法,以便在添加新怪物和调整赢得怪物的机会时轻松维护代码?

【问题讨论】:

  • 从字符串GSSBBBBBBB的随机位置选择字符。这样的字符串很容易改变。

标签: java algorithm refactoring maintainability


【解决方案1】:

基本上是@Egor Skriptunoff 所说的。这应该很容易扩展。如果您不想使用 enum,可以使用 Class&lt;Monster&gt; 的集合。

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);
}

如果您仍想实例化怪物类,您也许可以将枚举设为 Monsters 复数,并将其指向 Class&lt;? extends Monster&gt;。我只是想让这个例子更清楚。

【讨论】:

  • 没有迹象表明这实际上会发生,但如果你有 1000 多个重量可能在 1000 左右的怪物,这可能会成为一个相当大的瓶颈。
  • 我想你的意思是monster.size()
【解决方案2】:

我会使用随着每增加一个怪物而增加的总重量。

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();
}

【讨论】:

  • 添加怪物时仍然需要添加if语句。
  • @Dukeling 无论哪种方式,您都必须添加一些东西,而且我认为一行不需要太多维护。有时,拥有更全面的解决方案可能会掩盖它的真正作用。 ;)
  • 我认为理想的解决方案是您可以添加怪物而无需更改代码(因此可以在运行时完成),my answer 可以提供。并不是说我认为任何人都会在运行时进行游戏更新,即使是 MMO。
  • @Dukeling 有时你是对的,你需要一些简单且数据驱动的东西,例如从属性文件或 CSV。其他时候,您需要一些东西,您可以在一个地方准确地看到它在做什么。这真的取决于他认为对于他的用例来说更简单的 OP。
【解决方案3】:

这是基于Peter's answer,只是更易于维护。您所要做的就是向数组中添加一个新怪物并将权重添加到总权重中 - 如果您愿意,这可以很容易地扩展为在运行时发生(因此,不要介意更改代码,你不会甚至需要重新启动程序来添加怪物(假设您的程序的其余部分允许这样做)。

怪物类:

为每个怪物设置一个int 权重变量。

如果权重为 1,2 和 7,则分别为 10%、20% 和 70%(计算为 100*x/(1+2+7))。

全局:

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

全局权重初始化:

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

获取怪物功能:

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

以上是在每次调用期间返回一个新实例的一种惰性方式(可能不是最好的方式)。正确的方法可能是使用Factory pattern

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    相关资源
    最近更新 更多