C/C++ Arena

Step 7 of 7

Challenge: a table-driven state machine

Protocols, UI flows, order pipelines and game rounds are state machines: the thing is always in exactly one state, and events move it to another state, but only along legal paths. An order can go from Paid to Shipped, never from Shipped back to Cart.

Encoding the legal transitions in a table, instead of nested ifs scattered through the code, makes them easy to review (the table is the specification), test and extend.

#include <iostream>
#include <optional>

enum class Door { Closed, Open, Locked };
enum class Action { Open, Close, Lock, Unlock };

struct Transition {
    Door from;
    Action on;
    Door to;
};

constexpr Transition rules[] = {
    {Door::Closed, Action::Open, Door::Open},
    {Door::Open, Action::Close, Door::Closed},
    {Door::Closed, Action::Lock, Door::Locked},
    {Door::Locked, Action::Unlock, Door::Closed},
};

std::optional<Door> next(Door d, Action a) {
    for (const auto& r : rules) {
        if (r.from == d && r.on == a) return r.to;
    }
    return std::nullopt;                  // not in the table: illegal
}

const char* name(Door d) {
    switch (d) {
        case Door::Closed: return "closed";
        case Door::Open: return "open";
        case Door::Locked: return "locked";
    }
    return "?";
}

int main() {
    Door d = Door::Closed;
    for (Action a : {Action::Lock, Action::Open, Action::Unlock, Action::Open}) {
        if (auto n = next(d, a)) {
            d = *n;
            std::cout << "-> " << name(d) << "\n";
        } else {
            std::cout << "rejected, still " << name(d) << "\n";
        }
    }
}
-> locked
rejected, still locked
-> closed
-> open

How it works

Your task

Write the six transitions from the table below as a constexpr Transition array, and next as above. In Match::handle, call next(state_, e); on success, update state_, increment accepted_ and return true; otherwise return false without changing anything.

Your turn: implement a match lifecycle with these transitions:

From Event To
Idle Start Warmup
Warmup GoLive Live
Live Pause Paused
Paused Resume Live
Live End Over
Paused End Over

Write std::optional<State> next(State s, Event e) using a table, and class Match whose bool handle(Event e) applies a legal event (returning true) or rejects an illegal one (returning false, state unchanged). state() returns the current state and history() the number of accepted events.

Previous: Type erasure