UP | HOME

Switch and State Machines
COP-3223H

Table of Contents

switch statements

switch (expression) {
case value0:
  instructions0;
  break;
case value1:
  instructions1;
  break;
case value2:
  instructions2;
  break;
... // any number of cases
default:
  default_instructions;
  break;
}
  1. Evaluate the expression
  2. Jump to the case corresponding to its value.
  3. The break statement jumps to the end of the switch

The "break" statement is necessary to avoid "fall-through"

The values can be in any order.

Not all values need to be there.

The default is optional; without it, it's possible that none of the cases will run (like an if without an else).

Example: even or odd

switch (x % 2) {
case 0:
  printf("even\n");
  break;
case 1:
  printf("odd\n");
  break;
default:
  printf("this should never happen\n");
  assert(0);
  break;
}

Like else-if statements

if        (0 == (x % 2)) {
  printf("even\n");
} else if (1 == (x % 2)) {
  printf("odd\n");
} else {
  printf("this should never happen\n");
  assert(0);
}

Simpler syntax for a specific case of conditionals

Example: reading user input

switch (c) {
  case 'j':
    printf("pressed j, which is down\n");
    break;
  case 'k':
    printf("pressed k, which is up\n");
    break;
  case 'h':
    printf("pressed h, which is left\n");
    break;
  case 'l':
    printf("pressed l, which is right\n");
    break;
  default:
    printf("unexpected key\n");
    break;
}
if        ('j' == c) {
  printf("pressed j, which is down\n");
} else if ('k' == c) {
  printf("pressed k, which is up\n");
} else if ('h' == c) {
  printf("pressed h, which is left\n");
} else if ('l' == c) {
  printf("pressed l, which is right\n");
} else {
  printf("unexpected key\n");
}

Example: moving a character

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

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

int main(int argc, char **argv) {
  char c = '\0';
  int width = 40;
  int height = 20;
  int x = 1;
  int y = 1;
  int dx = 1;
  int dy = 1;

  set_input_mode ();

  while (1) {
    printf("\x1b[2J");
    printf("\x1b[H");
    printf("\033[%d;%dH", y, x);
    putchar('o');
    putchar('\n');
    printf("\033[%d;%dH", height + 1, 0);
    printf("y: %d\n", y);
    printf("x: %d\n", x);
    printf("c: %c\n", c);
    read (STDIN_FILENO, &c, 1);
    dx = 0;
    dy = 0;
    switch (c) {
    case 'j':
      dy = 1;
      break;
    case 'k':
      dy = -1;
      break;
    case 'h':
      dx = -1;
      break;
    case 'l':
      dx = 1;
      break;
    case 's':
      dy = 1;
      break;
    case 'w':
      dy = -1;
      break;
    case 'a':
      dx = -1;
      break;
    case 'd':
      dx = 1;
      break;
    }
    int newx = x + dx;
    int newy = y + dy;
    if ((0 < newx) && (newx < width)) {
      x = newx;
    }
    if ((0 < newy) && (newy < height)) {
      y = newy;
    }
  }
}

C gotcha: fall-through

switch (x % 2) {
case 0:
  printf("even\n");
  break;
case 1:
  printf("odd\n");
default:
  printf("this should never happen\n");
}

Without a "break", the program will continue running the next case.

case labels are just like labels for goto. The "break" here means goto to end of the switch block.

Intentional fall-through

When you want multiple cases to have the same behavior.

E.g., checking for an ASCII digit (as an alternatively to range checks).

Famous example: Duff's device - Wikipedia

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

Implementing a state machine

State transition table

State Push Coin
locked locked unlocked
unlocked locked unlocked

You can model computation with state machines and conduct formal verification of the correctness of the states.

https://en.wikipedia.org/wiki/Pentium_FDIV_bug

In C

  • Assign each state a number
  • Store state in a variable
  • Use switch to check current state
  • Use switch to update state

Show the conditions setting states

Use those states to produce output

Show how you can define machine as set of input states, conditions to transition between states, and output states

enum

Give names to state numbers

Show how you can use an enum to name the states to make them less esoteric.

enum values in switch statements

Makes it more intuitive to track state.

Combine the states with switch statements to build the state machine.

Turnstile C program

#include <stdio.h>
#include <assert.h>

int main(int argc, char **argv) {
  // 0 means locked
  // 1 means unlocked
  enum {
    locked,
    unlocked
  };

  int state = locked;

  // p means push
  // c means coin
  char c;

  while (1) {
    scanf("%c", &c);

    switch (state) {
    case locked:
      printf("current state: locked\n");
      switch (c) {
      case 'p':
        printf("push\n");
        state = locked;
        break;
      case 'c':
        printf("coin\n");
        state = unlocked;
        break;
      default:
        // unexpected input
        break;
      }
      break;

    case unlocked:
      printf("current state: unlocked\n");
      switch (c) {
      case 'p':
        printf("push\n");
        state = locked;
        break;
      case 'c':
        printf("coin\n");
        state = unlocked;
        break;
      default:
        // unexpected input
        break;
      }
      break;
    default:
      // unexpected state
      break;
    }
  }
}

Example: a simple game

Arithmetic and assignment syntactic sugar

x = x + 1;

// syntactic sugar
x += 1;

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;
    }      
  }
}

Get term.c

From within your game source directory:

wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3223hspring26/files/term.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <termios.h>

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

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

// https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input

bool kbhit() {
  struct timeval tv = { 0L, 0L };
  fd_set fds;
  FD_ZERO(&fds);
  FD_SET(0, &fds);
  if (select(1, &fds, NULL, NULL, &tv) == 1) {
    return true;
  } else {
    return false;
  }
}

int getch() {
  int r;
  unsigned char c;
  do {
    if ((r = read(0, &c, sizeof(c))) < 0) {
      return '\0';  // null character on error
    }
  } while (r < 1);  // ensures read returns one character
  return c;
}

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-10-12 Mon 10:34

Validate