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
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
Post a Comment