我有下面的课程,它将AI移到给定的工厂,这很好,但是感觉真的很乱。遵循这样的想法,即从工厂位置开始获取AI的x和y位置,如果值是正数,我们将加30;如果值是负数,我们将减去30

如果您需要更多逻辑解释让我知道。

private void movePosistion(Plant p) {
        /*
         * set which direction to move,the number generated relates to the
         * direction as below:
         *      1   2   3 
         *      4       5
         *      6   7   8
         */
        int xdiff = p.getXpos() - xpos;
        int ydiff = p.getYpos() - ypos;
        if (xdiff > 0){
            if (ydiff > 0){
                //8
                xpos += 30;
                ypos += 30;
            }else if(ydiff < 0){
                //3
                xpos += 30;
                ypos -= 30;
            }else{
                //5
                xpos += 30;
            }
        }else if(xdiff < 0){
            if (xdiff > 0){
                //6
                xpos -= 30;
                ypos += 30;
            }else if(xdiff < 0){
                //1
                xpos -= 30;
                ypos -= 30;
            }else{
                //4
                xpos -= 30;
            }
        }else{
            if (ydiff < 0){
                //7
                ypos -= 30;
            }else{
                //2
                ypos += 30;
            }
        }

        if (xpos > 720)
            xpos = 1;
        if (ypos > 720)
            ypos = 1;
        if (xpos < 1)
            xpos = 720;
        if (ypos < 1)
            ypos = 720;
    }


评论

对于某些x,您的头寸是否总是“ 1 + 30x”?我认为这是一个决定,最终可能会导致一个错误。通常,如果宽度为720点,则坐标的范围最好为0到719;这样,数学运算就可以更自然地拟合,而不必添加或删除1;例如,您可以在最后几行中使用模数运算符:xpos = xpos%720。

我编辑了答案以提供更多解释。

我刚刚注意到该函数称为“ movePosistion”;我在回答中更正了它。顺便说一句,感谢您接受它。

#1 楼

这是我的重构:

private void movePosition(Plant p) {
    xpos += Integer.signum(p.getXpos() - xpos) * DELTA_X;
    ypos += Integer.signum(p.getYpos() - ypos) * DELTA_Y;

    xpos = Math.floorMod(xpos, MAX_X);
    ypos = Math.floorMod(ypos, MAX_Y);
}


1。 Signum

signum实现了sign函数,对于负整数,零和正整数分别给出-1、0或1。它以非常简短易读的表达式对原始逻辑进行编码。
符号乘以适当数量的单位(代码中未详细说明常数,顺便说一句)。

我什么都没有原则上反对决策表(请参阅rolfl的答案),但我认为这不是必需的。我也可以做到!

摘自Code Complete 2nd Edition,第19章:一般控制问题,第431页:


使用决策表替换复杂条件

有时您会进行涉及多个变量的复杂测试。 [...]


...,有时你不知道;-)

2。模量

原始代码的环绕行为可以通过模运算来实现。请注意,您不能仅在Java中使用%运算符,因为它会计算余数,当您的位置低于零时,它可以为负。 floorMod操作实际上是在计算模块化算术。

现在,您可能会认为:这是错误的,原始代码无法像这样工作!


首先,OP的坐标范围是1到720(包括两端)。我对此有疑问,因此在此故意更改了方法。原因是,在原始代码中,不是使用原点为(0,0)的坐标空间,而是将原点平移为(1,1)。

大多数情况下,您最终不得不向操作中添加或减去1。最终可能导致偏离一对一的错误。但是,如果坐标是从0到719,则环绕逻辑仅由模运算给出。


您可能会说:“但是原始行为不像模数!”为什么这样说因为,假设x是710,然后我加30:使用模数,我回到20,而使用OP的代码,我将是1,因为当我们越界时,我们回到最小(或最大)位置。

为此,我回答说,您永远不在710位置,而仅在0、30、60,...,690位置。至少,这是我从OP的代码中了解到的,该对象似乎在网格上移动,而不是自由移动。我想该对象最初始终位于30的倍数,然后只能移动30个单位。

如果我错了,那么(1)对不起,(2)是的,模数并不是完全正确的答案;您最好使用rolfl的boundPos函数。



评论


\ $ \ begingroup \ $
我真的很喜欢这个答案,主要是因为它的简单性,给出的所有答案都是很好的,而且采取的方法也非常不同。
\ $ \ endgroup \ $
–扎克·鲍威尔(Zac Powell)
2014年4月11日12:54

#2 楼

我同意@rolfl,您需要提取方法并使用决策表。

摘自Code Complete第二版,第19章:常规控制问题,第431页:


使用决策表来替换复杂的条件

有时您具有包含多个变量的复杂测试。使用决策表执行测试而不是使用if或case可能会有所帮助。决策表查找起初更容易编码,只需要几行代码,而没有棘手的控制结构。复杂性的这种最小化使出错的机会最小化。如果数据发生更改,则可以更改决策表而无需更改代码。您只需要更新数据结构的内容即可。


为了便于阅读,在这里我会更加详细。首先,列举一个可能的区别:

public enum Difference {
    ZERO,
    BELOW_ZERO,
    ABOVE_ZERO
}


然后是一个包含可用规则的​​类:

public class MoveLogic {

    private final Map<Rule, Move> rules = new LinkedHashMap<>();

    public MoveLogic() {
        addRule(Difference.ABOVE_ZERO, Difference.ABOVE_ZERO, 30, 30); // 8
        addRule(Difference.ABOVE_ZERO, Difference.BELOW_ZERO, 30, -30); // 3
        addRule(Difference.ABOVE_ZERO, Difference.ZERO, 30, 0); // 5

        addRule(Difference.BELOW_ZERO, Difference.ABOVE_ZERO, -30, 30); // 6
        addRule(Difference.BELOW_ZERO, Difference.BELOW_ZERO, -30, -30); // 1
        addRule(Difference.BELOW_ZERO, Difference.ZERO, -30, 0); // 4

        addRule(Difference.ZERO, Difference.BELOW_ZERO, 0, -30); // 7
        addRule(Difference.ZERO, Difference.ABOVE_ZERO, 0, 30); // 2
    }

    private void addRule(Difference xDifference, Difference yDifference, int x, int y) {
        rules.put(new Rule(xDifference, yDifference), new Move(x, y));
    }

    public Move getMove(Difference xDifference, Difference yDifference) {
        return rules.get(new Rule(xDifference, yDifference));
    }
}


用法:

final Difference xDifference = calculateDifference(plant.getXpos(), xpos);
final Difference yDifference = calculateDifference(plant.getXpos(), ypos);

final Move move = moveLogic.getMove(xDifference, yDifference);
xpos += move.getX();
ypos += move.getY();


最后,创建Difference引用的辅助方法:

private Difference calculateDifference(final int plantPosition, final int position) {
    final int difference = plantPosition - position;
    if (difference > 0) {
        return Difference.ABOVE_ZERO;
    }
    if (difference < 0) {
        return Difference.BELOW_ZERO;
    }
    return Difference.ZERO;
}


Rule

public class Rule {

    private Difference xDifference;
    private Difference yDifference;

    public Rule(Difference xDifference, Difference yDifference) {
        this.xDifference = xDifference;
        this.yDifference = yDifference;
    }

    // generate hashCode and equals, the Map needs it!

}


Move

public class Move {

    private int x;
    private int y;

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

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}


看起来似乎太冗长,但可以避免重复出现以及具有索引和数组的魔术计算。

评论


\ $ \ begingroup \ $
似乎太冗长了。老实说,我什至会说过度设计。为什么要在运行时表示规则(这不在规范中)?为什么在类中编码“差异”的概念?为什么要采用复杂的方法?不要添加仅设置和获取值的getter / setter:使用公共字段;将来,只要有必要,您总是可以朝着更高的复杂性发展,但前提是有必要。这只是我的意见,希望对您有所帮助:-)
\ $ \ endgroup \ $
– coredump
2014-4-10 21:11



#3 楼

预计算可能的动作,并将条件转换为简单的数学表达式是解决此类问题的好方法。

您的代码也遭受重复块和重复逻辑的困扰:DRY-Don' t重复一遍。

通过提取函数来解决这个问题。

最后,您的代码中充满了魔幻数字,容易泄漏错误.....

考虑以下代码,尽管在代码行上比您的代码长,但实际上更具可读性(我承认,复杂的2D数组除外)。

/>

评论


\ $ \ begingroup \ $
@coredump-谢谢。。。你是对的。立即修复。
\ $ \ endgroup \ $
–rolfl
2014年4月10日在20:42

\ $ \ begingroup \ $
尽管原则上说表是一种好方法,但我认为这里不需要这样做。看到我的答案(我重用boundPos,顺便说一句)
\ $ \ endgroup \ $
– coredump
2014年4月10日在20:58

\ $ \ begingroup \ $
@coredump-很好。您的答案也是。您应该访问2nd Monitor聊天室以发现Code Review的另一面。
\ $ \ endgroup \ $
–rolfl
2014年4月11日上午10:51

#4 楼

这是我的看法。由于x和y中的运动是独立的,因此我为一个方向的运动定义了一个类。

但是您至少可以使用x和y中的运动是独立的想法。

评论


\ $ \ begingroup \ $
在Coordinate构造函数中,为什么当initialValue为负数时也不抛出异常?
\ $ \ endgroup \ $
– coredump
2014年4月10日在21:35

\ $ \ begingroup \ $
已更正。我想还有许多其他问题需要修复。例如,目前可以将Closer移至具有不同worldSize的坐标。
\ $ \ endgroup \ $
–toto2
2014年4月10日在22:28