During the Vintage Computing Christmas Challenge 2021 the goal was to create the shape of a given Christmas tree with as few bytes as possible. All platforms and languages were allowed.
The tree should be centered and look exactly like shown bellow.
* *** ***** ******* *** ******* *********** *************** ***** *********** ***************** *********************** *** ***
There were mainly two kinds of approaches:
- calculate stars using a formula
- save amount of stars within data
Do to the trunk of the tree, the second type was more efficient.
Shortest DOS version
The shortest DOS version with 44 bytes came from Hellmood:
org 100h ; define start of code for data access db 80 ; screen width, located at [SI], also "push ax" mov bx,d ; pointer to data for XLAT mov cx,1120 ; 14 lines with 80 chars each X: mov ax,cx ; get current sequence position div byte [si] ; transform to [X,Y] in AL, AH xlat ; get tree width for current line number sub ah,40 ; center tree in the middle jnc F ; - neg ah ; - F: sub ah,al ; inside or outside tree? salc ; set AL depending on carry flag imul ax,byte -42 ; transform into STAR or nothing int 29h ; write current char to screen loop X ; repeat unil all chars are plotter ret ; return to prompt (JMP to former AX=0x0000) d:db 2,2,12,9,6,3,8,6,4,2,4,3,2,1
Winning entry for C64
The shortest entry overall was for the C64 and was submitted by Serato with only 37 bytes:
!cpu 6510 ; enable unintended opcodes in ACME assembler chrout = $FFD2 crlf = $AAD7 pntr = $D3 width = 40 ;; BASIC header *= $0801 !word +, 10 !byte $9e !text "2092" !byte 0 + !word 0 table !byte -1 !byte -3 !byte -5 !byte -7 !byte -3 !byte -7 !byte -11 !byte -15 !byte -5 !byte -11 !byte -17 !byte -23 !byte -3 !byte -3 code -- lsr sbc #(128-width/2) sta pntr lda #'*' - jsr chrout inx bne - jsr crlf iny entry lax table,y bmi -- rts end
Serato's entry was provided with the following comments:
This entry to the Christmas Tree challenge saves space in six ways: 1. Using the c64 kernal routines to output individual characters and carriage return/linefeed. These routines save/restore X/Y allowing the registers to be used as loop counters. 2. Writing to the PNTR kernal variable to indent each row. 3. Using the unintended LAX opcode to simultaneously load A and X with the table values. 4. Storing the row widths as 2's complement negative values, to simplify the indent calculation to divide by two (LSR) and subtract (SBC). The inner loop simply counts back up to zero for rendering the correct width. 5. BASIC loads the Y register from address $30d. This defaults to 0 on startup, so Y does not need to be initialised. 6. Placing the table before the code in memory, the MSB of the table entries do not match the MSB of the first opcode (LSR), allowing the outer loop exit condition to be set implicitly in the Negative flag as the table values are read. These optimisations result in 37 bytes of machine code and data. Two further optimisations are possible each of which save 1 byte, but break the rules of this challenge: a - LSR and SBC can be replaced with the unintended opcode ARR, if you are willing to "centre" the tree on a virtual screen width of 32 chars. b - The BASIC interpreter leaves the line number in the zero page word at $39, so by setting the line number in the basic header to the address of the table, "LAX ($39),y" can be used for the table access.
Optimized post-deadline versions
36 bytes version for C64
The C64 version was shortened by altering the calculation so the default register values on start cause the first line to be drawn, shortening the table by one entry.
!cpu 6510 ; enable unintended opcodes in ACME assembler chrout = $FFD2 crlf = $AAD7 pntr = $D3 width = 40 ;; BASIC header *= $0801 !word +, 10 !byte $9e !text "2074" !byte 0 + !word 0 table ; !byte -1 !byte -3 !byte -5 !byte -7 !byte -3 !byte -7 !byte -11 !byte -15 !byte -5 !byte -11 !byte -17 !byte -23 !byte -3 !byte -3 code -- adc #width-1 lsr sta pntr lda #'*' - jsr chrout inx bmi - jsr crlf iny lax table-1,y bmi -- rts end
32 bytes version for ZX Spectrum
After the deadline following entry for the ZX Spectrum with only 35 bytes was submitted by reddie and optimized by char to 32 bytes:
# Christmas Tree post-event build, ZX Spectrum version # tnx to Manwe for info about event, better late than never =) # first version - 35 bytes - (c) reddie, 2021.12.25 # optimized to 32 bytes by char, 2021.12.26 - huge thanks from me! # full final object len = 32 bytes (from label "data" to label "end") # data array len = 14 bytes; code len = 18 bytes, 13 Z80 instructions # execute from Basic only! entry point = 61455, or just run TRD image # and then run "ctree32b" Basic-program, it will load & start the code # TRD file also contains source text in ALASM format data org -16*256+1 defb -5,-5,-25,-19,-13,-7,-17,-13,-9,-5,-9,-7,-5,-3 start dec c jr z,$ ;stop when finished ld a,23 ;set coords via ROM procedure rst 16 ld a,(bc) ;line len in negative format ld e,a rra sub b ;centering print rst 16 ld a,"*" inc e jr nz,print jr start end
35 bytes version for Plus/4
The following optimized version for the Commodore Plus/4 was created after the deadline:
; Plus/4 Christmas Tree by GeirS ; Assemble using 64tass BtmRow = $0c00+$28*$18 ; Address of bottom screen row Screen = BtmRow-$6c ; Adjustment for ($100-$28)/2 ScrollUp = $da89 ; KERNAL routine to scroll screen upwards VarTab = $2d ; Pointer: Start of BASIC variables * = $1001 ; Load address ; BASIC stub .word (NextLine),0 .null $9e,format("%4d", Start) NextLine .word 0 ; Code and data (35 bytes total) RowLoop lsr ; Calculate screen offset tay lda #$2a ; Asterisk character CharLoop sta Screen,y ; Put char in bottom screen row iny inx bne CharLoop ; Loop for all chars Start jsr ScrollUp ; Scroll the screen upwards (x=0 after) dec VarTab ; Adjust address of char count to use next lax (VarTab,x) ; Load char count into both a and x bmi RowLoop ; Loop while not done rts ; Asterisk counts (negative values to optimize the code) .char -3,-3,-23,-17,-11,-5,-15,-11,-7,-3,-7,-5,-3,-1