I’ll offer a handful of suggestions. Some of them contradict each other. But maybe some are useful.
Consider lists versus flags
You can iterate over the world and check a flag on each item to decide whether to do the flag-thing. Or you can keep a list of only those items that should do the flag-thing.
Consider lists & enumerations
You can keep adding boolean fields to your item class, isAThis and isAThat. Or you can have a list of strings or enum elements, like { “isAThis”, “isAThat”} or { IS_A_THIS, IS_A_THAT}. That way you can add new ones in the enumeration (or string consts) without adding fields. Not that there's anything really wrong with adding fields...
Consider function pointers
Instead of a list of flags or enums, could have a list of actions to execute for that item in different contexts. (Entity-ish…)
Consider objects
Some people prefer data-driven, or scripted, or component entity approaches. But old fashioned objects hierarchies are worth considering too. The base class needs to accept the actions, like “play this card for turn-phase B” or whatever. Then each kind of card can override and respond as appropriate. There’s probably a player object and game object as well, so the game can do things like, if(player->isAllowedToPlay()) { do the play…}.
Consider debug-ability
Once nice thing about a pile of flag fields is that you can examine & print out every item's state the same way. If state is represented by different types, or bags of components, or function pointers, or being in different lists, it may not be enough to just look at the item's fields. It's all tradeoffs.
Eventually, refactoring: Consider unit tests
No matter how much you generalize your architecture, you’ll be able to imagine things that it doesn’t cover. Then you’ll have to refactor. Maybe a little, maybe a lot.
A way to make this safer is with a body of unit tests. That way you can be confident that even though you rearranged things underneath (maybe by a lot!) the existing functionality still works. Each unit test looks, generally, like this:
void test1()
{
Game game;
game.addThis();
game.setupThat(); // use primary or backdoor API to get game to known state
game.playCard(something something).
int x = game.getSomeInternalState;
assertEquals(“did it do what we wanted?”, x, 23); // fail if x isn’t 23
}
As you can see, keeping those top-level API calls on game (or player, card, &c) stable is key to the unit testing strategy.