t04 - olikraus/m2tklib GitHub Wiki

Tutorial 4: Control Flow

This tutorial will show:

  • How m2tklib can interact with the remaining part of the program.
  • How we can switch between dialog boxes
  • How to design and create a control flow for your program.

As an example we will study a small game:

  • The system thinks of a random number between 0 and 15 (not known to the user)
  • The user enters a number
  • The system informs if the number, provided by the user, is lower, equal or higher of the internal random number.
  • The game is finished, if the user has encountered the random number (random number equals user input)
  • Otherwise, output some information and let the user enter a new number

Theory

Interactive Program Design

  1. Define the control flow (state machine) of your program
  2. Define suitable names for each state of your control flow diagram
  3. Derive and construct switch case statement

We will go through these 3 steps:

Define Control Flow Diagram

Here are some general hints:

  • Each state (line within the control flow diagram) describes one action.
  • Each state always has a description, what to do if the action is finished: A goto statement which assigns the next state
  • A state can go to itself
  • Do not try to optimize the sequence. This can be done later if it is really required. Separate tasks and use separate states for each task.

Example

Reference: FindNum example.

Here is one possible sequence for the specification of the game above:

  1. Generate random number r, goto 2
  2. Setup user number input, goto 3
  3. Wait for user number u, goto 4 if input finished
  4. If u < r, goto 5; if u > r goto 7; if u == r goto 9
  5. Setup user info "lower", goto 6
  6. Wait for user info "lower", goto 2 if finished
  7. Setup user info "higher", goto 8
  8. Wait for user info "higher", goto 2 if finished
  9. Setup user info "success", goto 10
  10. Wait for user info "success", goto 1 if finished

State Names

Constant numbers should be avoided in a good software program. So the numbers are replaced by some suitable names:

  1. GEN_RAND: Generate random number r, goto SETUP_NUM_INPUT
  2. SETUP_NUM_INPUT: Setup user number input, goto WAIT_NUM_INPUT
  3. WAIT_NUM_INPUT: Wait for user number u, goto CHK_NUM if input finished
  4. CHK_NUM: If u < r, goto SETUP_LOWER; if u > r goto SETUP_HIGHER; if u == r goto SETUP_SUCCESS
  5. SETUP_LOWER: Setup user info "lower", goto WAIT_LOWER
  6. WAIT_LOWER: Wait for user info "lower", goto SETUP_NUM_INPUT if finished
  7. SETUP_HIGHER: Setup user info "higher", goto WAIT_HIGHER
  8. WAIT_HIGHER: Wait for user info "higher", goto SETUP_NUM_INPUT if finished
  9. SETUP_SUCCESS: Setup user info "success", goto WAIT_SUCCESS
  10. WAIT_SUCCESS: Wait for user info "success", goto GEN_RAND if finished

With these names, it is easy to draw a control flow diagram. The execution starts in the upper left (GEN_RAND).

https://github.com/bj0rky/m2tklibwiki/blob/master/find_num.png

Here is the C-Code to define the constant values:

#define GEN_RAND 1
#define SETUP_NUM_INPUT 2
#define WAIT_NUM_INPUT 3
#define CHK_NUM 4
#define SETUP_LOWER 5
#define WAIT_LOWER 6
#define SETUP_HIGHER 7
#define WAIT_HIGHER 8
#define SETUP_SUCCESS 9
#define WAIT_SUCCESS 10

Program Construction

  • Follow the naming convention for elements (see here).
  • Introduce a global state variable.
  • Create procedures for an action if the task will consume more than a few lines.

For our little game we now need some variables:

uint8_t state; /* will contain the current state within the control flow diagram */ 
uint8_t r;     /* the generated random numer */ 
uint8_t u;     /* the number, which was entered by the user */ 

Finally we need a "next state" procedure, which calculates the next state, based on the current state:

void set_next_state(void) {
  switch(state) {
    case GEN_RAND: r = rand(); r&=15; state = SETUP_NUM_INPUT; break;
    case SETUP_NUM_INPUT: m2.setRoot(&top_el_num_input); state = WAIT_NUM_INPUT; break;
    case WAIT_NUM_INPUT: break; 	/* state is changed in the fn_num_input_ok() callback procedure */
    case CHK_NUM:
      if ( u < r ) state = SETUP_LOWER;
      else if ( u > r ) state = SETUP_HIGHER;
      else state = SETUP_SUCCESS;
      break;
    case SETUP_LOWER: m2.setRoot(&top_el_lower); state = WAIT_LOWER; break;
    case WAIT_LOWER: break; /* state is changed in the fn_lower_ok() callback procedure */
    case SETUP_HIGHER: m2.setRoot(&top_el_higher); state = WAIT_HIGHER; break;
    case WAIT_HIGHER: break; /* state is changed in the fn_higher_ok() callback procedure */
    case SETUP_SUCCESS: m2.setRoot(&top_el_success); state = WAIT_SUCCESS; break;
    case WAIT_SUCCESS: break; /* state is changed in the fn_success_ok() callback procedure */
    default: state = GEN_RAND; break;
  }
}

Some states are changed within the button callback procedures. As an example, here is the input dialog for the number:

void fn_num_input_ok(m2_el_fnarg_p fnarg) {
  state = CHK_NUM;
  m2.clear();
}
M2_LABEL(el_num_input_label, NULL, "Num: ");
M2_U8NUM(el_num_input_u8, "c2", 0, 15, &u);
M2_BUTTON(el_num_input_ok, "", "ok", fn_num_input_ok);
M2_LIST(list_num_input) = { &el_num_input_label, &el_num_input_u8, &el_num_input_ok };
M2_HLIST(top_el_num_input, NULL, list_num_input);

As soon as the user activates the "ok" button, the state is changed to CHK_NUM. Also note the clear() statement, which stops further event processing until the next menu is assigned by the "next state" procedure (see the BUTTON element).

Finally here is the code, which puts everything together (dogm128 library):

void loop() {
  m2.checkKey();
  if ( m2.handleKey() ) {
    dogm.start();
    do{
      m2.checkKey();
      m2.draw();
    } while( dogm.next() );
  }
  
  set_next_state();  
}

The calculation of the next state is the only additional step inside the main loop.

Conclusion

  • This tutorial has shown how to write a complete interactive application with several different menus.
  • A simple and systematic approach to an interactive application includes
    • the design of a control flow graph,
    • the introduction of a overall application "state" and
    • the implementation of a "next state" calculation procedure.

Links