Hello, I came across a website https://codegolf.stackexchange.com where people ask others to write programs in any programming language they wish and to make the program as short as possible.
I thought this would be a cool 6809 programming exercise since I’ve mainly been working on optimizing my programs for speed which almost always increases the size of the program, see my blog entries about 6809 optimization routines.
I came across what I thought was a cool request to draw the Mona Lisa on screen using the same method as a programmer named Jakub ‘Ilmenit’ Debski wrote. He dedicating the program to his wife Iza, who pasted away February 15th, 2014. This link describes the method the program needs to work.
The original program by Ilmenit was created for an Atari XL computer with a 6502 processor and is only 250 bytes long. It uses two system calls, one to setup the graphics mode and the other to draw pixels on the screen. My version for the CoCo 3 and 6809 processor is 283 bytes, but I had to write my own drawing routine and a few bytes to put the CoCo 3 in graphics mode so I’m ok with it being 33 bytes longer than the original.
On the Internet Archive wayback machine website I was able to find some info from the original post https://www.pouet.net/prod.php?which=62917 and I thought I’d reproduce them here, just so that others can find them easier:
“There are 64 pseudo random brush strokes of cycling color. Each stroke is shorter than previous to increase details. Intro code generates set of random numbers starting from initial 32bit seed with Galois LFSRs as a PRNG. This set is used to control direction of brush movement and and starting position. Each stroke has initial 16bit value that influences the seed. Therefore data is 4 bytes (initial seed) + 64*2 bytes of stroke PRNG seeds. Picture of Mona Lisa (3072 bytes) was compressed to those random brush strokes by external optimization program (a few days of work for modern CPU/GPU combo). The process can be seen as lossy compression (23x) of picture into random brush movements.
“The seeds were found with iterative brute-force approach. Each brush stroke is a layer which covers the previous one, therefore is mostly independent in calculations. For each layer there is a 16bit seed to be found that generates the best picture possible. Therefore there are 64 layers * 65536 possible values per layer and the program iterates through all of them (4 million of combinations – acceptable). This should be done for each initial 32bit seed (means *4 billion combinations) but can be performed in parallel. I’ve checked only a few thousands of randomly chosen initial seeds to find a nice looking picture. Details mask had to be used to multiply difference error on critical parts of the picture (eyes, nose, mouth). Otherwise too many details were put in the background.
Thank you all for the kind words.
I uploaded a video of the program running on a CoCo 3:
I included the source code below and I’d love to hear if anyone has any ideas to shrink the code even more. Hopefully it might inspire others to program their CoCo’s.
Updated code: I managed to get the code to 283 bytes, it is not relocatable now as I used the TFR PC,D to set the values for the A and B registers near the start and tweaked a few more things… I also managed to get a version that uses the BASIC ROM to draw the pixel but it only saves 2 bytes (281 bytes total for that version) since I had to prep the code before the draw pixel routine.
The version below is using all my code (no ROM calls) and is 283 bytes when assembled into a Machine language program loadable from BASIC.
I uploaded a zip file to the Color Computer Archive that contains the binary, a .DSK file and the source code below along with the original Atari XL 6502 version that inspired the code golf article.
See you in the next post, Glen
* Assemble with LWASM pragma noforwardrefmax * Forces LWASM to do multi-pass refrences so it can use reuslts that are smaller than 16 bits Testing EQU 0 * 0 = no high speed poke, 1 = faster high speed poke & program autostarts PIXELS_PER_LEN equ 32 ORG $FFB0 * Palette_0 CoCo3_Colours: FCB %00000000 * Colour 0 FCB %00100010 * Colour 1 FCB %00110101 * Colour 2 FCB %00111110 * Colour 3 ORG $7B96 * starts here so we can use $7C as direct page for variables * Mona Lisa Brush movement table * BRUSH = [ Brush: FDB $030A,$37BE,$2F9B,$072B,$0E3C,$F59B,$8A91,$1B0B FDB $0EBD,$9378,$B83E,$B05A,$70B5,$0280,$D0B1,$9CD2 FDB $2093,$209C,$3D11,$26D6,$DF19,$97F5,$90A3,$A347 FDB $8AF7,$0859,$29AD,$A32C,$7DFC,$0D7D,$D57A,$3051 FDB $D431,$542B,$B242,$B114,$8A96,$2914,$B0F1,$532C FDB $0413,$0A09,$3EBB,$E916,$1877,$B8E2,$AC72,$80C7 FDB $5240,$8D3C,$3EAF,$AD63,$1E14,$B23D,$238F,$C07B FDB $AF9D,$312E,$96CE,$25A7,$9E37,$2C44,$2BB9,$2139 * ]; CoCo_START: *********************************************************** ORCC #$50 * Disable interrupts TFR PC,D * A=$7C, B=$1A LDX <$8A * X=0, in BASIC memory location $008A and $008B are always zero ; LDA #%01111100 * A = $7C STA $FF90 * CoCo 3 Mode, MMU Enabled, GIME IRQ Enabled, GIME FIRQ Enabled, Vector RAM at FEXX enabled, Standard SCS Normal, ROM Map 16k Int, 16k Ext TFR A,DP * DP = $7C SETDP $7C * Let the assembler know the DP value IF Testing STB $FFD9 * High Speed mode enabled ENDIF * Clear the screen ! CLR ,X+ CMPX #Brush BNE < * All clear * X now points to the Brush table * CoCo Prep *********************************************************** * Set Hires Screen Resolution and the number of Colours ; LDA #%10000000 * = $80, Graphics mode, Colour output, 60 hz, max vertical res ; LDB #%00011010 * = $1A, 256 x 192 x 16 Colours requires 24,576 bytes = $6000 RAM ; LDD #$801A * Graphics mode, Colour output, 60 hz, max vertical res & 256 x 192 x 16 Colours requires 24,576 bytes = $6000 RAM LDA #$80 STD $FF98 * Graphics mode, Colour output, 60 hz, max vertical res *********************************************************** LEAU cursor_y,PCR * U = #cursor_y next_part: init_part: LDB part ; part 0-63, from the end STB length ; ; (word) crc_seed = word_seed; LDD ,X++ ; get the next value in A and B STD crc_seed+2 ; STA crc_seed LSB & STB crc_seed LLSB STD ,U ; STA cursor_y & STB cursor_x init_part_end next_length: LDA #PIXELS_PER_LEN-1 STA pixel next_pixel: * inlined_rnd * crc_seed ASL cursor_y-1 * LLSB ROL cursor_y-2 * LSB ROL cursor_y-3 * MSB ROL cursor_y-4 * MMSB BCC nofeedback LDB #4 * Set to 4 above LEAY -4,U * U=crc_seed ; LDU #crc_seed * U=crc_seed xor_seed_loop: LDA ,Y * Get the crc_seed EORA -4,Y * XOR the value STA ,Y+ * save the new value + move pointer DECB * decrement the counter BNE xor_seed_loop ; bpl xor_seed_loop STA direction * a=direction=crc_seed crc_seed LLSB nofeedback: move_cursor: LDB direction ; B=direction ANDB #$02 ; and #2 ; random value 0 or 2 for cursor_y/cursor_x LSRB ; make it 0 or 1 LDA B,U ; B=0 then LDA cursor_y else B=1 then LDA cursor_x TST direction BMI > INCA INCA ! DECA ANDA #$7F ; and #$7F ; limit value to 0-127 STA B,U ; sta cursor_y(or cursor_x) drawpoint: * drawPixel(bx, by, COLOR[part AND 3]) * bx is a range from (0 to 127) * by is a range from (0 to 95) * Coco 3 Screen resolution is 256 x 192 x 16 colours, starting at $0000 * each row is 128 bytes LDD cursor_y * D = address, A = Y - co-ordinate, B= x - co-ordinate CMPA #96 BHS DoneDrawPoint ANDB #$7F LSRA RORB * 1/2 and move bit zero to the carry TFR D,Y LDD #$F003 * Right Nibble and B has the right nibble colour ANDB part * Make colour a range of 0 to 3 BCS > * If carry is set then it is an odd number (right nibble used) LDA #$10 MUL * Set high nibble with the colour (left nibble) LDA #$0F ! ANDA ,Y * A = the original pixel value for the old nibble STB SelfModA_OR_B+1 * Self mod to do an OR of A & B SelfModA_OR_B: ORA #$FF * A = A logical OR B STA ,Y * WRite the new value to the screen DoneDrawPoint: ;;;;;;;; loop DEC pixel ; DEC pixel BPL next_pixel DEC length ; DEC length BPL next_length DEC part ; DEC part BPL next_part BRA * * Loop forever part FCB 63 xor_const: ;* LLSB,LSB,MSB,MMSB ; FCB $B7,$1D,$C1,$04 * Little Endian FCB $04,$C1,$1D,$B7 * Big Endian crc_seed: ;* LLSB,LSB,MSB,MMSB ; FCB $E5,$54,$C8,$7E * Little Endian FCB $7E,$C8 * Big Endian ; next two bytes will be set when the program starts the crc_seed is 4 bytes so we have to make sure we make space for it cursor_y EQU crc_seed+4 cursor_x EQU cursor_y+1 direction EQU cursor_x+1 ; this gets zeroed by setting graphics mode pixel EQU direction+1 length EQU pixel+1 IF Testing ORG $A42E * Make it autostart FDB CoCo_START ENDIF END CoCo_START