flame Posted April 29, 2020 Share Posted April 29, 2020 hey everyone, I am wondering if there is a way to briefly delay a branch from executing but not fail to do so. I have a timer that increments to 10 then resets, I compare against it but of course a branch fails to execute. I looked at INTIM but that results in flash so I suspect is improper use of this. Quote Link to comment Share on other sites More sharing options...
MLdB Posted April 29, 2020 Share Posted April 29, 2020 Please supply your code here and explain your purpose. If we know what you are trying to achieve (and why), we could probably help you out! 1 Quote Link to comment Share on other sites More sharing options...
flame Posted April 29, 2020 Author Share Posted April 29, 2020 Of course, apologies. lda punch ;check if punch is not zero beq .Damage ;if zero jump to branch that handles player damage lda #$0f ;set white sta COLUP1 ;set GRP1 white lda timer ;load timer value (0-8) cmp #8 ;check if timer reaches 8 bne .Safe ;jump out lda #0 sta type ;remove enemy type jmp .Safe I have enemies that appear on screen that disappear if punched, of course if GRPx are colliding. The intention is to have GRP1 flash white briefly before it disappears. I hope this makes sense. Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted April 29, 2020 Share Posted April 29, 2020 I would be happy to help, but there's not enough context, either in your initial description, or in the subsequent code, for me to understand exactly what you're trying to do. You set the sprite colour to white. You don't show where you reset it (to black?). Or, where or why you increment timer - assuming 8 is your delay. Why not post all the code, then it will be easier for people to review/help. Good job, though, making an effort to program in assembler! Quote Link to comment Share on other sites More sharing options...
flame Posted April 29, 2020 Author Share Posted April 29, 2020 Here is the full mess of code. I am very bad at commenting things. The initial colour of GRP1 is defined outside of the kernel depending on what the enemy type is. If the type is 0 it is not drawn. My intention is merely to have it flash white briefly before it disappears. main.asm Quote Link to comment Share on other sites More sharing options...
MLdB Posted April 30, 2020 Share Posted April 30, 2020 (edited) I'm a bit confused. The game runs and enemies flash white when hit. My fist guess is that you noticed some flashes are shorter than others. This is what I believe you are doing (based on a quick look at your code): When an enemy is hit, set it's color to white until timer is a chosen value (8 in snippet, 1 in main.asm), after which the enemy is removed from the game. But the time it takes for timer to get to that number after an enemy is hit is not constant, because you do not reset its value when the enemy is hit. timer is instead incremented every frame (?) and simply loops from 1 - 9. I also noticed a lot of lines like these: cmp TREEHOUSE and ldx FOREST Did you mean: cmp #TREEHOUSE and ldx #FOREST here as these are constants and not labels? If I'm not mistaken, these lines currently are instructing the Atari to compare the accumulator against the current value of spritecolourptr and load into X the current value of spriteprt instead of the values of TREEHOUSE and FOREST. Edited April 30, 2020 by MLdB Quote Link to comment Share on other sites More sharing options...
flame Posted April 30, 2020 Author Share Posted April 30, 2020 Just now, MLdB said: I'm a bit confused. The game runs and enemies flash white when hit. My fist guess is that you noticed some flashes are shorter than others. This is what I believe you are doing (based on a quick look at your code): When an enemy is hit, set it's color to white until timer is a chosen value (8 in snippet, 1 in main.asm), after which the enemy is removed from the game. But the time it takes for timer to get to that number after an enemy is hit is not constant, because you do not reset its value when the enemy is hit. timer is instead incremented every frame (?) and simply loops from 1 - 9. I also noticed a lot of lines like these: cmp TREEHOUSE and ldx FOREST Did you mean: cmp #TREEHOUSE and ldx #FOREST here as these are constants and not labels? If I'm not mistaken, these lines currently are instructing the Atari to compare the accumulator against the current value of spritecolourptr and load into X the current value of spriteprt instead of the values of TREEHOUSE and FOREST. Thank you for looking at this. The problem is that sometimes because it is not really a timer, sometimes it fails which results in the enemy remaining on screen but turned white. Essentially I want to delay the code that removes the enemy briefly so that the flash is noticeable. I had planned to give it the (colour cycle) cycle so that it is not always white, was going to see how that looked after I had fixed this issue. As for some of the comparisons you are correct. I should be doing #FOREST, but it was a typo to leave out the # and the code worked regardless. Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted April 30, 2020 Share Posted April 30, 2020 (edited) I'll get some time to look further at your code later today. A brief peek, though, prompts me to explain a better way to do multiple if statements. Rather than checking for a value and branching, checking for another and branching, etc., you can use tables where the value you check is an index, and the table points to code to execute. A jump vector table, in other words. For example, where you have ... lda worldpos cmp #$80 beq .Fireball cmp #$83 beq .SnekRT cmp #$84 beq .Brute cmp #$85 beq .Brute cmp #$86 beq .Brute cmp #$87 beq .Spikes cmp #$88 beq .Bats cmp #$89 beq .Bats cmp #$8a beq .Insect Well there we can use (worldpos*2) as an index to a table of words, like this... lda worldpos asl ; drop the high bit, and *2 for the rest tax lda VectorTable,x sta zp lda VectorTable+1,x sta zp+1 jmp (zp) ; vector to the handling code ; The table... VectorTable .word .Fireball ; handles $80 .word .None ; unhandled $81 - dummy handler .word .None ; $82 .word .SnekRT ; $83, etc... .word .Brute .word .Brute ; etc. We need to make sure there is an entry in the table for every single possible value of worldpos (the low 7 bits) that is actually possible. ; The handlers... .None rts .Fireball ; whatever the fireball thing does here rts .SnekRT ; whatever the "snekRT' thing does here rts .Brute ; whatever the 'brute' thing does here rts This is a much better way, as it doesn't involve a chain of compares. That is, the time to process any single one is slightly slower but if you were for example checking the last one in the earlier code, you have to run through all the other comparisons. There are a few places in your code where you can use this idea of vectors instead of multiple compares. I'd advise you to review/consider. Edited April 30, 2020 by Andrew Davie 1 Quote Link to comment Share on other sites More sharing options...
flame Posted April 30, 2020 Author Share Posted April 30, 2020 Thank you for this suggestion. I honestly didn't know another way of doing it but knew that there had to be an alternative I couldn't think of. It is absolutely something I want to look into, I ran up against a number of branch out of range errors with the implementation I had. Quote Link to comment Share on other sites More sharing options...
MLdB Posted April 30, 2020 Share Posted April 30, 2020 3 minutes ago, pvmpkin said: As for some of the comparisons you are correct. I should be doing #FOREST, but it was a typo to leave out the # and the code worked regardless. Miraculously It will fail whenever the value stored at address FOREST (or TREEHOUSE) changes between the time it was written to variable XYZ and when it is loaded back in from XYZ and compared to the value stored at address FOREST again. 1 Quote Link to comment Share on other sites More sharing options...
flame Posted April 30, 2020 Author Share Posted April 30, 2020 Just now, MLdB said: It will fail whenever the value stored at address FOREST (or TREEHOUSE) changes between the time it was written to variable XYZ and when it is loaded back in from XYZ and compared to the value stored at address FOREST again. Of course. I have since fixed the # in my code, I had some constants doing that. Quote Link to comment Share on other sites More sharing options...
flame Posted May 1, 2020 Author Share Posted May 1, 2020 (edited) Andrew, your code mentions zp, is that a variable that I don't have ? It also indicates jmp(zp) which I have never seen before. I was going to ask if there was a way to jump to a subroutine based on a variable, is that what this does ? Also I have some values that count backwards, is this handled in the code ? Do I only need to include a 'range' ? Edited May 1, 2020 by pvmpkin Quote Link to comment Share on other sites More sharing options...
MLdB Posted May 1, 2020 Share Posted May 1, 2020 zp is short for Zero Page, meaning any variable in RAM ($80-$FF). jmp (zp) will jump to the address stored in ram locations zp and zp+1 (16 bits combined) 1 Quote Link to comment Share on other sites More sharing options...
flame Posted May 1, 2020 Author Share Posted May 1, 2020 12 minutes ago, MLdB said: zp is short for Zero Page, meaning any variable in RAM ($80-$FF). jmp (zp) will jump to the address stored in ram locations zp and zp+1 (16 bits combined) thank you but I don't understand this as an answer to my question. It seems to be a yes but I don't know if I am missing something. Quote Link to comment Share on other sites More sharing options...
+splendidnut Posted May 1, 2020 Share Posted May 1, 2020 3 hours ago, pvmpkin said: Andrew, your code mentions zp, is that a variable that I don't have ? It also indicates jmp(zp) which I have never seen before. I was going to ask if there was a way to jump to a subroutine based on a variable, is that what this does ? Also I have some values that count backwards, is this handled in the code ? Do I only need to include a 'range' ? Yes, zp is placeholder name for a variable that needs to be in the zeropage (Atari 2600 memory is in the zeropage). You need to add it in your variable table... and you should probably rename it something like enemyRoutinePtr to clarify it's purpose. 1 Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted May 1, 2020 Share Posted May 1, 2020 4 hours ago, pvmpkin said: Andrew, your code mentions zp, is that a variable that I don't have ? It also indicates jmp(zp) which I have never seen before. I was going to ask if there was a way to jump to a subroutine based on a variable, is that what this does ? Also I have some values that count backwards, is this handled in the code ? Do I only need to include a 'range' ? "jmp (addr)" will jump to the address stored in the zero page variable "addr". More specifically, the low byte of the destination in "addr" and the high byte of the destination in "addr+1". I used "zp" to indicate that it sould be a variable in zero page. You can, and should, call the variable anything relevant to your intended usage. But it must be in zero page. Another way to jump to a routine is to push the routine's address (-1) onto the stack, and then call "rts". The rts instruction pulls a 2-byte address off the stack, adds one, and puts that value in the PC address counter, so execution will then continue from that address. This is an oddbod usage, but perfectly valid. I forget if you have to push the high byte onto the stack first, or the low byte. Would be pretty easy to find out. One will work as expected, and the other will work... as expected 1 Quote Link to comment Share on other sites More sharing options...
MLdB Posted May 1, 2020 Share Posted May 1, 2020 48 minutes ago, Andrew Davie said: I forget if you have to push the high byte onto the stack first, or the low byte. I guess it will be high first in, so low is first out as 6502 is little endian, expecting the low byte to be read first. 1 Quote Link to comment Share on other sites More sharing options...
flame Posted May 2, 2020 Author Share Posted May 2, 2020 Thank you all for the explanation, I believe I understand it now so I'll see if I can implement this. Coming back to my original question, is there no way to briefly delay execution of code in the way I need ? Quote Link to comment Share on other sites More sharing options...
MLdB Posted May 2, 2020 Share Posted May 2, 2020 (edited) You need one timer variable per event that you want to delay and one frame counter variable if you need to delay one or more events for more than 256 frames. For the case you describe, let's name these variables framecounter and deathseqtim (for death sequence timer). Once every frame you do: inc framecounter Before an enemy enters the screen you make sure to clear the death sequence timer: lda #0 sta deathseqtim ; as long as deathseqtim is 0, the enemy is still alive When an enemy is killed you do: lda #DEATHSEQDURATION ; duration of sequence in frames. sta deathseqtim ; a non-zero value indicates the enemy died. Now as long as an enemy (alive or dead) is on screen, you do: lda deathseqtim beq SkipDeathSeq ; Remember that 0 means the enemy is still alive. ; deathsegtim is non-zero, the enemy has died. lda #DEADENEMYCOLOR sta COLUP0 ; Or "sta enemy_sprite_color" if needed ; You only need the next three lines if the death sequence should take longer ; than 256 frames. It enables you to have the timer count down only on every ; 2nd, 4th, 8th, etc frame. lda framecounter and #%00000001 ; Count down on even frames only, as an example bne SkipDeathSeq dec deathseqtim bne SkipDeathSeq ; The sequence timer ran out, remove the enemy from the screen. ; (insert whatever you do to remove the enemy) SkipDeathSeq: Different combinations of values for DEATHSEQDURATION and the value used to and the framecounter with, will result in different durations for the sequence. My values here decrement the timer every even frame meaning the 30 counts will expire in 60 frames or 1 second. The timing will be very stable, just a single frame of max difference because of the low value for the and. Edited May 2, 2020 by MLdB Quote Link to comment Share on other sites More sharing options...
flame Posted May 4, 2020 Author Share Posted May 4, 2020 thank you, seems like I got it working. I needed to change some things around but I am happy with this ! Quote Link to comment Share on other sites More sharing options...
flame Posted May 14, 2020 Author Share Posted May 14, 2020 hey @Andrew Davie ; FOREST RIGHT ForestRT subroutine lda #1 sta visible lda worldpos asl tax lda VectorTable,x sta zp lda VectorTable+1,x sta zp+1 jmp (zp) ; The table... VectorTable .word .Well .word .Gremlin .word .None .word .Well .Well: jsr SetWell jmp .Done .Gremlin: jsr SetGremlin jmp .Done .None: ldx #0 stx type .Done: rts I have this in as a test and it appears to be working from $80 onwards, but I am not seeing how I would count from $7F down, am I missing something glaringly obvious or does this code not work like that ? also I assumed that the zp value needed to be a .word ? I tried as a byte and that didn't work for me. Quote Link to comment Share on other sites More sharing options...
+Andrew Davie Posted May 14, 2020 Share Posted May 14, 2020 1 hour ago, pvmpkin said: hey @Andrew Davie ; FOREST RIGHT ForestRT subroutine lda #1 sta visible lda worldpos asl tax lda VectorTable,x sta zp lda VectorTable+1,x sta zp+1 jmp (zp) ; The table... VectorTable .word .Well .word .Gremlin .word .None .word .Well .Well: jsr SetWell jmp .Done .Gremlin: jsr SetGremlin jmp .Done .None: ldx #0 stx type .Done: rts I have this in as a test and it appears to be working from $80 onwards, but I am not seeing how I would count from $7F down, am I missing something glaringly obvious or does this code not work like that ? also I assumed that the zp value needed to be a .word ? I tried as a byte and that didn't work for me. It's mostly OK. Yes, "zp" is two bytes (.word, or better "ds 2"). You're storing into both of them with the "sta zp" and "sta zp+1". Since you had the top bit ($80) set in your values, I advised how to strip this off and still use the values to index the table. So, in other words, the top bit is ignored (discarded by the "asl"). If you want to use $7F down, then you have to re-think things. One way, and NOT the way I would recommend, is to think about what you actually have when you use $7F. The "asl" will shift it left (multiply by 2) to give you a value of %11111110 (i.e., 254). Since you're indexing a word table that's 127th entry. So, if you extend that table and put a handler pointer in at entry #127 then $7F would work. But as I said, this is NOT the way to do it. Consider instead if your worldpos value was simply a number counting from 0 (and not $80). Then you simply take the worldpos, asl to get an index to a word table, and that's all you have to do. It's your original use of adding $80 to the top bit that makes things "wrong", and why $7F doesn't work. Well, it works, but not as you would expect - you don't have an entry in the 127th position of the table. A small optimisation - instead of indexing into a single word table, try dropping the "asl" and use the worldpos as a direct index into TWO tables of byte values - one pointing to the low address of each handler, and the other to the high address of the handler. You can do that with nifty macros... here's how I do it... MAC POSVAL .byte {1}Handler0 .byte {1}Handler1 ;etc... ENDM LoTable POSVAL < HiTable POSVAL > So the access would be... ldx worldpos lda LoTable,x sta zp lda HiTable,x sta zp+1 jmp (zp) Again, this relies on you dropping $80 on the top of worldpos, or masking it out before use above. You can name LoTable and HiTable anything you want - this is just an example of "efficient" macro usage to make table definition easier. Quote Link to comment Share on other sites More sharing options...
flame Posted May 14, 2020 Author Share Posted May 14, 2020 Thank you so much for steering me towards this. It was suggested to me to subtract the worldpos from $80 and then I was able to count down from $7F and works perfectly. After rewriting all of the area data it's saved me a lot of ROM space, much less cycle use and I don't get that issue with out of range branches ! 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.