; ======================================================================
; ISOCUBES — a real masked isometric engine for the 48K Spectrum, in
; the spirit of Knight Lore's "Filmation" technique: height-aware
; projection, depth-sorted painter's-algorithm drawing, and masked
; sprite compositing (no separate mask table needed — dest = (dest
; AND NOT image) OR image, since every sprite here is one flat ink
; colour on a transparent background).
;
; From the article at https://thespeccy.com/articles/isocubes
;
; Keys: Q A O P move along the two isometric axes, SPACE exits to BASIC.
;
; Build:  pasmo --tapbas isocubes.asm isocubes.tap
; ======================================================================

        org 32768

ORIGCOL: equ 16
ORIGROW: equ 8
GRIDW:   equ 4

; ======================================================================
start:
        ld (savedsp),sp        ; see the Spectranos article for why:
                                ; lets SPACE unwind cleanly to BASIC
        call newtitle
mainloop:
        halt
        ld a,(state)
        or a
        jr z,frame_title
        call frame_demo
        jr mainloop

frame_title:
        call key_s
        jr nz,mainloop
        call newdemo
        jr mainloop

frame_demo:
        ld hl,tick
        inc (hl)
        call movehero
        call key_space
        ret nz
        call 0x0DAF
        ld sp,(savedsp)
        ret

; ----------------------------------------------------------------------
newtitle:
        xor a
        ld (state),a
        call clearall
        ld b,4
        ld c,10
        ld de,t_title
        ld a,0x46
        call print
        ld b,6
        ld c,3
        ld de,t_sub
        ld a,0x45
        call print
        ld b,14
        ld c,8
        ld de,t_keys
        ld a,0x47
        call print
        ld b,15
        ld c,7
        ld de,t_exit
        ld a,0x45
        call print
        ld b,17
        ld c,3
        ld de,t_press
        ld a,0xC6
        call print
        ret

newdemo:
        ld a,1
        ld (state),a
        xor a
        ld (herox),a
        ld a,2
        ld (heroy),a
        call drawscene
        ret

; ----------------------------------------------------------------------
; movement: Q=y-1, A=y+1, O=x-1, P=x+1; only onto height-0 cells
; ----------------------------------------------------------------------
movehero:
        ld a,(tick)
        and 3
        ret nz
        ld a,0xFB               ; Q
        in a,(0xFE)
        rra
        jr c,mv_notq
        ld a,(heroy)
        or a
        jr z,mv_notq
        dec a
        ld c,a
        call trymove_y
mv_notq:
        ld a,0xFD               ; A
        in a,(0xFE)
        rra
        jr c,mv_nota
        ld a,(heroy)
        cp GRIDW-1
        jr nc,mv_nota
        inc a
        ld c,a
        call trymove_y
mv_nota:
        ld a,0xDF               ; O
        in a,(0xFE)
        rra
        rra
        jr c,mv_noto
        ld a,(herox)
        or a
        jr z,mv_noto
        dec a
        ld c,a
        call trymove_x
mv_noto:
        ld a,0xDF               ; P
        in a,(0xFE)
        rra
        jr c,mv_notp
        ld a,(herox)
        cp GRIDW-1
        jr nc,mv_notp
        inc a
        ld c,a
        call trymove_x
mv_notp:
        ret

trymove_y:
        ld a,(herox)
        ld (candx),a
        ld (candy_x),a         ; unused placeholder for symmetry
        ld a,c
        ld (candy),a
        ld a,(herox)
        ld d,a
        ld a,(candy)
        ld e,a
        call getheight_xy
        or a
        ret nz
        ld a,(candy)
        ld (heroy),a
        jp drawscene

trymove_x:
        ld a,c
        ld (candx),a
        ld a,(heroy)
        ld (candy),a
        ld a,(candx)
        ld d,a
        ld a,(heroy)
        ld e,a
        call getheight_xy
        or a
        ret nz
        ld a,(candx)
        ld (herox),a
        jp drawscene

; D=x,E=y -> A=height at that cell
getheight_xy:
        ld a,d
        ld (dgx),a
        ld a,e
        ld (dgy),a
        jp getheight

; ----------------------------------------------------------------------
; scene: clear, then draw all 16 cells in depth order, inserting the
; hero at the point where cumulative depth first exceeds its own
; ----------------------------------------------------------------------
drawscene:
        call clearall
        ld a,(herox)
        ld b,a
        ld a,(heroy)
        add a,b
        ld (charsum),a
        xor a
        ld (herodrawn),a
        ld hl,drawtable
        ld (dtptr),hl
        ld b,16
dsloop: push bc
        ld hl,(dtptr)
        ld a,(hl)
        ld (dgx),a
        inc hl
        ld a,(hl)
        ld (dgy),a
        inc hl
        ld (dtptr),hl
        ld a,(dgx)
        ld c,a
        ld a,(dgy)
        add a,c
        ld c,a                  ; c = this cell's depth sum
        ld a,(charsum)
        cp c
        jr nc,ds_notyet          ; charsum>=cellsum: hero not due yet
        ld a,(herodrawn)
        or a
        jr nz,ds_notyet
        call drawhero
        ld a,1
        ld (herodrawn),a
ds_notyet:
        call drawcell
        pop bc
        dec b
        jr nz,dsloop
        ld a,(herodrawn)
        or a
        ret nz
        jp drawhero

; ----------------------------------------------------------------------
; projection: C=x,D=y,E=height -> B=row,C=col (cell units)
; ----------------------------------------------------------------------
project:
        ld a,c
        sub d
        add a,a
        add a,ORIGCOL
        ld l,a
        ld a,c
        add a,d
        sub e
        add a,ORIGROW
        ld b,a
        ld c,l
        ret

; (dgx),(dgy) -> A = height (0,1,2) from the grid table
getheight:
        ld hl,grid_height
        ld a,(dgx)
        ld b,a
gh_row: or a
        jr z,gh_rowdone
        push af
        ld de,GRIDW
        add hl,de
        pop af
        dec a
        jr gh_row
gh_rowdone:
        ld a,(dgy)
        ld e,a
        ld d,0
        add hl,de
        ld a,(hl)
        ret

; draw the cell at (dgx),(dgy): a flat chequered tile if height=0, else
; a top face plus left/right side faces down to the ground
drawcell:
        call getheight
        or a
        jr z,dc_flat
        ld (cellheight),a
        ld a,(dgx)
        ld c,a
        ld a,(dgy)
        ld d,a
        ld a,(cellheight)
        ld e,a
        call project             ; B,C = top face position
        ld de,solidcell
        call putcell
        push bc
        call attradr_rc
        ld a,0x46                ; block top: bright yellow
        ld (hl),a
        pop bc
        inc c
        push bc
        ld de,solidcell
        call putcell
        call attradr_rc
        ld a,0x46
        ld (hl),a
        pop bc
        dec c
        ld a,c
        ld (sidecol),a
        inc b
        ld a,b
        ld (siderow),a
        ld a,(cellheight)
        ld (sideleft),a
dc_sides:
        ld a,(sideleft)
        or a
        jr z,dc_done
        ld a,(siderow)
        ld b,a
        ld a,(sidecol)
        ld c,a
        push bc
        ld de,solidcell
        call putcell
        call attradr_rc
        ld a,0x41                 ; left face: blue (darker)
        ld (hl),a
        pop bc
        inc c
        push bc
        ld de,solidcell
        call putcell
        call attradr_rc
        ld a,0x45                 ; right face: cyan (lighter)
        ld (hl),a
        pop bc
        ld a,(siderow)
        inc a
        ld (siderow),a
        ld a,(sideleft)
        dec a
        ld (sideleft),a
        jr dc_sides
dc_done:
        ret
dc_flat:
        ld a,(dgx)
        ld c,a
        ld a,(dgy)
        ld d,a
        ld e,0
        call project
        ld a,(dgx)
        ld e,a
        ld a,(dgy)
        add a,e
        and 1
        jr z,flatA
        ld a,0x45
        jr flatgo
flatA:  ld a,0x41
flatgo: ld (tileattr),a
        ld de,dithercell         ; textured, not solid -- this is what
        call putcell             ; gives masking something real to
        push bc                  ; preserve (see maskblit16 / the
        call attradr_rc          ; article's masking comparison)
        ld a,(tileattr)
        ld (hl),a
        pop bc
        inc c
        push bc
        ld de,dithercell
        call putcell
        call attradr_rc
        ld a,(tileattr)
        ld (hl),a
        pop bc
        ret

; ----------------------------------------------------------------------
; hero: drawn with a genuine masked blit, no separate mask table --
; dest = (dest AND NOT image) OR image, since the sprite is one flat
; ink colour on a transparent background
; ----------------------------------------------------------------------
drawhero:
        ld a,(herox)
        ld c,a
        ld a,(heroy)
        ld d,a
        ld e,0
        call project
        dec b
        ld de,hero_spr
        call maskblit16
        ld a,(herox)
        ld c,a
        ld a,(heroy)
        ld d,a
        ld e,0
        call project
        dec b
        call attradr_rc
        ld a,0x46
        ld (hl),a
        inc hl
        ld (hl),a
        ld de,31
        add hl,de
        ld a,0x47
        ld (hl),a
        inc hl
        ld (hl),a
        ret

; draw a masked 16x16 sprite at cell row B, col C. DE = image data
; (16 rows x 2 bytes). dest = (dest AND NOT(image)) OR image.
maskblit16:
        push bc
        call scradr_rc
        ld (scraddr),hl
        call mb8
        pop bc
        inc b
        call scradr_rc
        ld (scraddr),hl
        call mb8
        ret
mb8:    ld b,8
mbr:    ld hl,(scraddr)
        ld a,(de)
        inc de
        ld c,a
        cpl
        and (hl)
        or c
        ld (hl),a
        inc l
        ld a,(de)
        inc de
        ld c,a
        cpl
        and (hl)
        or c
        ld (hl),a
        dec l
        inc h
        ld (scraddr),hl
        djnz mbr
        ret

; the same sprite, drawn with a plain solid overwrite instead of a
; mask -- kept only so the article's "before" screenshot can show
; the bug this causes. The shipped demo never calls this.
drawherosolid:
        ld a,(herox)
        ld c,a
        ld a,(heroy)
        ld d,a
        ld e,0
        call project
        dec b
        ld de,hero_spr
        call blit16
        ld a,(herox)
        ld c,a
        ld a,(heroy)
        ld d,a
        ld e,0
        call project
        dec b
        call attradr_rc
        ld a,0x46
        ld (hl),a
        inc hl
        ld (hl),a
        ld de,31
        add hl,de
        ld a,0x47
        ld (hl),a
        inc hl
        ld (hl),a
        ret

; ----------------------------------------------------------------------
; text
; ----------------------------------------------------------------------
print:
        ld (textattr),a
pr1:    ld a,(de)
        or a
        ret z
        push de
        call printch
        pop de
        inc de
        inc c
        jr pr1

printch:
        push bc
        sub 32
        ld l,a
        ld h,0
        add hl,hl
        add hl,hl
        add hl,hl
        ld de,font
        add hl,de
        ex de,hl
        call scradr_rc
        ld b,8
pch1:   ld a,(de)
        ld (hl),a
        inc de
        inc h
        djnz pch1
        pop bc
        push bc
        call attradr_rc
        ld a,(textattr)
        ld (hl),a
        pop bc
        ret

; ----------------------------------------------------------------------
; low-level addressing / blitting / io
; ----------------------------------------------------------------------
scradr_rc:
        ld a,b
        and 0x18
        or 0x40
        ld h,a
        ld a,b
        and 7
        rrca
        rrca
        rrca
        or c
        ld l,a
        ret

attradr_rc:
        ld a,b
        rrca
        rrca
        rrca
        and 3
        add a,0x58
        ld h,a
        ld a,b
        and 7
        rrca
        rrca
        rrca
        or c
        ld l,a
        ret

putcell:
        push bc
        call scradr_rc
        ld b,8
put1:   ld a,(de)
        ld (hl),a
        inc de
        inc h
        djnz put1
        pop bc
        ret

blit16:
        push bc
        call scradr_rc
        call cell8
        pop bc
        inc b
        call scradr_rc
cell8:
        ld b,8
c81:    push hl
        ld a,(de)
        ld (hl),a
        inc de
        inc l
        ld a,(de)
        ld (hl),a
        inc de
        pop hl
        inc h
        djnz c81
        ret

clearall:
        ld hl,16384
        ld de,16385
        ld bc,6143
        ld (hl),0
        ldir
        ld hl,22528
        ld de,22529
        ld bc,767
        ld (hl),0
        ldir
        xor a
        out (254),a
        ret

key_space:                       ; Z set if SPACE is pressed
        ld a,0x7F
        in a,(0xFE)
        and 1
        ret

key_s:                            ; Z set if S is pressed
        ld a,0xFD
        in a,(0xFE)
        and 2
        ret

; ----------------------------------------------------------------------
; data
; ----------------------------------------------------------------------
state:     defb 0
tick:      defb 0
herox:     defb 0
heroy:     defb 2
candx:     defb 0
candy:     defb 0
candy_x:   defb 0
dgx:       defb 0
dgy:       defb 0
dtptr:     defw 0
charsum:   defb 0
herodrawn: defb 0
cellheight: defb 0
sidecol:   defb 0
siderow:   defb 0
sideleft:  defb 0
textattr:  defb 0x47
tileattr:  defb 0
scraddr:   defw 0
savedsp:   defw 0

; fixed draw order: all 16 (x,y) cells sorted by ascending x+y
drawtable:
        defb 0,0
        defb 1,0, 0,1
        defb 2,0, 1,1, 0,2
        defb 3,0, 2,1, 1,2, 0,3
        defb 3,1, 2,2, 1,3
        defb 3,2, 2,3
        defb 3,3

; height map, row-major x=0..3,y=0..3 (0=flat,1=low block,2=tall block)
grid_height:
        defb 0,0,1,0
        defb 0,2,0,0
        defb 0,0,0,0
        defb 0,0,0,0

; cell textures / sprites
solidcell: defb 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
dithercell: defb 0xAA,0x00,0x55,0x00,0xAA,0x00,0x55,0x00

hero_spr:
        defb 0x0F,0xF0        ; fedora brim
        defb 0x1F,0xF8        ; fedora crown
        defb 0x0F,0xF0        ; face
        defb 0x0F,0xF0        ; face
        defb 0x1F,0xF8        ; shoulders
        defb 0x3F,0xFC        ; jacket
        defb 0x37,0xEC        ; satchel
        defb 0x3F,0xFC        ; jacket
        defb 0x1F,0xF8        ; jacket
        defb 0x1B,0xD8        ; legs apart
        defb 0x1B,0xD8
        defb 0x1B,0xD8
        defb 0x33,0xCC
        defb 0x33,0xCC
        defb 0x33,0xCC
        defb 0x00,0x00

; strings
t_title:  defb "ISOCUBES",0
t_sub:    defb "A MASKED ISOMETRIC ENGINE",0
t_keys:   defb "Q A O P TO MOVE",0
t_exit:   defb "SPACE EXITS ANYTIME",0
t_press:  defb "PRESS S TO BEGIN",0

        include "fontdata.inc"

        end start
