Saturday, 3 October 2015

The Matrix Digital Rain for the ZX Spectrum

A few days ago I coded The Matrix digital rain effect, a fictional representation of the code for the virtual reality of The Matrix. The technique is simple: fill the screen with random characters and scroll down columns of attributes, occasionally switching between black and green.

Here's the final code - 147 bytes of Z80 using the default Sinclair font:

        org 08000h

; black border / black attributes

        xor a
        out (0FEh),a
        ld hl,05AFFh
attr:   ld (hl),a
        dec hl
        bit 2,h
        jr z,attr

; fill screen with random characters

        ld e,a
fillscr:ld d,040h
fill:   call rndchar
        ld a,d
        cp 058h
        jr nz,fill
        inc e
        jr nz,fillscr

; digital rain loop

frame:  ld b,06h
        halt
column: push bc

; randomize one character

        call random
        and 018h
        jr z,docol
        add a,038h
        ld d,a
        call random
        ld e,a
        call rndchar

; select a random column

docol:  call random
        and 01Fh
        ld l,a
        ld h,058h

; ~1% chance black -> white

        ld a,(hl)
        or a
        ld bc,0247h
        jr z,check

; white -> bright green

white:  cp c
        ld c,044h
        jr z,movecol

; bright green -> green

        cp c
        ld c,04h
        jr z,movecol

; ~6% chance green -> black

        ld bc,0F00h
check:  call random
        cp b
        jr c,movecol
        ld c,(hl)

; move column down

movecol:ld de,020h
        ld b,018h
down:   ld a,(hl)
        ld (hl),c
        ld c,a
        add hl,de
        djnz down
        pop bc
        djnz column

; test for keypress

        ld bc,07FFEh
        in a,(c)
        rrca
        jr c,frame
        ret

; display a random glyph

rndchar:call random
crange: sub 05Fh
        jr nc,crange
        add a,a
        ld l,a
        ld h,0
        add hl,hl
        add hl,hl
        ld bc,(05C36h)
        add hl,bc
        ld b,8
char:   ld a,(hl)
        ld (de),a
        inc d
        inc hl
        djnz char
        ret

; get a byte from the ROM

random: push hl
        ld hl,(seed)
        inc hl
        ld a,h
        and 01Fh
        ld h,a
        ld (seed),hl
        ld a,(hl)
        pop hl
        ret

seed:

Sunday, 26 July 2015

Z80 Size Programming Challenge #5

Recently I issued the fifth Z80 challenge for the Sinclair Spectrum:

This time the challenge is to write a solid flood fill routine to fill a region of unset pixels, bounded in 4 directions (up, down, left, right) by set pixels or the screen edge. The routine should be called with the X and Y coordinates in a register. There's no need to set the screen attributes.

Scoring is multi-objective: a routine will be judged by the code size and stack space required to fill a test image. Your routine will be awarded one point for each competing routine it is smaller *and* uses less stack space than. The routine(s) with the most points will be declared winner(s).

The deadline is Wednesday 22nd July, midday (GMT).

  1. The X and Y coordinates are in pixels with 0,0 at the top left.
  2. No memory other than the screen, stack and your routine can be written.
  3. If you call a ROM routine it's size will be added to your code size.
  4. Programs must return. The RET instruction is included in the size.
  5. So everyone has a fair chance comment with the code size not code.
  6. There are no prizes, just the chance to show off your coding skills.

The test image is designed to check correct behaviour at the screen boundary and to be pathological — triggering suboptimal behaviour in some common flood fill algorithms:

Final Results

Congratulations to everyone who coded a working flood fill and to Dworkin Z Amberu who claimed first place by being shorter and using less memory than competing entries.

Entries have been plotted on this genuine fake Spectrum screenshot. If the graph is empty below and to the left of an entry, that entry is in first place:


ColourCoderCodeMemoryTime
RedJohn Metcalf98~2K2.1 seconds
OrangePaul Rhodes102~1.8K3.2 seconds
YellowRalph Becket109~2K8.8 seconds
GreenMiguel Jódar166~800 bytes4.8 seconds
WhiteDworkin Z Amberu58~9.8K28.6 seconds
CyanJohn Metcalf54~6.1K28.6 seconds
BlackDworkin Z Amberu84~270 bytes40 seconds
BlueDworkin Z Amberu1928 bytes~40 minutes
PurpleAdrian Brown19912 bytes~3 hours?

Shortest Entry

The simplest entry is a recursive routine weighing in at 54 bytes. Despite being too heavy on the stack to score well it's one of the easiest to understand. Each time the routine is called it checks whether or not the pixel at X,Y is set. If not the pixel will be set then the fill routine is called recursively with the pixels up, down, left and right of the current pixel:

; called with e = X horizontal, d = Y vertical
FILL:
  ld b,e
  ld a,d
  and 248
  rra
  cp 96
  ret nc
  rra
  rra
  ld l,a
  xor d
  and 248
  xor d
  ld h,a
  ld a,e
  xor l
  and 7
  xor e
  rrca
  rrca
  rrca
  ld l,a
  ld a,128
PLOTBIT:
  rrca
  djnz PLOTBIT
  or (hl)
  cp (hl)
  ret z
  ld (hl),a
  inc e
  call nz,FILL
  dec e
  dec de
  call ZFILL
  inc de
  call FILL
  inc d
  inc d
ZFILL:
  call nz,FILL
  dec d
  ret

The winning entries will be available shortly.

Monday, 6 April 2015

Z80 Size Programming Challenge #4

The fourth Z80 challenge for the ZX Spectrum was issued last week:

Back to something simple for the next challenge, a diagonal fade-to-white CLS. Write the shortest code to wipe the screen by increasing the ink colour of each character until it reaches white.

The clear should start at the top left and move one character across the screen per frame. The initial screen can be assumed to be monochrome — black text, white background, flash off, bright off. There's no need to clear the screen bitmap. Here's a demonstration of the clear in slow motion:

Target: under 50 bytes.

The deadline is Monday 6th April, midday (GMT).

  1. Your program shouldn't rely on the initial contents of registers.
  2. Programs must halt between frames. The HALT is included in the size.
  3. No RAM/ROM other than the attribute memory should be written to.
  4. Programs must return. The RET instruction is included in the size.
  5. So everyone has a fair chance comment with the code size not code.
  6. There are no prizes, just the chance to show off your coding skills.

Final Results

Congratulations to everyone who entered and Arcadiy Gobuzov who claimed first place with a solution in 26 bytes. Most of the solutions use LDDR to move the attribute data with anonymous and Ralph Becket being the two exceptions. Here are the final results:

CoderSize
Arcadiy Gobuzov26
ub880d27
Bohumil Novacek27
anonymous27
Adrian Brown27
John Metcalf27
Ralph Becket30
Jim Bagley31
Paul Rhodes31

Winning Entry

Here's Arcadiy's winning entry in 26 bytes:

        xor a ; if comment then 25, but exit if a==56 on start
loop:        
        ld hl,#5ADF   ;
        cp (hl)       ;
        ld bc,#02E0   ; 23 lines of attributes
        ld de,#5AFF   ; 
        lddr          ; move down attributes
        ld c,e        ; e = #1F
        add hl,bc     ;
        lddr          ; roll upper line of attributes to right
        halt
        ret z
        ld a,(de)     ; de = first address of attibutes
        cp #3F        ;
        adc a,c       ; add 0 or 1 (carry)
        ld (de),a     ; now a in range [38..3f]
        jr loop

Here's my own solution in 27 bytes. Unfortunately I missed the final CP (HL) to squeeze out the last byte:

fadetowhite:
        ld de,23295 ; 90 255
        ld a,(de)
        cp 63
        ret z
        ld hl,23263 ; 90 223
        ld bc,736   ;  2 224
        halt
        lddr
        ld c,e
        add hl,bc
        lddr
        ld a,(de)
        cp 63
        adc a,c
        ld (de),a
        jr fadetowhite

Here's an alternative — a fade-to-black wipe (from white ink, black paper, no bright, no flash) in 25 bytes:

fadetoblack:
        ld de,23295 ; 90 255
        ld a,(de)
        or a
        ret z
        ld hl,23263 ; 90 223
        ld bc,736   ;  2 224
        halt
        lddr
        ld c,e
        add hl,bc
        lddr
        ld a,(de)
        add a,l
        sbc a,l
        ld (de),a
        jr fadetoblack

Monday, 30 March 2015

Z80 Size Programming Challenge #3

Recently I issued the third Z80 programming challenge for the ZX Spectrum:

This time the challenge is to write the shortest code to display a masked 16×16 pixel sprite. The routine should be called with the address of the sprite data in one register and the X and Y screen coordinates in another. There's no need to save the screen area being overwritten or set the colours / attributes. This is somewhat trickier than previous challenges but more likely to be of practical use.

The deadline is Monday 30th March, midday (GMT).

Target: under 125 bytes.

  1. The X and Y coordinates are in pixels with 0,0 at the top left.
  2. The sprite needs to be clipped if it goes over the screen edge.
  3. Sprite data can be formatted however you like within 64 bytes.
  4. Programs must return. The RET instruction is included in the size.
  5. So everyone has a fair chance comment with the code size not code.
  6. There are no prizes, just the chance to show off your coding skills.

Solutions can be emailed to digital.wilderness@googlemail.com or posted here after the deadline.


Final Results

Congratulations to everyone who rose to the challenge, this was a tough one. Adrian claimed an impressive victory with a neat piece of self-modifying code. Here are the final results:

CoderSize
Adrian Brown68
John Metcalf88
Ralph Becket97
Arcadiy Gobuzov99

Winning Entry

Adrian Brown submitted an ingenious solution in only 68 byte. The code displays a sprite pixel by pixel in approx 18ms. The instruction at DS_SetResOp is modified to set, reset or leave the appropriate bit.

DrawSprite:
    ; At most we want to draw 16 lines (lets store
    ; the 4 onto c as well as its saves a byte)
    ld bc, 01004h
DS_YLoop:
    ; Gotta be able to stop doing all this push/pop
    ; with exx at some point - but hey ho
    push bc
    push de

    ; Splitting is actually helpful as it gives us
    ; the byte increase on clipping :D
DS_XLoop1:
    ; Lets get that data byte
    ld b, 4
DS_XLoop2:
    ; Bit cheaty, roll the actual data, it will end
    ; up back as it started so thats fine
    ld a, (hl)
    rlca
    rlca
    ld (hl), a

    ; Store the data pointer
    push hl

    ; See if we want to draw or not, bit sneaky
    ; because of data layout
    or %10011111
    ld l,a

    ; Now calculate the screen address, start it
    ; here so carry is clear
    ld a,e
    rra
    ; Lets use the check to set the C flag
    cp 96
    jr nc, DS_SkipPixel
    rra
    or a
    rra
    push af
    xor e
    and %11111000
    xor e
    ld h,a

    ; Now work out the opcode for set/res bit (we need
    ; 01 for bit, 10 for res and 11 for set - so data
    ; needs to be 10 for bit, 01 for res and 00 for set)
    ld a,d
    and %00000111
    rlca
    rlca
    ; Thats nice, this will do the cpl for us on the
    ; bit number ;)
    xor l
    rla
    ld (DS_SetResOp + 1),a

    pop af
    xor d
    and %00000111
    xor d

    ; Move across - check for clipping, do it here so
    ; we can use a as a value > 192
    inc d
    jr nz, DS_NoClipX

    ; Stick Y off the bottom so the rest of the line is clipped
    ; we can use a at this point as its got to be > 192
    ld e, a
DS_NoClipX:

    rrca
    rrca
    rrca
    ld l,a

    ; Go set/res the bit
DS_SetResOp:
    set 0, (hl)
DS_SkipPixel:
    ; Store the data pointer 
    pop hl

    ; Go do the byte of data
    djnz DS_XLoop2

    ; Now we need to move to the next bytes
    inc hl
    dec c
    jr nz, DS_XLoop1

    pop de
    pop bc

    ; Just increase down
    inc e
    djnz DS_YLoop
    ret

;***********************************************************
; Sprite Data twiddled a bit, Mask/Data/Mask/Data
; Mask = 0 we want the screen, set data to 1 means we convert
; the set/res into a bit which is fine, All rolled right
; three bit to get the pixel data i want in bits 3+4
;***********************************************************

SpriteData:
    db %10101010, %01001011, %10110100, %10101010
    db %10101010, %11110101, %01011111, %10101010
    db %11001010, %01011111, %11110101, %10110010
    db %01101011, %01010101, %01010101, %10111100
    db %11001101, %11010101, %01010101, %00110111
    db %11001101, %11110111, %01010101, %00110111
    db %01010111, %11010101, %01110101, %11010101
    db %01010111, %01010101, %01010101, %11011101
    db %01010111, %01010101, %01110101, %11010101
    db %01010111, %01010101, %11010101, %11011101
    db %11001101, %01010101, %01110111, %00110111
    db %11001101, %11010101, %11011101, %00110111
    db %01101011, %01110101, %01010111, %10111100
    db %11001010, %01011111, %11110101, %10110010
    db %10101010, %11110101, %01011111, %10101010
    db %10101010, %01001011, %10110100, %10101010

Here my own solution in 88 bytes. This displays the sprite row by row and is slightly faster, taking approx 5ms.

; called with hl = address of sprite, de = position on screen

putsprite:
  ld c,16
nextline:
  ld a,d
  and 7
  inc a
  ld b,a
  ld a,e
  rra
  cp 96
  ret nc
  rra
  or a
  rra
  push de
  push hl
  ld l,a
  xor e
  and 248
  xor e
  ld h,a
  ld a,l
  xor d
  and 7
  xor d
  rrca
  rrca
  rrca
  ld l,a

  ld e,255
spd:
  ex (sp),hl
  ld a,(hl)
  inc hl
  ld d,(hl)
  inc hl
  ex (sp),hl

  push bc
  rrc e
  jr noshift
shiftspr:
  rra
  rr d
  rr e
noshift:
  djnz shiftspr

  push hl
  ld b,3
mask:
  bit 0,e
  jr z,bm1
  and (hl)
db 254 ;  jr bm2
bm1:
  xor (hl)
bm2:
  ld (hl),a
  inc l
  ld a,l
  and 31
  ld a,d
  ld d,e
  jr z,clip
  djnz mask
clip:
  bit 0,e
  ld e,0
  pop hl
  pop bc
  jr nz,spd
  pop hl
  pop de
  inc e
  dec c
  jr nz,nextline
  ret

sprite:
  db %11111100, %00111111, %00000000, %00000000
  db %11110000, %00001111, %00000011, %11000000
  db %11100000, %00000111, %00001100, %00110000
  db %11000000, %00000011, %00010000, %00001000

  db %10000000, %00000001, %00100010, %00000100
  db %10000000, %00000001, %00100111, %00000100
  db %00000000, %00000000, %01000010, %00010010
  db %00000000, %00000000, %01000000, %00001010

  db %00000000, %00000000, %01000000, %00010010
  db %00000000, %00000000, %01000000, %00101010
  db %10000000, %00000001, %00100000, %01010100
  db %10000000, %00000001, %00100010, %10100100

  db %11000000, %00000011, %00010001, %01001000
  db %11100000, %00000111, %00001100, %00110000
  db %11110000, %00001111, %00000011, %11000000
  db %11111100, %00111111, %00000000, %00000000

Monday, 15 December 2014

Z80 Size Programming Challenge #2

Last week I issued the second Z80 programming challenge:

Something slightly more complex this time. Write the shortest code to mirror the entire Sinclair Spectrum screen (256×192 pixels) left to right including the colours / attributes. The deadline is Monday 15th, midday (GMT).

Target: under 50 bytes.

  1. Your program shouldn't rely on the initial contents of registers.
  2. No RAM/ROM other than the screen memory should be written to.
  3. Programs must return. The RET instruction is included in the size.
  4. So everyone has a fair chance comment with the code size not code.
  5. There are no prizes, just the chance to show off your coding skills.

Final Results

We stepped up the difficultly for the second challenge so congratulations to everyone who entered. Introspec ZX and Tim Webber discovered the shortest solutions. Here are the final results:

CoderSize
Introspec Zx34
Tim Webber34
John Metcalf34
Paul Rhodes35
Simon Brattel35
Jim Bagley36
Steve Wetherill38
John Young49
Chris Walsh49
Dariusz EM50

Winning Entries

Introspec submitted the first 34 byte solution using a couple of neat tricks. Note the use of CP L to check which side of the screen it's working on and the byte saved by setting B to #58:

                ld hl,16384+6912

screenflip:     ld d,h
                ld a,l
                xor #1F
                ld e,a
                cp l
                jr nc,noflip

                ld a,(de)
                ld c,(hl)
                ld (hl),a
                ld a,c
                ld (de),a

noflip:         ld b,#58
                ld a,h
                cp b
                jr nc,skipattr

byteflip:       rlc (hl)
                rra
                djnz byteflip
                ld (hl),a

skipattr:       dec hl
                bit 6,h
                jr nz,screenflip
                ret

Tim Webber's solution saves a series of addresses on the stack to be used later:

start:          ld hl,23296
loop1:          dec hl
                bit 6, h
                ret z
                ld a, 87
                cp h
                jr c, noinv
                ld b,8
doinv:          rl (hl)
                rra
                djnz doinv
                ld (hl), a
noinv:          push hl
                bit 4,l
                jr nz, loop1
                pop de
                pop hl
                ld a,(de)
                ld c, (hl)
                ld (hl), a
                ex de, hl
                ld (hl), c
                jr loop1

Although I didn't enter I also found a couple of 34 byte solutions. The first mirrors two bytes in the inner loop:

                ld hl,16384
mirror:         ld d,h
                ld a,l
                xor 31
                ld e,a
                ld a,h
                cp 91
                ret z
                cp 88
                ld a,(de)
                ld c,a
                jr nc,attrib
                ld b,8
                rrca
mirrorbits:     rl (hl)
                rra
                djnz mirrorbits
                db 1 ; skip the next two instructions
attrib:         ld a,(hl)
                ld (hl),c
                ld (de),a
                inc l
                inc hl
                jr mirror

My second has two separate loops. The first loop mirrors bytes, the second mirrors the screen:

                ld hl,22527
mir:            ld a,128
mirrorbits:     rl (hl)
                rra
                jr nc,mirrorbits
                ld (hl),a
                dec hl
                bit 6,h
                jr nz,mir
mirror:         inc hl
                ld d,h
                ld a,l
                xor 31
                ld e,a
                ld a,h
                cp 91
                ret z
                ld a,(de)
                ld c,a
                ld a,(hl)
                ld (hl),c
                ld (de),a
                inc l
                jr mirror

Is 34 Bytes Optimal?

Definitely not! After the deadline a solution was discovered that combines code from Tim Webber and Introspec's entries to mirror the screen in 33 bytes:

start:          ld hl,23296 ; Tim Webber/Introspec
loop1:          dec hl
                bit 6, h
                ret z
                ld a, h
                ld b,88
                cp b
                jr nc, noinv
doinv:          rlc (hl)
                rra
                djnz doinv
                ld (hl), a
noinv:          push hl
                bit 4,l
                jr nz, loop1
                pop de
                pop hl
                ld a,(de)
                ld c, (hl)
                ld (hl), a
                ex de, hl
                ld (hl), c
                jr loop1

Another 33 byte solution combines the code from Tim Webber and Simon Brattel's entries:

start:          ld hl,23296 ; Tim Webber/Simon Brattel
loop1:          dec hl
                ld a,h
                cp 88
                jr nc, noinv
                and 64
                ret z
                add a,a
doinv:          rlc (hl)
                rra
                jr nc,doinv
                ld (hl), a
noinv:          push hl
                bit 4,l
                jr nz, loop1
                pop de
                pop hl
                ld a,(de)
                ld c, (hl)
                ld (hl), a
                ex de, hl
                ld (hl), c
                jr loop1

Entries will be available shortly on John Young's website. Thanks to everyone who entered for making the contest a success :-)

Monday, 8 December 2014

Z80 Size Programming Challenge #1

A few days ago I issued a Z80 programming challenge for the ZX Spectrum:

Something simple for the first challenge. Write the shortest code to fill the screen with a chequerboard pattern of 1 pixel squares. No RAM/ROM other than the 6144 byte bitmap screen memory should be written to.

Target: under 25 bytes.

  1. Your program shouldn't rely on the initial contents of registers.
  2. Programs must return. The RET instruction is included in the size.
  3. So everyone has a fair chance comment with the code size not code.
  4. There are no prizes, just the chance to show off your coding skill.

Final Results

Congratulations to all who entered, especially Allan Høiberg and Introspec Zx who both discovered a 15-byte solution. The final results are as follows:

CoderSize
Allan Høiberg15
Introspec Zx15
Jim Bagley16
Paul Rhodes16
Krystian Włosek16
Tim Webber16
Steve Wetherill16
John Young16
Simon Brattel16
John Metcalf16
Dariusz EM17
Chris Walsh23

Winning Entries

Allan was the first to discover a 15-byte solution:

                LD BC,22272
                LD A,85
LoopB:          BIT 6,B
                RET Z
LoopC:          DEC C
                LD (BC),A
                JR NZ,LoopC
                CPL
                DJNZ loopB

Introspec found a 15-byte solution with only one loop:

                ld hl,16384+6143
filloop5:       ld a,h
                rra
                sbc a,a
                xor %01010101
                ld (hl),a
                dec hl
                bit 6,h
                jr nz,filloop5
                ret

My own attempts all fell short at 16 bytes:

                ld hl,22528-256
                ld bc,24*256+170
fill:           dec l
                ld (hl),c
                jr nz,fill
                rrc c
                dec h
                djnz fill
                ret

Entries are archived on John Young's website. Thanks to everyone who entered or otherwise supported the challenge. :-)

Saturday, 15 March 2014

Plotting the Mandelbrot Set on the ZX Spectrum

ZX Spectrum MandelbrotZX Spectrum Mandelbrot

The Mandelbrot set is a fractal which iterates the equation zn+1 = zn² + c in the complex plane and plots which points tend to infinity. Plotting the set with Sinclair BASIC takes over 24 hours so I was curious how much faster it would be in assembly.

It turns out if we use fast 16-bit fixed-point arithmetic we can plot the Mandelbrot in about 5 minutes. To minimise multiplications each iteration is calculated as:

rn+1 = ( rn + in ) × ( rn - in ) + x

in+1 = 2 × in × rn + y

The following test is used to detect points which tend to infinity:

|in| + |rn| ≥ 2 × √ 2.
  org 60000
  ld de,255*256+191
XLOOP:
  push de
  ld hl,-180   ; x-coordinate
  ld e,d
  call SCALE
  ld (XPOS),bc
  pop de
YLOOP:
  push de
  ld hl,-96    ; y-coordinate
  call SCALE
  ld (YPOS),bc
  ld hl,0
  ld (IMAG),hl
  ld (REAL),hl
  ld b,15      ; iterations
ITER:
  push bc
  ld bc,(IMAG)
  ld hl,(REAL)
  or a
  sbc hl,bc
  ld d,h
  ld e,l
  add hl,bc
  add hl,bc
  call FIXMUL
  ld de,(XPOS)
  add hl,de
  ld de,(REAL)
  ld (REAL),hl
  ld hl,(IMAG)
  call FIXMUL
  rla
  adc hl,hl
  ld de,(YPOS)
  add hl,de
  ld (IMAG),hl
  call ABSVAL
  ex de,hl
  ld hl,(REAL)
  call ABSVAL
  add hl,de
  ld a,h
  cp 46        ; 46 ≅ 2 × √ 2 << 4
  pop bc
  jr nc,ESCAPE
  djnz ITER
  pop de
  call PLOT
  db 254       ; trick to skip next instruction
ESCAPE:
  pop de
  dec e
  jr nz,YLOOP
  dec d
  jr nz,XLOOP
  ret

FIXMUL:        ; hl = hl × de >> 24
  call MULT16BY16
  ld a,b
  ld b,4
FMSHIFT:
  rla
  adc hl,hl
  djnz FMSHIFT 
  ret

SCALE:         ; bc = (hl + e) × zoom
  ld d,0
  add hl,de
  ld de,48     ; zoom

MULT16BY16:    ; hl:bc (signed 32 bit) = hl × de
  xor a
  call ABSVAL
  ex de,hl
  call ABSVAL
  push af
  ld c,h
  ld a,l
  call MULT8BY16
  ld b,a
  ld a,c
  ld c,h
  push bc
  ld c,l
  call MULT8BY16
  pop de
  add hl,de
  adc a,b
  ld b,l
  ld l,h
  ld h,a
  pop af
  rra
  ret nc
  ex de,hl
  xor a
  ld h,a
  ld l,a
  sbc hl,bc
  ld b,h
  ld c,l
  ld h,a
  ld l,a
  sbc hl,de
  ret

MULT8BY16:     ; returns a:hl (24 bit) = a × de
  ld hl,0
  ld b,8
M816LOOP:
  add hl,hl
  rla
  jr nc,M816SKIP
  add hl,de
  adc a,0
M816SKIP:
  djnz M816LOOP
  ret

PLOT:          ; plot d = x-axis, e = y-axis
  ld a,7
  and d
  ld b,a
  inc b
  ld a,e
  rra
  scf
  rra
  or a
  rra
  ld l,a
  xor e
  and 248
  xor e
  ld h,a
  ld a,d
  xor l
  and 7
  xor d
  rrca
  rrca
  rrca
  ld l,a
  ld a,1
PLOTBIT:
  rrca
  djnz PLOTBIT
  or (hl)
  ld (hl),a
  ret

ABSVAL:        ; returns hl = |hl| and increments
  bit 7,h      ; a if the sign bit changed
  ret z
  ld b,h
  ld c,l
  ld hl,0
  or a
  sbc hl,bc
  inc a
  ret

XPOS:dw 0
YPOS:dw 0
REAL:dw 0
IMAG:dw 0