1. Technology

Controlling Input Instead of accepting anything

By

This is a short tutorial on how to do controlled line input in console programs instead of using scanf( to read input. Scanf is uncontrolled (it accepts anything), unbounded and in general not a very good way to get input. The method I recommend is to call getch() which returns an int. You need to #include for getch().

The function below is a specialized example of this and comes from the Game Programming in C tutorial and is part of the game Star Empires. It accepts input of the following chars : 0-9, backspace, escape and enter/return and only up to MAXLEN length, in this case four.

As it's numeric input, the range of values it will accept is 0-9999 and it will not accept a blank input. If escape is pressed it returns a value of -1 for that.

#define MAXLEN 4

/* handles input chars 0-9, backspace and return */
int GetInput() {
  char * back ="\b \b";
  char buffer[MAXLEN+1]; // max 4 digits
  char key;
  int result=0;
  int buflen=0;
  int hitreturn=0;
  buffer[0]='\0';
  do {
    key=_getch() ;
    if (key==27)
      return -1;
    buflen=strlen(buffer) ;
    if ((buflen < MAXLEN && key >='0' && key <='9') ||
(buflen >0 && (key== 8 || key==13)))
      {
        if (key ==13)
          hitreturn=1;
        else
          if (key==8)
            {
              buffer[--buflen]='\0';
              printf("%s",back) ;

            }
          else
            {
              buffer[buflen++]=key;
              buffer[buflen]='\0';
              printf("%c",key) ;
            }
     }
   }
   while (!hitreturn) ;
  return atoi(buffer) ;
}

How it Works

After initializing, it's just a big do loop that only exits if

  • Escape is pressed, or
  • Enter or Return is pressed (both have the same key value of 13) and there is at least one digit.

It's really important that the string terminating character '\0' is correctly placed with every key press. If it's missed then strlen will fail. The variable buflen keeps track of it, starting at 0 before the do loop and moving forward 1 with each valid key '0'-'9'. The key is placed in the buffer at [buflen] then buflen is incremented and the terminator put there. The postincrement buflen++ makes this an elegant two line statement.

buffer[buflen++]=key;
buffer[buflen]='\0';

Likewise when backspace is pressed (only valid if buflen is 1 or more, ie something has already been typed), buflen is decremented and the terminator written there. The string back (a backspace character \b, a space and another backspace character) when printed repositions the cursor over the last typed character, wipes it out with a space and then moves back so the next typed character will appear correctly.

buffer[--buflen]='\0';

The flag hitreturn is set to 1 when enter/return is pressed and the function atoi converts the buffer to an int. Because of the way the buffer data is restricted, this can only have values in the range 0-9999 in it. While it would be possible to pass extra parameters in (say a maximum value), it would complicate it more. Such range checking functionality is best done external to this function.

Why not use getche()? The difference between getch() and getche() is that getche() echos the character. However we want to filter out characters not see incorrect ones so getch() is used instread.

Keeping a Background Task Running

Normally when getch() is called, everything else is blocked. However conio.h has another funcion _kbhit() which returns true if a key has been pressed. Combined with the clock() function from and the clock_t type, this allows code to be run and only if a key is pressed does getch() get called.

void OneSecond() {
  clock_t onesec;
  char c;
  onesec = clock()+1000; // Five second on...
  while (clock() < onesec) {
    if (_kbhit()) // Key pressed
    {
      c = _getch() ;
      if (c=='\0')
        c=_getch() ;
      if (c==27)
        return;
    
      printf("%c",c) ;
      if (a>='0' && c <='z')
       {
   HandleKey(c) ;
   return;
       }
    }
  else {
    DoSomething() ;
  }
}

While (SomeCondition is true) {
  DisplayTime() ;
  OneSecond() ;
 }

In this example, the OneSecond function is called (from the external loop) and this displays the time then calls OneSecond() to see if a key has been hit. If it's the escape key then it just exits, otherwise any key 'a'..'z' is displayed and the function HandleKey() returns.

The external loop is regulated to execute once each second. This can be useful for a program that does regular processing but needs to react instantly to a key press.

Why the if (c=='\0')

On Windows PCs, the function keys and cursor keys return two characters with the first a 0. This detects that and gets the real value.

©2014 About.com. All rights reserved.