Thread: Asking Questions and answers lead to if statement

  1. #16
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Let me try to see if I understand what TheStreet and his student are trying to do, and if I can help.

    First, let's decide you can have up to 10 players, and each name can be up to 63 characters long. In C, you need to reserve one character for end-of-string mark, '\0'. Since you might decide to change the maximum number of players, let's make it a macro:
    Code:
    #include <stdio.h>
    
    #define MAX_PLAYERS 10
    
    int main(void)
    {
        char  name[MAX_PLAYERS][64];
        int   number[MAX_PLAYERS];
        int   players = 0;
        int   player;
    Next, we want to ask the names for all the players. I don't like to ask the number of players first, because it's simpler to just ask them each, and use some marker to indicate "no, that's all of us".

    Let's use a while loop to ask for the name of each player. We need the limit on players, because we cannot go over the array bounds:
    Code:
        while (players < MAX_PLAYERS) {
    
            printf("Please input name for player %d, or - if no more players:\n", 1 + players);
    The scanf() function will return the number of conversions it did. It may be zero if there is no more input, or if the input does not match the pattern. Since we have 63 characters reserved for a player name, let's ask for any characters (but no more than 63) that are not newlines (CR or LF). The %63[^\r\n] is like %63s except that it accepts also spaces (it only stops at a newline). The original %s was unlimited, so typing a very long name would overrun the buffer (as sscanf() expects you to tell how long your buffers are; it does not check).
    Code:
            /* Scan for up to 63 characters not newlines (CR or LF) into name[players] */
            if (scanf(" %63[^\r\n]", name[players]) < 1)
                break;
    If there was no name given, the above breaks out of the loop.

    If the name is empty, or starts with a dash or underscore, we want to break out of the loop also:
    Code:
            /* No more names if empty name or starts with a - or a _ */
            if (name[players][0] == '\0' || name[players][0] == '-' || name[players][0] == '_')
                break;
    At this point, we have a new players name in name[players]. Okay, so we have a new player. Increase the count, and ask for the next name, then:
    Code:
            players++;
        }
    If there were no names given at all, we want to abort the program.
    Code:
        if (players < 1) {
            printf("\nNo players at all? Okay, no game then.\n");
            return 0;
        }
    Otherwise, we are ready to play the game.

    For this demonstration, lets just ask one number between 1 and 10 inclusive (with both 1 and 10 accepted) from each player, and storing it into number[player] for each one. This time, let's use a for loop:

    Code:
        for (player = 0; player < players; player++) {
    
            printf("Your turn, %s. Please input a number between 1 and 10, inclusive.\n", name[player]);
    It is very easy to input something that is really not a number, so we want to let the player retype their answer if that happens. One way to do that is to have a loop which asks until the answer is satisfactory. This one uses an endless while loop for that:
    Code:
            while (1) {
    
                if (scanf(" %d", &number[player]) < 1) {
    Notice the less-than-one? If it is true, we did not get a number. scanf() does not discard the input, so we have to somehow "consume" it to discard it. The easiest is to ask scanf() to discard everything up to the next newline (by reading it, but not storing it anywhere):
    Code:
                    /* Skip this line in the input buffer. */
                    scanf(" %*[^\r\n]");
    and then prompt the same player to retry.
    Code:
                    printf("That's not a number, %s. Try again.\n", name[player]);
                    continue;
                }
    Note that break breaks out of the loop, and continue skips to the next iteration of the current loop, starting at the start of the loop body. Both work only on the current loop, they don't affect the outer for loop in any way. Above, continue will therefore skip back to the scanf().

    Next, we check the number for validity:
    Code:
                if (number[player] < 1) {
                    printf("%d is too small; it must be between 1 and 10, inclusive. Try again, %s.\n", number[player], name[player]);
                    continue;
    
                } else
                if (number[player] > 10) {
                    printf("%d is too large; it must be between 1 and 10, inclusive. Try again, %s.\n", number[player], name[player]);
                    continue;
                }
    At this point, we know the number is okay. So, we break out of the loop, so we can continue on in the for loop.
    Code:
                break;
            }
        }
    Note that the first brace above ended the while loop, and the second the for loop. The break only breaks out of the innermost scope, here out of the while loop, not all the way out of the for loop. Just out of the innermost while loop.

    At this point we know we have players players (0 to players-1), their names in name[] and chosen values in number[].

    I don't know what you'd like to do with those numbers, so let's just output the choices and exit the program:
    Code:
        printf("\nNumbers chosen:\n");
    
        for (player = 0; player < players; player++)
            printf("%s: %d\n", name[player], number[player]);
    
        return 0;
    }
    If you have any questions on any of the code above, please ask, and I'll try to explain.

  2. #17
    TEIAM - problem solved
    Join Date
    Apr 2012
    Location
    Melbourne Australia
    Posts
    1,907
    Hey there

    what we want is for that output to a single string. i.e. John....... then something happens, jeremy......... then next thing happens and finally Jenny........ and we get another thing happening.
    That's fair enough - You have the names, now you want to do something with them - Let's figure out what you want to do

    Imagine that you have just started your program - What do you want to see?

    I'd imagine that it would be some sort of greeting message. Next you would want the program to prompt something. Your best bet at the moment is to open Notepad (or any text editor) and start typing what you want. Here is a good start

    Welcome to the game!


    Enter player 1: Anna
    Enter player 2: Jeff
    Enter player 3: Brice
    Enter player 4: Jake
    Enter player 5: Richard
    Enter player 6: Dawn
    Enter player 7: Devon
    Enter player 8: Lee
    Enter player 9: Megan
    Enter player 10: Michael


    Hello Anna! You're player number 1!
    You have 4 letters in your name
    Your Ninja name is "ka-to-to-ka"


    ...

    [note]
    I got the Ninja name idea from a pic posted on FB
    Basically every letter of your name is replaced with the following...

    A=ka J=zu S=ari
    B=zu K=me T=chi
    C=mi L=ta U=do
    D=te M=rin V=ru
    E=ku N=to W=mei
    F=lu O=mo X=na
    G=ji P=no Y=fu
    H=ri Q=ke Z=zi
    I=ki R=shi
    [/note]

    To get the length of a string, you will need to look into "strlen" (from string.h). To check each letter you will need to know how to address a single char in the array of strings (a 2D array).

    Maybe you can print a random ninja name and take turns trying to guess what the real name was - You can use "strncmp" from string.h to compare different strings

    A good cause and affect game where you can print out the use names is "Battleship" - i.e. "You sunk Anna's Battleship!"

    You are only limited by your imagination!

    Still struggling to understand this and maybe we need to go and build a lego tower or something!
    Programming is something that is learnt through patience and practice - The more you put into learning it, the more you get back.

    We have read around fgets and can't see how this applies
    Basically, the best way of getting strings from a user into a character array is to do something like this:
    Code:
    char mitakimime[6]; //Character array with 6 "letters"
    
    /* Put input into mitakimime, 
        maximum of 5 characters or until enter is hit (array size -1), 
        get it from the standard input stream */
    
    fgets(mitakimime, 5, stdin);
    But, while you are learning, scanf is fine.
    Fact - Beethoven wrote his first symphony in C

  3. #18
    Registered User
    Join Date
    Oct 2012
    Location
    Lostwithiel, Cornwall
    Posts
    9
    Hi Nominal Animal

    Your explanation was brilliant and hugely helpful. Does the value name[player] get stored more than once please? i.e. If we want this to loop back around we have added *2 to this string:

    for (player = 0; player < players; player++) {It now looks like this:

    for (player = 0; player < players*2; player++) {This fails in that it assumes there are 4 players not 2 players going twice.

    Any ideas please?

    Thanks

    Rich

  4. #19
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by TheStreet View Post
    Does the value name[player] get stored more than once please?
    Well, no.. but you don't need that, though.

    If you want full rounds, use a nested loop:
    Code:
        int  round;
    
        for (round = 0; round < 2; round++) {
    
            /* Start of round 'round' */
    
            for (player = 0; player < players; player++) {
    
                printf("Round %d. Your turn, %s.\n", round + 1, name[player]);
    
                /* ... */
            }
        }
    If you don't have rounds, but specific number of turns, use the modulus operator %, which gives the remainder of an integer division. In other words:
    Code:
        int  turn;
    
        for (turn = 0; turn < 50; turn++) {
    
            player = turn % players;
    
            printf("Turn %d. It is your turn, %s.\n", turn + 1, name[player]);
    
            /* ... */
        }
    The math is simple. Given a nonnegative integer i and a positive integer n , i % n returns the remainder when i is divided by n . Because i % n is always in 0 .. n-1 inclusive, turn % players is always a valid player number: 0 to players-1 inclusive.

    By the way, the above examples are also show how having arrays start at index 0 (instead of 1) makes many things easier; all you really need to remember is that the last element is at index number_of_elements - 1.

  5. #20
    Registered User
    Join Date
    Oct 2012
    Location
    Cornwall, UK
    Posts
    3

    I got it working!

    Hey Nominal Animal.

    I just wanted to say, thanks for all of your help.

    Just so you know, I'm one of the people who is working with The Street and am also learning to code. With your help, I actually managed to get our little program working correctly!

    You've been a great help!

  6. #21
    Registered User
    Join Date
    Oct 2012
    Location
    Cornwall, UK
    Posts
    3
    Alright, now here's a silly question and I'm sure it's been heard a lot, but here goes.

    I understand quite a bit now, and figured out that I don't need 2 scanf commands to swallow the "\n" that would be outputted after - or _ is entered. So, I've set up this line to allow the user to quit out of the game if they choose to.

    Code:
    printf("\nPress enter to move on to the next player's turn, or type Q to quit.\n");
                 scanf("%c[^\r\n]", &c);
                        if(c=='Q'||c=='q')
                            return 0;
    The problem with this though is that if anything but enter or q/Q is entered, it'll print out 2 player turns. How can you stop scanf accepting unwanted things, or do I have to use a different command for this whole thing?

    Thanks for any help.

  7. #22
    Registered User Kirilenko's Avatar
    Join Date
    Aug 2012
    Location
    France
    Posts
    3
    You have to handle the different cases directly. For example, you read your character c, and then:

    • if it is \n, you move on the next player's turn;
    • if it is q or Q, you quit;
    • else, you again requests an input.


    You can do it with an infinite loop, and the keyword continue. The pseucode is above.

  8. #23
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Tidan_Likida View Post
    Alright, now here's a silly question and I'm sure it's been heard a lot, but here goes.

    I understand quite a bit now, and figured out that I don't need 2 scanf commands to swallow the "\n" that would be outputted after - or _ is entered. So, I've set up this line to allow the user to quit out of the game if they choose to.
    Because input is line-buffered, the user must press enter in any case. So, your problem is, really, consuming a line, up to and including the trailing newline, and check whether it started/contained Q.

    Here is what I'd do:

    Read individual characters from input in a loop (getc(stdin)). If it is Q or q, set a flag that indicates the user wants to quit. Otherwise, loop until you get a newline or an EOF (end of input):
    Code:
        int  done;
        int  c;
    
        done = 0;
    
        c = getc(stdin);
        while (c != EOF && c != '\n' && c != '\r') {
            if (c == 'Q' || c == 'q')
                done = 1;
            
            c = getc(stdin);
        }
    
        /* If c == EOF, if there is no more input.
         * (done) is true, if user input anything with a Q in it.
         * (!done) is true, if user did not input anything with a Q in it.
        */
    The instruction is then something like "Type Q to quit, or an empty line to give the turn to the next player" or something similar.

    If you have other commands (selection from a list of actions the player might choose from), you can easily extend the above, as long as each choice is just one character (or digit).

    It is okay to mix the different approaches (scanf() and getc(stdin)) as long as you carefully consider what each of them really do. In particular, scanf() considers spaces and newlines to be the same (except if you specify what characters you reject/only accept), but it will not "consume" input it cannot convert.

    The per-character loop works well, because it stops when it sees the first character of a newline. The next sscanf() will simply skip the rest of the newline characters as it considers them the same as blanks (white-space). scanf() family of functions does that for everything except single character conversions and conversions where you list the accepted/rejected characters. You can make it explicit by adding space: it matches zero or more blanks. That is also why I like to put a space as the first thing in the conversion specification: it makes sure, and reminds me, that it will consume all newlines and white-space characters, then start looking for stuff to convert.

    Quote Originally Posted by Tidan_Likida View Post
    How can you stop scanf accepting unwanted things, or do I have to use a different command for this whole thing?
    Your scanf() pattern has a bug in it. If you intended it to check for one non-newline character, then it should be %1[^\r\n] .

    As it is now, it expects one character, then a fixed pattern of [, followed by CR, followed by LF, followed by ].

    If you have a list of characters you accept (or reject, like above), then [ is the conversion specifier; you do not use c or s. The number before it is optional, but specifies the length of the string. And the target is a char array, at least two chars in this case, since sscanf() will always add the '\0' at end of strings.

    If you want to consume something, but not save it anywhere, use an asterisk immediately after the percent sign. For interactive use, scanning %*[\r\n] does not work too well, because it needs to see the character after the newline -- it actually waits for the first character of the next line to arrive before it returns. It won't do anything to it, but the user must "feed" the next line -- and that's no good in a game situation.

    You should also always check the result from a scanf() operation. It returns the number of conversions it did. If it cannot do any (returns zero or negative), it will not "consume" the input; you'll need to either error, or consume the input somehow. Conversions that are not stored (the asterisk *) are not counted in the result.

  9. #24
    Registered User
    Join Date
    Oct 2012
    Location
    Cornwall, UK
    Posts
    3
    Quote Originally Posted by Click_here View Post
    Welcome to the game!


    Enter player 1: Anna
    Enter player 2: Jeff
    Enter player 3: Brice
    Enter player 4: Jake
    Enter player 5: Richard
    Enter player 6: Dawn
    Enter player 7: Devon
    Enter player 8: Lee
    Enter player 9: Megan
    Enter player 10: Michael


    Hello Anna! You're player number 1!
    You have 4 letters in your name
    Your Ninja name is "ka-to-to-ka"


    ...

    I got the Ninja name idea from a pic posted on FB
    Basically every letter of your name is replaced with the following...

    A=ka J=zu S=ari
    B=zu K=me T=chi
    C=mi L=ta U=do
    D=te M=rin V=ru
    E=ku N=to W=mei
    F=lu O=mo X=na
    G=ji P=no Y=fu
    H=ri Q=ke Z=zi
    I=ki R=shi
    Going back to this post about Ninja names, how exactly would you go about coding something like this? We've been trying to work out how to go about doing it, but really are not sure of the commands you'd need to do it. Would it require a lot of "if" statements, or would it be something totally different to replace single letters of a name (for example "Roger") with the characters in the table for the ninja name ("Roger" ending up as "Shimojikushi").

    Thanks for any help on this.

  10. #25
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Tidan_Likida View Post
    Going back to this post about Ninja names, how exactly would you go about coding something like this?
    Well, you have 26 letters, A..Z.

    First create an array with the letter equivalents. Since they are all two or three letters, I'd use
    Code:
    static const char ninja_letter[26][4] = {
        /* A = */ "ka",
        /* B = */ "zu",
        /* C = */ "mi",
           :
        /* Z = */ "zi"
    };
    
    #define NINJA_LETTER_MAXLEN 3
    Now, if you have a lower case letter c, (c >= 'a' && c <= 'z'), the corresponding string is ninja_letter[c - 'a']. For uppercase letter c, (c >= 'A' && c <= 'Z'), the corresponding string is ninja_letter[c - 'A'].

    Because the output length depends on the input length (it is (2+1) to (3+1) times the length of the original string, if you include the dashes), I'd say the best approach is to write a function which dynamically allocates the memory for the ninja name. Or, if you have a maximum length for the input name, just make sure the ninja name is larger, i.e. four times the length.

    That approach also means you can safely use strcat() to append to the existing ninja name: you know there is enough room. Normally, you use strncat(), which takes the buffer length as an additional parameter, to make sure you don't accidentally overflow the buffer.

    I'd also let the caller choose what to add between letters, if anything. I'd also ignore any non-ASCII letter characters in the input, but keep spaces; that makes the procedure quite a bit more complicated, though.

    Here's what I'd probably end up with.
    Code:
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    char *ninja_name(const char *original, const char *const separator)
    {
        /* Lengths of the strings, or 0 if the pointer is NULL. */
        const size_t  original_len = (original != NULL) ? strlen(original) : 0;
        const size_t  separator_len = (separator != NULL) ? strlen(separator) : 0;
    
        /* Number of chars for the new string: ninja letters, plus separator in between. */
        const size_t  maximum = original_len * NINJA_LETTER_MAXLEN + (original_len - 1) * separator_len + 1;
    
        /* Previous and current characters picked out from original. */
        int   prev, curr;
    
        /* Result string. */
        char *result;
    
        /* NULL or empty original name? */
        if (original_len < 1) {
            errno = EINVAL; /* Invalid parameter error. */
            return NULL;
        }
    
        /* Allocate memory for the result. sizeof (char) == 1 in C. Add one for end-of-string '\0'. */
        result = malloc(maximum + 1);
        if (!result) {
            errno = ENOMEM; /* Not enough memory error. */
            return NULL;
        }
    
        /* Clear result. I'll fill it with '\0', because I'm twisted that way,
         * but technically it would be enough to just make sure it
         * is empty initially; result[0] = '\0'. */
        memset(result, '\0', maximum + 1);
    
        /* The character loop is actually one step behind of the
         * original. That way we decide what to do with the previous
         * character, when we see the current character.
         * Loop over each character in original; we have at least one char. */
        prev = '\0';
        do {
    
            if (*original == '\t' || *original == '\n' || *original == '\v' ||
                *original == '\f' || *original == '\r' || *original == ' ') {
    
                /* Skip all white-spaces, */
                do {
                    original++;
                } while (*original == '\t' || *original == '\n' || *original == '\v' ||
                         *original == '\f' || *original == '\r' || *original == ' ');
    
                /* replacing them with a single space. */
                curr = ' ';
    
            } else
            if (*original >= 'a' && *original <= 'z') {
    
                /* Lowercase letters are kept as-is. */
                curr = *(original++);
    
            } else
            if (*original >= 'A' && *original <= 'Z') {
    
                /* Uppercase letters are converted to lower case. */
                curr = *(original++) - 'A' + 'a';
    
            } else
            if (*original == '\0') {
    
                /* End of input, no more chars. */
                curr = EOF;
    
            } else {
    
                /* Everything else is skipped. */
                original++;
    
    
                /* Back to the start of the loop body! */
                curr = '\0'; /* Set to anything except EOF, so that the while() keeps the loop going. */
                continue;
            }
    
            /* 'curr' is now a letter, a space, or EOF.
             * Decide how to add 'prev' to the ninja name,
             * knowing what the next character ('curr') is.
            */
            if (prev >= 'a' && prev <= 'z')
                strcat(result, ninja_letter[prev - 'a']);
            else
            if (prev == ' ' && curr >= 'a' && curr <= 'z')
                strcat(result, " ");
    
            /* If both prev and curr are letters,
             * and the user specified a separator,
             * we add that too.
             * Remember, we made sure we have enough
             * room in 'result' even for the longest possible case.
            */
            if (prev >= 'a' && prev <= 'z' && curr >= 'a' && curr <= 'z')
                strcat(result, separator);
    
            /* 'prev' is now handled. Check the next character. */
            prev = curr;
    
        } while (curr != EOF);
    
        /* All done. */
        return result;
    }
    ninja_name("Nominal Animal", NULL) returns "tomorinkitokata katokirinkata".
    ninja_name("Tidan_Likida", "-") returns "chi-ki-te-ka-to-ta-ki-me-ki-te-ka".

    Now that I tested it, it might have been nicer to convert all consecutive non-letters to a space instead. It actually makes the code a bit shorter.

  11. #26
    TEIAM - problem solved
    Join Date
    Apr 2012
    Location
    Melbourne Australia
    Posts
    1,907
    There are a lot of clever ideas in Nominal Animal's post

    To develop your own program, it might be a good idea to go through each character in the name using a for loop. Use if statements and printf's to print the ninja name on the screen. I.e.
    Code:
    if (ch[0][i] == 'a') printf("ka");
    Once you have a basic program working, start incorporating some of the features in Norminal Animal's post
    Fact - Beethoven wrote his first symphony in C

  12. #27
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Click_here is right; I answered the question literally, showing how I'd do it.

    It might be much better to write a simpler program that say took the name as a command-line parameter, and just prints out the parameters and corresponding ninja names:
    Code:
    #include <stdio.h>
    #include <string.h>
    
    static const char ninja_letter[26][4] = {
        "ka",  "zu",  "mi",  "te",  "ku",  "lu",  "ji",  "ri",
        "ki",  "zu",  "me",  "ta",  "rin", "to",  "mo",  "no",
        "ke",  "shi", "ari", "chi", "do",  "ru",  "mei", "na",
        "fu",  "zi"
    };
    
    
    int main(int argc, char *argv[])
    {
        int arg;
    
        /* If the user supplies no names, or just -h or --help
         * on the command line, show help.
        */
        if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            fprintf(stderr, "\n");
            fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
            fprintf(stderr, "       %s NAME(s)...\n", argv[0]);
            fprintf(stderr, "\n");
            return 1;
        }
    
        /* Loop over each command-line argument. */
        for (arg = 1; arg < argc; arg++) {
            const char *next = argv[arg];
            char        dash = '\0';
    
            /* First, print the original name; no newline. */
            printf("%s: ", argv[arg]);
    
            /* Scan each character in the name: */
            while (*next) {
                if (*next >= 'a' && *next <= 'z') {
    
                    /* This is a lowercase letter. Print the separator, if any, */
                    if (dash != '\0')
                        fputc(dash, stdout);
    
                    /* and then the ninja letter. */
                    fputs(ninja_letter[*(next++) - 'a'], stdout);
    
                    /* The next separator is a dash, since this was a letter. */
                    dash = '-';
    
                } else
                if (*next >= 'A' && *next <= 'Z') {
    
                    /* This is an uppercase letter. Print the separator, if any, */
                    if (dash != '\0')
                        fputc(dash, stdout);
    
                    /* and then the ninja letter. */
                    fputs(ninja_letter[*(next++) - 'A'], stdout);
    
                    /* The next separator is a dash, since this was a letter. */
                    dash = '-';
    
                } else {
    
                    /* This is not a letter, so skip it. */
                    next++;
    
                    /* Next separator is a space. */
                    dash = ' ';
                }
            }
    
            /* Print a newline, ending the ninja name. */
            fputs("\n", stdout);
    
            /* Flush pending output right now. */
            fflush(stdout);
        }
    
        return 0;
    }
    Above, the only real trick is with the dash variable: I keep the separator, be it a dash or space, in it. It is printed (if not an end-of-string character '\0') before every letter. After each letter, it is set to dash, otherwise to space. That way you get a dash between letters, but a space after a non-letter character in the name (even if it is not printed).

    If you have trouble following how the dash actually works, consider the logic this way:
    Code:
    set separator to (none)
    
    for each character in name:
    
        if character is a letter:
    
            if separator is not (none):
                output separator
            end if
    
            output ninja letter
    
            set separator to (dash)
    
        else:
            set separator to (space)
        end if
    
    end for
    
    output end-of-name
    and do it by hand for a short string with letters and non-letters.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Calculating Target Lead
    By Jesse Richards in forum Game Programming
    Replies: 2
    Last Post: 09-05-2012, 06:37 PM
  2. questions and answers from a file
    By jaralvarado in forum C Programming
    Replies: 10
    Last Post: 07-24-2011, 10:10 AM
  3. questions on following statement
    By sanddune008 in forum C Programming
    Replies: 5
    Last Post: 07-30-2010, 03:14 AM
  4. Giving Whole Answers to Questions
    By Kleid-0 in forum A Brief History of Cprogramming.com
    Replies: 59
    Last Post: 01-30-2005, 10:26 PM
  5. Rookie needs a lead
    By DirtElk in forum C Programming
    Replies: 5
    Last Post: 02-18-2002, 08:27 AM

Tags for this Thread