Elegant "10 PRINT" maze on the ZX81

David Jones posted an elegant version of 10PRINT on the ZX81:

5 LIST
10 PRINT " ▌▄"(1+RND);
20 GOTO 10

Using Sinclair BASIC’s string slicing syntax, it’s very compact. It’s stymied from being a one-liner by the ZX81’s insistence on one statement per line. It can be expressed on one line on the ZX Spectrum, however:

10 PRINT " ▌▄"(1+RND);: GO TO 10

The infinite scroll is banjaxed by the scroll? prompt, alas.

The output’s a little different from the familiar C64 version described in Montfort et al’s book “10 PRINT CHR$(205.5+RND(1)); : GOTO 10”. It’s less diagonal, more structural:


You can get much the same effect in PETSCII using:

10 print chr$(161.5+rnd(1));:goto 10
10 Likes

Very nice! BBC Basic can do it as a one-liner but it’s not elegant.

MODE 4
VDU 23, 90, &F0F; &F0F; &F0F; &F0F;
VDU 23, 91, 0; 0; -1; -1;
REPEAT
VDU 89+RND(2)
UNTIL FALSE

(Condensing onto one line left as an exercise for the reader. Under 70 characters is possible.)

3 Likes

The aforementioned maze

10 PRINT CHR$(161.5+RND(1));:GOTO 10

looks like this (with the original PET character set):

We can make this slightly more elegant by using CHR$(181) and CHR$(184), as in

10 PRINT CHR$(181+INT(RND(1)+.5)*3);:GOTO 10

but this is also noticeably slower.
We can speed this up a bit and make the code more elegant by exploiting the fact that Commodore BASIC encodes true as -1 and false as 0, thus arriving at

10 PRINT CHR$(181-3*(RND(1)>=.5));:GOTO 10

Which looks like this:

2 Likes

Love it!

Slightly more verbose in my own RTB Basic:

defchar (190, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F)
defchar (191, 0,0,0,0,0,0,0xFF,0xFF,0xFF,0xFF)

for i = 1 to tWidth * (tHeight-2) cycle
  vdu 190 + rnd (1)
repeat

end

(characters are 8x10)

And the run:

-Gordon

2 Likes

How would you change the program to have a clear center square, with a ‘x’ in it and the ‘word’ help?

I wasn’t meaning visual elegance. I was referring to the way that ZX81 BASIC appears to be subscripting from a string. This is similar to the way that one might do it in Python:

from random import randrange

while True:
    print("▌▂"[randrange(2)], end="")

I got deeply into /* you are not expected to understand this */ territory with my attempt for the BBC Micro’s teletext mode:

10 MODE 7: M=HIMEM-4: HIMEM=M-1: !M=&F0EA97
20 IF POS=0 THEN VDU M?0
30 VDU M?RND(2): GOTO 20

BBC BASIC’s syntax and system conventions are a little unusual:

  • line 10: !M=&F0EA97 is the same as poking four bytes 97 ea f0 00 starting at address M
  • line 20: Teletext graphics need a special command character at the beginning of the line. The VDU command is basically PRINT CHR$ &97;, or “use white on black block graphics
  • line 30: this prints a random character from either &F0 or &EA

Here’s a version you can run in the browser: BBC Microbot - Owlet Editor

3 Likes

I figured out what the asymmetry is that I perceived. Thinking of the white areas as being the path, you can always go either left or down, without getting to a dead end. Going up or right often leads to dead ends. Paths have a tendency to be up-right or down-left - after going up there’s never an option to go left, though sometimes you can go left then up.

With different characters or taking the opposite colours, different directions are favoured.

1 Like

excellent solution! Using POS is a great way to deal with the need for a special character at the start of each line. And I quite like the computed PEEK.

A more prosaic approach:

MODE 7
REPEAT PRINT:VDU&97
FOR I=0TO37
VDU 228+6*RND(2)
NEXT
UNTIL FALSE
1 Like

Well, this is deep into the bag of tricks of BBC BASIC and its idiosyncrasies!

I do get !M=&F0EA97, a BASIC indirection (“pling”) to put a 4-byte value into memory at a given location. (Since &F0EA97 is just 3 bytes, it’s implicitly setting the last byte to zero. We may have expected this to be the first byte, as in a 32-bit value. So BASIC seems to parse and evaluate it on a byte-for-byte basis.)
The semantics of M?RND(2) are a bit more difficult to parse: As I understand it ? queries a location given after the question mark. I guess, M?2 means, we query the location provided in M at an offset of 2, so that RND(2) will give us an offset of either 0 or 1? (I’ve the feeling I’m missing some here, as VDU seems to require some sort of separator for multi-byte arguments, so M?RND(2) has somehow to evaluate to a single byte, as I understand it.)

Further, I guess, HIMEM is a pointer to the top of user accessible memory (which we can both read from and write to) and POS is implicitly set on every screen output command. (Can we also write to this? Also, I understand that VPOS is the vertical equivalent to POS.)

1 Like

In essence, ! is word indirection (where a word here is 32-bits) and ? is byte indirection.

So essentially peek and poke.

A = ?0

reads a byte from location 0 into A.

?0 = A

Stores A (the bottom 8 -bits or A) into location 0. ! would do the same but 4 bytes at a time.

It’s borrowed from BCPL - it’s a very Cambridge thing.

-Gordon

1 Like

Yeah, it is a bit confusing. M?N is equivalent to PEEK(M+N), so it’s useful for indexed memory access. RND(n) for a positive integer value of n returns an integer in the range [1,n]. So we’re choosing a byte value of either PEEK(M+1) or PEEK(M+2).

The funny thing being, I had this as a typo and corrected it to meet the usual boundary conditions for RND(). Could well be that this wasn’t so much a typo, but rather followed intuitively from your code.

Thinking of m?n as an index expression makes much sense! (The bit I was struggling with here was really how this worked in terms of a general grammar.)
And it also folds back to the Sinclair BASIC example with the indexed string literal, making it clear how the BBC BASIC program actually somehow emulates the Sinclair styntax.

1 Like

CoCo:

1 Like

TRS-80:

Ahhhhh! As an utterly non-Sinclair person, TIL that’s how you do MID$ in a ZX.

1 Like

Can someone help me understand how these “one liners” generate a nice, maze-like pattern with lines often longer than one “pixel”? I would have expected more like TV “snow.” Is the secret in the glyphs?

1 Like

It’s probably easier to observe them in action than explaining them.

Here’s the original one, composing the maze of diagonal graphics characters (similar to forward and back slashes), both on an emulated Commodore PET:

10 PRINT CHR$(205.5+RND(1));: GOTO 10

https://masswerk.at/pet/?data=base64:MTAgUFJJTlQgQ0hSJCgyMDUuNStSTkQoMSkpOzpHT1RPIDEw&rom=2&list=true&autorun=true

And here the variant discussed here:

 10 PRINT CHR$(161.5+RND(1));: GOTO 10

https://masswerk.at/pet/?data=base64:MTAgUFJJTlQgQ0hSJCgxNjEuNStSTkQoMSkpOzpHT1RPIDEw&rom=2&list=true&autorun=true

How it works (here on the example of the original):
As usual, CHR$() provides a character string for the given character number, which will be automatically truncated to an integer. So, CHR$(205.5) produces the string for character code 205. RND() returns a number 0 < n < 1 with an equal probability of being below or above 0.5. If it is greater, the CHR$() expression yields the string for character code 206. So the expression will print either code 205 or code 206 at roughly equal probabilities. Since the PRINT statement ends in a semicolon, the next iteration will print where the last one left, without advancing to a new line. (But, as the line overflows by reaching the right side of the screen, we still continue at the next line, by this filling the entire screen, at which point the “maze” starts to scroll for an animated effect.)

The 2 characters give us 4 “atomic” combinations:

  • // – a diagonal passage to the lower left
  • \\ – a diagonal passage to the lower right
  • /\ – a top corner
  • \/ – a bottom corner

If we combine these further, we get what starts to resemble a tilted maze:

//\/  a crisscrossing passage
\/\\

////  a series of simple passages
////

\/\/  a passage with a turn at the top
//\\

\\//  two passages converging to a cross-like
//\\  blockade in the maze

etc.

So, yes, the secret is in the glyphs and how they combine and line up, with strokes either touching at opposing corners of their 8 × 8 character matrix (forming a corner) or not (touching diagonally for a visual continuation forming a “passage”).

I think the answer is that the characters used - not in the original Commodore world but in this chunky-pixels world - are 2x2 pixels. The one-liner chooses between two pixels adjacent horizontally, or two vertically. (In the teletext case, 2x3, and therefore two horizontal or three vertical)

Compare:

(We may discern that PETSCII code 161 is a half-filled character block to the left and PETSCII 162 a half-fill at the bottom. If we get two characters of the same kind in adjacent screen positions this forms a passage, if not, it will be some kind of a corner.
Notably, the maze produced by the combination of these two characters will have some bias regarding how its “paths” are formed, as they are only turn-symmetrical in a single direction, but not symmetrical along any of the major axes.)

1 Like

The general term for such patterns is Truchet tiles: made up of two or more square tiles that have special properties (most notably, lack of rotational symmetry). No computer that I know of has characters explicitly designed to form Truchet tiles, so for one-liners, we have to approximate. Some character pairs work well, some less well. Some computers don’t have suitable characters at all.

For systems that allow user-defined characters, it’s possible to make almost perfect Truchet tilings, such as: BBC Micro MODE 4 one-liner.