LtLame
LtLame

Reputation: 25

C Programming, how to run a for loop while waiting for user input

for my coding assignment, I am having trouble.

The gist of what I want to achieve is to run a program that alternates a variable char OUTPUT between 'A' and 'B' every 1 minute. So the first minute, OUTPUT should be A. The second minute, it'll be B. So on and so forth.

However, if a user inputs the character P, OUTPUT will be P for a minute before returning to the A/B cycle.

I haven't exactly gotten code down yet, because none of my attempts have worked. I've tried using while and for loops. But every time I use scanf or getchar(), the program stops indefinitely to wait for the user input.

void timer(){
int t, d, change;
char p;

for(t = 1; t <= 30000; t++)
for(d = 1; d <= 30000; d++){
    if(t == 30000){
        change = 1;
    }
    else if(p == 'p'){
        change = 0;
    }  
    else{
        p = getchar();
    }

is sorta what I have right now. It's a rather incomplete function, but change is supposed to return 1 or 0 and in the main function, if it's 1, the OUTPUT will change to A/B.

I've also done some research and most people recommend multithreading, but we haven't learned that in class, so we can't use that for the program. I also don't have access to sleep() or delay(). Basically I'm restricted to the standard c library.

Upvotes: 2

Views: 2385

Answers (2)

David C. Rankin
David C. Rankin

Reputation: 84569

If you are still stuck, then here is a short example using pselect (set to not block, e.g. both fields of struct timespec set to 0). It simply uses an infinite loop to check pselect to determine whether input is waiting to be read on stdin. If so it reads the input and if the user entered a P (or any string beginning with P) P is output, once per-second for a time period, before the resuming with either A or B (whatever was not displayed last before 'P').

I have the time period currently set to 10 sec. for testing, just adjust the #define PERIOD 10 constant to change to whatever number of seconds you need (e.g. 60 in your case) I wasn't going to watch a minute each worth of A's, B's or P's...

The use of pselect is fairly straight forward. It is identical to select except for the timeval struct select uses for time and select has no sigmask parameter. (irrelevant here) The key is to set both fields of the timespec struct to 0 so that pselect will not block waiting for input. You simply call it, and if the return is 1, then you have input waiting. Then it's just a matter of reading the input (I chose fgets so it would read and consume the trailing '\n'). Check whether the users entered P (by simply checking the first character in buf), if so, then begin a new period outputting P, otherwise simply continue plodding along with your A or B output. There are additional comments inline below:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/select.h>
#include <unistd.h>

#define MAXC 64     /* size of buffer for fgets */
#define PERIOD 10   /* no. of seconds for alternating period */

/* simple function calling pselect, returns 1 when input waiting */
int haveinput (int filedes)
{
    fd_set set;
    struct timespec timeout;

    /* Initialize the file descriptor set. */
    FD_ZERO (&set);
    FD_SET (filedes, &set);

    /* Initialize the timeout data structure. */
    timeout.tv_sec = 0;     /* timeout 0 - immediately return, */
    timeout.tv_nsec = 0;    /* if NULL, blocks indefinitely.   */

    return pselect (filedes + 1, &set, NULL, NULL, &timeout, NULL);
}

int main (void)
{
    int c = 'A', 
        last = c;    /* holds last 'A' or 'B' for pickup after 'P' */
    unsigned long begin = time (NULL);  /* begin seconds of period */

    for (;;) {       /* loop until 'q' entered or EOF */
        unsigned long current = time (NULL);    /* current seconds */
        if (haveinput (STDIN_FILENO) == 1) {    /* is input ready? */
            char buf[MAXC] = "";                /* if so, get it!  */
            if (!fgets (buf, MAXC, stdin) || *buf == 'q')
                break;                    /* if EOF or 'q' -- bail */
            if (*buf == 'P') {            /* if user entered 'P'   */
                c = 'P';                  /* set c = 'P' */
                begin = time (NULL);      /* restart time period   */
            }
        }
        else {
            putchar (c);        /* if no imput read, output char */
            fflush (stdout);    /* output is buffered so fflush  */
            sleep (1);          /* arbitrary second to wait */
        }
        if (current - begin >= PERIOD) {  /* end of period reached */
            if (last == 'A')            /* if last was 'A' use 'B' */
                c = last = 'B';
            else                        /* otherwise use 'A' */
                c = last = 'A';
            begin = time (NULL);        /* restart time period */
            putchar ('\n');
        }
    }
    putchar ('\n');

    return 0;
}

(note: this is a 'nix specific solution. Windows does not provide sys/select.h (nor does MinGW). While windows provides a version of select through Winsock2.h and lib Ws2_32.lib it does not behave consistent with the implementation here.)

Example Use/Output

With an alternating period of 10 sec. instead of 60:

$ ./bin/pselect_abp
AAAAAAAAAAA
BBBBBBBBBBB
AAAAAAP
PPPPPPPPPPP
BBBBBBBBBBB
AAAAAAzz
AAAAA
BBBBBBBBBBB
AAAqA

Give it a try, look things over and let me know if you have further questions.

Upvotes: 1

mnistic
mnistic

Reputation: 11020

To do what you need, which is enable user input to change the periodic output, you will need to either:

  1. Spin off a thread that will be doing the periodic output and change it based on the value of a variable in main thread that is set based on user input.
  2. Do an async input read with select, such as here: https://stackoverflow.com/a/448982/4454124

Since you say you haven't covered threads yet, that's probably not the way you want to go right now, so I would suggest the select route, even though it's not standard C.

Upvotes: 0

Related Questions