Выполнить эту задачу в C ++; Миграция с AS3.0 - PullRequest
1 голос
/ 22 февраля 2010

У меня слишком много информации, чтобы работать с , поэтому сейчас я рассмотрю вопрос, на который дан ответ, пока не смогу разобраться во всем и принять решение об окончательной реализации! Спасибо большое GF и Саймон Бьюкен . Хотел бы я принять оба ваших ответа, так как они оба являются определенными возможностями!

Дополнительная / пересмотренная концептуальная информация в соответствии с предложением:

Что я собираюсь сделать;

Я создаю игру. В этой игре каждый используемый объект является экземпляром класса DOBJ. Класс TUR расширяет класс DOBJ. Класс SHO расширяет класс TUR.

Каждый класс TUR имеет массив SHO, хранящийся в его массиве SHOARR. Каждому экземпляру SHO должен быть присвоен набор инструкций.

Я точно знаю, что мог бы создать тысячи различных классов SHO, инструкции которых были заданы во время построения.

Однако, учитывая, что у меня будет так много разных действующих экземпляров SHO, меня заинтересовал другой способ передачи набора инструкций. Через конструкцию ШО будет идеальным.

Инструкции, которые я пытаюсь передать каждому SHO, являются простыми операторами if;

if(frame > 64) { rotation += 4; };<br> if(state == 0 && frame < 32) { xs = 12; ys = 12; state = 1; };

Оригинальный вопрос

Миграция с ActionScript3.0 на C ++ действительно оказывается пробной. Спасибо тем, кто уже ответил на мои вопросы, а также тем, кто открыл стек переполнение в первую очередь. На вопрос ... (TL; DR возле дна, чтобы перейти непосредственно к вопросу)

Я пытаюсь применить ту же логику, что и в AS3.0, к своему проекту в C ++, и это не очень хорошо.

В AS3.0 я привык подбирать любой тип данных в массив. Это сделало вещи довольно простыми. Теперь, когда я столкнулся с разработчиком C ++, я понял, что больше не могу этого делать.

Так что теперь я застрял с проблемой переписывания маленькой системы ИИ на новом языке, где движущая точка системы даже не совместима!

Вот пример фрагмента кода, который я писал в AS3.0;

AI[NUM][1]( OBJ, AI[NUM][2], AI[NUM][3] );

AI - массив, NUM - целое число, OBJ - экземпляр класса.

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

В данном случае;

AI[NUM][1] будет функцией AI[NUM][2] будет переменной AI[NUM][3] будет числом

Обычно мой ИИ запускался при вызове функции для изменения или сравнения переменной с числом.

Примером будет;

CompareST( someObject, "x", 500 );

и верните true, если переменная someObject x была меньше (ST) 500.

Сам массив AI был просто заполнен массивами вызовов, подобных этому.

Совершенно новичок в C ++. Я понятия не имел, как это сделать, поэтому я немного поискал и прочитал множество различных веб-сайтов и пришел к выводу, что мне следует изучить указатели на функции.

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

TL; DR

EDIT ++:

Что мне нужно для каждого объекта - это набор инструкций, которые нужно проверять каждый кадр. Однако для каждого экземпляра класса инструкции должны быть разными.

Я планирую иметь МНОЖЕСТВО разных экземпляров, поэтому создавать классы для каждого из них нецелесообразно.

Таким образом, мне нужен был способ передать набор инструкций каждому через его конструктор и прочитать + выполнить их в любое время, когда вызывается их функция think ().

Моей конечной целью (помимо выяснения лучшего способа решения этой проблемы) было бы иметь возможность иметь массив вызовов функций, например;

A[n][0]( O, A[n][1], A[n][2] );

Где; O это экземпляр, который изменяет функция A[n][0] - это функция (равенство или сравнение) A[n][1] - переменная, например; «x», O["x"] (или указатель на эту переменную в случае C ++) A[n][2] - это значение, по которому переменная изменяется или сравнивается с ней.

И я не уверен, как бы переписать это в C ++ или изменить его для работы по-другому.

Последствия / Дополнительная информация

Что я на самом деле намереваюсь сделать, так это дать объекту набор инструкций во время его создания через конструктор. Например, при создании дать объекту инструкцию ждать 64 кадра, а затем вращаться в противоположном направлении, было бы что-то вроде этого;

t.AI = [ [ 1, AIF.CompareET, "STATE", 0, AIF.CompareGT, "FRAME", 64, 0, AIF.EqualityAT, "baseRotation", 180, AIF.EqualityET, "STATE", 1 ] ];

в псевдокоде;

(1 в массиве обозначает, как читать остальную часть массива, в этом случае все до нечетного 0 [то, что следует после 64) является сравнением. Если что-то из этого не удастся, все, что после 0 будет не смотреть)

Сравнение STATE равно (ET) 0, если true
Сравните FRAME больше, чем (GT) 64, если true
Добавьте 180 к (AT) baseRotation, установите STATE равным 1

Извините, что это оказалось очень долго. Надеюсь, это понятно, и я не прошу чего-то глупо трудного объяснить.

Ответы [ 3 ]

1 голос
/ 22 февраля 2010

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

Одной из возможностей будет использование Boost.Any (или лучше, Boost.Variant , если вы используете только фиксированный набор типов):

typedef void (*Function)(Object*, const std::string&, boost::any&);
std::vector<Function> functions;

Учитывая некоторую функцию:

void f(Object* obj, const std::string& name, boost::any& value) {
    // ...
}

Вы можете сохранить и назвать его так же, как в вашем примере:

functions.push_back(&f);
functions[0](obj, "x", boost::any(500));

Чтобы использовать декларативный синтаксис, мне на ум приходят три варианта:

  • Вы используете аналогичный подход и имеете центральную функцию «интерпретатор» , например. на основе переключателя (не забывайте переключаться на целые числа или указатели на члены вместо строк, если вам нужна производительность)
  • вы изобретаете свой собственный язык и генерируете код C ++ из файлов описания
  • вы создаете функциональные объекты декларативным способом

Для создания композиции вы можете использовать Boost.Bind или что-то вроде пользовательских объектов, представляющих операции:

struct Operation {
    virtual ~Operation() {}
    virtual bool operator()(Object&) = 0;
};    

template<class T>
struct GreaterThen : Operation {
    typedef T Object::*Member;
    Member member;
    const T value;
    CompareGT(Member member, const T& value) : member(member), value(value) {}
    bool operator()(Object& obj) { return (obj.*member > value); }
};

template<class T>
struct SetTo : Operation {
    typedef T Object::*member;
    Member member;
    const T value;
    SetTo(Member member, const T& value) : member(member), value(value) {}
    bool operator()(Object& obj) { obj.*member = value; return true; }
};

Теперь мы можем создавать списки операций:

typedef std::vector<Operation*> OpList;
OpList operation;
operations.push_back(new GreaterThen<int>(&Object::Frame, 64));
operations.push_back(new SetTo<int>(&Object::State, 1));

Мы можем использовать вспомогательные функции, чтобы избежать необходимости указывать типы шаблонов:

template<class T>
Operation* opGreaterThen(T Object::*mem, const T& val) {
    return new GreaterThen<T>(mem, val);
}

Предполагая, что аналогичный помощник для SetTo и используя Boost.Assign выше становится:

OpList operations = boost::assign::list_of
    (opGreaterThen(&Object::Frame, 64)) 
    (opSetTo      (&Object::State,  1));

Выполнение операций становится следующим:

OpList::iterator it = operation.begin();
for( ; it != operations.end(); ++it) {
    Operation& op = *it; // just for readability
    if(!op(someObject)) break; // stop if operation returns false        
}
1 голос
/ 22 февраля 2010

Хотя возможно (хотя и сложно) иметь массив произвольных типов, он вам почти никогда не нужен, поскольку вы должны знать что-то о том, что и где делать с этим интересным: например, ваш пример 'TL; DR' выглядит примерно так:

struct AIRule {
    // Can only handle comparing ints, see later for more general solution.
    typedef bool compare_type(AIObject*, AIObject::*int, int);
    compare_type* compare;
    AIObject* object;
    AIObject::int* member;
    int comparand;
};

Так что теперь вы можете сделать что-то вроде:

bool ai_equal(AIObject* object, AIObject::int* member, int comparand) {
    return object->*member == comparand;
}

...
    ai[n].compare = &ai_equal;
    ai[n].object = some_object;
    ai[n].member = &AIObject::some_member;
    ai[n].comparand = 50;
...
    if (ai[n].compare(ai[n].object, ai[n].member, ai[n].comparand)) {
        ...
    }

Это просто перемещает проблему любого типа из массива правил в member. C ++ должен знать, по крайней мере, сколько байтов является членом, и строка (например) может быть намного больше, чем int. Вы можете обойти это, используя указатели: по сути, это любая версия C ++, но затем вам нужно удалить ее самостоятельно (или вы потеряете память!), После чего приведенный ниже метод интерфейса упрощается.

Если бы я делал то, что вы хотите, я бы использовал наследование:

struct Sprite {
    int frame;
    double rotation;

    Sprite() {
        frame = 0;
        rotation = 0.0;
    }
    virtual ~Sprite() {}

    virtual void think() {
        ++frame;
    }

    virtual void draw() {
        ...
    }
};

struct RotatingSprite : public Sprite {
    int state;

    MyShape() {
        state = 0;
    }

    void think() {
        Sprite::think();
        if (state == 0 && frame > 64) {
            state = 1;
            rotation += 180.0;
        }
    }
};

Или указатель на функцию:

struct Sprite {
    int frame;
    double rotation;
    void (*think)(Sprite*);

    Sprite() {
        frame = 0;
        rotation = 0.0;
    }
};

void rotate_think(Sprite* sprite) {
    if (sprite->state == 0 && sprite->frame > 64) {
        sprite->state = 1;
        sprite->rotation += 180.0;
    }
}

...
    sprite->think = &rotate_think;

Если вам действительно нужно сделать это динамически, я бы рекомендовал использовать ++ часть C ++. Для предикатов (предикат - это просто то, что возвращает логическое значение, например isLowerCase ()), создайте интерфейс AIPredicate, а для действий - интерфейс AIAction:

struct AIPredicate {
    // "When you delete an AIPredicate, delete the full type, not just this interface."
    virtual ~AIPredicate() {}
    // "You can treat this as a function (operator()) but I'm not providing an implementation here ( = 0)"
    virtual bool operator()(AIObject* object) = 0;
};

struct AIAction {
    virtual ~AIAction() {}
    virtual void operator()(AIObject* object) = 0;
};

struct AIRule {
    // std::auto_ptr (or std::unique_ptr if you can use C++0x) will delete predicate for you.
    // Add "#include <memory>" to your includes if it complains (most std headers will include it already)
    std::auto_ptr<AIPredicate> predicate;
    std::auto_ptr<AIAction> action;
};

Теперь вы можете делать такие типы, как:

struct AIFrame : public AIPredicate {
    // Implement the operator() member AICondition promises.
    bool operator()(AIObject* object) {
        return object->foo < 100;
    }
};

...
    // Use .reset() instead of = if you use std::unique_ptr.
    ai[n].predicate = new AIFooIsLow();

Если вы хотите иметь очень общий тип предиката, вы можете использовать очень мощную (и сложную) функцию шаблонов:

// The naming convention I'm using here is 'T'TitleCase for template parameters, TitleCase for types,
// lower_case for arguments and variables and '_'lower_case for members.
template<typename TMemberType, AIObject::TMemberType* TMember>
struct AIMemberEquals : public AIPredicate {
    // Constructor: Initializes a new instance after it is created.
    AIMemberEquals(TMemberType comparand) {
        // Save comparand argument so we can use it in operator().
        _comparand = comparand;
    }

    bool operator()(AIObject* object) {
        return object->*TMember == comparand;
    }
    // Stores the value to compare.
    TMemberType _comparand;
};

К сожалению, создание шаблонов выглядит немного сумасшедшим:

ai[n].predicate = new AIMemberEquals<int, &AIObject::some_member>(100);

Прочитайте его как «создайте новый экземпляр (тип, который AIMemberEquals применил к int и (элемент some_member для AIObject)» с аргументом 100 ».

Когда у вас есть несколько предикатов, управление памятью становится немного сложнее без C ++ 0x unique_ptr или shared_ptr, типов, которые будут удалять объект для вас, так как std :: auto_ptr не работает в контейнерах:

#include <vector>

struct AIData {
    // vector is fairly close to AS3's Array type, it is a good default for
    // arrays of changing or unknown size.
    std::vector<AIPredicate*> predicates;

    // Destructor: will be run before the memory for this object is freed.
    ~AIData() {
        for (int i = 0; i != predicates.size(); ++i) {
            delete predicates[i];
        }
    }
};

...
    ai[n].predicates.push_back(new AIFooIsLow());
...
    for (int i = 0; i != ai[n].predicates.size(); ++i) {
        (*ai[n].predicates[i])(ai[n].object);
    }

В C ++ 0x:

struct AIData {
    // unique_ptr will delete it for you, so no ~AIData() needed.
    std::vector<unique_ptr<AIPredicate>> predicates;
};

Ваш последний пример в C ++ может выглядеть примерно так:

std::auto_ptr<Shape> shape(new Shape());
...
std::auto_ptr<AIRule> rule(new AIRule());

rule->predicates.push(new AIMemberEquals<int, &Shape::state>(0));
rule->predicates.push(new AIMemberGreater<int, &Shape::frame>(64));

rule->actions.push(new AIAddMember<double, &Shape::rotation>(180.0));
rule->actions.push(new AISetMember<int, &Shape::state>(1));

shape->ai.push(rule); // .push(std::move(rule)); if you are using unique_ptr

Конечно, не так красиво, но работает и довольно гибко.

1 голос
/ 22 февраля 2010

Ничего себе.

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

Если это так, вы ищете функциональные указатели. Попробуйте этот урок .

Вы должны иметь возможность использовать указатель на функцию с установленным аргументом и указывать на правильную функцию в зависимости от ваших потребностей. Для этого вам также не понадобится массив указателей на функции - любая функция, соответствующая определению, должна это делать. Из учебника объявите указатель на функцию следующим образом:

int (TMyClass::*functptr)(classname, int, int) = NULL;                // C++

Затем назначьте его позже:

this.functptr = &TMyClass::doitthisway; 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...