; compile with dasm (http://dasm-dillon.sourceforge.net/)
        processor 6502

        org $801
        
        dc.w .next, 10
        dc.b $9e, " 14912", 0
.next
        dc.w 0

        org $2f00

        ds.b $100, $01

        org $3000 ; color data
        ds.b 1000, $01

        org $3400 ; sprite data
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $00, $80, $00
        dc.b $00, $80, $00
        dc.b $00, $80, $00
        dc.b $00, $80, $00
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $f0, $07, $80
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $00, $00, $00
        dc.b $00, $80, $00
        dc.b $00, $80, $00
        dc.b $00, $80, $00
        dc.b $00, $80, $00
        dc.b $00, $00, $00
        dc.b $00, $00, $00

.missiletick
        dc.b $01
.missilegentick
        dc.b $40
.level
        dc.b $38
.rnglo
        dc.b $aa
.rnghi
        dc.b $aa

.circleradius ; $00-$78, low nibble should be zero or 8
        dc.b $00
.circlelo
        dc.b $00
.circlehi
        dc.b $24
.circleoff
        dc.b $02
.circleradiuscounter
        dc.b $0

.scratch
        ds.b 3, $00

.circlewaspressed
        dc.b $00
.hitsound
        dc.b $00
.circlesound
        dc.b $00

        align 8

.bitlookup
        dc.b $80, $40, $20, $10, $08, $04, $02, $01

.sqrtlut ; make sure this doesn't cross a page boundary
        ; byte(.sqrtlut + r*8 + dy) = floor(sqrt(r^2 - dy^2))
        ; r is in steps of 0.5 pixel
        dc.b $ff, $ff, $ff, $ff, $ff, $ff, $ff, $ff
        dc.b $0, $ff, $ff, $ff, $ff, $ff, $ff, $ff
        dc.b $1, $0, $ff, $ff, $ff, $ff, $ff, $ff
        dc.b $1, $1, $ff, $ff, $ff, $ff, $ff, $ff
        dc.b $2, $1, $0, $ff, $ff, $ff, $ff, $ff
        dc.b $2, $2, $1, $ff, $ff, $ff, $ff, $ff
        dc.b $3, $2, $2, $0, $ff, $ff, $ff, $ff
        dc.b $3, $3, $2, $1, $ff, $ff, $ff, $ff
        dc.b $4, $3, $3, $2, $0, $ff, $ff, $ff
        dc.b $4, $4, $4, $3, $2, $ff, $ff, $ff
        dc.b $5, $4, $4, $4, $3, $0, $ff, $ff
        dc.b $5, $5, $5, $4, $3, $2, $ff, $ff
        dc.b $6, $5, $5, $5, $4, $3, $0, $ff
        dc.b $6, $6, $6, $5, $5, $4, $2, $ff
        dc.b $7, $6, $6, $6, $5, $4, $3, $0
        dc.b $7, $7, $7, $6, $6, $5, $4, $2

.rangelut ; make sure this doesn't cross a page boundary
        dc.b $00, $00, $00, $00, $00, $00, $00, $01
        dc.b $00, $00, $00, $00, $00, $00, $01, $03
        dc.b $00, $00, $00, $00, $00, $01, $03, $07
        dc.b $00, $00, $00, $00, $01, $03, $07, $0f
        dc.b $00, $00, $00, $01, $03, $07, $0f, $1f
        dc.b $00, $00, $01, $03, $07, $0f, $1f, $3f
        dc.b $00, $01, $03, $07, $0f, $1f, $3f, $7f
        dc.b $01, $03, $07, $0f, $1f, $3f, $7f, $ff
        dc.b $02, $07, $0f, $1f, $3f, $7f, $ff, $ff
        dc.b $04, $0e, $1f, $3f, $7f, $ff, $ff, $ff
        dc.b $08, $1c, $3e, $7f, $ff, $ff, $ff, $ff
        dc.b $10, $38, $7c, $fe, $ff, $ff, $ff, $ff
        dc.b $20, $70, $f8, $fc, $fe, $ff, $ff, $ff
        dc.b $40, $e0, $f0, $f8, $fc, $fe, $ff, $ff
        dc.b $80, $c0, $e0, $f0, $f8, $fc, $fe, $ff
        dc.b $00, $80, $c0, $e0, $f0, $f8, $fc, $fe
        dc.b $00, $00, $80, $c0, $e0, $f0, $f8, $fc
        dc.b $00, $00, $00, $80, $c0, $e0, $f0, $f8
        dc.b $00, $00, $00, $00, $80, $c0, $e0, $f0
        dc.b $00, $00, $00, $00, $00, $80, $c0, $e0
        dc.b $00, $00, $00, $00, $00, $00, $80, $c0
        dc.b $00, $00, $00, $00, $00, $00, $00, $80


        org $358f ; it is important that .missilehi/lo/bits/slopes do not cross a page boundary
.missilecount
        dc.b 1 ; max 8
.missilehi
        ds.b 8, $20
.missilelo
        ds.b 8, $40
.missilebits
        ds.b 8, $10
.missileslopes
        ds.b 8, $60
.missilelastvals
        ds.b 8, $ff

.noisefreq
        dc.b $df
.noisectl
        dc.b $7c
.noisesr
        dc.b $e0
.hitfreq
        dc.b $f8
.hitctl
        dc.b $dd
.hitad
        dc.b $ff
.circlefreq
        dc.b $fe
.circlectl
        dc.b $bd
.circlead
        dc.b $fe
.circlesr
        dc.b $ff
.filtercutoff
        dc.b $f0

        org $3a40
        sei
        lda #$3b
        sta $d011 ; enable display, set high bit of raster to 0, enable bitmap

        lda #$35 ; turn off rom
        sta $01

        lda #$7f
        ; disable some interrupts
        sta $dc0d ; timers
        sta $dd0d ; keyboard scan

        ; initial position for sprite 0
        lda #$3f
        sta $d000
        sta $d001

        lda #$01
        sta $d015 ; enable sprite 0

        lda #$0a
        sta $d027 ; color sprite 0

        lda #$00
        sta $d021 ; set background color
        sta $d020 ; ...and border color

        lda #$c8
        sta $d018 ; set bitmap base to $2000 and color base to $3000

        lda #$d0
        sta $33f8 ; set sprite 0 address to $3400

        lda #$c0
        sta $d012 ; break on a particular raster

        lda #<.raster
        sta $fffe
        lda #>.raster
        sta $ffff

        lda #$01
        sta $d01a ; enable raster interrupts

        cli

.wait
        lda .rnglo
        adc #$1
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #$aa
        sta .rnghi
        eor .rnglo
        sta .rnglo

        jmp .wait

.drawcirclesub
        lda $10
        sta .circleload+1
        sta .circlestore+1

        lda $11
        sta .circleload+2
        sta .circlestore+2

        lda $12
        clc
        adc #7
        bmi .dontdrawline
        cmp #22
        bcs .dontdrawline
        asl
        asl
        asl
        sta $26 ; biased dx

        lda .circleradius
        clc
        adc #<.sqrtlut
        sta .sqrtlookup+1
        ldx #$7
.drawcircleloop
        txa
        clc
        adc $13
        bpl .nonnegativey
        eor #$ff ; invert the number
        clc
        adc #$1
.nonnegativey
        cmp #$8
        bcs .dontdrawline
        tay

.sqrtlookup
        lda .sqrtlut,Y
        cmp #$ff
        beq .dontdrawline
        ora $26
        tay

.rangelookup
        lda .rangelut,Y
.circleeor
        eor #$ff
.circleload
        and $ffff,X
.circlestore
        sta $ffff,X
.dontdrawline
        dex
        bpl .drawcircleloop
        rts

.drawcircleline
        lda #0
        sec
        sbc .circleoff
        sta $12
        jsr .drawcirclesub

        lda $12
        clc
        adc #$8
        sta $12
        lda $10
        clc
        adc #$8
        sta $10
        lda $11
        adc #$0
        sta $11
        jsr .drawcirclesub

        lda $12
        clc
        adc #$8
        sta $12
        lda $10
        clc
        adc #$8
        sta $10
        lda $11
        adc #$0
        sta $11
        jsr .drawcirclesub

        rts

.drawcircle
        lda .circlelo
        and #$7
        eor #$ff
        clc
        adc #$1
        sta $13
        ; ..
        lda .circlelo
        and #$f8
        sta $10
        lda .circlehi
        sta $11
        jsr .drawcircleline

        lda $13
        clc
        adc #$8
        sta $13
        ; ..
        lda .circlelo
        and #$f8
        clc
        adc #$40
        sta $10
        lda .circlehi
        adc #$1
        sta $11
        jsr .drawcircleline

        lda $13
        sec
        sbc #$10
        sta $13
        ; ..
        lda .circlelo
        and #$f8
        sec
        sbc #$40
        sta $10
        lda .circlehi
        sbc #$1
        sta $11
        jsr .drawcircleline

        rts

.raster
        lda #$ff
        sta $d019 ; clear interrupt condition
        cli

        lda #0
        sta .hitsound
        sta .circlesound

        lda #$29 ; and immediate
        sta .circleeor
        lda #$5D ; eor $ffff,X
        sta .circleload
        jsr .drawcircle

        lda .circleradiuscounter
        beq .nocircle
        clc
        adc #$02
        bpl .radiusup
        sec
        sbc #$08
        ora #$80
.radiusup
        sta .circleradiuscounter
        and #$7f
        bne .noresetradiusbit
        sta .circleradiuscounter
.noresetradiusbit
        and #$78
        sta .circleradius
.nocircle

        dec .missiletick
        beq .updatemissiles
        jmp .nomissiles

.updatemissiles
        lda #$04
        sta .missiletick

        dec .missilegentick
        bne .nogeneratemissile

        lda .level
        sec
        sbc #$1a
        bcs .morelevels
        lda #$18 ; we're generating one missile per 24 ticks, which is the limit
.morelevels
        clc
        adc #$18
        sta .level
        sta .missilegentick

.generateanother
        ; generate a new missile
        ldx .missilecount
        cpx #$8
        bcs .nogeneratemissile
        lda .rnglo
        sta .missilelo,X
        sta .valload+1
        lda #$20
        sta .missilehi,X
        sta .valload+2
        lda .rnghi
        and #$f0
        sta .missileslopes,X
.valload
        lda $ffff
        sta .missilelastvals,X
        inc .missilecount

        lda .rnglo
        adc #$1
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #$aa
        adc .rnglo
        sta .rnghi
        and #3

        bne .nogeneratemissile
        lda .level
        clc
        adc .missilegentick
        sta .missilegentick
        jmp .generateanother

.nogeneratemissile

        lda #<.missilelo
        sta .mldlo+1
        lda #<.missilehi
        sta .mldhi+1
        lda #<.missilebits
        sta .mldbits+1
        lda #<.missileslopes
        sta .mldslopes+1
        lda #<.missilelastvals
        sta .mldlastvals+1

        ldx #$0
        lda .missilecount
        bne .updatemissilesloop
        jmp .nomissiles
.updatemissilesloop

        ; these have labels so we can remove missiles as we loop
.mldlo
        lda .missilelo,X
        sta .missilelo,X
        sta .comparemissile+1
.mldhi
        lda .missilehi,X
        sta .missilehi,X
        sta .comparemissile+2
.mldbits
        lda .missilebits,X
        sta .missilebits,X
.mldslopes
        lda .missileslopes,X
        sta .missileslopes,X
.mldlastvals
        lda .missilelastvals,X
        sta .missilelastvals,X

.comparemissile
        eor $ffff
        and .missilebits,X
        bne .removemissile

        ; move missile downward
        clc
        lda .missilelo,X
        adc #$1
        tay
        and #$7
        bne .nomissileoverflowy
        tya
        clc
        adc #-$8
        clc
        adc #$40 ; one row is $140
        tay
        lda .missilehi,X
        adc #$1
        sta .missilehi,X
        cmp #$40 ; don't let missiles go too far
        bcc .nomissileoverflowy

        ; this missile needs to be removed
.removemissile
        inc .mldlo+1
        inc .mldhi+1
        inc .mldbits+1
        inc .mldslopes+1
        inc .mldlastvals+1
        dec .missilecount
        bne .updatemissilesloop
        jmp .nomissiles

.nomissileoverflowy
        tya
        sta .missilelo,X
        sta .mloadaddr+1
        sta .mstoreaddr+1
        lda .missilehi,X
        sta .mloadaddr+2
        sta .mstoreaddr+2

        ; move missile sideways if necessary
        lda .missileslopes,X
        tay
        and #$f
        bne .nosidewaysmissile

        tya
        lsr
        lsr
        lsr
        lsr
        adc #$01
        and #$7
        ora .missileslopes,X
        sta .missileslopes,X

        and #$80
        bne .leftwardmissile

        clc
        lda .missilebits,X
        ror
        ror .missilebits,X
        bcc .nosidewaysmissile

        lda .missilelo,X
        adc #7 ; actually want 8, but carry bit is set
        sta .missilelo,X
        sta .mloadaddr+1
        sta .mstoreaddr+1

        lda .missilehi,X
        bcc .nosidewaysmissile
        adc #0
        sta .missilehi,X
        sta .mloadaddr+2
        sta .mstoreaddr+2

        jmp .nosidewaysmissile

.leftwardmissile
        clc
        lda .missilebits,X
        rol
        rol .missilebits,X
        bcc .nosidewaysmissile

        lda .missilelo,X
        sbc #8
        sta .missilelo,X
        sta .mloadaddr+1
        sta .mstoreaddr+1

        lda .missilehi,X
        bcs .nosidewaysmissile
        sbc #0
        sta .missilehi,X
        sta .mloadaddr+2
        sta .mstoreaddr+2

.nosidewaysmissile
        dec .missileslopes,X

.mloadaddr
        lda $2000
        tay
        eor #$ff
        and .missilebits,X
        ora .hitsound
        sta .hitsound
        tya
        eor .missilebits,X
        sta .missilelastvals,X
.mstoreaddr
        sta $2000

        inx
        cpx .missilecount
        bcs .nomissiles
        jmp .updatemissilesloop
.nomissiles

.input
        lda $dc01
        tax
        and #$01
        bne .noup
        clc
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #0
        sta .rnghi
        dec $d001
        bne .noup
        inc $d001
.noup
        txa
        and #$02
        bne .nodown
        clc
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #0
        sta .rnghi
        inc $d001
        bne .nodown
        dec $d001
.nodown
        txa
        and #$04
        bne .noleft
        clc
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #0
        sta .rnghi
        lda $d000
        bne .nocheckleft
        dec $d010
        beq .nocheckleft
        inc $d010
        lda #$01
.nocheckleft
        clc
        adc #$ff
        sta $d000
.noleft
        txa
        and #$08
        bne .noright
        clc
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #0
        sta .rnghi
        inc $d000
        bne .noright
        lda $d010
        eor #$01
        bne .noresetright
        dec $d000
        eor #$01
.noresetright
        sta $d010
.noright
        txa
        and #$10
        bne .nobutton

        lda .circleradius
        bne .nobutton

        clc
        adc .rnglo
        sta .rnglo
        lda .rnghi
        adc #0
        sta .rnghi
        lda $d001
        clc
        adc #-40 ; extra offset for crosshair
        sta .scratch+0
        lda $d000
        sec
        sbc #23 ; extra offset for crosshair
        sta .scratch+1
        lda $d010
        sbc #0
        sta .scratch+2

        jmp .pixel2offset

.nobutton

        lda $dc01
        and #$10
        cmp .circlewaspressed
        beq .nopressedchange
        sta .circlewaspressed
        cmp #$0
        beq .nopressedchange
        ldx .circleradius
        bne .nopressedchange
        sta .circlesound
.nopressedchange

        lda #$49 ; eor immediate
        sta .circleeor
        lda #$3D ; and $ffff,X
        sta .circleload
        jsr .drawcircle

        lda .rnglo
        and #$0f
        clc
        adc .noisefreq
        eor #$ff
        sta $d400
        sta $d401
        lda .noisectl
        eor #$ff
        sta $d404
        lda .noisesr
        eor #$ff
        sta $d406

        lda .hitfreq
        eor #$ff
        sta $d407
        sta $d408
        lda .hitctl
        eor #$ff
        ldx .hitsound
        beq .nohitsound
        ora #$01
.nohitsound
        sta $d40B
        lda .hitad
        eor #$ff
        sta $d40c

        lda .circlefreq
        eor #$ff
        sta $d40e
        sta $d40f
        lda .circlectl
        eor #$ff
        ldx .circlesound
        beq .nocirclesound
        ora #$01
.nocirclesound
        sta $d412
        lda .circlead
        eor #$ff
        sta $d413
        lda .circlesr
        eor #$ff
        sta $d414

        lda .filtercutoff
        eor #$ff
        sta $d416
        lda #$01
        sta $d417

        lda #$1f
        sta $d418

        jmp .wait

; in:
; .scratch+0 = y
; .scratch+1 = low x
; .scratch+2 = high x
; out:
; .circlelo = low address
; .circlehi = high address
; .circleoff = offset in byte
.pixel2offset
        lda #$20
        sta .circlehi
        lda #$00
        sta .circlelo
        lda .scratch+0 ; y
        tay
        lsr
        lsr
        lsr
        clc
        beq .skipcolumnloop
        tax
.columnloop
        lda #$40 ; 40 columns * 8 = 0x140
        adc .circlelo
        sta .circlelo
        lda #$1
        adc .circlehi
        sta .circlehi
        dex
        bne .columnloop
.skipcolumnloop

        tya ; add low y bits
        and #$7
        adc .circlelo
        sta .circlelo
        lda #$0
        adc .circlehi
        sta .circlehi

        lda .scratch+1 ; x low
        tay
        and #$f8
        adc .circlelo
        sta .circlelo
        lda .scratch+2 ; x high
        adc .circlehi
        sta .circlehi

        tya
        and #$7
        sta .circleoff

        lda #2
        sta .circleradius
        sta .circleradiuscounter

        jmp .nobutton

        ds.b $1000, $ff
