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 task

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.

Challenge 2

Write a turnstile as a table: a std::map from {State, Event} pairs to the next state. A coin unlocks a locked turnstile, a push locks an unlocked one, and a coin while unlocked keeps it unlocked. A push while locked isn't in the table, so handle returns false. coins() counts accepted coins.

Challenge 3

Recognise numbers like -12.5 with an explicit state machine: an optional sign, one or more digits, then optionally a . followed by one or more digits. Write bool is_number(std::string_view s) with an enum class of states and a loop that moves between them one character at a time. Only some states accept at the end.

Previous: Type erasure