Lab 02 - Working With Graphics In Assembly
In the previous lab, we worked on a basic assembly program that renders various colors on a screen. This taught us the basics of registers, loops, branching, as well as optimization techniques for assembly code. For this lab, we will take it a step higher and attempt to animate a bouncing graphic on a screen.
Initial Program
And here is the initial program given to us
And here is the code for it.
; ; draw-image-subroutine.6502 ; ; This is a routine that can place an arbitrary ; rectangular image on to the screen at given ; coordinates. ; ; Chris Tyler 2024-09-17 ; Licensed under GPLv2+ ; ; ; The subroutine is below starting at the ; label "DRAW:" ; ; Test code for our subroutine ; Moves an image diagonally across the screen ; Zero-page variables define XPOS $20 define YPOS $21 ; Set up the data structure ; The syntax #<LABEL returns the low byte of LABEL ; The syntax #>LABEL returns the high byte of LABEL LDA #<G_X ; POINTER TO GRAPHIC STA $10 LDA #>G_X STA $11 LDA #$05 STA $12 ; IMAGE WIDTH STA $13 ; IMAGE HEIGHT ; Set initial position X=Y=0 LDA #$00 STA XPOS STA YPOS ; Main loop for diagonal animation MAINLOOP: ; Set pointer to the image ; Use G_O or G_X as desired LDA #<G_O STA $10 LDA #>G_O STA $11 ; Place the image on the screen LDA #$10 ; Address in zeropage of the data structure LDX XPOS ; X position LDY YPOS ; Y position JSR DRAW ; Call the subroutine ; Delay to show the image LDY #$00 LDX #$50 DELAY: DEY BNE DELAY DEX BNE DELAY ; Set pointer to the blank graphic LDA #<G_BLANK STA $10 LDA #>G_BLANK STA $11 ; Draw the blank graphic to clear the old image LDA #$10 ; LOCATION OF DATA STRUCTURE LDX XPOS LDY YPOS JSR DRAW ; Increment the position INC XPOS INC YPOS ; Continue for 29 frames of animation LDA #28 CMP XPOS BNE MAINLOOP ; Repeat infinitely JMP $0600 ; ========================================== ; ; DRAW :: Subroutine to draw an image on ; the bitmapped display ; ; Entry conditions: ; A - location in zero page of: ; a pointer to the image (2 bytes) ; followed by the image width (1 byte) ; followed by the image height (1 byte) ; X - horizontal location to put the image ; Y - vertical location to put the image ; ; Exit conditions: ; All registers are undefined ; ; Zero-page memory locations define IMGPTR $A0 define IMGPTRH $A1 define IMGWIDTH $A2 define IMGHEIGHT $A3 define SCRPTR $A4 define SCRPTRH $A5 define SCRX $A6 define SCRY $A7 DRAW: ; SAVE THE X AND Y REG VALUES STY SCRY STX SCRX ; GET THE DATA STRUCTURE TAY LDA $0000,Y STA IMGPTR LDA $0001,Y STA IMGPTRH LDA $0002,Y STA IMGWIDTH LDA $0003,Y STA IMGHEIGHT ; CALCULATE THE START OF THE IMAGE ON ; SCREEN AND PLACE IN SCRPTRH ; ; THIS IS $0200 (START OF SCREEN) + ; SCRX + SCRY * 32 ; ; WE'LL DO THE MULTIPLICATION FIRST ; START BY PLACING SCRY INTO SCRPTR LDA #$00 STA SCRPTRH LDA SCRY STA SCRPTR ; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32 LDY #$05 ; NUMBER OF SHIFTS MULT: ASL SCRPTR ; PERFORM 16-BIT LEFT SHIFT ROL SCRPTRH DEY BNE MULT ; NOW ADD THE X VALUE LDA SCRX CLC ADC SCRPTR STA SCRPTR LDA #$00 ADC SCRPTRH STA SCRPTRH ; NOW ADD THE SCREEN BASE ADDRESS OF $0200 ; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT LDA #$02 CLC ADC SCRPTRH STA SCRPTRH ; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH ; NOW WE HAVE A POINTER TO THE IMAGE IN MEM ; COPY A ROW OF IMAGE DATA COPYROW: LDY #$00 ROWLOOP: LDA (IMGPTR),Y STA (SCRPTR),Y INY CPY IMGWIDTH BNE ROWLOOP ; NOW WE NEED TO ADVANCE TO THE NEXT ROW ; ADD IMGWIDTH TO THE IMGPTR LDA IMGWIDTH CLC ADC IMGPTR STA IMGPTR LDA #$00 ADC IMGPTRH STA IMGPTRH ; ADD 32 TO THE SCRPTR LDA #32 CLC ADC SCRPTR STA SCRPTR LDA #$00 ADC SCRPTRH STA SCRPTRH ; DECREMENT THE LINE COUNT AND SEE IF WE'RE ; DONE DEC IMGHEIGHT BNE COPYROW RTS ; ========================================== ; 5x5 pixel images ; Image of a blue "O" on black background G_O: DCB $00,$0e,$0e,$0e,$00 DCB $0e,$00,$00,$00,$0e DCB $0e,$00,$00,$00,$0e DCB $0e,$00,$00,$00,$0e DCB $00,$0e,$0e,$0e,$00 ; Image of a yellow "X" on a black background G_X: DCB $07,$00,$00,$00,$07 DCB $00,$07,$00,$07,$00 DCB $00,$00,$07,$00,$00 DCB $00,$07,$00,$07,$00 DCB $07,$00,$00,$00,$07 ; Image of a black square G_BLANK: DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00 DCB $00,$00,$00,$00,$00
Making the graphic bounce around
So far all this code is doing is moving a graphic to from the top-left to the bottom-right of the screen in an diagonal line, and then immediately resets back to the top. What we want to accomplish in this lab is to make the graphic bounce around the screen instead of just restarting its same path over and over again.
In order to achieve this, instead of restarting the animation whenever the graphic hits the border of the screen, we should instead either decrement or increment the x and y positions accordingly in order to keep the image in the screen. To be specific, when the X-position reaches the edge of the screen (at x = 27) we need to start decrementing the X-position until it reaches the other edge (at x = 0). Once it reaches that, then we change the direction and start incrementing the X-position until it reaches the other edge and reapeat. The same logic applies for the Y-position.
To do that we need to make a few changes.
First, I added two new key values called RIGHT and DOWN. These values will be used as flags that tell us whether the graphic should be moving right or left, or down or up. I initialize these values to true (1) so the graphic will initially move right and down.
; ... other code ; Zero-page variables define XPOS $20 define YPOS $21 define RIGHT $00 ; boolean define DOWN $01 ; boolean ; ... other code ; Set RIGHT-DOWN movement to true LDA #$01 STA RIGHT ; $0000 STA DOWN ; $0001 ; ... other code
With that set up, we are now ready to make the changes. We need to replace the old code responsible for moving the image to something more dynamic based on these new flags.
;===========
; OLD CODE
;===========
; Increment the position
INC XPOS
INC YPOS
;===========
; NEW CODE
;===========
; Update image position based on RIGHT-DOWN flags
LDA #1
CMP RIGHT ; Check if RIGHT is true(1)
BNE MOV_LEFT ; If RIGHT is not true(1), jump to MOV_LEFT
INC XPOS ; RIGHT is true, so increment XPOS
LDA #27
CMP XPOS ; Check if new XPOS is equal to 27
BNE MOV_Y ; If new XPOS not yet equal to 27, skip to MOV_Y
DEC RIGHT ; XPOS is equal to 27, so we want to move left. Set RIGHT to false(0)
JMP MOV_Y ; proceed to MOV_Y
MOV_LEFT: DEC XPOS ; RIGHT is false, so go left, decrement XPOS
LDA #0
CMP XPOS ; Check if new XPOS is equal to 0
BNE MOV_Y ; If new XPOS is not yet equal to 0, skip to MOV_Y
INC RIGHT ; XPOS is equal to 0, so we want to move right, set RIGHT to true(1)
MOV_Y: LDA #1
CMP DOWN ; Check if DOWN is true(1)
BNE MOV_UP ; If DOWN is not true(1), jump to MOV_UP
INC YPOS ; DOWN is true, so increment YPOS
LDA #27
CMP YPOS ; Check if new YPOS is equal to 27
BNE MAINLOOP ; if new YPOS is not yet equal to 27, no need to update flags, jump back to MAINLOOP
DEC DOWN ; YPOS is equal to 27, so we want to move up. Set DOWN to false(0)
JMP MAINLOOP ; We are done updating flags, jump back to MAINLOOP
MOV_UP: DEC YPOS ; DOWN is false, so go up, decrement YPOS
LDA #0
CMP YPOS ; Check if new YPOS is equal to 0
BNE MAINLOOP ; If new YPOS is not yet equal to 0, no need to update flags, jump back to MAINLOOP
INC DOWN ; YPOS is equal to 0, so we want to move down, set DOWN to true(1)
JMP MAINLOOP ; We are done updating flags, jump back to MAINLOOP
Integrating this new piece of code into whole program gives us this modified version of the program.
;
; This program animates a bouncing graphic within a screen.
;
; Zero-page variables
define XPOS $20
define YPOS $21
define RIGHT $00 ; boolean
define DOWN $01 ; boolean
; Set up the data structure
; The syntax #<LABEL returns the low byte of LABEL
; The syntax #>LABEL returns the high byte of LABEL
LDA #<G_X ; POINTER TO GRAPHIC
STA $10
LDA #>G_X
STA $11
LDA #$05
STA $12 ; IMAGE WIDTH
STA $13 ; IMAGE HEIGHT
; Set initial position X=14, Y=0
LDA #14
STA XPOS ; $0020
LDA #$0
STA YPOS ; $0021
; Set RIGHT-DOWN movement to true
LDA #$01
STA RIGHT ; $0000
STA DOWN ; $0001
; Main loop for diagonal animation
MAINLOOP:
; Set pointer to the image
; Use G_O or G_X as desired
LDA #<G_O
STA $10
LDA #>G_O
STA $11
; Place the image on the screen
LDA #$10 ; Address in zeropage of the data structure
LDX XPOS ; X position
LDY YPOS ; Y position
JSR DRAW ; Call the subroutine
; Delay to show the image
LDY #$00
LDX #$50
DELAY:
DEY
BNE DELAY
DEX
BNE DELAY
; Set pointer to the blank graphic
LDA #<G_BLANK
STA $10
LDA #>G_BLANK
STA $11
; Draw the blank graphic to clear the old image
LDA #$10 ; LOCATION OF DATA STRUCTURE
LDX XPOS
LDY YPOS
JSR DRAW
; Update image position based on RIGHT-DOWN flags
LDA #1
CMP RIGHT ; Check if RIGHT is true(1)
BNE MOV_LEFT ; If RIGHT is not true(1), jump to MOV_LEFT
INC XPOS ; RIGHT is true, so increment XPOS
LDA #27
CMP XPOS ; Check if new XPOS is equal to 27
BNE MOV_Y ; If new XPOS not yet equal to 27, skip to MOV_Y
DEC RIGHT ; XPOS is equal to 27, so we want to move left. Set RIGHT to false(0)
JMP MOV_Y ; proceed to MOV_Y
MOV_LEFT: DEC XPOS ; RIGHT is false, so go left, decrement XPOS
LDA #0
CMP XPOS ; Check if new XPOS is equal to 0
BNE MOV_Y ; If new XPOS is not yet equal to 0, skip to MOV_Y
INC RIGHT ; XPOS is equal to 0, so we want to move right, set RIGHT to true(1)
MOV_Y: LDA #1
CMP DOWN ; Check if DOWN is true(1)
BNE MOV_UP ; If DOWN is not true(1), jump to MOV_UP
INC YPOS ; DOWN is true, so increment YPOS
LDA #27
CMP YPOS ; Check if new YPOS is equal to 27
BNE MAINLOOP ; if new YPOS is not yet equal to 27, no need to update flags, jump back to MAINLOOP
DEC DOWN ; YPOS is equal to 27, so we want to move up. Set DOWN to false(0)
JMP MAINLOOP ; We are done updating flags, jump back to MAINLOOP
MOV_UP: DEC YPOS ; DOWN is false, so go up, decrement YPOS
LDA #0
CMP YPOS ; Check if new YPOS is equal to 0
BNE MAINLOOP ; If new YPOS is not yet equal to 0, no need to update flags, jump back to MAINLOOP
INC DOWN ; YPOS is equal to 0, so we want to move down, set DOWN to true(1)
JMP MAINLOOP ; We are done updating flags, jump back to MAINLOOP
; ==========================================
;
; DRAW :: Subroutine to draw an image on
; the bitmapped display
;
; Entry conditions:
; A - location in zero page of:
; a pointer to the image (2 bytes)
; followed by the image width (1 byte)
; followed by the image height (1 byte)
; X - horizontal location to put the image
; Y - vertical location to put the image
;
; Exit conditions:
; All registers are undefined
;
; Zero-page memory locations
define IMGPTR $A0
define IMGPTRH $A1
define IMGWIDTH $A2
define IMGHEIGHT $A3
define SCRPTR $A4
define SCRPTRH $A5
define SCRX $A6
define SCRY $A7
DRAW:
; SAVE THE X AND Y REG VALUES
STY SCRY
STX SCRX
; GET THE DATA STRUCTURE
TAY
LDA $0000,Y
STA IMGPTR
LDA $0001,Y
STA IMGPTRH
LDA $0002,Y
STA IMGWIDTH
LDA $0003,Y
STA IMGHEIGHT
; CALCULATE THE START OF THE IMAGE ON
; SCREEN AND PLACE IN SCRPTRH
;
; THIS IS $0200 (START OF SCREEN) +
; SCRX + SCRY * 32
;
; WE'LL DO THE MULTIPLICATION FIRST
; START BY PLACING SCRY INTO SCRPTR
LDA #$00
STA SCRPTRH
LDA SCRY
STA SCRPTR
; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32
LDY #$05 ; NUMBER OF SHIFTS
MULT:
ASL SCRPTR ; PERFORM 16-BIT LEFT SHIFT
ROL SCRPTRH
DEY
BNE MULT
; NOW ADD THE X VALUE
LDA SCRX
CLC
ADC SCRPTR
STA SCRPTR
LDA #$00
ADC SCRPTRH
STA SCRPTRH
; NOW ADD THE SCREEN BASE ADDRESS OF $0200
; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT
LDA #$02
CLC
ADC SCRPTRH
STA SCRPTRH
; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH
; NOW WE HAVE A POINTER TO THE IMAGE IN MEM
; COPY A ROW OF IMAGE DATA
COPYROW:
LDY #$00
ROWLOOP:
LDA (IMGPTR),Y
STA (SCRPTR),Y
INY
CPY IMGWIDTH
BNE ROWLOOP
; NOW WE NEED TO ADVANCE TO THE NEXT ROW
; ADD IMGWIDTH TO THE IMGPTR
LDA IMGWIDTH
CLC
ADC IMGPTR
STA IMGPTR
LDA #$00
ADC IMGPTRH
STA IMGPTRH
; ADD 32 TO THE SCRPTR
LDA #32
CLC
ADC SCRPTR
STA SCRPTR
LDA #$00
ADC SCRPTRH
STA SCRPTRH
; DECREMENT THE LINE COUNT AND SEE IF WE'RE
; DONE
DEC IMGHEIGHT
BNE COPYROW
RTS
; ==========================================
; 5x5 pixel images
; Image of a blue "O" on black background
G_O:
DCB $00,$0e,$0e,$0e,$00
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $00,$0e,$0e,$0e,$00
; Image of a yellow "X" on a black background
G_X:
DCB $07,$00,$00,$00,$07
DCB $00,$07,$00,$07,$00
DCB $00,$00,$07,$00,$00
DCB $00,$07,$00,$07,$00
DCB $07,$00,$00,$00,$07
; Image of a black square
G_BLANK:
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
And here is the output of the modified program.
As you can see, the graphic is now successfully bouncing around the screen.
Changing the graphic everytime it bounces off an edge
Currently our program can only animate either one of two graphic choices; i.e, a blue "O" or a yellow "X". As an additional experiment, let's try to change the graphic everytime it bounces off an edge.
To do this, we need to add a new flag that tells us whether we need to change the graphic or not. For this I chose to call it USE_O which represents whether or not we should render the graphic "O".
This flag will be found in zeropage address $0002 and will be initialized to true(1).
; ... other code ; Zero-page variables define XPOS $20 define YPOS $21 define RIGHT $00 ; boolean define DOWN $01 ; boolean define USE_O $02 ; boolean ; ... other code LDA #$01 ; value for true ; Set RIGHT-DOWN movement to true STA RIGHT STA DOWN ; Set USE_O to true (which means to use G_O as the starting image) STA USE_O ; ... other code
Next, we need to make sure that our graphic changes depending on the status of the the USE_O flag.
;=========== ; OLD CODE ;=========== ; Set pointer to the image ; Use G_O or G_X as desired LDA #<G_O STA $10 LDA #>G_O STA $11 ; Place the image on the screen LDA #$10 ; Address in zeropage of the data structure LDX XPOS ; X position LDY YPOS ; Y position JSR DRAW ; Call the subroutine ;=========== ; NEW CODE ;=========== LDA #$01 CMP USE_O ; if USE_O is not true(1), jump to USE_X (use G_X) BNE USE_X ; if USE_O is true(1), use G_O LDA #<G_O STA $10 LDA #>G_O STA $11 JMP DRW_IMG USE_X: LDA #<G_X STA $10 LDA #>G_X STA $11 DRW_IMG: ; Place the image on the screen LDA #$10 ; Address in zeropage of the data structure LDX XPOS ; X position LDY YPOS ; Y position JSR DRAW ; Call the subroutine
Once that is set, we need to update the flag accordingly whenever the graphic hits the edge of the screen. Since branch destinations must be within -128 to +127 bytes of the byte after the branch instruction, I had to put the logic into a separate subroutine because it was getting quite long inside the MAINLOOP.
;===========
; OLD CODE
;===========
; Update image position based on RIGHT-DOWN flags
LDA #1
CMP RIGHT ; Check if RIGHT is true(1)
BNE MOV_LEFT ; If RIGHT is not true(1), jump to MOV_LEFT
INC XPOS ; RIGHT is true, so increment XPOS
LDA #27
CMP XPOS ; Check if new XPOS is equal to 27
BNE MOV_Y ; If new XPOS not yet equal to 27, skip to MOV_Y
DEC RIGHT ; XPOS is equal to 27, so we want to move left. Set RIGHT to false(0)
JMP MOV_Y ; proceed to MOV_Y
MOV_LEFT: DEC XPOS ; RIGHT is false, so go left, decrement XPOS
LDA #0
CMP XPOS ; Check if new XPOS is equal to 0
BNE MOV_Y ; If new XPOS is not yet equal to 0, skip to MOV_Y
INC RIGHT ; XPOS is equal to 0, so we want to move right, set RIGHT to true(1)
MOV_Y: LDA #1
CMP DOWN ; Check if DOWN is true(1)
BNE MOV_UP ; If DOWN is not true(1), jump to MOV_UP
INC YPOS ; DOWN is true, so increment YPOS
LDA #27
CMP YPOS ; Check if new YPOS is equal to 27
BNE MAINLOOP ; if new YPOS is not yet equal to 27, no need to update flags, jump back to MAINLOOP
DEC DOWN ; YPOS is equal to 27, so we want to move up. Set DOWN to false(0)
JMP MAINLOOP ; We are done updating flags, jump back to MAINLOOP
MOV_UP: DEC YPOS ; DOWN is false, so go up, decrement YPOS
LDA #0
CMP YPOS ; Check if new YPOS is equal to 0
BNE MAINLOOP ; If new YPOS is not yet equal to 0, no need to update flags, jump back to MAINLOOP
INC DOWN ; YPOS is equal to 0, so we want to move down, set DOWN to true(1)
JMP MAINLOOP ; We are done updating flags, jump back to MAINLOOP
;===========
; NEW CODE
;===========
; Update RIGHT, DOWN, and USE_O flags for next render
JSR UPDATE_FLAGS
; Loop
JMP MAINLOOP
UPDATE_FLAGS: ; (Subroutine): Update flags for next render
LDA #1
CMP RIGHT ; Check if RIGHT is true(1)
BNE MOV_LEFT ; If RIGHT is not true(1), jump to MOV_LEFT
INC XPOS ; RIGHT is true, so increment XPOS
LDA #27
CMP XPOS ; Check if new XPOS is equal to 27
BNE MOV_Y ; If new XPOS not yet equal to 27, skip to MOV_Y
DEC RIGHT ; XPOS is equal to 27, so we want to move left. Set RIGHT to false(0)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
JMP MOV_Y ; proceed to MOV_Y
MOV_LEFT:
DEC XPOS ; RIGHT is false, so go left, decrement XPOS
LDA #0
CMP XPOS ; Check if new XPOS is equal to 0
BNE MOV_Y ; If new XPOS is not yet equal to 0, skip to MOV_Y
INC RIGHT ; XPOS is equal to 0, so we want to move right, set RIGHT to true(1)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
MOV_Y:
LDA #1
CMP DOWN ; Check if DOWN is true(1)
BNE MOV_UP ; If DOWN is not true(1), jump to MOV_UP
INC YPOS ; DOWN is true, so increment YPOS
LDA #27
CMP YPOS ; Check if new YPOS is equal to 27
BNE RETURN ; if new YPOS is not yet equal to 27, no need to update flags, return from subroutine
DEC DOWN ; YPOS is equal to 27, so we want to move up. Set DOWN to false(0)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
BNE RETURN ; We are done updating flags, return from subroutine
MOV_UP:
DEC YPOS ; DOWN is false, so go up, decrement YPOS
LDA #0
CMP YPOS ; Check if new YPOS is equal to 0
BNE RETURN ; If new YPOS is not yet equal to 0, no need to update flags, return from subroutine
INC DOWN ; YPOS is equal to 0, so we want to move down, set DOWN to true(1)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
RETURN:
RTS ; We are done updating flags, return from subroutine
Integrating these changes into our program gives us this modified version of the program.
;
; This program animates a bouncing graphic within a screen.
; The graphic changes everytime it bounces off an edge.
;
; Zero-page variables
define XPOS $20
define YPOS $21
define RIGHT $00 ; boolean
define DOWN $01 ; boolean
define USE_O $02 ; boolean
; Set up the data structure
; The syntax #<LABEL returns the low byte of LABEL
; The syntax #>LABEL returns the high byte of LABEL
LDA #<G_X ; POINTER TO GRAPHIC
STA $10
LDA #>G_X
STA $11
LDA #$05
STA $12 ; IMAGE WIDTH
STA $13 ; IMAGE HEIGHT
; Set initial position X=14, Y=0
LDA #14
STA XPOS ; $0020
LDA #$0
STA YPOS ; $0021
LDA #$01 ; value for true
; Set RIGHT-DOWN movement to true
STA RIGHT ; $0000
STA DOWN ; $0001
; Set USE_O to true (which means to use G_O as the starting image)
STA USE_O ; $0002
; Main loop for diagonal animation
MAINLOOP:
LDA #$01
CMP USE_O
; if USE_O is not true(1), jump to USE_X (use G_X)
BNE USE_X
; if USE_O is true(1), use G_O
LDA #<G_O
STA $10
LDA #>G_O
STA $11
JMP DRW_IMG
USE_X:
LDA #<G_X
STA $10
LDA #>G_X
STA $11
DRW_IMG: ; Place the image on the screen
LDA #$10 ; Address in zeropage of the data structure
LDX XPOS ; X position
LDY YPOS ; Y position
JSR DRAW ; Call the subroutine
; Delay to show the image
LDY #$00
LDX #$50
DELAY:
DEY
BNE DELAY
DEX
BNE DELAY
; Set pointer to the blank graphic
LDA #<G_BLANK
STA $10
LDA #>G_BLANK
STA $11
; Draw the blank graphic to clear the old image
LDA #$10 ; LOCATION OF DATA STRUCTURE
LDX XPOS
LDY YPOS
JSR DRAW
; Update RIGHT, DOWN, and USE_O flags for next render
JSR UPDATE_FLAGS
; Loop
JMP MAINLOOP
; Update flags for next render
UPDATE_FLAGS:
LDA #1
CMP RIGHT ; Check if RIGHT is true(1)
BNE MOV_LEFT ; If RIGHT is not true(1), jump to MOV_LEFT
INC XPOS ; RIGHT is true, so increment XPOS
LDA #27
CMP XPOS ; Check if new XPOS is equal to 27
BNE MOV_Y ; If new XPOS not yet equal to 27, skip to MOV_Y
DEC RIGHT ; XPOS is equal to 27, so we want to move left. Set RIGHT to false(0)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
JMP MOV_Y ; proceed to MOV_Y
MOV_LEFT:
DEC XPOS ; RIGHT is false, so go left, decrement XPOS
LDA #0
CMP XPOS ; Check if new XPOS is equal to 0
BNE MOV_Y ; If new XPOS is not yet equal to 0, skip to MOV_Y
INC RIGHT ; XPOS is equal to 0, so we want to move right, set RIGHT to true(1)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
MOV_Y:
LDA #1
CMP DOWN ; Check if DOWN is true(1)
BNE MOV_UP ; If DOWN is not true(1), jump to MOV_UP
INC YPOS ; DOWN is true, so increment YPOS
LDA #27
CMP YPOS ; Check if new YPOS is equal to 27
BNE RETURN ; if new YPOS is not yet equal to 27, no need to update flags, return from subroutine
DEC DOWN ; YPOS is equal to 27, so we want to move up. Set DOWN to false(0)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
BNE RETURN ; We are done updating flags, return from subroutine
MOV_UP:
DEC YPOS ; DOWN is false, so go up, decrement YPOS
LDA #0
CMP YPOS ; Check if new YPOS is equal to 0
BNE RETURN ; If new YPOS is not yet equal to 0, no need to update flags, return from subroutine
INC DOWN ; YPOS is equal to 0, so we want to move down, set DOWN to true(1)
LDA #1
EOR USE_O ; Calculate (1 XOR USE_O) to toggle the USE_O value (change the graphic)
STA USE_O ; Store result back into USE_O
RETURN:
RTS ; We are done updating flags, return from subroutine
; ==========================================
;
; DRAW :: Subroutine to draw an image on
; the bitmapped display
;
; Entry conditions:
; A - location in zero page of:
; a pointer to the image (2 bytes)
; followed by the image width (1 byte)
; followed by the image height (1 byte)
; X - horizontal location to put the image
; Y - vertical location to put the image
;
; Exit conditions:
; All registers are undefined
;
; Zero-page memory locations
define IMGPTR $A0
define IMGPTRH $A1
define IMGWIDTH $A2
define IMGHEIGHT $A3
define SCRPTR $A4
define SCRPTRH $A5
define SCRX $A6
define SCRY $A7
DRAW:
; SAVE THE X AND Y REG VALUES
STY SCRY
STX SCRX
; GET THE DATA STRUCTURE
TAY
LDA $0000,Y
STA IMGPTR
LDA $0001,Y
STA IMGPTRH
LDA $0002,Y
STA IMGWIDTH
LDA $0003,Y
STA IMGHEIGHT
; CALCULATE THE START OF THE IMAGE ON
; SCREEN AND PLACE IN SCRPTRH
;
; THIS IS $0200 (START OF SCREEN) +
; SCRX + SCRY * 32
;
; WE'LL DO THE MULTIPLICATION FIRST
; START BY PLACING SCRY INTO SCRPTR
LDA #$00
STA SCRPTRH
LDA SCRY
STA SCRPTR
; NOW DO 5 LEFT SHIFTS TO MULTIPLY BY 32
LDY #$05 ; NUMBER OF SHIFTS
MULT:
ASL SCRPTR ; PERFORM 16-BIT LEFT SHIFT
ROL SCRPTRH
DEY
BNE MULT
; NOW ADD THE X VALUE
LDA SCRX
CLC
ADC SCRPTR
STA SCRPTR
LDA #$00
ADC SCRPTRH
STA SCRPTRH
; NOW ADD THE SCREEN BASE ADDRESS OF $0200
; SINCE THE LOW BYTE IS $00 WE CAN IGNORE IT
LDA #$02
CLC
ADC SCRPTRH
STA SCRPTRH
; NOTE WE COULD HAVE DONE TWO: INC SCRPTRH
; NOW WE HAVE A POINTER TO THE IMAGE IN MEM
; COPY A ROW OF IMAGE DATA
COPYROW:
LDY #$00
ROWLOOP:
LDA (IMGPTR),Y
STA (SCRPTR),Y
INY
CPY IMGWIDTH
BNE ROWLOOP
; NOW WE NEED TO ADVANCE TO THE NEXT ROW
; ADD IMGWIDTH TO THE IMGPTR
LDA IMGWIDTH
CLC
ADC IMGPTR
STA IMGPTR
LDA #$00
ADC IMGPTRH
STA IMGPTRH
; ADD 32 TO THE SCRPTR
LDA #32
CLC
ADC SCRPTR
STA SCRPTR
LDA #$00
ADC SCRPTRH
STA SCRPTRH
; DECREMENT THE LINE COUNT AND SEE IF WE'RE
; DONE
DEC IMGHEIGHT
BNE COPYROW
RTS
; ==========================================
; 5x5 pixel images
; Image of a blue "O" on black background
G_O:
DCB $00,$0e,$0e,$0e,$00
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $0e,$00,$00,$00,$0e
DCB $00,$0e,$0e,$0e,$00
; Image of a yellow "X" on a black background
G_X:
DCB $07,$00,$00,$00,$07
DCB $00,$07,$00,$07,$00
DCB $00,$00,$07,$00,$00
DCB $00,$07,$00,$07,$00
DCB $07,$00,$00,$00,$07
; Image of a black square
G_BLANK:
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
DCB $00,$00,$00,$00,$00
Here is the output of the modified program.
As you can see the graphic now changes everytime it bounces around in the screen.
Thoughts
This lab was quite tricky because it ramped up the complexity. We had to work with macros, subroutines, and implementing data structures using DCB which were all concepts that were a bit unfamiliar to me, but after going through this lab I got to understand these concepts a lot better and would now say that I have these tools under my toolbelt. I am getting more and more acquainted with assembly each week, and I look forward to learning more as I delve into the subsequent labs to come.
Comments
Post a Comment