UP | HOME

State Machines
COP-3223H

Table of Contents

State machines

  • States
  • Transitions
  • Starting and ending states

Example: turnstile

Tornelli.jpg

N/D, CC BY-SA 4.0 via Wikimedia Commons A turnstile

State machine

Turnstile_state_machine_colored.svg

Chetvorno, CC0, via Wikimedia Commons Turnstile state machine colored

tic tac toe

State
x's turn
o's turn
x wins
o wins

Transitions

  • x's turn
    • valid move -> o's turn
    • winning move -> x wins
    • invalid move -> x's turn
  • o's turn
    • valid move -> x's turn
    • winning move -> o wins
    • invalid move -> o's turn

enum

  • Give names to numbers.
enum { xturn, oturn, xwin, owin };
int game_state = xturn;

Compiler assigns integers under the hood.

enum values in switch statements

  • Makes it more intuitive to track state.
switch(game_state) {
  case xturn:
    // ...
    break;
  case oturn:
    // ...
    break;
  case xwin:
    // ...
    break;
  case owin:
    // ...
    break;
}

Example: a simple game

Chase

// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#Noncanon-Example

#include <stdio.h>
#include <stdbool.h>

#include "term.c"

int main(int argc, char **argv) {
  set_input_mode ();
  printf("\033[?25l"); // hide cursor

  int width = 40;
  int height = 20;

  // player data
  enum { vulnerable, invincible, dead };
  int player_state = vulnerable;
  int player_x = width / 2;
  int player_y = height / 2;

  // enemy data
  int enemy_x = 1;
  int enemy_y = 1;
  enum { chasing, running, defeated };
  int enemy_state = chasing;

  // potion data
  int potion_x = 29;
  int potion_y = 11;
  int random = 1;

  enum { available, using, done };
  int potion_state = available;
  int potion_counter = 0;

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");

    switch (potion_state) {
    case available:
      printf("\033[%d;%dH", potion_y, potion_x);
      printf("/\\");
      printf("\033[%d;%dH", potion_y + 1, potion_x);
      printf("\\/");
      break;
    }

    printf("\033[%d;%dH", enemy_y, enemy_x);
    switch (enemy_state) {
    case chasing:
      putchar('@');
      break;
    case running:
      putchar('@');
      break;
    case defeated:
      putchar('X');
    }

    // draw characters
    printf("\033[%d;%dH", player_y, player_x);
    switch (player_state) {
    case vulnerable:
      putchar('h');
      break;
    case invincible:
      putchar('H');
      break;
    case dead:
      printf(":(");
    }

    /* // debugging output */
    /* printf("\033[%d;%dH", height + 1, 0); */
    /* printf("player_y: %d\n", player_y); */
    /* printf("player_x: %d\n", player_x); */
    /* printf("enemy_y: %d\n", enemy_y); */
    /* printf("enemy_x: %d\n", enemy_x); */
    /* printf("potion_y: %d\n", potion_y); */
    /* printf("potion_x: %d\n", potion_x); */

    // read input

    int direction_x = 0;
    int direction_y = 0;
    bool valid = true;
    do {
      char c = '\0';
      scanf("%c", &c);
      switch (c) {
      case 'i':
        direction_y = -2;
        break;
      case 'k':
        direction_y = 2;
        break;
      case 'j':
        direction_x = -2;
        break;
      case 'l':
        direction_x = 2;
        break;
      default:
        valid = false;
        break;
      }
    } while (! valid);

    switch(player_state) {
    case vulnerable:
      player_x += direction_x;
      player_y += direction_y;
      break;
    case invincible:
      player_x += direction_x;
      player_y += direction_y;
      if (enemy_x - 1 <= player_x && player_x <= enemy_x + 1 &&
          enemy_y - 1 <= player_y && player_y <= enemy_y + 1) {
        player_state = vulnerable;
        enemy_state = defeated;
        potion_state = done;
      }
      break;
    }

    switch (enemy_state) {
    case chasing: // chasing
      if (player_x > enemy_x) {
        enemy_x += 1;
      }
      if (player_x < enemy_x) {
        enemy_x -= 1;
      }
      if (player_y > enemy_y) {
        enemy_y += 1;
      }
      if (player_y < enemy_y) {
        enemy_y -= 1;
      }

      if ((player_x == enemy_x || player_x == enemy_x + 1) &&
          (player_y == enemy_y || player_y == enemy_y + 1)) {
        player_state = dead;
      }
      break;
    case running:
      if (player_x > enemy_x) {
        enemy_x -= 1;
      }
      if (player_x < enemy_x) {
        enemy_x += 1;
      }
      if (player_y > enemy_y) {
        enemy_y -= 1;
      }
      if (player_y < enemy_y) {
        enemy_y += 1;
      }
      if (enemy_x < 0) {
        enemy_x = 0;
      }
      if (enemy_x > width) {
        enemy_x = width;
      }
      if (enemy_y < 0) {
        enemy_y = 0;
      }
      if (enemy_y > height) {
        enemy_y = height;
      }
      break;
    }

    switch (potion_state) {
    case available:
      if ((player_x == potion_x || player_x == potion_x + 1) &&
          (player_y == potion_y || player_y == potion_y + 1)) {
        player_state = invincible;
        enemy_state = running;
        potion_state = using;
        potion_counter = 10;
      }
      break;
    case using:
      potion_counter -= 1;
      if (potion_counter == 1) {
        player_state = vulnerable;
        enemy_state = chasing;
        potion_state = available;
        random = (random * 97) % 65533;
        potion_x = random % width;
        random = (random * 97) % 65533;
        potion_y = random % height;
      }
      break;
    }      
  }
}

Design

Game elements

  • Player
  • Enemy
  • Potion

Gameplay

  • Evade enemy
  • Drink potion, limited time
  • Chase enemy

Akin to pac-man, where cherry allows pac-man to eat enemies.

States

Player

enum { vulnerable, invincible, dead };

Enemy

enum { chasing, running, defeated };

Potion

enum { available, using, done };

Starting states

int player_state = vulnerable; int enemy_state = chasing; int potion_state = available;

State behavior

Player

State Behavior
vulnerable can be harmed
invincible can harm enemy
dead game over

Enemy

State Behavior
chasing moving towards player
running escaping the player
defeated no longer moving

Potion

State Behavior
available can be consumed
using already consumed
done no longer usable

State transitions

Starting states

  • player vulnerable
  • enemy chasing
  • potion available

Enemy reaches player

  • player dead

Player reaches potion

  • player invincible
  • enemy running
  • potion using

Player reaches enemy

  • enemy defeated
  • potion done

Potion expires

  • player vulnerable
  • enemy chasing
  • potion available

Author: Paul Gazzillo

Created: 2026-03-11 Wed 14:14

Validate