BASIC Contest (Blue Cross, size-optimized)

A small BASIC Contest was held from 13.8.2019 until 18.8.2019. The contest originated from an assembler contest by Nurpax (see Twitter and github) and was adapted for BASIC.

A PRINT-only version "compo" was held afterwards until the 24.8.2019.

The Challenge

  • Make a BASIC programm that creates the image above 1:1 
    • Exactly the same shape, colors, etc.
      • No additional characters, cursor, etc.
    • Stable (meaning, the programm does not finish afterwards)
  • Use BASIC but do not include own assembler code
    • PEEK and POKE are allowed
    • Even SYS is allowed (though not prefered)
  • Make the size of the PRG file as small as possible
  • Don't manipulate the file (e.g. truncating the last bytes)
  • The best hand-in will be used per person

Additional challenge -> "PRINT-only" version

Same rules as above, but additionaly:

  • No PEEK/POKE (except 53280,53281 and 198)
  • No SYS or similar
  • No selfmodifying code
  • No presentation of the code or even just of the size before the deadline
  • Only md5 before deadline wanted

Submit

In order not to reveal the code, first only a checksum (md5sum) plus the program size in bytes had to be anonunced. The code was revealed after the deadline only. (If you had a bas-file for CBM prg Studio, it was welcome too.)

Submissions  were to be made here: Twitter or Forum64.de.

Results

Bytes* Programmer
68 Atomcode (f64)
75 TNT/BF (aka Corporal Jonlan)
76 Logiker
79 YPS (f64)
80 wizball6502 (f64)
? Holy Moses/Role (f64)

 * regular size of file in bytes without truncating (which is CBM prg Studio + 4)

Results with Code

#1 Atomcode

2019?"♥":fOX=2TO81:pO54238+X,.:pO982+aB(26-INT(5/8*X))*40+X,160:nE:wA.,.

#2 ´TNT/BF (=CorporalJonlan)

0 print"{clr}":forx=2to41:y=982+int(x/5*{pi})*40:pokey+x,160:pokey+43-x,160:poke53278+x,0:next:cont

#3 Logiker

0pO53280,0:pO53281,0:?"{clear}":fOi=-39.5to40:pO4^5+i+abs(int(5/8*i)*40),160:nE:wait1,0

#4 YPS

1 ?"{clear}":fOx=.5to40:pO53280+x,0:a=4^5+int(x*5/8)*40:pOa+x,160:pOa+40-x,160:nE:wA1,0

#5 Wizball6502

0 print"{clear}"
1 fori=0to41:poke53280+i,0:poke982+o+int(i*.625)*40+i*(1oro>0),160:next:o=43:goto1

PRINT-only version Code

The PRINT-only version was mainly for fun. Here is what the participants delivered:

TNT/BF (aka CorporalJonlan) (86 Bytes)

0 forc=17to145step128:fori=7to45:poke53268+(iAND1),0:print"{reverse on}{space}"left$(chr$(c),91and2^(iand7));:next:print"{left}{insert}{space}{up}{right}";:next:cont 

BIF (104 Bytes)

0 print"[clr,blau]":poke242,.:a$(1)="[links] [links]":a$(.)="[revers,links,links]  [links,links]":a$="[runter]
1 fori=.to29step1.2:poke53280+i,.:printa$(iand1)a$;:next:a$="[hoch]":goto1
 

Logiker (109 Bytes)

10 poke53280,0:poke53281,0:?"{clear}{reverse on}";:fori=0to3:x$="{down}  {down} {down}  {down} {down} ":?x$" ";:next:?x$"{left}{148} {up}{right}";:fori=0to4:?"  {up} {up}  {up} {up}  {up}";:next:cont 

Lessons Learned (about organisation of the contest)

  1. Goal must be precise
    1. About Coding:
      1. Is own assembler code allowed?
      2. Are PEEK/POKE allowed?
      3. Is SYS allowed?
      4. Is self-modifying code allowed?
      5. May the code do more than asked for? E.g. additional POKEs, pixels, etc.?
    2. About Final State:
      1. Must the goal be reached permanently or just for a short time?
      2. Must the computer stay in a legit stay (e.g. leave the program without need of reset)?
    3. General
      1. A picture can really help.
      2. A general goal might hard to be verified.
  2. Should the current sizes be revealed?
    1. Might be demotivating for participants if they think that the current top rank is not reachable.
    2. Might be motivating for some people to do more as well.
  3. Time
    1. There should be enough time.
  4. Place
    1. It's good to have several places so more people know about it.
    2. It's good to have one central place that collects the main information.
  5. Actually measuring the file size might cause troubles
    1. "CBM prg Studio" shows a smaller size then the resulting file.
    2. Is a manipulation of the standard file format allowed (e.g. cutting off the last 3 bytes
  6. Who cares about file size?
    1. The C64 uses Blocks!
    2. PCs have TBs of memory.

Appendix

Information from Atomcode (Forum64) #1

2019?"♥":fOX=2TO81:pO54238+X,.:pO982+aB(26-INT(5/8*X))*40+X,160:nE:wA.,.

• Dem Algorithmus liegt die Überlegung zugrunde, beide Diagonalen zusammen als nur eine zu plottende Funktion zu behandeln, die 80 Zeichen breit ist, 26 Zeichen hoch und einer Dreieckkurve entspricht, und zwar von 0/24 nach 39/0 aufsteigend und dann von 40/-1 nach 79/23 wieder absteigend, wobei das Wrapping der theoretischen 80-Zeichen-Zeile auf 40 Zeichen dafür sorgt, dass der absteigende Teil um 1 weiter unten (-1 bis 23 => 0 bis 24) und 40 weiter links (40 bis 79 => 0 bis 39) geplottet wird. Es wird dabei keine Speicherzelle außerhalb des Bildschirmspeichers überschrieben.

Anders gesagt: Eine Gerade, deren oberer rechter Teil nach unten gespiegelt und nach links verschoben wird. Siehe Bild.

FOR X=2 TO 81:POKE 982+ABS(26-INT(5/8*X))*40+X,160:NEXT

• Die Rechen-Basis (982) ist etwas kleiner als der Ursprung (1024) gewählt und wird durch leicht erhöhte Koordinaten ausgeglichen.

• Die Nulllinie ist unten. Das aktuelle Y (die Zeile) wird berechnet, indem X mit der Steigung 5/8 multipliziert wird, fängt also bei 1 an (2*5/8). Das wird von 26 Zeilen abgezogen (25 echte Bildschirmzeilen plus einer gedachten wegen der kleineren Basisadresse), dann mal Zeilenbreite 40 genommen und zur Basisadresse 982 addiert, sodass die erste Zeilenadresse-2 die unterste ist. Das X selbst kommt für den Plot natürlich noch dazu, fängt mit 2 also genau auf der Zeilenadresse an. Das INT() ist notwendig, weil wir für Y Vielfache von 40 brauchen und nichts dazwischen.

• Wenn wir oben angekommen sind und das als Term errechnete Y >= 26 wird, wird das X >= 42, sodass dann das Wrapping greift (982+0*40+42=1024). Die ABS-Funktion entfernt das Vorzeichen der Zeilenberechnung und sorgt so dafür, dass die errechnete Zeile nicht Richtung Zeropage geht, sondern wieder größer wird. Bei X=81 und Y=|26-5/8*81|=24 ist Schluss: 982+24*40 ergibt Zeilenadresse-2 von Zeile 23. Plus die 81 aus X ergibt Spalte 39 in Zeile 24.

• Die Rahmen- und Hintergrundfarbe wird auf schwarz gesetzt, indem ab der letzten Spiegelung der entsprechenden VIC-Adressen Nullen geschrieben werden. Die letzte Spiegelung wurde gewählt, damit keine wichtigen Steuerregister des VICs verändert werden. Es wird lediglich der SID gelöscht.

• WAIT.,. wartet, bis der Inhalt der Adresse 0 mit 0 AND-verknüpft ungleich 0 wird und damit für immer. Lässt sich mit Stop+Restore abbrechen. Die 2 Byte kürzere CPU-Jam-Alternative ist SYS7, ruft aber beim VICE-Emulator den Debugger auf.

 

Information from TNT/BF (aka CorporalJonlan (Twitter @CplJonlan)) #2

0 print"{clr}":forx=2to41:y=982+int(x/5*{pi})*40:pokey+x,160:pokey+43-x,160:poke53278+x,0:next:cont

x starts from 2 instead of 0 to get the slope starting from wanted position.
Base address is screen start -42 to offset the x starting from 2.
Pi is in the calculation because it's used too seldom :)
Current line is rendered mirrored.
Colors are set by poking into VIC II registers inside the loop.
Cont is nice enough to continue from itself.

For completeness: conditional scroll, mimicking the winning assembler solution. 79 bytes.

0 forx=0to41:poke214,23+z/8:print:z=(zand7)+5:poke1982+x,160:poke2025-x,160:poke53280+x,0:next:cont

x does two loops too many so that we have correct z value at "x=2".
poke 214 controls the scrolling - we scroll only when z >= 8.

 

Information from Logiker #3

Meine Version ist ähnlich jener von atomcode , nur etwas länger. Aber sie sollte auch viel leichter verständlich sein.

  • Durch die ABS-Funktion erreicht, dass man die Zeilen immer positiv von 0 bis 24 durchläuft. i läuft ca. von -40 bis 40 wodurch die Spiegelung erreicht wird. Einmal wird sozusagen hinzugezählt (von links), einmal abgezogen (von rechts).
  • .5 wurde wegen dem Runden hinzugezählt, deshalb die komische Zahl "-39.5".
  • wait.,. oder ähnliches kannte ich zu diesem Zeitpunkt noch nicht ;-)
  • 4^5 entspricht 1024

 

Appendix PRINT-only version

Information from TNT/BF (aka CorporalJonlan (Twitter @CplJonlan))

 

0 forc=17to145step128:fori=7to45:poke53268+i,0:print"{reverse on}{space}"left$(chr$(c),91and2^(iand7));:next:print"{left}{insert}{space}{up}{right}";:next:cont


We print two lines, the first one going downward and the second one going upward. Vertical direction is controlled by variable C which holds character code for petsdcii {down} or {up}.

Variable I counts 39 iterations, one less than width of screen. Starting from 7 instead or 0 (or 1) is explained below.

POKE command sets screen colors, nothing fancy there.

PRINT command first prints reverse space. Then it may or may not print the vertical movement, controlled with the second argument of LEFT$() function. We want it to be at least 1 when we need a vertical move. We do that by extracting bits from a byte-sized immediate value.

The sequence from left to right is 0, 1, 1, 0, 1, 1, 0, 1 which then repeats. Because the least significant bit is on the right, that needs to be reversed and we get binary value 10110110, 182 in decimal. As we can decide the I index freely without affecting size of the program, we rotate bits seven times to left to get value 01011011, 91 in decimal. That saves one byte.

After the inner loop completes, we move cursor left, print {insert} to move previously printed reverse space to bottom right corner without scrolling the screen. Then we print the last space and move {up} and {right} to position the cursor for the second line.

And as always, we end with CONT to halt the program.


Note: as PRINT CHR$(0) does nothing, you can replace LEFT$(CHR$(C),XYZ) with CHR$(C*XYZ). That however requires XYZ to be either 0 or 1, which usually means you need to wrap it in SGN() or INT(). Doing that results in equally sized binary.



BONUS: 85 byte version using  RND()

0 forc=17to145step128:fori=0to38:poke53280+i,0:print"{reverse on}{space}"left$(chr$(c),rnd((iand7)-.5)*2);:next:print"{left}{insert}{space}{up}{right}";:next:cont

It needs to be 53280+(iAND1), so my shortest program is 86 bytes.