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