Mimsy Were the Borogoves

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

BASIC video decoder ring

Jerry Stratton, August 10, 2014

I’ve been fooling around with BASIC a bit after getting HotPaw BASIC for iOS. First things were getting Mastermind (80 Microcomputing, October 1981, p. 122) and the infamous Super Star Trek BASIC programs working. I can now use my 64-gigabyte iPad 2 as a 1978 TRS-80 Model I.

One of the things I always had in mind back in the day but never got around to1 was a cipher decoder assistant. Over time, a cipher decoder seemed more and more pointless—who uses ciphers in the age of public keys?

Here’s a simple encipher program in Python for Editorial:

  • #coding: utf-8
  • import workflow
  • from random import shuffle
  • from string import ascii_lowercase
  • originalText = workflow.get_input().lower()
  • letters = list(ascii_lowercase)
  • cipher = list(ascii_lowercase)
  • shuffle(cipher)
  • cipher = dict(zip(letters, cipher))
  • cipheredText = ''
  • for letter in list(originalText):
  • if letter in cipher:
  • cipheredText = cipheredText + cipher[letter]
  • else:
  • cipheredText = cipheredText + letter
  • workflow.set_output(cipheredText)

This will shuffle the letters of the alphabet and use the new shuffling as the cipher. This puts it one step above a video decoder ring, which only had 25 possible ciphers.

The BASIC code acts as it would on any old computer of the era. It waits for text commands and prints out the currently-guessed cipher letter by letter and then the currently-guessed deciphered text.

  • 10 rem VIDEO DECODER RING
  • 20 cr$ = chr$(10)
  • 30 print "Provide the text to decode, with an empty line to end. Commas may be forbidden."
  • 40 rem GET CODE
  • 50 input a$
  • 60 if a$ = "" then goto 90
  • 70 cd$ = cd$+cr$+cr$+a$
  • 80 goto 40
  • 90 rem ANALYZE TEXT
  • 100 dim ct(256)
  • 110 for i = 1 to len(cd$)
  • 120 c$ = mid$(cd$,i,1)
  • 130 c = asc(c$)
  • 140 if c <> 10 and c <> 13 then
  • 150 ct(c) = ct(c)+1
  • 160 end if
  • 170 next i
  • 180 rem ORGANIZE CODE
  • 190 al$ = " etaoinshrdlcumwfgypbvkjxqz"
  • 200 rem REMOVE UNUSED CHARACTERS
  • 210 dim cs(256),cd(256),ce$(256)
  • 220 n = 0
  • 230 for i = 1 to 256
  • 240 if ct(i) > 0 then
  • 250 n = n+1
  • 260 cs(n) = ct(i)
  • 270 cd(n) = i
  • 280 end if
  • 290 next i
  • 300 rem SORT CHARACTERS (cd) AND CHARACTER COUNTS (cs)
  • 310 for i = 2 to n
  • 320 for j = i to 2 step -1
  • 330 if cs(j-1) > cs(j) then
  • 340 x = cs(j)
  • 350 cs(j) = cs(j-1)
  • 360 cs(j-1) = x
  • 370 x = cd(j)
  • 380 cd(j) = cd(j-1)
  • 390 cd(j-1) = x
  • 400 else
  • 410 exit for
  • 420 end if
  • 430 next j
  • 440 next i
  • 450 rem GET INITIAL EQUIVALENTS
  • 460 for i = n to 1 step -1
  • 470 l = n-i+1
  • 480 if l > len(al$) then
  • 490 ce$(i) = "."
  • 500 else
  • 510 ce$(i) = mid$(al$,l,1)
  • 520 end if
  • 530 next i
  • 540 gosub 2000
  • 550 gosub 1000
  • 600 rem LOOP FOR COMMANDS
  • 610 print "What next? (frequency, decoding, shift, exchange, quit): "
  • 620 input a$
  • 630 if len(a$) > 3 then a$ = left$(a$,3)
  • 640 if a$ = "fre" then gosub 2000
  • 650 if a$ = "dec" then gosub 1000
  • 660 if a$ = "qui" then end
  • 670 if a$ = "shi" then gosub 3000
  • 680 if a$ = "exc" then gosub 4000
  • 900 goto 600
  • 999 end
  • 1000 rem PRINT CURRENT DECODING
  • 1010 dc$ = ""
  • 1020 for i = 1 to len(cd$)
  • 1030 c$ = mid$(cd$,i,1)
  • 1040 c = asc(c$)
  • 1050 for j = 1 to n
  • 1060 if cd(j) = c then
  • 1070 dc$ = dc$+ce$(j)
  • 1080 end if
  • 1090 next j
  • 1100 next i
  • 1110 print "DECODED:"
  • 1120 print dc$
  • 1130 print
  • 1140 return
  • 2000 rem PRINT CURRENT FREQUENCY
  • 2010 print "FREQUENCY"
  • 2020 for i = n to 1 step -1
  • 2030 print i,chr$(cd(i)),cs(i),ce$(i)
  • 2040 next i
  • 2050 print
  • 2060 return
  • 3000 rem SHIFT EQUIVALENT
  • 3010 m1$ = "Shift what character?"
  • 3020 m2$ = "Shift to what character?"
  • 3030 gosub 5000
  • 3040 if f$ = "" or t$ = "" then return
  • 3050 rem LOOP THROUGH CHARACTERS TO FIND CURRENT POSITION OF f$
  • 3060 for i = 1 to n
  • 3070 if cd(i) = f then
  • 3080 rem LOOP TO FIND CURRENT POSITION OF t$
  • 3090 for j = 1 to n
  • 3100 if t$ = ce$(j) then
  • 3110 if j = i then
  • 3120 print "Canceled."
  • 3130 return
  • 3140 end if
  • 3150 if j < i then
  • 3160 rem MOVE ALL CHARACTERS DOWN
  • 3170 for k = j to i
  • 3180 ce$(k) = ce$(k+1)
  • 3190 next k
  • 3200 else
  • 3210 rem MOVE ALL CHARACTERS UP
  • 3220 for k = j to i step -1
  • 3230 ce$(k) = ce$(k-1)
  • 3240 next k
  • 3250 end if
  • 3260 ce$(i) = t$
  • 3270 gosub 2000
  • 3280 gosub 1000
  • 3290 return
  • 3300 end if
  • 3310 next j
  • 3320 gosub 6000
  • 3330 end if
  • 3340 next i
  • 3350 return
  • 4000 rem EXCHANGE EQUIVALENTS
  • 4010 m1$ = "Exchange what character?"
  • 4020 m2$ = "Exchange with what character?"
  • 4030 gosub 5000
  • 4040 if f$ = "" or t$ = "" then return
  • 4050 rem LOOP TO FIND f$
  • 4060 for i = 1 to n
  • 4070 if cd(i) = f then
  • 4080 rem LOOP TO FIND t$
  • 4090 for j = 1 to n
  • 4100 if t$ = ce$(j) then
  • 4110 if j = i then
  • 4120 print "Canceled."
  • 4130 return
  • 4140 end if
  • 4150 x$ = ce$(j)
  • 4160 ce$(j) = ce$(i)
  • 4170 ce$(i) = x$
  • 4180 gosub 2000
  • 4190 gosub 1000
  • 4200 return
  • 4210 end if
  • 4220 next j
  • 4230 gosub 6000
  • 4240 end if
  • 4250 next i
  • 4260 return
  • 5000 rem GET FROM AND TO
  • 5010 print m1$
  • 5020 input f$
  • 5030 if f$ = "" then return
  • 5040 if len(f$) <> 1 then goto 5010
  • 5050 print m2$
  • 5060 input t$
  • 5070 if t$ = "" then return
  • 5080 if len(t$) <> 1 then goto 5050
  • 5090 f = asc(f$)
  • 5100 return
  • 6000 rem ADD NEW CHARACTER f, f$ as t$ at i
  • 6010 print "New character ";t$;" not found. Adding."
  • 6020 ce$(i) = t$
  • 6030 gosub 2000
  • 6040 gosub 1000
  • 6050 return

The first obvious take-away is how unreadable BASIC code was. Compare this to the Python used to encipher the code, for example. In this code, the only indications of separate sections of code are REM lines and jumps in line numbers. However, it’s a pretty simple program. At the beginning, it asks for the ciphertext. Then, it organizes the characters in the text by frequency and assigns them to the standard frequency of letters in the English language.

Then, it displays what that guess means to the ciphertext and asks for either a shift or an exchange. A shift rotates all guesses between the new correspondence and the previous correspondence for that guess. An exchange simply exchanges two guesses.

For example, if the current guess is:

[toggle code]

  • a   e
  • q   t
  • w   a
  • z   o
  • r   n
  • s   i
  • p   s
  • l   h

Exchanging ‘z’ to correspond with h instead of o will result in z corresponding to h and l corresponding to o.

[toggle code]

  • a   e
  • q   t
  • w   a
  • z   h
  • r   n
  • s   i
  • p   s
  • l   o

Shifting z to correspond to h will result in everything between z and l shifting up.

[toggle code]

  • a   e
  • q   t
  • w   a
  • z   h
  • r   o
  • s   n
  • p   i
  • l   s

Shifting might be useful if the guess is mostly correct but the guesses are off by one or so toward the top.

Here’s an example ciphertext to decode:

f wyr umfl sonu zynzly fv rky ympryov qlng pmifvw kne udgk rkyi mzzoygfmry zwz. ekyv fu rmlafvw rn muyofgmvp mqndr rkfp, m lnr ns rkyu jnvr dvjyoprmvj eki fj qy pn zmomvnfj mqndr rky wncyovuyvr. qdr zynzly fv znlfgy prmryp, ind jnvr kmcy rn ytzlmfv fr rn rkyu. rkyi mloymji wyr fr. mvj rkyi jnvr dvjyoprmvj eki ey jnvr.

This was an interesting exercise, and mostly useful for reminding me why it took twenty years to look back fondly on BASIC. For anything even moderately complex it becomes nearly impossible to follow. It sometimes seemed as though it was going to be impossible to get it to work on even the few BASICs I have available to me now. Some seem to have very low character limitations in strings, for example, which will mean you won’t be able to do the above ciphertext. Technically I should probably have turned that into an array rather than into a string, but I’d guess that the same systems that have trouble with long strings will also have trouble with big arrays.

  1. At least as I recall—I don’t have any of those files any more. There’s a whole lot of code I don’t remember on those long-lost cassette tapes and 5.25-inch disks.

  1. <- iOS BASIC
  2. Django bookmarklets ->