NCL 307: Making a Selection

In the last lesson, Keyboard input changed a setting.

Pressing Up increased the temperature. Pressing Down decreased it. Each key directly caused an action.

This time, we'll give the user several choices and let them decide which action to perform.

And we'll use that to build our first game.

Pick a number

Think of a whole number from 1 to 1023.

Don't tell the computer.

Our program will try to find it by asking whether each guess is too high or too low.

The screen will look something like this:

        PICK A NUMBER

 Think of a number from 1 to 1023.

 Is your number higher or lower
 than 512?


      > HIGHER
        LOWER
        I PICKED 512!

Up and Down will move the > between the three choices.

Enter will choose the one it points at.

This gives our input loop two different jobs:

  • Up and Down change the selection.
  • Enter performs the selected action.

Let's build it.

Keeping track of the game

The computer needs to remember which numbers could still be the one you picked.

We'll use three registers:

#low r0
#high r1
#guess r2

MOVE #low 1
MOVE #high 1023

#low contains the lowest number that could still be correct.

#high contains the highest.

#guess will contain the computer's current guess.

To make a guess, add the two ends of the range and divide the result by two:

ADD #guess #low #high
DIV #guess #guess 2

With a range from 1 to 1023, #guess becomes 512.

How the computer guesses

The computer keeps track of the lowest and highest numbers that could still be correct.

Each guess is halfway between them:

guess = (low + high) รท 2

If the player says the answer is higher, everything up to the current guess can be eliminated.

If the player says it is lower, everything from the current guess upward can be eliminated.

This means each answer removes about half of the remaining possibilities.

This method is called a binary search.

Drawing the game

Most of the Display won't change while the game is running.

We can draw those parts once:

D.FIL " "

D.CUR 8 1
D.TXT "PICK A NUMBER"

D.CUR 1 3
D.TXT "Think of a number from 1 to 1023."

D.CUR 1 5
D.TXT "Is your number higher or lower"

D.CUR 1 6
D.TXT "than "

D.CUR 6 8
D.TXT "HIGHER"

D.CUR 6 9
D.TXT "LOWER"

D.CUR 6 10
D.TXT "I PICKED "

D.BLT

Only two fields contain the current guess:

512?
512!

Those are the parts we'll update when the computer makes another guess.

Restoring the guess fields

A guess can contain up to four digits.

Each changing field also contains one punctuation character, so each field can be up to five characters wide:

512?
512!

We'll build a restoration string for those five positions:

#restore s0

SMOVE #restore ""
SPADR #restore #restore 5 " "
SPADR #restore #restore 10 "\b"

Writing #restore clears five characters and returns the cursor to the beginning of the field.

Now we can draw the current guess:

D.CUR 6 6
D.CHR #restore
D.CHR #guess
D.CHR "?"

D.CUR 15 10
D.CHR #restore
D.CHR #guess
D.CHR "!"

If a later guess is shorter, the old field is cleared first.

Everything around these fields remains untouched.

Remembering the selection

Our game has three choices:

Selection Choice
0 HIGHER
1 LOWER
2 I PICKED

We'll keep the current selection in another register:

#selection r3

MOVE #selection 0

Selection 0 means HIGHER.

Selection 1 means LOWER.

Selection 2 means I PICKED.

The > on the Display will show the player which one is currently selected.

Turning the selection into a position

Our three choices occupy rows 8, 9, and 10.

That means we can calculate the row from #selection:

#row r4

MOVE #row #selection
ADD #row #row 8

When #selection is 0, #row becomes 8.

When it is 1, #row becomes 9.

When it is 2, #row becomes 10.

Then we can draw the marker:

D.CUR 4 #row
D.CHR ">"

The program doesn't need to separately remember the Display position of every choice.

It can calculate the position from the selection it already knows.

Moving the selection

Now we can read the Keyboard:

#key s1

$input
SYS.AKEY #key

BSEQ $up #key "UP"
BSEQ $down #key "DOWN"
BSEQ $choose #key "ENTER"

JUMP $input

Any other key is ignored.

Unlike the temperature controls in the last lesson, Up and Down don't perform the selected action.

They only change which action is selected.

Moving down

First, make sure we aren't already at the last choice:

$down
BGE $input #selection 2

Then erase the old marker:

D.CUR 4 #row
D.CHR " "

Change the selection:

INC #selection

Calculate its new row and draw the marker:

MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

JUMP $input

One Down press moves the selection once.

Moving up

Moving up is the same process in the other direction:

$up
BLE $input #selection 0

D.CUR 4 #row
D.CHR " "

DEC #selection

MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

JUMP $input

Again, the bounds check happens before the state changes.

The selection can never become less than 0 or greater than 2.

Choosing an action

Up and Down decide what the player wants to do.

Enter actually does it.

When Enter is pressed, #selection tells us which action was chosen:

$choose
BEQ $higher #selection 0
BEQ $lower #selection 1
JUMP $correct

We've used this kind of chained branch before.

What's different is how #selection got its value: the player chose it by moving the marker on the Display.

Keeping the range valid

There is another boundary we need to protect.

Suppose the only remaining possibility is 1023.

Our state would look like this:

low     1023
high    1023
guess   1023

If the player chooses HIGHER, there is nowhere higher for the answer to be.

Changing #low to 1024 would give us:

low     1024
high    1023

That isn't a valid range.

The same problem can happen at the lower end. If the only remaining possibility is 1, choosing LOWER cannot be true.

Before changing the range, we'll make sure the player's answer is still possible.

Higher

Suppose the computer guessed 512, and your number is higher.

Before changing anything, check that #guess isn't already the highest possible number:

$higher
BGE $input #guess #high

If it is, the choice cannot be true, so the program simply returns to the input loop.

Otherwise, #guess and everything below it can be eliminated.

The new lowest possible number is one greater than the current guess:

MOVE #low #guess
INC #low
JUMP $next_guess

If the guess was 512, #low is now 513.

Lower

The other direction works the same way.

First, make sure #guess isn't already the lowest possible number:

$lower
BLE $input #guess #low

If it is, there is nowhere lower for the answer to be.

Otherwise, the new highest possible number is one less than the current guess:

MOVE #high #guess
DEC #high

Both paths keep the remaining range valid.

Making another guess

After changing the range, the computer calculates another midpoint:

$next_guess
ADD #guess #low #high
DIV #guess #guess 2

We'll also return the selection to HIGHER.

First, erase the marker from its current row:

D.CUR 4 #row
D.CHR " "

Then reset the selection:

MOVE #selection 0

MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

Finally, update the two guess fields:

D.CUR 6 6
D.CHR #restore
D.CHR #guess
D.CHR "?"

D.CUR 15 10
D.CHR #restore
D.CHR #guess
D.CHR "!"

JUMP $input

Nothing else on the screen changed, so nothing else needs to be redrawn.

The game is ready for another answer.

Finding your number

Eventually, the computer will display the number you picked.

Move the marker down to:

I PICKED 731!

and press Enter.

That takes us to $correct.

We can replace the game screen with a victory message:

$correct
D.FIL " "

D.CUR 8 4
D.TXT "I FOUND IT!"

D.CUR 10 6
D.TXT #guess

D.BLT

Execution then reaches the end of the program.

You've made a game.

Putting it together

Here's the complete program:

#low r0
#high r1
#guess r2
#selection r3
#row r4

#restore s0
#key s1

MOVE #low 1
MOVE #high 1023
MOVE #selection 0

-- Build the field restoration string.
SMOVE #restore ""
SPADR #restore #restore 5 " "
SPADR #restore #restore 10 "\b"

-- Make the first guess.
ADD #guess #low #high
DIV #guess #guess 2

-- Draw the parts of the screen that don't change.
D.FIL " "

D.CUR 8 1
D.TXT "PICK A NUMBER"

D.CUR 1 3
D.TXT "Think of a number from 1 to 1023."

D.CUR 1 5
D.TXT "Is your number higher or lower"

D.CUR 1 6
D.TXT "than "

D.CUR 6 8
D.TXT "HIGHER"

D.CUR 6 9
D.TXT "LOWER"

D.CUR 6 10
D.TXT "I PICKED "

D.BLT

-- Draw the current guess.
D.CUR 6 6
D.CHR #restore
D.CHR #guess
D.CHR "?"

D.CUR 15 10
D.CHR #restore
D.CHR #guess
D.CHR "!"

-- Draw the initial selection marker.
MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

$input
SYS.AKEY #key

BSEQ $up #key "UP"
BSEQ $down #key "DOWN"
BSEQ $choose #key "ENTER"

-- Ignore any other key.
JUMP $input

$up
BLE $input #selection 0

D.CUR 4 #row
D.CHR " "

DEC #selection

MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

JUMP $input

$down
BGE $input #selection 2

D.CUR 4 #row
D.CHR " "

INC #selection

MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

JUMP $input

$choose
BEQ $higher #selection 0
BEQ $lower #selection 1
JUMP $correct

$higher
BGE $input #guess #high

MOVE #low #guess
INC #low
JUMP $next_guess

$lower
BLE $input #guess #low

MOVE #high #guess
DEC #high

$next_guess
ADD #guess #low #high
DIV #guess #guess 2

-- Return the selection to HIGHER.
D.CUR 4 #row
D.CHR " "

MOVE #selection 0

MOVE #row #selection
ADD #row #row 8

D.CUR 4 #row
D.CHR ">"

-- Update the guess fields.
D.CUR 6 6
D.CHR #restore
D.CHR #guess
D.CHR "?"

D.CUR 15 10
D.CHR #restore
D.CHR #guess
D.CHR "!"

JUMP $input

$correct
D.FIL " "

D.CUR 8 4
D.TXT "I FOUND IT!"

D.CUR 10 6
D.TXT #guess

D.BLT

Try it

Pick several different numbers and see how quickly the computer finds them.

Try 1, 512, and 1023.

When the computer has narrowed the range to a single number, try choosing HIGHER or LOWER anyway. The program should refuse to move outside the remaining range.

Then play again and give the computer a wrong answer earlier in the game.

Watch what happens to its later guesses.

The computer doesn't know what number you're thinking of. It only knows the state it has built from the answers you gave it.

One choice, several effects

The > is what the player sees, but the marker itself isn't the selection.

The program remembers the player's choice as a number in:

#selection

That same value is used in several places.

Up and Down change it.

#row turns it into a position on the Display, where the program draws the >.

When Enter is pressed, the program uses it again to decide whether the player chose HIGHER, LOWER, or I PICKED.

The marker and the action agree because they both come from the same stored choice.

Change #selection, and the program can use the new value everywhere that choice matters.


Our first game used one number to move between three choices.

In NCL 308: Moving Around a Board, we'll expand that idea to nine positions on a game board, and give each position something of its own to remember.