Game State Machine

Finite State Machines 

Game State MachineGame State Machine

The game code is organized in what's called a finite state machine, or FSM. A representation of that is above. States as well as the transitions between them. We can look at our visual state machine and see that only certain states are connected or that some states can only be reached by passing through other states.

Inside each state is code that pertains to that state. The GAME state contains all the code necessary to run the game and it is only reachable after the IMU game kit is calibrated and the bounds are set, as seen by the path from CALIBRATION > TLBOUND > BRBOUND.

Software implementation 

A visual to organize our code is nice but how is the finite state machine actually traversed? In the code we use on variable to track what state we are in, and another to track what the next state will be. To do this in the Arduino IDE, we use a switch case.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
switch (var) {
    case 1:
      //do something when var equals 1
      break;
    case 2:
      //do something when var equals 2
      break;
  }

Where our 'var' will be the current state variable. The switch will go down the line of cases until it finds the one that fits and runs whatever is inside. So using a switch case as a state machine would look like this.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
switch (currentState) {
    case stateOne:
      //do state one things
      break;
    case stateTwo:
      //do state two things
      break;
  }

Lets build one.

A Simple Game 

We're going to use some of the original functions from the IMU Game kit code and make a new project from scratch. Let's make a simple shape viewer. With a press of the microswitch a circle appears. Another press, another shape.

State Diagram

Let's plan out our viewer as a state diagram. When the kit powers up there should be a start screen that waits for you to press the switch. After you press it a shape appears. With each press a new shape. That state diagram looks like this.

Simple Game StatesSimple Game States

Open Up a Blank Arduino Sketch

Now let's formally define what hardware and software libraries we'll need. The microswitch uses pin 7 and doesn't need a library. The pin it uses will have to be set as an input in the setup. The screen needs to be defined and needs a library, U8glib. The font must be set in the setup. For the screen to work some extra code must be placed in the 'loop()' so adding that too makes our code look like this.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <U8glib.h>
U8GLIB_SH1106_128X64 screen(13, 11, 10, 9, 12);

#define SWITCH_PIN 7

void setup() {
  pinMode(SWITCH_PIN, INPUT);
  screen.setFont(u8g_font_6x10);
}

void loop() {
  screen.firstPage();
  do {

  } while ( screen.nextPage() );
}

Adding in the State Machine

We need to formally define our states so we can use them in the switch case state machine. Add this code before the setup. We also need variables to keep track of states. Call them currentState and nextState .

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
enum possibleStates {
  START_SCREEN,
  SQUARE,
  CIRCLE,
  DISC
};

enum possibleStates currentState;
enum possibleStates nextState;

At the end of the setup function let's set those two variables to equal our first state, START_SCREEN.

Download file Copy to clipboard
1
2
currentState = START_SCREEN;
nextState = START_SCREEN;

Now we are going to add the state machine into the main loop() . At the end of the loop() we want to add 'currentState = nextState' so that way we can change state. All together it looks like this.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void loop() {
  screen.firstPage();
  do {
    switch (currentState) {
      case START_SCREEN:
        //show start screen
        break;
      case CIRCLE:
        //show CIRCLE
        break;
      case SQUARE:
        //show SQUARE
        break;
      case DISC:
        //show DISC
        break;
    }
    //Stuff not in the state machine
    currentState = nextState;
  } while ( screen.nextPage() );
}

Start Screen

For this state we want to write to the screen and wait for the microswitch. Writing to the screen consists of telling the SH1106 where we want to write and what we want to write. A simple message like "Press to Start" looks like this. Place this code underneath case START_SCREEN :

Download file Copy to clipboard
1
2
screen.setPrintPos(1, 20);
screen.print("Press to Start");

You can play around with the '(1,20)'' section to change where it writes to the screen.

Tracking Button Input

The common digitalRead() to track button presses won't give us the full functionality we need for this project. We don't just want on or off. We want the act of pressing or letting go to be our state transition. To do this we need a variable to keep track of whether or not the button is not being held. This goes above setup.

Download file Copy to clipboard
1
bool notHeld = false;

We also need something out side of the state machine to check if this is true or not based on the reading from the microswitch. Adding this code right above 'currentState = nextState;' will do the trick.

Download file Copy to clipboard
1
if(digitalRead(SWITCH_PIN)){ notHeld = true; }

Start Screen Continued

Now that we can use the button let's add a if statement that if the button is not pressed but it was just held a fraction of a second ago then the user has just lifted their finger off the microswitch and the state should change. The entire STARTSCREEN state looks like this.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
case START_SCREEN:
        //show start screen
        screen.setPrintPos(1, 20);
        screen.print("Press to Start");
        if (!digitalRead(SWITCH_PIN) && notHeld) {
          notHeld = false;
          nextState = CIRCLE;
        }
        break;

Shape States

For each shape state we want to print a shape and also change the state if the switch is pressed. So drawing each shape uses this code and reuse the code from the start screen case to change the state.

Download file Copy to clipboard
1
2
3
screen.drawCircle(20, 20, 14);
screen.drawBox(10, 10, 30, 30);
screen.drawDisc(20, 20, 14);

Adding those draw functions and the button check to each state finishes our shape viewer! The full code is below.

Download file Copy to clipboard
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <U8glib.h>
U8GLIB_SH1106_128X64 screen(13, 11, 10, 9, 12);

#define SWITCH_PIN 7


enum possibleStates {
  START_SCREEN,
  SQUARE,
  CIRCLE,
  DISC
};

enum possibleStates currentState;
enum possibleStates nextState;

bool notHeld = false;


void setup() {
  pinMode(SWITCH_PIN, INPUT);
  screen.setFont(u8g_font_6x10);

  currentState = START_SCREEN;
  nextState = START_SCREEN;
}

void loop() {
  screen.firstPage();
  do {
    switch (currentState) {
      case START_SCREEN:
        //show start screen
        screen.setPrintPos(1, 20);
        screen.print("Press to Start");
        if (!digitalRead(SWITCH_PIN) && notHeld) {
          notHeld = false;
          nextState = CIRCLE;
        }
        break;
      case CIRCLE:
        //show CIRCLE
        screen.drawCircle(20, 20, 14);
        if (!digitalRead(SWITCH_PIN) && notHeld) {
          notHeld = false;
          nextState = SQUARE;
        }
        break;
      case SQUARE:
        //show SQUARE
        screen.drawBox(10, 10, 30, 30);
        if (!digitalRead(SWITCH_PIN) && notHeld) {
          notHeld = false;
          nextState = DISC;
        }
        break;
      case DISC:
        //show DISC
        screen.drawDisc(20, 20, 14);
        if (!digitalRead(SWITCH_PIN) && notHeld) {
          notHeld = false;
          nextState = CIRCLE;
        }
        break;
    }
    //Stuff not in the state machine
    if (digitalRead(SWITCH_PIN)) {
      notHeld = true;
    }
    currentState = nextState;
  } while ( screen.nextPage() );
}