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