VCCC 2025: Snowflake
This is my entry for this year’s Vintage Computing Christmas Challenge (VC³) by Logiker. The challenge: Recreate this snowflake made of asterisks on the platform of your choice, make your code as small as possible, and have as much fun as possible doing it. :)
Previous VC³ fun challenges I participated in: A present box in 2024, and a diamond pattern in 2023.
Prototyping
After the first shock (“This shape is way more complex than last year!”), I quickly got sidetracked by all the symmetries and built a JavaScript prototype around that. The idea: Mirror and transform all the points and only include so many explicit coordinate pairs.
For the Amiga version, I glanced at the BCPL
global vector
for a bit, looking for some output function we can call without much overhead.
Maybe we can use writef, feed it the coordinates, and print everything
with row/column-based escape codes?
// Print a "*" at row y, column x
// Possible ANSI escape sequences:
// <CSI>y;xH
// <CSI>y;xf
writef("\x9b%d;%dH*", y, x)
Turns out working with this particular BCPL function is horrible and I quickly lost my patience. The string constant needs to be longword-aligned, you have to pass it as a BCPL pointer (i. e. shifted to the right by 2), it isn’t null-terminated, but has its length in the first byte, and then I only had text output under Kickstart 2+. No fun!
So I ditched writef and friends and went for
an alert requester again (like last year). Here, we can pass
the coordinates as bytes by building a special alert text
structure:
dc.w 320 ; x coordinate (2 bytes) dc.b 128 ; y coordinate (1 byte) dc.b '*',0 ; text, null-terminated dc.b 1 ; continuation byte not 0 = more text chunks to come dc.w 328 ; next star at 328, 136 dc.b 136 dc.b '*',0 dc.b 1 ... dc.w 384 ; final star at 384, 192 dc.b 192 dc.b '*',0 dc.b 0 ; continuation byte is 0 = end of alert
My entry
After some evenings of staring at the code, preparing everything for a release, and then finding another way to save two bytes, I ended up with a solution that takes up 84 bytes of code and data:
move.l 368(a2),a6 ; intuition base
clr.l -(a7) ; alert text end marker
lea .coords(pc),a1
moveq #8*8,d4 ; diagonal loop initialization
.loop subq.b #8,d4 ; loop until d4 < 0
move.b d4,d0 ; and set y := x
bge.b .star
move.b -(a1),d4 ; otherwise, read x,y from coords
move.b -(a1),d0
blt.b .done ; negative value marks the end
.star move.w d0,d6 ; x working copy
move.w d4,d7 ; y working copy
add.l (a2),d6 ; more-or-less center x, (a2) = 150
move.l #$802a0001,-(a7); y center = 0x80, star = 0x2a,
; string terminator, continuation = 1
add.b d7,(a7) ; add y.b to 0x80
move.w d6,-(a7) ; write x.w
neg.w d0 ; mirror: x := -x
blt.b .star
neg.w d4 ; mirror: y := -y
blt.b .star
exg d0,d4 ; mirror: swap x, y
neg.w d1 ; use d1 negation to loop twice
blt.b .star
clr.w d0 ; transform: x := 0
neg.w d5 ; use d5 negation to loop twice
blt.b .star
bra.b .loop
.done move.l d0,d1 ; d1 = height (d0 is 255 here)
move.l a7,a0 ; a0 = stack = alert message
jmp -90(a6) ; DisplayAlert
; jmp -90(a6) is 0xffa6 in binary,
; and 0xff is used as end marker
dc.b 9*8,0*8
dc.b 4*8,1*8
dc.b 5*8,2*8
dc.b 1*8,7*8
dc.b 2*8,8*8
dc.b 6*8,5*8
dc.b 7*8,5*8
.coords
The code takes two nasty shortcuts:
- The alert message is constructed on the stack (a7), but the stack pointer isn’t restored on exit. In other words, if you click to dismiss the alert, you will get a real guru meditation…
- The last bytes of the alert message need to be five zero bytes to cleanly
terminate the list of message chunks:
; last message chunk generated by .star dc.w 320 dc.b 128,'*',0,1 ; final message chunk dc.w 0 ; dummy x dc.b 0,0,0 ; dummy y, empty string, continuation = 0
Butclr.l -(a7)only clears four bytes – we quietly assume the top of the stack contains a memory address like0x00ff492ewhen we get executed, where the top byte is already zero.
Other strategies
At 84 bytes, my entry lands on a 55th (-ish) place overall, if I counted correctly. The shortest entry is a ZX Spectrum program with only 33 bytes. And, much to my delight, there were four other Amiga entries this year!
- A text-based 94-byte snowflake by Saturnus the Invincible of SCA (using
writeoutputfrom the BCPL global vector) - A text-based 114-byte snowflake by DrSnuggles (using
wrchfrom the BCPL vector for output; see also my 2023 entry) - A cool bootblock with 290 bytes also by DrSnuggles, working with a custom copperlist and graphics library functions to render the stars (it seems fast ram should be disabled for this one)
- A text-based snowflake by Nicalejo written in E, with the source code taking up 209 bytes.
Many of the other entries don’t bother with coordinates and encode a quadrant of the snowflake as a bit pattern instead:
; snowflake data from STI’s entry dc.b %00000000 ; ******** dc.b %01101101 ; * * * dc.b %10110110 ; * * * dc.b %11011111 ; * dc.b %01101111 ; * * dc.b %10110001 ; * *** dc.b %11110011 ; ** dc.b %01110101 ; * * * dc.b %10111111 ; *
Interesting! The code around those solutions looks more elegant, too – maybe I was too focused on mirror coordinates early on?
Improvements
Unused register
When writing this post, I noticed a very obvious optimization in the code: I needlessly put the y coordinate in an extra register. We can rewrite this to:
...
.star move.w d0,d6
move.w d4,d7
add.l (a2),d6
move.l #$802a0001,-(a7)
add.b d7,(a7)
add.b d4,(a7)
...
This brings down the code+data size to 82 bytes. If there is a post challenge this year, this might be a version I can upload – unless there’s something else I’m missing. The fun never ends! :)
Keep the stack
In STI’s snowflake code, this line caught my attention:
; see: sca-flake.asm in STI’s entry _main: move.l d2,a0 ; there's 380 free bytes for us here
So, d2 is initially pointing to an area of free space? Interesting! As a little experiment, I changed the target buffer from a7 to a0:
move.l d2,a0 ; d2 points to an empty-ish area
clr.l -(a7) ; no end-marker bytes needed
...
.star
...
move.l #$802a0001,-(a7)-(a0)
add.b d4,(a7)(a0)
move.w d6,-(a7)-(a0)
...
move.l a7,a0 ; a0 already set
jmp -90(a6) ; DisplayAlert
This is still a bit hacky – I take d2 and fill the memory region backwards, while the usable memory is after the address in d2. This will usually overwrite the mousepointer, but it will keep the stack intact (no crash when exiting) – and the total code size is down to 80 bytes!
Or we could use this information to make a better-behaving program: We fill the buffer starting from d2 upwards, not downwards, keep the overall code size, but cleanly exit to the system. That is, as long as we only write 380 bytes or less, I’d have to measure that…


