mirror of
https://github.com/cc65/cc65.git
synced 2024-12-22 12:30:41 +00:00
1141 lines
40 KiB
NASM
1141 lines
40 KiB
NASM
;-----------------------------------------------------------------------------------
|
|
; KIMGFX: Simple pixel graphics for the MOS/Commodore KIM-1
|
|
;-----------------------------------------------------------------------------------
|
|
; (c) Plummer's Software Ltd, 04/25/2023 Created
|
|
; David Plummer
|
|
;-----------------------------------------------------------------------------------
|
|
;
|
|
; File: subs.asm Assembly language subroutines for KIMGFX
|
|
;
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
|
|
.SETCPU "6502"
|
|
|
|
.export _ClearScreen
|
|
.export _ScrollScreen
|
|
.export _SetPixel
|
|
.export _ClearPixel
|
|
.export _DrawCircle
|
|
.export _DrawLine
|
|
.export _AscToPet
|
|
.export _ReverseBits
|
|
.export _DrawChar
|
|
.export _CharOut
|
|
.export _Demo
|
|
.export _Delay
|
|
.export _getch
|
|
|
|
|
|
.import _font8x8_basic
|
|
|
|
; This is the assumed location of the MTU visible memory board's 8K of memory. You can adjust this
|
|
; constant to refelct other locations as needed.
|
|
|
|
SCREEN = $A000
|
|
|
|
; Note that even though these constants are defined here and respected, there are still going to be
|
|
; logic assumptions in GetPixelAddress that assume a 320x200 screen. If you change these, you'll
|
|
; need to adjust GetPixelAddress to match.
|
|
|
|
SCREEN_WIDTH = 320
|
|
SCREEN_HEIGHT = 200
|
|
SCREEN_BYTES = SCREEN_WIDTH * SCREEN_HEIGHT / 8
|
|
CHARWIDTH = 8
|
|
CHARHEIGHT = 8
|
|
BYTESPERROW = (SCREEN_WIDTH / 8)
|
|
BYTESPERCHARROW = (BYTESPERROW * 8)
|
|
CHARSPERROW = (SCREEN_WIDTH / CHARWIDTH)
|
|
ROWSPERCOLUMN = (SCREEN_HEIGHT / CHARHEIGHT)
|
|
LASTROW = SCREEN + SCREEN_BYTES - BYTESPERCHARROW
|
|
|
|
.segment "ZEROPAGE"
|
|
|
|
btpt: .res 1
|
|
|
|
dest:
|
|
dest_lo: .res 1
|
|
dest_hi: .res 1
|
|
|
|
src:
|
|
src_lo: .res 1
|
|
src_hi: .res 1
|
|
|
|
adp1:
|
|
adp1_lo: .res 1
|
|
adp1_hi: .res 1
|
|
|
|
adp2:
|
|
adp2_lo: .res 1
|
|
adp2_hi: .res 1
|
|
|
|
scroll_src:
|
|
scroll_src_lo: .res 1
|
|
scroll_src_hi: .res 1
|
|
|
|
scroll_dest:
|
|
scroll_dest_lo: .res 1
|
|
scroll_dest_hi: .res 1
|
|
|
|
|
|
.segment "DATA"
|
|
|
|
; Arguments for graphics functions
|
|
|
|
_x1cord: .res 2
|
|
_x2cord: .res 2
|
|
_y1cord: .res 2
|
|
_y2cord: .res 2
|
|
_cursorX: .res 1
|
|
_cursorY: .res 1
|
|
|
|
; Linedraw
|
|
|
|
dx: .res 2
|
|
dy: .res 2
|
|
e2: .res 2
|
|
sx: .res 1
|
|
sy: .res 1
|
|
dltemp: .res 2
|
|
pixel: .res 1
|
|
|
|
; DrawCircle
|
|
|
|
xval: .res 2 ; These could move to zeropage for perf, but presume we
|
|
yval: .res 2 ; we want to minimize the amount we grow zero page use
|
|
err: .res 2
|
|
temp: .res 2
|
|
tempa: .res 1
|
|
tempx: .res 1
|
|
tempy: .res 1
|
|
temp2: .res 2
|
|
x0: .res 2
|
|
y0: .res 2
|
|
|
|
; CharOut
|
|
|
|
tempstr: .res 2
|
|
|
|
.export _x1cord ; Make sure these show up on the C side as zero page
|
|
.export _x2cord
|
|
.export _y1cord
|
|
.export _y2cord
|
|
.export _cursorX
|
|
.export _cursorY
|
|
|
|
.segment "CODE"
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; GetPixelAddress - Calculate the address of a pixel in the video memory
|
|
;-----------------------------------------------------------------------------------
|
|
; Based on MTU PIXADR code
|
|
;-----------------------------------------------------------------------------------
|
|
; In: _x1cord (16-bit)
|
|
; _y1cord (16-bit)
|
|
; Out: adp1 (16-bit) Address of pixel to set
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_GetPixelAddress:
|
|
lda _x1cord ; compute bit address first
|
|
sta adp1 ; also transfer x1cord to adp1
|
|
and #$07 ; + which is simply the low 3 bits of x
|
|
sta btpt
|
|
lda _x1cord+1 ; finish transferring x1cord to adp1
|
|
sta adp1+1
|
|
lsr adp1+1 ; double shift adp1 right 3 to get
|
|
ror adp1 ; int(xcord/8 )
|
|
lsr adp1+1
|
|
ror adp1
|
|
lsr adp1+1
|
|
ror adp1
|
|
sec ; and temporary storage
|
|
lda _y1cord
|
|
sta adp2
|
|
sta temp
|
|
lda #0
|
|
sbc _y1cord+1
|
|
sta adp2+1
|
|
sta temp+1
|
|
asl adp2 ; compute 40*(y1cord)
|
|
rol adp2+1 ; 2*(y1cord)
|
|
asl adp2
|
|
rol adp2+1 ; 4*(y1cord)
|
|
lda adp2 ; add in temporary save of (y1cord)
|
|
clc ; to make 5*(y1cord)
|
|
adc temp
|
|
sta adp2
|
|
lda adp2+1
|
|
adc temp+1
|
|
sta adp2+1 ; 5*(y1cord)
|
|
asl adp2 ; 10*(1cord)
|
|
rol adp2+1
|
|
asl adp2 ; 20#(y1cord)
|
|
rol adp2+1
|
|
asl adp2 ; 40*(y1cord)
|
|
rol adp2+1
|
|
lda adp2 ; add in int(x1cord/8) computed earlier
|
|
clc
|
|
adc adp1
|
|
sta adp1
|
|
lda adp2+1
|
|
adc adp1+1
|
|
adc #>SCREEN ; add in vmorg*256
|
|
sta adp1+1 ; final result
|
|
rts ; return
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; Mask tables for individual pixel subroutines
|
|
;
|
|
; MSKTB1 is a table of 1 bits corresponding to bit numbers
|
|
; MSKTB2 is a table of 0 bits corresponding to bit numbers
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
msktb1: .byte $80,$40,$20,$10,$08,$04,$02,$01
|
|
msktb2: .byte $7F,$BF,$DF,$EF,$F7,$FB,$FD,$FE
|
|
|
|
_Delay: pha
|
|
sta temp
|
|
txa
|
|
pha
|
|
tya
|
|
pha
|
|
|
|
@loopa: ldx #$ff
|
|
@loopx: ldy #$ff
|
|
@loopy: dey
|
|
bne @loopy
|
|
dex
|
|
bne @loopx
|
|
dec temp
|
|
bne @loopa
|
|
|
|
pla
|
|
tay
|
|
pla
|
|
tax
|
|
pla
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; SetPixel - Set a pixel in the video memory
|
|
;-----------------------------------------------------------------------------------
|
|
; x - _x1cord (16-bit)
|
|
; y - _y1cord (16-bit)
|
|
;-----------------------------------------------------------------------------------
|
|
; Mask tables for individual pixel subroutines
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_SetPixel: jsr _GetPixelAddress
|
|
ldy btpt ; get bit number in y
|
|
lda msktb1,y ; get a byte with that bit =1, others =0
|
|
ldy #0
|
|
ora (adp1),y ; combine the bit with the addressed vm
|
|
sta (adp1),y ; byte
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; ClearPixel - Clears a pixel in the video memory
|
|
;-----------------------------------------------------------------------------------
|
|
; x - _x1cord (16-bit)
|
|
; y - _y1cord (16-bit)
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_ClearPixel: jsr _GetPixelAddress
|
|
ldy btpt ; get bit number in y
|
|
lda msktb2,y ; get a byte with that bit =0, others =1
|
|
ldy #0
|
|
and (adp1),y ; remove the bit from the addressed vm
|
|
sta (adp1),y ; byte
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; ClearScreen - Clears the entire video memory (and thus the screen)
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_ClearScreen:
|
|
lda #$00
|
|
|
|
ldx #<SCREEN
|
|
stx dest_lo
|
|
ldx #>SCREEN
|
|
stx dest_hi
|
|
|
|
ldy #0
|
|
: sta (dest), y ; Loop unwound by a factor of 8, which means our iny before the branchh
|
|
iny ; will still work as it's on a page crossing boundary.
|
|
sta (dest), y ; This will avoid most of the overhead of the branch.
|
|
iny
|
|
sta (dest), y
|
|
iny
|
|
sta (dest), y
|
|
iny
|
|
sta (dest), y
|
|
iny
|
|
sta (dest), y
|
|
iny
|
|
sta (dest), y
|
|
iny
|
|
sta (dest), y
|
|
iny
|
|
bne :-
|
|
|
|
inc dest_hi
|
|
ldx dest_hi
|
|
cpx #>SCREEN + $20
|
|
bne :-
|
|
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; ScrollScreen - Scrolls the entire video memory (and thus the screen) up one row
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
BYTES_TO_MOVE = SCREEN_BYTES - BYTESPERCHARROW
|
|
PAGES_TO_MOVE = BYTES_TO_MOVE / 256
|
|
|
|
_ScrollScreen:
|
|
pha
|
|
tya
|
|
pha
|
|
txa
|
|
pha
|
|
|
|
; Load the source (A140) and destination (A000) addresses
|
|
lda #<(SCREEN+BYTESPERCHARROW)
|
|
sta scroll_src_lo
|
|
lda #>(SCREEN+BYTESPERCHARROW)
|
|
sta scroll_src_hi
|
|
lda #<SCREEN
|
|
sta scroll_dest_lo
|
|
lda #>SCREEN
|
|
sta scroll_dest_hi
|
|
|
|
ldx #PAGES_TO_MOVE
|
|
@outerLoop:
|
|
ldy #0
|
|
@innerLoop: ;
|
|
; I could do this faster in self-modifying code (avoiding the zero page overhead) but then it
|
|
; couldn't go into ROM
|
|
|
|
lda (scroll_src),y ; I've unwound the loop to do 8 bytes at a time. Since we're doing full pages
|
|
sta (scroll_dest),y ; as long as we unwind the loop to do 8 bytes at a time, we know we'll still
|
|
iny ; do the final increment on a page boundary.
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
lda (scroll_src),y
|
|
sta (scroll_dest),y
|
|
iny
|
|
bne @innerLoop ; If Y overflows, it will be 0, so won't branch
|
|
inc scroll_src_hi
|
|
inc scroll_dest_hi
|
|
dex
|
|
bne @outerLoop
|
|
|
|
; Clear the last line
|
|
lda #<LASTROW
|
|
sta scroll_dest_lo
|
|
lda #>LASTROW
|
|
sta scroll_dest_hi
|
|
lda #$00
|
|
ldy #0
|
|
fullPageLoop:
|
|
sta (scroll_dest_lo),y
|
|
iny
|
|
bne fullPageLoop
|
|
inc scroll_dest_hi
|
|
partialPageLoop:
|
|
sta (scroll_dest_lo),y
|
|
iny
|
|
cpy #BYTESPERCHARROW - 256 ; Only clear up to the 64th byte (256 + 64 == 320)
|
|
bne partialPageLoop
|
|
|
|
pla
|
|
tax
|
|
pla
|
|
tay
|
|
pla
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; DrawCircle - Draws a circle in video memory of a given radius at a given coord
|
|
;-----------------------------------------------------------------------------------
|
|
; x - _x1cord (16-bit)
|
|
; y - _y1cord (16-bit)
|
|
; radius - _y2cord (16-bit)
|
|
;-----------------------------------------------------------------------------------
|
|
; Implements the midpoint circle algorithm without floating point or trig functions
|
|
;-----------------------------------------------------------------------------------
|
|
; int x = radius;
|
|
; int y = 0;
|
|
; int err = 0;
|
|
;
|
|
; while (x >= y)
|
|
; {
|
|
; SETPIXEL(x0 + x, y0 + y, val);
|
|
; SETPIXEL(x0 + y, y0 + x, val);
|
|
; SETPIXEL(x0 - y, y0 + x, val);
|
|
; SETPIXEL(x0 - x, y0 + y, val);
|
|
; SETPIXEL(x0 - x, y0 - y, val);
|
|
; SETPIXEL(x0 - y, y0 - x, val);
|
|
; SETPIXEL(x0 + y, y0 - x, val);
|
|
; SETPIXEL(x0 + x, y0 - y, val);
|
|
;
|
|
; y++;
|
|
; err += 1 + 2 * y;
|
|
; if (2 * (err - x) + 1 > 0) {
|
|
; x--;
|
|
; err += 1 - 2 * x;
|
|
; }
|
|
; }
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_DrawCircle: lda _x1cord ; x0 = _x1cord
|
|
sta x0
|
|
lda _x1cord+1
|
|
sta x0+1
|
|
lda _y1cord ; y0 = _y1cord
|
|
sta y0
|
|
lda _y1cord+1
|
|
sta y0+1
|
|
|
|
lda _y2cord ; x = radius
|
|
sta xval
|
|
lda _y2cord+1
|
|
sta xval+1
|
|
|
|
lda #$0 ; yval = 0;
|
|
sta yval
|
|
sta yval+1
|
|
sta err ; err = 0;
|
|
sta err+1
|
|
circleloop:
|
|
lda xval+1 ; if (xval < yval) we're done;
|
|
sec
|
|
cmp yval+1
|
|
bcc doneCircle ; if high byteof yval is greater, we can draw
|
|
bne doCircle ; it not greater and not equal, is less, so done
|
|
lda xval ; in other cases we need to compare the LSB next
|
|
cmp yval
|
|
bcs doCircle ; if it's less, but MSB was equal, we go draw
|
|
|
|
doneCircle: rts
|
|
|
|
doCircle: lda x0 ; Draw the first of 8 symmetric quadrant copies
|
|
clc
|
|
adc yval
|
|
sta _x1cord
|
|
lda x0+1
|
|
adc yval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
sec
|
|
sbc xval
|
|
sta _y1cord
|
|
lda y0+1
|
|
sbc xval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 + y, y0 - x, val);
|
|
|
|
lda x0
|
|
sec
|
|
sbc yval
|
|
sta _x1cord
|
|
lda x0+1
|
|
sbc yval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
sec
|
|
sbc xval
|
|
sta _y1cord
|
|
lda y0+1
|
|
sbc xval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 - y, y0 - x, val);
|
|
|
|
lda x0
|
|
sec
|
|
sbc xval
|
|
sta _x1cord
|
|
lda x0+1
|
|
sbc xval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
sec
|
|
sbc yval
|
|
sta _y1cord
|
|
lda y0+1
|
|
sbc yval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 - x, y0 - y, val);
|
|
|
|
lda x0
|
|
sec
|
|
sbc xval
|
|
sta _x1cord
|
|
lda x0+1
|
|
sbc xval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
clc
|
|
adc yval
|
|
sta _y1cord
|
|
lda y0+1
|
|
adc yval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 - x, y0 + y, val);
|
|
|
|
lda x0
|
|
clc
|
|
adc yval
|
|
sta _x1cord
|
|
lda x0+1
|
|
adc yval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
clc
|
|
adc xval
|
|
sta _y1cord
|
|
lda y0+1
|
|
adc xval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 + y, y0 + x, val);
|
|
|
|
lda x0
|
|
clc
|
|
adc xval
|
|
sta _x1cord
|
|
lda x0+1
|
|
adc xval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
clc
|
|
adc yval
|
|
sta _y1cord
|
|
lda y0+1
|
|
adc yval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 + x, y0 + y, val);
|
|
|
|
lda x0
|
|
clc
|
|
adc xval
|
|
sta _x1cord
|
|
lda x0+1
|
|
adc xval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
sec
|
|
sbc yval
|
|
sta _y1cord
|
|
lda y0+1
|
|
sbc yval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 + x, y0 - y, val);
|
|
|
|
lda x0
|
|
sec
|
|
sbc yval
|
|
sta _x1cord
|
|
lda x0+1
|
|
sbc yval+1
|
|
sta _x1cord+1
|
|
lda y0
|
|
clc
|
|
adc xval
|
|
sta _y1cord
|
|
lda y0+1
|
|
adc xval+1
|
|
sta _y1cord+1
|
|
jsr _SetPixel ; SETPIXEL(x0 - y, y0 + x, val);
|
|
|
|
inc yval ; yval++;
|
|
bne :+
|
|
inc yval+1
|
|
|
|
: lda yval ; temp = 2 * yval + 1;
|
|
asl
|
|
sta temp
|
|
lda yval+1
|
|
rol
|
|
sta temp+1
|
|
inc temp
|
|
bne :+
|
|
inc temp+1
|
|
:
|
|
lda err ; err += temp
|
|
clc
|
|
adc temp
|
|
sta err
|
|
lda err+1
|
|
adc temp+1
|
|
sta err+1
|
|
; if (2 * (err - xval) + 1 > 0) then dec xval
|
|
lda err ; temp = err-xval
|
|
sec
|
|
sbc xval
|
|
sta temp
|
|
lda err+1
|
|
sbc xval+1
|
|
sta temp+1
|
|
|
|
asl temp ; temp = 2*(err-xval)+1
|
|
rol temp+1
|
|
inc temp
|
|
bne :+
|
|
inc temp+1
|
|
:
|
|
lda temp+1 ; if (temp > 0) we'll dec xval
|
|
bmi doneLoop ; less than zero, so no dec
|
|
bne decxval ; if not zero, go ahead and dec
|
|
|
|
lda temp ; MSB is zero so now check the LSB
|
|
beq doneLoop ; both bytes are zero, so no dec
|
|
|
|
decxval: lda xval ; xval--
|
|
bne :+
|
|
dec xval+1
|
|
: dec xval
|
|
|
|
updateerr: lda xval ; temp = xval * 2
|
|
asl
|
|
sta temp
|
|
lda xval+1
|
|
rol
|
|
sta temp+1
|
|
|
|
lda #1 ; temp2 == 1-temp == 1-(xval*2)
|
|
sec
|
|
sbc temp
|
|
sta temp2
|
|
lda #0
|
|
sbc temp+1
|
|
sta temp2+1
|
|
|
|
lda err ; err += 1-(xval*2)
|
|
clc
|
|
adc temp2
|
|
sta err
|
|
lda err+1
|
|
adc temp2+1
|
|
sta err+1
|
|
|
|
doneLoop: jmp circleloop
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; Character set translation tables
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
ascToPetTable: .byte $00,$01,$02,$03,$04,$05,$06,$07,$14,$20,$0d,$11,$93,$0a,$0e,$0f
|
|
.byte $10,$0b,$12,$13,$08,$15,$16,$17,$18,$19,$1a,$1b,$1c,$1d,$1e,$1f
|
|
.byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f
|
|
.byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f
|
|
.byte $40,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf
|
|
.byte $d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$5b,$5c,$5d,$5e,$5f
|
|
.byte $c0,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f
|
|
.byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$db,$dc,$dd,$de,$df
|
|
.byte $80,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8a,$8b,$8c,$8d,$8e,$8f
|
|
.byte $90,$91,$92,$0c,$94,$95,$96,$97,$98,$99,$9a,$9b,$9c,$9d,$9e,$9f
|
|
.byte $a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7,$a8,$a9,$aa,$ab,$ac,$ad,$ae,$af
|
|
.byte $b0,$b1,$b2,$b3,$b4,$b5,$b6,$b7,$b8,$b9,$ba,$bb,$bc,$bd,$be,$bf
|
|
.byte $60,$61,$62,$63,$64,$65,$66,$67,$68,$69,$6a,$6b,$6c,$6d,$6e,$6f
|
|
.byte $70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$7a,$7b,$7c,$7d,$7e,$7f
|
|
.byte $e0,$e1,$e2,$e3,$e4,$e5,$e6,$e7,$e8,$e9,$ea,$eb,$ec,$ed,$ee,$ef
|
|
.byte $f0,$f1,$f2,$f3,$f4,$f5,$f6,$f7,$f8,$f9,$fa,$fb,$fc,$fd,$fe,$ff
|
|
|
|
; PETSCI to Ascii lookup table - not current used, so commented out, but can be used to map fonts
|
|
;
|
|
;
|
|
petToAscTable: .byte $00,$01,$02,$03,$04,$05,$06,$07,$14,$09,$0d,$11,$93,$0a,$0e,$0f
|
|
.byte $10,$0b,$12,$13,$08,$15,$16,$17,$18,$19,$1a,$1b,$1c,$1d,$1e,$1f
|
|
.byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f
|
|
.byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f
|
|
.byte $40,$61,$62,$63,$64,$65,$66,$67,$68,$69,$6a,$6b,$6c,$6d,$6e,$6f
|
|
.byte $70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$7a,$5b,$5c,$5d,$5e,$5f
|
|
.byte $c0,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf
|
|
.byte $d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$db,$dc,$dd,$de,$df
|
|
.byte $80,$81,$82,$83,$84,$85,$86,$87,$88,$89,$8a,$8b,$8c,$8d,$8e,$8f
|
|
.byte $90,$91,$92,$0c,$94,$95,$96,$97,$98,$99,$9a,$9b,$9c,$9d,$9e,$9f
|
|
.byte $a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7,$a8,$a9,$aa,$ab,$ac,$ad,$ae,$af
|
|
.byte $b0,$b1,$b2,$b3,$b4,$b5,$b6,$b7,$b8,$b9,$ba,$bb,$bc,$bd,$be,$bf
|
|
.byte $60,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f
|
|
.byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$7b,$7c,$7d,$7e,$7f
|
|
.byte $a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7,$a8,$a9,$aa,$ab,$ac,$ad,$ae,$af
|
|
.byte $b0,$b1,$b2,$b3,$b4,$b5,$b6,$b7,$b8,$b9,$ba,$bb,$bc,$bd,$be,$bf
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; PetToAsc - Convert a PETSCII character to ASCII
|
|
;-----------------------------------------------------------------------------------
|
|
; A - Character to convert
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_AscToPet: tay
|
|
lda ascToPetTable, y
|
|
rts
|
|
|
|
_PetToAsc: tay
|
|
lda petToAscTable, Y
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; ReverseBits - Reverse the bits in a byte
|
|
;-----------------------------------------------------------------------------------
|
|
; A = octet to be reversed
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_ReverseBits:
|
|
ldx #8 ; set counter to 8 (for 8 bits)
|
|
lda #0 ; initialize A to 0
|
|
sta temp ; clear result byte (accumulator for the reversed octet)
|
|
: asl ; shift leftmost bit of input into carry
|
|
lda temp ; load the temporary result byte into A
|
|
ror ; rotate carry into leftmost bit of result
|
|
sta temp ; store the updated result back into memory
|
|
dex ; decrement counter
|
|
bne :- ; repeat until all bits are processed
|
|
lda temp ; load the final reversed byte into A
|
|
rts ; return with result in A
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; LoadFont - Makes sure the font data is ready to use. This usually requires
|
|
; reversing the bits so that they match the bit order of the screen
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_LoadFont: ldx #3
|
|
lda #<_font8x8_basic
|
|
sta adp1_lo
|
|
lda #>_font8x8_basic
|
|
sta adp1_hi
|
|
ldy #0
|
|
@loop: lda (adp1), y
|
|
jsr _ReverseBits
|
|
sta (adp1), y
|
|
iny
|
|
bne @loop
|
|
|
|
inc adp1_lo
|
|
bne :+
|
|
inc adp1_hi
|
|
: dex
|
|
bne @loop
|
|
rts
|
|
|
|
ScreenLineAddresses:
|
|
.word SCREEN + 0 * BYTESPERCHARROW, SCREEN + 1 * BYTESPERCHARROW
|
|
.word SCREEN + 2 * BYTESPERCHARROW, SCREEN + 3 * BYTESPERCHARROW
|
|
.word SCREEN + 4 * BYTESPERCHARROW, SCREEN + 5 * BYTESPERCHARROW
|
|
.word SCREEN + 6 * BYTESPERCHARROW, SCREEN + 7 * BYTESPERCHARROW
|
|
.word SCREEN + 8 * BYTESPERCHARROW, SCREEN + 9 * BYTESPERCHARROW
|
|
.word SCREEN + 10 * BYTESPERCHARROW, SCREEN + 11 * BYTESPERCHARROW
|
|
.word SCREEN + 12 * BYTESPERCHARROW, SCREEN + 13 * BYTESPERCHARROW
|
|
.word SCREEN + 14 * BYTESPERCHARROW, SCREEN + 15 * BYTESPERCHARROW
|
|
.word SCREEN + 16 * BYTESPERCHARROW, SCREEN + 17 * BYTESPERCHARROW
|
|
.word SCREEN + 18 * BYTESPERCHARROW, SCREEN + 19 * BYTESPERCHARROW
|
|
.word SCREEN + 20 * BYTESPERCHARROW, SCREEN + 21 * BYTESPERCHARROW
|
|
.word SCREEN + 22 * BYTESPERCHARROW, SCREEN + 23 * BYTESPERCHARROW
|
|
.word SCREEN + 24 * BYTESPERCHARROW
|
|
.assert( (* - ScreenLineAddresses) = ROWSPERCOLUMN * 2), error
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; DrawChar - Draws an ASCII character at char location x, y
|
|
;-----------------------------------------------------------------------------------
|
|
; 0 <= x < 40
|
|
; 0 <= y < 25
|
|
; Preserves all registers, but its not very threadsafe or reentrant
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_DrawChar: sty tempy
|
|
stx tempx
|
|
sta tempa
|
|
|
|
tya ; Get the address in screen memory where this
|
|
asl ; character X/Y cursor pos should be drawn
|
|
tay
|
|
txa
|
|
clc
|
|
adc ScreenLineAddresses, y
|
|
sta dest_lo
|
|
lda ScreenLineAddresses+1, y
|
|
adc #0
|
|
sta dest_hi
|
|
|
|
lda #0 ; Get the address in font memory where this
|
|
sta src_hi ; Petscii chracter lives (after conversion from
|
|
lda tempa ; ascii)
|
|
|
|
sty temp2
|
|
jsr _AscToPet
|
|
ldy temp2
|
|
|
|
asl
|
|
rol src_hi
|
|
asl
|
|
rol src_hi
|
|
asl
|
|
rol src_hi
|
|
clc
|
|
adc #<_font8x8_basic ; Add the base address of the font table to the offset
|
|
sta src_lo
|
|
lda src_hi
|
|
adc #>_font8x8_basic
|
|
sta src_hi
|
|
|
|
ldy #0 ; opy the character def to the screen, one byte at a time
|
|
ldx #0
|
|
: lda (src), y ; Copy this byte from the character def to the screen target
|
|
sta (dest, x)
|
|
lda dest_lo ; Advance to the next "scanline", or pixel row, down
|
|
clc
|
|
adc #<BYTESPERROW
|
|
sta dest_lo
|
|
lda dest_hi
|
|
adc #>BYTESPERROW
|
|
sta dest_hi
|
|
|
|
iny
|
|
cpy #8
|
|
bne :-
|
|
|
|
ldy tempy
|
|
ldx tempx
|
|
lda tempa
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; CursorOn - Turns on the text cursor and draws it at the current cursor pos
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
CursorOn: ldx _cursorX
|
|
ldy _cursorY
|
|
lda #'_'
|
|
jsr _DrawChar
|
|
rts
|
|
|
|
CursorOff: ldx _cursorX
|
|
ldy _cursorY
|
|
lda #' '
|
|
jsr _DrawChar
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; DrawText - Draws an ASCII char in A at the current cursor pos, saves all regs
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_CharOut: sta temp
|
|
lda #0
|
|
sta temp+1
|
|
txa
|
|
pha
|
|
tya
|
|
pha
|
|
|
|
ldx #<temp
|
|
ldy #>temp
|
|
jsr _DrawText
|
|
|
|
pla
|
|
tay
|
|
pla
|
|
tax
|
|
rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; Backspace - Erase the current character and move back one position. Does not
|
|
; move back up to previous line
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
Backspace: lda _cursorX
|
|
beq colzero
|
|
jsr CursorOff
|
|
dec _cursorX
|
|
jsr CursorOn
|
|
colzero: rts
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; DrawText - Draws an ASCII string at the current cursor position
|
|
;-----------------------------------------------------------------------------------
|
|
; XY - Pointer to the string to draw, stops on NUL or 255 chars later
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_DrawText: stx adp1_lo
|
|
sty adp1_hi
|
|
jsr CursorOff
|
|
|
|
ldy #0
|
|
checkHWrap: lda _cursorX
|
|
cmp #CHARSPERROW
|
|
bcc checkVWrap
|
|
lda #0
|
|
sta _cursorX
|
|
inc _cursorY
|
|
|
|
checkVWrap: lda _cursorY
|
|
cmp #ROWSPERCOLUMN
|
|
bcc loadChar
|
|
jsr _ScrollScreen
|
|
lda #ROWSPERCOLUMN-1
|
|
sta _cursorY
|
|
|
|
loadChar: lda (adp1), y
|
|
beq doneText
|
|
|
|
cmp #$0a
|
|
bne :+
|
|
|
|
lda #0 ; Back to the left edge
|
|
sta _cursorX
|
|
inc _cursorY ; Advance to the next line
|
|
iny
|
|
bne checkHWrap
|
|
|
|
: sty temp
|
|
ldx _cursorX
|
|
ldy _cursorY
|
|
jsr _DrawChar
|
|
ldy temp
|
|
inc _cursorX
|
|
iny
|
|
bne checkHWrap
|
|
|
|
doneText: jsr CursorOn
|
|
rts
|
|
|
|
demoText1: .byte " *** COMMODORE KIM-1 SHELL V0.1 ***", $0A, $0A
|
|
.byte " 60K RAM SYSTEM. 49152 BYTES FREE.", $0A, $0A, $00
|
|
readyText: .byte $0A,"READY.", $0A, 00
|
|
|
|
_Demo: jsr _ClearScreen
|
|
lda #0
|
|
sta _cursorX
|
|
sta _cursorY
|
|
ldx #<demoText1
|
|
ldy #>demoText1
|
|
jsr _DrawText
|
|
rts
|
|
|
|
_Ready: ldx #<readyText
|
|
ldy #>readyText
|
|
jsr _DrawText
|
|
rts
|
|
|
|
|
|
;-----------------------------------------------------------------------------------
|
|
; DrawLine - Draws a line between two points
|
|
;-----------------------------------------------------------------------------------
|
|
; _x1cord (16-bit)
|
|
; _y1cord ( 8-bit)
|
|
; _x2cord (16-bit)
|
|
; _y2cord ( 8-bit)
|
|
;-----------------------------------------------------------------------------------
|
|
; Implements something like Bresenham's algorithm for drawing a line:
|
|
;-----------------------------------------------------------------------------------
|
|
; void DrawLine(int x0, int y0, int x1, int y1, byte val)
|
|
; {
|
|
; int dx = abs(_x2cord - _x1cord), sx = _x1cord < _x2cord ? 1 : -1;
|
|
; int dy = abs(_y2cord - _y1cord), sy = _y1cord < _y2cord ? 1 : -1;
|
|
; int err = (dx > dy ? dx : -dy) / 2, e2;
|
|
;
|
|
; while (1)
|
|
; {
|
|
; SETPIXEL(_x1cord, _y1cord, val);
|
|
;
|
|
; if (_x1cord == _x2cord && _y1cord == _y2cord)
|
|
; break;
|
|
;
|
|
; e2 = err;
|
|
;
|
|
; if (e2 > -dx)
|
|
; {
|
|
; err -= dy;
|
|
; _x1cord += sx;
|
|
; }
|
|
; if (e2 < dy)
|
|
; {
|
|
; err += dx;
|
|
; _y1cord += sy;
|
|
; }
|
|
; }
|
|
; }
|
|
;-----------------------------------------------------------------------------------
|
|
|
|
_DrawLine: sta pixel
|
|
|
|
ldx #$01 ; positive x-step for now
|
|
stx sx
|
|
|
|
; Calculate dx = (x2cord - X1cord) and see if its positive or not
|
|
|
|
lda _x2cord ; Calculate dx = (x2cord - X1cord)
|
|
sec
|
|
sbc _x1cord
|
|
sta dx
|
|
lda _x2cord+1
|
|
sbc _x1cord+1
|
|
sta dx+1
|
|
bpl calcdy ; dx is positive (dx >= 0), so we're good
|
|
|
|
; dx was negative (dx < 0), so we set sx to -1 and get the absolute
|
|
; value by subtracting the other direction
|
|
|
|
ldx #$FF ; negative x-step
|
|
stx sx
|
|
lda _x1cord ; Calculate dx = (x2cord - X1cord)
|
|
sec
|
|
sbc _x2cord
|
|
sta dx
|
|
lda _x1cord+1
|
|
sbc _x2cord+1
|
|
sta dx+1
|
|
|
|
; Calculate dy = (y2cord - y1cord) and see if its positive or not
|
|
|
|
calcdy: ldx #$01 ; positive y-step for now
|
|
stx sy
|
|
lda _y2cord
|
|
sec
|
|
sbc _y1cord
|
|
sta dy
|
|
bcs positivedy ; If y2cord > y1cord, then dy is positive and we're good
|
|
|
|
; dy was negative (dy < 0), so we set sy to -1 and get the absolute value
|
|
|
|
ldx #$FF ; negative y-step
|
|
stx sy
|
|
lda _y1cord
|
|
sec
|
|
sbc _y2cord
|
|
sta dy
|
|
|
|
; Now we have dx and dy, so we can calculate err, but first we need
|
|
; to see if dx > dy or not
|
|
|
|
positivedy: lda dx+1 ; Check if dx > dy (both are always positive now)
|
|
bne dxgt ; If MSB of dx is greater than zero, then dx > dy since dy is 8-bits
|
|
lda dy
|
|
cmp dx
|
|
bcs dygte
|
|
|
|
dxgt: lda dx ; We found dx>dy so set err = dx / 2
|
|
sta err
|
|
lda dx+1
|
|
lsr
|
|
sta err+1 ; err = dx/2
|
|
ror err
|
|
jmp loop
|
|
|
|
dygte: lda #0 ; we found dx <= dy so set err = -dy / 2
|
|
sec
|
|
sbc dy ; else err = -dy / 2
|
|
ror
|
|
ora #$80
|
|
sta err
|
|
lda #$FF
|
|
sta err+1
|
|
|
|
; Now we have dx, dy, and err, so we can start drawing pixels
|
|
|
|
loop: lda pixel
|
|
beq clearpixel
|
|
jsr _SetPixel ; Plot the current _x1cord, _y1cord
|
|
jmp next
|
|
clearpixel: jsr _ClearPixel ; Clear the current _x1cord, _y1cord
|
|
|
|
next: lda _x1cord ; if (_x1cord == _x2cord && _y1cord == _y2cord) then we rts
|
|
cmp _x2cord
|
|
bne noteq
|
|
lda _y1cord
|
|
cmp _y2cord
|
|
bne noteq
|
|
lda _x1cord+1
|
|
cmp _x2cord+1
|
|
bne noteq
|
|
|
|
rts
|
|
|
|
noteq: lda err ; e2 = err
|
|
sta e2
|
|
lda err+1
|
|
sta e2+1
|
|
|
|
; Check the two update conditions for x and y, and update if needed
|
|
|
|
lda e2 ; if (e2 > -dx) is the same as if (e2 + dx > 0), so we test that because its easier
|
|
clc ; If its true then we dec err and inc _x1cord
|
|
adc dx
|
|
sta temp
|
|
lda e2+1
|
|
adc dx+1
|
|
bmi doneupdatex ; If result is negative, then e2 + dx < 0, so we don't dec err or inc _x1cord
|
|
bne stepx ; If MSB is non-zero, then e2 + dx > 0, so we DO dec err and inc _x1cord
|
|
lda temp ; If result is zero in MSB, then we check the LSB here
|
|
beq doneupdatex ; If LSB is zero, then we don't dec err or inc _x1cord
|
|
; We already know e2 + dx > 0, so LSB can't be negative
|
|
stepx: lda sx
|
|
bmi decx
|
|
incxval: inc _x1cord ; _x1cord += 1 because sx == 1
|
|
bne updatexerr
|
|
inc _x1cord+1
|
|
jmp updatexerr
|
|
|
|
decx: lda _x1cord ; _x1cord += 1 because sx == 1
|
|
sec
|
|
sbc #1
|
|
sta _x1cord
|
|
lda _x1cord+1
|
|
sbc #0
|
|
sta _x1cord+1
|
|
|
|
updatexerr: lda err ; err -= dy
|
|
sec
|
|
sbc dy
|
|
sta err
|
|
lda err+1
|
|
sbc #0
|
|
sta err+1
|
|
|
|
doneupdatex: lda e2+1 ; if (e2 < dy) then we inc err and inc _y1cord
|
|
bmi updateerry ; If MSB is negative, then e2 < dy, so we inc err and inc _y1cord
|
|
bne noupdatey ; If the MSB of e2 is set and positive, then we know e2 > dy, so we don't inc err or inc _y1cord
|
|
lda e2
|
|
sec
|
|
sbc dy
|
|
beq noupdatey ; e2 - dy == 0 so we don't inc err or inc _y1cord
|
|
bcs noupdatey ; if e2 was large enough that carry never cleared, then e2 > dy do no update
|
|
|
|
updateerry: lda err ; err += dx
|
|
clc
|
|
adc dx
|
|
sta err
|
|
lda err+1
|
|
adc dx+1
|
|
sta err+1
|
|
|
|
stepy: lda _y1cord
|
|
clc
|
|
adc sy
|
|
sta _y1cord
|
|
|
|
noupdatey: jmp loop
|
|
|
|
_getch: jsr $1E5A ; Get character using Monitor ROM call
|
|
and #$7F ; Clear top bit
|
|
cmp #$0D ; Check for '\r'
|
|
bne gotch ; ...if CR character
|
|
lda #$0A ; Replace with '\n'
|
|
gotch: rts
|