Friday, August 15, 2014

NES Trivia Gameplay

As covered in the NES Trivia Screen article, the code for this game is at http://blazinggames.com/books/NES/NESTrivia/ . We are only covering the most relevant snippets of code. If you wish to look at the main game loop in its entirety search the code for the MainGameLoop label.

With the Questions displayed we now need to monitor the user input. To allow for the player to hold the up or down buttons down there is a delay between reads of the joystick that is initiated when the player performs an action. This works well for holding down the directions but rapidly clicking on a button doesn't work as well as it should as some of the clicks can happen between the delay. This will be fixed in the second version of trivia.

DEC INPUT_DELAY
BNE MainGameLoop_adjustScroll
INC INPUT_DELAY ; Make sure if no input we update next frame
JSR ReadController1
TAY

Moving up or down is simply a matter of adjusting the CURRENT_ANSWER variable. Because there are only 4 possible answers (0..3) we can simplify the range checking logic by simply using the AND operation to force the value of the adjusted CURRENT_ANSWER to within the acceptable range. This technique saves us from having to have a conditional branch which, at least in my opinion, makes for more readable code. While people who are not familiar with boolean operations may disagree with this, if you are programming in assembly language a lot you will quickly get use to working with boolean operations.

MainLoop_checkButtonUp:
TYA
AND #BUTTON_UP
BEQ MainLoop_checkButtonA
LDX CURRENT_ANSWER
DEX
TXA
AND #3
STA CURRENT_ANSWER
LDA #DELAY_TIME
STA INPUT_DELAY
JMP MainGameLoop_adjustScroll

The heart of the trivia game logic comes when the A button is pressed. The handling code waits for the button to be released and then calls DisplayQuestionResults. This function potentially prints a lot to the screen so we temporarily turn off the display so we can write to the PPU without any issues. It then sets up the print location before checking to see if the answer the player selected is the correct answer. The appropriate result string is printed, and if the answer is correct the score is increased. Finally, this function prints the explanation before returning to the main game loop.

DisplayQuestionResults:
LDA #$00
STA $2001 ; disable rendering

; set up print coordinates
TAX ; accumulator already 0
LDY ANSWER_ROWS+4
JSR SetScreenXY

; See if selected answer is correct
LDY #14 ; Offset to correct answer
LDA [QUESTION_INFO_PTR],Y
CMP CURRENT_ANSWER
BEQ DisplayQuestionResults_correct
CallPrintString incorrectText
JMP DisplayQuestionResults_explain

DisplayQuestionResults_correct:
INC SCORE
CallPrintString correctText

DisplayQuestionResults_explain:
; set up coordinates for explanation text
LDY ANSWER_ROWS+4
INY
INY
LDA #0
TAX
JSR SetScreenXY
; print explanation
LDY #12
JSR PrintQuestionInfoString
LDA #011110 ; enable background + sprites
STA $2001
RTS

When we return to the main loop we wait for the press and then the release of a button so that the player has time to acknowledge the explanation. After this, we need to know if the question is the last question or not. If it is the last question, we jump to the summary screen loop. If it is not the last question, the pointer to the current question info structure is incremented by 16 (the length of the structure) and we call DisplayQuestion to get the next question displayed.

LDX CURRENT_QUESTION
INX
STX CURRENT_QUESTION
CPX #9
BEQ SummaryScreen
LDA QUESTION_INFO_PTR
CLC
ADC #16
BCC MainLoop_showNextQuestion
INC QUESTION_INFO_PTR+1

MainLoop_showNextQuestion:
STA QUESTION_INFO_PTR
JSR DisplayQuestion

We are now finished with the game's core logic. Essentially we have a game. The only thing missing from having a complete game at this point is the title and summary screens. Normally, these are not things that I would bother to cover as they are very straightforward, but as this is our first NES game, it is probably a good idea to cover them. This is what we will be doing next.

No comments: