Gigi Labs

Please follow Gigi Labs for the latest articles.

Sunday, March 16, 2014

C: Rock, Scissors, Paper (using random numbers)

Hello everyone, and welcome back to the Ranch! :)

In my last C article, "C: Calculating the Average of Several Numbers", we learned how to work with numbers in C. This included using integer variables, input/output of integers, and arithmetic operations.

Today, we're going to take this further and learn how to generate random numbers in C. We'll use this to write a little Rock, Scissors, Paper game.

Pseudo-random numbers in C


So let's start off with a pretty empty program:

#include <stdio.h>

int main(int argc, char ** argv)
{
    return 0;
}

We can generate a random integer using the rand() function, which is defined in stdlib.h. This gives us a random number between 0 and a constant integer called RAND_MAX. We can output the value of RAND_MAX as we would any other integer, although its value is not guaranteed to be the same across different computers:

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

int main(int argc, char ** argv)
{
    int a = rand();
    int b = rand();
    int c = rand();

    printf("RAND_MAX is: %d\n", RAND_MAX);
    printf("a is: %d\n", a);
    printf("b is: %d\n", b);
    printf("c is: %d\n", c);

    return 0;
}

Let us now run this program a few times and see what happens:


On my computer, RAND_MAX is defined as 2147483647, so each time I call rand(), it will give me a random number between 0 and 2147483647.

Another thing you'll see from the output is that each time I run the program, I get the same values for a, b and c. That isn't quite random, is it?

The reason why this is happening is that these random numbers aren't really random.  They are actually generated from a mathematical sequence, that takes a very, very long time to begin repeating itself, but which isn't truly random. These are called pseudo-random numbers. Imagine this sequence:

4, 29, 55, 12, 4, 7, 3, 97, 84, ...

This sequence seems pretty random; there isn't any pattern in it. So each time we get a random number with rand(), we'll get a different number: first 4, then 29, then 55, etc.

Our problem is that each time we run the program, we're starting from the beginning of the sequence, and this gives us the same values. So what we want to do is start from a different position each time.

We do this by seeding the pseudo-random number generator using the srand() function. Let's just add this line at the beginning of main():

    srand(100);

Let's run the program again:


You'll notice the numbers have changed, since we're using a different seed. However, each time we run the program, the same numbers are repeated. In fact we are still making the same mistake as before: we are using the same seed, and starting the pseudo-random number sequence from the same point each time.

In order to use a different seed each time we run the program, we can use the current time as the seed. We can use the time() function, defined in time.h, to give us the current time. So let's include that header file at the top...

#include <time.h>

...and replace our call to srand() with this:

srand(time(NULL));

When we run the program, things work as we expect, and we get different pseudorandom numbers each time we run:


Scaling random numbers


Great. Now, the numbers we are generating are a little bit big. For example, for Rock, Scissors, Paper, we don't need a number between 0 and 2147483647; all we need is a number between 1 and 3 (both inclusive), to represent either rock, scissors, or paper. We can limit the range of a pseudorandom number by dividing it by the maximum we want, and extracting the remainder. This is done using the modulus (%) operator, which gives us the remainder of a division:

    int a = rand() % 10;
    int b = rand() % 10;
    int c = rand() % 10;

Let's say rand() gives us 55. If we divide 55 by 10, then the remainder can be at most 9. So what we're doing above is limiting each random number to the range 0 to 9 (both inclusive). We'll need to add 1 to the number by which we're dividing, in order to make it inclusive (i.e. between 0 and 10 in this case).

What if we need a minimum value that is not zero? In short, we declare a general-purpose function that returns random integers between two integers (both inclusive) like this:

// returns a random number between min and max, both inclusive)
int randomInt(int min, int max)
{
    return min + (rand() % (max - min + 1));
}

We can then use this function from within our main() like this, to give us random numbers between 1 and 10:

    int a = randomInt(1, 10);
    int b = randomInt(1, 10);
    int c = randomInt(1, 10);

And voilĂ :


Rock, Scissors, Paper in C


OK, so now that we know how to generate pseudorandom numbers, we can write out game of Rock, Scissors, Paper. In this game, two people independently choose one of rock, scissors or paper, and state their choice at the same time. The outcome is then as follows:

  • Rock beats scissors (breaks it).
  • Scissors beats paper (cuts it).
  • Paper beats rock (wraps it).

To start off, let's throw away all our code in main(), but keep our randomInt() function:

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

// returns a random number between min and max, both inclusive)
int randomInt(int min, int max)
{
    return min + (rand() % (max - min + 1));
}

int main(int argc, char ** argv)
{
    // code goes here

    return 0;
}

First, we add code to accept user input:

    int input = -1;

    while (input != 0)
    {
        printf("Choose...\n");
        printf("   1 for rock\n");
        printf("   2 for scissors\n");
        printf("   3 for paper\n");
        printf("or 0 to exit\n");

        scanf("%d", &input);
    }

The user will choose a number between 1 and 3 to represent rock, scissors, or paper respectively. A while loop repeats the game until the user enters 0 to exit the game. We have already used for loops in C: Calculating the Average of Several Numbers, but a while loop will simply keep executing the code within curly brackets as long as the condition within round brackets is true, in this case as long as input is not zero:


Once the human player has chosen, the computer player makes his choice at random:

       int computerChoice = randomInt(1, 3);

We can then display the computer's choice:

       switch(computerChoice)
       {
           case 1: printf("Computer chooses rock\n"); break;
           case 2: printf("Computer chooses scissors\n"); break;
           case 3: printf("Computer chooses paper\n"); break;
       }

A switch statement allows us to specify separate code to be executed depending on the value of a variable. In this case, if computerChoice is 1, we write that the computer has chosen rock, and similarly for the other choices. Note that each case in a switch statement should always end in a break statement; otherwise unexpected things may happen. :)

Finally, we can add the game logic to check who won:

       if (input == computerChoice)
       {
           printf("Game ends in a draw.\n");
       }
       else if (input == 1 && computerChoice == 2)
       {
          printf("Rock beats scissors! You win!\n");
       }
       else if (input == 2 && computerChoice == 3)
       {
          printf("Scissors beats paper! You win!\n");
       }
       else if (input == 3 && computerChoice == 1)
       {
          printf("Paper beats rock! You win!\n");
       }
       else if (computerChoice == 1 && input == 2)
       {
          printf("Rock beats scissors! Computer wins!\n");
       }
       else if (computerChoice == 2 && input == 3)
       {
          printf("Scissors beats paper! Computer wins!\n");
       }
       else if (computerChoice == 3 && input == 1)
       {
          printf("Paper beats rock! Computer wins!\n");
       }

Using a cascade of if statements, we compare the user input with the computer's random choice and see who won. The == operator allows us to compare two integers and determine whether they are equal. The && operator, on the other hand, is a boolean AND operator. If both conditions around the && operator are true, then the printf() statement within the curly brackets of that if statement is executed.

We can now actually play our game:


Summary


Great! :) We have a fully working Rock, Scissors, Paper game here. And in creating it, we learned about pseudo-random numbers, while loops, if statements, switch statements, and also a few operators including == (is equal to), != (is not equal to) and && (boolean AND). Below is the full code.

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

// returns a random number between min and max, both inclusive)
int randomInt(int min, int max)
{
    return min + (rand() % (max - min + 1));
}

int main(int argc, char ** argv)
{
    int input = -1;

    while (input != 0)
    {
        printf("Choose...\n");
        printf("   1 for rock\n");
        printf("   2 for scissors\n");
        printf("   3 for paper\n");
        printf("or 0 to exit\n");

        scanf("%d", &input);

       int computerChoice = randomInt(1, 3);

       switch(computerChoice)
       {
           case 1: printf("Computer chooses rock\n"); break;
           case 2: printf("Computer chooses scissors\n"); break;
           case 3: printf("Computer chooses paper\n"); break;
       }

       if (input == computerChoice)
       {
           printf("Game ends in a draw.\n");
       }
       else if (input == 1 && computerChoice == 2)
       {
          printf("Rock beats scissors! You win!\n");
       }
       else if (input == 2 && computerChoice == 3)
       {
          printf("Scissors beats paper! You win!\n");
       }
       else if (input == 3 && computerChoice == 1)
       {
          printf("Paper beats rock! You win!\n");
       }
       else if (computerChoice == 1 && input == 2)
       {
          printf("Rock beats scissors! Computer wins!\n");
       }
       else if (computerChoice == 2 && input == 3)
       {
          printf("Scissors beats paper! Computer wins!\n");
       }
       else if (computerChoice == 3 && input == 1)
       {
          printf("Paper beats rock! Computer wins!\n");
       }
    }

    return 0;
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.