State Machines
COP-3223H
Table of Contents
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
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