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

Popular posts from this blog

Understanding Compiler Passes

Identifying the Essence of a Function Clone

Building GCC