andersch.dev

<2023-04-10 Mon>

Finite State Machines (FSM)

A finite-state machine (FSM) is a model of computation. Every state machine is comprised of its states and transitions.

Example implementation in C

enum game_transition_e
{
    GAME_TRANSITION_NONE,
    GAME_TRANSITION_TO_MENU,
    GAME_TRANSITION_TO_GAME,
    GAME_TRANSITION_EXIT
};
struct game_transition_t
{
    game_transition_e type;
};

enum game_state_e { GAME_STATE_MENU, GAME_STATE_INGAME, GAME_STATE_EXIT };
struct game_state_t
{
    game_state_e type;
};

#include <stdio.h>
int main()
{
    game_state_t game_state;
    game_state.type = GAME_STATE_MENU;
    game_transition_t game_transition;
    game_transition.type = GAME_TRANSITION_NONE;

    int game_running = 1;
    while (game_running)
    {
        // detect transition
        game_transition.type = GAME_TRANSITION_NONE;

        char input = getchar();
        switch (input)
        {
            case 'x': game_transition.type = GAME_TRANSITION_EXIT;  break;
            case 'm': game_transition.type = GAME_TRANSITION_TO_MENU; break;
        }

        switch (game_state.type)
        {
            case GAME_STATE_MENU:
            {
                switch (game_transition.type)
                {
                    case GAME_TRANSITION_EXIT:
                    {
                        game_state.type = GAME_STATE_EXIT;
                    } break;

                    case GAME_TRANSITION_NONE:
                    default: { } break;
                }
            } break;

            case GAME_STATE_INGAME:
            {
            } break;

            case GAME_STATE_EXIT:
            {
                game_running = 0;
            } break;
        }

        // log
        printf("TRANSITION: %u\n", game_transition.type);
        printf("STATE:    %u\n", game_state.type);
    }
};

Implementation using X-Macros in C

Using X-Macros, one can define states and transitions in a centralized place and define default state transition (plus offer strings for all enums).

/* NOTE: for this example, game states and transitions are identical  */
#define GAME_STATES(X) \
        X(NONE)   \
        X(MENU)   \
        X(INGAME) \
        X(EXIT)

typedef enum game_transition_e
{
    #define TRANSITION_ENUM(state, ...) GAME_TRANSITION_TO_##state,
    GAME_STATES(TRANSITION_ENUM)
    GAME_TRANSITION_COUNT
} game_transition_e;

const char* game_transition_strings[GAME_TRANSITION_COUNT] = {
    #define TRANSITION_ENUM_STRING(state, ...) #state,
    GAME_STATES(TRANSITION_ENUM_STRING)
};

typedef enum game_state_e {
    #define STATE_ENUM(state, ...) GAME_STATE_##state,
    GAME_STATES(STATE_ENUM)
    GAME_STATE_COUNT
} game_state_e;

const char* game_state_strings[GAME_STATE_COUNT] = {
    #define STATE_ENUM_STRING(state, ...) #state,
    GAME_STATES(STATE_ENUM_STRING)
};

#define DEFAULT_TRANSITION(state, ...) if (game_transition.type == GAME_TRANSITION_TO_##state) {  game_state.type = GAME_STATE_##state; }

typedef struct game_state_t      { game_state_e type;      } game_state_t;
typedef struct game_transition_t { game_transition_e type; } game_transition_t;

#include <stdio.h>
#include <unistd.h>
int main()
{
    game_state_t game_state;
    game_state.type = GAME_STATE_MENU;
    game_transition_t game_transition;
    game_transition.type = GAME_TRANSITION_TO_NONE;

    int game_running = 1;
    while (game_running)
    {
        /* log */
        printf("STATE:      %s\n", game_state_strings[game_state.type]);
        printf("TRANSITION: %s\n", game_transition_strings[game_transition.type]);

        /* detect transition */
        game_transition.type = GAME_TRANSITION_TO_NONE;
        char input = getchar();
        getchar(); /* consume newline */
        switch (input)
        {
            case 'x': game_transition.type = GAME_TRANSITION_TO_EXIT;  break;
            case 'm': game_transition.type = GAME_TRANSITION_TO_MENU; break;
            case 'g': game_transition.type = GAME_TRANSITION_TO_INGAME; break;
        }

        switch_again:
        switch (game_state.type)
        {
            case GAME_STATE_MENU:
            {
                switch (game_transition.type)
                {
                    case GAME_TRANSITION_TO_EXIT: {
                        game_state.type = GAME_STATE_EXIT;
                        goto switch_again; /* avoid one frame of delay */
                    } break;
                    case GAME_TRANSITION_TO_NONE: {            } break;
                    default: { GAME_STATES(DEFAULT_TRANSITION) } break;
                }
            } break;

            case GAME_STATE_INGAME:
            {
                switch (game_transition.type)
                {
                    case GAME_TRANSITION_TO_EXIT: { game_state.type = GAME_STATE_MENU; /* go to menu before exiting */ } break;
                    case GAME_TRANSITION_TO_NONE: {            } break;
                    default: { GAME_STATES(DEFAULT_TRANSITION) } break;
                }
            } break;

            case GAME_STATE_EXIT:
            {
                game_running = 0;
            } break;
        }
    }
};