(work in progress!)
The examples below are actually for the IBM 5110. The IBM 5100 is largely compatible (same PALM opcodes), but does have different keyboard scan codes and different character codes for the display.
In December 2022, a “Christmas Challenge” was hosted – open to all platforms. Here are some example entries, as a simple exercise to get oriented with using this system.
Christmas Star BASIC
This approach decomposes the STAR image into a set of 8 commonly re-used patterns. It is sort of a form of “RLE compression.” The full STAR image is 17×17 (289 bytes) whereas the compressed version is 5×17 (85 bytes, with the patterns consuming an additional 8×4=32 bytes, which is still altogether about a 60% compression).
190 REM PREPARED FOR IBM 5100 BASIC (1-BASED ARRAY INDEX) 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
This version is identical to the above but is formatted to be more concise. For example, the “REM” comments removed, and more DATA values are placed on the same line number. On the IBM 5110, line numbers are limited to 64 characters (they can’t wrap past the edge of the screen). To make this slightly shorter, the use of C$ could be removed (as it is used only to center the star for a better presentation but was not a strict requirement).
21 DIM A$4(8),B$1(2),C$23(1) 24 READ A$(1),A$(2),A$(3),A$(4),A$(5),A$(6),A$(7),A$(8) 25 READ B$(1),B$(2),C$(1),N 27 FOR I = 1 TO N STEP 5 28 READ A1,A2,B1,A4,A5 29 PRINT C$(1)||A$(A1)||A$(A2)||B$(B1)||A$(A4)||A$(A5) 30 NEXT I 41 DATA ' ','****',' ***',' **',' *','* ' 47 DATA '** ','*** ',' ','*','',85 67 DATA 1,6,1,5,1,1,7,1,4,1,1,8,1,3,1,1,2,1,2,1,2,2 71 DATA 2,2,2,3,2,2,2,8,4,2,2,2,7,5,2,2,2,6,1,2,2,2 76 DATA 1,5,2,2,2,6,4,2,2,2,7,3,2,2,2,8,2,2,2,2,2,1 80 DATA 2,1,2,1,1,8,1,3,1,1,7,1,4,1,1,6,1,5,1
An algorithm approach:
The following is an IBM 5110 BASIC compatible solution provided by “Peter De Wachter” (who submitted the APL solution for VCCC2022).
010 DATA 4,3,2,1,-4,-3,-2,-1,0,-1,-2,-3,-4,1,2,3,4 020 DIM V(17) 030 MAT READ V 040 FOR I=1 TO 17 050 FOR J=1 TO 17 060 IF V(I)<=-V(J) THEN 90 070 PRINT ' '; 080 GOTO 100 090 PRINT '*'; 100 NEXT J 110 PRINT 120 NEXT I
It is a valid 17×17 solution. Typing it into the online Norbert 5110 emulator may requiring using the mouse to click the virtual keyboard (modern keyboards might not map ;, -, and (, ) as expected).
Here is my attempt at describing this approach: think of a scale of peaks (higher values) and valley’s (lower values), which we can call “weight.” Then define a filter that indicates how much of this scale is applicable (a kind of “water-level” on how high a peak need to be in order to get selected or revealed). Using a dynamic filter over time, our desired shape can be revealed (and we can use rows on a screen to represent that “over time” dimension).
As mentioned earlier, the problem is very symmetric. As depicted below, the top left quadrant is one quarter of the problem. But look within that quadrant, it is again symmetric within itself (the “sub-quadrants” are themselves inverses of each other). That may be a clue to help decide how to set the weights (where you see they are also a sequence of inverses).
With this in mind, one can experiment with different weights and filters to experiment on what new patterns can be revealed.
Christmas Star PALM Assembly
I had been studying for a way to detect keystrokes on the IBM 5110 using PALM, since it is an essential need towards making an interactive game. Then, I happened to come across the VCCC2022 notice just a few days before its end date. So, I adapted Corti’s approach he used for “building walls” in the Ball Bounce demo to draw the star and added what I had hoped would be a way to detect if SPACEBAR has been pressed. And, it worked! I had noticed that whenever a key is pressed, its corresponding scan code ends up in some L3 registers.
But, since the CRT is 64×16, that means the full 17×17 star can’t be drawn. So, this ends up as a casual “wild” submission with a 17×16 star. Still, it was a fun exercise with a few lessons learned: MOVE applies both HI and LO (16-bit), you can’t BRAch more than 255 addresses (so you have to buffer R0 (Program Counter) to jump back later, and confirmation that Alfred Arnold Macro Assembler works splendidly for producing PALM machine code.
This is by no means an “optimal” approach for this platform. As I learn more about PALM, I hope to revisit this challenge and present something that is more optimal.
; For Vintage Computing Christmas Challenge 2022 ; written by voidstar 12/20/2022 ; https://voidstar.blog/ ; ; Assembled using Alfred Arnold Macro Assembler 1.42 Beta [Bld 231] ; (Alfred added PALM support earlier this year by my request) ; http://john.ccac.rwth-aachen.de:8000/as/ ; ; asw -i ..\include program_star.asm ; ; remove header and convert .p to a raw binary: ; p2bin program_star.p ; ; demo using VEMU 5110 emulator: ; emu5110_release_x64 a -1 command_input_ASM_GO2000.txt program_star.bin ; NOTE: a (APL) or b (BASIC) will work at 0x2000 offset. If using 0x0B00, sometimes BASIC ; won't work (it writes 0x0000 to 0xB2E0, depending on when and how long you let it run). ; cpu IBM5110 include "ebcdic.inc" intsyntax +$hex,-x'hex' ; < support $-style hex instead of IBM 0x style codepage cp037 ; < activate the EBCDIC mapping of chars ; BEGIN: CLEAR SCREEN LWI R14, #$0200 ; set start of CRT screen address LWI R9, #$4040 ; let R9 be the space/blank screen character (for IBM 5110 use $4040, for IBM 5100 use $0000) cs_1: MOVE (R14)+, R9 ; move all of R9 to address of R14, then increment R14+2 LBI R1, #$06 ; load Lo(R1) back to #$06 (can't guarantee R1 wasn't modified by interrupt) SBSH R14, R1 ; skip if Hi(R14) == R1 (i.e. have we reached #$0600, which is one past the memory-mapped screen addresses) BRA cs_1 ; if did not skip, then keep clearing next two characters ; BEGIN: MAKE STAR ; prepare some registers to hold the screen character values that will be drawn LWI R7, #$5C5C ; draw "two" **'s at a time LWI R8, #$5C40 ; left STAR then space/blank LWI R9, #$405C ; space/blank then RIGHT star MOVE R11, R0 ; R11 = R0 (store starting addressing) ; do vertical lines first ; vertical 1 bw_v1: LWI R5, #$0200+(1*64)+29 ; set R5 to address of row 1, col 29 (0-based index) LBI R6, #14 ; 14 vertical rows to draw bw_v1a: MOVE (R5), R7 ; draw at whatever screen address that R5 is pointing at SUB R6, #1 ; decrement "number of rows drawn" counter SNZ R6 ; skip if not-zero BRA bw_v2 ; go to the next block (if R6 is zero) ADD R5, #64 ; increment screen address to the next row address (64 characters per row) BRA bw_v1a ; continue drawing vertically at the next row ; vertical 2 bw_v2: LWI R5, #$0200+(3*64)+31 ; set R5 to address of row 3, col 31 (0-based index) LBI R6, #10 ; 10 vertical rows to draw bw_v2a: MOVE (R5), R7 SUB R6, #1 SNZ R6 ; skip if not-zero BRA bw_v3 ; go to the next block ADD R5, #64 ; go to next row (address) BRA bw_v2a ; vertical 3 bw_v3: LWI R5, #$0200+(4*64)+33 LBI R6, #8 bw_v3a: MOVE (R5), R7 SUB R6, #1 SNZ R6 ; skip if not-zero BRA bw_v4 ; go to the next block ADD R5, #64 ; go to next row (address) BRA bw_v3a ; vertical 4 bw_v4: LWI R5, #$0200+(2*64)+35 LBI R6, #12 bw_v4a: MOVE (R5), R7 SUB R6, #1 SNZ R6 ; skip if not-zero BRA bw_v5 ; go to the next block ADD R5, #64 ; go to next row (address) BRA bw_v4a ; vertical 5 bw_v5: LWI R5, #$0200+(4*64)+37 LBI R6, #8 bw_v5a: MOVE (R5), R7 SUB R6, #1 SNZ R6 ; skip if not-zero BRA bw_h1 ; go to the next block ADD R5, #64 ; go to next row (address) BRA bw_v5a ; now draw the horizontals ; the vertical loops above has drawn the majority of the star. ; the following just adds in the few missing stars row-by-row as needed. ; (horizontals) bw_h1: ; row 0: top left tip LWI R5, #$0200+(0*64)+29 MOVE (R5), R8 ; row 0: top right tip LWI R5, #$0200+(0*64)+37 MOVE (R5), R8 ; row 1: top right LWI R5, #$0200+(1*64)+35 MOVE (R5)+, R9 ; "draw" and then increment R5 by two MOVE (R5), R8 ; "draw" the next pair ; row 2 LWI R5, #$0200+(2*64)+31 MOVE (R5), R8 LWI R5, #$0200+(2*64)+37 MOVE (R5), R8 ; row 3 LWI R5, #$0200+(3*64)+33 MOVE (R5), R9 LWI R5, #$0200+(3*64)+37 MOVE (R5), R8 ; row 4: left side LWI R5, #$0200+(4*64)+25 MOVE (R5)+, R7 MOVE (R5), R7 ; row 4: right side LWI R5, #$0200+(4*64)+39 MOVE (R5)+, R7 MOVE (R5), R8 ; row 5: left side LWI R5, #$0200+(5*64)+25 MOVE (R5)+, R9 MOVE (R5), R7 ; row 5: right side LWI R5, #$0200+(5*64)+39 MOVE (R5), R7 ; row 6: left side LWI R5, #$0200+(6*64)+27 MOVE (R5), R7 ADD R5, #64 ; row 7 left side MOVE (R5), R9 ; row 8 left side ADD R5, #64 MOVE (R5), R9 ; row 6: right side LWI R5, #$0200+(6*64)+39 MOVE (R5), R8 ; row 7 already handled inline above ; row 8 already handled inline above ; row 9: left side LWI R5, #$0200+(9*64)+27 MOVE (R5), R7 ; row 9: right side LWI R5, #$0200+(9*64)+39 MOVE (R5), R8 ; row 10: left side LWI R5, #$0200+(10*64)+25 MOVE (R5)+, R9 MOVE (R5), R7 ; row 10: right side LWI R5, #$0200+(10*64)+39 MOVE (R5), R7 ; row 11: left side LWI R5, #$0200+(11*64)+25 MOVE (R5)+, R7 MOVE (R5), R7 ; row 11: right side LWI R5, #$0200+(11*64)+39 MOVE (R5)+, R7 MOVE (R5), R8 ; row 12 LWI R5, #$0200+(12*64)+33 MOVE (R5), R9 LWI R5, #$0200+(12*64)+37 MOVE (R5), R8 ; row 13 LWI R5, #$0200+(13*64)+31 MOVE (R5), R8 LWI R5, #$0200+(13*64)+37 MOVE (R5), R8 ; row 14: bottom right LWI R5, #$0200+(14*64)+35 MOVE (R5)+, R9 MOVE (R5), R8 ; row 15: bottom left tip LWI R5, #$0200+(15*64)+29 MOVE (R5), R8 ; row 15: bottom right tip LWI R5, #$0200+(15*64)+37 MOVE (R5), R8 ; BEGIN: MAIN LOOP LWI R13, #$39 ; space bar scancode LWI R5, #$69 ; keyboard scancode address, can also use #$6B or #$B0 poll: MOVE R12, (R5) ; let R12 = MEM[addr @ R5] SBS R13, R12 ; does R13 == R12 (key code for spacebar) BRA poll ; no (not skipped), keep polling ; increment R7, R8, R9 ; MOVE (R5), R5 ; clear key stroke (comment this out to loop continuously) ADD R7, #1 ; increment lo[R7] (does carry, but doesn't matter since we do MLH below) MLH R7, R7 ; copy, hi[R7] = lo[R7] MLH R8, R7 ; copy, hi[R8] = lo[R7] (update R8 to be whatever R7 became) R8 == "xx40" ADD R9, #1 ; increment lo[R9] (this does a carry which will "destroy" the space in hi[R9]) MLH R9, R8 MOVE R0, R11 ; restart the program HALT
TBD: How to obtain and run/use 5110VEMU for both BASIC and ASM inputs.
Addition Notes on Algorithm Approach
Printing the “Christmas Star” to the screen seems like a straight-forward and simple task. The most “brute-force” approach is to simply use 17 PRINT statements. This type of approach requires around 500 characters.
But what if you wanted to do things like rotate/spin the star? Shrink or enlarge (scale) the star? Draw different portions of the star in different colors? Rather than just a “2-D grid of characters,” one can imagine the star as a geometric object. From there, a variety of approaches can begin to formulate on how to render this shape using an algorithm approach. The specifics will depend on the available instruction set, which the VCCC2022 submissions showed a wide variety of solutions across platforms and programming languages available to those platforms.
Many clever solutions were in the 50-100 byte range, with the most optimal solutions being around 30 bytes. Because the problem is symmetric vertically and horizontally, that should suggest you only need to solve “1/4th” of the problem — once you have an approach to one corner, then that same approach should apply to the others (by applying an inverse).