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.
- The X and Y coordinates are in pixels with 0,0 at the top left.
- The sprite needs to be clipped if it goes over the screen edge.
- Sprite data can be formatted however you like within 64 bytes.
- Programs must return. The
RET
instruction is included in the size. - So everyone has a fair chance comment with the code size not code.
- 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:
Coder | Size |
---|---|
Adrian Brown | 68 |
John Metcalf | 88 |
Ralph Becket | 97 |
Arcadiy Gobuzov | 99 |
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