The usual approach is to store pointer to a conditional function.
You implement the set of conditions as separate functions, and attach a pointer to the right condition in the table. Each function tests for given set of conditions. You iterate through the list till one of pointed functions returns true. Of course "current state" can be used as part of conditions, which removes the need for 2d array.
struct {
bool(*test)(); //the condition
void(*onsuccess)(); //event
} condition;
This may be inefficient if the conditions repeat a lot in a chain like:
ev1: (a && b && c)
ev2: (a && !b && c)
ev3: (a && b && d)
ev4: (a && !b && !c)
ev5: (!a)
(most events tests for a
separately, if testing for a is computationally expensive it will take much more CPU time than necessary).
In this case you'd have to remap the condition_set -> event
list into a single decision tree - much harder to maintain but more CPU-efficient:
a:
b:
c: ev1
d: ev3
!b:
c: ev2
!c: ev4
!a: ev5
This of course cannot be a simple 1-dimensional list any more, but requires a tree-like structure instead, say, branched linked list:
struct {
bool(*test)(); //the condition
void(*onsuccess)(); //event (if any)
condition* next_sibling; //other conditions dependent on parent but independent from this one,
condition* first_child; //other conditions dependent on this one,
} condition;
NULL on any of the pointers means "no such item" (except on "test" which can't be null). And of course recursion required to traverse the list.