Java AI - оптимальное прохождение булевых массивов

Я кодирую объекты ИИ для игры.

Моя цель - оптимизировать код, чтобы я мог одновременно обрабатывать как можно больше процессов ИИ.

Игра представляет собой двумерную физику, поэтому я уже имею дело с узким местом при обработке столкновений, так что ИИ должен быть эффективным.

Команды, с которыми я имею дело, - это простое движение ASDF + QE, щелчки и положения мыши.

Я понимаю, что моделирование движений ИИ на реальных клавишах / мышке может быть не очень хорошей идеей. Но я хочу начать здесь, а потом разобрать.

Я решил использовать логические массивы для представления команд следующим образом:

//Commands: continue, up, down, left, right, turnQ, turnE, mouse0, mouse1 

boolean[] moveBools = {false, false, false, false, false, false, false, false, false}; 

//The first boolean in the array 'continue' will terminate the loop if its true.

Я борюсь с тем, как передать эти данные в мою функцию, которая фактически перемещает физические тела. Я мог бы просто передавать целый логический массив в каждой итерации игры или передавать / changes / из итерации в итерацию.

Этот тип программирования соединяет настоящую булеву математику, что мне далеко не известно. Я предполагаю, что на самом деле я мог бы создавать операторы, как это может быть сделано в C++, чтобы состояния массива вычитались друг из друга и передавали результат.

Вопрос: учитывая предыдущий контекст, как я могу передать логические массивы с оптимальной скоростью?

3 ответа

Решение

Прямое преобразование того, что вы написали, было бы использовать BitSet, Это позволяет получить пересечение / или, и, и не битов. Вы могли бы сделать:

BitSet move = new BitSet(9);
move.set(1); // going up
move.set(2); // also going down?

Вы можете отправить это относительно упакованным (например, по сети), передавая toByteArray() и реконструировать его на принимающей стороне с new BitSet(inputByteArray)

Сколько из этих логических значений будет или должно быть установлено в истинное выражение состояния?
Если мы угадаем, мы можем сделать что-то попроще. Например, когда continue равно true, все остальные значения игнорируются, и не имеет смысла (для меня) иметь и вверх, и вниз, или как влево, так и вправо. Хотя, возможно, имеет смысл нажимать одну или обе кнопки мыши вместе с другими состояниями и аналогично иметь что-то вроде вверх и влево и одновременно включать Q, но проблему можно упростить, если мы допустим только одно состояние.

IE Если вы представляете состояние, я бы определил перечисление состояний и посчитал, что это сделано, фактически передавая эквивалент массива bool как одно число, количество перечислений.

public enum SingleAiMoves {
    NONE, STOP,
    LEFT, RIGHT,
    UP, DOWN,
    TURN_Q, TURN_E,
    MOUSE1, MOUSE2
}

Это позволяет только одно состояние за один раз.
Если вам нужно объединить несколько состояний, но только действительные, вы можете определить логическую группу состояний, например:

public enum AiMove {
    //         u/d    l/r    q/e    m1     m2     stop
    STOP      (null,  null,  null,  false, false, true),
    NONE      (null,  null,  null,  false, false, false),
    UP        (true,  null,  null,  false, false, false),
    DOWN      (false, null,  null,  false, false, false),
    LEFT      (null,  true,  null,  false, false, false),
    RIGHT     (null,  false, null,  false, false, false),
    TURN_Q    (null,  null,  true,  false, false, false),
    TURN_E    (null,  null,  false, false, false, false),
    MOUSE_1   (null,  null,  null,  true,  false, false),
    MOUSE_2   (null,  null,  null,  false, true,  false),
    MOUSE_1_2 (null,  null,  null,  true,  true,  false),
    UP_LEFT   (true,  true,  null,  false, false, false),
    DOWN_LEFT (false, true,  null,  false, false, false),
    ... // some 108(?) combinations listed here.... ;

    private final Boolean upDown;
    private final Boolean leftRight;
    private final Boolean turnQE;
    private final boolean mouse1;
    private final boolean mouse2;
    private final boolean stop;

    AiMove(Boolean upDown, Boolean leftRight, Boolean turnQE,
           boolean mouse1, boolean mouse2, boolean stop) {
        this.upDown = upDown;
        this.leftRight = leftRight;
        this.turnQE = turnQE;
        this.mouse1 = mouse1;
        this.mouse2 = mouse2;
        this.stop = stop;
    }
    public boolean isStopped() { return stop; }
    public boolean hasUp() { return Boolean.TRUE.equals(upDown); }
    public boolean hasDown() { return Boolean.FALSE.equals(upDown); }
    public boolean hasLeft() { return Boolean.TRUE.equals(leftRight); }
    public boolean hasRight() { return Boolean.FALSE.equals(leftRight); }
    public boolean hasTurnQ() { return Boolean.TRUE.equals(turnQE); }
    public boolean hasTurnE() { return Boolean.FALSE.equals(turnQE); }
    public boolean hasMouse1() { return mouse1; }
    public boolean hasMouse2() { return mouse2; }
}

Лично я думаю, что если вы пойдете по этому пути, вы можете представить более подробные состояния, чем некоторые внутренние входы контроллера.

Кроме того, вы можете получить фантазию с битовыми флагами или маскировкой, или выглядеть чище, не фантазии, EnumSet<SingleAiMoves> но в отличие от перечисленного выше, и, как ваш массив и BitSet, это позволило бы состояниям, которые идут и вверх и вниз, и / или влево и вправо, чтобы существовать.

Наконец, поскольку нахождение перечисления предположительно допустимых комбинаций утомительно и относительно нечитабельно в моем примере выше, вы можете использовать BitSet внутри перечисления и прояснить конструкцию перечисления. Вероятно, таким образом он использует немного больше памяти.

public enum AiMove {
    NONE      (),
    UP        (0),
    DOWN      (1),
    LEFT      (2),
    RIGHT     (3),
    TURN_Q    (4),
    TURN_E    (5),
    MOUSE_1   (6),
    MOUSE_2   (7),
    STOP      (8),
    MOUSE_1_2 (MOUSE_1, MOUSE_2),
    UP_LEFT   (UP, LEFT),
    DOWN_LEFT (DOWN, LEFT),
    ... // some 108(?) combinations listed here.... ;

    private final BitSet bitsUDLRQE12S = new BitSet(9);

    AiMove(int index) {
        bitsUDLRQE12S.set(index);
    }

    AiMove(AiMove... moves) {
        for (AiMove move : moves) {
            bitsUDLRQE12S.or(move.getBitSet());
        }
    }

    private BitSet getBitSet() { return bitsUDLRQE12S; }

    public boolean hasUp() { return bitsUDLRQE12S.get(0); }
    public boolean hasDown() { return bitsUDLRQE12S.get(1); }
    public boolean hasLeft() { return bitsUDLRQE12S.get(2); }
    public boolean hasRight() { return bitsUDLRQE12S.get(3); }
    public boolean hasTurnQ() { return bitsUDLRQE12S.get(4); }
    public boolean hasTurnE() { return bitsUDLRQE12S.get(5); }
    public boolean hasMouse1() { return bitsUDLRQE12S.get(6); }
    public boolean hasMouse2() { return bitsUDLRQE12S.get(7); }
    public boolean isStopped() { return bitsUDLRQE12S.get(8); }
}
enum Command {
    continuing, up, down, left, right, turnQ, turnE, mouse0, mouse
}

void process(EnumSet<Command> commands) {
    while (commands.isEmpty()) {
        ...
    }
    ...
}

Перечисление возможно. EnumSet - это нечто оптимальное, например BitSet, а EnumMap - как массив.

В противном случае используйте битовые маски и передайте int. Битовые операции над int просты и немного более эффективны.

Java передает значения, но в случае массивов она передает ссылку на массив по значению - отдельные элементы не передаются, только начальная ссылка на массив.

Это означает, что при передаче логического массива вы передаете не весь массив, а просто значение, которое ссылается на массив. Для ясности и скорости это может быть вашим лучшим выбором.

Если вы пытаетесь оптимизировать не по скорости, а по памяти, вы можете рассмотреть возможность использования java.util.BitSet.

Другие вопросы по тегам