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;
}
- Evaluate the expression
- Jump to the case corresponding to its value.
- 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
N/D, CC BY-SA 4.0 via Wikimedia Commons A turnstile
State machine
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.
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