Vintage Computing Christmas Challenge 2022 (VC³ 2022)

I wasn’t aware of the Stardot thread - lots of creative solutions! I did my entry in PALM assembly for the IBM 5100/5110.

I imagine it’s hard to “normalize” the binary sizes of all the BASIC solutions (i.e. accounting for what portions of the BASIC ROM they invoke to execute the keywords they use). The smallest BASIC ROM that I recall was around 2K - 4K.

I submitted an entry using MINT (a tiny forthlike interpreted language) on a Z80 RC2014. The interpreter is <1700 bytes of Z80 machine code

I got it down to a fairly unreadable 124 bytes with a run-length coding approach and output to a serial terminal.

A collegue tried an algorithmic approach and using his tiny-forth S3 and got it down to 53 bytes.

Another friend tried on a Sinclair Spectrum in Z80 assembly language - with only one call to the character print routing in ROM, to an amazing 29 bytes of asm!

:S(` `);:X(`*`);:M10S;:RX\NM;:I4S;:T10+R;:AI1X7S1R;:BI2X5S2R;:CI3X3S3R;:DI4X1S4R;:FMABCD7T1S5T2S3T3S1TI9R3S1T2S3T1S5T7TDCBA; 


A slightly more readable version:

There are 11 functions defined:

S 	Print a number of spaces defined by a number on top of the stack
X 	Print a number of asterisks defined by number on stack
M 	Print 10 spaces. This offsets the graphic towards the centre of the screen
R 	Print a row of stars followed by a newline
I 	Print an indent of 4 spaces.
T 	Print a row of 10+n stars followed by a newline. 
A,B,C,D Print various combinations of stars and spaces
F 	A top level function that prints the whole star graphic.


:S(` `);
:X(`*`);
:M10S;
:RX\NM;
:I4S;
:T10+R;
:AI1X7S1R;
:BI2X5S2R;
:CI3X3S3R;
:DI4X1S4R;
:FMABCD7T1S5T2S3T3S1TI9R3S1T2S3T1S5T7TDCBA;

1 Like

Maclisp solution:

(comment)
(sstatus feature noldmsg)

(defun starp (x y)
  (or (and (> x y) (<= y 4) (<= x (+ y 4))) (and (<= x 4) (<= y (+ x 4)))))

(defun star ()
  (terpri)
  (loop for i from -8 to 8 do
    (loop for j from -8 to 8 do
      (princ (if (starp (abs i) (abs j)) '* '/ )))
    (terpri)))

(star)
(quit)

Amazing indeed! I hope we see it one day.

It never occurred to me to use logical operators here, but I see gfoot made very good use of this approach in Basic (and in 6502 and in ARM too.)

Edit: I see the presentation of successful submissions to the contest will be live tonight in about 4 hours:

Well the results are in and the video is up, so I’ll post mine here…

Rather than be algorithmically clever, I just did a run-length encoding of the ‘data’ however my decoder was written in RTB Basic - which is my own BASIC Interpreter that runs under Linux or on a bare-bones Raspberry Pi - it’s not a ‘retro’ style of BASIC at all, but rather what I wanted in my own ideal BASIC…

// Xmas coding challenge 2022
//    RTB Basic
//    Gordon Henderson

cls
vTab = 4
out$ = ""

read s$

i = 0
while i < len (s$) cycle
  x$ = mid$ (s$, i, 1)

  if x$ = "_" then
    print "    "; out$
    out$ = ""
    i    = i + 1
    continue
  endif

  n = asc (x$) & 0x1F
  if x$ < "a" then
    c$ = "*"
  else
    c$ = " "
  endif

  for j = 1 to n cycle
    out$ = out$ + c$
  repeat

  i = i + 1
repeat

end

data "dAgA_dBeB_dCcC_dDaD_Q_aO_bM_cK_dI_cK_bM_aO_Q_dDaD_dCcC_dBeB_dAgA_"

The editor here seems to be colouring it in ways I don’t like, but I’m sure it’s readable. The encoding is trivial, A-Z is number of Stars, a-z is number of spaces and if either stars are 31 then it’s a newline.

It might be possible to make the code a bit more dense, but I wanted it easy to read in printable text without resorting to some some of uucode/base64 to represent binary in printable text.

So there you are - it’s nowhere near the smallest, might even be one of the longest, but it was a bit of fun - and for an added bonus it can also print out last years tree too… And next years, if it’s a simple pattern like the previous 2 :wink:

-Gordon

1 Like

It’s great to see so many different approaches - I tried a couple of triangle decompositions, and a scan line approach, all of them using X swap Y symmetry. The shortest BBC Basic entry uses random numbers to fill in the star-shaped envelope - very unexpected. Even though we saw data-driven approaches last year - did one even win? - for some reason I didn’t even give that a go.

I took a few screenshots of some notable entries:

David Payne, BBC Micro, BBC Basic, 49 bytes

regregex, BBC Micro, 6502 assembly, 46 bytes

serato, C64, 6502 assembly, 34 bytes (uses “isc” undocumented opcode)

StasMas, BK 0010, PDP-11 assembly, 30 bytes

Peter De Wachter, IBM 5110, APL, 30 bytes

Dr Beep, ZX Spectrum, Z80 assembly, 29 bytes

Dr Beep, ZX81 , Z80 assembly, 27 bytes

1 Like

I had a look at the version that uses random numbers… It’s quite clever, but depends on some knowledge of how BBC Basic works - especially RND.

So the code has a random number which is either -1 or 1 (sgn rnd) multiplies it by a scaling factor based on the x and y values in the for loops… The loops need to be repeated many times to fill-in the star… Crude eye-ball experiments with my version seem to indicate that it takes between 10 and 25 iterations…

This is it translated to my own RTB Basic:

cls
cycle
  for y = -8 to 4 cycle
    for x = -4 to y+4 cycle
      hvTab (x * sgn (rnd (2) - 1) + 8, y * sgn (rnd (2) - 1) + 9)
      print "*";
    repeat
  repeat
  update
repeat

Which does (somewhat surprisingly!) work…

(You need to know that RND in BBC Basic returns a signed 32-bit integer in the range +/- 2^31 - there is no equivalent in RTB, but rnd (2) - 1 yields -1 or 0 and sgn then returns -1 or 1).

The VDU command simply sends bytes to the display, code 31 is move cursor to x,y and 42 is of-course “*”. No vdu command in RTB, but hvTab does the same thing as vdu 31.

-Gordon

Wow over 200 submissions! I’ll have to study the PDP-11 one some more (not familiar with EMT-- I assume EMIT to some output device? and SOB, some kind of shift and branch?)

The 5110 APL is neat! Wonder if that same APL works on other APL-systems. A lot of the BASIC solutions seem to take advantage of some local system feature (which is great, but I wonder what is the “most portable” solution).

I wish there was an easier way to evaluate the total execution count of each of these solution (including anything in ROM that they use). You could instrument emulators that could get that count. Or look at relative runtime performance, although all systems can be overclocked to some extent (see circuit bending). Maybe one could look at the energy cost of running the software, and get some kind of efficiency from that (it’s a pertinent metric in modern satellite/space and medical {in-body} software that needs to really sip lightly on the Watts).

EDIT: Finally got thru the entire video. Summary of the entries (with time offsets) is available here:
VCCC2022 — voidstar
Also, wow, a KENBAK-1 entry was submitted (this is 50th anniversary year for that system).

After the 5110 assembly solution, I took a stab at a BASIC solution for the IBM 5110 (not submitted).

Below is the annotated “long” version, to (hopefully) keep the approach straightforward, which is:

  • decompose the shape into a few common strings,
  • assign those strings to indexes in an array,
  • define a data sequence specifying what order to draw the piece-parts strings to reconstruct the desired image.

This is just one of the generic (kind of) “RLE” approaches, whereas the “procedurally (algorithm) generated” approaches should be more efficient (but I suspect more “brittle” at being adaptable to other patterns).

There are some annoying things about this older IBM BASIC on the 5110:

  • single line can’t go over 64 characters (can’t wrap to multiple lines on the screen)
  • can’t combine commands using colon ‘:’ (you can in certain PRINT statements, but not FOR or IF)
  • IF statements can only GOTO (can’t do compound statements, like IF A=5 THEN PRINT ‘5’ is invalid)
  • strings consisting of all blanks end up truncated out (had to add keyboard support for the || concatenate symbol to the emulator to test that out)
  • arrays starting at index 1 was a typical BASIC thing, right?
200 REM DEFINE 4-CHARACTER PADDING
210 DIM A$4(8)
220 REM DEFINE 1-CHARACTER PADDING
230 DIM B$1(2)
234 REM DEFINE 23-CHARACTER PADDING (FOR CENTERING)
235 DIM C$23(1)
239 REM 8 UNIQUE 'STAR' STRING OPTIONS
240 READ A$(1),A$(2),A$(3),A$(4),A$(5),A$(6),A$(7),A$(8)
248 REM B$ USED FOR SINGLE '*' VS ' ' OPTION
249 REM C$ USED FOR LEFT PAD FOR CENTERING
250 READ B$(1),B$(2),C$(1)
260 READ N
270 FOR I = 1 TO N STEP 5
280   READ A1,A2,B1,A4,A5
290   PRINT C$(1)||A$(A1)||A$(A2)||B$(B1)||A$(A4)||A$(A5)
295   REM || IS CONCATE CODE FOR IBM 5110
300 NEXT I
310 END
400 REM ===========================
405 REM UNIQUE 'STAR' SEQUENCES (GROUPS OF 4)
410 DATA '    '
420 DATA '****'
430 DATA ' ***'
440 DATA '  **'
450 DATA '   *'
460 DATA '*   '
470 DATA '**  '
480 DATA '*** '
485 REM PLACEHOLDER FOR ON/OFF CENTER PORTION
490 DATA ' '
500 DATA '*'
504 REM BLANK CONTENT (DIM SIZE WILL GET PADDED)
505 DATA ''
510 REM HOW MANY SECTORS (HOW MUCH DATA)
520 DATA 85
530 REM DATA SECTOR ABBREVIATIONS
540 REM A-SERIES INDEXES
550 REM 1=0000
560 REM 2=1111
570 REM 3=0111
580 REM 4=0011
590 REM 5=0001
600 REM 6=1000
610 REM 7=1100
620 REM 8=1110
630 REM B-SERIES INDEXES
640 REM 1=0
650 REM 2=1
660 REM  A A B A A
670 DATA 1,6,1,5,1
680 DATA 1,7,1,4,1
690 DATA 1,8,1,3,1
700 DATA 1,2,1,2,1
710 DATA 2,2,2,2,2
720 DATA 3,2,2,2,8
730 DATA 4,2,2,2,7
740 DATA 5,2,2,2,6
760 DATA 1,2,2,2,1
770 DATA 5,2,2,2,6
780 DATA 4,2,2,2,7
790 DATA 3,2,2,2,8
800 DATA 2,2,2,2,2
810 DATA 1,2,1,2,1
820 DATA 1,8,1,3,1
830 DATA 1,7,1,4,1
840 DATA 1,6,1,5,1

Removing all the “fluff” (e.g. REM’s) and streamlining this, I get it down to only 541 bytes. The 5110 had 16KB minimum, but for early vintage systems with only 1-4K, code size really starts to matter. (even with 32K, I realized why many early games didn’t include in-game instructions - English text eats up RAM very quickly).

The IBM 5110 shows the amount of available memory in the bottom right, so it’s easy to monitor on that system. After the first run, then 182 bytes of additional RWS/RAM is used (I assume from the DIM allocations). I’d have to instrument the emulator to see how many instructions the “read” “for” “print” “step” and “next” keyword equate to in the ROM (i.e. to fairly say how much native instruction work is needed to run the BASIC).

The assembly version is self sufficient but still ~300 bytes - I’d say it was “compiled for speed” rather than size, and had most loops unrolled.

2 Likes

Everything starts at one. It took civilizations thousands of years to discover zero, after all.

One of the neat tricks baked into BASIC is that, rather than giving you grief about array subscripts, it gives you an extra entry. So when you DIM A(10), you get 11 whole values to play with: A(0) to A(10). BASIC wants your code to work, even if it’s not quite “right”.

As for my entry, which I thought was going to be awesomely small in BBC BASIC, turned out rather large:

1B=&7C00:MO.7:F.Y=0TO8:P.SPC(-4*(Y<4)-(Y-4)*(Y>4))STRI.-(Y+1)*(Y<4)-(13-Y)*(Y>3),"*"):F.X=0TO8:S=40*Y+X:T=S-2*X:P=B?S:B?(16+T)=P:B?(640-T)=P:B?(656-S)=P:N.,:P.TAB(1,16)

(Owlet emulator link)

It relies on:

  • The BBC Micro’s MODE 7 being character mapped
  • Drawing the top left corner of the star using spaces and asterisks controlled by logical functions (Y>4 is a number, after all)
  • Mirroring that quarter star in X and Y directions using BBC BASIC’s rather clever ? indirection operator, which can be PEEK or POKE, depending on how you use it.

So, long and difficult to follow. Not great.

I was really impressed by John Kennedy’s AppleSoft BASIC entry. It’s not the shortest, but it’s remarkably un-obfuscated:

10  TEXT : HOME : VTAB 21
20  COLOR= 10
30  FOR I = 0 TO 4
40  HLIN I,16 -I AT I *2 +8: VLIN I *2,32 -I *2 AT I +4
50  HLIN I,16 -I AT 24 -I *2: VLIN I *2,32 -I *2 AT 12 -I
60  NEXT 
1 Like

Yeah, I thought DIM A(10) would give 11 entries. But, on the IBM 5110’s BASIC (borrowed from the IBM System/3, so I’m not sure its prior lineage from that) — attempting to access that 0th index gives ERROR 701 (Subscript not in range of specified dimensions (too large or negative))…

DIM A(10)

A(0)=5

ERROR 701

Of course, setting A(10) is fine.

I’m still trying to see which of the submitted BASIC solutions can be adapted back to the IBM 5110 (with minimal changes) - just going by what I can extract from the video (were all the submissions archived somewhere? I haven’t got on Discord yet)

When I did my own BASIC, I looked at what I could find as a “standard” - all BASICs I had access to did the 0…N thing, so DIM A(10) gives elemens 0 through 10 inclusive.

Just like BCPL - A := GETVEC (10) allocates 11 words…

-Gordon

1 Like

I think FORTRAN started arrays at 1. Indexeing starting at 0 made sense in B,
and that curse carried on into C. BCPL did the right thing, 0 or 1 for indexing.
How about BASIC on the IBM 1130? JOSS perhaps? TECO for the Insane.

AWK has 1-based arrays, as well.
(I guess, this now suuficiently retro to be worth a mention here. If I’m not mistaken, Perl has a config-variable to mimic this behavior for compatibilty.)

Didn’t quite get the star correct though.

1 Like

Yes - good to see the Kenbak-1 entry. A fully functioning cpu, unfortunately constrained to just 256 bytes of memory.

IIRC John Blankenbacker is still alive.

For those not in the know:

ISC (also “ISB”, “INS”): INC operand + SBC operand
ISC zeropage (as used in the program) … $E7 (2 bytes, 5 cycles)

Compare: https://masswerk.at/6502/6502_instruction_set.html#ISC

And I can see small issues with the 4th row…
For those eager to investigate, here’s the assembled code:

1000: A6 B3 A0 09 20 0C E5 A0
1008: 11 A9 2A 20 D2 FF 91 D1
1010: 88 C4 B3 D0 F6 E4 B3 D0
1018: E7 A9 10 38 E7 B3 AA 10
1020: E1 60

As a BASIC program:

100 GOSUB 102:SYS 4096:END
101 REM BYTE LOADER
102 FOR A=4096 TO 4129:READ B:POKE A,B:NEXT
103 RETURN
104 DATA 166,179,160,9,32,12,229,160
105 DATA 17,169,42,32,210,255,145,209
106 DATA 136,196,179,208,246,228,179,208
107 DATA 231,169,16,56,231,179,170,16
108 DATA 225,96
1 Like

Awk does not have numerically indexed arrays, it has only associative arrays. Line numbers and field numbers in awk are, however, one-based, the zeroth field is the un-split line, and the zeroth match index is the complete matched string (rather than the first capture group).

You are certainly right, but I faintly remember setting “$[ = 1”, when porting some AWK script to Perl. But this was 20+ years ago… (Could have been for some string related functions, which were quite FORTRAN-like in AWK.)

Ouch, that’s obvious once you look for it! To my embarrassment, my Basic entry didn’t quite get the star right either - perhaps it never did, or perhaps I submitted not quite the right version. In my case, it’s a three-character fix and doesn’t affect the length. I think the organiser had a great number of entries to sift through, and still produced the video admirably rapidly.