For my “1632 Atari podcaST” I had the opportunity, or need, to look at the LOGO language implementation by Atari for the Atari ST computer. Back in the 80’s I gave LOGO a passing glance, and thought it was only for turtle graphics. I have now learned that turtle graphics is but a small part of the overall language. I thought it would be fun to learn more about LOGO, and give it the chance I didn’t before. To that end, I set out to write a “Guess My Number” program. In this post, I describe how I did it, provide the source code, and describe how it works.
I set out writing this on the Atari ST using Atari ST-LOGO. I got it all complete except the looping mechanism to keep retrieving guesses until the number is found. I really got hung up on this, and just about gave up. LOGO as a language does not have the benefit of being blessed by a standards committee. Thus each implementation, while sharing core support of the language, offers dialect differences. And just try searching for LOGO resources without pulling back company logo’s, or logo creation, etc.
Since Atari ST-LOGO is buggy and I was having problems saving and loading, I reverted to the Atari 8 bit implementation “Atari LOGO”. I stumbled along with the program and got hung up with the same program flow or looping failure. Failure on my part, not the language.
After posting in the “Inverse ATASCII” Facebook page about a different error I was getting regarding and IF statement that needed to PRINT throwing an error, I received a suggestion from Colin about how to package the PRINT portion of the statement. This worked. Then I had to tackle the looping construct. Unlike other languages there are no built in conditional loop constructs such as WHILE. There is a REPEAT but it needs a finite number of executions to be specified. The answer was creating a WHILE procedure, and breaking the guess input into a procedure and the guess check into a procedure.
One of the quirks with LOGO is how it handles variable references. When declaring one, you use the MAKE primitive and precede the variable name with a single double-quote. Then when referencing it, you precede the name with a colon. This took some getting used to.
First I will present the entire source, then I will break it down.
Entire Source (Atari 8 bit LOGO)
TO WHILE :CONDITION :INSTRUCTIONLIST IF NOT RUN :CONDITION [STOP] RUN :INSTRUCTIONLIST WHILE :CONDITION :INSTRUCTIONLIST END TO CHKGUESS IF ( FIRST :GUESS ) < :NUM [PRINT [Too low]] IF ( FIRST :GUESS ) > :NUM [PRINT [Too high]] IF ( FIRST :GUESS ) = :NUM [PRINT [You guessed it!]] END TO GETGUESS PRINT SE [Guess #] :TRY MAKE "GUESS RL MAKE "TRY :TRY + 1 END TO GUESSNUM MAKE "NUM 1 + RANDOM 100 MAKE "TRY 1 MAKE "GUESS 0 PRINT [I have a number between 1 and 100] PRINT [What is it?] WHILE [NOT ( FIRST :GUESS ) = :NUM] [GETGUESS CHKGUESS] END
Breakdown
The first routine is the LOGO implementation of a WHILE loop. The command is not native and must be written! This implementation is straight out of the Atari LOGO Reference Guide. The first line declares the procedure named WHILE taking two argument variables CONDITION and INSTRUCTIONLIST:
TO WHILE :CONDITION :INSTRUCTIONLIST
This line causes execution to stop if the condition is no longer tested as true:
IF NOT RUN :CONDITION [STOP]
This line causes the passed instruction list to be executed:
RUN :INSTRUCTIONLIST
This line is a recursive call to itself. This keeps the loop moving:
WHILE :CONDITION :INSTRUCTIONLIST
This line terminates the procedure:
END
The next procedure checks the users guess and responds appropriately. This procedure is called repeatedly during the game immediately following the GETGUESS procedure. Both as part of the WHILE loop instruction list. The first line declares the routine named CHKGUESS taking no arguments:
TO CHKGUESS
This line checks if the guess is less than the number and prints “Too low” if so. Because the variable GUESS is actually a list, you must specify the first element of the list. This evaluation must happen before the comparison so it is encapsulated in parenthesis (). The value returned by (FIRST :GUESS) is then compared to :NUM (the random number). If the guess is less, the user is informed its too low. The words “Too low” are encapsulated in square brackets to treat it as one string. The entire PRINT command is also encapsulated in square brackets which suppresses it returning a value to the IF statement. Without, the line will always evaluate, inclusive of printing too low, and then generate an error.
IF ( FIRST :GUESS ) < :NUM [PRINT [Too low]]
The remaining lines handle the “too high” (greater than), and “you guessed it” (equivalence) conditions:
IF ( FIRST :GUESS ) > :NUM [PRINT [Too high]] IF ( FIRST :GUESS ) = :NUM [PRINT [You guessed it!]] END
The next procedure gets a guess from the user after displaying their current attempt number. The first line declares the routine named GETGUESS receiving no arguments:
TO GETGUESS
This line prints the guess (attempt) number. It uses the PRINT suffix SE which is short for SENTENCE. SENTENCE prints each list element following it. The first element is [Guess #] which is encapsulated in square brackets [] to treat it as one string. The second element is the variable TRY, which is the counter for the guess (attempt) number:
PRINT SE [Guess #] :TRY
This line gets input from the user using the RL (READLINE) command and assigns the output to the variable GUESS:
MAKE "GUESS RL
The next line increments the guess (attempt) number by one:
MAKE "TRY :TRY + 1 END
Finally, the last procedure is the main procedure for the game. In this procedure the game variables are initialized, game instruction is displayed, and the game loop started. The first line declares the procedure named GUESSNUM receiving no arguments:
TO GUESSNUM
The next three lines declare the game variables NUM, TRY, and GUESS. NUM is declared to be 1 plus a random number between 0 and 100 (0 to 99), so we end up with a number between 1 and 100. TRY is set to 1 (first attempt), and GUESS is set to 0:
MAKE "NUM 1 + RANDOM 100 MAKE "TRY 1 MAKE "GUESS 0
These two lines simply display the game instruction. Both encapsulate the text to be displayed in square brackets to treat them as one string:
PRINT [I have a number between 1 and 100] PRINT [What is it?]
This line starts the process of retrieving a guess from the user and checking it against the random number. WHILE is the first procedure defined above. It takes a condition list, and instruction list. The condition list is “[NOT (FIRST :GUESS) = :NUM]”, and the instruction list is [GETGUESS CHKGUESS]. This first evaluates the first element in the GUESS variable list, compares it to the NUM variable and applies NOT. If the guess is not the number, then the instruction list is executed:
WHILE [NOT ( FIRST :GUESS ) = :NUM] [GETGUESS CHKGUESS] END
Atari ST-LOGO Modification
To make this work on the Atari ST, it required a minor modification. The PRINT line in the GETGUESS routine needs to be changed. The # sign causes an error so it must be removed. Because that shortens the text by also removing the preceding space, the square brackets [] can be removed, but must be replaced with a single double-quote preceding the word Guess:
TO GETGUESS PRINT SE "Guess :TRY MAKE "GUESS RL MAKE "TRY :TRY + 1 END
Results
Running on the Atari 8 bit under Atari LOGO:
Running on the Atari ST under Atari ST-LOGO:
One thing this language offers is native support for building and managing linked lists, which is very nice, but I think I’m about done with LOGO for now.
Thanks! What I’ve posted here is just glimpse of what I’ve learned about Logo. My next episode of the 1632 Atari podcaST will have much more detail about the language.
Great write up! All I ever did in LOGO was make pretty Spirograph art.
Pingback: E005 – The One With The Monitors – 1632
Pingback: 1632-E005: The One With The Monitors – Inverse ATASCII