Christmas (programming) Challenge (2021)

I guess PostScript counts as retro, right, 'cos no-one (except Don Lancaster, perhaps) uses it as a programming language these days. So here’s a super-fancy one that prints centre-justified on A4 paper:

%!
/ZapfDingbats 14 selectfont 297.5 519 moveto (ACEGCGKOEKQWCC) dup length
exch { 64 sub } forall 2 1 count  2 sub { -1 roll } for {dup /n exch def
gsave {(O)} repeat n array astore 0 1 index { length add } forall string
0 3 2 roll { 3 copy putinterval  length add } forall pop dup stringwidth
pop 2 div neg 0 rmoveto  show  grestore 0 -14 rmoveto }  repeat showpage

which looks approximately like:
xmastree

  • PostScript, like FORTH, uses RPN. Don’t get waylaid by for {..., it’s ...} for that matters.
  • I used the same concept of an ASCII string encoding the length of the lines. Using PostScript graphics, centre-justifying strings is easy, so I don’t need to care about the spaces.
  • There’s no facility for joining strings in the language. Here I used a horribly inefficient method of joining an array of single characters into a string. I’ll admit to nicking it from here: How to concatenate strings?
  • I’d have preferred to rely entirely on the stack instead of using any variables, but my stack-fu was weak and I had to use a temporary counter variable. You’ll see code like dup /n exch def crop up frequently in handwritten PostScript: it means “store a copy of the value at the top of the stack in the variable n
  • You’ll notice the lack of asterisks in the code. PostScript fonts don’t have a strict adherence to any one standard encoding, and Zapf Dingbats has an especially odd one. It places ✯ (U+272F, “Pinwheel Star”) in the position of Latin Capital Letter O, so the (O) in the code is just a very fancy asterisk. If I felt suitably retro, I could replace it with ('), which Zapf Dingbats understands to be ✇ (U+2707, “Tape Drive”) …
    xmastree_smol_tape
5 Likes

So, here you are, @scruss 's Perl program on punch cards:

(Sadly, just a bit too long for a single card.)
Note: Since simple EBCD for the 029 doesn’t feature { and }, these are transcribed to (* and *), and back again.

Instructions:

  • download the images
  • navigate to the Virtual Card Read-Punch
  • select “Read” and drop both images
  • activate the “RUN” button

:slight_smile:

6 Likes

“Tape Drive” is strangely similar to the Radiation Warning Symbol.

The brave turtle celebrates having defeated its vertigo:

Run …

to line :n
  rt 90 bk 4*:n
  repeat :n [
    fd 4 pd
    repeat 6 [ fd 4 bk 4 rt 60 ]
    pu fd 4
    ]
  bk 4*:n lt 90 fd 12
end
pu
make "L [ 3 3 23 17 11 5 15 11 7 3 7 5 3 1 ]
repeat 14 [ line first :L make "L bf :L ]
fd 12

… in …

…and smile with the turtle celebrating its achievement.

Spoilers

20211229-032009__starring_the_turtle

Turtle power!

5 Likes

I have the Blue book somewhere. Maybe I should hunt in abebooks for the Red book.

Bravo! It runs perfectly under ucblogo on Linux

1 Like

I couldn’t resist the perverse temptation to write a COBOL-68 version for TOPS-20.

00100   * XMASTR.CBL - Vintage Computing Christmas Challenge 2021
00200   *              Print Xmas tree centered on 79-character display
00300   *              2021 David Meyer <[email protected]> +JMJ
00400
00500   IDENTIFICATION DIVISION.
00600   PROGRAM-ID. XMASTR.
00700
00800   DATA DIVISION.
00900   WORKING-STORAGE SECTION.
01000   01 LINE-OUT.
01100      05 CHAR PIC X OCCURS 79 TIMES.
01200   01 LINE-NO PIC 99.
01300   01 TIER PIC 9.
01400   01 TIER-ROW PIC 9.
01500   01 STARS PIC 99.
01600   01 FIRST-STAR PIC 99.
01700   01 LAST-STAR PIC 99.
01800   01 POS PIC 99.
01900
02000   PROCEDURE DIVISION.
02100   MAIN.
02200       PERFORM PRINT-LINE VARYING LINE-NO FROM 0 BY 1
02300           UNTIL LINE-NO = 14.
02400       STOP RUN.
02500
02600   PRINT-LINE.
02700       IF LINE-NO > 11 MOVE 3 TO STARS
02800       ELSE PERFORM COMPUTE-STARS.
02900       COMPUTE FIRST-STAR = (79 - STARS) / 2 - 1.
03000       COMPUTE LAST-STAR = FIRST-STAR + STARS - 1.
03100       MOVE SPACES TO LINE-OUT.
03200       PERFORM SET-STAR VARYING POS FROM FIRST-STAR BY 1
03300           UNTIL POS > LAST-STAR.
03400       DISPLAY LINE-OUT.
03500
03600   COMPUTE-STARS.
03700       DIVIDE LINE-NO BY 4 GIVING TIER REMAINDER TIER-ROW.
03800       COMPUTE STARS = 2 * (1 + TIER) * (1 + TIER-ROW) - 1.
03900
04000   SET-STAR.
04100       MOVE '*' TO CHAR(POS).

2 Likes

I love all of @papa’s contributions!

Nothing in Lisp yet? This is an outrage! Here’s some Maclisp:

Screenshot from 2021-12-30 13-15-58

2 Likes

Obfuscated TECO. There is no other TECO. For added pain, I edited this in TECO. I say TECO a lot these days.
Screenshot from 2021-12-30 14-04-25

Screenshot from 2021-12-30 14-04-45

4 Likes

Bourne shell

#!/bin/sh
set -o noglob
row=0
while [ $row -lt 14 ]
do
  if [ $row -gt 11 ]
  then
    n=3
  else
    n=$((2 * (1 + $row / 4) * (1 + $row % 4) - 1))
  fi
  stars=`echo '***********************' | cut -c1-$n`
  printf '%*s\n' $(((79 - $n) / 2 + $n)) $stars
  row=$(($row+1))
done

I decided to up my game and take the challenge with TOP-20 MACRO assembly language. This is a data-driven solution, but handles the tree shape and centering by encoding them in the form of two arrays, one for the column position of the first star on each line, and another for the column position of the last star on each line (octal values).

Ways I would like to improve this:

  1. The two arrays FIRST and LAST use a 36-bit word to store each column position, which wastes a lot of memory since the highest value stored is only 51. Is there a way to compress the arrays?
  2. An algorithmic solution. This will take time since I still have a long way to go in my textbook before I get to enough arithmetic techniques to code the algorithm in MACRO.
@type xmastr.mac
        TITLE   XMASTR - Vintage Computing Christmas Challenge 2021
        SEARCH  MONSYM

COMMENT / Print Xmas tree centered on 79-character diaplay
          2021 David Meyer <[email protected]> +JMJ
        /

START:  RESET
        MOVEI   10,0                    ;current line no
LINE:   MOVEI   11,1                    ;current column
        MOVE    12,FIRST(10)            ;col. of 1st star on line
        MOVE    13,LAST(10)             ;col. of last star on line
CHAR:   CAMLE   11,12
        JRST    PSTAR
        HRROI   1,SPACE
        JRST    PRINT
PSTAR:  HRROI   1,STAR
PRINT:  PSOUT
        CAMG    11,13
        AOJA    11,CHAR
        HRROI   1,NEWLIN
        PSOUT
        CAIGE   10,15
        AOJA    10,LINE
        HALTF


FIRST:  50                              ;column of first star on line
        47
        46
        45
        47
        45
        43
        41
        46
        43
        40
        35
        47
        47
LAST:   50                              ;column of last star on line
        51
        52
        53
        51
        53
        55
        57
        52
        55
        60
        63
        51
        51

SPACE:  ASCIZ   / /
STAR:   ASCIZ   /*/
NEWLIN: ASCIZ   /
/
        END     START
@exec xmastr.mac
LINK:   Loading
[LNKXCT XMASTR execution]
                                        *
                                       ***
                                      *****
                                     *******
                                       ***
                                     *******
                                   ***********
                                 ***************
                                      *****
                                   ***********
                                *****************
                             ***********************
                                       ***
                                       ***

2 Likes

Nice! Then I kind of have to make one in ITS MIDAS:

        title xmas

a=1
b=2
c=3
p=17

tyo==1

.vector pdl(pdllen==10)

go:     move p,[-pdllen,,pdl]
        .open tyo,[.uao,,'tty]
         .lose %lssys
        movei a,1
        movei b,2
        repeat 3,[
        pushj p,section
        addi b,2
        movei a,-1(b)
]       movei a,3
        pushj p,branch
        movei a,3
        pushj p,branch
        .logout 1,

section:
        repeat 4,[
        push p,a
        pushj p,branch  
        pop p,a
        add a,b
]       popj p,

branch: push p,a
        ash a,-1
        subi a,12.
        movns a
        move c,[440700,,spaces]
        .call [ setz ? sixbit /siot/ ? movei tyo ? c ? setz a ]
         .lose %lssys
        pop p,a
        move c,[440700,,stars]
        .call [ setz ? sixbit /siot/ ? movei tyo ? c ? setz a ]
         .lose %lssys
        .iot tyo,[^M]
        .iot tyo,[^J]
        popj p,

spaces: ascii /                                  /
stars:  ascii /**********************************/

end go
1 Like

I did part of Advent of Code 2021 in Racket, which is a dialect of Scheme (the 7.x version of Racket that I am using is built on top of Chez Scheme, although 8.0 and beyond use a different implementation), but I did not solve this challenge there. I would do so, but since your implementation can be very quickly translated, it seems moot now. :wink:

I note that your version runs in Common Lisp under SBCL or CMUCL with only trivial changes to the // and '/ operators:

(defun branch (n)
  (loop for i from 0 to (- 12 (/ n 2)) do (princ #\ ))
  (loop for i from 1 to n do (princ #\*))
  (terpri))

The rest of the code is unchanged.

1 Like

There are a whole bunch of solutions to this problem on SDF on bboard under the RETRO group. I see some of you are over there!

2 Likes

Yes, Common Lisp got a lot from Lisp Machine Lisp, and some features were backported from the latter into Maclisp. So it’s not very hard to write code that can be shared between all those dialects.

2 Likes

I’d like to suggest a more factored Forth solution:

: stars ( u -- ) 0 do ." *" loop ;
: branch ( u -- u ) 12 over 2/ - spaces  dup stars  cr ;
: section ( u1 u2 -- u1 ) 4 0 do  branch  over +  loop drop ;
: tree ( u1 u2 -- u3 u4 ) 3 0 do section  1+ dup 1+ swap loop ;
: root ( -- ) 3 branch branch drop ;
: xmas ( -- ) 2 1 tree 2drop root ;

3 Likes

My APL solution in Dyalog APL: 50 characters

↑{(' '⍴⍨20-⍵),'*'⍴⍨¯1+2×⍵}¨(↑,/1 2 3×⊂1 2 3 4),3⍴2
2 Likes

I also implemented it in KAP, which is my own APL-based programming language. It lacks rendering of 2-dimensional character arrays as regular text, so one needs a call to io:println which makes in longer than the APL solution. It’s 61 characters.

{io:println ((20-⍵)⍴↑" "),@*⍴⍨¯1+2×⍵}¨(⊃,/1 2 3×⊂1 2 3 4),3⍴2

Link to the language in case anyone is interested: GitHub - lokedhs/array: Simple array language written in kotlin

2 Likes

Welcome, and nice work! Impressive to have designed and implemented your own APL-ish language.

1 Like

I have also implemented this in COBOL for MVJ 3.8j. This is a free version of MVS that was released in the 70’s and can be run on the Hercules emulator. The source includes the necessary JCL to run it on TK4 (a distribution of MVS that includes all the software you need. http://wotho.ethz.ch/tk4-/)

//TREE     JOB (COBOL),
//             'TREE DISPLAY',
//             CLASS=A,
//             MSGCLASS=A,
//             REGION=8M,TIME=1440,
//             MSGLEVEL=(1,1)
//TREE     EXEC COBUCG,
//         PARM.COB='FLAGW,LOAD,SUPMAP,SIZE=2048K,BUF=1024K'
//COB.SYSPUNCH DD DUMMY
//COB.SYSIN    DD *
       IDENTIFICATION DIVISION.
       PROGRAM-ID.  'TREE'.
      *
       ENVIRONMENT DIVISION.
      *
       CONFIGURATION SECTION.
       SOURCE-COMPUTER.  IBM-360.
       OBJECT-COMPUTER.  IBM-360.
      *
       DATA DIVISION.
      *
       WORKING-STORAGE SECTION.
           77 I1 PIC 99.
           77 I2 PIC 99.
           77 OUTPUT-INDEX PIC 99.
           77 OUTS PIC X.
           77 N1 PIC 99.
           77 N2 PIC 99.
           77 DRAW-LINE-START PIC 99.
           77 DRAW-LINE-SIZE PIC 99.
           77 EMPTY-STRING PIC X(40) VALUE ' '.
           01 OUT-STRING.
            02 OUT-VALUES PIC X OCCURS 40.
           01 OUT-LINE REDEFINES OUT-STRING.
            02 RESULT PIC X(40).
      *
       PROCEDURE DIVISION.
      *
       MAIN-PART.
           MOVE 1 TO I1.
           PERFORM DRAW-BLOCK1 UNTIL I1 IS GREATER THAN 3.
      *
           MOVE 1 TO I1.
           MOVE 18 TO DRAW-LINE-START.
           MOVE 3 TO DRAW-LINE-SIZE.
           PERFORM DRAW-MULTI-LINE UNTIL I1 IS GREATER THAN 3.
           STOP RUN.
       DRAW-BLOCK1.
           MOVE 1 TO I2.
           PERFORM DRAW-BLOCK2 UNTIL I2 IS GREATER THAN 4.
           ADD 1 TO I1.
       DRAW-BLOCK2.
           MULTIPLY I1 BY I2 GIVING N2.
           SUBTRACT N2 FROM 20 GIVING DRAW-LINE-START.
      *
           MULTIPLY N2 BY 2 GIVING N1.
           SUBTRACT 1 FROM N1 GIVING DRAW-LINE-SIZE.
      *
           PERFORM DRAW-SINGLE-LINE.
           ADD 1 TO I2.
           ADD 1 TO I2.
           SUBTRACT 1 FROM I2.
       DRAW-MULTI-LINE.
          PERFORM DRAW-SINGLE-LINE.
          ADD 1 TO I1.
       DRAW-SINGLE-LINE.
           MOVE EMPTY-STRING TO RESULT.
           MOVE 1 TO OUTPUT-INDEX.
           MOVE ' ' TO OUTS.
           PERFORM DRAW-CHAR UNTIL
                OUTPUT-INDEX IS GREATER THAN DRAW-LINE-START.
           MOVE '*' TO OUTS.
           ADD DRAW-LINE-START TO DRAW-LINE-SIZE GIVING N1.
           PERFORM DRAW-CHAR UNTIL OUTPUT-INDEX IS GREATER THAN N1.
           DISPLAY RESULT.
       DRAW-CHAR.
           MOVE OUTS TO OUT-VALUES (OUTPUT-INDEX).
           ADD 1 TO OUTPUT-INDEX.
/*
//COB.SYSLIB   DD DSNAME=SYS1.COBLIB,DISP=SHR
//GO.SYSOUT   DD SYSOUT=*,DCB=(RECFM=FBA,LRECL=161,BLKSIZE=16100)
/*
//
2 Likes