8
\$\begingroup\$

I have the next problem.

A item can have a lot of states:

NORMAL  = 0000000
DRY     = 0000001
HOT     = 0000010
BURNING = 0000100
WET     = 0001000
COLD    = 0010000
FROZEN  = 0100000
POISONED= 1000000

A item can have some states at same time but not all of them

  • Is impossible to be dry and wet at same time.
  • If you COLD a WET item, it turns into FROZEN.
  • If you HOT a WET item, it turns into NORMAL
  • A item can be BURNING and POISON

Etc.

I have tried to set binary flags to states, and use AND to combine different states, checking before if it is possible or not to do it, or change to another status.

Does there exist a concrete approach to solve this problem efficiently without having an interminable switch that checks every state with every new state?

It is relatively easy to check 2 different states, but if there exists a third state it is not trivial to do.

\$\endgroup\$
2
  • \$\begingroup\$ I don't understand why you think a 3rd state makes it not so trivial as it should be as simple as checking 2. Can you post an example of how you are currently doing it and an example using 3 states. \$\endgroup\$
    – John
    Nov 11, 2012 at 16:45
  • \$\begingroup\$ Maybe you can check the best solution to solve the declaration if it has a lot of states: stackoverflow.com/q/13385744/1077364 \$\endgroup\$
    – vgonisanz
    Nov 14, 2012 at 19:47

3 Answers 3

6
\$\begingroup\$

When I need to use flags I usually do something along these lines.

enum obj_state
{
    NORMAL      = 0x00000;
    DRY         = 0x00002;
    HOT         = 0x00004;
    BURNING     = 0x00008;
    WET         = 0x00010;
    COLD        = 0x00020;
    FROZEN      = 0x00040;
    POISONED    = 0x00080;
};

int objFlags;

void DryOn() { objFlags |= DRY; }
void HotOn() { objFlags |= HOT; }
// etc...

void DryOff() { if (FlagOn(DRY)) objFlags ^= DRY; }
void HotOff() { if (FlagOn(HOT)) objFlags ^= HOT; }
// etc...

bool isDryOn() { return FlagOn(DRY); }
bool isHotOn() { return FlagOn(HOT); }
// etc...


// If the given Bit is on this will return true.
bool FlagOn(obj_state s) { return (objFlags & s) == s; }

// returns -1 if failed, 1 if successful
int apply_cold(Object obj)
{
    if (obj.isWetOn())
    {
        obj.ColdOff();
        obj.WetOff();
        obj.FrozenOn();
    }
    else
        return -1;

    return 1;
}


//---------------------------------
// alt way of doing DryOn and WetOn
// since these conditions can not be
// active at the same time.
void DryOn() 
{ 
    if (isWetOn()) 
        return;
    else
        objFlags |= DRY; 
}

void WetOn() 
{ 
    if (isDryOn())
        return;
    else;
        objFlags |= WET; 
}

This makes using them for stuff like apply_cold() very easy and you can obviously build in your state conditions like dry and wet.

\$\endgroup\$
1
  • \$\begingroup\$ Maybe you can check the best solution to solve the declaration if it has a lot of states: stackoverflow.com/q/13385744/1077364 \$\endgroup\$
    – vgonisanz
    Nov 14, 2012 at 19:47
7
\$\begingroup\$

Two observations:

  1. Your condition system seems to have two orthogonal axes: temperature and poison. Represent them as such.
  2. When thinking about this you should separate transitions from states. COLD and HOT are transitions in the way you mention them, not states.

Combining those observations would result in something like this:

// These is the representation of the two axes.
int temperature; // can be between -2 and +2, 0 is normal, 1 is hot, 2 is burning, -1 is cold, -2 is frozen
bool poisoned;

// These methods represent state transitions.
void applyHeat() {
    if ( temperature <= 2 ) {
        ++temperature;
    }
}

void applyCold() {
    if ( temperature >= -2 ) {
        --temperature;
    }
}

void applyPoison() {
    poisoned = true;
}

void removePoison() {
    poisoned = false;
}
\$\endgroup\$
3
  • \$\begingroup\$ The point is, i will add more states not orthogonals, is possible to do? HOT is a state too, normal = 30 ºC, hot = 70 ºC cold = 5 ºC. But if add heat and is hot, it suppost to turn into burning. \$\endgroup\$
    – vgonisanz
    Nov 11, 2012 at 12:34
  • \$\begingroup\$ How about you model the temperature as an integer value in degrees Celsius instead of a boolean saying "hot" or "cold"? \$\endgroup\$
    – Philipp
    Nov 11, 2012 at 12:45
  • \$\begingroup\$ Of course you can add more temperature-states, as you see in my answer I have actually already represented the hot-state. What Philipp says means seeing every degree Celsius as a state, which is fine, although note this might not be what you want from a game design perspective: more simulation doesn't entail deeper game play, per se. \$\endgroup\$
    – Eric
    Nov 11, 2012 at 13:06
3
\$\begingroup\$

Representing your states as bitmask like you write, you can just translate your descriptions of the constraints into code:

if ( (state & HOT) && (state & COLD) ) {
    state &= ~HOT;
    state &= ~COLD;   // reset both HOT and COLD flags if both are set
}

if ( (state & COLD) && (state & WET) ) {
    state &= ~WET;    // cold items can't be wet
    state |= FROZEN;  // instead, they're frozen
}

if ( (state & HOT) && (state & WET) ) {
    state &= ~WET;    // hot and wet items dry up...
    state &= ~HOT;    // ...and cool down
}

// add other constraints here...

You could wrap that into an makeStateConsistent() that you can call before testing the state bits to ensure that the state makes sense.

However, one limitation of this approach is that it can't account for the order of state changes. For example, if you want to have a different outcome for hot items that become wet than for wet items that become hot, you can't do it like this: all the makeStateConsistent() method sees is a hot and wet object, with no information about how it got to be that way.

Instead, what you could do is make the item state private (at least conceptually) and manipulate it through a set of methods like coolItem(), heatItem(), wetItem(), dryItem() and so on. That way, the state change methods themselves can take care of any additional changes. For example, the heatItem() method might look something like this:

if ( state & COLD ) {
    state &= ~COLD;    // cold items become normal temp when heated
    if ( state & FROZEN ) {
        state &= ~FROZEN;  // ...and melt if they were frozen
        state |= WET;
    }
} else if ( state & WET ) {
    state &= ~WET;    // wet items dry up when heated, stay normal temp
} else {
    state |= HOT;     // dry normal temp items become hot
}

Of course, you may still want to also have a makeStateConsistent() method as a backup, just in case you have a bug in your state change methods.

Also, in some cases you may be able to simplify your code by eliminating unnecessary states. For example, do you really need a separate FROZEN state, or would it be enough to just treat any cold and wet items as frozen?

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .