fprwards and backwards logic in IF statements (in assembly)
fprwards and backwards logic in IF statements (in assembly)
in normal languages you can do an IF statement with multiple conditions quite easily, but assembly is a bit like having the memory of a goldfish..as registers get trashed all the time.
So far, I have been doing things like:testing for the opposite of what im looking for and then skipping over code if the conditions are met.
then testing for the next condition will be handled the same way , so multiple conditions for one IF statement are possible.
Its kind of a pain but works
Another way of doing it with forwards logic would be to test for what im looking for but then call a function, if i wanted multiple conditions to be met it would be another function inside that. The only prob is that leads to so many functions or subroutines being made, as opposed to new labels being made in the above example. The functions would have silly names as they need to be different each time like:
AlmostMoveCharacterLeft:
(test for more things that would stop a character moving left)
AlmostAlmostMoveCharacterLeft:
(test for one more thing then actually move him)
Has anyone found a better way?
So far, I have been doing things like:testing for the opposite of what im looking for and then skipping over code if the conditions are met.
then testing for the next condition will be handled the same way , so multiple conditions for one IF statement are possible.
Its kind of a pain but works
Another way of doing it with forwards logic would be to test for what im looking for but then call a function, if i wanted multiple conditions to be met it would be another function inside that. The only prob is that leads to so many functions or subroutines being made, as opposed to new labels being made in the above example. The functions would have silly names as they need to be different each time like:
AlmostMoveCharacterLeft:
(test for more things that would stop a character moving left)
AlmostAlmostMoveCharacterLeft:
(test for one more thing then actually move him)
Has anyone found a better way?
Re: fprwards and backwards logic in IF statements (in assembly)
Can't you reserve a byte of memory for what you're doing, and set bits as state flags? Or is that dumb/inefficient for some reason?
Re: fprwards and backwards logic in IF statements (in assembly)
If you need to perform multiple tests before doing something, I'd place all the tests, along with the code that does the something, in a single subroutine. The subroutine starts by doing test 1, and returns if it fails. Same for tests 2, 3, etc. If all the tests pass (i.e. you haven't returned yet), you reach the code that does the something.
That's my thoughts on this, anyway. And I acknowledge that I might have completely misunderstood your question.
Re: fprwards and backwards logic in IF statements (in assembly)
I am using bytes for state flags etc.
and yes the second answer is correct for those types of situations. Like the movecharacterleft
the situation where i use backwards logic is: see if the option chosen is the first - then execute it, if not then see if it is the second then execute that
I jp across the code which executes option 1 , then if its not option 2 jp over that code
and yes the second answer is correct for those types of situations. Like the movecharacterleft
the situation where i use backwards logic is: see if the option chosen is the first - then execute it, if not then see if it is the second then execute that
I jp across the code which executes option 1 , then if its not option 2 jp over that code
Last edited by Wall_Axe on Mon Oct 16, 2023 7:25 pm, edited 1 time in total.
Re: fprwards and backwards logic in IF statements (in assembly)
Not that I know of!
If it's a case of "IF A THEN DO X" and there's no "ELSE" function then you can either code it as "IF NOT A THEN SKIP THIS NEXT BIT... (DO X)... CARRY ON" or as "IF A THEN CALL X", so whether you call a function and return, or take no action, you end up in the same place. The former is probably quicker, and doesn't use the stack, but you won't be able to use function X from anywhere else in your code if it's written in-line like that. The latter is more faff but maybe more readable later, and would allow you to call function X from other parts of your code too.
The danger with using CALL is if you have several checks each calling different functions, a return from the first one will land you right back in the middle of your IF checks, rather than at the end of them all. In that case, you might want to do something like put the address you want to return to in HL before doing your IF checks, then jump to the function, and the function either pushes HL onto the stack and does a return, or ends with a JP (HL).
Really whatever you pick is going to be a trade-off between being quick to write (like the first example), more structured and easier to follow (use CALL to separate named functions), speed of processing (where you might write the most common path in-line for speed, and all other cases branch off), or memory efficient (where you might cut out more functions into separate calls so you can re-use them, instead of duplicating simpler tasks whenever you need them, for speed).
And if you have a whole tree of decisions and sub-branches, it's another trade-off between having everything in one place, and branching off to do those sub-clauses. It can be hard to read if everything is broken down and spread across several separate functions, but then you're more likely to fluff up the logic and branching if you try and do it all in one go instead of breaking it down. There's no real answer to that in assembly; that's why structured languages like C exist, to make programming these things easier!
If it's a case of "IF A THEN DO X" and there's no "ELSE" function then you can either code it as "IF NOT A THEN SKIP THIS NEXT BIT... (DO X)... CARRY ON" or as "IF A THEN CALL X", so whether you call a function and return, or take no action, you end up in the same place. The former is probably quicker, and doesn't use the stack, but you won't be able to use function X from anywhere else in your code if it's written in-line like that. The latter is more faff but maybe more readable later, and would allow you to call function X from other parts of your code too.
The danger with using CALL is if you have several checks each calling different functions, a return from the first one will land you right back in the middle of your IF checks, rather than at the end of them all. In that case, you might want to do something like put the address you want to return to in HL before doing your IF checks, then jump to the function, and the function either pushes HL onto the stack and does a return, or ends with a JP (HL).
Really whatever you pick is going to be a trade-off between being quick to write (like the first example), more structured and easier to follow (use CALL to separate named functions), speed of processing (where you might write the most common path in-line for speed, and all other cases branch off), or memory efficient (where you might cut out more functions into separate calls so you can re-use them, instead of duplicating simpler tasks whenever you need them, for speed).
And if you have a whole tree of decisions and sub-branches, it's another trade-off between having everything in one place, and branching off to do those sub-clauses. It can be hard to read if everything is broken down and spread across several separate functions, but then you're more likely to fluff up the logic and branching if you try and do it all in one go instead of breaking it down. There's no real answer to that in assembly; that's why structured languages like C exist, to make programming these things easier!
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
An example of such a condition might help.
You're writing an adventure style game (although menu driven) so you should probably look at how stuff like the Quill handles conditions there, basically
High level (global) conditions - these are checked every time the game state advances
Local (room based) conditions - each room has a list of things to check which can't be done in other locations (e.g. opening a door with a key)
You can also have item based (or skill based) conditions which are checked depending on what you are carrying or if you have a particular skill.
GAC had low priority conditions as well I think which were checked last of all and after player input was processed I seem to remember.
Then you just shoot through all the conditions which are active (either via functions or a jump table to handler routines which jump back to the function which pulls items out of the condition lists to check until there are no more.
I have something like that but for drawing stuff (which I add to a queue)
sample drawing code
EDIT: And I've not even optimised list handling or the draw functions yet, I know they can be made more efficient (but they are fast enough atm so that can come later if there are speed issues).
EDIT2: If you do it like that though you probably want a function that says "don't bother checking any more things, state has changed" e.g. you moved into another room, opened another exit, etc. would likely do that.
You're writing an adventure style game (although menu driven) so you should probably look at how stuff like the Quill handles conditions there, basically
High level (global) conditions - these are checked every time the game state advances
Local (room based) conditions - each room has a list of things to check which can't be done in other locations (e.g. opening a door with a key)
You can also have item based (or skill based) conditions which are checked depending on what you are carrying or if you have a particular skill.
GAC had low priority conditions as well I think which were checked last of all and after player input was processed I seem to remember.
Then you just shoot through all the conditions which are active (either via functions or a jump table to handler routines which jump back to the function which pulls items out of the condition lists to check until there are no more.
I have something like that but for drawing stuff (which I add to a queue)
Code: Select all
ld hl, (drawListPtr) ; note all my draw routines are aligned to 256 byte boundary so this is a single byte, low byte is always 0
; otherwise I'd have to check high byte and low byte being 0 which would take longer. also shortens the amount of data for each list item
nextSprite:
ld a, (hl) ; list is terminated by a single 0
or a
jr z, doneDraw
inc hl
ld (drawListPtr), hl
ld l, 0 ; this is the address of the drawing function to call
ld h, a
jp (hl) ; call it. It will use (drawListPtr) set earlier to remove arguments it wants and set hl to correct value once it has processed the arguments
doneDraw:
ld hl, drawList
ld (drawListPtr), hl
Code: Select all
ALIGN 256
movebufftoscreen:
ld hl, (drawListPtr)
ld e, (hl)
inc hl
ld d, (hl)
inc hl
ld a, (hl)
inc hl
ld b, (hl)
inc hl
ld (drawListPtr), hl
; have our parameters in DE and B and A now
; do things with the stack :) code removed to avoid complicating stuff
; it basically just does H = A, L = B
; then copies 2 bytes at a time (HL++) to (DE++) 8 times, so like this in C
; for(b = 8; b > 0; --b) {
; *de++=*hl++;
; *de++=*hl++;
; }
; it's a bit more complicated than that cos of the speccy screen layout of course
ld hl, (drawListPtr) ; set HL to correct value for next item in the list (will point to 0 if there are no more)
jp nextSprite ; jump back to handler
EDIT2: If you do it like that though you probably want a function that says "don't bother checking any more things, state has changed" e.g. you moved into another room, opened another exit, etc. would likely do that.
Re: fprwards and backwards logic in IF statements (in assembly)
Well, I would say that's not that hard
Let's suppose you want to do:
IF conditionA AND conditionB AND conditionC THEN MakeActionX
ELSE MakeActionY
In a mix of assembly and pseudocode it could be:
TestConditionA
JP NZ, MakeActionY
TestConditionB
JP NZ, MakeActionY
TestConditionC
JP NZ, MakeActionY
JP MakeActionX
As you can see, you need to do it step by step, not in a single instruction.
We assume here that by testing some condition, you set the zero flag. If condtion is true then the flag is set, otherwise it is reset.
And remember to name your variables and labels wisely. AlmostAlmostMoveCharacterLeft is almost certainly a bad name
Let's suppose you want to do:
IF conditionA AND conditionB AND conditionC THEN MakeActionX
ELSE MakeActionY
In a mix of assembly and pseudocode it could be:
TestConditionA
JP NZ, MakeActionY
TestConditionB
JP NZ, MakeActionY
TestConditionC
JP NZ, MakeActionY
JP MakeActionX
As you can see, you need to do it step by step, not in a single instruction.
We assume here that by testing some condition, you set the zero flag. If condtion is true then the flag is set, otherwise it is reset.
And remember to name your variables and labels wisely. AlmostAlmostMoveCharacterLeft is almost certainly a bad name
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
For my movement code I check all the conditions and only if all collision checks pass etc. do I update the piece position
So for moving left it is:
is column <= 1? ; at left hand edge
jr z, donemoveleft
; are we at an even column? we are always ok to move then, since collision is 2 cells wide
is column even?
jr z, allowmoveleft
; checks collision now on the left
collided with something?
if yes: jr donemoveleft
allowmoveleft:
; update piece position
donemoveleft:
etc.
EDIT: Anything more complicated I'd go down the list(s) of conditions to check though (some lists can be static, e.g. a list per room). You may want a flag on each condition to say "this no longer occurs, do not check" (e.g. once you have used a key on a door don't check it again)
EDIT2: Might be worth stubbing out your condition handlers on a PC in C or some other high level language (I'd use C# personally) that you can easily convert to Z80 once you have it working as intended on the PC.
EDIT3: When I say "list" I mean just an array (of variable length items) terminated by NULL (or you can store a length if you want, probably harder to maintain that though). If a handler bails out early (e.g. it sees its condition is disabled) remember to set the next item to process pointer correctly (by skipping any parameters you did not remove from the list during processing).
So if you have a handler with 5 bytes of parameters but by the time you have processed the first one, you see it does not apply, remember to set the current list pointer to the next item, not where you got to when extracting the parameters.
So for moving left it is:
is column <= 1? ; at left hand edge
jr z, donemoveleft
; are we at an even column? we are always ok to move then, since collision is 2 cells wide
is column even?
jr z, allowmoveleft
; checks collision now on the left
collided with something?
if yes: jr donemoveleft
allowmoveleft:
; update piece position
donemoveleft:
etc.
EDIT: Anything more complicated I'd go down the list(s) of conditions to check though (some lists can be static, e.g. a list per room). You may want a flag on each condition to say "this no longer occurs, do not check" (e.g. once you have used a key on a door don't check it again)
EDIT2: Might be worth stubbing out your condition handlers on a PC in C or some other high level language (I'd use C# personally) that you can easily convert to Z80 once you have it working as intended on the PC.
EDIT3: When I say "list" I mean just an array (of variable length items) terminated by NULL (or you can store a length if you want, probably harder to maintain that though). If a handler bails out early (e.g. it sees its condition is disabled) remember to set the next item to process pointer correctly (by skipping any parameters you did not remove from the list during processing).
So if you have a handler with 5 bytes of parameters but by the time you have processed the first one, you see it does not apply, remember to set the current list pointer to the next item, not where you got to when extracting the parameters.
Re: fprwards and backwards logic in IF statements (in assembly)
Ralf has put his finger on the way that we do it. You can easily have a mix of OR and AND conditions, too. The condition code just threads through a series of checks, one at a time. You don't need to track the state of where you are at within the condition checking in an additional register, the current position in the code tracks that automatically:
In the same mix of assembly and pseudocode it could be this, which also has shortcircuiting (you can tell that because there are two separate jumps to MakeActionY, and if the first jump is taken then conditionC is never checked. And if the IsAOrB jump is taken then conditionB is never checked):
Code: Select all
IF ((conditionA OR conditionB) AND conditionC) THEN MakeActionX
ELSE MakeActionY
Code: Select all
TestConditionA
JP Z, IsAOrB
TestConditionB
JP NZ, MakeActionY
IsAOrB:
TestConditionC
JP NZ, MakeActionY
JP MakeActionX
Last edited by Seven.FFF on Mon Oct 16, 2023 9:07 pm, edited 9 times in total.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel • NXTP • ESP Update • ESP Reset • CSpect Plugins
SevenFFF / Threetwosevensixseven / colonel32
NXtel • NXTP • ESP Update • ESP Reset • CSpect Plugins
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
For super complicated decisions you can use an expression tree and you can do those pretty easily if you use reverse polish notation (so like Forth) for that. You can use a stack then to traverse the tree
so
IF (A and B) OR (C and D)
turns into
A B AND C D AND OR
where A, B, C D are atoms (T or F, for true or false) so they just push themselves onto a stack (does not have to be the machine stack pointed to by SP, probably easier if it isn't)
AND and OR consume the top 2 items on the stack and replace it with T or F as appropriate. When the expression is finished you are left with either T or F on the top of the stack.
You can also do short-circuit evaluation that way.
LISP syntax is ok for that as well (OR (AND A B) (AND C D))
So to evaluate if(T OR F)
T F OR
1: push T onto stack. stack: T
2: push F onto stack. stack: F T
3: evaluate OR -- pops F and T, F OR T is true, push T onto stack. stack: T
we are done, top of stack is T, which is result of T OR F
EDIT: Obvs more useful with a more concrete example, say you have a function HERE which takes an item and pushes T or F onto the stack depending upon whether it is in the room or carried and UNLOCKED which takes a lockable object and pushes whether it is locked or not. Then you can have
KEY HERE CHEST HERE CHEST UNLOCKED NOT AND AND
would mean if the chest is NOT UNLOCKED AND the chest is HERE AND so is the KEY, this condition would be true. NOT pops the top of the stack and pushes F if it was T and vice versa
Advantage of doing something like that is the expression evaluator is very simple even for complicated expressions. If you know all functions (like HERE, AND, OR, NOT, UNLOCKED) have addresses > #4000 you can just use DW to define a list of words to check (objects would have IDs < #4000). You can also make items 8 bits if you want but that makes it more complicated to evaluate (since you need to know what is a function and what is an object ID)
Evaluator:
look at first item in list (so KEY). It's < #4000, so push it to the evaluation stack
second item is HERE which is a function since it is >= #4000. Call it. It removes KEY from the stack and pushes T or F depending on whether it is here or held
etc. until you reach the end of the data for that condition (length or terminate it somehow).
If the stack has more than 1 item on it at the end of all that, it's an error (unless you want to do subroutines which are also very easy to do with this method). Popping an empty stack is always an error though
so
IF (A and B) OR (C and D)
turns into
A B AND C D AND OR
where A, B, C D are atoms (T or F, for true or false) so they just push themselves onto a stack (does not have to be the machine stack pointed to by SP, probably easier if it isn't)
AND and OR consume the top 2 items on the stack and replace it with T or F as appropriate. When the expression is finished you are left with either T or F on the top of the stack.
You can also do short-circuit evaluation that way.
LISP syntax is ok for that as well (OR (AND A B) (AND C D))
So to evaluate if(T OR F)
T F OR
1: push T onto stack. stack: T
2: push F onto stack. stack: F T
3: evaluate OR -- pops F and T, F OR T is true, push T onto stack. stack: T
we are done, top of stack is T, which is result of T OR F
EDIT: Obvs more useful with a more concrete example, say you have a function HERE which takes an item and pushes T or F onto the stack depending upon whether it is in the room or carried and UNLOCKED which takes a lockable object and pushes whether it is locked or not. Then you can have
KEY HERE CHEST HERE CHEST UNLOCKED NOT AND AND
would mean if the chest is NOT UNLOCKED AND the chest is HERE AND so is the KEY, this condition would be true. NOT pops the top of the stack and pushes F if it was T and vice versa
Advantage of doing something like that is the expression evaluator is very simple even for complicated expressions. If you know all functions (like HERE, AND, OR, NOT, UNLOCKED) have addresses > #4000 you can just use DW to define a list of words to check (objects would have IDs < #4000). You can also make items 8 bits if you want but that makes it more complicated to evaluate (since you need to know what is a function and what is an object ID)
Evaluator:
look at first item in list (so KEY). It's < #4000, so push it to the evaluation stack
second item is HERE which is a function since it is >= #4000. Call it. It removes KEY from the stack and pushes T or F depending on whether it is here or held
etc. until you reach the end of the data for that condition (length or terminate it somehow).
If the stack has more than 1 item on it at the end of all that, it's an error (unless you want to do subroutines which are also very easy to do with this method). Popping an empty stack is always an error though
Re: fprwards and backwards logic in IF statements (in assembly)
thats an interesting way of using the stack, might have to use that later.
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
Most compilers do something like that as an intermediate step for expression evaluation.
As I said, it's very simple.
Don't use the actual machine (SP) stack though EXX is an excellent choice for things like that though (always have your expression stack in H'L' and pop into D'E' say, with B'C' a count of how many things are on the stack say?) Just reserve a workspace area of say 256 bytes or less for the expression stack (and report an error and stop if you push too much too it in a debug build - which will then tell you if you did something wrong or you need to make your workspace area bigger in future).
Also, don't mess with the actual SP (by changing it to something else, with the idea of changing it back later) unless you really know what you are doing.
You might want to push a tag for each item onto the stack (so TAG_NUMBER, TAG_ITEM, TAG_FUNCTION) etc. to make deciding what is actually on the stack when you pop it easier (another reason not to use the processor stack - you might want to push 8 bit items onto it). Giving TRUE and FALSE their own tags might be handy as well (they can just be tags then, no additional data).
For messages you can then have
TAG_MSG, msgID, TAG_FUNCTION, displayMessage, END_OF_EXPRESSION
and calling subroutines can have
TAG_FUNCTION, mySubroutine, TAG_CALL, (... stuff to do after mySubroutine returns here)
which stores the point in the list you were at - you can usually use the stack for that as well (or you might have a separate stack just for subroutine return addresses - that's probably easier thinking about it), sets the list pointer to mySubroutine list instead, executes that until TAG_RETURN or END_OF_EXPRESSION, etc.
If the RPN thing gets on your nerves you can write a parser on the PC which turns normal looking expressions into RPN data.
EDIT: Anyway, golden rule is, when expressions start getting complicated it's usually easier to make your code data driven than end up with loads of complicated logic in ASM (and loads of code). With a data-driven expression evaluator you only have a small amount of code that is debugged and tested
I'm sure ketmar will have more forth like tips as well
As I said, it's very simple.
Don't use the actual machine (SP) stack though EXX is an excellent choice for things like that though (always have your expression stack in H'L' and pop into D'E' say, with B'C' a count of how many things are on the stack say?) Just reserve a workspace area of say 256 bytes or less for the expression stack (and report an error and stop if you push too much too it in a debug build - which will then tell you if you did something wrong or you need to make your workspace area bigger in future).
Also, don't mess with the actual SP (by changing it to something else, with the idea of changing it back later) unless you really know what you are doing.
You might want to push a tag for each item onto the stack (so TAG_NUMBER, TAG_ITEM, TAG_FUNCTION) etc. to make deciding what is actually on the stack when you pop it easier (another reason not to use the processor stack - you might want to push 8 bit items onto it). Giving TRUE and FALSE their own tags might be handy as well (they can just be tags then, no additional data).
For messages you can then have
TAG_MSG, msgID, TAG_FUNCTION, displayMessage, END_OF_EXPRESSION
and calling subroutines can have
TAG_FUNCTION, mySubroutine, TAG_CALL, (... stuff to do after mySubroutine returns here)
which stores the point in the list you were at - you can usually use the stack for that as well (or you might have a separate stack just for subroutine return addresses - that's probably easier thinking about it), sets the list pointer to mySubroutine list instead, executes that until TAG_RETURN or END_OF_EXPRESSION, etc.
If the RPN thing gets on your nerves you can write a parser on the PC which turns normal looking expressions into RPN data.
EDIT: Anyway, golden rule is, when expressions start getting complicated it's usually easier to make your code data driven than end up with loads of complicated logic in ASM (and loads of code). With a data-driven expression evaluator you only have a small amount of code that is debugged and tested
I'm sure ketmar will have more forth like tips as well
Re: fprwards and backwards logic in IF statements (in assembly)
i suppose thts almost like creating a new language, but might be necessary for some complicated things.
The most complicated things ive ran into so far were:
which in assembly is:
i did that using jp,nz only. so it was convoluted not very readable but works well.
ive gotten into the habit of doing the backwards logic so i just use it,even though it might be easier to do it using ret and call
the keyboard read function you shared uses it,hence the labels upisnotpressed:
The most complicated things ive ran into so far were:
Code: Select all
if keypressed
is_key_pressed=1
else
if is_key_pressed=1
do_action()
is_key_pressed=0
endif
endif
Code: Select all
call read_keyboard
LD A,-1
CP B
jp Z,keyispressed
LD A,(uppressed)
CP 1
JP NZ,dontmovearrowup
call optionsup
LD A,0
LD (uppressed),A
JP dontmovearrowup
keyispressed:
LD A,1
LD (uppressed),A
ret
dontmovearrowup:
ive gotten into the habit of doing the backwards logic so i just use it,even though it might be easier to do it using ret and call
the keyboard read function you shared uses it,hence the labels upisnotpressed:
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
I'd separate your move and select logic from the menu contents.
Have a current menu ptr which knows how many items are in the menu and call menuupdown as soon as you read the keyboard into B, which knows that the value will be in the B register.
Something like this for menu data
and have the menu handler code just inc and dec (currentmenuoption), including wraparound if you want that
if you need parameters for your handler functions you can add them as well. Best to use a fixed amount of parameters (1 byte or 2 bytes) for all functions, don't make the number of parameters variable length, if you need more than 2 bytes put a pointer to the parameter in the 2 bytes instead. This assumes the function you call knows what to do with the parameters it is passed (usually passed in DE)
To work out the pointer to optionNtext for option N you just need to calculate
(currentmenu) + 1 + currentmenuoption * 4 ; since we have 2 words of data per menu option at the moment. the add 1 byte is because we have the number of items as the first byte so need to skip over that
You can do a multiply by 4 by doing
ADD HL, HL
ADD HL, HL
then add on the currentmenu pointer to that. The handler to call is 2 bytes after that
When you press the select button set HL to the onoptionNpressed function and make a call to there. The easiest way to call a function through a pointer is to have the following function in your code (which does not return)... you only need 1 copy of this function (you can even find this instruction in the ROM and use that if you want lol)
JPHL:
jp (hl)
then when you want to call it use
call JPHL
then returning from the handler will all magically work as if you called the function
Note jp (hl) is terribly named it should be jp hl
so doing this will call my_func then return and get stuck in an infinite loop (so you can then bring up the debugger and verify you are in the endless loop).
ld hl, my_func
call JPHL
; test... go into an infinite loop so we can check in the debugger this is where we returned to
.endlessloop
jr .endlessloop
my_func:
ret
try that and you will see it calls my_func then when it returns it is at the endless loop in the debugger.
EDIT: If you do something like that you don't have to do stuff like
ld a, -1
cp b ; is b -1?
you can just do something like this
ld hl, currentmenuoption
ld a, (hl)
add b ; subtract 1 if b was -1, otherwise add 1
; verify we haven't gone off top or bottom of menu (wrap it around if you like)
; if all is ok
ld (hl), a
Have a current menu ptr which knows how many items are in the menu and call menuupdown as soon as you read the keyboard into B, which knows that the value will be in the B register.
Something like this for menu data
Code: Select all
mainmenu db 3 ; num items
dw option0text ; pointer to text for option 0
dw onoption0pressed; handler for option 0
dw option1text ; pointer to text for option 1
dw onoption1pressed; handler for option 1
dw option2text ; pointer to text for option 2
dw onoption2pressed; handler for option 2
currentmenu dw mainmenu ; start at mainmenu
currentmenuoption db 0 ; first option in the list
if you need parameters for your handler functions you can add them as well. Best to use a fixed amount of parameters (1 byte or 2 bytes) for all functions, don't make the number of parameters variable length, if you need more than 2 bytes put a pointer to the parameter in the 2 bytes instead. This assumes the function you call knows what to do with the parameters it is passed (usually passed in DE)
To work out the pointer to optionNtext for option N you just need to calculate
(currentmenu) + 1 + currentmenuoption * 4 ; since we have 2 words of data per menu option at the moment. the add 1 byte is because we have the number of items as the first byte so need to skip over that
You can do a multiply by 4 by doing
ADD HL, HL
ADD HL, HL
then add on the currentmenu pointer to that. The handler to call is 2 bytes after that
When you press the select button set HL to the onoptionNpressed function and make a call to there. The easiest way to call a function through a pointer is to have the following function in your code (which does not return)... you only need 1 copy of this function (you can even find this instruction in the ROM and use that if you want lol)
JPHL:
jp (hl)
then when you want to call it use
call JPHL
then returning from the handler will all magically work as if you called the function
Note jp (hl) is terribly named it should be jp hl
so doing this will call my_func then return and get stuck in an infinite loop (so you can then bring up the debugger and verify you are in the endless loop).
ld hl, my_func
call JPHL
; test... go into an infinite loop so we can check in the debugger this is where we returned to
.endlessloop
jr .endlessloop
my_func:
ret
try that and you will see it calls my_func then when it returns it is at the endless loop in the debugger.
EDIT: If you do something like that you don't have to do stuff like
ld a, -1
cp b ; is b -1?
you can just do something like this
ld hl, currentmenuoption
ld a, (hl)
add b ; subtract 1 if b was -1, otherwise add 1
; verify we haven't gone off top or bottom of menu (wrap it around if you like)
; if all is ok
ld (hl), a
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
Although I think you said you are responding to key releases rather than keypresses in which case stash the previous value of B in a variable, and if after you read the keyboard you find up/down is not pressed, LD B with the last keypress instead.
A better way would be to have a key repeat timer though which you could add to the read_keyboard routine (or do it just after that returns).
Then you can make the key respond as soon as you press it (increment a timer for every frame you are pressing it), and when that timer gets to the repeat time, set the timer back to 0 (or 1) and set the "up/down" pressed state again, so it does not go off every frame.
In pseudo code
If you do it that way, it always responds as soon as you press up or down for the first time but then doesn't respond again until you hold it down for REPEAT_DELAY frames. But if you release and repress it, it responds immediately again.
You can get fancier and have REPEAT_REPEAT timer as well where you do things like
as soon as key pressed -> respond
if held down -> send the key again after REPEAT_DELAY frames
if you keep holding it -> send the key every REPEAT_REPEAT frames now instead
which is how the spectrum basic editor/input system works (and you can set both the delay times by poking the sysvars)
EDIT: If you use timers like that though you will want to put a HALT instruction at the end of your main loop so you only update every 1/50th of a second.
Also only call read_keyboard once a frame and stash the results if you need them later on. Ideally you would process all the input as soon as read_keyboard returned though
A better way would be to have a key repeat timer though which you could add to the read_keyboard routine (or do it just after that returns).
Then you can make the key respond as soon as you press it (increment a timer for every frame you are pressing it), and when that timer gets to the repeat time, set the timer back to 0 (or 1) and set the "up/down" pressed state again, so it does not go off every frame.
In pseudo code
Code: Select all
initalise updownrepeat timer to 0 at start of game
call read keyboard
if up or down was pressed (you'll notice my routine returns 0 if both are pressed same time so you don't need to worry about that)
; so you can just ld a, b : or a : jr nz, upordownwaspressed or something
increment updownrepeattimer
else updownrepeattimer = 0
if (updownrepeattimer > REPEAT_DELAY)
updownrepeattimer =1
if(updownrepeattimer == 1)
perform the action
You can get fancier and have REPEAT_REPEAT timer as well where you do things like
as soon as key pressed -> respond
if held down -> send the key again after REPEAT_DELAY frames
if you keep holding it -> send the key every REPEAT_REPEAT frames now instead
which is how the spectrum basic editor/input system works (and you can set both the delay times by poking the sysvars)
EDIT: If you use timers like that though you will want to put a HALT instruction at the end of your main loop so you only update every 1/50th of a second.
Also only call read_keyboard once a frame and stash the results if you need them later on. Ideally you would process all the input as soon as read_keyboard returned though
Re: fprwards and backwards logic in IF statements (in assembly)
Putting the function pointer with the menu is a good idea, I'll have to study it a bit more though
Re: fprwards and backwards logic in IF statements (in assembly)
I suppose the situation I'm thinking about is wanting to do something AND return. Whereas you can only do one or the other using basic assembly commands.
Storing the location on a stack could be risky for creating bugs?
Storing the location on a stack could be risky for creating bugs?
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
I don't get what you are on about there, sorry.
What do you mean by "wanting to do something and return"? Do you mean return from a function after it does something or return from a menu?
You can have as many ret statements in a function as you want (as long as you make sure the stack is always balanced).
If you want to give an example of what you mean use pseudo code (or code in C, which can be pseudocode as well), since it's easier to express higher level concepts then.
What do you mean by "wanting to do something and return"? Do you mean return from a function after it does something or return from a menu?
You can have as many ret statements in a function as you want (as long as you make sure the stack is always balanced).
If you want to give an example of what you mean use pseudo code (or code in C, which can be pseudocode as well), since it's easier to express higher level concepts then.
Re: fprwards and backwards logic in IF statements (in assembly)
It's better to get out of the habit of thinking in BASIC and then trying to write that in assembly. Instead just think about what you need to do to accomplish the task in assembly directly.
Conditions are checked by setting flags. If you need the results later you can either save them somewhere or just re-do the test again. Re-reading a value from memory and doing another compare is a tiny operation compared to even decoding the "IF" statement in BASIC. Don't try to micro-optimise before you're comfortable coding (and thinking) in assembly.
is perfectly fine code when starting out.
Conditions are checked by setting flags. If you need the results later you can either save them somewhere or just re-do the test again. Re-reading a value from memory and doing another compare is a tiny operation compared to even decoding the "IF" statement in BASIC. Don't try to micro-optimise before you're comfortable coding (and thinking) in assembly.
Code: Select all
LD A, (value_to_check)
CP 6
CALL Z, value_is_6
LD A, (value_to_check)
CP 4
CALL Z, value_is_4
RET
Re: fprwards and backwards logic in IF statements (in assembly)
If you want to call a routine which returns from the calling routine, you can pop and discard the first return address before returning.
You can also jump to the first routine instead of calling it, so the ret returns from the first routine.
Code: Select all
call FirstRoutine
; SecondRoutine returns here
FirstRoutine:
call SecondRoutine
; SecondRoutine does NOT return here
ret
SecondRoutine:
; adjust the stack to return to the caller’s caller
pop hl
ret
Code: Select all
call FirstRoutine
; SecondRoutine returns here
FirstRoutine:
jp SecondRoutine
; SecondRoutine does NOT return here
ret
SecondRoutine:
; return to the caller’s caller
ret
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel • NXTP • ESP Update • ESP Reset • CSpect Plugins
SevenFFF / Threetwosevensixseven / colonel32
NXtel • NXTP • ESP Update • ESP Reset • CSpect Plugins
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
Using the machine stack (SP) can be tricky since it stores the return address on the stack but it is easy to write stack functions which do not use SP if you need an independent (or several) stacks.
e.g.
Code: Select all
stackworkspace BLOCK STACK_SIZE ; some assemblers use DEFS instead of BLOCK to reserve memory
MyStackPtr dw stackworkspace
; can have a count of stack current size if you want to or you can work it out on demand
; push DE to stack
push_to_mystack:
ld hl, (MyStackPtr)
ld (hl), e
inc hl
ld (hl), d
inc hl
ld (MyStackPtr), hl
ret
; pop from stack into DE
pop_from_mystack:
ld hl, (MyStackPtr)
dec hl
ld d, (hl); pop high byte first since we are moving stack pointer backwards in memory
dec hl
ld e, (hl)
ld (MyStackPtr), hl
ret
With your own stack you can push 8 bit values as well (although you need to know how many bytes to pop which is why tagged data helps)
Re: fprwards and backwards logic in IF statements (in assembly)
You can, but don't.
When you're just starting out in assembly you will absolutely confuse the F out of yourself if you start manipulating the stack like this. It's a more advanced technique and really only necessary for squeezing every last drop out of CPU time.
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
I agree.AndyC wrote: ↑Tue Oct 17, 2023 12:10 pm You can, but don't.
When you're just starting out in assembly you will absolutely confuse the F out of yourself if you start manipulating the stack like this. It's a more advanced technique and really only necessary for squeezing every last drop out of CPU time.
A good tip worth doing though is replace this
call func
ret
with this
jp func
which always works (as long as you don't try and later do something after the jp of course) I usually just comment out the call/ret combo when replacing it with a jp since then I know what my intention was.
Dropping into functions can be useful as well
setupforfunc: ; sometimes need to do this code e.g. initialisation, doing an expensive calculation we don't need to do if we already know the anwer etc.
; drop into the following func instead of returning
func:
; stuff
ret
e.g. I have drawsprite which calculates the screen address but another entry point drawspriteknowaddress which skips that part of the function, so drawsprite "drops into" drawspriteknowaddress once it has calculated the address
Re: fprwards and backwards logic in IF statements (in assembly)
I think this has gone way past confusing the OP. He can just do a bunch of simple condition checks chained together as originally suggested,but he seems to want to make it more complicated and use the stack.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel • NXTP • ESP Update • ESP Reset • CSpect Plugins
SevenFFF / Threetwosevensixseven / colonel32
NXtel • NXTP • ESP Update • ESP Reset • CSpect Plugins
- ParadigmShifter
- Manic Miner
- Posts: 951
- Joined: Sat Sep 09, 2023 4:55 am
Re: fprwards and backwards logic in IF statements (in assembly)
I very very rarely do a double return by popping the return address of the calling function before returning but that is very rare (not done it in current code, I think I did it once in my console code stuff once only, can't remember why).
func1:
call func2
ret
func2:
call func3
ret
func3:
; return to func1 instead of func2
pop af ; pop return address into some register pair you don't care about (if you care about all registers, you can use INC SP twice but that is slower)
ret ; will return to func1 instead of func2
I wouldn't recommend that though in 99.99% of cases
func1:
call func2
ret
func2:
call func3
ret
func3:
; return to func1 instead of func2
pop af ; pop return address into some register pair you don't care about (if you care about all registers, you can use INC SP twice but that is slower)
ret ; will return to func1 instead of func2
I wouldn't recommend that though in 99.99% of cases