Author Archives: Allen Huffman

About Allen Huffman

Co-founder of Sub-Etha Software.

GEEKPI’s Getting Started with MicroPython on Raspberry Pi Pico kit

It seems only yesterday I first mentioned the new Raspberry Pi Pico. At this time of its introduction, I wondered two things:

  1. Why did they use the “Raspberry Pi” name for a new piece of hardware that does not run Linux. It was closer to an Arduino than a Pi. I expect this naming will cause confusion, since folks have had years learning what a “Pi” can do (video, audio, keyboard, mouse, etc.) and the Pico does none of that.
  2. Why did they bother with a $4 Pico, if a PiZero can be had for only $1 more.

#2 is answered with “because folks like me dislike the slowness of booting a full OS and all the hassles of dealing with Linux for an embedded project.” However, I already use Arduinos for that purpose. The Pico just seemed more like the larger Arduino models.

Side Note: To me, and many others, “Arduino” will always mean “Arduino Uno“, the tiny and cheap Arduino with 4K of RAM. Because it was the version that started things up, the name Arduino is mostly associated with these smaller limited models. But, Arduino even makes more powerful versions that can run Linux.

#1 I think is “just because it will cause confusion.” I think the same thing about the “new” Atari VCS. (If you didn’t know, there is a new Atari out — and it is called the same thing the original Atari VCS was called back in 1977. No confusion there. ;-)

But I digress…

I recently received a $45 GEEKPI BASIC Pico kit to review. You can find it on Amazon (see that link).

Reading through the specs of this $4 circuit board, I find it is pretty impressive. Speeds up to 133MHz, 264K of RAM, and 2MB of Flash storage. It has enough power, memory and storage to run things like Python, which a 4K Arduino just cannot do.

The included manual had me download a Python IDE, then plug up and connect to the Pico with a USB cable. I could then type and run my first “Hello World” program in Python. You can even copy the “main.py” python script to the Pico so it will power up and run on it’s own. (A few other steps were needed to install MicroPython on the Pico, but they were easy and only took a few minutes.)

Impressive.

The initial downside is that the Pico does not come with the header pins soldered on. I had to turn to a coworker to do this for me so I could plug it into the breadboard and hook up some wires for a blinking LED example.

I expect at some point (if not already) you will be able to buy a model with those pins already soldered on, much like you can do with a Pi Zero.

I do not know when I’ll have time to fully explore the “power of the Pico,” but it looks like it will be a fun time. It appears to be quite capable with I/O and protocols (SPI, I2C, UARTs, etc.).

More to come…

char versus C versus C#

Updates:

  • 2020-07-16 – Added a few additional notes.

I am mostly a simple C programmer, but I do touch a bit of C# at my day job.

If you don’t think about what is going on behind the scenes, languages like Java and C# are quite fun to work with. For instance, if I was pulling bytes out of a buffer in C, I’d have to write all the code manually. For example:

Buffer: [ A | B | C | D | D | E | E | E | E ]

Let’s say I wanted to pull three one-byte values out of the buffer (A, B and C), followed by a two-byte value (DD), and a four byte value (EEEE). There are many ways to do this, but one lazy way (which breaks if the data is written on a system with different endianness to how data is stored) might be:

#include <stdint.h>

uint8_t a, b, c;
uint16_t d;
uint32_t e;

a = Buffer[0];
b = Buffer[1];
c = Buffer[2];
memcpy (&d, &Buffer[3], 2);
memcpy (&e, &Buffer[5], 4);

There is much to critique about this example, but this post is not about being “safe” or “portable.” It is just an example.

In C#, I assume there are many ways to achieve this, but the one I was exposed to used a BitConverter class that can pull bytes from a buffer (array of bytes) and load them in to a variable. I think it would look something like this:

UInt8 a, b, c;
UInt16 d;
UInt32 e;

a = Buffer[0];
b = Buffer[1];
c = Buffer[2];
d = BitConverter.ToInt16(Buffer, 3);
e = BitConverter.ToInt32(Buffer, 5);

…or something like that. I found this code in something new I am working on. It makes sense, but I wondered why some bytes were being copied directly (a, b and c) and others went through BitConverter. Does BitConverter not have a byte copy?

I checked the BitConverter page and saw there was no ToUInt8 method, but there was a ToChar method. In C, “char” is signed, representing -127 to 128. If we wanted a byte, we’d really want an “unsigned char” (0-255), and I did not see a ToUChar method. Was that why the author did not use it?

Here’s where I learned something new…

The description of ToChar says it “Returns a Unicode character converted from two bytes“.

Two bytes? Unicode can represent more characters than normal 8-bit ASCII, so it looks like a C# char is not the same as a C char. Indeed, checking the Char page confirms it is a 16-bit value.

I’m glad I read the fine manual before trying to “fix” this code like this:

Char a, b, c;
UInt16 d;
UInt32 e;

// The ToChar will not work as intended!
a = BitConverter.ToChar(Buffer, 0); //Buffer[0];
b = BitConverter.ToChar(Buffer, 1); //Buffer[1];
c = BitConverter.ToChar(Buffer, 2); //Buffer[2];
d = BitConverter.ToInt16(Buffer, 3);
e = BitConverter.ToInt32(Buffer, 5);

For a C programmer experimenting in C# code, that might look fine, but had I just tried it without reading the manual first, I’d have been puzzled why it was not copying the values I expected from the buffer.

Making assumptions about languages, even simple things like a data type “char”, can cause problems.

Thank you, manual.

Researching 8-bit floating point.

Recently, I ran in to a situation where a floating point value (represent current) was being converted to a byte value before being sent off in a status message. Thus, any calculations on the other side were being done with whole values (1, 2, 42, etc.). I was asked if the precision could be increased (adding a decimal place) and realized “you can’t get there from here.”

This made me wonder how much could be done with an 8-bit floating point representation. A quick web search led me to this article:

http://www.toves.org/books/float/

I also found a few others discussion other methods of representing a floating point value with only 8-bits.

Now I kinda want to code up such a routine in C and do some tests to see if it would be better than our round-to-whole-number approach.

Has anyone reading this already done this? I think it would be a fun way to learn more about how floating point representation (sign, mantissa, exponent) works.

But it doesn’t seem very useful.

C, printf, and pointers.

I learned a new C thing today.

But first, an old C thing.

When you write “standard C” and want to print a value using printf, you are at the mercy of standard C data types. For example, “%d” is a “Signed decimal integer” and “%u” is an “Unsigned decimal integer.”

There apparently mean the data types “int” and “unsigned int”.

int x;
printf ("x is %d\n", x);

unsinged int y;
printf ("y is %u\n", y);

At my day job, I am using a PIC24 processor, which is a 16-bit chip and is Harvard Architecture, similar to an Arduino UNO processor. When I try to write generic code on a PC using GCC (64-bit compiler), I run in to things like this:

char *ptr = 0x1234;
printf ("ptr is 0x%x\n", ptr);

%x expects an “Unsigned decimal value”. ptr is NOT a “Unsigned decimal value” that %x expects, so it will give a warning like:

warning: initialization of 'char *' from 'int' makes pointer from integer without a cast [-Wint-conversion]

warning: format '%x' expects argument of type 'unsigned int', but argument 2 has type 'char *' [-Wformat=]

This seems like a reasonable warning. In the past, I’ve cast the pointer to an unsigned int like this:

char *ptr = 0x1234;
printf ("ptr is 0x%x\n", (unsigned int)ptr);

A 16-bit compiler may be just fine with this, but it would generate warnings on a 32 or 64-bit compiler because the “int” is a “long” or “long long” to them. Thus, different casting is needed.

That makes code non-portable, because printing a long int (“%lu”) expects different things on different architectures. In other words, I can cast my code to compile cleanly on the 16-bit system, but then I get warnings on the 64-bit system. Or vise versa.

…but that’s not what this post is about. Instead, it’s about why I was casting in the first place — to print pointers.

I was unaware that a “%p” had been added specifically for printing pointers. Unfortunately, it was not supported in the 16-bit PIC compiler I am using, so that won’t work.

inttypes.h

Instead, I found a stack overflow post that introduced me to “inttypes.h” and uintptr_t. I was unaware of this and see the casting lets me do something like this:

printf ("Dump (0x%x, %u)\r\n", (uintptr_t)ptr, size);

This will cast the pointer to an unsigned integer value which can then be printed!

On some systems.

There’s still the problem with a %u expecting an “int” but the value might be a “long int” (if it’s a 32-bit or 64-bit pointer).

PRIxWHAT?

It looks like someone thought of this, and my original “non portable printf” casting issue.

I see this file also contains #defines for various printf outputting types such as PRIXPTR, PRIX, PRIU8, and lowercase versions where it makes sense like PRIxPTR. These turn in to quoted strings like “d” or “X” or whatever. Meaning, you could use them to get the output type that matches what you want on the compiler you are using, rather than hard-coding %d.

uint32_t x = 42;
printf ("This is my value: %" PRIu32 "\n", x);

Above, PRIu32 turns in to the appropriate %u for printing a 32-bit value. This might be %u on one system, or %lu on another (depending on the size of “int” on the architecture).

Neat!

That lets me printfs be portable, IF the compiler supports these defines.

…as long as your compiler supports it, that is.

Fortunately, my PIC compiler does, so from now on I’ll stop hard-coding %x when printing pointers, and using those defines when printing stdint types like uint32_t:

void Dump(void *ptr, unsigned int size)
 {
     printf ("Dump (0x%"PRIXPTR", %u)\r\n", (uintptr_t)ptr, size);

At least, I think I will.

Until next time…

Raspberry Pi Pico?

For those who find the unwieldy huge size of the Raspberry Pi Zero off-putting, the Raspberry Pi Foundation has now released a Raspberry Pi Pico.

It uses a custom chip, the RP2040, and seems to be more of an Arduino than a Pi. It has 264KB of memory and can be programmed in C/C++ or MicroPython. Thus, it is not a Linux system.

The unfortunate name of calling it a Pi may cause some confusion.

But it’s still neat. With the Pi group entering this market, it finally gives them something to compete with Arduino. The Pi is great, but having a slow-booting disk base OS that can corrupt the file system if you do not shut down properly each time was not a good fit for embedded designs.

The Pi Zero is $5, and the new Pi Pico is $4. This is a great price point compared to things like the mini Arduinos, but it’s for folks who can solder if you want to hook anything to it. Since the Pi Zeros are sold with versions that have header pins soldered on (for $5 more), it seems likely someone will do that for the Pico allowing folks who don’t solder well (such as myself) to make use of this item.

My current favorite supplier of Pi items is Vilros:

More to come…

Benchmarking the CoCo keyboard – part 7

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

NOTE: This one is gonna jump around a bit, referring to examples in the previous installment, so hang tight…

Some comments from the previous installment, where I shared some code that wasn’t speeding up like I thought it should when I replaced hard-coded values with variables. As the post when live, I read through it and noticed my mistake. I added a comment to see if anyone else could spot it:

0 REM arrowbench9.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

craig immediately chimed in with the issue:

In arrowbench9.bas it pokes $F7 instead of $FF ?

In test, shouldn’t line 10 should use same as arrowbench9.bas line 20 ?

Can you use ‘next’ instead of ‘goto90’ ?

– craig

Right off the bat, craig noticed my mistake. In like 20, it’s supposed to POKE those four keyboard column values to 255 (&HFF). But, when I substituted the variables, I did not make a variable for 255 — instead, I incorrectly used the V variable which was the &HF7 value the PEEK would change to when that key was being held down. Oops! Thus, I was never resetting it so it was, apparently, always acting as if the key was being held down, processing all the variable X/Y stuff every time.

A fix to the original arrowbench2.bas might look like this:

0 REM arrowbench10.bas
5 Z=&HFF:V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,Z:POKED,Z:POKEL,Z:POKER,Z
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

Now the keyboard table is properly reset back to 255 before each scan. While the original arrowbench2.bas reported 2890, this version (using the properly variable to reset those locations) reports 2351 and is indeed faster. My bad.

As to the other comments, that was yet another typo, where I created variables then forgot to use them:

5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
20 PRINT HEX$(PEEK(U))" "HEX$(PEEK(D))" "HEX$(PEEK(L))" "HEX$(PEEK(R)),HEX$(PEEK(&H155))" "HEX$(PEEK(&H156))" "HEX$(PEEK(&H157))" "HEX$(PEEK(&H158))
30 GOTO 10

…should have used U/D/L/R and a new Z in line 10, just like the example before.

As to using “NEXT” instead of “GOTO 90”, this refers to trying to bypass additional checks if one key is satisfied:

0 REM arrowbench11.bas
5 Z=&HFF:V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,Z:POKED,Z:POKEL,Z:POKER,Z
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

In this benchmark example, I did GOTO 90 to go to the “end of what we are timing” line. But if this was being used in a program, a NEXT would have been faster than scanning forward to find line 90 and then doing the NEXT. BUT, if I tried that here, when NEXT was done (1 to 1000), it would not return and would go to the next line — and if that was 40, 50 or 60, it would do the check then try a NEXT and error with a “?NF ERROR” (next without for).

But, the point is well made — NEXT with a check after it could even be faster than a GOTO (scanning lines) to a next. That would be a fun benchmark.

Faster, even.

craig also pointed out an interesting optimization… Rather than clear all four keyboard values, whether they need it or not, why not just clear the one(s) that changed?

Faster.. what about moving line 20 out of the loop and only poking the matching peeks?

IF PEEK(U)=V THEN POKEU,F:IF Y>.THEN Y=Y-1:GOTO90

F=&HFF

For diagonals, change to GOTO50 on line 30.
Line 60 needs no GOTO90

– craig

This is worthy of an updated benchmark. Let’s take arrowbench10.bas above (2351) and modify it like this:

0 REM arrowbench12.bas
5 Z=&HFF:V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
30 IF PEEK(U)=V THENPOKEU,Z:IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THENPOKED,Z:IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THENPOKEL,Z:IF X>.THEN X=X-1
60 IF PEEK(R)=V THENPOKER,Z:IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

Now it only clears the value if it was changed. This produces a value of … 1603! We have a winner! Great improvement, craig!

Also, when I added the “GOTO 90” at the end of the example, to bypass the other checks (eliminating the possibility of diagonals), craig is suggesting the code could simply skip checking DOWN if we had an UP (GOTO 50 moves to the Left/Right check next) and, likewise, like 50 could GOTO 90 to skip over the Right check. Thus:

  • If UP is pressed…
    • Check for LEFT. If LEFT is pressed…
      • Exit checks. We have an UP and a LEFT
    • else Check for RIGHT. If RIGHT is pressed…
      • Exit checks. We have an UP and RIGHT.
    • else Exit checks. We have UP.
  • else check DOWN. If DOWN is pressed…
    • Check for LEFT. If LEFT is pressed…
      • Exit checks. We have DOWN and LEFT
    • else Check for RIGHT. If RIGHT is pressed…
      • Exit checks. We have DOWN and RIGHT.
    • else Exit checks. We have DOWN.

When I write it out that way, you can see that this type of logic (adding GOTO to skip steps) means that the program would be fastest checking for UP and LEFT. And slowest for checking for JUST down. This means a game would move at different speeds based on which direction is detected. While this is a great optimization, it may not be desirable since a game may wish consistent speed (i.e. always worst case) than having a speed that varies.

Big thanks to craig for spotting my typo, and providing these two additional optimizations. I really like the “only POKE if it changed” one. It would be fun to benchmark and see if “worst case” all four arrows were being held down, is this slower than just clearing them all at once.

IF ELSE SLOWER

One additional note about using IF… Once BASIC starts processing a line, it has to parse the entire line whether that code is executed or not. For example:

IF A=42 THEN DO THIS:DO THAT:DO THE OTHER:DO NOTHING:DO OVER

Even if A is not 42, BASIC still has to at least scan through all the tokens and such on the rest of the line to skip it. Also, since ELSE could be used on Extended Color BASIC, there could be an ELSE clause that still needs to be processed:

10 IF A=42 THEN DO THIS:DO THAT:DO THE OTHER:DO NOTHING:DO OVER ELSE ...

Because of this, IF/ELSE can actually be slower than doing something like:

10 IF A=42 THEN 30
20 DO OVER:GOTO 40
30 DO THIS:DO THAT:DO THE OTHER:DO NOTHING:GOTO 40
40 ...continue... 

This looks quite awful, but that’s how I had to program on my Commodore VIC-20 because it had no ELSE. And, it turns out, this can be quite a bit faster! Now line 10 checks for A to be 42, and if it is, it skips a line (which is fast) to get to 30 and hande it. If it is NOT 42, it quickly skips two lines instead to having to parse through a whole line of BASIC tokens.

I benchmarked something like this in an earlier article series, and was very impressed at the speedups that can be achieved just by making any IF line use a GOTO… though if it’s likely the line is true (A=42) more often than it’s not, it might not make sense. One size does not fit all.

And with that, I’ll end today’s installment without providing anything new, other than some handy speedups craig showed us.

We now have some quick ideas on using arrow keys to change X and Y coordinates. X and Y coordinates are used in games like Atari Adventure and Pac-Man to know which direction to send an enemy at the player. In the case of Pac-Man (see my earlier article series on that one), the ghosts target spots around (or on top of ) Pac-Man and decide which direction to turn based on which choice would be closer (using the wonderful Pythagorean theorem we learned about in school).

BUT, if our program was not using that, and just wanted a screen location to POKE a player character to, we could probably simplify this keyboard code a bit and use less variables.

To be continued…

Exploring Atari VCS/2600 Adventure – part 4

See also: part 1, part 2, part 3, part 4 … and more to come…

Objectively speaking

Welcome back to the world of Atari Adventure! After spending some time figuring out how the rooms were drawn, it’s time to look at how the game objects are drawn.

Adventure contains many objects that can be displayed:

  1. Bat
  2. Bridge
  3. Castle gate (or Portcullis – “a strong, heavy grating sliding up and down in vertical grooves, lowered to block a gateway to a fortress or town.”)
  4. Chalice
  5. Created by Warren Robinett (author’s name)
  6. Dot (for getting in to the easter egg room)
  7. Dragon (red, green and yellow)
  8. Key
  9. Magnet
  10. Number “1” (for game select screen)
  11. Number “2”
  12. Number “3”
  13. Sword

Some objects have multiple frames. For instance, the bat has two: wings up, and wings down. The dragon has three: open mouth, closed mouth, dead. The dragons can also be drawn facing left, or facing right.

I also found an entry for something called “Surround,” which appears to be the the square around the player in the invisible mazes.

In the ROM disassembly, it looks like these objects are just stored as bytes that represent them:

GfxChallise:
  .byte $81 ;X      X                                                                  
  .byte $81 ;X      X                                                                  
  .byte $C3 ;XX    XX                                                                  
  .byte $7E ; XXXXXX                                                                   
  .byte $7E ; XXXXXX
  .byte $3C ;  XXXX
  .byte $18 ;   XX
  .byte $18 ;   XX
  .byte $7E ; XXXXXX
  .byte $00 

Above, the game-winning chalice appears to be 8×9.

The dragon is much larger:

GfxDrag0:
  .byte $06 ;     XX 
  .byte $0F ;    XXXX 
  .byte $F3 ;XXXX  XX 
  .byte $FE ;XXXXXXX 
  .byte $0E ;    XXX 
  .byte $04 ;     X
  .byte $04 ;     X
  .byte $1E ;   XXXX
  .byte $3F ;  XXXXXX
  .byte $7F ; XXXXXXX
  .byte $E3 ;XXX   XX
  .byte $C3 ;XX    XX
  .byte $C3 ;XX    XX
  .byte $C7 ;XX   XXX
  .byte $FF ;XXXXXXXX
  .byte $3C ;  XXXX
  .byte $08 ;    X
  .byte $8F ;X   XXXX
  .byte $E1 ;XXX    X
  .byte $3F ;  XXXXXX
  .byte $00

But it is still represented as 8 pixels wide. The code to display it must magnify it to make it larger on screen.

Even the bridge, the widest object displayed in the game, is represented as 8-bits wide. This is the first thing we will need to dig in to… What controls how large these small object representations are drawn?

Also, it appears every object is terminated with a $00 rather than having the length at the start. For example, instead of this:

10 READ N:FOR I=1 TO N:READ A$:PRINT A$:NEXT:END
20 DATA 10,a,b,c,d,e,f,g,h,i,j

…it works like this:

10 READ A$:IF A$="0" THEN END ELSE PRINT A$:GOTO 10:END
20 DATA a,b,c,d,e,f,g,h,i,0

Since both would have taken the same amount of data storage space in the ROM, I am betting the code to parse that data may have been smaller to loop and check for 0 versus loading a size and counting down.

Also, this presents a restriction for the graphics — none can contain an “empty” row in the graphic ($00). Because of this, each line (byte) must have at least one pixel set. This explains the dots in the easter egg signature!

;Object #4 : State FF : Graphic
GfxAuthor:
 .byte $F0    ;XXXX
 .byte $80    ;X
 .byte $80    ;X
 .byte $80    ;X
 .byte $F4    ;XXXX X
 .byte $04    ;     X
 .byte $87    ;X    XXX
 .byte $E5    ;XXX  X X
 .byte $87    ;X    XXX
 .byte $80    ;X
 .byte $05    ;     X X
 .byte $E5    ;XXX  X X
 .byte $A7    ;X X  XXX
 .byte $E1    ;XXX    X
 .byte $87    ;X    XXX
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $F0    ;XXXX
 .byte $01    ;       X
 .byte $40    ; X
 .byte $E0    ;XXX
 .byte $40    ; X
 .byte $40    ; X
 .byte $40    ; X
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $E0    ;XXX
 .byte $80    ;X
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $20    ;  X
 .byte $20    ;  X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $E0    ;XXX
 .byte $01    ;       X
 .byte $01    ;       X
 .byte $01    ;       X
 .byte $88    ;   X   X
 .byte $A8    ;X X X
 .byte $A8    ;X X X
 .byte $A8    ;X X X
 .byte $F8    ;XXXXX
 .byte $01    ;       X
 .byte $E0    ;XXX
 .byte $A0    ;X X
 .byte $F0    ;XXXX
 .byte $01    ;       X
 .byte $80    ;X
 .byte $E0    ;XXX
 .byte $8F    ;X   XXXX
 .byte $89    ;X   X  X
 .byte $0F    ;    XXXX
 .byte $8A    ;X   X X
 .byte $E9    ;XXX X  X
 .byte $80    ;X
 .byte $8E    ;X   XXX
 .byte $0A    ;    X X
 .byte $EE    ;XXX XXX
 .byte $A0    ;X X
 .byte $E8    ;XXX X
 .byte $88    ;X   X
 .byte $EE    ;XXX XXX
 .byte $0A    ;    X X
 .byte $8E    ;X   XXX
 .byte $E0    ;XXX
 .byte $A4    ;X X  X
 .byte $A4    ;X X  X
 .byte $04    ;     X
 .byte $80    ;X
 .byte $08    ;    X
 .byte $0E    ;    XXX
 .byte $0A    ;    X X
 .byte $0A    ;    X X
 .byte $80    ;X
 .byte $0E    ;    XXX
 .byte $0A    ;    X X
 .byte $0E    ;    XXX
 .byte $08    ;    X
 .byte $0E    ;    XXX
 .byte $80    ;X
 .byte $04    ;     X
 .byte $0E    ;    XXX
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $80    ;X
 .byte $04    ;     X
 .byte $0E    ;    XXX
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $04    ;     X
 .byte $00

Those rows would have been empty, but having a $00 there would have prevented there rest from being drawn. Interesting!

Draw something

For today’s installment, I’ll be using Color BASIC on a CoCo emulator to write a simple program to plot these objects on the screen. I copied all of the assembly definitions into an editor then did a search and replace to turn those .byte instructions into incredibly inefficient one-number-per-line DATA statements like this:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81 ' X      X   
2860 DATA &H81 ' X      X   
2870 DATA &HC3 ' XX    XX   
2880 DATA &H7E '  XXXXXX    
2890 DATA &H7E '  XXXXXX   
2900 DATA &H3C '   XXXX     
2910 DATA &H18 '    XX      
2920 DATA &H18 '    XX      
2930 DATA &H7E '  XXXXXX    
2940 DATA &H00

Unfortunately, READ/DATA in Color BASIC does not allow that. It will READ the first value, then give an ?SN ERROR when it tries to read the next because the parser doesn’t handle the apostrophe “REM” marker!

So I changed it to this:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81:REM X      X   
2860 DATA &H81:REM X      X   
2870 DATA &HC3:REM XX    XX   
2880 DATA &H7E:REM  XXXXXX    
2890 DATA &H7E:REM  XXXXXX   
2900 DATA &H3C:REM   XXXX     
2910 DATA &H18:REM    XX      
2920 DATA &H18:REM    XX      
2930 DATA &H7E:REM  XXXXXX    
2940 DATA &H00

This also fails — and is a problem I only recently discovered when writing my Base-64 articles.

I guess I can’t have this code be as bulky and inefficient as I wanted. I removed them so it was just the DATA statements:

2830 ' Object #10 : State FF : Graphic  
2840 ' GfxChallise:
2850 DATA &H81
2860 DATA &H81
2870 DATA &HC3
2880 DATA &H7E
2890 DATA &H7E
2900 DATA &H3C
2910 DATA &H18
2920 DATA &H18
2930 DATA &H7E
2940 DATA &H00

Oh well. Who needs comments anyway — especially in BASIC where they just bloat the code and slow it down.

A quick-and-dirty routine allowed me to see it was working:

Atari Adventure graphics rendered on a CoCo :)

…but there was no way I was going to be able to fit the “Created by Warren Robinett” graphic on a low-res 64×32 screen. To solve this, I hacked together a version that used a “high resolution” screen:

Atari Adventure graphics rendered in “high resolution” on a CoCo :)

It looks like the data is pretty straight forward. The dragons would have to be drawn reversed to make them face right, and the bridge looks like it needs an X scaling factor added to make it as wide as it is in the game. But, not bad for a first attempt.

Just for fun, here is the brute-force hacky nasty Color BASIC code I quickly put together to do this:

10 REM AdvObjs.bas
20 POKE 65395,0
30 FOR A=0 TO 7:BT(A)=2^A:NEXT
40 CLS0:XS=1:YS=1:Y=1
50 PMODE 1,1:PCLS:SCREEN 1,0
60 READ V:IF V=-1 THEN 120 ELSE IF V=0 THEN 90
70 X=XS:FOR A=7 TO 0 STEP-1:IF V AND BT(A) THEN PSET(X*2,Y*2,C)
80 X=X+1:NEXT:Y=Y+1:GOTO 60
90 XS=XS+9:IF XS>120 THEN XS=1:YS=YS+32
100 C=C+1:IF C=1 THEN C=2 ELSE IF C>3 THEN C=0
110 Y=YS:GOTO 60
120 GOTO 120
130 ' Object #0A : State FF : Graphic  
140 ' GfxBridge:
150 DATA &HC3
160 DATA &HC3
170 DATA &HC3
180 DATA &HC3
190 DATA &H42
200 DATA &H42
210 DATA &H42
220 DATA &H42
230 DATA &H42
240 DATA &H42
250 DATA &H42
260 DATA &H42
270 DATA &H42
280 DATA &H42
290 DATA &H42
300 DATA &H42
310 DATA &H42
320 DATA &H42
330 DATA &H42
340 DATA &H42
350 DATA &HC3
360 DATA &HC3
370 DATA &HC3
380 DATA &HC3
390 DATA &H00
400 ' Object #5 State #1 Graphic :'1'  
410 ' GfxNum1:
420 DATA &H04
430 DATA &H0C
440 DATA &H04
450 DATA &H04
460 DATA &H04
470 DATA &H04
480 DATA &H0E
490 DATA &H00
500 ' Object #0B : State FF : Graphic  
510 ' GfxKey:
520 DATA &H07
530 DATA &HFD
540 DATA &HA7
550 DATA &H00
560 ' Object #5 State #2 Grphic :
570 ' GfxNum2:
580 DATA &H0E
590 DATA &H11
600 DATA &H01
610 DATA &H02
620 DATA &H04
630 DATA &H08
640 DATA &H1F
650 DATA &H00
660 ' Object #5 State #3 Graphic :'3'  
670 ' GfxNum3:
680 DATA &H0E
690 DATA &H11
700 DATA &H01
710 DATA &H06
720 DATA &H01
730 DATA &H11
740 DATA &H0E
750 DATA &H00
760 ' Object #0E : State 03 : Graphic  
770 ' GfxBat1:
780 DATA &H81
790 DATA &H81
800 DATA &HC3
810 DATA &HC3
820 DATA &HFF
830 DATA &H5A
840 DATA &H66
850 DATA &H00
860 ' Object #0E : State FF : Graphic  
870 ' GfxBat2:
880 DATA &H01
890 DATA &H80
900 DATA &H01
910 DATA &H80
920 DATA &H3C
930 DATA &H5A
940 DATA &H66
950 DATA &HC3
960 DATA &H81
970 DATA &H81
980 DATA &H81
990 DATA &H00
1000 ' Object #6 : State #00 : Graphic  
1010 ' GfxDrag0:
1020 DATA &H06
1030 DATA &H0F
1040 DATA &HF3
1050 DATA &HFE
1060 DATA &H0E
1070 DATA &H04
1080 DATA &H04
1090 DATA &H1E
1100 DATA &H3F
1110 DATA &H7F
1120 DATA &HE3
1130 DATA &HC3
1140 DATA &HC3
1150 DATA &HC7
1160 DATA &HFF
1170 DATA &H3C
1180 DATA &H08
1190 DATA &H8F
1200 DATA &HE1
1210 DATA &H3F
1220 DATA &H00
1230 ' Object 6 : State FF : Graphic    
1240 ' GfxDrag1:
1250 DATA &H80
1260 DATA &H40
1270 DATA &H26
1280 DATA &H1F
1290 DATA &H0B
1300 DATA &H0E
1310 DATA &H1E
1320 DATA &H24
1330 DATA &H44
1340 DATA &H8E
1350 DATA &H1E
1360 DATA &H3F
1370 DATA &H7F
1380 DATA &H7F
1390 DATA &H7F
1400 DATA &H7F
1410 DATA &H3E
1420 DATA &H1C
1430 DATA &H08
1440 DATA &HF8
1450 DATA &H80
1460 DATA &HE0
1470 DATA &H00
1480 ' Object 6 : State 02 : Graphic    
1490 ' GfxDrag2:
1500 DATA &H0C
1510 DATA &H0C
1520 DATA &H0C
1530 DATA &H0E
1540 DATA &H1B
1550 DATA &H7F
1560 DATA &HCE
1570 DATA &H80
1580 DATA &HFC
1590 DATA &HFE
1600 DATA &HFE
1610 DATA &H7E
1620 DATA &H78
1630 DATA &H20
1640 DATA &H6E
1650 DATA &H42
1660 DATA &H7E
1670 DATA &H00
1680 ' Object #9 : State FF : Graphics  
1690 ' GfxSword:
1700 DATA &H20
1710 DATA &H40
1720 DATA &HFF
1730 DATA &H40
1740 DATA &H20
1750 DATA &H00
1760 ' Object #0F : State FF : Graphic  
1770 ' GfxDot:
1780 DATA &H80
1790 DATA &H00
1800 ' Object #4 : State FF : Graphic   
1810 ' GfxAuthor:
1820 DATA &HF0
1830 DATA &H80
1840 DATA &H80
1850 DATA &H80
1860 DATA &HF4
1870 DATA &H04
1880 DATA &H87
1890 DATA &HE5
1900 DATA &H87
1910 DATA &H80
1920 DATA &H05
1930 DATA &HE5
1940 DATA &HA7
1950 DATA &HE1
1960 DATA &H87
1970 DATA &HE0
1980 DATA &H01
1990 DATA &HE0
2000 DATA &HA0
2010 DATA &HF0
2020 DATA &H01
2030 DATA &H40
2040 DATA &HE0
2050 DATA &H40
2060 DATA &H40
2070 DATA &H40
2080 DATA &H01
2090 DATA &HE0
2100 DATA &HA0
2110 DATA &HE0
2120 DATA &H80
2130 DATA &HE0
2140 DATA &H01
2150 DATA &H20
2160 DATA &H20
2170 DATA &HE0
2180 DATA &HA0
2190 DATA &HE0
2200 DATA &H01
2210 DATA &H01
2220 DATA &H01
2230 DATA &H88
2240 DATA &HA8
2250 DATA &HA8
2260 DATA &HA8
2270 DATA &HF8
2280 DATA &H01
2290 DATA &HE0
2300 DATA &HA0
2310 DATA &HF0
2320 DATA &H01
2330 DATA &H80
2340 DATA &HE0
2350 DATA &H8F
2360 DATA &H89
2370 DATA &H0F
2380 DATA &H8A
2390 DATA &HE9
2400 DATA &H80
2410 DATA &H8E
2420 DATA &H0A
2430 DATA &HEE
2440 DATA &HA0
2450 DATA &HE8
2460 DATA &H88
2470 DATA &HEE
2480 DATA &H0A
2490 DATA &H8E
2500 DATA &HE0
2510 DATA &HA4
2520 DATA &HA4
2530 DATA &H04
2540 DATA &H80
2550 DATA &H08
2560 DATA &H0E
2570 DATA &H0A
2580 DATA &H0A
2590 DATA &H80
2600 DATA &H0E
2610 DATA &H0A
2620 DATA &H0E
2630 DATA &H08
2640 DATA &H0E
2650 DATA &H80
2660 DATA &H04
2670 DATA &H0E
2680 DATA &H04
2690 DATA &H04
2700 DATA &H04
2710 DATA &H80
2720 DATA &H04
2730 DATA &H0E
2740 DATA &H04
2750 DATA &H04
2760 DATA &H04
2770 DATA &H00
2780 ' Object #10 : State FF : Graphic  
2790 ' GfxChallise:
2800 DATA &H81
2810 DATA &H81
2820 DATA &HC3
2830 DATA &H7E
2840 DATA &H7E
2850 DATA &H3C
2860 DATA &H18
2870 DATA &H18
2880 DATA &H7E
2890 DATA &H00
2900 ' Object #11 : State FF : Graphic  
2910 ' GfxMagnet:
2920 DATA &H3C
2930 DATA &H7E
2940 DATA &HE7
2950 DATA &HC3
2960 DATA &HC3
2970 DATA &HC3
2980 DATA &HC3
2990 DATA &HC3
3000 DATA &H00
3010 ' Object #1 States 940FF (Graphic)
3020 ' GfxPort01:
3030 DATA &HFE
3040 DATA &HAA
3050 'GfxPort02:
3060 DATA &HFE
3070 DATA &HAA
3080 'GfxPort03:
3090 DATA &HFE
3100 DATA &HAA
3110 'GfxPort04:
3120 DATA &HFE
3130 DATA &HAA
3140 'GfxPort05:
3150 DATA &HFE
3160 DATA &HAA
3170 'GfxPort06:
3180 DATA &HFE
3190 DATA &HAA
3200 'GfxPort07:
3210 DATA &HFE
3220 DATA &HAA
3230 'GfxPort08:
3240 DATA &HFE
3250 DATA &HAA
3260 'GfxPort09:
3270 DATA &H00
3280 DATA -1

It ain’t pretty, but it’s mine!

I suppose next I should get back to looking at how the rooms are connected. And there’s some interesting stuff in here, including rooms that “change” as the game is played.

Stay tuned…

Benchmarking the CoCo keyboard – part 6

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7 and more (coming “soon”).

Let’s jump right in with a mystery… This code:

0 REM arrowbench.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>. THEN Y=Y-1
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(&H157)=&HF7 THEN IF X>. THEN X=X-1
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…reports 2139.

This code, using variables instead of five constants…

0 REM arrowbench2.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…reports 2890 – slower.

My previous BASIC benchmarks have shown that looking up variables should be faster than parsing HEX values, at least when there are a small amount of variables to parse through.

But in this case, it is slower.

And this code, which just adds some GOTOs to skip additional checking when a key is matched (it will not support diagonals):

0 REM arrowbench8.bas
10 TIMER=0:FOR A=1 TO 1000
20 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
30 IF PEEK(&H155)=&HF7 THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(&H156)=&HF7 THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(&H157)=&HF7 THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(&H158)=&HF7 THEN IF X<&HFE THEN X=X+1
90 NEXT:PRINT TIMER

…prints 2186 (a bit slower than the first, since it always has to skip the extra characters at the end of each line).

Yet this:

0 REM arrowbench9.bas
5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 TIMER=0:FOR A=1 TO 1000
20 POKEU,V:POKED,V:POKEL,V:POKER,V
30 IF PEEK(U)=V THEN IF Y>.THEN Y=Y-1:GOTO90
40 IF PEEK(D)=V THEN IF Y<&HDF THEN Y=Y+1:GOTO90
50 IF PEEK(L)=V THEN IF X>.THEN X=X-1:GOTO90
60 IF PEEK(R)=V THEN IF X<&HFE THEN X=X+1:GOTO90
90 NEXT:PRINT TIMER

…reports 1431 – faster.

A quick test using the TRON command shows that the second version using variables does not always run lines 50 and 60. It is sometimes getting a value back that triggers that extra code, hitting the GOTO and skipping the other lines.

But why does this happen when using variables, but not when using hard-coded constant values? This test program seems to show they display the same values:

5 V=&HF7:U=&H155:D=&H156:L=&H157:R=&H158
10 POKE&H155,&HFF:POKE&H156,&HFF:POKE&H157,&HFF:POKE&H158,&HFF
20 PRINT HEX$(PEEK(U))" "HEX$(PEEK(D))" "HEX$(PEEK(L))" "HEX$(PEEK(R)),HEX$(PEEK(&H155))" "HEX$(PEEK(&H156))" "HEX$(PEEK(&H157))" "HEX$(PEEK(&H158))
30 GOTO 10

What am I not seeing? Where is my typo?

Please help.

UPDATE: I found my typo. And it’s a stupid one. Do you see what I did wrong?

To be continued…

CoCo MC6847 VDG chip “draw black” challenge responses.

See also: challenge, responses, and more responses.

Recently, I was annoyed to find that there did not seem to be any way to set a black pixel on the CoCo’s normal green background. I have since been schooled in the simplest way to make this work, which I will share after a long digressing ramble.

Never the Same Color Twice

The CoCo’s MC6847 VDG chip provides nine colors. Commenter Jason wrote:

“It always bothered me that the CoCo had nine colors in semi-graphics modes. The number nine should raise a red flag for anyone who is familiar with computers and the tendencies for things to be powers of two.

“It’s interesting that seven of the eight colors are from the NTSC test pattern (https://en.wikipedia.org/wiki/SMPTE_color_bars) which leads me to believe they’re all a particular frequency distance from each other. This would make the circuitry simpler.”

– Jason

I suppose it’s actually eight foregrounds colors with a black background, which matches the black border of the screen. There was even a test pattern program included in one of Radio Shack’s quick reference guide that I still have:

Color Adjustment Test Display

5 FOR X = 0 TO 63
10 FOR Y = 0 TO 31
15 C = INT(X/8+1)
20 SET(X,Y,C)
25 NEXT Y,X
30 GOTO 30

That produces the following output, showing the eight possible colors (plus the black background):

Color Adjustment Test Display, Radio Shack TRS-80 Color Computer Quick Reference Guide, page 55.

And here is the NTSC test pattern Jason referenced:

By Denelson83 – Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=1067498

This made me want to alter the program to make it render something with the matching colors in the correct order (and with the Xroar emulator set to not emulate a crappy 1980s RF modulated TV signal):

The 7 NTSC color bar colors that the CoCo produces.

The extra color that is not shown is orange. I wonder why those eight colors (plus black) were chosen? And what makes the colors used by the two PMODE high res graphics screens? I’ll have to revisit that in the future.

But I digress…

You can’t, even if you SET your mind to it

My original article was written because I noticed you couldn’t SET a black pixel on a normal CoCo text screen. Even though the manual listed nine colors, with zero being black, attempting to do SET(X,Y,0) would result in that pixel being set to the green background color instead of black — the same as SET(X,Y,1). While other colors acted as you expected…

CoCo SET command.

SET seemed to be treating color 0 (black) as 1 (green). Because reasons.

In order to SET a black pixel on the normal text screen, extra code would be needed.

Ciaran Anscomb

Xroar emulator author Ciaran Anscomb was the first to respond with his GOSUB routine to achieve the desired effects:

I mean I think you’re making it harder by asking for it to work with
plain CLS and not CLS1, but in that case:

100 IFPEEK(1024+INT(Y/2)*32+INT(X/2))<128THENPOKE1024+INT(Y/2)*32+INT(X/2),143
110 RESET(X,Y):RETURN

– Ciaran Anscomb

His method would PEEK the character value at the 2×2 block that was being SET and, if that value was less than 128, it would change it to 143 and then use RESET… And it works:

10 CLS
20 FOR A=0 TO 31
30 X=A:Y=A:GOSUB 100
40 NEXT
50 GOTO 50
99 ' Ciaran Anscomb
100 IFPEEK(1024+INT(Y/2)*32+INT(X/2))<128THENPOKE1024+INT(Y/2)*32+INT(X/2),143
110 RESET(X,Y):RETURN

Jim Gerrie

BASIC programmer extroidinaire Jim Gerrie provided his take on this routine:

100 SET(X,Y,1):SET(X-(X/2-INT(X/2)=.),Y,1):SET(X-(X/2-INT(X/2)=.),Y-(Y/2-INT(Y/2)=.),1):SET(X,Y-(Y/2-INT(Y/2)=.),1)
101 RESET(X,Y):RETURN

– Jim Gerrie

His version sets some pixels to color 1, and some to color 0 (using the “.” shortcut), based on some math with X and Y and divisions and integer conversions and … well, stuff I don’t grasp.

His also works! But as it draws, you can see it blipping surrounding pixels in the 2×2 block on then off. And while it passes the test case which drew a diagonal line, it doesn’t allow for setting arbitrary pixels near each other. They turn into full blocks.

However, he also added a second attempt:

I know that my first suggestion above is a bit of a cheat. Here’s a more robust suggestion:

10 CLS
20 FOR A=0 TO 31
30 X=A:Y=A:GOSUB 100
40 NEXT
50 GOTO 50
100 IFPOINT(X,Y)<.THENXX=(X/2-INT(X/2)=.)-(X/2-INT(X/2)>.):YY=(Y/2-INT(Y/2)=.)-(Y/2-INT(Y/2)>.):SET(X,Y,1):SET(X-XX,Y,1):SET(X-XX,Y-YY,1):SET(X,Y-YY,1)
101 RESET(X,Y):RETURN

– Jim Gerrie

This version passes the test as well, and looks like it better handles setting pixels at any position without impacting pixels around it.

What’s the POINT?

Ciaran made use of PEEK to detect what was on the screen before adding something new, and Jim figured out what pixels to set back to the background color. Neither did it the way I was expecting — using POINT:

POINT (X,Y) Tests whether specified graphics cell is on or off, x (horizontal) = 0-63; y (vertical) = 0-31. The value returned is -1 if the cell is in a text character mode; 0 if it is off, or the color code If it is on, See CLS for color codes.

IF POINT(10,10) THEN PRINT "ON" ELSE PRINT "OFF"

I expected I’d see folks use this to see if a pixel was set, and handle accordingly. Somehow. But as I read this description (from the Quick Reference Guide), I see that note that says “The value returned is -1 if the cell is in a text character mode.”

Text character mode? It’s just the background, isn’t it?

All green backgrounds are not the same

And that takes me back to Ciaran’s code:

IF PEEK(1024+INT(Y/2)*32+INT(X/2))<128 . . .

Less than 128 is a text character. The graphics blocks (2×2) start as 128. If the square is a text character then set it to 143. So what is that? That is a 2×2 graphics block that has all pixels set to the green color. And that green color is the same color as the background screen. Which isn’t 143 when you use CLS. Try this:

CLS:PRINT PEEK(1024)

If you clear the screen then PEEK to see what value is at the top left character (1024), it returns 96. 96 is the space character (yeah, ASCII is 32, but values in screen memory aren’t ASCII).

Ciaran’s code sees if it’s anything (including that green space), set it to 143, which is a green block that looks the same. Try this:

CLS 1:PRINT PEEK(1024)

That will print 143. Yet, visually, CLS and CLS 1 look the same. But, CLS is filling the screen with the space text character (96) and CLS 1 fills it with the green graphics character (143)! CLS 0-8 fill the screen with solid graphics characters, and CLS with no parameter is the space.

Now, I knew about character 143 looking like the normal space but not being one, because we used to use this as a cheap “copy protection” method. On DISK, you could save out a file like this:

SAVE "HELLO.BAS"

…and you’d get a file on BASIC called HELLO.BAS. But, if you did this:

SAVE "HELLO"+CHR$(143)+".BAS"

…Disk BASIC would write out a file called HELLO(char 143).BAS. When you did a DIR they would look the same, but you couldn’t do a LOAD”HELLO.BAS” to get the one with the CHR$(143) in it. Unless you exempted the disk directory bytes you would not know there was an “invisible” character at the end of the “HELLO” filename.

Sneaky. And I did this with some of my own programs.

But years later, when the CoCo 3 came out, it’s 40 and 80 column screen did NOT support the 2×2 graphics block characters, and this trick was no longer as sneaky since you would see “HELLO(some funky character).BAS” in the directory listing and know something weird had been done.

But I digress, again…

Why do it the hard way, anyway?

It turns out, even though I knew about the “add CHR$(143)” trick, I had forgotten (or never knew/realized) that CLS and CLS 1 filled the screen with different characters. And, if the screen has a graphics character at the position, RESET will then work to change that pixel back to black.

Ciaran got me exploring this because in his e-mail he added:

If you allow CLS1, the problem solves itself :)

– Ciaran Anscomb

I had to follow up and ask what he meant by this. And, well, nevermrind, then. All I needed to do was start the program with CLS 1 instead of CLS 0, and then I could use RESET() to set individual black pixels on the screen.

I could have a subroutine that expect X, Y and C (for color) and if the color was 0 (black), do a RESET, else do a SET:

0 REM RESET
10 CLS 1
20 FOR A=0 TO 31
30 X=A:Y=A:C=0:GOSUB 100
40 NEXT
50 GOTO 50
100 IF C=0 THEN RESET(X,Y) ELSE SET(X,Y,C)
110 RETURN

And that works fine on any CLS 0-8 screen. Remember, SET(X,Y,0) never gives you a black pixel. 0 seems to mean “background color” instead, while RESET(X,Y) seems to mean “set to black”.

To me, this is a bit counterintuitive, since today I would expect “reset” to mean “set to background color” but this isn’t a graphics mode — it’s just graphics characters on a screen, so the only way BASIC could have done this is if it remembered what the CLS # value was, and made RESET set to that color. Which would be extra ROM space for a simple enhancement that most could work around with RESET instead.

And that, my friends, is how a rabbit hole works.

Until next time…

Exploring Atari VCS/2600 Adventure – part 3

See also: part 1, part 2, part 3, part 4 … and more to come…

Defining the invisible

When we last left off, I was trying to figure out what all the bits did in the room definition attribute byte:

;Offset 4 : Bits 5-0 : Playfield Control                                                                           
;            Bit 6 : True if right thin wall wanted.                                                               
;            Bit 7 : True if left thin wall wanted.  

In the disassembly I was looking at (created in 2006 or earlier), it did not go in to details about what “Playfield Control” was for. By some trial, I was about to work out which bits represented the right half of a room to be drawn Mirrored or Reversed, as well as the bits that defined drawing a thing left or right wall line:

Bit 0 - Right half of screen is Reversed.
Bit 1 - ?
Bit 2 - ?
Bit 3 - Right half of screen Mirrored.
Bit 4 - ?
Bit 5 - ?
Bit 6 - Thin right wall.
Bit 7 - Thin left wall.

There were a few bits left over, and I knew the game had rooms that where “invisible” mazes where you only saw the portion of the maze directly around the player:

Atari Adventure “invisible” maze.

This screenshot is from game variation 2 and 3, and it is below the room to the left of the easter egg room (or, from the yellow castle, down, right, then down). By roaming around the room, and trying to match up its shape with the source code, I believe it is this location:

MazeEntry:
  .byte $F0,$FF,$0F ;XXXXXXXXXXXXXXXX        RRRRRRRRRRRRRRRR
  .byte $00,$30,$00 ;      XX                        RR
  .byte $F0,$30,$FF ;XXXX  XX    XXXXXXXXRRRRRRRRR   RR  RRRR
  .byte $00,$30,$C0 ;      XX          XXRR          RR
  .byte $F0,$F3,$C0 ;XXXXXXXX  XX      XXRR      RR  RRRRRRRR
  .byte $00,$03,$C0 ;          XX      XXRR      RR
  .byte $F0,$FF,$CC ;XXXXXXXXXXXX  XX  XXRR  RR  RRRRRRRRRRRR

By looking at the room definition data for an entry that uses these graphics, I find room 10:

LFE75: .byte <MazeEntry,>MazeEntry,$08,$08,$25,$03,$09,$09,$09

Its attributes are $25, which is the bit pattern 00101001. Bit 5 is being used, and it wasn’t in my earlier room examples, so I believe that is for “invisible”:

Bit 0 - Right half of screen is Reversed.
Bit 1 - ?
Bit 2 - ?
Bit 3 - Right half of screen Mirrored.
Bit 4 - ?
Bit 5 - Invisible.
Bit 6 - Thin right wall.
Bit 7 - Thin left wall.

For all 30 rooms defined in the ROM, I only see bits 0, 3, 5, 6 and 7 ever used. (Distinct attribute values are: $21, $24, $25, $61, and $a1. Bits 0-3 can be $1=0001, $4=1000 or $5=1001, and bits 4-7 can be $2=0010, $6=0110 or $a=1010. It seems to check out, but please double check me. I make many mistakes when writing these things and could be a … bit … off.)

This gives me five types of rooms to render.

Color me bad

There is also a color value (when in Color mode) and a black and white color value (when in Black and White mode). The Atari had a switch to alter the colors the games used so they were easier to view on a black and white TV set. Later in the console’s life, not all games continued to support this switch.

For Adventure, I see various values in the ROM, but no reference to what color they generate. Some are in definitions called “Yellow Castle” or “Red Maze”, but most are not described with a color. Instead, I look at the translated translated Adventure code by Peter David Hirschberg:

https://github.com/peterhirschberg/adventure/blob/master/src/adventure.ts

Yes, translates translated. Back around 2006, he converted the Adventure assembly code to C++ and wrote wrapper code to allow it to run on a modern PC under the title “Adventure: Revisited.” More recently, he took that converted C code and converted it to TypeScript. (I had to look up just what that was. It’s a Microsoft superset of JavaScript.) Because of this, you can now play his conversion inside a web browser:

http://peterhirschberg.com/#/AdventurePlay

In his TypeScript source, he fills in some of the gaps with new comments including this nice color table:

const COLOR_BLACK=0
const COLOR_LTGRAY=1
const COLOR_WHITE=2
const COLOR_YELLOW=3
const COLOR_ORANGE=4
const COLOR_RED=5
const COLOR_PURPLE=6
const COLOR_BLUE=7
const COLOR_LTCYAN=8
const COLOR_CYAN=9
const COLOR_DKGREEN=10
const COLOR_LIMEGREEN=11
const COLOR_OLIVEGREEN=12
const COLOR_TAN=13
const COLOR_FLASH=14

But, since the original ROM code used hardware-specific values, these numbers do not map to what the assembly code used for those colors. If I wanted to parse the actual data bytes in the ROM code (rather than converting it to a modern enumerated lookup table), I’d need to know which value represented which color. Fortunately, he also updated the room definition structures to use the above labels so it’s obvious:

let roomDefs: ROOM[] = [
  { graphicsData: roomGfxNumberRoom,
    flags: ROOMFLAG_NONE,
    color: COLOR_PURPLE,
    roomUp: 0x00, roomRight: 0x00,
    roomDown: 0x00, roomLeft: 0x00 }, // 0 - Number Room

  { graphicsData: roomGfxBelowYellowCastle,
    flags: ROOMFLAG_LEFTTHINWALL,
    color: COLOR_OLIVEGREEN,
    roomUp: 0x08, roomRight: 0x02,
    roomDown: 0x80, roomLeft: 0x03 }, // 1 - Top Access

  { graphicsData: roomGfxBelowYellowCastle, flags: ROOMFLAG_NONE,
    color: COLOR_LIMEGREEN,
    roomUp: 0x11, roomRight: 0x03,
    roomDown: 0x83, roomLeft: 0x01 }, // 2 - Top Access
...

Compare those three entries with the same three in the disassembly (and note the order is a bit different):

RoomDataTable:                                                                                                                   
LFE1B: .byte <NumberRoom,>NumberRoom,$66,$0A,$21,$00,$00,$00,$00   

LFE24: .byte <BelowYellowCastle,>BelowYellowCastle,$D8,$0A,$A1,$08,$02,$80,$03                   

LFE2D: .byte <BelowYellowCastle,>BelowYellowCastle,  $C8,$0A,$21,$11,$03,$83,$01

And we can assume that $66 is PURPLE, $DB is OLIVE GREEN, and $C8 is LIME GREEN. This would let us do our own Atari VCS to Whatever color table for displaying that data.

Of course… I could have just played the game and gone to each room, looked at the color on the screen, then figured it out that way, but he already did the work and I was lazy.

And I know what some of you are thinking… I could have just looked at his source code to figure out the attributes bits. But, alas, I could not. He already converted them. He translated those down to just:

const ROOMFLAG_NONE          = 0x00
const ROOMFLAG_MIRROR        = 0x01 // bit 0 - 1 if graphics are mirrored, 0 for reversed
const ROOMFLAG_LEFTTHINWALL  = 0x02 // bit 1 - 1 for left thin wall
const ROOMFLAG_RIGHTTHINWALL = 0x04 // bit 2 - 1 for right thin wall

…and he must have figured out that Invisible rooms all use a specific color, because he handles the invisible rooms this way:

function Surround()
{
  // get the playfield data
  const currentRoom: ROOM = roomDefs[objectBall.room]
  if (currentRoom.color == COLOR_LTGRAY)
  {
    // Put it in the same room as the ball (player) and center it under the ball
    objectSurround.room = objectBall.room
    objectSurround.x = (objectBall.x-0x1E)/2
    objectSurround.y = (objectBall.y+0x18)/2
  }
  ...

He rewrote the game in a manner that makes sense, rather than trying to do a literal translation of machine-specific assembly code in to C. Not that anyone would ever try such a literal translation

It doesn’t look like he bothered to support Black and White color mode, either ;-) and neither will I.

With the graphics data, room attributes, and colors figured out, the only thing left in the room definition structure are the room exits. As I mentioned in the previous installment, those don’t all seem to be obvious.

We’ll kick that can down the road a bit, since there are a few more fun things to do before having to think again.

Until next time…