Smalltalk the fun way/Number Guessing Game

In this tutorial we'll create a "number-guessing game".

It goes as follows: somebody thinks a random number and you try to guess it in as few attempts as possible.

Each time you make a guess, you are given you one of the following answers:


 * 1) "My number is bigger"
 * 2) "My number is smaller"
 * 3) "You guessed right!"

When you finally guess the number, the game ends.

We'll start with a version without UI, and then make it more interactive.

A first Implementation
In our case it is the computer the one "thinking" the random number. But how do we generate random numbers in Smalltalk to begin with?

By reading the help of class, we come across this two handy usage forms:

You can try that out in a Workspace.

Let's begin by creating a class. It should have the instance variable, which will hold the random number the player will try to guess.

Here is the class template:

We'll implement the initialization method as follows:

For now we'll be interacting with the game directly through the Workspace. Go to a Workspace and create an instance of our game:

But we can't play yet! We would like to make guesses and receive an answer. It should work like this:

An simple implementation of this method could look like this:

And now the game can begin.

Try it out in the Workspace, by entering guesses and "print-it" to see the answer.

Better Interaction
Although it is possible to play by printing message-sends in the Workspace, it's not as inviting as it could be. Let's make this a more "interactive" experience.

For basic using interaction, like showing alerts or asking for information, there is the class. There is a "default" (singleton) instance of this class already present in the system and ready to use.

First, we'll have the game ask us a number. The way to ask for input is by using the method. It shows a dialog box where the user can enter some text. The call returns that text as a string. Try this in a Workspace, and make sure you "print" the resulting value:

The dialog looks like this:



Second, we'll have the game tell us in a dialog box what the response is. This time the method is. Expanding on the example above, select and evaluate these statements:

It should look like this:



Armed with this knowledge we can implement our corresponding methods. One to ask for a number guess, and the other to show a response to the player:

Remember how the return value of  is of kind  ? This is why we are converting it to a number with.

It seems that we are ready to use these two methods together. Let's do just that in a new method:

Go ahead and try it out in the Workspace:

This is how the guess-input should look like:



This is how a response should look like:



The game-loop
As you might have noticed, our game is short-lived. It only asks and responds once. Then it stops.

We need to do this repeatedly until the number is correctly guessed. The way to repeat a block of statements in Smalltalk is precisely by using the class. Take a look at this class, especially in the category "controlling".

We'll be using the method  in order to keep asking until the guess equals the random number. We use  and not   because we want our block to be evaluated at least once, so that "guess" has at least one initial value.

The new version of  looks like this:

Try it out:

Now the game-loop is in place. The only problem is that the game keeps "remembering" the initial random number. Even after you won.

One solution of course is to "re-initialize" the game on the Workspace, like this:

It would be much nicer if this as part of our game-loop... but where exacly? Let's look at our alternatives:


 * Before the game begins? It doesn't seem right, since we already chose a random number when the game was initialized.
 * After the game finishes? It doesn't seem right either, since you could want to ask for the random number after you won.

This seems like a dilemma. If we just could know if a game was won or not... then we could re-initialize the game at the beginning, only if it was previously won.

We'll just add that information to our game.

Adding game status
In order to store the game status we will introduce a new instance variable. Modify the class to look like this:

The variable  will be initially , and become   when the game was won.

Accordingly, change the initialization method like this:

Now we are ready to make the necessary changes to the "game-loop".

Our first change will be:

This makes sure that if we re-start the game, it will pick re-initialize itself if the game was finished. And remember that in the initialization we set  back to.

However, something is missing: we never set "finished" to  after the game is won! Let's do just that:

Refactoring the game-loop
The "play" method now does what we want. But the intention is somehow lost in too many low-level details. This goes against the "Smalltalk way" of doing things, and could use some refactoring.

Separation of concepts
We'll try to separate concepts to different, intention revealing, methods. But before we do, allow me modify the assignment of  so that it's confined to the repeating block. You'll see why later:

A first consequence of this change is that we can switch to the more intuitive  (at least if you have experience in other programming languages). Since we don't have "guess" in the condition anymore, we can re-structure our look like this:

Now, let's try to find out what our method is doing. We could come up with:


 * 1) It makes sure the game is properly re-initialized, if needed
 * 2) It performs the steps of a game interation
 * 3) It repeats the steps in a loop until the game is won

We'll refactor the re-initialization and the game-iteration to these methods:

The refactored version of  now looks like this:

Different levels of abstraction
Something just does not feel right about the presence of  in both   and. And that is that we are mixing abstraction levels: we have nice intention-revealing methods for the user interaction, the game-iteration and re-initialization, yet we still deal with the  variable directly. This is not elegant and something we could also improve.

For that, let's create these methods:

And modify the calling sites accordingly:

Before you go any further and try to play, make sure you re-initialize the game instance in the Workspace one last time:

Otherwise you'll get an error when you try to "play". This is because we introduced a new variable that remained un-initialized (with  ) in our existing instance. Alternatively, you can create a new instance altogether:

Now we are ready to play again, as many times as we want.

Number of tries
The goal of this game is not only to guess a number, but to do so in "as few attempts as possible".

In order to make the game more challenging we could tell the user how many times he/she tried at the end. Let's introduce a new instance variable for this purpose:

Don't forget to initialize it:

Each time the player makes a guess, this counter should go up by one. We stick to our hesitation to manipulate instance variables without revealing our intention, so we create this method:

And call it thus:

The message at the end also needs to be changed. It should mention the number of tries.

One way to accomplish this is by concatenating two strings:

Another by using the  method of  :

And yet another, by using streams:

To better illustrate, we'll combine the last two. This is how it looks like:

It it very correct and object oriented, but looks pretty much complicated for just concatenating two strings and a number.

Of course that part could also be written like this:

It is up to you.

However, if when choosing this variant you have to be careful that the sentence "Number of..." really is at the beginning of the next line. This is because within string-literals Smalltalk will honor all newlines, tabs, etc. In our case we have an explicit newline character.

Now play again, and see how many tries you need in average to beat the computer.



Have fun!