Questions about make a text entry routine.

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Questions about make a text entry routine.
by on (#180234)
I am working on a little project to enter text into variables and then use that variable to generate custom text later in the game. Rather than make different topics for every issue, I thought I would make one topic if that is OK?

I am using a cursor to select letters kind of like a name entry screen at the start of an RPG or something. I map the cursor to where I want it using this code which works fine. See spriteselect.nes

Code:
loadtitlesprites:   
  LDA #$7f
  STA $0200        ; put spritein center ($7f) of screen vert
  LDA #tempval
  STA $0201        ; tile
  LDA #$00
  STA $0202        ; color = 0, no flipping   
  LDA #$10
  STA $0203        ; put sprite on left of screen ($10) of screen horiz


However I want to change the vertical / horizontal values to variable so I can track my cursor sprites location and make sure it doesnt go out of bounds.

So I declare 2 variable, store the values I want in the variable and use the variable to write to the screen like this

Code:
cursorx      .dsb 1
cursory      .dsb 1

   LDA #$10
   STA cursorx
   LDA #$7f
   STA #cursory

loadtitlesprites:   
  LDA #cursory
  STA $0200        ; put spritein center ($7f) of screen vert
  LDA #tempval
  STA $0201       
  LDA #$00
  STA $0202        ; color = 0, no flipping   
  LDA #cursorx
  STA $0203        ; put sprite on left of screen ($10) of screen horiz


Which I would assume would map the cursor sprite to the exact same location but it is mapping the sprite to the top left of the screen. I tried changing the values I am storing in cursorx and y but it always maps the sprite to the same location. See spriteselecttopleftt.nes

Any idea what I might be doing wrong?

Here is the full code.

http://pastebin.com/PynpV5sF
Re: Questions about make a text entry routine.
by on (#180236)
This code is very, very wrong. You've got # for immediates used in things like sta -- this shouldn't work (the assembler should throw an error).

Furthermore, you're using .dsb to "define RAM variables" but .dsb refers to static-valued bytes in ROM, not RAM. In situations like that, on a .dsb label:

* lda dsblabel becomes lda {ROM address of dsblabel} (i.e. absolute addressing)
* lda #dsblabel becomes lda {ROM address of dsblabel} (i.e. absolute addressing) -- this appears to be an asm6 parser bug
* sta dsblabel becomes sta {ROM address of dsblabel} (i.e. absolute addressing)
* sta #dsblabel becomes sta {ROM address of dsblabel} (i.e. absolute addressing) -- this isn't valid code; definitely an asm6 bug

What you want is something like this. I've put ; *** on lines which are different.

Code:
cursorx = $02c0    ; *** Refers to RAM location $02c0
cursory = $02c1    ; *** Refers to RAM location $02c1

   LDA #$10
   STA cursorx
   LDA #$7f
   STA cursory     ; *** Note lack of #

loadtitlesprites:   
  LDA cursory      ; *** Note lack of #
  STA $0200        ; put spritein center ($7f) of screen vert
  LDA #tempval   
  STA $0201       
  LDA #$00
  STA $0202        ; color = 0, no flipping   
  LDA cursorx      ; *** Note lack of #
  STA $0203        ; put sprite on left of screen ($10) of screen horiz

There is nothing special about memory locations $02c0 and $02c1; they just happen to be areas in RAM. Alternately you could use zero page if you wanted (e.g. cursorx = $1c). It's up to you to know (manage) what areas of zero page and RAM are used/unused.

I recommend that you also turn on listings generation using flags -l (lowercase-ELL) and/or -L (uppercase-ELL) (latter forces even more verbose listings generation), then assemble your game like: asm6 mygame.asm mygame.nes mygame.lst and review the mygame.lst file if you have complications/problems.

TL;DR -- it's very important you understand the difference between immediate addressing (ex. lda #val) and absolute addressing (ex. lda val).
Re: Questions about make a text entry routine.
by on (#180237)
I'm surprised the assembler didn't give you an error.
Re: Questions about make a text entry routine.
by on (#180238)
You seem to be updating the coordinates of the first sprite, but all others are left with the coordinates (0, 0), which causes them to show up at the top left corner of the screen. In order to hide unused sprites, you must give them a vertical coordinate of 239 or larger, to place them below the bottom of the screen.
Re: Questions about make a text entry routine.
by on (#180239)
OmegaMax wrote:
I'm surprised the assembler didn't give you an error.

asm6 appears to have bugs relating to parsing and addressing modes for certain things. Take for instance this simple code:
Code:
  org $8000

start:
  lda #cursorx   ; Should assemble to A9 0F (low byte of address of cursorx)
                 ; i.e. should be the same as lda #<cursorx
  lda cursorx    ; Should assemble to AD 0F 80
  sta cursorx    ; Should assemble to 8D 0F 80
  sta #cursorx   ; Should throw an error/fail to assemble
  jmp start      ; Should assemble to 4C 00 80

cursorx .dsb 1
cursory .dsb 1

Results:

Code:
pass 1..
pass 2..
last try..
test.out written (17 bytes).
test.lst written.

                                  org $8000
08000                           
08000                           start:
08000 AD 0F 80                    lda #cursorx
08003 AD 0F 80                    lda cursorx
08006 8D 0F 80                    sta cursorx
08009 8D 0F 80                    sta #cursorx
0800C 4C 00 80                    jmp start
0800F                           
0800F 00                        cursorx .dsb 1
08010 00                        cursory .dsb 1

asm6 source code is available for anyone who wants to try and fix this. ;-)

Edit: clarification on lda #cursorx results. Sorry, was copy-pasting a lot.
Re: Questions about make a text entry routine.
by on (#180240)
I wasn't aware of that,thanks for the info.
Re: Questions about make a text entry routine.
by on (#180243)
koitsu wrote:
Furthermore, you're using .dsb to "define RAM variables" but .dsb refers to static-valued bytes in ROM, not RAM.

This is not correct. In ASM6, .dsb and .dsw are used to "define storage", they reserve a byte or a word of memory at the current PC, and increment the PC accordingly, without outputting anything. These are meant for defining variables in RAM, unlike .db and .dw, which are meant for writing bytes and words to ROM.

Quote:
Code:
cursorx = $02c0    ; *** Refers to RAM location $02c0
cursory = $02c1    ; *** Refers to RAM location $02c1

I have to say koitsu, this is the one thing you advocate that I strongly disagree with. Maybe in the old days of Atari 2600 coding it made sense to hardcode the address of each variable by hand, or when you're making small patches or hacks that need few variables, but when you have hundreds of variables, hardcoded addresses are hard to maintain and very error prone.

Say you have 200 variables declared manually one after the other, and the very first one is a byte that holds the number of diamonds the player can carry. Then, during development, you decide you want the player to be able to carry more then 255 diamonds, and you have to make that variable 16-bit. To open space for the new byte, you'll have to either manually change the addresses of the 199 variables that come after it (to preserve the order, which might be important for some variables), or move the one immediately after it to the end, which would certainly be easier. But it could be an array, so you'd end up with a gap that you'd have to move more variables to fill, increasing the chances of a mistake. Speaking of arrays, if you're entering addresses by hand, you could easily mess up when declaring arrays, because there's nothing to make sure there won't be any overlaps:

Code:
var1 = $0304
var2 = $0308

Is var1 an array of 4 bytes? Or is it a 1 byte variable followed by a gap of 3 bytes I'll have to fill up later? It's really easy to mess up with things like these. It's much safer to use .dsb, .dsw, .res or whatever the assembler you're using supports:

Code:
var1 .dsb 4
var2 .dsb 1

Ah, it's a 4-byte array/value, and since dsb reserves the space I can't accidentally declare an overlapping variable in there.
Re: Questions about make a text entry routine.
by on (#180245)
I'm not going to get into a stylistic debate, as it's off-topic and will likely confuse the OP or others less experience viewing the thread.

That said, you are encouraged to give a long explanation about shit like $ = $02c0 and explaining what this "magic $" refers to, and only if the assembler supports that nomenclature (it's * in some others), else insert some long-winded thing about ca65/ld65 and linker configurations and... yeah. :P You know where this is going, and it's off-topic. (I will not participate in a separate thread debating the subject either)
Re: Questions about make a text entry routine.
by on (#180246)
I have no intention of starting a debate either, in this thread or any other, but you did give the OP wrong information about something he was actually doing right, and in a safer way than in your proposed fix, so I had to point it out. I was simply explaining why he should disregard your remarks on the specific subject of variable declaration.
Re: Questions about make a text entry routine.
by on (#180247)
Thanks very much for the detailed answers! I have a follow up question, when using

Code:
Code:
cursorx = $02c0    ; *** Refers to RAM location $02c0
cursory = $02c1    ; *** Refers to RAM location $02c1


to declare the variables, the cursor maps to where I would like to be exactly.

However when using

Code:
cursorx .dsb 1
cursory .dsb 1


Which I totally understand the logic in, the cursor is mapped to a different wrong location, this time on the middle right of the screen.
Re: Questions about make a text entry routine.
by on (#180248)
Haha Now you're making me look bad after defending .dsb! :lol: Seriously, .dsb should be the safest way to declare variables, so there must be something else wrong. I'll try and take a look at the code.

Note that $02c0 and $02c1 are in the middle of your shadow OAM, where you wouldn't normally keep variables.
Re: Questions about make a text entry routine.
by on (#180249)
lazerbeat wrote:
Thanks very much for the detailed answers! I have a follow up question, when using

Code:
Code:
cursorx = $02c0    ; *** Refers to RAM location $02c0
cursory = $02c1    ; *** Refers to RAM location $02c1


to declare the variables, the cursor maps to where I would like to be exactly.

However when using

Code:
cursorx .dsb 1
cursory .dsb 1


Which I totally understand the logic in, the cursor is mapped to a different wrong location, this time on the middle right of the screen.

This is almost certainly because your .dsb statements are referring to memory locations in ROM, not in RAM, as I covered. Thus, your attempts to sta to a ROM location end up doing nothing, and the values default to $00 in ROM. You can't just drop .dsb statements into the source code and have them "magically work" as you intend.

Do an assembly generation (-l flag + specify the .lst file in the command line) between the two versions (ones with =, ones with .dsb) and you should hopefully see the difference.

I hope this is helpful for tokumaru as to why I advocate what I do, especially for beginners or folks who haven't gotten into the advanced subjects yet (ex. organising code better for assemblers). I'm not saying .dsb is wrong, I'm saying telling people that equates and .dsb are the same thing is misleading and confusing (this thread is an example).

NES RAM is from $0000-07FF. But it's still up to the programmer to manage that RAM and organise its use however they wish. "In the middle of your shadow OAM" means nothing to me (there is nothing in the source code posted in the initial post that indicates such -- all I can do is go off the source code shown).
Re: Questions about make a text entry routine.
by on (#180251)
I have compiled with both using -L I must admit, I am only a very low level beginner at this so the output is pretty far over my head. I have attached both outputs as txt files though!
Re: Questions about make a text entry routine.
by on (#180252)
koitsu wrote:
This is almost certainly because your .dsb statements are referring to memory locations in ROM, not in RAM

He has .enum $0000 before declaring the variables, which temporarily sets the PC to $0000 (i.e. zero page) so they're definitely in RAM. I'm still trying to figure out what could have happened...

EDIT: The listing should help.
Re: Questions about make a text entry routine.
by on (#180260)
Now that the full listings have been provided, and use of enum has been declared, then that helps. (This is why it's important to show the full code of something sometimes, not just snippets)

The problem is certainly that you have code that is using memory locations $10 and $11 explicitly, which happen to be the same memory locations used by the cursorx and cursory variables in your enum block. Look closely (this is just one small snippet -- this is done in several spots):

Code:
                                .enum $0000  ;;start variables at ram location 0
....
00010                           cursorx      .dsb 1
00011                           cursory      .dsb 1
00012                           .ende
...
0804B A9 10                        LDA #$10
0804D 85 10                        STA cursorx
0804F A9 7F                        LDA #$7f
08051 85 11                        STA cursory
...
08090 A9 DE                             LDA #<pic0              ; load low byte of first picture
08092 85 10                             STA $10
08094 A9 84                             LDA #>pic0              ; load high byte of first picture
08096 85 11                             STA $11

Again: this is just one of several places where $10 and $11 are explicitly used.

To determine if this was the real root cause (gut feeling said it was), I had to trace through the order-of-operation of your code, since you have several subroutines and so on. The way things are ordered:

Code:
Value $10 is stored in cursorx (ZP $10)
Value $7f is stored in cursory (ZP $11)
Value $e2 (low byte of pic0) is stored in ZP $10
Value $84 (high byte of pic0) is stored in ZP $11
Run subroutine DrawScreen
  - This populates PPU $2000 (nametable) and manipulates $11 due to use of indirect addressing (LDA ($10),y)
  - Returns
Run subroutine LoadScreen
  - Disables NMI and screen (writes $00 to $2000 and $00 to $2001)
  - Conditionally does one of the following based on screennumber (ZP $05):
    - If 0:
      - Value $e2 (low byte of pic0) is stored in ZP $10 (yes, again; see Test0 label)
      - Value $84 (high byte of pic0) is stored in ZP $11 (yes, again; see Test0 label)
      - Changes palettenumber (ZP $07)
      - Returns
    - Else:
      - Value $e2 (low byte of pic1) is stored in ZP $10 (see label Test1)
      - Value $88 (high byte of pic1) is stored in ZP $11 (see label Test1)
      - Changes palettenumber (ZP $07)
      - Returns
Run subroutine Vblank
  - Polls $2002 (bit $2002 / bpl previnstruction)
  - Writes $80 (%10000000) to $2000 -- enables NMI, nametable = $2000, etc.
  - Writes $1e (%00011110) to $2001 -- enables sprites & background, show both in leftmost column
  - Returns
Infinite loop (JMP to itself)

To me, it looked like nothing was relying on cursorx/cursory at all within the main code, which left the NMI handler for review:

Code:
NMI vector points to label called NMI

NMI label:
  - Branches conditionally to label enginestateselect
  - Label enginestateselect:
    - Does a JSR loadtitlesprites
    - Routine loadtitlesprites:
      - Uses cursorx and cursory variables

Thus, we can see here how and why cursorx and cursory end up having "the wrong values". Fix your PPU routines to use labels/variable names that are within the enum space, and not hard-coded memory locations $10 and $11, and I think you'll be good. I might suggest declaring the variable as 2 bytes of space (e.g. ppubase .dsb 2 in the enum section), then use ppubase (for what was $10) and ppubase+1 (for what was $11), but do it however you want. (This would make tokumaru happy too, haha :D)

Let me know where I can send a bill. :-) (I'm kidding BTW; I'm just a sarcastic ass at times, haha)

P.S. -- You're missing a cli, even though it's not hurting you right now. Just something to keep in mind.
Re: Questions about make a text entry routine.
by on (#180266)
koitsu wrote:
(This would make tokumaru happy too, haha :D)

What's most important than anything is that you remain CONSISTENT. If you use .enum and .dsb for some variables, = for others, and also hex values as operands directly in some places, you're just asking for trouble, because a collision is bound to happen. I personally find the .enum/.dsb approach to be the safest and easiest to maintain, and the worse is undoubtedly not even naming your variables. But pick the method you feel most comfortable with and stick with it!

Quote:
Let me know where I can send a bill. :-) (I'm kidding BTW; I'm just a sarcastic ass at times, haha)

Congrats on finding the bug... I gave up after a while!
Re: Questions about make a text entry routine.
by on (#180328)
Thanks very much for all the help everyone! I updated my code to reflect it and added in collision detection. I uploaded the rom and code incase this might be useful for anyone in the future.

I have more things I am going to try and add so will probably have more questions soon.
Re: Questions about make a text entry routine.
by on (#180356)
tokumaru wrote:
I have to say koitsu, this is the one thing you advocate that I strongly disagree with. Maybe in the old days of Atari 2600 coding it made sense to hardcode the address of each variable by hand, or when you're making small patches or hacks that need few variables, but when you have hundreds of variables, hardcoded addresses are hard to maintain and very error prone.

Did programmers in the assembler dark age just have to deal with this?
Re: Questions about make a text entry routine.
by on (#180368)
From the little I've seen of source code from back in the day, the assemblers seemed very limited. Labels were limited to few characters, and I do remember variables having hardcoded addresses.
Re: Questions about make a text entry routine.
by on (#180369)
I don't know what "assembler dark age" refers to time-wise (are we talking 60s? 90s? It matters!), but in commercial assemblers available in the late 80s/very early 90s the "commonly universal" way (meaning this generally worked across any/all assemblers) was accomplished through expressions and equates (a.k.a. EQU/=), i.e.:

Code:
var1   = $0300   ; 1 byte  (address $0300)
var2   = var1+1  ; 2 bytes (address $0301)
var3   = var2+2  ; 1 byte  (address $0303)
var4   = var3+1  ; 1 byte  (address $0304)
endvar = var4+1  ; end of variables

People today would whine/cry/bitch "but this isn't intuitive! You'd have to look at the next line/variable to determine what the size of the previous is!" Yes, that's correct -- and exactly what comments are for. :-) What do you want me to say about it? It's how things were done. And the same model/approach works today (I'll get to it in a moment).

The other approach, which is the equivalent of the enum model in asm6, is to use ORG (or in some, * or $) to change the active address the assembler is using to whatever you define, then put it back when done. In Merlin 8/16 you'd do that like so:

Code:
     ORG $0300
var1 DS 1
var2 DS 2
var3 DS 1
var4 DS 1
     REORG

Merlin 8/16 also had the DUM/DEND pseudo-ops, which sound great but can be a bit confusing. I'll just include a screenshot of the manual; ask yourself what STA IOBSLOT would actually assemble equivalently to: STA $60, STA $0060, STA $B7E9, or STA $B848? (IOBADRS is $B7E8, by the way -- hard to read; Apple II people will know this :-) ). The correct answer, BTW, is STA $B7E9. I'll get to why I consider this methodology/approach bad (it confuses beginners) in a moment -- be sure to note that the code mainly uses DFB (a.k.a. DB) to define a byte with a pre-set value. Stay with me here!

In early 90s cross-platform assemblers (ex. trasm (Tricks Assembler) and x816), you'd use .base/.end for things that contained static data (i.e. data in ROM, or code in ROM), or .table/.end (for RAM). The x816 example for the above would be:

Code:
     .table $0300
var1 .db 0
var2 .dw 0
var3 .db 0
var4 .db 0
     .end

What's misleading about the Merlin 8/16 DUM/DEND and x816 example is that you end up using pseudo-ops that actually define values. Beginners very quickly/easily -- this thread is an example (sorry OP, not saying you're a beginning, just using this thread as a reference point) -- get confused because of context. They then start doing things like this:

Code:
     .table $0300
var1 .db 0
var2 .dw $1234
var3 .db $1f
var4 .db 0
     .end

...then expect lda var3 to load value $1f in the accumulator. This simply isn't the case unless the programmer had written lda #$1f / sta var3 somewhere prior in their code.

That's why Merlin 8/16 offers the DS pseudo-op (Define Storage), which is a little different than DB and equivalents.

This is why, for beginners especially, I have always mandated using the EQU/= methodology -- because not only WYSIWYG, but it's very easy to tell when looking at an assembly listing (varies per assembler; many of them generate horribly complicated listings) what's wrong and correlate that value with something immediately in your program. The more "abstraction" you add (linkers, etc.) the more complicated the troubleshooting process becomes, and (IMO) the more forum posts you end up with people asking what's wrong. That's why I consider such things "advanced" topics. Keeping things simple really makes a difference.

That's all I really have to say on the matter. (Now I'm even a bit irritated because I said I wouldn't go into this, but ended up doing so anyway. I'm almost 40 and still need to learn how to truly not give a shit sometimes...)
Re: Questions about make a text entry routine.
by on (#180689)
I have been doing a bit more work on my program and I have run into a problem where I feel I am probably missing something pretty silly.

The cursor moves around to select character but I am trying to add collision detection to stop the cursor moving out of bounds.

I have defined the limits of where I want my cursor to move like this

Code:
leftwall   = $00
rightwall   = $f0
topwall      = $7f
bottomwall   = $cf   


and pressing up for example I use this code to check if I have reached "topwall" if so, don't move up any more.

Code:
checkup:
   lda #%00010000
   and justpressed
   beq checkdown

   lda cursory
   cmp #topwall
   beq checkdown


The problem is, I can't move to the column on the far right as rightwall %f0 is not the far right column of the screen so this stops the cursor one column left of the far right side of the screen.

Code:
   lda #%10000000
   and justpressed
   beq checkselect

   lda cursorx
   cmp #rightwall
   beq checkselect


I assume there is a way around this but I can't work out what it is. Would anyone be kind enough to offer some advice?
Re: Questions about make a text entry routine.
by on (#180690)
It would certainly be easier with source code, but seems like you're adding $10 to cursorx twice when moving to the right (when moving, you add $10 to the sprite X and then store that in cursorx, then add $10 to cursorx again). So, when the sprite is at $E0 your cursorx is already $F0, preventing movement.
Re: Questions about make a text entry routine.
by on (#180691)
You're clearly adding #$10 to cursorx ($0007) again, after you have already updated the sprite's X coordinate ($0203):
Code:
 00:81CF:AD 03 02  LDA $0203 = #$E0
 00:81D2:18        CLC
 00:81D3:69 10     ADC #$10
 00:81D5:8D 03 02  STA $0203 = #$E0
 00:81D8:85 07     STA $0007 = #$E0
 00:81DA:A5 07     LDA $0007 = #$E0
 00:81DC:18        CLC
 00:81DD:69 10     ADC #$10
 00:81DF:85 07     STA $0007 = #$E0

This causes cursorx to reach $0f prematurely, so the boundary check triggers one column too early.

One of the problems here is that you don't have a clear separation between cursorx/cursory and the sprite coordinates in page $0200. See, you're checking if the boundaries have been reached by comparing cursorx/cursory to the boundary values, but then you proceed to update the sprite coordinates directly, and copy the result back over to cursorx/cursory. It's a bit of a mess. Ideally, you'd do all the math using only cursorx/cursory, and then, after all the input had been processed, you'd copy the final values of cursorx/cursory over to the OAM. You shouldn't be doing math directly in the $0200-$02FF area, and you should definitely not have your logic use state variables some of the time and the direct OAM locations at other times. The state of your game should be contained and manipulated in a single place.

In short: either use cursorx/cursory or $0200/$0203 when updating the state, but don't mix and match them in the middle of the logic. Mixing might work if you're careful and keep things always perfectly synced, but sync problem is exactly what caused your bug.
Re: Questions about make a text entry routine.
by on (#180692)
Thanks very much, that was a silly mistake for me to have made, I didn't make it with up, left or down so I didn't check the code for right thoroughly enough!

As always, the help is very much appreciated.