Jump to content

giving a range in C

goatedpenguin

Hi all, I am VERY new to C and I am learning it by making a small program of Rock, paper, scissors. Now the way it works is that the user enters either Rock, paper or scissors and the program choose a random choice by crossing a random number. Here is my logic about the way I want to go about this:

char RPS[2] = {"Rock", "Paper", "Scissors"};
//I need to give it a range of from [0, 2](inclusive) so that it can print the element out at whatever the random number is but I dont know how to do this
//Also the way I made the list is wrong so I need some help there. If someone can explain me how to make a string in C I would appreciate it.

 

Thanks in advance 🙂

Link to comment
Share on other sites

Link to post
Share on other sites

C does not have a string data type, it only has char arrays. The array you've created has a size of two, so it can only contain two characters, not three strings.

 

So for your use case you'd need an array of char arrays (char[][]). But since you only have three choices, it's likely easier to just pick a random number, then use a switch case to print the appropriate output.

 

One way you can limit the range of a random number is by using a modulo division (which is not ideal, but it'll do):

int r = rand() % 3;

This generates a random number, then divides it by 3 and returns the remainder, which is a number between 0–2.

 

Now just switch over the random number and print the appropriate word

switch(r) {
  case 0:
    // print Rock
    break;
  case 1:
    // print Paper
    break;
  default:
    // print Scissors
}

 

Spoiler
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

void main() {
    char values[3][8] = {
        "Rock",
        "Paper",
        "Scissors"
    };

    srand(time(NULL));
    int n = rand() % 3;
    printf("%d - %s\n", n, values[n]);
}

 

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

Thanks for all the help but can you explain how to make a "string" in C and what srand is(I am really confused on what a seed is since other languages don't have such a concept)? 🙂

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, goatedpenguin said:

Thanks for all the help but can you explain how to make a "string" in C

As I said above, C does not have a string data type. The only way to get a "string" in C is to use a char array.

 

For example you could also create the array of "strings" in my example above like this:

char rock[] = "Rock";
char paper[] = "Paper";
char scissors[] = "Scissors";

char *values[] = {rock, paper, scissors};

 

This creates three char arrays, containing "Rock", "Paper", "Scissors", then an array of char-pointers (with length 3), pointing to each of them.

 

1 hour ago, goatedpenguin said:

I am really confused on what a seed is since other languages don't have such a concept

Most languages have this concept, actually. Though more modern languages may choose to hide it from the developer (at least by default). For example in Java, if you do

final var random = new Random();

it actually generates a new seed for this instance of Random internally. But you can explicitly specify a seed, if you want:

final var random = new Random(0);

 

Random numbers are generally only pseudo-random. They use an algorithm that produces a seemingly random sequence of numbers. When you start with the same seed, the sequence this algorithm produces is always the same. That's why you typically use something like the current date (in milliseconds) as a seed, to ensure the sequence is different from the last time. You should only seed the random number generator once (e.g. on app start)

 

For example, if I do this in C, the sequence of random numbers returned by rand is always the same:

srand(0);

for(int n = 0; n < 100; n++) {
    int n = rand();
    printf("%d,", n);
}

Run this code a few times, and you'll see that is returns the same 100 "random" numbers each time.

 

Which would make for a rather boring game of rock, paper, scissors, since you can now predict what the program will choose. If you use "srand(time(NULL));" instead, the sequence is a different one each time.

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

Just now, Eigenvektor said:

As I said above, C does not have a string data type. The only way to get a "string" in C is to use a char array.

 

For example you could also create the array of "strings" in my example above like this:

char rock[] = "Rock";
char paper[] = "Paper";
char scissors[] = "Scissors";

char *values[] = {rock, paper, scissors};

 

This creates three char arrays, containing "Rock", "Paper", "Scissors", then an array of char-pointers (with length 3), pointing to each of them.

 

Most languages have this concept, actually. Though more modern languages may choose to hide it from the developer (at least by default). For example in Java, if you do

final var random = new Random();

it actually generates a new seed for this instance of Random internally. But you can explicitly specify a seed, if you want:

final var random = new Random(0);

 

Random numbers are generally only pseudo-random. They use an algorithm that produces a seemingly random sequence of numbers. When you start with the same seed, the sequence this algorithm produces is always the same. That's why you typically use something like the current date (in milliseconds) as a seed, to ensure the sequence is different from the last time. You should only seed the random number generator once (e.g. on app start)

 

For example, if I do this in C, the sequence of random numbers returned by rand is always the same:

srand(0);

for(int n = 0; n < 100; n++) {
    int n = rand();
    printf("%d,", n);
}

Run this code a few times, and you'll see that is returns the same 100 "random" numbers each time.

 

Which would make for a rather boring game of rock, paper, scissors, since you can now predict what the program will choose. If you use "srand(time(NULL));" instead, the sequence is a different one each time.

Thanks for all the help I will share the code once I am done on this post so that yall can give me some suggestions for improving it. 🙂

Link to comment
Share on other sites

Link to post
Share on other sites

4 hours ago, goatedpenguin said:

Thanks for all the help but can you explain how to make a "string" in C and what srand is(I am really confused on what a seed is since other languages don't have such a concept)? 🙂

Eigenvektor did a great job explaining how to use srand, but I can explain a bit more about strings in C.

 

In C, a string is just an array of chars ending with a null byte. We can construct a string from scratch like this:

#include <stdio.h>

int main() {
    // Allocate space to hold the string
    char string[10];

    // Place characters in the array
    string[0] = 'H';
    string[1] = 'e';
    string[2] = 'l';
    string[3] = 'l';
    string[4] = 'o';
    // Don't forget the null byte
    string[5] = '\0';

    // Print the string to the terminal
    puts(string);

    return 0;
}

This prints out Hello to the terminal. Note that even though our array has enough space to store 10 chars, only 5 get printed out. This is because functions operating on strings, like puts and strlen, stop when they encounter a null byte. That's the '\0' in the code above. If your string doesn't end with a null byte, then string functions can run further than they should and access invalid memory.

 

That being said, you most likely won't work with strings in this way very often. It's more convenient to use string literals like what you did in your original post. All three of these are valid ways of storing a string using string literals:

// String literals are automatically null-terminated
char s1[6] = "Hello";
char s2[] = "Hello";
char* s3 = "Hello";

The difference between the first two methods and the third is that the third is read-only. This is because the first two strings are allocated on the stack while the third is allocated in a read-only section of the program.

 

For example, we're able to modify the first two strings no problem:

#include <stdio.h>

int main() {
    // String literals are automatically null-terminated
    char s1[6] = "Hello";
    char s2[] = "Hello";
    char* s3 = "Hello";

    // Modify the first two strings
    s1[1] = 'a';
    s2[0] = 'Y';

    // Print the strings to the terminal
    puts(s1);
    puts(s2);
    puts(s3);

    return 0;
}

image.png.711285b287cfb498c66acfb6b8e98404.png

 

But trying to modify the third string crashes the program with a segmentation fault, because we tried writing to read-only memory.

#include <stdio.h>

int main() {
    // String literals are automatically null-terminated
    char s1[6] = "Hello";
    char s2[] = "Hello";
    char* s3 = "Hello";

    // Try to modify the third string
    s3[1] = 'a';

    // Print the strings to the terminal
    puts(s1);
    puts(s2);
    puts(s3);

    return 0;
}

image.png.5a2f8a298bff0f4529df12e2d849e30f.png

 

The advantage of using string literals is that you can let the compiler decide how to store the string, so you don't have to worry about the size of the array or forgetting the null terminator. Lastly, here's how I would write the code you initially posted:

Spoiler
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    char* RPS[3] = {"Rock", "Paper", "Scissors"};

    // Generate a random number between 0 and 2 (inclusive)
    srand(time(NULL));
    int index = rand() % 3;
    printf("%s\n", RPS[index]);

    return 0;
}

 

 

 

Computer engineering grad student, cybersecurity researcher, and hobbyist embedded systems developer

 

Daily Driver:

CPU: Ryzen 7 4800H | GPU: RTX 2060 | RAM: 16GB DDR4 3200MHz C16

 

Gaming PC:

CPU: Ryzen 5 5600X | GPU: EVGA RTX 2080Ti | RAM: 32GB DDR4 3200MHz C16

Link to comment
Share on other sites

Link to post
Share on other sites

Thank you so much for clearing all the misconceptions for now i am going to keep my code simple and not use pointers yet since i don’t have a good understanding of them. Again thanks everyone for the help i will make sure to give a update of my code in a lil bit. 😉

Link to comment
Share on other sites

Link to post
Share on other sites

So I have finished my program and it is a bit more nuanced but the while loop in my program is screwing it up and its not working. Code is below, suggestions to improve it will be great too thanks 🙂

 

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    
    //var init and set a random num
    srand(time(NULL));
    int random = rand() % 3;
    char RPS[3][9] = {"Rock", "Paper", "Scissors"};
    char Input_RPS[9] = {""};
    char *computer_choice;
    computer_choice = RPS[random];
    
    
   
    
    //logic
    while(1){
        
        printf("Enter Rock, Paper or Scissors and see if you win! Enter q to quit the program. All lower caps! ");
        scanf("%s", Input_RPS);

        if(Input_RPS == "q"){
            printf("Sorry to see you go");
            break;
        }
        else if(computer_choice == Input_RPS){
            printf("I choose the same one too its a draw! Gl to both of us next time ;) ");
            
        }
        else if(computer_choice == "Rock" && Input_RPS == "paper"){
            printf("You won!");
        }
        else if(computer_choice == "Paper" && Input_RPS == "Rock"){
            printf("You lost!");
        }
        else if(computer_choice == "Rock" && Input_RPS == "scissors"){
            printf("You lost!");
        }
        else if(computer_choice == "Scissors" && Input_RPS == "rock"){
            printf("You won!");
        }
        else if(computer_choice == "Paper" && Input_RPS == "scissors"){
            printf("You won!");
        }
        else if(computer_choice == "Scissors" && Input_RPS == "paper"){
            printf("You lost!");
        }

    }
}

 

Link to comment
Share on other sites

Link to post
Share on other sites

In C, you cannot use == to check for string equality. This is because the == operator checks if the strings' pointers are equal, rather than checking the characters they contain. To fix this, use the strcmp function. strcmp will return 0 if the two strings are equal.

 

For example, this code:

if(Input_RPS == "q"){
    printf("Sorry to see you go");
    break;
}

would be correctly written as:

if(strcmp(Input_RPS, "q") == 0){
    printf("Sorry to see you go");
    break;
}

 

Computer engineering grad student, cybersecurity researcher, and hobbyist embedded systems developer

 

Daily Driver:

CPU: Ryzen 7 4800H | GPU: RTX 2060 | RAM: 16GB DDR4 3200MHz C16

 

Gaming PC:

CPU: Ryzen 5 5600X | GPU: EVGA RTX 2080Ti | RAM: 32GB DDR4 3200MHz C16

Link to comment
Share on other sites

Link to post
Share on other sites

8 hours ago, dcgreen2k said:

In C, you cannot use == to check for string equality. This is because the == operator checks if the strings' pointers are equal, rather than checking the characters they contain. To fix this, use the strcmp function. strcmp will return 0 if the two strings are equal.

 

For example, this code:

if(Input_RPS == "q"){
    printf("Sorry to see you go");
    break;
}

would be correctly written as:

if(strcmp(Input_RPS, "q") == 0){
    printf("Sorry to see you go");
    break;
}

 

I see thanks for the help, one questions though, if I #include many headers could this slow the performance of a program and what is the difference between including a header and importing a module(like python has modules but C has headers are they the same thing or do they differ a bit?)

Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, goatedpenguin said:

I see thanks for the help, one questions though, if I #include many headers could this slow the performance of a program and what is the difference between including a header and importing a module(like python has modules but C has headers are they the same thing or do they differ a bit?)

Including headers in C does not slow down the program. It can only slow down compilation, but this isn't something you should be worried about.

 

Including a header and importing a module let you do similar things but are very different under the hood. In C and C++, including a header is very simple - the compiler copies and pastes the contents of the header file into your source code file where the #include statement is. Python's import statement is much more complex and I'm not that familiar with the specifics of it. However, imports are done at runtime since Python code isn't compiled in the same way C code is.

 

In short:

C's #include: May slow down compilation, no effect on runtime speed

Python's import: May slow down runtime

Neither of these are things you should worry about, though

Computer engineering grad student, cybersecurity researcher, and hobbyist embedded systems developer

 

Daily Driver:

CPU: Ryzen 7 4800H | GPU: RTX 2060 | RAM: 16GB DDR4 3200MHz C16

 

Gaming PC:

CPU: Ryzen 5 5600X | GPU: EVGA RTX 2080Ti | RAM: 32GB DDR4 3200MHz C16

Link to comment
Share on other sites

Link to post
Share on other sites

13 hours ago, dcgreen2k said:

To fix this, use the strcmp function. strcmp will return 0 if the two strings are equal.

I never really understood why stricmp was never implemented in the C standard (or at least not the ones I learned formally)...although I just import the Windows stuff so I get stricmp.

 

Anyways @goatedpenguin, one thing to note as well strcmp doesn't handle lower case and upper case differences.  So strcmp("rock", "Rock") will not be the same according to it.  Just something to keep in mind when you are testing.

 

To explain a bit why "cat" == variable_with_cat doesn't work though, you will need to think of C in terms of what it's comparing

 

When you define something like

char var[4] = "cat";

char var2[4] = "cat";

There is a place in memory which stores the value for cat for var and also another area where it stores var2...now in C, strings like that the variable just holds the memory address.  So like var might = 0x00234; in memory, and var2 might = 0x00238; in memory.

So when you compare

var == var2, you actually are comparing

0x00234 == 0x00238.

 

That is why comparing doesn't work.

 

As a note, you have also capitalized Rock in one of the cases...which you told the user to write in lower case 😉

 

 

 

Okay, not for commenting about your code.  It might be beneficial to convert what the user inputs into just a number...instead of all comparing the char*. 

 

const char* validOptions[] = {"rock", "paper", "scissors"};


//returns -1 if not a valid option or it will return the valid option #
int userInputToCode(const char* input) {
	int i = 0;
    for(i = 0; i < 3; i++) { //Extra points if you replace 3 with the size of validOptions...but I didn't want to get too detailed
    	if(stricmp(validOptions, input) == 0)
        	return i;
    }
    return -1
}

//Returns 0 if equal...but 1 if it isn't equal
int stricmp(const char* lhs, const char* rhs) {
    while(lhs != 0 && rhs != 0) {
    	if(tolower(lhs) != tolower(rhs)) //Compare case insenstive
        	return 1;
    }
	return 0;
}

 

One of the reasons why I would do this though is so I could generalize it later on if I wanted other types of options without having to change it everywhere in the code.  That's the thing, a bit of extra time early on can save you a ton of time refactoring later on.

3735928559 - Beware of the dead beef

Link to comment
Share on other sites

Link to post
Share on other sites

One minor thing to point out:

char Input_RPS[9] = {""};

scanf("%s", Input_RPS);

This is technically a security vulnerability (possible buffer overflow). You're creating an array with a fixed size, then accept user input of arbitrary length. This means a user can enter as many characters as they want, exceeding the size of your buffer, potentially overflowing into other memory regions. Best case, nothing happens because the memory is unused. Worst case… who knows (i.e. undefined behavior).

 

You can use "%8s" to limit the input size that is read to 8 characters:

scanf("%8s", Input_RPS);

Though, afaik, the recommended way of doing it is:

fgets(Input_RPS, sizeof(Input_RPS), stdin);

 

You can also simplify your logic by working with numbers, rather than char arrays, internally. Meaning convert user input into a number ("rock" -> 0, "paper" -> 1, "scissors" -> 2), then compare these directly to the chosen random number.

Spoiler
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int sanitize(char* user_input) {
    // Ignore anything but first character, since it is distinct enough
    if (user_input[0] == 'r' || user_input[0] == 'R') {
        return 0;
    } else if (user_input[0] == 'p' || user_input[0] == 'P') {
        return 1;
    } else if (user_input[0] == 's' || user_input[0] == 'S') {
        return 2;
    } else if (user_input[0] == 'q' || user_input[0] == 'Q') {
        return -1;
    }
    return -2;
}

void evaluate(int user_choice, int game_choice, int *user_score, int *game_score) {
        if (user_choice == game_choice) {
            printf("Draw\n");

        } else if ((user_choice + 1) % 3 == game_choice) {
            printf("You lose!\n");
            ++(*game_score);

        } else {
            printf("You win!\n");
            ++(*user_score);

        }
}

int main(){
    srand(time(NULL));

    char RPS[3][9] = {
        "Rock",
        "Paper",
        "Scissors"
    };

    char user_input[9] = {};

    int game_score = 0;
    int user_score = 0;

    printf("Enter '[r]ock', '[p]aper' or '[s]cissors' and see if you win!\n");
    printf("Enter 'q' to quit\n");

    //logic
    while(1){
        printf("\nPlease enter [r],[p],[s],[q]: ");
        fgets(user_input, sizeof(user_input), stdin);
        int user_choice = sanitize(user_input);

        if(user_choice == -1){
            printf("Sorry to see you go\n");
            exit(0);
        } else if (user_choice < 0) {
            continue;
        }

        int game_choice = rand() % 3;

        printf("Your choice: %s\n", RPS[user_choice]);
        printf("Mine       : %s\n", RPS[game_choice]);

        evaluate(user_choice, game_choice, &user_score, &game_score);
        printf("\nYour score is %d wins, %d losses\n", user_score, game_score);
    }
}

 

 

Remember to either quote or @mention others, so they are notified of your reply

Link to comment
Share on other sites

Link to post
Share on other sites

7 hours ago, Eigenvektor said:

Though, afaik, the recommended way of doing it is:

True, should also be checking for null pointer after the read as well

if(fgets(user_input, sizeof(user_input), stdin) == NULL) {
    //code because the user put in a blank input
    continue;
}

 

3735928559 - Beware of the dead beef

Link to comment
Share on other sites

Link to post
Share on other sites

Hi all sorry for not replying for a long time after digesting all the info I managed to finish my program and just wanted to say thanks I will be soon making a new post for my other project. 🙂

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×