- D Cookbook
- Adam D. Ruppe
- 1312字
- 2021-07-16 11:50:46
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:
- Create a random number generator, seeding it with the replay value, if given, or
unpredictableSeed
for a new game. - Generate a random number between 0 and 100.
- 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.
- 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.
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.
- Java語言程序設計
- Mastering Spring MVC 4
- Apache Hive Essentials
- HBase從入門到實戰
- Apache Karaf Cookbook
- Python機器學習編程與實戰
- Selenium Testing Tools Cookbook(Second Edition)
- Linux C編程:一站式學習
- 深入淺出Go語言編程
- Mastering Elixir
- 奔跑吧 Linux內核
- Drupal 8 Development:Beginner's Guide(Second Edition)
- DevOps 精要:業務視角
- Building Clouds with Windows Azure Pack
- Microsoft Windows Identity Foundation Cookbook