Calculator Community > Axe

Converting from Prgm to App

<< < (2/4) > >>

E37:
@Ki1o Sorry I took so long, I forgot.
I sent your code to my calc and gave it a look. The code in its original state worked fine as a program and an app. As a program, it leaves garbage all over the homescreen on quit. Adding a 'ClrHome' in Exit will fix that. The total compiled program is only 8k bytes total. Since probably 1k of that is data, you still have some space to play with before you hit the execution limit.

When I uncommented all of your AI code, it consistantly corrupted regardless of whether it was a program or app. I also noticed that the slimes always ignored walls - even before corrupted garbage showed up on screen. I am still digging around but my guess is that bu the time the character walks around that big loop, the slimes move so far out of bounds that they corrupt some area of memory. Regardless of what the cause is, as far as I can tell, it has to do with the mob AI and not compiling as an app.

Ki1o:
Thanks for testing! I started to think it may be the AI and you just confirmed it. I am still moving forward with the rewrite because I realized that there were issues with the way I was handling the turn based game loop, so I'm hoping to eliminate any other unexpected behavior with better code. Also I just want to confirm, there should be no issue using custom variables to call function labels right?

E37:
Alright, a rewrite often helps eliminate nasty bugs. And there is no difference at all between built in variables and custom ones. Anything you can do with one, you can do with the other.

Since you are rewriting, I have a couple suggestions:


You make two appvars but both are a fixed size. You could combine them into one big appvar. Additionally, you only need to call GetCalc after creating new appvars or resizing existing ones. Once you have created all of them, they won't move.



Free RAM areas:
0x966E is 128 bytes of free memory like L5. It normally holds the contents of the homescreen. (So Disp will overwrite data like L5) When you quit, call Fill(0x966E, 128, ' '). That is just filling it with the space character so it appears blank. I use this one a bunch so I am super positive it is very safe.
0x8000 is 256 bytes of free memory. The end of it overlaps with L4-512 (not a problem if you don't use L4-512 as a 756 byte buffer) But even if you do, it is still 165 bytes of memory. You don't need to clear it or anything when you quit. This also means that if you combine 0x8000 and L4, you get a 933 byte long block of memory. Just remember that it will be corrupted on flash operations (which you aren't doing)
0x8E2C is 400 bytes of free memory. Just fill it with 0's when you quit.
0x85E7 is 158 bytes of free memory. I don't think you even need to fill it with 0's when you quit but you might as well to be safe.
L2 can be used as a 768 byte buffer if you fill it with 0's when you quit. (Hint: you can use ClrDraw(L2) instead of Fill(L2, 768, 0). Its faster and smaller (assuming you have used ClrDraw(buffer) somewhere else)
I think that is all the juicy free memory areas. There is quite a bit of smaller ones but they aren't really worth it unless you are really hurting. Omnimaga's free ram page is a very good tool (Ignore anything it says about Axe - it is very outdated) When using it, make sure to double check that you aren't overlapping any of Axe's variables. To do that, you should do 'Print oA>Hex' Repeat for A-theta, r1-r6, Y1-Y10 (Y variables take up 3 bytes each), X1T-Y6T (the parametric variables - yes, those are built in Axe variables!) and L1-L6. Compare the printed addresses to the free ram are you are looking at.




When dealing with the AI of a bunch of different mobs, it is really awkward (and super slow) to keep having to call {CurrentMob + 5}r and the like to access its variables.
Compare these two pieces of code:

--- Code: (Hard to read) ---0->MobX
2->MobY
4->MobHP

Lbl MobAI .r1 is the pointer to the mob's data
r1->X .save R1 so function calls don't clobber it

.IsWall and similar functions are just made up psuedocode for the example
!If IsWall({X + oMobX}r + 1, {X + oMobY}r) .Simple, but it will get messy quickly
{X + oMobX}r++
End

!If IsWall({X + oMobX}r, {X + oMobY}r + 1)
{X + oMobY}r++
End

!If {X + oMobHP}r
MobDies(X)
End

Return

--- End code ---

Versus:

--- Code: (Easy to read) ---L1+0->MobX .Put your Mob data at a predetermined spot. I usually use 0x966E (described above in the free ram section) but you can put it wherever you want.
L1+2->MobY
L1+4->MobHP

Lbl MobAI .r1 is the pointer to the mob's data
r1->X .save the value of r1
Copy(X, oMobX, SizeOfMob) .Copies the mob's data to a predetermined spot. Fill in SizeOfMob with however big your mobs are, in bytes.

!If IsWall(MobX + 1, MobY) .This is so much easier to read and type than the above example. And it is way faster and uses less space!
MobX++
End

!If IsWall(MobX, MobY + 1)
MobY++
End

!If MobHP
MobDies(oMobX)
End

Copy(oMobX, X, SizeOfMob) .Copy the data back now that we are done
Return


--- End code ---






Asm(E5) pushes HL (which is Axe's version of TI-BASIC's Ans) onto the stack and Asm(E1) pops it. That lets you write code like:

--- Code: ---Lbl XMagic
X:Asm(E5) .Set X as Axe 'Ans' variable and then push it onto the stack

5->X .Modify and use X however you want
Disp X>Dec

Asm(E1)->X .Pop the last entry from the stack and set X to it
Return

--- End code ---

This is super useful as it lets you create local variables.
I don't know how familiar you are with assembly and registers, so I'll explain in more detail what that code does. If you understand what 'push hl' and 'pop hl' means, you can ignore all of this.


--- Code: ---.Like TI-BASIC, Axe has a variable which is the result of the previous calculation. However, Axe's version doesn't have a name.
X+5 .Add 5 to X but don't do anything with the result. The value is stored in HL which is what Axe's 'Ans' is called.
->Y .Store HL to Y. Since the result hasn't changed, we can use HL again
+2->Z .Add 2 to HL and then store HL to Z
X .Sets HL to the value of X
Asm(E5) .Pushes the value of HL onto the stack. You can push and pop whenever you want as long as you know what you are doing
+5 .Adds 5 to HL
SomeFunction() .Call some function that possibly modifies X. At the start of the function, HL will hold X+5. (so the first line of the function could be '->X' for instance)
Asm(E1) .Pops HL from the stack. Now HL holds the same value it did when the most recent Asm(E5) was called
->X .And save that value to X, effectively protecting it from any modifications that might happen in SomeFunction. (Called functions can push and pop if they want too)

.You can push multiple things to the stack at the same time
X:Asm(E5)
Y:Asm(E5)
MyVariable:Asm(E5)

For(x, 0, 5)
Disp X>Dec
End

Asm(E1)->MyVariable .Notice that the order of pops is inverted from the order of pushes
Asm(E1)->Y
Asm(E1)->X

...
There are some restrictions on when you can push and pop from the stack. Well, you can do it at any time, but if you don't know what you are doing, all you will get is a crash
You must make sure that you call pop the same amount of times you call push before you call 'return'. (This is easy, all it means is that you can't call push a bunch at the start of a function and then call Return without popping everything. You don't need to do anything with the popped results if you don't want to)
The stack is used when calling functions. All that means is that you can't push a variable in one function, call another and pop that variable in the function you just called
The stack is also used for single argument For loops. (3 argument for loops don't have this problem - they use a much less efficient method of looping that doesn't use the stack) That means you can't push a variable outside a For(10) loop and pop it inside the loop. (it is still safe to push and pop inside the loop, just make sure your pushes and pops are balanced inside the loop body.
If you just push some variables at the start of a function and pop them at the end, you will always be safe. It also works well inside complex loops.
...


--- End code ---

All this is mostly useful for pushing some variables at the start of a function and then popping them at the end so you don't need to worry about clobbering a variable used elsewhere. Its also super fast, a push or pop is almost twice as fast as addition! (*comparing 'push hl' [11cc] and 'pop hl' [10cc] against 'add hl, de' [19cc]) I would recommend spending 15-20 minutes trying out different uses for this before putting it any serious code. Its no fun debugging code you don't fully understand.
I use this so frequently in my code that I wrote a personal axiom that adds commands 'Push' and 'Pop' that just do Asm(E5) and Asm(E1) respectively.

Ki1o:
@E37 As usual, thanks for all the help! I will definitely be making use of this. I figured dereferencing for each variable would be slow but your idea is wayy better. Idk what I was thinking. I was also thinking of creating one big appvar instead of creating multiple appvars with GetCalc() so I'll definitely be doing that. I've run into a weird little bug/issue with Axe that I cannot explain and I was hoping you would be able give me some answers. What I am currently attempting to do in my rewrite at the moment is ensure that only 1 keypress is registered at a time. I'm following a tutorial series and although it's in another language it's pretty easy to convert to Axe. Anyways, my original input code looked like this:

--- Code: ---Lbl UpdateGame
If getKey(2)
PlayerX--
⁻8→ScrollXOffset
ʟUpdatePlayer→UpdateState
End
If getKey(3)
PlayerX++
8→ScrollXOffset
ʟUpdatePlayer→UpdateState
End
If getKey(4)
PlayerY--
⁻8→ScrollYOffset
ʟUpdatePlayer→UpdateState
End
If getKey(1)
PlayerY++
8→ScrollYOffset
ʟUpdatePlayer→UpdateState
End
If getKey(15)
ʟUpdateQuit→UpdateState
End
Pause 16
Return
Lbl UpdatePlayer
If ScrollXOffset<<0
ScrollLeft()
End
If ScrollXOffset>>0
ScrollRight()
End
If ScrollYOffset<<0
ScrollUp()
End
If ScrollYOffset>>0
ScrollDown()
End
If ScrollXOffset=0 ? ScrollYOffset=0
ʟUpdateGame→UpdateState
End
Return
Lbl UpdateQuit
1→Quit
Return

--- End code ---
However this results in diagonal movement when 2 buttons are pressed, which I don't want. So, following the tutorial I tried this code:

--- Code: ---Lbl UpdateGame
For(I,1,4)
If getKey(I)
PlayerX+sign{I-1+°DirX}→PlayerX
PlayerX+sign{I-1+°DirY}→PlayerY
sign{I-1°DirX}*8→ScrollXOffset
sign{I-1°DirY}*8→ScrollYOffset
ʟUpdatePlayer→UpdateState
End
End
If getKey(15)
ʟUpdateQuit→UpdateState
End
Pause 16
Return
Lbl UpdatePlayer
If ScrollXOffset<<0
ScrollLeft()
End
If ScrollXOffset>>0
ScrollRight()
End
If ScrollYOffset<<0
ScrollUp()
End
If ScrollYOffset>>0
ScrollDown()
End
If ScrollXOffset=0 ? ScrollYOffset=0
ʟUpdateGame→UpdateState
End
Return
Lbl UpdateQuit
1→Quit
Return
--- End code ---
This somehow causes the program to quit straight to the homescreen. I tried to figure out the cause of this by commenting out different lines and testing. If I comment out the lines that dealt with either horizontal movement or vertical movement and scrolling, the program executes just fine. However if all 4 lines of code are present it results in an immediate quit to homescreen. This should be impossible since the game screen hasn't even been drawn. It skips any execution of code and seems to instantly quit and I cannot for the life of me understand why. I'm really hoping that you might have some insight. I can provide the entire source so you can test it yourself if you would like.

E37:
@Ki1o
You are making this way harder than it needs to be. All you need is an if-else chain like this:

--- Code: ---If getKey(1)

ElseIf getKey(2)

ElseIf getKey(3)

ElseIf getKey(4)

End

--- End code ---

To answer you original question, I don't see anything wrong with your logic. The UpdateState isn't initialized when you code first starts but it isn't initialized in your original code either. Beyond that, I don't know. I don't think I have ever actually used sign{} in serious code so it is possible but very unlikely that there is a bug there. The full source would be useful in troubleshooting because I don't see any problems with what you have. I'm not sure what you are doing with UpdateState in general. Are you are trying to make some kind of state machine? I would avoid that if possible as state machines like that can be way harder to debug than Goto spaghetti. (Source: I do that for a living)
I did notice the line 'PlayerX+sign{I-1+°DirY}→PlayerY' That PlayerX is either a typo or a bug.


A quick code critique:
In both this snippit and the code on GitHub, I noticed that you tend to over complicate logic and rely heavily on indexing into arrays. Both are great ways to spend 80% of your time debugging. Here is how I would write movement code. I have a bit more code than your example but I am doing more checks.

--- Code: ---
1→°KDown+1→°KLeft+1→°KRight+1→°KUp .add some constants for the arrow keys. In my code I define every key like this. Now, I don't have to remember that 2 is left, I just type °KLeft
15→°KClear

Lbl UpdatePlayer

getKey(°KClear) or Quit→Quit .getKey returns either 1 or 0. I use 'or' so as not to set Quit to 0 if it was 1 when we got here. This should be moved to the main loop since it isn't player related code.

If ScrollXOffset
If >>0
ScrollRight()
ScrollXOffset--
.-4??PlayerX++ .If you want to make PlayerX change on the boundary of tiles instead of when movement starts, you can uncomment this and remove the PlayerX++ from the getKey block below
Else
ScrollLeft()
ScrollXOffset++
.+4??PlayerX--
End
Return .Don't let the player push keys during scrolling
ElseIf ScrollYOffset .Doesn't need to be an else-if (because of the Return in the line above) but it doesn't hurt anything and makes the intent clearer
If >>0
ScrollDown()
ScrollYOffset--
.-4??PlayerY++
Else
ScrollUp()
ScrollXOffset++
.+4??PlayerY--
End
Return
End
.If execution makes it here, both ScrollXOffset and ScrollYOffset are 0. The screen isn't moving so it is safe to pick a new direction

.getKey(num) commands are very quick. Don't be afraid to check the same key multiple times if it makes your code cleaner. It *is* possible that the key state changes between checks but unlikely. As long as you keep that in mind, it is totally fine to do.
If getKey(°KDown) ? getKey(°KUp)-1 .getKey(°KUp)-1 is the same as getKey(°KUp)=0. Checking both keys makes the player not move if up and down are both held. It is a preference thing and not needed if you don't like it
If Walkable(PlayerX, PlayerY+1) .Or whatever check you use to see if the player is allowed to walk into a given tile. I check this inside the if-else chain so that the walkable status of a tile doesn't impact what direction the player is moving.
PlayerY++
8→ScrollYOffset
Goto UpdatePlayer .This is optional. Without it, the player will spend one tick standing still for each tile they move, making movement seem slightly jerky on an actual calc
End
ElseIf getKey(°KUp) ? getKey(°KDown)-1
If Walkable(PlayerX, PlayerY-1) .You don't check for collision in your code. I assume that will be added later? If it doesn't apply, just remove the 'If Walkable' statements
PlayerY--
-8→ScrollYOffset
Goto UpdatePlayer
End
ElseIf getKey(°KRight) ? getKey(°KLeft)-1
If Walkable(PlayerX+1, PlayerY)
PlayerX++
8→ScrollXOffset
Goto UpdatePlayer
End
ElseIf getKey(°KLeft) ? getKey(°KRight)-1
If Walkable(PlayerX-1, PlayerY)
PlayerX--
-8→ScrollXOffset
Goto UpdatePlayer
End
End
.I only did a couple minor optimizations in this example since my goal was readable code. There is quite a bit that can be done with this code but that is left as an exercise to the reader.

Return

--- End code ---

Navigation

[0] Message Index

[#] Next page

[*] Previous page

Go to full version