Lab 3 - 6502 Program Lab

Introduction

For this lab, I initially intended to implement a Hangman game using 6502 assembly. However, as I started working on the graphics and colour changes, I realized that handling the colour updates for the game was much more complicated than I anticipated. Specifically, I had trouble filling the screen with colours after each incorrect guess.

Rather than getting stuck, I decided to go for a simpler approach: I switched to a Number Guessing Game. This game still fits the requirements of the lab—providing user input, offering feedback on “too high” or “too low,” and displaying a dynamic game flow. The number guessing game is also less complex and allowed me to focus more on learning assembly language and experimenting with graphics.

In a future post, I will share the challenges I faced with the Hangman game, especially the graphics issue that I couldn’t overcome in this project.


Code

; ROM routine entry points

define      INIT_SCREEN      $ff81 ; Initialize/clear screen

define      READ_CHAR        $ffcf ; Input character from keyboard

define      DISPLAY_CHAR     $ffd2 ; Output character to screen

define      SCREEN_SIZE      $ffed ; Get screen size

define      SET_CURSOR       $fff0 ; Set cursor coordinates


; Constants

define      RANDOM_NUM       $0082 ; Random number storage

define      GUESSES_LEFT     $0081 ; Remaining guesses counter

define      USER_INPUT       $23   ; Temporary storage for user input

define      DISPLAY_COLOR    $24   ; Color storage for feedback


; Zero-page variables

define        PTR_LOW        $00

define        PTR_HIGH       $01


; Start of program

    jsr DISPLAY_MESSAGE  ; Display title

    dcb "-","N","u","m","b","e","r",32,"G","u","e","s","s","i","n","g",32,"G","a","m","e","-",$0d

    dcb $0d     

    dcb "Y","o","u",32,"c","a","n",32,"t","r","y",32,"o","n","l","y",32,"f","o","u","r",32,"t","i","m","e","s",".",$0d,$00


START:

    LDA $FE            ; Load random value

    AND #$0F           ; Mask upper bits

    CMP #10            ; Ensure value is between 0-9

    BCS START          ; Retry if not within range

    STA RANDOM_NUM     ; Store valid number


    LDA RANDOM_NUM

    CLC

    ADC #$30           ; Convert to ASCII ('0' = $30, '9' = $39)

    LDA #$00

    STA $40        

    LDA #$02    

    STA $41


    LDA #$02           ; Set initial guess color

    STA DISPLAY_COLOR

    LDA #$04           ; Set remaining guesses to 4

    STA GUESSES_LEFT


; Main game loop

NUMBER_GUESSING:

    jsr DISPLAY_MESSAGE

    dcb $0d

    dcb $0d,"Y","o","u","r",32,"i","n","p","u","t",32,"(","0","-","9",")",58,00


GET_USER_INPUT:

    LDA #$00        ; Clear input storage

    STA USER_INPUT

    JSR READ_CHAR   ; Get user input from keyboard


    CMP #$30        ; Check if input is '0' or higher

    BCC GET_USER_INPUT

    CMP #$3A        ; Check if input is '9' or lower

    BCS GET_USER_INPUT


    SEC

    SBC #$30        ; Convert to binary

    STA USER_INPUT  ; Store user input


    LDA RANDOM_NUM  ; Compare with random number

    CMP USER_INPUT

    BEQ CORRECT_GUESS


    BCS TOO_LOW

    JMP TOO_HIGH


TOO_LOW:

    jsr FEEDBACK_LOOP

    jsr DISPLAY_MESSAGE

    dcb $0d,"T","o","o",32,"l","o","w","!",00

    DEC GUESSES_LEFT

    BNE TRY_AGAIN

    JMP GAME_OVER


TOO_HIGH:

    jsr FEEDBACK_LOOP

    jsr DISPLAY_MESSAGE

    dcb $0d,"T","o","o",32,"h","i","g","h","!",00

    DEC GUESSES_LEFT

    BNE TRY_AGAIN

    JMP GAME_OVER


TRY_AGAIN:

    JSR DISPLAY_MESSAGE

    dcb $0d,"T","r","y",32,"a","g","a","i","n",00

    JSR NUMBER_GUESSING


CORRECT_GUESS:

    jsr DISPLAY_MESSAGE

    dcb $0d,"C","o","n","g","r","a","t","s","!",00

    jsr DISPLAY_MESSAGE

    dcb $0d,"C","o","r","r","e","c","t",32,"a","n","s","w","e","r",46,00

    

    jsr FILL_GRAPHICS_WITH_RANDOM_COLORS   ; Fill screen with random colors


    JMP GAME_EXIT   ; Exit game


GAME_OVER:

    jsr DISPLAY_MESSAGE

    dcb $0d,"G","a","m","e",32,"O","v","e","r",00

    jsr DISPLAY_MESSAGE

    dcb $0d,"T","h","e",32,"a","n","s","w","e","r",32,"w","a","s",32,00

    LDA RANDOM_NUM

    CLC

    ADC #$30

    JSR DISPLAY_CHAR

    JMP GAME_EXIT   ; Exit game


FEEDBACK_LOOP:

    LDA #$02          ; Set color for feedback

    STA DISPLAY_COLOR

    LDA DISPLAY_COLOR

    STA ($40),Y

    INY              

    BNE FEEDBACK_LOOP

    INC $41           

    LDX $41           

    RTS


DISPLAY_MESSAGE:

    PLA

    CLC

    ADC #$01

    STA PTR_LOW

    PLA

    STA PTR_HIGH

    TYA

    PHA

    LDY #$00

print_next:

    LDA (PTR_LOW),Y

    BEQ print_done

    JSR DISPLAY_CHAR

    INY

    JMP print_next

print_done:

    TYA

    CLC

    ADC PTR_LOW

    STA PTR_LOW

    LDA PTR_HIGH

    ADC #$00

    STA PTR_HIGH

    PLA

    TAY

    LDA PTR_HIGH

    PHA

    LDA PTR_LOW

    PHA

    RTS


FILL_GRAPHICS_WITH_RANDOM_COLORS:

    LDA #$00         ; Set pointer to $0200 (low byte)

    STA $40          ; Store low byte in $40

    LDA #$02         ; Set high byte (assuming screen starts at $0200)

    STA $41          ; Store high byte in $41


    LDY #$00         ; Reset index to start at 0


FILL_LOOP:

    LDA $FE          ; Read random number from PRNG

    AND #$0F         ; Mask to get color code

    STA ($40),Y      ; Store random color at pointer+Y

    INY              ; Increment Y


    BNE FILL_LOOP    ; If Y is not 0, continue loop


    INC $41          ; Increment high byte (next row)

    LDX $41          ; Check current page

    CPX #$06         ; If less than $06, continue

    BNE FILL_LOOP    ; Repeat for next page


    RTS              ; Return when done

1. Basic Code Structure Implementation

The core of this lab is about implementing a number guessing game where the user attempts to guess a random number in the shortest number of tries. For the basic structure, I followed the lab instructions, which required receiving user input, providing feedback (“too high” or “too low”), and tracking the number of remaining guesses.

The game starts with a simple description for the user. Once the user inputs a guess, the game compares the input to the random number. If the guess is too high or too low, the game provides the corresponding feedback and decreases the remaining guesses. This continues until the user either guesses the correct number or runs out of guesses.

However, I encountered my first problem when I tried to implement the feature where the graphics change based on the number of remaining guesses. I initially thought that the graphics should reflect the remaining chances, but the result was messy and didn’t work as expected.

I then came across a post from my classmate Ziyang, who had a much simpler and more effective approach. Ziyang’s solution inspired me to shift my focus. Instead of changing the colours based on the remaining guesses, I realized that I should change the colours each time the user makes an incorrect guess. This new idea worked much better, and I was able to implement it successfully.

In addition to the color logic, I also referred to Ziyang’s blog when writing my DISPLAY_MESSAGE subroutine, which handles printing strings to the screen. Their implementation uses a classic 6502 assembly technique: extracting the return address from the stack and using it as a pointer to a null-terminated string. This pattern lets us call the subroutine with a string placed directly afterward, making it efficient and compact for displaying messages.

This method isn’t unique to his blog—it’s a widely used convention in 6502 programming. For instance, the tutorial “6502, Printing, Pointers, and Subroutines” documents a nearly identical approach, explaining how to use the stack to find the string’s address and loop until a null terminator ($00) is reached.

Ziyang’s implementation was especially helpful because it applied this technique clearly in a lab context, and I was able to adapt it into my own version of DISPLAY_MESSAGE, with small changes like using DISPLAY_CHAR instead of CHROUT.


2. Simple Explanation After the Game Starts

After the game starts, the part where the program receives user input plays a crucial role. To ensure the input is valid, input validation is done. In the code, the CMP #$30 and CMP #$3A instructions check whether the input is between ‘0’ and ‘9’. This step is necessary to prevent invalid input, such as letters or special characters. For example, if the user enters something other than a number, the input is ignored, and the user is prompted again to enter a valid number. This process is essential to validate the game’s input and prevents errors that could arise from invalid input. Essentially, it acts as a safeguard to ensure only proper numerical input is accepted.


3. Display Color Variable and Functionality

The part of the game that changes display colours is key in providing the user with visual feedback. The DISPLAY_COLOR variable is used to store the colour, which is primarily used when providing feedback to the user. For example, the instruction LDA #$02 sets the colour, and STA DISPLAY_COLOR stores the value in memory, which then applies the colour change on screen. By using this variable, the colour change is applied in real-time to the screen, providing immediate feedback when the user’s guess is wrong. This change in colour helps the user recognize their mistake more effectively and adds a dynamic aspect to the game, making it more interactive. The use of the colour variable enhances the visual interaction, allowing the game to respond to the user’s actions.

Additionally, these colour changes play a crucial role in the user experience. The colour updates help reinforce that the game is responding immediately to the user’s actions. When the user’s guess is wrong, the colour change instantly lets them know that something was incorrect, which improves the user’s overall experience by providing clear, immediate feedback.


4. Adding a More Exciting Graphic After the User Wins

When I was near the completion of the code, I thought it would be fun to reward the user with more exciting graphics if they correctly guessed the number. This idea came to me while reflecting on my work from Lab 1, where I experimented with random colours for each pixel on the screen. I realized I could reuse that idea and make the winning screen visually more dynamic.

By utilizing the random colour concept from Lab 1, I was able to fill the screen with random colours whenever the user successfully guessed the number, adding a fun and rewarding visual aspect to the game.












5. Output Changes Based on User Input

The feedback loop is the core interaction of the game. Whenever the user inputs a guess, feedback must be reflected on the screen in real-time. This is handled in the FEEDBACK_LOOP subroutine. The instruction LDA #$02 sets the color, and STA DISPLAY_COLOR stores it in memory, which is then reflected on the screen using STA ($40), Y. This ensures that each time the user makes an incorrect guess, the screen color changes, providing immediate visual feedback.

Additionally, each time the user makes a guess, the screen is updated and the colour changes based on the feedback, making the game feel responsive and interactive. This process allows the game to react instantly to the user’s input, making the game more engaging. The feedback loop is crucial for enhancing the real-time interactivity of the game, as it ensures that the game responds to the user’s actions immediately and visually.











These concepts are crucial for improving the user experience because they create a more interactive and responsive game. Real-time feedback increases the game’s immersion by providing immediate responses to the user’s actions. For instance, when a user guesses too high or too low, the game immediately reacts, giving visual cues (like colour changes) that show the result of the guess. This helps users stay engaged and provides a more dynamic gameplay experience. Furthermore, reacting immediately to incorrect input is important because it prevents frustration and keeps the user informed, making them feel that their input is always acknowledged.


Reflection

This project was much harder than I initially expected, especially due to the limitations of the 6502 assembly language. Working with low-level instructions, managing memory manually, and controlling the graphics output were all challenging aspects of this lab. Despite the obstacles, I feel more confident in my ability to approach assembly programming and problem-solving creatively.

It was also a valuable lesson in collaboration—even though I didn’t directly collaborate with Ziyang, his approach inspired me to rethink my solution, which significantly improved my project. Special thanks to you!


Reference

6502, Printing, Pointers, and Subroutines

Ziyang’s solution


What’s Next?

In the next post, I’ll dive into the Hangman game I initially planned to create. I’ll share what went wrong with the graphics and how I tried to overcome those challenges. Stay tuned!





Comments

Popular posts from this blog

SPO600 2025 Winter Project - Stage 1: Create a Basic GCC Pass (part1)

SPO600 2025 Winter Project - Stage 2: GIMPLE Level Clone Analysis and Pruning (part4)

Lab 1 - 6502 Assembly Language