Generating random numbers

Random numbers are commonly necessary in computer programs. Phobos' std.random module offers a variety of random number generators and other functions related to randomization. Here, we'll create a little number guessing game with a replay function. The user guesses the generated number, and then the program tells them if they were high or low.

How to do it…

Let's generate random numbers by executing the following steps:

  1. Create a random number generator, seeding it with the replay value, if given, or unpredictableSeed for a new game.
  2. Generate a random number between 0 and 100.
  3. Ask the user for their guess. Tell them if they were low or high and let them continue trying until they get it. Save each line of user input to the replay file.
  4. When the user is done playing, let them know how many tries they took to get the correct number.

The code is as follows:

import std.random, std.conv, std.string, std.stdio;

int playRound(ref Random generator, File userInput, File saveFile) {
    int tries = 0;
    auto value = uniform(0, 100, generator);

    writeln("Guess the number between 0 and 100:");
    while(true) {
        tries++;
        auto guess = userInput.readln().strip().to!int;;;
        if(saveFile.isOpen) saveFile.writeln(guess); 
        // save in the replay file
        if(guess == value) break; // correct!
       writefln("Your guess of %s was too %s, please try again,", guess, guess > value ? "high" : "low");
    }
    writefln("Correct! You guessed %d in %d tries.", value, tries);
     return tries;
  }
void main(string[] args) {
    Random gen;
    File userInput, saveFile;
    // prepare input and seed the generator
     if(args.length > 1) {
         // use the given replay file
        userInput = File(args[1], "rt");
        gen.seed(to!uint(userInput.readln().strip())); 
        // using the saved seed = same game
    } else {
        // new game, use an unpredictable seed and
        //create a replay file
       userInput = stdin; // take input from the keyboard
       auto seed = unpredictableSeed;; // a random game
       gen.seed(seed);
       saveFile = File("replay.txt", "wt");
      saveFile.writeln(seed); 
      // save the seed so we can reproduce the game later
    }
   int totalTries = 0;
   foreach(round; 0 .. 3)
       totalTries += playRound(gen, userInput, saveFile);
   writefln("You guessed three numbers in %s tries!", totalTries);
}

How it works…

The overall idea of the game is to create numbers that look random, but can actually be reproduced for the replay. To achieve this goal, we used Phobos' std.random module and created our own random number generator, logging the seed we use for each game. The user's input is also logged. Given the random seed and user input, we can reliably recreate a game.

Tip

If you don't need precise control over the random number generator, you can use the default settings by calling the functions without the final argument. For example, auto randomNumber = uniform(0, 100); gives a number from a preselected and automatically seeded default random number generator.

Let's look at the code, starting from main. The first thing you'll notice is that this main function took arguments. These are passed to the program on the command line. The args[0] variable will be set to the name of the program, then args[1 .. $] are the other arguments the user passed, if any. For example, if the program is executed with the following command line:

game replay.txt arg2

Then, the args variable passed to main would have the following content: ["game", "replay.txt", "arg2"]. That's why we use if(args.length > 1) instead of args.length > 0. There's always at least one command-line argument: the name of the program in args[0].

There are two File variables. File is the file I/O of std.stdio. It wraps C's I/O which is based upon FILE* to provide maximum C compatibility while presenting a more D-like interface. Our two files are userInput and saveFile. The userInput file is used to feed the user input to the program. The saveFile file is used to save the user input for later use in the replay.

If the replay file is provided on the command line, we use it as user input. The first line of this is the random seed, so we immediately load that. If no replay file is provided, we use stdin, the keyboard, for user input and open a new file called replay.txt, which is opened as a writable text (wt) file, for saveFile. We also make an unpredictableSeed, use it to initialize the random number generator, and save it to the file for future use.

The stdin file is readable and the stdout file is writable. All the methods that you can use on these files can also be used on any other file and vice versa. In fact, the global writeln function simply forwards to stdout.writeln.

Once we're set up, we used foreach over a numeric interval to run playRound three times and then write out the results. The writefln function, unlike writeln, uses a format string instead of printing out each argument in order. The format string, which comes from C's printf function, can get very complex. Here, we only used a simple one. The text in the string is printed, with %s instances being replaced with the value of the next argument to writefln. Unlike in C, in D you can always use the %s specifier for any argument and it will be automatically converted to string for you. Other specifiers, such as %d, can be used to give more control over formatting, such as leading zeroes, field length, precision, and more. Look up the options for printf, including Posix positional parameters, to see more options.

Next, let's look at the playRound function. The first thing to note is that it takes the random number generator by ref. This is important; since Random is a struct, passing it to a function means the function would work on a copy. When we called playRound the second time, since the variable in main would not be updated, it would always create the same random number! Passing the random number generator by reference ensures that it gets updated and won't repeat the same values. You will almost always want to pass random number generators to functions by reference.

Let's also look at the following line of code in more detail:

userInput.readln().strip().to!int;

The readln method is a method of the File object that reads the next line, including the new line character at the end. To get rid of the new line character, we call strip, a function from std.string. The strip function removes whitespace from both the beginning and the end of a string. Finally, we convert the string to an integer by using std.conv.to. Thanks to UFCS, we can write this using left-to-right dot syntax. There's no parentheses at the end of to!int because empty parentheses on a function call are optional. This is part of D's property syntax, which we'll discuss in more depth later.

The final piece of syntax that may be new to you is guess > value ? "high" : "low". This is called the ternary operator, inherited from C. The syntax is condition ? ifTrue: ifFalse. It is essentially a miniature if statement.

There's more…

The best way to win this game is to use a binary search algorithm. First, guess 50. If you are too high, try 25. If too low, try 75. For each guess, you can cut the possibilities in half until you land at the solution. You should be able to consistently guess each number in about six tries. In Phobos' std.algorithm module, there are specializations on the search algorithms for sorted ranges that use this same technique to very rapidly search even large sets of data.

Note

Phobos' std.random function is not cryptographically secure. While it is useful for games and other insensitive tasks, if you need to protect your data, a better source of randomness is required. Third-party cryptographic libraries such as OpenSSL provide functions for these tasks. Bindings can be found in the Deimos collection at http://github.com/d-programming-deimos.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.106.7