Monthly Archives: November 2020

Compressing BASIC DATA with Base-64 – part 3

See also: part 1, part 2, part 3 and part 4.

A Faster Base-64

I had planned to end this series with this third part, giving a simple way to turn 8-bit value DATA statements into Base-64 DATA statements. But smarter folks than I have looked at my previous work, so now my plans have changed. We will need an extra part or two… or three.

Today, let’s highlight some comments made to previous installments.

The always thought-provoking MiaM wrote:

I would had written out the 2 exponent values directly as 4, 16 and 64 rather than using INT(2^2).

The decdode-4-“chars”-to-3-bytes parts could use a modified base 64 thingy where you have the first 6 bits of three bytes in a row, and the fourth “char” contains the upper two bits for the previous three “chars”/bytes. Or the other way around, start with the “char” that contains the upper two bits for the following three chars and put that in a numerical variable. Then have a loop that runs three times. Each time first shift the “upper two bits” variable two steps left, i.e. multiply by 4, and then read a “char” and to that char OR the result of the “upper two bit” variable ANDed with 192 (=%110000).

That format would be incompatible with the standardized representation used by BASE 64, but it would indeed be a format with the same density and using the same characters.

Btw you could use the “BASE 90” as a way to slightly compress some data, by just having the values >63 represent two instances of the actual value minus 64. That might not save much, but perhaps worth investigating.

MiaM

Very good point! Eliminating “power of two” calculations and changing them to hard coded values should offer a noticeable speed increase. Pre-calculating values (i.e. writing 4 instead of 2^2) is a good way to save some time, and possibly space too (since “2^2” takes up more memory than “4”).

Normally I would go on a Benchmarking BASIC tangent, but I will save that for later.

I was more intrigued by the concept of making an easier to parse Base-64 format. Since the original goal of this article was to cram as much type-able DATA numbers as possible in to a BASIC program, there is nothing that says it needs to follow the standard Base-64 encoding format. Any format that gets more bits of data in to type-able DATA values would suffice.

This opens up an opportunity to tweak the encode/decode method to be easier to do in BASIC. Mia suggests something like this:

Instead of the standard Base-64 encoding of three 8-bit values into four 6-bit values:

+- Byte 1 --+- Byte 2 --+- Byte 3 --+
| 000000|00 | 0000|0000 | 00|000000 |
| \__A__/\___B___/ \___C___/ \_D__/

We could alter the encoding into a different version:

+- Byte 1 --+- Byte 2 --+- Byte 3 --+
| 00|000000 | 00|000000 | 00|000000 |
| \| \_A__/   \| \_B__/ | |/ \_C__/ |
|  \___________D__________/

The benefit here is that decoding this in BASIC could be done much easier and faster. Rather than all the multiplication/division needed to shift bits and then combine them into bytes, it could be as simple as this a few ANDs and divides. Here’s a rough example of converting three 8-bit (0-255) input values (A, B and C) into four 6-bit (0-63) output values (O1, O2, O3 and O4).

0 REM 6BIT.BAS
1 REM As proposed by MiaM

10 READ A,B,C
15 REM --XXXXXX of A
20 O1=(A AND &H3F)

25 REM --XXXXXX of B
30 O2=(B AND &H3F)

35 REM --XXXXXX of C
40 O3=(C AND &H3F)

45 REM XX------ of A
50 O4=(A AND &HC0)/4

55 REM XX------ of B
60 O4=O4+(B AND &HC0)/16

65 REM XX------ of C
70 O4=O4+(C AND &HC0)/64

75 PRINT "ENCODED:"
80 PRINT A;B;C,O1;O2;O3;O4

85 A=0:B=0:C=0
90 A=O1+INT(O4 AND &H30)*4
100 B=O2+INT(O4 AND &HC)*16
110 C=O3+INT(O4 AND &H3)*64

120 PRINT "DECODED:"
125 PRINT O1;O2;O3;O4,A;B;C

1000 DATA 111,222,123

Running this program displays:

Three 8-bit values converted to four 6-bit values, and back.

The three 8-bit input values (111, 222 and 123) are converted into four 6-bit output values, then those four are turned back into three 8-bit values to verify it worked.

The conversion is very simple, since the output values O1, O2 and O3 are just the right 6-bits of the input values A, B, and C, which can be obtained by using AND to mask off the top two bits:

20 O1=(A AND &H3F)
30 O2=(B AND &H3F)
40 O3=(C AND &H3F)

Optimization Note: We could save a few bytes by omitting the parenthesis and the space before the &H3F. Due to how the Color BASIC’s parser works, we need the space between the variables (A, B and C) and the keyword “AND”. That space is what tells the tokenizer we want a variable followed by the keyword AND, versus a variable that starts with “AA”:

A=255

PRINT A AND 4
4

PRINT A AND4
4

PRINT AAND4
0

Above, Color BASIC thinks the third example is a variable called “AAND4” which is truncated to just be “AA” since Color BASIC only cares about the first two characters of a variable name:

A=255
AAND4=42

PRINT AAND4
42

PRINT A AND4
4

Oh the fun bugs that must have caused me back in the day!

But I digress…

The fourth byte is built by doing the opposite AND to get only the top two bits of A, then a divided that by 4 to shift them to the right 2 bits (AA—— to —AA—-), then do the same mask to B and divide by 16 to shift them 4 bits to the right (BB—— to ——BB–) and again for C divided by 64 to shift 6 bits to the right (CC—— to ——CC) and then add them together to make the result (–AABBCC).

50 O4=(A AND &HC0)/4
60 O4=O4+(B AND &HC0)/16
70 O4=O4+(C AND &HC0)/64

I think I did a poor job explaining that. But here it is visually:

INPUT (three 8-bit values):

A: aaAAAAAA
B: bbBBBBBB
C: ccCCCCCC

OUTPUT (four 6-bit values):

O1: --AAAAAA
O2: --BBBBBB
O3: --CCCCCC
O4: --aabbcc

Maybe that helps.

Doing it this was can be done faster and with less code, I think. Some benchmarking needs to be done to see if AND is faster than addition for combining the values, and the O4 line can just be made as one thing without the intermediate line numbers and steps:

50 O4=(A AND &HC0)/4+(B AND &HC0)/16+(C AND &HC0)/64

We will want to make the decoder as small as possible, since if we save 100 bytes doing BASE-64 over HEX and the decoder takes more than 100 bytes it defeats the purpose.

Maybe we can figure out this “base 90” concept in a future article, as well.

To be continued…

Compressing BASIC DATA with Base-64 – part 2

See also: part 1, part 2, part 3 and part 4.

Today we will explore writing a standard base-64 converter in BASIC, and then see if we can make a smaller and faster (and nonstandard) Color-BASIC-specific one.

When we last left off, we were looking at ways to get as much encoded data on to a DATA statement as possible. Instead of using integer numbers (base-10) or hex values (base-16), we began exploring if we could increase the base and use more typeable character to encode the data.

Although it seems we could create a weird base-90 format using every typeable character except for quote (which we’d need to start a DATA line else we couldn’t use comma), the decoder would be much larger and have to do much more work, and we actually wouldn’t benefit since we really need numbers that round to specific numbers of bits:

  • Base-8 (octal) values can be represented by 3-bits (111). (Extended BASIC supports octal when you use &Oxx or just &xx.)
  • Base-16 (hexadecimal) values can be represented as 4-bits (1111). (Extended BASIC supports hexadecimal when you use &Hxx.)
  • Base-32 values would be represented as 5-bits (11111).
  • Base-64 values would be represented as 6-bits (111111).
  • Base-128 values would be represented as 7-bits (111111).

As you can see, a base-90 value isn’t a large enough range to give us an extra bit over base-64. We need to use bases that are nice multiples of the power of 2. Because of this, we’ll ignore a made-up base-90 and look at something a bit more standard, such as base-64 encoding.

Pump up the base

As previously discussed, natively, you can represent a number in a DATA statement as a base-10 value, or a hexadecimal value. Both of these are the value 32:

100 DATA 32,&H20

BASIC will READ them the same way, though hex values are much faster for BASIC to read and parse. Using native hex values like “&H20” is the fastest way to load DATA, but it is also the largest since every value has two extra characters (“&H”) in front.

A recent tip was given by Shaun Bebbington about how you can represent zero just by leaving it out between commas. It saves space, and the parse gets zero from this faster than if you put a zero there:

100 DATA 8,6,7,5,3,,9

But since we are trying to get as much DATA in there as possible, we don’t want to separate numbers by commas. We can pack all the 2-digit hex values together in a string then read that entire string and parse out the individual 2-digit hex values. That is more work, and slower, but gets more data per DATA line. Here are the values 0 to 15 in hex (00 to 0f):

100 DATA 000102030405060708090A0B0C0D0E0F

As previously demonstrated, this is the most efficient way to store HEX values. Even when we pad a low 0-15 value to make it two digits (1 represented by 01), it stills saves space over comma delimited values since no commas are used.

But each hex value is wasting 50% of the bits it takes to represent it. HEX values of 0-15 could be represented by four bits (0000 to 1111). We are storing them as one 8-bit character and thus achieving 50% storage efficiency.

We can do better by using a higher base-x value that can use those wasted bits. We want the highest value we can represent with typeable characters, which is 64 (since the next higher would be 128 and we don’t have a way to type 128 different characters on the CoCo).

Base-64

The standard Base-64 encoding uses the following 64 characters to represent values of 0 to 63:

ABCDEFGHIJKLMNOPQRSTUVWZYZabcdefghijklmnopqrstuvwxyz01234567890+/

Each base-64 character needs 6-bits to be represented (000000-111111).

Representing values that way only wastes 2 bits per character, rather than 4-bits like hex base-16 does:

ASCII HEX Chars.:    ASCII Base-64 Chars.:
      0    15              0    63
     "0"   "F"            "A"   "/"
     /       \            /       \
xxxx0000  xxxx1111   xx000000   xx111111

But, converting to and from base-64 is much trickier. Hex base-16 is as simple as this:

  • Hex F0” -> F is 15 which is 1111 in binary. 0 is 0000 in binary. Thus the first character becomes the left four bits, and the second character becomes the right four bits. Super easy. Barely an inconvenience. Two ASCII bytes represent one byte of data.

But for base-64, we are dealing with 6-bits, and two of those won’t fit into an 8-bit byte. Instead, four base-64 6-bit values are merged together to make a 3-byte 24-bit value.

  • Base-64 “ABCD” (xx000000 xx000001 xx000010 xx000011) -> A is 0 which is 000000 in binary. B is 1 which is 000001 in binary. C is 2 which is 000010 in binary. D is 3 which is 000011 in binary. These values are merged together (removing the unused 2-bits in each one) and stored in 3 bytes as:
+- Byte 1 --+- Byte 2 --+- Byte 3 --+
| 000000|00 | 0001|0000 | 10|000011 |
| \__A__/\___B___/ \___C___/ \_D__/
  • Byte 1 contains 6 bits of base-64 value A and 2 bits of base-64 value B.
  • Byte 2 contains 4 bits of base-64 value B and 4-bits of base-64 value C.
  • Byte 3 contains 2 bits of base-64 value C and 6 bits of base-64 value D.

Well that’s a mess. Moving bits around like that is super easy under languages like C, but a bit more work in BASIC.

Encode this!

We will start with encoding a simple ASCII string into base-64 using a web tool:

https://www.base64encode.org

If you go to that link, you can type something in and then encode it into base-64. I typed:

Greetings from Sub-Etha Software! Do you know where your towel is?

And that gets encoded into this:

R3JlZXRpbmdzIGZyb20gU3ViLUV0aGEgU29mdHdhcmUhIERvIHlvdSBrbm93IHdoZXJlIHlvdXIgdG93ZWwgaXM/

Each character represents a 6-bit (0-63) value which we will have to combine into 8-bit values and decode.

An easy way to decode the characters used by base-64 encoding is with a string:

10 Z$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

We can use Extended BASIC’s INSTR() function to match a character from the encoded string with a character in that string, and the position it is found in will the the value it represents (well, minus 1, since INSTR returns a base-1 value).

Here is an example that will display the bytes of the encoded string:

0 REM base64-1.bas
10 Z$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
20 READ A$:PRINT A$
30 FOR A=1 TO LEN(A$)
40 PRINT INSTR(Z$,MID$(A$,A,1))-1;
50 NEXT
1000 REM BASE-64 DATA
1010 DATA R3JlZXRpbmdzIGZyb20gU3ViLUV0aGEgU29mdHdhcmUhIERvIHlvdSBrbm93IHdoZXJlIHlvdXIgdG93ZWwgaXM/

Running that shows me this:

Displaying base-64 values.

If A is 0, then R should be 17, and that is what it prints first. Now we know we can get the values for each character in a base-64 encoded string.

Next we have to turn four 6-bit base-64 values into three bytes (24-bits). I am not sure what a good way to do this is, so I’ll just brute-force it and see how that works out.

First, I know that I need four base-64 values to make my 3 8-bit values, so I’ll modify my loop to skip every four values, and then add an inner loop to process the individual four base-64 values.

Inside that inner loop it will process the next four base-64 6-bit values and convert them into 3 8-bit values.

Here is what I came up with:

0 REM base64.bas
5 POKE65395,0
10 Z$="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
20 READ A$
30 FOR A=1 TO LEN(A$) STEP 4
35 REM GET 4 6-BIT VALUES
40 FOR B=0 TO 3:B(B)=INSTR(Z$,MID$(A$,A+B,1))-1
50 IFB(B)<0 THEN B(B)=0
60 NEXT
65 REM CONVERT TO 3 8-BIT
70 C1=INT(B(0)*INT(2^2)) OR INT(B(1)/INT(2^4))
80 C2=(B(1) AND &HF)*INT(2^4) OR B(2)/INT(2^2)
90 C3=(B(2) AND &H3)*INT(2^6) OR B(3)
100 PRINT CHR$(C1);CHR$(C2);CHR$(C3);
110 NEXT
120 END
1000 REM BASE-64 DATA
1010 DATA R3JlZXRpbmdzIGZyb20gU3ViLUV0aGEgU29mdHdhcmUhIERvIHlvdSBrbm93IHdoZXJlIHlvdXIgdG93ZWwgaXM/

I figured out all the 6-bit to 8-bit stuff (lines 70-90) with alot of trial and error, so I expect there is a faster and easier way to do this. But, then end results is a program that will print out the expected message, albeit really slowly.

A successful, but slow, decode of a base-64 encoded message.

One unexpected problem was with the powers of two — (2^2) and such. They produce rounding errors which caused some bits to be lost. I had to use INT() round them. That took me hours to figure out, but it’s just part of the inaccuracies of floating point values, especially limited ones like a 1970s BASIC used.

PROBLEM: Since the goal here is to put more data in DATA statements, the base-64 decode routine needs to be small. If it is 100 bytes larger than just using HEX, you have to save 100 bytes in DATA before you break even. The routine I give is not small and not fast. It would probably not be useful in the 10 LINE contest I mentioned. Maybe one of you can help improve it.

Now that we have a simple base-64 decoder, the next step will be making an encoder to turn DATA statement values into a base-64 string.

Until next time…

Compressing BASIC DATA with Base-64 – part 1

See also: part 1, part 2, part 3 and part 4.

NOTE: This article was started a year or two ago, so some references may no longer be pertinent.

NOTE 2: It was then updated in April 2020 before finally being published in November, so some references in the updates may no longer be relevant.

There is some kind of “10 line BASIC” contest, and one of the categories allows for assembly language as long as it can be embedded in a typeable BASIC program. I previously discussed embedded assembly in BASIC in my Interfacing BASIC with Assembly series.

One of the examples I gave was a Pac-Man maze demo that used some assembly code to scroll the screen up and down. The loader looked like this:

2000 REM
2001 REM LOAD ASSEMBLY ROUTINE
2002
2010 READ A,B
2020 IF A=-1 THEN 2070
2030 FOR C = A TO B
2040 READ D:POKE C,D
2050 NEXT C
2060 GOTO 2010
2070 RETURN 'END
2080 DATA 16128,16217,189,179,237,90,39,14,90,39,28,90,39,42,90,39,55,204,255,255,32,67,142,4,32,166,132,167,136,224,48,1,140,5,255,47,244,32,47,142,5,223,166,132,167,136,32,48,31,140,4,0,44,244,32,30,142,4,1,166,132,167,31,48,1,140,5,255,47,245,32,14
2090 DATA 142,5,254,166,132,167,1,48,31,140,4,0,44,245,204,0,0,126,180,244,-1,-1

That particular bit of BASIC code was created by the LWASM assembler. It reads assembly language values from DATA statements and POKEs them into memory where they can be executed.

In another series, I discussed various ways to use DATA statements either for the fastest reading/loading or the smallest size.

Today, I’d like to revisit this subject and offer some more ways to compress data into DATA to store even more than before.

Or something like that.

Knowing our limitations

We know the BASIC input buffer is 249 characters long, so we should be able to type in a single digit line number, the keyword “DATA” and 244 more characters.

BASIC allows typing in up to 249 characters.

Above, I have a one digit line number (“0”), then four characters for the keyword (“DATA”) then seven full lines of 32 characters (32*7=224) plus 20 characters on the final line – so 1+4+224+20 is 249. Hey, it works!

Since loading a BASIC program saved in ASCII is the same as typing it in, that limit should also apply for loading a non-tokenized ASCII program. I will be using that method here to load test programs into the XRoar emulator, so I won’t be able to cheat and load a tokenized line that would have exceeded our typing limit.

If we encode our assembly code as 2-digit hex values, we should have room to type a single digit line number, the keyword “DATA”, and 122 2-digit hex values. We could enter all the hex digits from &H00 to &H79 on a line. This appears to work:

The BASIC input buffer is 249 characters, so that’s the most you can type before pressing ENTER.

As soon as we press enter, BASIC tokenizes the “DATA” keyword. It no longer takes up four bytes. This means even though we typed the full 249 characters, we could EDIT the line and perhaps type a few more characters. Typing “EDIT 0″…

Editing the longest typed line…

…then pressing “X” to extend (go to the end) of the line allows us to add two more digits:

In EDIT mode, there are now two more characters available since DATA has been tokenized.

I am guessing DATA is tokenized into a 2-byte token. However, if you add these two characters, then re-list the line:

BASIC can’t list all the characters.

…we see BASIC does not show the final character. However, if you use the READ command to read that line in to a string, and the PRINT it, you see it’s actually there:

BASIC is storing all the characters, but LIST cannot show them.

The BASIC LIST command has a limit to how much it will display, it seems, and it is one character less than it should be. Bug?

BASIC line packing

It is possible to create BASIC lines that contain much more data than you can type in. They will run fine, but cannot be fully listed. There were several BASIC “crunch” programs available, including one by Carl England that I used often, that did this trick.

However, if we want to stick to how much someone could type in, we need to limit ourselves to that 249 buffer, and not rely on doing the EDIT trick to add more to it.

If that is the case, the most amount of HEX encoded assembly language bytes you can fit on a BASIC line is 122 per single-digit line. The code to read in those lines and POKE them in to memory can easily fit into one line as well, so we could easily fit a 1098 byte assembly language program into a ten line BASIC program.

And, we could even stick a few more bytes on the end of the loader line. Using a simple test program, I figured I could get the loader plus 1167 bytes of assembly code POKEd into memory. It looked like this:

0DATA000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778
1DATA797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1
2DATAF2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A
3DATA6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3
4DATAE4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C
5DATA5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5
6DATAD6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E
7DATA4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7
8DATAC8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F40
9 L=16128:FORA=0TO9:READA$:FORB=1TOLEN(A$)/2:POKEL,VAL(MID$(A$,B*2,2)):L=L+1:NEXT:NEXT:DATA4142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E
100 BT=0:FORA=0TO9:READA$:FORB=1TOLEN(A$)/2:PRINTMID$(A$,B*2,2);:BT=BT+1:NEXT:NEXT:PRINTBT"BYTES":END

Notice LINE 100. It is not part of the program. I can load this and type “RUN 100” to get it to dump all the data and show me what it would be POKEing into memory, as well as the final byte count. For a contest entry, it would only include lines 0-9. (Using the EXIT/eXtend trick, you could probably get another 9 or 10 bytes total, but they might consider that cheating, and maybe someone could optimize the loader code to make even more room.)

We can do better…

Over on the Facebook group, someone (who was it?) suggested using some alternative encoding to get even more data in to the DATA statement. There are existing forms of doing this with 7-bit characters, using the printable range of characters.

The wikipedia has a page showing many different implementations of binary to text encodings.

Since we need to encode things that can be typed in, this puts some limits on what we can do. Characters 0-32 are control characters, so normal printable text characters start with ASCII 32 (space) and go to ASCII 127.

See https://en.wikipedia.org/wiki/ASCII for the list.

There are some other characters in this range that we cannot type on a CoCo keyboard. Here is the printable CoCo character set:

Printable CoCo characters from ASCII 32 to ASCII 127.

We can type the inverted (lower case) letters “a” through “z”, but we have no way to type the inverted @ symbol, or the inverted left bracket, backslash, right bracket, up arrow or left arrow.

But, just because they are typeable does not mean we could use them all in a DATA statement. Quote, for instance, is okay if it’s in the middle of a string that is read…

10 READ A$:PRINT A$
20 DATA ABC"DEF

…but if the quote started up at the beginning of a string of characters, BASIC would skip it and join everything after it together until it sees another quote or an end of line:

10 READ A$:PRINT A$
20 DATA “ABCDEF

Also, comma is a problem. READ separates data using the comma UNLESS the data is quoted. This would only read the first ABC:

10 READ A$:PRINT A$
20 DATA ABC,DEF

…but this would read “ABC,DEF” as one string:

10 READ A$:PRINT A$
20 DATA “ABC,DEF”

So even though we can type about 91 characters, we can’t use all of them in a DATA statement.

Reduce the bass! (Er, base…)

It seems we could achieve a base-90 (?) format by having each line start with a quote (thus we could include a comma in the DATA), but the decoder would have to be much larger.

So instead of that, we’ll turn the base down a bit and do something a bit easier in the next installment.

Until next time…

DeltaBoard – theme changing BBS

I ran a BBS called DeltaBoard for awhile. The idea came from a friend of mine (Paul T.?) who thought a BBS that changed (Delta meaning change) would be cool.

So my BBS changed, selecting a different theme and name when you called in. There were eight different themes. I thought I’d share the welcome screen for each one.

Theme 1: Delta

    ____    ____       _____
    |   \  |      |      |     /\
    |    ) |---   |      |    /  \   Online in Iowa since last week!
    |___/  |____  |____  |   /____\    Originally Online in 1989!
 
          /) The DeltaBoard (\
    3/12/2400/9600/14.4 Baud - 8/N/1
 
          SysOp:  Zigwald Malushi
      Co-SysOps:  
 
If you are a first time caller, enter 'NEW':

Theme 2: Star Drek

.             .               .                 .                .
                         Space...  The final frontier...                   .
              .                    .             .          .        .
  .      _/_/_/  _/_/_/  _/_/_/  _/_/_/      _/_/    _/_/    _/_/_/  _/  _/
        _/        _/    _/  _/  _/  _/      _/  _/  _/  _/  _/      _/  _/
    .  _/_/_/    _/    _/_/_/  _/_/        _/  _/  _/_/    _/_/    _/_/ 
          _/    _/    _/  _/  _/  _/      _/  _/  _/  _/  _/      _/  _/
     _/_/_/    _/    _/  _/  _/  _/      _/_/    _/  _/  _/_/_/  _/  _/    .
 .          .             .          .                 .               .
         S T A R   D R E K   :   T h e   L o s t   G e n e r a t i o n     .
  .            .        .         .                    .
                           Captain:  Jean Luc Malushi     .         .
.        .              Sci. Officer:  Lt. Commander Bob     .            .
                    .         Bad Monster:  ......        .       .
     .           .              .                     .                 .
Input your Starfleet ID or enter 'NEW' to join the academy:

Theme 3: Castle Delta

 ___ ___ ___               ___ ___ ___
 | |_| |_| |               | |_| |_| |
 |         |   You stand   |         |
  \       /    before...    \       /
  _|     |___________________|_____|_ 
 |            C A S T L E            |
 |  ____    ____       _____         |
 |  |   \  |      |      |     /\    |
 |  |    ) |---   |      |    /  \   |
 |  |___/  |____  |____  |   /____\  |
 |___________________________________|
 
    Your King:  Zigwald of Nothingham
         Surf:  Sire ....
   Chief Peon:  ....
 
The gaurd shouts "Halt!  Who goes there?  State thy name or shout 'NEW' if
this is your first visit..."
 

Theme 4: Hotel Delta

  -----------------------------------
 |             H O T E L             |
 |  ____    ____       _____         |
 |  |   \  |      |      |     /\    |
 |  |    ) |---   |      |    /  \   |
 |  |___/  |____  |____  |   /____\  |
 |                                   |
 |           [..  VACANCY]           |
  -----------------------------------
        Manager:  Zigwald Malushi
          Clerk:  ....
        Bellhop:  Hopsing
 
"Please sign your name or scribble 'NEW' to register" the clerk requests.

Theme 5: Haunted Mansion

Based on the famous Disneyland and Walt Disney World attraction ;-)

When hinges creek in doorless chambers, and strange and frightening sounds
echo through the halls...  Whenever candlelights flicker where the air is
deathly still - that is the time when ghosts are present - practicing their
terror with ghoulish delight...
        .                .      .    \\  .  /--------------------------\
Welcome, foolish mortals, to the...  //.   / .  .  _  . . . . ___ _ __  \
   _   .  .  __________  .  .       /_     | |__| /_\ | | |\|  | |_ | \ |
  (_)     __/ ___  ___ \__   .   __/  \  . | |  | | | |_| | |  | |_ |_/ |
 .   .   /__\ |_|  |_| /__\   __/    /_    | .  .  _  . .  __ .  _  . . |
   .     |[]| .. __ .. |[]|  /  .   / /  . | |\/| /_\ |\| (_  | / \ |\| |
     . + |--| # |  | # |--| +        /_    | |  | | | | | __) | \_/ | | |
      _|_|__|___|__|___|__|_|_   .    / .  \                            /
 __---         /    \         ---__         \--------------------------/
 
I am your host.  Your ... Ghost Host.  Kindly step all the way in please and
make room for everyone.  There's no turning back now . . .
 
Please tell me your name, or type 'NEW' if this is your first visit.

Theme 6: DeltaBoard

                               you have dialed...
 ________  ._______  ._______        .__  .__  ._______  ._______  .__  .__
 \\ .___ \ \\ .___ \ \\_____ \       \\ \ \\ \ \\ .___ \ \\ .____\ \\ \ \\ \
  \\ \_\\ \ \\ \_\\ \      \\ \  .___ \\ \_\\ \ \\ \_\\ \ \\ \_____ \\ \_\\ \
   ))____. ) )) ___. )      )) ) ))__) ))____. ) )) ___. ) )) ___. ) ))____. )
  .____// / // /_// /      // /            // / // /_// / // /_// /      // /
 //______/ //______/      //_/            //_/ //______/ //______/      //_/
 
                                congratulations.
                           we knew you could do it...
 
     /) Welcome to The DeltaBoard - Des Moines' ONLY OS-9 Based Network (\
    ((      "A private system operating 24 hours a day (sometimes)."     ))
     \)       If this is your first visit, please login as 'NEW'.       (/
 

Theme 7: Den of Iniquity

    ._.__________________________
  /   \                         \
 /_____\  \/\/ E |_ < [] |\/| E  \     ||=\\   ||===  ||\ ||    /\  |--
        )                         )    ||  >>  ||==   ||\\||   <  > |--
       /          T []           /     ||=//   ||===  || \||    \/  |
      /    ..   .    ,  __      /
     /     /\   |      (_      /   || ||\ || ||  //\\  ||  || || ==== ||  ||
    /     /--\  |      __)    /    || ||\\|| || << .>> ||  || ||  ||   \\//
   /     ~    ~ ~~~~         /     || || \|| ||  \\/\\ \\==// ||  ||    ||
  (      .__________________(_____
   \    /                        /   B E W A R E  :  N o   f u r n i t u r e
    \__/________________________/    (under 18 not permitted without parent)
 
          If this  is your first visit, type 'NEW' as your Username.
 

Theme 8: Revenge-Net

  -+=====================================================================+-
    |                                                                    |
    |  +--+   +---+ +   + +---+ +   + +---+ +---+     +   + +---+ +-+-+  |
    |  |   >  |     |   | |     |\  | |     |         |\  | |       |    |
    |  +--+   +-+   |   | +-+   | \ | | +-+ |-+   +-+ | \ | +-+     |    |
    |  |   \  |      \ /  |     |  \| |   | |         |  \| |       |    |
    |  +    + +---+   +   +---+ +   + +---+ +---+     +   + +---+   +    |
    |                                                                    |
  -+=====================================================================+-
 
                  "For all of those who have venged before..."
       "People who seek revenge ... are just admitting that they lost."
       "Never seek revenge on someone who doesn't think they beat you."
      "Don't get mad, get even ... even if it means serving jail time..."
 
  (quotes from 'Revenge is Sweet Enough for Babies', Thom Cobra, Pan Books,
        ISBN 1-55851-380-4 - used with permission from the publisher.)

                  If you are a first time caller, enter 'NEW':
 

Ah, fun times. There were customized screens for bad passwords, logging out, menus, message of the day, and more. Fun stuff! OS-9 made it so easy to do things like this with scripts (batch files) and command line utilities.

I’ll share more, later…

Logitech K845ch mechanical keyboard

I just received a review unit of the Logitech K845ch mechanical keyboard. It has nice clicky keys, and lights under them. It can do five different light patterns at three different light levels. It’s interesting. One of the modes is a “react” mode so the key cap lights when you press it then quickly fades out.

I’ve missed these mechanical keyboards, so much. Thanks to Ed Snider’s work on the CoCoMech, I was made aware that they still exist. The unit I have used quiet switches, but I kinda want one with the loud clicking sound now… Fun!

VIC-20 knowledge lived on, without me knowing it.

I have posted a number of CoCo BASIC articles that used the example of bouncing a ball around the screen. I have a simple routine I use for this, which involves an X and Y location variable, and movement MX and MY variables that will be positive or negative for which direction the ball is moving.

To my surprise, I found nearly this exact code in the old VIC-20 manual as a bouncing ball example!

This code called it DX and DY (delta), but it’s basically the same code I’ve been using all these years. I had no idea I learned this from the VIC-20 manual back in 1982!

Classic.

Selling my original Atari Jaguar setup.

As much as it pains me to do so, I need the space and money more than I need all my 1990s Atari Jaguar stuff. I have eBay listings set up to sell my original (made in the USA by IBM) Atari Jaguar, the Jaguar CD unit, and all my games.

I even have a very rare add-on called a Catbox which provided all the various audio and video outputs as well as serial and the Jaguar network port. Mine was, I think, from the first run and was sent to me by the manufacturer as a thank you for some audio samples I contributed to a game they were working on. Fun times.

You can find my listings starting today at 6 p.m. PST here:

https://www.ebay.com/usr/allenhuffman