RoboCatz.com

Program Controller / Menu (in RobotC)

Think about these questions:
This is an experiment in the use of a control program that is able to run multiple missions. This program will sequence of missions and you can specify where in the sequence the robot starts. There is a key feature of this program that enables you to select where the robot starts in the mission sequence. The key feature of this program is actually something that is missing. It was left out on purpose in order to achieve this desired effect.

What is it? What's missing?

To understand what is missing, let's take a look at some of the common ways these control structures are written.

"if" Blocks

Many beginning programmers start building control structures using "if" blocks. These are relatively easy to understand.

if (expression is true)
{
Do this
}

It may be necessary to check several different expressions and perform different actions depending on them. For example:
if (expression "A" is true)
{
Do this "a"
}
if (expression "B" is true)
{
Do this "b"
}
if (expression "C" is true)
{
Do this "c"
}

One drawback is that this does seem to take a lot of lines of code. If the different expressions are all looking at a common variable, there is a shortcut you can use to save space: switch case.

"switch case" Blocks

Let's say that a user is presented with a menu of items to choose from. When the user makes their selection, a value of the menu item is returned to be analyzed to determine what course of action to take.

switch (selectedItem) 
{
case 1: 
  doFunction1();
  break;
case 2: 
  doFunction2();
  break;
case 3: 
  doFunction3();
  break;
default:
  doDefault();
  break;
}
This code is telling the robot that we will "switch" the actions that will be performed depending on the particular index value of the "selectedItem". If the selectedItem is the first one, then we will doFunction1() afterwhich we will break and seek new input from the user. If the selectedItem is the second one, then we will doFunction2() afterwhich we will break and seek new input from the user. If the selectedItem is the third one, then we will doFunction3() afterwhich we will break and seek new input from the user.

Interesting Adjustment:

What happens if we take out the break statements?
What would it look like without the break statements?

switch (selectedItem) 
{
case 1: 
  doFunction1();
case 2: 
  doFunction2();
case 3: 
  doFunction3();
default:
  doDefault();
}
Without the break statements, the program does not exit the switch block until the end. If the selectedItem is number one (1), then the robot will doFunction1() and then go on to case 2 and will doFunction2() and then go on to case 3 and will doFunction3(). If the selectedItem is number three (3), then the robot will doFunction3() and will then go on from there. It does not loop back to case 1.

In essence, the robot will start at the selectedItem and will then do every action in order from that point on.

Let's get rid of the default from the block.

switch (selectedItem) 
{
case 1: doFunction1();
case 2: doFunction2();
case 3: doFunction3();
case 4: doFunction4();
case 5: doFunction5();
}
Now this is simpler. Whatever the selectedItem is, the robot will start with the function and then perform the remaining functions in order.

Now think of each function as a "mission" to be performed.

Now you can tell the robot to start at Mission 1 and it will do 1, 2, 3, 4, and 5. You can tell the robot to start at mission 3 and it will do 3, 4, and 5.

This helps you by enabling you to have one program that runs all of the missions and that one program is flexible enough that you could re-start at any one particular mission and the rest of the sequence would flow normally from that point.

Source Code

#include "Library2012.c"
/*------
Author: Me
Date: 12/17/2012
Description: Menu Program
-------*/
#define MAX_MENU_ITEMS  8
#define FUNCTION_1  10
#define FUNCTION_2  20
#define FUNCTION_3  30
#define FUNCTION_4  40
string menu_prompts[MAX_MENU_ITEMS];
int menu[MAX_MENU_ITEMS];
int lastMenuItem = 1;
int cursorAtMenuItem = 1;

int addMenuItem(string *promptStr, int retVal)
{
  menu_prompts[lastMenuItem++] = *promptStr;
  return retVal;
}
void showMenu()
{
  display(1, "-- Missions --");
  int cdLine = 1;
  for(cdLine = 1; cdLine <= 6; cdLine++)
  {
    nxtDisplayTextLine(cdLine+1, "  %s", menu_prompts[cdLine]);
  }
}
void clearDisplay()
{
  int cdLine = 1;
  for(cdLine = 1; cdLine <= 8; cdLine++)
  {
    nxtDisplayTextLine(cdLine, " ");
  }
}

void showCursor(int menuItemNum) { nxtDisplayTextLine(menuItemNum+1, ">>%s", menu_prompts[menuItemNum]); }
void adjustMenuItem(const bool bIncrement) {
  showMenu();
  if(bIncrement)
  {
    cursorAtMenuItem++; cursorAtMenuItem = (cursorAtMenuItem >= lastMenuItem) ? 1 : cursorAtMenuItem;
  }
  else
  {
    cursorAtMenuItem--; cursorAtMenuItem = (cursorAtMenuItem < 1 ) ? lastMenuItem : cursorAtMenuItem;
  }
  showCursor(cursorAtMenuItem);
  PlaySoundFile("! Click.rso");
  wait(.6);
}
int startMenu()
{
  cursorAtMenuItem = 1;
  showCursor(cursorAtMenuItem);
  while(nNxtButtonPressed != kEnterButton) { // Wait Here
    switch(nNxtButtonPressed)
    {
      case kLeftButton:	adjustMenuItem(false);  break;
      case kRightButton:adjustMenuItem(true);   break;
      default:
        wait(0.1);
    }
  }
  PlaySoundFile("! Click.rso");
  return cursorAtMenuItem;
}

void doFunction1() {
  // blah blah blah
  //nViewStateNXT = scrnDefault;
  wait(4);
}
void doFunction2() {
  // blah blah blah
}
void doFunction3() {
  // blah blah blah
}


task main()
{
  menu[lastMenuItem] = addMenuItem("This is a Test", FUNCTION_1);
  menu[lastMenuItem] = addMenuItem("Another Mission", FUNCTION_2);
  menu[lastMenuItem] = addMenuItem("Last One", FUNCTION_3);
  int selectedItem;
  showMenu();
  selectedItem = startMenu();
  clearDisplay();
  display(1, menu_prompts[selectedItem]);
  string str = "";
  StringFormat(str, "Test: %3d ", menu[selectedItem]);
  display(2, str);
  switch (menu[selectedItem]) 
  {
  case FUNCTION_1: doFunction1();
  case FUNCTION_2: doFunction2();
  case FUNCTION_3: doFunction3();
  }
  return;
}

Menu without the Display

If you have an NXT with the blank screen problem, or want a simple menu without having to display text on the LCD panel, try assigning a button to each sub-program you wish to execute.

task main()
{
	int selectedItem = 0;
	while(true) {   // Start an infinite while loop
/*-------------------------------------
Start the Menu loop here
-------------------------------------*/

		while(true) { // Wait Here
			// Break out of the loop if a button was pressed
                        // Pressing a button sets a variable to be used in a switch statement

			if (getButtonPress(buttonUp)) { selectedItem = 2; playSoundFile("Click"); break; }
			if (getButtonPress(buttonRight)){ selectedItem = 4; playSoundFile("Click"); break; }
			if (getButtonPress(buttonDown)) { selectedItem = 1; playSoundFile("Click"); break; }
			if (getButtonPress(buttonLeft)){ selectedItem = 3; playSoundFile("Click"); break; }
			if (getButtonPress(buttonEnter)){ selectedItem = 5; playSoundFile("Click"); break; }
		}
/*-------------------------------------
Now select the mission program based on the button pressed
-------------------------------------*/
		switch (selectedItem)
		{

		case 1:                                 //first mission
		// blah blah blah
		break;

		case 2:                                 //second mission
		// blah blah blah
		break;

		case 3:                                 //third mission
		// blah blah blah
		break;

		case 4:                                 //fourth mission
		// blah blah blah
		break;
		}
	}
}