Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

While sorrowful dogs brood: The TRS-80 Model 100 Poet

Jerry Stratton, December 26, 2018

At Tandy Assembly 2018, I bought a DWP-230 daisywheel printer, and I bought the December 1982 issue of 80 micro. That issue included the fifth installment of Jay Chidsey’s “Bit Smitten” series.

This month Jay Chidsey shows you how to gain access to string data for use in programs which require random selection of words.

It was written for the Model I or III and only 16K of RAM. There was nothing in it, however, that used the specific capabilities of the Model I/III. Because of the ever-present variations on different versions of BASIC, even though both the I and 100 use Microsoft BASIC, some conversions were necessary.

The biggest change was generating random numbers. The Model I/III used RND(N) to generate random integers from 1 to N. The Model 100 only generates random numbers between 0 and 1. It’s up to the programmer to convert that to the desired integer range. The standard way of doing this is to multiply the random fraction by N, take the integer from that, and add 1.

  • REM random number from one to N
  • R = INT(RND(1)*N)+1

Further, while Chidsey didn’t use it, the Model I/III had the ability to use a semi-random seed1 so that the same results don’t come up every time you run the program. The Model 100 lacks that ability, which means the programmer has to seed the random number generator randomly. The standard means of doing this was to poll the clock. If you poll the seconds, you have a semi-reasonable random number from 0 to 59, giving you sixty seeds. I chose, instead to poll the ones digit of the seconds twice, in order to get a potentially more random seed from 0 to 99. There are two places in the program where the time could conceivably provide random seconds: the time the program starts up, and the time that the user says, start generating poetry.

  • 50 R1 = VAL(RIGHT$(TIME$, 1)) 'Tens digit for random seed
  • 280 PRINT:PRINT "Press any key to wax poetic.";
  • 290 GOSUB 5200
  • 300 REM seed randomizer
  • 310 CLS:PRINT "Cogitating..."
  • 320 R2 = VAL(RIGHT$(TIME$, 1)) 'Ones digit for random seed
  • 330 SD = R1*10+R2
  • 340 IF SD < 1 THEN 500
  • 350 S = RND(1)
  • 360 PRINT@20, SD
  • 370 SD = SD - 1
  • 380 GOTO 340

Even this would only mean a hundred different permutations of poetry, all down the line. Since there’s a keypress wait at the end of each poem, I also have it randomize a bit more based on the ones digit at that point.

  • 5260 SD = VAL(RIGHT$(TIME$, 1)) 'randomize a bit for the next poem
  • 5270 IF SD < 1 THEN RETURN
  • 5280 S = RND(1)
  • 5290 SD = SD - 1
  • 5299 GOTO 5270

At this point, there are a hundred poems to start with, then a thousand poems for the second poem, and ten thousand potentials for the third—depending on how random the randomizer is in the Model 100.

The other major change involved adjusting the PRINT@ locations. The Model I/III had 48 lines where the Model 100 has only 8.

I had a lot of help from David A. Lien’s learner’s manual in doing these conversions, as well as adding new features.

The original version of the program generated only one form of poem:

    • How madly despairs the plum!
    • Plums seldom despair.
    • More beautiful are the dogs,
    • as they play.

The first two lines used one set of adjective, verb, and noun, and the second two lines used a different set.

I added several more forms, such as

    • Manors brood.
    • Moons dance.
    • Soldiers shout with soldiers.
    • Run sweetly!

Each line uses its own set of nouns, verbs, and/or adjectives; no line uses a noun, verb, and an adjective, and one line uses two nouns. So I separated the subroutine that generates a random noun, verb, and adjective into three subroutines that generate one each. I kept the subroutine that generates the three-at-a-time noun, verb, and adjective but had it call the three new subroutines to get each of those parts of speech.

linespurpose
10-99variable setup
100-199count up the nouns, verbs, and adjectives
200-299display the count and read in the parts of speech
300-399seed the random number generator
500-599choose the poem format, generate the poem, and display it
1000-1999poetry formats
2000-4999words in DATA statements
5000-5999utility subroutines
6000-6999subroutines to get random parts of speech

Because there were more formats, I also moved the code for turning an adjective into an adverb, pluralizing a noun, and matching the verb to the noun, into the subroutine for that part of speech. Each subroutine generates two variables. For nouns, the singular and plural form (NS$, NP$). For adjectives, the adjective and adverb (AJ$, AV$). And for verbs, the they form and the it form (VT$, VI$).

There was also a grammatical bug in the original. Chidsey appears to have chosen his words carefully to avoid nouns and verbs that can’t have ’S’ added to them, and to avoid adjectives that cannot be turned into an adverb by adding ‘LY’ to them. But he did include the noun ‘FLY’ and he pluralized it into ‘FLYS’. I added a quick check to see if the final letter was ‘Y’ and turned ‘FLY’ into ‘FLIES’.

And also ended up with ‘PLAIES’ as the it form of ‘PLAY’. Thus the long line 6130 to make sure that verbs ending in ‘Y’ get ‘IES’ as their it form unless the second-to-last letter is a vowel.

  • 6129 REM he/she/it
  • 6130 IF RIGHT$(VT$, 1) = "y" AND INSTR(VOWEL$, MID$(VT$, LEN(VT$)-1, 1)) = 0 THEN VI$ = LEFT$(VT$, LEN(VT$)-1) + "ies":RETURN
  • 6140 VI$ = VT$ + "s"

The adjectives contain three issues that I chose not to change. “Purplely” is perfectly valid as far as I can tell, it’s just not a word that gets used. “SCENICLY” is an archaic spelling of “SCENICALLY” which itself is not exactly the most common adverb for that purpose. And ‘GOODLY’ is a common adverb that isn’t usually the adverb form of ‘GOOD’. I chose to punt on fixing those issues until and unless enough examples come up to indicate a good solution for a goodly number of cases.

Here, then, is the full code. You can add nouns, verbs, and adjectives by adding more DATA statements to the appropriate section of code in section 2000 (nouns), 3000 (verbs) or 4000 (adjectives). Nouns need to be able to be pluralized by adding an ’s’ to them. Verbs need to be able to be transformed to the it form by adding an ’s’ to them or, if they end in ‘y’ but now vowel-‘y’, replace the ‘y’ with ‘ies’. Adjectives must be able to be turned into adverbs by adding ‘ly’ to them.

Unless, of course, you add the appropriate code in section 6000 to catch special cases.

I’ve also set it so that when a poem is displayed, you can hit the letter ‘s’ to append it to the file poems.do. And you can hit the letter ‘p’ to print it, the same as using the PRINT function key. The escape key exits the program.

Here’s the full code:

  • 10 REM Inspired by Jay Chidsey, Bit Smitten Part V
  • 20 REM 80 Micro, December 1982 (#35) p. 101
  • 30 REM SETUP
  • 40 CLS:CLEAR 1500
  • 50 R1 = VAL(RIGHT$(TIME$, 1)) 'Tens digit for random seed
  • 60 VOWEL$ = "aeiou"
  • 70 CR$ = CHR$(13) + CHR$(10)

If you add more words you may have to increase the memory cleared for strings. Line seventy sets up the end-of-line character, which, for these computers, needs to be a carriage return (13) and a line feed (10).

  • 100 READ N$:N=N+1
  • 110 IF N$="end" THEN 120 ELSE 100
  • 120 READ V$:V=V+1
  • 130 IF V$="end" THEN 140 ELSE 120
  • 140 READ A$:A=A+1
  • 150 IF A$="end" THEN 160 ELSE 140
  • 160 N=N-1:V=V-1:A=A-1 ' sets n,v,a to actual count
  • 170 N$="":V$="":A$="" ' sets strings to blank
  • 180 DIM N$(N),V$(V),A$(A)
  • 190 RESTORE ' data now ready to be reread for content

In order to DIM the noun, verb, and adjective arrays exactly, the program reads all the words in just to count them up, and then resets the DATA pointer back to the beginning.

  • 200 IF PEEK(1) = 51 THEN PC$ ="TRS-80 Model 100" ELSE IF PEEK(1) = 171 THEN PC$ = "Tandy 200" ELSE PC$ = "Tandy 102"
  • 210 TI$ = "The " + PC$ + " Poet":GOSUB 5500
  • 220 PRINT "Nouns............"N
  • 230 PRINT "Verbs............"V
  • 240 PRINT "Adjectives......."A
  • 250 FOR I=1 TO N:READ N$(I):NEXT:READ EN$ 'peels off 'end'
  • 260 FOR I=1 TO V:READ V$(I):NEXT:READ EN$
  • 270 FOR I=1 TO A:READ A$(I):NEXT
  • 280 PRINT:PRINT "Press any key to wax poetic.";
  • 290 GOSUB 5200

According to Tony B. Anderson in Programming Tips, Peeks, and Pokes for the Tandy Portable Computers, memory address 1 contains a code that indicates whether this is a Model 100, 102, or 200. I can’t verify the 102, but my 100 and 200 match his numbers.

  • 300 REM seed randomizer
  • 310 CLS:PRINT "Cogitating..."
  • 320 R2 = VAL(RIGHT$(TIME$, 1)) 'Ones digit for random seed
  • 330 SD = R1*10+R2
  • 340 IF SD < 1 THEN 500
  • 350 S = RND(1)
  • 360 PRINT@20, SD
  • 370 SD = SD - 1
  • 380 GOTO 340

This is the major randomizer. It takes the ones digit of the seconds from when the user ran the program, multiplies it by ten and adds the current ones digit of the seconds, after they’ve said they’re ready to make some poetry.

TRS-80 Model 100 gazelle poetry

It doesn’t use a FOR…NEXT loop because zero is an option, and this particular BASIC does not allow going through a FOR…NEXT loop zero times.

  • 500 REM make poetry
  • 510 F = INT(RND(1)*5)+1
  • 520 CLS
  • 530 ON F GOSUB 1100,1200,1300,1400,1500
  • 540 PRINT PO$
  • 550 GOSUB 5200
  • 560 GOTO 500
  • 999 END

F is a random number from 1 to 5; there are five subroutines for generating poetry, and the program branches to one of those subroutines based on F.

  • 1000 REM poetry format subroutines
  • 1100 REM nouns and nouns
  • 1110 GOSUB 6000:N1$ = NP$:GOSUB 6100:V1$ = VT$
  • 1120 GOSUB 6000:N2$ = NP$:GOSUB 6100:V2$ = VT$
  • 1130 TI$ = N1$ + " and " + N2$:GOSUB 5100
  • 1140 ST$ = N1$:GOSUB 5300:PO$ = PO$ + V1$ + "." + CR$
  • 1150 ST$ = N2$:GOSUB 5300:PO$ = PO$ + V2$ + "." + CR$
  • 1160 GOSUB 6000:N1$ = NP$:GOSUB 6000:N2$ = NP$:GOSUB 6100
  • 1170 ST$ = N1$:GOSUB 5300:PO$ = PO$ + VT$ + " with " + N2$ + "." + CR$
  • 1180 GOSUB 6100:GOSUB 6200
  • 1190 ST$ = VT$:GOSUB 5300:PO$ = PO$ + AV$ + "!" + CR$
  • 1199 RETURN
  • 1200 REM nouns seldom
  • 1210 GOSUB 5000
  • 1220 TI$ = "The " + NS$:GOSUB 5100
  • 1230 PO$ = PO$ + "How " + AV$ + " " + VI$ + " the " + NS$ + "!" + CR$
  • 1240 PO$ = PO$ + " " + NP$ + " seldom " + VT$ + "." + CR$
  • 1250 GOSUB 5000
  • 1260 PO$ = PO$ + "More " + AJ$ + " are the " + NP$ + "," + CR$
  • 1270 PO$ = PO$ + " as they " + VT$ + "." + CR$
  • 1299 RETURN
  • 1300 REM adjective was the noun
  • 1310 GOSUB 5000:TI$ = AJ$ + " was the " + NS$:GOSUB 5100
  • 1320 ST$ = AJ$:GOSUB 5300:PO$ = PO$ + "was the " + NS$ + "," + CR$
  • 1330 GOSUB 6000:GOSUB 6200
  • 1340 ST$ = AJ$:GOSUB 5300:PO$ = PO$ + "the " + NS$ + "." + CR$
  • 1350 GOSUB 5000
  • 1360 PO$ = PO$ + "The " + NS$ + " " + VI$ + " " + AV$ + "." + CR$
  • 1399 RETURN
  • 1400 REM the noun verbs like a noun
  • 1410 GOSUB 5000:N1$ = NS$:V1$ = VI$:GOSUB 6000:N2$ = NS$:GOSUB 6100:V2$ = VI$
  • 1420 TI$ = "Like a " + N2$:GOSUB 5100
  • 1430 PO$ = PO$ + "The " + N1$ + " " + V1$ + " like a " + N2$ + CR$
  • 1440 PO$ = PO$ + "And " + AV$ + " " + V2$ + CR$
  • 1450 GOSUB 5000
  • 1460 PO$ = PO$ + "While " + AJ$ + " " + NP$ + " " + VT$ + "." + CR$
  • 1499 RETURN
  • 1500 REM nouns verb like nouns
  • 1510 GOSUB 5000:N1$ = NP$:GOSUB 6000:N2$ = NP$
  • 1520 TI$ = N1$ + " " + VT$:GOSUB 5100
  • 1530 ST$ = N1$:GOSUB 5300:PO$ = PO$ + VT$ + " like " + N2$ + CR$
  • 1540 GOSUB 6200:PO$ = PO$ + " " + VT$ + " like " + AJ$ + " " + N2$ + "." + CR$
  • 1550 GOSUB 6200:ST$ = N1$:GOSUB 5300:PO$ = PO$ + VT$ + " " + AV$ + CR$
  • 1560 GOSUB 5000:PO$ = PO$ + " and " + NP$ + " " + VT$ + " " + AV$ + "." + CR$
  • 1599 RETURN

And, these are the formats for the poems. Each subroutines constructs PO$, including a centered title (using the subroutine in 5100) and often capitalizing the first word of the sentence (using the subroutine in 5300). Chidsey’s original program just printed each poem to the screen. I wanted to be able to save an interesting poem to a file, so switched to creating a string that is more easily saved. Here, Anderson’s tips book failed me. He lists 65024 as the start of the LCD buffer for the Model 100, which worked, but 64048 for the start of the Model 200’s buffer, which rarely worked, and usually just provided a bunch of spaces. Constructing out of strings and saving that creates a much cleaner file, anyway.

  • 2000 REM Nouns
  • 2010 DATA stone, ring, cloud, barn, dog, cat, mountain, river
  • 2020 DATA hare, horse, lion, gazelle
  • 2030 DATA cottage, manor, castle
  • 2040 DATA flower, tree, wildflower, plum
  • 2050 DATA star, planet, moon, comet
  • 2060 DATA needle, pen, village, soldier, garden, forest, boy
  • 2070 DATA villager, book, poet, spirit, path, mother, mask, crow
  • 2080 DATA snow, rain, thunder, tomb
  • 2999 DATA end
  • 3000 REM verbs
  • 3010 DATA run, jump, fly, see, play, throw, sing, shout, climb
  • 3020 DATA stand, leap, dance, sink, glow, sparkle, walk, chant, reflect, think
  • 3030 DATA live, ripple, write, brood, pierce, pray, despair, soar
  • 3040 DATA fall, speak, burn, roll
  • 3999 DATA end
  • 4000 REM adjectives
  • 4010 DATA beautiful, sorrowful, good, forgetful, purple, sweet
  • 4020 DATA bright, loud, earnest, deep, quick, grand
  • 4030 DATA dark, sinful, regretful, peaceful, painful, quiet
  • 4040 DATA cold, rough, mad, stern, light, slow, colorful, mortal, indecent, proud, regal, shy
  • 4050 DATA strange, lone, wise, hollow
  • 4999 DATA end

If you add nouns, verbs, or adjectives to the appropriate section, you may have to increase the amount of memory CLEARed for string space. Also, unless you want to program a smarter adverbizer or pluralizer, use nouns that can have “s” added to them; verbs that can have “s” added to them unless they end in a consonant plus a “y”, in which case the “y” is replaced by “ies”; and adjectives that can be turned into adverbs by adding “ly” to them. It will help if the adverb is the adverb form of the adjective it is constructed from. As opposed to, for example, “good” and “goodly” or “lone” and “lonely”.

Fly like a horse
  • 5000 REM random noun, verb, and adjective
  • 5010 GOSUB 6000
  • 5020 GOSUB 6100
  • 5030 GOSUB 6200
  • 5099 RETURN
  • 5100 REM create title from TI$
  • 5110 TL = LEN(TI$)
  • 5120 TS = 20-LEN(TI$)/2:PO$ = SPACE$(TS)
  • 5130 FOR I = 1 TO TL
  • 5140 TC$ = MID$(TI$, I, 1)
  • 5150 IF ASC(TC$) > 96 THEN TC$ = CHR$(ASC(TC$)-32)
  • 5160 PO$ = PO$ + TC$
  • 5170 NEXT I
  • 5180 PO$ = PO$ + CR$ + CR$
  • 5199 RETURN
  • 5200 REM wait for keypress
  • 5210 A$=INKEY$:IF A$="" THEN 5210
  • 5220 IF ASC(A$) = 27 THEN PRINT "Done poeticizing.":END 'ESCape
  • 5230 IF ASC(A$) = 0 THEN 5210 'PRINT key, et. al.
  • 5240 IF A$ = "s" THEN GOSUB 5400:GOTO 5210 'SAVE poem
  • 5250 IF A$ = "p" THEN LCOPY:GOTO 5210 'PRINT poem
  • 5299 RETURN
  • 5300 REM capitalize st$ and add to poem
  • 5310 SC$ = CHR$(ASC(LEFT$(ST$, 1))-32)
  • 5320 SC$ = SC$ + MID$(ST$, 2, LEN(ST$))
  • 5330 PO$ = PO$ + SC$ + " "
  • 5399 RETURN
  • 5400 REM save poem to poems.do
  • 5410 IF PO$ = "" THEN PRINT@240, "No Poem":RETURN
  • 5420 OPEN "RAM:poems.do" FOR APPEND AS 1
  • 5440 PRINT #1, PO$;
  • 5450 PRINT #1, ""
  • 5460 CLOSE 1
  • 5470 PRINT@240, "Saved"
  • 5499 RETURN
  • 5500 REM print a centered title
  • 5510 GOSUB 5100
  • 5520 PRINT PO$
  • 5530 RETURN
  • 5600 REM wait for keypress and then do a little randomizing
  • 5610 GOSUB 5200
  • 5620 SD = VAL(RIGHT$(TIME$, 1)) 'randomize a bit for the next poem
  • 5630 IF SD < 1 THEN RETURN
  • 5640 S = RND(1)
  • 5650 SD = SD - 1
  • 5660 GOTO 5630

These are the various subroutines used to do something other than get an individual word. Because many lines require one each of noun, verb, and adjective, the first subroutine provides them. The second subroutine takes whatever is in TI$ and (a) makes it all-caps while (b) centering it for a 40-character-width display.

The keypress wait subroutine checks for the escape key (and quits), semi-invisible keys such as the PRINT key, which return a numeric value of zero and shouldn’t move to the next poem anyway, and then the letters “s” and “p” for saving to “poems.do” and printing to an attached printer, respectively. Otherwise, it returns to where it came from.

The capitalization subroutine capitalizes the first letter of ST$, which is likely to be a word beginning a line of poetry.

Subroutine 5400 saves the current poem (PO$) to “poems.do” for future reference.

Subroutine 5500 prints a centered title. This is so that the variable length “The MODEL Poet” title at the beginning can be centered.

Subroutine 5600 calls the keypress wait subroutine, and then does a short randomization based on the seconds. Since it’s a number from 0 to 9, it doesn’t take as long as the major randomization that goes from 0 to 99.

  • 6000 REM random noun
  • 6010 I = INT(RND(1)*N)+1
  • 6020 NS$ = N$(I)
  • 6030 NP$ = NS$ + "s"
  • 6099 RETURN
  • 6100 REM random verb
  • 6110 I = INT(RND(1)*V)+1
  • 6119 REM I/you/we/they
  • 6120 VT$ = V$(I)
  • 6129 REM he/she/it
  • 6130 IF RIGHT$(VT$, 1) = "y" AND INSTR(VOWEL$, MID$(VT$, LEN(VT$)-1, 1)) = 0 THEN VI$ = LEFT$(VT$, LEN(VT$)-1) + "ies":RETURN
  • 6140 VI$ = VT$ + "s"
  • 6199 RETURN
  • 6200 REM random adjective/adverb
  • 6210 I = INT(RND(1)*A)+1
  • 6220 AJ$ = A$(I)
  • 6230 AV$ = AJ$ + "ly"
  • 6299 RETURN

And, finally, the subroutines that provide a random word—either a noun, a verb, or an adjective. The subroutines each provide two forms of their word. In the case of nouns, the singular and plural; in the case of verbs, the they form and the it form; and for adjectives the adjective and the adverb form of the adjective.

    • The barn writes like a crow
    • And loudly jumps
    • While sorrowful dogs brood.
  1. Semi-random, because as far as I know it just used the clock.

  1. <- iPad flashcards
  2. BASIC auto-numbering ->