A simple guessing game in D

This short post is inspired by the recent spate of languages introducing themselves through a small game program. As such, here is a version of the guessing game in D.

The objective of the game is very simple – there is a secret number randomly generated per game (in the range [1, 100] for simplicity), and the user has to guess the number to exit the game.

I also wanted to get a bit more practice with the de facto package cum dependency manager for D, dub.

Let’s start off by creating the project:

Macushla:MiniProjects z0ltan$ dub init guessing_game
Package recipe format (sdl/json) [json]: json
Name [guessing_game]: 
Description [A minimal D application.]: A simple guessing game in D
Author name [Timmy Jose]: 
License [proprietary]: MIT
Copyright string [Copyright © 2017, Timmy Jose]: 
Add dependency (leave empty to skip) []: 
Successfully created an empty project in '/Users/z0ltan/Rabota/ProgrammingLanguages/D/MiniProjects/guessing_game'.
Package successfully created in guessing_game

By default, dub also creates a .gitignore file in the root directory of the new project/package.

Here’s how the layout of the default configuration looks like:

Macushla:MiniProjects z0ltan$ cd guessing_game/
Macushla:guessing_game z0ltan$ tree .
.
├── dub.json
└── source
    ├── app.d

1 directory, 2 files

The dub.json file contains the basic configuration metadata for the project, and is for consumption by dub. Its initial contents are the data entered during the creation of the project.

A sample file, app.d is created inside the source directory. These can all be customised, of course.

Okay, so let’s create a new file to contain our guessing game code as a module.

Macushla:MiniProjects z0ltan$ touch source/guessing_game.d

All right, time to code up the game. Here are the contents of source/guessing_game.d

module guessing_game;

import std.stdio;

public void playGame() 
{
	import std.random: uniform;

	immutable uint secretNumber = uniform(1, 101);
	uint guesses = 0;

	writeln("\nLet's play a guessing game. Your task is to guess the secret number (between 1 and 100, inclusive)...");

	import std.conv: ConvException;

	while (true) {
		try {
			uint guess = getNumber("Enter your guess: ");

			++guesses;
			if (guess == secretNumber) {
				writefln("You win! You took %s guesses.", guesses);
				break;
			} else if (guess < secretNumber) {
				writeln("Too small!");
			} else {
				writeln("Too big!");
			}
		} catch (ConvException ex) {
			writeln("Did not get valid input. Try again...");
		}
	}
}


uint getNumber(string prompt) 
{
	writeln(prompt);

	import std.conv: to;
	import std.string: chomp;
		
	return readln().chomp.to!uint;
}

As you can see, this program is trivially readable for anyone who’s worked with C, C++, or indeed Java. The interesting bits to note would be the module system, as well as selective imports of module’s functions inside local lexical scopes.
Thankfully, D comes with a built-in module, std.random in its standard library (Phobos), and we can use the uniform function to generate our random number.

Another interesting bit to note is the following snippet of code:

return readln().chomp.to!uint;

The to! is a template function that is extremely powerful. It can be used to convert almost any type to any other type. In case the conversion fails though, this template function will throw a std.conv.ConvException exception. Note that D does not have checked exceptions (in Java-speak), only unchecked exceptions.

Also note the exception handling mechanism, which is extremely similar to that of Java.

Finally, observe the declaration of playGame as public. The helper function getNumber in source/guessing_game.d, by contrast is private, and not accessible outside the module in which it is defined.

Okay, so this is the guessing game logic. Now let’s take a look at our game’s entry point, source/app.d

import guessing_game;

void main()
{
	guessing_game.playGame();	
}

All we do is import the guessing_game module and then invoke playGame. Note that we could also have written this module like so:

import guessing_game: playGame;

void main() 
{
      playGame();
}     

Let’s try out the game!

Macushla:guessing_game z0ltan$ dub run
Performing "debug" build using dmd for x86_64.
guessing_game ~master: building configuration "application"...
Linking...
Running ./guessing_game 

Let's play a guessing game. Your task is to guess the secret number (between 1 and 100, inclusive)...
Enter your guess: 
50
Too small!
Enter your guess: 
hello
Did not get valid input. Try again...
Enter your guess: 
-1
Did not get valid input. Try again...
Enter your guess: 
75
Too small!
Enter your guess: 
88
Too big!
Enter your guess: 
81
Too big!
Enter your guess: 
78
You win! You took 5 guesses.
A simple guessing game in D