First: You have a commercial rom in that zip. (even though it's slightly hacked, it's not good to put on this forum)
Other forum members: Please check that my info on $2002, and .rs is correct. Otherwise, correct me. I don't want to be "the blind leading the blind."
67726e: Get ready for a BOOK! (Make sure to read all the comments I put in the code blocks too, there's more info there.)
In any case: It may help you to write a flowchart of how your code is running. Something much more readable than the actual code, but still gets the idea across. This post is going to help you get your code working first. There are also some bad practices, but I'll get to them later.
Starting from your main game loop (The Forever Label):
Forever:
Check if player can move.
If not, jmp to forever.
(all buttons do nothing except for the dpad, so I'll skip 'em)
Read Up.
If pressed, subtract one from player's y position unless he is outside the top of the maze.
Read Down
If pressed, add one to player's y position unless he is outside the bottom of the maze.
(Here's where it gets weird)
Read Left.
Start collision checking (Here? Why? If left is not pressed, the collision detection routine will never be run since the BEQ ReadLeftDone above it skips it. So...the player can move freely through any wall as long as left isn't pressed)
I am now going to break away from my flowchart, because there are actual coding mistakes here. Even assuming the collision detection was in the right place, it wouldn't work.
Code:
; Start of checking for collsion
LDA $0203;This can never be less than #$18. Because you keep the player from going further left when it is equal to that.
LSR A;Can't be less than #$0C
LSR A;Can't be less than #$06
LSR A;Can't be less than #$03
LSR A;Can't be less than #$01
STA XBLOCK;Hmm... This can never be 0.
LDA $0200;Can't be less than #$27 because you keep the player from going up in this case.
LSR A;Can't be less than #$13
LSR A;Can't be less than #$09
LSR A;Can't be less than #$04
LSR A;Can't be less than #$02
STA YBLOCK;This one can't be 1 OR 0.
That's a problem. You can't access the first two rows, and the first column of your CollisionTable. This can be fixed by subtracting those values so that #$18, #$27 (the coordinates where your playfield starts) becomes #$00, #$00 (so you can look up the right values in the collision array.)
The quickfix is something like this:
Code:
; Start of checking for collsion
LDA $0203;This can never be less than #$18. Because you keep the player from going further left when it is equal to that.
SEC
SBC #$18
LSR A
LSR A
LSR A
LSR A
STA XBLOCK
LDA $0200
SEC
SBC #$27
LSR A;
LSR A;
LSR A;
LSR A;
STA YBLOCK
Another good exercise (like making a flowchart) is thinking through which values are possible on a given routine. (As I did above) It helped me realize some parts of your data were inaccessible.
Moving on.
Code:
LDA #$00
LDX #$00;Load A and X with Zero. Fine.
CollisionRoutine:
CLC
ADC #13;Add the width of the playfield. Not... fine. At least not here. This makes part of your CollisionTable inacessible again. Let's say YBLOCK = 0. You'd be checking row 1. It's always gonna be offset by one, so the 0 row is never accessible. It's also one off every other time.
INX ;Actually... if YBLOCK is 0 there's more wrong than that. This would make X = 1.
CPX YBLOCK ;Which would not be equal to YBLOCK. It would have to loop all the way to #$FF, then back to #$00 before it stopped adding #13.
BNE CollisionRoutine
CLC
ADC XBLOCK
STA COLLISIONBLOCK;
LDX COLLISIONBLOCK;I'll
LDY CollisionTable, x
CPY #1
BEQ ReadLeftDone
The quick fix for this?
Code:
LDA #$00
LDX YBLOCK
BEQ CollisionRoutine.XAdd;This way it doesn't add 13 when it doesn't need to.
CollisionRoutine:
CLC
ADC #13
DEX;Why? I've reversed your logic. Rather than starting at 0 and INX'ing to YBLOCK, I'm starting at YBLOCK and DEX'ing to 0.
BNE CollisionRoutine
CollisionRoutine.XAdd:
CLC
ADC XBLOCK
STA COLLISIONBLOCK;
LDX COLLISIONBLOCK
LDY CollisionTable, x
CPY #1
BEQ ReadLeftDone
Finally, the .rs command doesn't work like you think it does. (It may not work like I think it does either, since I don't use it. Another forum member will cover my behind if I'm wrong) .rs reserves a number of bytes, it doesn't set a variable to the number you give you it.
Code:
LIVES .rs 3 ; set default # of lives
This does NOT set LIVES to 3, it reserves 3 bytes where the .rsset counter currently is.
Code:
.rsset $0000;Sets the counter to location $0000
LIVES .rs 3; Reserves RAM location $0000, $0001, $0002 for use with LIVES. the rs counter location is now $0003.
That said, the final thing I did to your code to make collision detection work completely (At least while traveling left, since I didn't move your collision detection routine from where it is. Also note it ONLY checks the player's top left point, so parts of him can still pass through blocks. But it DOES work for just that point.) was this:
Code:
; Player Data
XBLOCK .rs 1
YBLOCK .rs 1
COLLISIONBLOCK .rs 1
There are obviously problems with the other .rs uses, but I couldn't really fix them without completely breaking your code.
Here's what I believe happens.
Code:
.rsset $0000 ;;start variables at RAM location 0
NMI_COUNTER .rs 0 ;All references to NMI_COUNTER are assembled to $00. The rsset counter is not incremented.
ALLOW_MOVE .rs 0 ;All references to ALLOW_MOVE are assembled to $00. The rsset counter is not incremented. This means NMI_COUNTER and ALLOW_MOVE refer to the SAME RAM location (!)
ALLOW_ANIM .rs 1;All References to ALLOW_ANIM are assembled to $00. (The same location as the above variables) the rsset counter is incremented by one to $0001
LIVES .rs 3 ; All references to LIVES are assembled to $01. The rsset counter is incremented by three to $0004
SCORE .rs 0 ;All references to SCORE become $04. Counter not incremented
PLAYING .rs 0 ;All references to PLAYING become $04 (The same location as score) The counter is not incremented.
So the fix I did above just made sure XBLOCK, YBLOCK and COLLISIONBLOCK all referred to different RAM locations. I couldn't fix your NMI_COUNTER and ALLOW_MOVE being the same RAM location without putting more thought into it than I want to right now. (Sorry, this is a LONG post, and this is actually the last part I've written. This has been like three hours or so of writing...
I may make another post with a fix for this too when I'm less tired.)
It is up to you to figure out how to make collision detection work for the other directions. But now I will cover some other things in your code, that either confused me, or just aren't as well done as they could be.
I could be wrong on this, so correct me if I'm wrong other forum members.
Code:
VBLANKWAIT2:
BIT $2002 ;This counts as a read of PPU status. So it resets the high/low latch. Other forum members correct me if I'm wrong of course.
BPL VBLANKWAIT2
LOADPALLETES:
LDA $2002 ; There is no need for this, because of that.
LDA #$3F
Later, something similar.
Code:
LOADBKG:
LDA $2002;;Each write to 2006, switches whether high/low will be written to. Since you're done it twice above, You're back where you want to be. This isn't needed.
LDA #$20
STA $2006
It happens at least one other time. Interestingly enough you don't do it before writing to the scroll register ($2005, which uses the same latch), but you don't need to. But... it doesn't hurt to keep them there, really (just uses some extra bytes and cycles at startup). I say again, if I'm wrong about the above please let me know other forum people, since I'd need to do some stuff in my own games.
And we might want to clarify the wiki if I'm right, since I can see where he got the idea.
Code:
FOREVER:
LDY #$01
CPY ALLOW_MOVE
BEQ LATCHCONTROLLER
JMP FOREVER
Loading a value will change the zero bit. So...
Code:
FOREVER:
LDY ALLOW_MOVE
BNE LATCHCONTROLLER;It's faster by a little bit, since you're not comparing. But the branch has to be reversed.
JMP FOREVER
Of course you'll still need to cmp/cpy if you have more than two possibilities. But if you only have two, and one of them is zero, this is fine.
One other thing. One of the things people hate about NESASM is you have to specify zero page RAM locations to get any gain from them. You do this by putting < before a zero page RAM location at each use. Otherwise, it's slower than it should be, and uses 1 more byte than it should. (the same as regular RAM)
Code:
FOREVER:
LDY <ALLOW_MOVE
BNE LATCHCONTROLLER;It's faster by a little bit, since you're not comparing.
JMP FOREVER
Yes... every time you use a zero page RAM location. I didn't do that on any of the fixed code other than this, but I totally should've.
Another subtle optimization tip
Code:
StartUpMove:
LDY $0200
CPY #$27
BEQ ReadUpDone
LDX #$00
MoveUpLoop:
LDA $0200, x ; load sprite X position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $0200, x ; save sprite X position
INX
INX
INX
INX
CPX #$10;Can we get rid of this cmp?
BNE MoveUpLoop
ReadUpDone:
Loop optimization is a very good skill to have when working on more complex games.
Code:
StartUpMove:
LDY $0200
CPY #$27
BEQ ReadUpDone
LDX #$10;Very similar to what I did with your collision check loop way above in this post. I'm loading the number and going to 0, rather than loading 0 and going to the number
MoveUpLoop:
LDA $0200, x ; load sprite X position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $0200, x ; save sprite X position
DEX;DEX instead of INX like above
DEX
DEX
DEX;When the result of this DEX is < 0, it will continue to the label ReadUpDone. The program becomes a little faster since it's doing less by not cmp'ing
BPL MoveUpLoop;BPL instead. This is so it will still do the zeroth sprite. This can be done for all your directions.
ReadUpDone:
TAX is always faster than storing and loading the accumulator, and assuming COLLISIONBLOCK (which is RAM) isn't used for anything else (it's not), then... you can use it for something else. Essentially it's wasted RAM, and the code is slower/uses more bytes than it could.
Code:
;STA COLLISIONBLOCK;Commented out. It's not needed.
TAX
;LDX COLLISIONBLOCK;Commented out. It's not needed.
LDY CollisionTable, x
CPY #1
BEQ ReadLeftDone
Similar to something above, loading a value changes the zero status bit.
Code:
LDY CollisionTable, x
;CPY #1;No need for this.
BNE ReadLeftDone;But reverse the branch condition
Edit: OH MY GOODNESS, I FORGOT THIS WHICH IS ACTUALLY IMPORTANT!
Code:
NMI_COUNT:
INC NMI_COUNTER
LDY NMI_COUNTER
CPY #60
BNE CONTINUE_NMI
;JSR CLEAR_NMI_COUNTER;This is NOT the proper way to use JSR.
;JSR would jump to a bit of reused code that would END IN RTS.
;If you do not RTS from a JSR you leave data on your stack which will eventually overflow.
;This is BAD
;JSRs are used to go someplace, then return (By hitting an RTS in the code that was jsr'd to). If you don't need to return use JMP.
;JMP is probably what you were looking for.
;But JMP CLEAR_NMI_COUNTER there would be useless.
;Because there's no need to JMP someplace if the program will go there immediately after the JMP anyway.
;What happens if NMI_COUNTER is equal to 60? It goes down.
;What's right beneath that branch? The label we're JMP'ing to.
;So no need for it.
CLEAR_NMI_COUNTER:
LDY #$00
STY NMI_COUNTER
INC ALLOW_MOVE
CONTINUE_NMI:
LDA #$00
STA $2003 ; set low byte (00) of the RAM address
LDA #$02
STA $4014 ; set low byte (02) of the RAM address & start transfer
LDA #$00
STA $2005
STA $2005
RTI ; return from interrupt
Which... brings me to a similar point in another location.
Code:
MoveLeftLoop:
LDA $0203, x ; load sprite X position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $0203, x ; save sprite X position
INX
INX
INX
INX
CPX #$10
BNE MoveLeftLoop
;JMP ReadLeftDone;You're JMP'ing to a label immediately after the JMP instruction. There's no need. So it's commented out. This is true for all your direction press code, not just left.
ReadLeftDone:
Actually while I'm still here, one more loop optimization thing (This uses my revised code, but the fix holds true for what you originally had):
Code:
LDA #$00
LDX YBLOCK
BEQ CollisionRoutine.XAdd;This way it doesn't add 13 when it doesn't need to.
CollisionRoutine:
CLC;CLC, clears the carry bit.
;But once it's clear it won't be set by addition until the accumulator wraps around.
;If you're sure that won't happen (after all your playfield is only 11x13=143, less than 255), you can get away with clearing it once.
;ALWAYS CLEAR IN CODE WHERE YOU DON'T KNOW THE STATUS BEFOREHAND THOUGH
;It's faster since you save cycles during each additional loop. Say YBLOCK was 11. You clear the carry flag 10 more times than you need to.
ADC #13
DEX
BNE CollisionRoutine
CollisionRoutine.XAdd:
CLC
ADC XBLOCK
STA COLLISIONBLOCK
LDX COLLISIONBLOCK
LDY CollisionTable, x
CPY #1
BEQ ReadLeftDone
So to make it not clear during the loop:
Code:
CLC;I put it before the routine, so it's not run multiple times during the loop
LDA #$00
LDX YBLOCK
BEQ CollisionRoutine.XAdd;This way it doesn't add 13 when it doesn't need to.
CollisionRoutine:
ADC #13
DEX
BNE CollisionRoutine
CollisionRoutine.XAdd:
ADC XBLOCK
STA COLLISIONBLOCK
LDX COLLISIONBLOCK
LDY CollisionTable, x
CPY #1
BEQ ReadLeftDone
But you can certainly feel free to always CLC in a loop. And of course ALWAYS do it when you're not %100 sure of the status of it before an addition somewhere in your code. Most of these later tips are just to get you to think more about the code you're writing, and to think through how to make things faster, but you can ignore almost everything after I finished getting the code working. (EXCEPT THE JSR THING!)
Edit: I've been writing this for hours now, might as well keep going. A good idea to get used to subroutines might be writing subroutines for each joypad button. Because the way you have it now, all code that needs to check for a button needs to be placed in between those code blocks, and that will get messy and hard to read FAST.
If you had subroutines for the joypad buttons you could do stuff like this:
Code:
JSR p2ap;Jump to a subroutine that checks if player two's a button is pressed
BNE skipscreenadd;Branch to "skipscreenadd" if it's not being pressed
You could make your collision detection a subroutine so you could use it for the player and all monsters in the maze. (Or at least use it for all the directions without copying and pasting the whole thing). Try it out, if you don't understand how to make a subroutine, or how to make one work, ask! I'll help! (And I'll try to write less than this)
That concludes the book. I'll edit this if I'm corrected about .rs/$2002 (or anything else, just so someone doesn't have to read later in the thread to find out I'm wrong.