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.