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
- Define the control flow (state machine) of your program
- Define suitable names for each state of your control flow diagram
- 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:
- Generate random number r, goto 2
- Setup user number input, goto 3
- Wait for user number u, goto 4 if input finished
- If u < r, goto 5; if u > r goto 7; if u == r goto 9
- Setup user info "lower", goto 6
- Wait for user info "lower", goto 2 if finished
- Setup user info "higher", goto 8
- Wait for user info "higher", goto 2 if finished
- Setup user info "success", goto 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:
GEN_RAND
: Generate random number r, gotoSETUP_NUM_INPUT
SETUP_NUM_INPUT
: Setup user number input, gotoWAIT_NUM_INPUT
WAIT_NUM_INPUT
: Wait for user number u, gotoCHK_NUM
if input finishedCHK_NUM
: If u < r, gotoSETUP_LOWER
; if u > r gotoSETUP_HIGHER
; if u == r gotoSETUP_SUCCESS
SETUP_LOWER
: Setup user info "lower", gotoWAIT_LOWER
WAIT_LOWER
: Wait for user info "lower", gotoSETUP_NUM_INPUT
if finishedSETUP_HIGHER
: Setup user info "higher", gotoWAIT_HIGHER
WAIT_HIGHER
: Wait for user info "higher", gotoSETUP_NUM_INPUT
if finishedSETUP_SUCCESS
: Setup user info "success", gotoWAIT_SUCCESS
WAIT_SUCCESS
: Wait for user info "success", gotoGEN_RAND
if finished
With these names, it is easy to draw a control flow diagram. The execution starts in the upper left (GEN_RAND
).
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
- Previous Tutorial: Tutorial 3: User Interface Design
- Next Tutorial: Tutorial 5: Simultaneous Operation
- Wiki Start Page