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