### Author Topic: Game optimizations?  (Read 174 times)

0 Members and 1 Guest are viewing this topic.

#### Batprime11

• LV0 Newcomer (Next: 5)
• Posts: 4
• Rating: +0/-0
##### Game optimizations?
« on: July 31, 2022, 06:22:29 pm »
I have been working on a Chrome dino clone (chrome://dino) for a while now. Now I have optimized to the best of my capabilities  , and am wondering if there are any optimizations that I am missing. I will take anything, even if it is one byte smaller. I am optimizing for size mostly, but any speed optimizations would also be welcome.

Code: [Select]
.DINO#Icon(007E00FF00DF00FF00F000FE81F883F8C7FEFFFAFFF83FF00FE0066004400660)41→Q→ZPic1DINO1→N.ANIM 1Goto MENUprgmTILESPRTLbl TXXTText(20→B*256+3Text r₁Text(6659Text r₂Text(8195Text r₃ReturnLbl PAUSEFor(35565)EndReturn[0000000000004143→Pic1DINO1[7EFFDFFFFFF0FCE0[677F7F3F1F0C080C[E0F8E8C080C00000.ANIM 2[0000000000004143→Pic1DINO2[7EFFDFFFFFF0FCE0[677F7F3F1F080C00[E0F8E8C0C0C080C0.CACTUS[001858D8D8DADBDB→Pic1CACT1[DB7E3C1818181818Lbl OPTNClrDrawPAUSE()TXXT("Min Dist","Min Offset","Back"Text(40*256)Text "Ducking coming soon!IRect(3,20,32,6Text(20*256+33Text Z►DecText(26*256+35Text Q►DecDispGraphWhile 1If B=26 and getKey(2)Text(26*256+35Text max(Q-1,1)→Q►DecPAUSE()EndIf B=26 and getKey(3)Text(26*256+35Text min(Q+1,65534)→Q►DecPAUSE()EndIf B=20 and getKey(2)Text(20*256+33Text max(Z-1,1)→Z►DecPAUSE()EndIf B=20 and getKey(3)Text(20*256+33Text min(Z+1,65534)→Z►DecPAUSE()EndIf getKey(15) or (B=32 and getKey(54))Pause 400Goto MENUEndIf getKey(4)3Asm(E5IRect(,B,30,6Asm(E1IRect(,max(B-6,20)→B,30,6PAUSE()EndIf getKey(1)3Asm(E5IRect(,B,30,6Asm(E1IRect(,min(B+6,32)→B,30,6PAUSE()EndDispGraphEndLbl MENUClrDrawFix 5RectI(0,16,*2+1,26RectI(1→D,17,31,24TXXT("PLAY!","OPTIONS","QUIT")IRect(3,20,27,7While 1If getKey(4)3Asm(E5IRect(,B,27,7Asm(E1IRect(,max(B-6,20)→B,27,7D++PAUSE()EndIf getKey(1)3Asm(E5IRect(,B,27,7Asm(E1IRect(,min(B+6,32)→B,27,7D--PAUSE()EndDispGraphIf getKey(15)Fix 4ReturnEndEndIf getKey(54)!If B-32Fix 4ReturnEnd!If B-26Goto OPTNEndLbl MLOOPClrDraw196→I47*256→C→B→A15→F→D→J-2→E→H#ExprOnWhile 1RectI(0,63,95,1RectI(31,23,65,1RectI(31,0,+1,22!If H--Rect(90,0,+2,23rand^Q+Z→HEndIf I++ and {22*12+L₆+3}ʳ[e]7TS(,+1,88,47,Pic1CACT1⁻3→IEndLbl JUMPIf C-47 or (D<<0)D+E→D+B→BEndIf C>47-1→D12→E12032→BElse!If C-4712→EEndIf C≥47 and getKey(4)⁻350→DEndIf getKey(1)50→EEndDS<(F,20)If Pic1DINO1=NPic1DINO2→NElsePic1DINO1→NEndEnd2Asm(E5TS(,,-2,C,N)DispGraphAsm(E1TS(,,-2,C,N)RectI(0,63,95,1)RectI(31,23,65,1)WRect(31,0,+1,21)Horizontal-B/256→CEndIf pxl-Test(15,C+4) or pxl-Test(8,*2+C) or pxl-Test(4,C+16) or getKey(15)Goto MENU

#### E37

• LV6 Super Member (Next: 500)
• Posts: 329
• Rating: +21/-0
• Trial and error is the best teacher
##### Re: Game optimizations?
« Reply #1 on: August 04, 2022, 05:09:12 pm »
@Batprime11
Sorry for taking so long, whenever an Axe question pops up I somehow manage to forget to check Omnimaga for a couple of days. Speed optimizations are my specialty but I'll see what I can do for size.

.DINO
#Icon(007E00FF00DF00FF00F000FE81F883F8C7FEFFFAFFF83FF00FE0066004400660)
41→Q→Z
Pic1DINO1→N
.ANIM 1

...
Any kind of comments or explanation of what each bit of code does would really help
You seem to be trying to optimize the size of the source as much as the executable
As it is, I don't know what each letter variable means and don't want to play a guessing game
So I am just going to point out the inline optimizations rather than any logic ones
I put a some spacing in places I thought were logical breaks just to help myself read it. You can ignore them
...

prgmTILESPRT

Lbl TXXT
Text(20→B*256+3
...
*256 translates to ld h, l \ ld l, 0 which is 3 bytes
+3 translates to inc hl (x3) which is also three bytes
So it is more space efficient to just do
20→B
Text(20*256+3)
Basically, +3 on its own takes up just as much space as a constant would so there is never any reason to use it to produce a constant result
same thing with *256. Remember that numeric constants are just 3 bytes
I believe only times you can beat numeric constants for size is push/pop hl, +-1, +-2, and 0, or 255, *2, *4. I might be missing one or two but I think that is a complete list. Division is, of course, completely out of the question as / 2 takes up 4 bytes. There are a lot more operations that tie with a numeric constant but at that point it is better to use the constant. I recommend pulling up a list of assembly instructions for doing optimizations like this. Thinking out what logic translates to in assembly makes it a lot easier to see what could or could not save you space.
...
Text r₁
Text(6659
Text r₂
Text(8195
Text r₃
Return

Lbl PAUSE
For(35565) .do you mean 65535? Not that it matters but 35565 is a strange number. Also, -1 (negative 1)  works and is easier to type out
End
Return

.Axe doesn't require constants/variables to be declared before they are used like in C or most other languages. You can move this data to the bottom of the program if you want. I find that doing so makes it nicer to scroll through
[0000000000004143→Pic1DINO1
[7EFFDFFFFFF0FCE0
[677F7F3F1F0C080C
[E0F8E8C080C00000
.ANIM 2
[0000000000004143→Pic1DINO2
[7EFFDFFFFFF0FCE0
[677F7F3F1F080C00
[E0F8E8C0C0C080C0
.CACTUS
[DB7E3C1818181818

Lbl OPTN .You can use lowercase letters for variable and function names as long as they don't start with one
ClrDraw
PAUSE()
TXXT("Min Dist","Min Offset","Back"
Text(40*256)
Text "Ducking coming soon!
IRect(3,20,32,6
Text(20*256+33
Text Z►Dec
Text(26*256+35
Text Q►Dec
DispGraph

While 1

If B=26 and getKey(2) .The '?' and '??' operators take up the same amount of space as 'and' and 'or' but they also short circuit and don't have bitwise problems. I would use '?' and '??' as drop in replacements for any time you use 'and' and 'or' respectively. Just make sure the short-circuiting doesn't mess any logic up
Text(26*256+35

Text max(Q-1,1)→Q►Dec
PAUSE()
End

If B=26 and getKey(3) .Fun fact: Axe only reads the lower byte of the value passed in here so getKey(X*256+3) would always work the same as getKey(3)
Text(26*256+35
Text min(Q+1,65534)→Q►Dec .The optimization that I did for the same operation on Z applies here too
PAUSE()
End

...
All of your 'If B=# and getKey(#)' statements can be optimized to
!If B-#??getKey(#)-1
This is because getKey always returns 1 or 0. Doing -1 on its result effectively inverts it
...
If B=20 and getKey(2)
Text(20*256+33

...
Text max(Z-1,1)→Z►Dec can be optimized to
Z--??+1→Z
Text ►Dec
For 2 bytes off
...
Text max(Z-1,1)→Z►Dec

PAUSE()
End

If B=20 and getKey(3)
Text(20*256+33
...
Like previously, Text min(Z+1,65534)→Z►Dec can be optimized to
Z++??-1→Z
Text ►Dec
For 2 bytes off. It does limit the max value to 65535 instead of 65534 but I think that is probably fine in your use case
...
Text min(Z+1,65534)→Z►Dec
PAUSE()
End

If getKey(15) or (B=32 and getKey(54)) .Luckily for you, Axe ignores the parenthesis
Pause 400 .Pause <constant> is 10 bytes while your PAUSE() function is only 3 bytes to call. Since this wait is isn't very sensitive, just use PAUSE() here if the timing is close enough.
End

If getKey(4)
3
Asm(E5 .By pushing/popping here you only gain one byte. Is it really worth the readability hit? Then again, your code is already so unreadable I guess it doesn't matter
IRect(,B,30,6
Asm(E1
IRect(,max(B-6,20)→B,30,6
PAUSE()
End

If getKey(1)
3
Asm(E5
IRect(,B,30,6
Asm(E1
IRect(,min(B+6,32)→B,30,6
PAUSE()
End

DispGraph
End

ClrDraw
Fix 5
RectI(0,16,*2+1,26
RectI(1→D,17,31,24
TXXT("PLAY!","OPTIONS","QUIT")
IRect(3,20,27,7

While 1
If getKey(4)
3
Asm(E5
IRect(,B,27,7
Asm(E1
IRect(,max(B-6,20)→B,27,7
D++
PAUSE()
End

If getKey(1)
3
Asm(E5
IRect(,B,27,7
Asm(E1
IRect(,min(B+6,32)→B,27,7
D--
PAUSE()
End

DispGraph

If getKey(15)
Fix 4 .Fix 4 takes up 4 bytes + the return makes it 5. doing a goto to the Fix 4 and return below saves 2 bytes
Return
End

EndIf getKey(54)
!If B-32
Fix 4
Return
End

!If B-26
Goto OPTN
End

Lbl MLOOP
ClrDraw
196→I
47*256→C→B→A
15→F→D→J-2→E→H .What do all these variables mean?
#ExprOn
...
I doubt you are getting much speed boost from #ExprOn. Axe's built in text command is so slow basically invalidates any speed optimizations by itself. (it is just a call to Ti-OS's Text function which is horribly inefficient - so great for size but not for speed) I believe #ExprOn lets Axe pick an unrolled version of Horizontal- below but besides possible other small optimizations it likely doesn't do anything. But again, Axe's built in Text command is horribly slow.
...

While 1
RectI(0,63,95,1
RectI(31,23,65,1
RectI(31,0,+1,22
!If H--
Rect(90,0,+2,23
rand^Q+Z→H
End

...
I could be wrong but the following If statement looks like you are bit testing from the screen. If you are, {22*12+L₆+3}ʳ and 128 (or whatever bit mask you need. add 1 to the address if you need the high byte) is much more space and speed efficient. Also 1 byte reads for non-constant addresses are better than 2 byte ones. (So in most cases leave off the ʳ if you don't need the high 8 bits) In your case, you are reading a constant address so that ʳ is fine.
That won't return the same value as [e] but it will more than make up for you having to hardcode whatever value TS( is expecting
...
If I++ and {22*12+L₆+3}ʳ[e]7
TS(,+1,88,47,Pic1CACT1 .What does TS do? Put a sprite on the screen?
⁻3→I
End

Lbl JUMP
...
Signed operations take up more space than unsigned ones. I believe that ({oD+1} and 128) does the same thing as (D << 0) where that o is the degrees sign. 'xor' works if you want to invert. It's 2's complement so you can just check the most significant bit for the sign
You may want to shift all your operations on D up by some constant value like 65536 / 2. That way you can check below zero without using the signed operators
...
If C-47 or (D<<0)
D+E→D+B→B
End

...
Depending on the expected value of C you can do this
!If C-47
12→E
Else!If /256 .Check to see if the high byte of the last calculation is non-zero if so, it overflowed and C is less than 47
-1→D .subtraction not negative
12→E
12032→B
End
...
If C>47
-1→D
12→E
12032→B
Else!If C-47
12→E
End

...
You can move the 12→E into this if block like this
If C≥47?Select(getKey(4), 12→E)
-350→D
End

If you add the constant 0 as the last entry in both branches of the previous optimized if statement, it will always have hl as non-zero if both branches fail and 0 if they pass so you can remove the C≥47 from the upcoming if block completely. Each 0 constant takes up 3 bytes but C≥47 takes up 11. Additionally you can use the 'and 0' trick described below on the 12→E block for a total of 6 bytes saved (or do a push/pop hl pair in each to save the zero from the conditional if you really hate whoever is reading your code for 7 bytes saved)
...
If C≥47 and getKey(4)
⁻350→D
End

.I would recommend changing this to ':getKey(1)?50→E' just to condense your code a little so more could fit onscreen.
If getKey(1)
50→E
End

...
IIRC, there is no reason to ever use DS< it just expands to what code you would normally replace it with. When I tested it a while ago, I couldn't find any cases where it was faster or more space efficient than just doing '!If F--:20→F:<code>:End'
...
DS<(F,20) .Thanks for closing the parenthesis on most commands after here. Every little bit helps

...
Instead of that if-else block do:
(N-Pic1DINO1?Pic1DINO1,Pic1DINO2)→N (10 byte savings)
...
If Pic1DINO1=N .You kinda dropped the ball on this one. Why not '!If N-Pic1DINO' here? You used that trick several other places.
Pic1DINO2→N
Else
Pic1DINO1→N
End

End

.You may be able to assume something about the value of hl at the end of the DS< block.
2
Asm(E5
TS(,,-2,C,N)
DispGraph
Asm(E1
TS(,,-2,C,N)
RectI(0,63,95,1)
RectI(31,23,65,1)
WRect(31,0,+1,21) .Axe will replace an 'and 0' with ld l, 0 which is only 2 bytes so you can replace this statement with WRect(31,and 0,+1,21). 'or 255' works the same way. Remember that that just affects the lower 8 bits so you have to be sure the high 8 are in a state you want them to be in
Horizontal-
B/256→C

EndIf pxl-Test(15,C+4) or pxl-Test(8,*2+C) or pxl-Test(4,C+16) or getKey(15)
...
If you are feeling especially energetic, you could replace those pxl-Tests with constant bit tests on L6. Since your X value is a constant you wouldn't have to worry about shifting your bit mask. All you would have to do is add C*12 to the offset. I'm not sure if you would come out ahead but it could be worth a shot. You would certainly lose for individual tests but if you could eliminate all 3 you wouldn't have the weight of including the function.
...

There are quite a few places where using 'and 0' instead of the constant '0' would save a byte. I didn't mention them to avoid comment clutter. They are easy to spot once you know what to look for.

That is probably between 30 and 80 bytes off the total at the cost of whatever readability your code had left. The reality is that the majority of your size is probably just calls to the OS or Axe so there really isn't too much to do to begin with. I would be very careful when optimizing anything larger than this program that aggressively for size because of the risk of logic bugs creeping in during the optimization. I haven't tested these optimizations because you didn't give me the full source so you should double check they work before using them.
I would also suggest getting really comfortable with '?' and '??' because when used correctly they can be your best friends and are better than the bitwise 'and' and 'or' in pretty much every case. Taking advantage of the fact they short circuit to condense a really fat series of if blocks can be nice. You can also do single line conditionals like ':A=3?5→A' which would set A to 5 if it held 3 before which is a lot more readable on the limited space of a calculator screen than the 3 lines usually given for an if block.

Overall, you did some pretty solid optimizations. I would take a serious look at your flow of logic as you are likely to get more gains from reordering and reworking than one-off optimizations unless you already have one of the best possible logic paths. If you implement whatever of my suggestions you want add some comments I'd be thrilled to do another pass.

Here are a couple of other tricks that I didn't get to use but you may find useful:
Built in Axe functions and Axioms do a 'push hl' for commas meaning you can't safely push/pop across them but user functions that use r1-r6 don't so it is safe to push/pop there
If you do a pop <any register> you can then safely goto inside the single argument For( loop. Axe won't let you but you can code it in assembly like Asm(C3(LLabel)) where that is the little L for label address
Assign custom constants. They might slightly slow down compile time but they make the code so much easier to read. You can even rename letter variables like ':oB→oYPos' so after that you can use YPos instead of B. (where the o is the degrees symbol)
While I don't really know what is going on with your logic, I suspect that you could cut down on a lot of calls to [WI]Rect( by just doing DispGraphClrDraw and reworking graphics accordingly. Rectangles that span the width of the screen (like I assume you have for the ground) could be turned into a Fill( command which would save on speed and maybe space too. The value argument of Fill( works great with the 'and 0' and 'or 255' trick I mentioned since it only uses the lower byte.
When given the option always do <Variable>-<Constant> (like X-5) and not <Constant>-<Variable> (like 5-X) the second case is 2 bytes larger.
You can use the colon (':') to separate lines on the same line anytime you want to pack a couple statements on the same line. ':Asm(E5)' is a good example but it work for all parts of an If statement. In fact, in the last example, the colon is optional and ':3Asm(E5)' works!
I'm still around... kind of.

#### Batprime11

• LV0 Newcomer (Next: 5)
• Posts: 4
• Rating: +0/-0
##### Re: Game optimizations?
« Reply #2 on: August 06, 2022, 08:46:53 pm »
Sorry, I can't pull up my current file right now, because TokenIDE isn't working, but I commented the file up there.
Code: [Select]
.DINO...B=(In menus)Cursor location (In game)Dino location (/256)Q=Random space between cacti (Offset from Z)Z=Minimum space between cacti...#Icon(007E00FF00DF00FF00F000FE81F883F8C7FEFFFAFFF83FF00FE0066004400660)40→Q→ZPic1DINO1→NGoto MENU...8x8 sprite stacking routine I madeTakes 5 arguments: Width, height, X, Y, Pointer to start of sprite data....prgmTILESPRTLbl TXXTText(20→B*256+3)Text r₁Text(6659)Text r₂Text(8195)Text r₃ReturnLbl PAUSEFor(35565)EndReturn.First animation of the running dino[0000000000004143]→Pic1DINO1[7EFFDFFFFFF0FCE0][677F7F3F1F0C080C][E0F8E8C080C00000].Second animation of the running dino[0000000000004143]→Pic1DINO2[7EFFDFFFFFF0FCE0][677F7F3F1F080C00][E0F8E8C0C0C080C0].Cactus sprites[001858D8D8DADBDB]→Pic1CACT1[DB7E3C1818181818]Lbl OPTNClrDrawPAUSE()TXXT("Min Dist","Min Offset","Back")Text(40*256)Text "Ducking coming soon!IRect(3,20,32)Text(20*256+33)Text Z►DecText(26*256+35)Text Q►DecDispGraphWhile 1.If the cursor is at the right place and the left arrow key is pressedIf B=26 and getKey(2)Text(26*256+35.If the random distance is greater than 1, if so, decrease the random distanceText max(Q-1,1)→Q►DecPAUSE()EndIf B=26 and getKey(3)Text(26*256+35Text min(Q+1,65534)→Q►DecPAUSE()End.If the minimum distance is greater than 1, if so, decrease the minimum distanceIf B=20 and getKey(2)Text(20*256+33)Text max(Z-1,1)→Z►DecPAUSE()EndIf B=20 and getKey(3)Text(20*256+33)Text min(Z+1,65534)→Z►DecPAUSE()End.Go to the menu if [clear] or back is pressedIf getKey(15) or (B=32 and getKey(54))Pause 400Goto MENUEndIf getKey(4)3Asm(E5)IRect(,B,30,6)Asm(E1)IRect(,max(B-6,20)→B,30,6)PAUSE()EndIf getKey(1)3Asm(E5)IRect(,B,30,6)Asm(E1)IRect(,min(B+6,32)→B,30,6)PAUSE()EndDispGraphEndLbl MENUClrDrawFix 5RectI(0,16,*2+1,26)RectI(1→D,17,31,24)TXXT("PLAY!","OPTIONS","QUIT")IRect(3,20,27,7)While 1If getKey(4)3Asm(E5)IRect(,B,27,7)Asm(E1)IRect(,max(B-6,20)→B,27,7)D++PAUSE()EndIf getKey(1)3Asm(E5)IRect(,B,27,7)Asm(E1)IRect(,min(B+6,32)→B,27,7)D--PAUSE()EndDispGraph.Quit if [clear] is pressedIf getKey(15)Goto QQEndEndIf getKey(54).Checks cursor location for where to go!If B-32Lbl QQFix 4ReturnEnd!If B-26Goto OPTNEndLbl MLOOPClrDraw.I is a counter so I don't get bold cacti1→I.Dino location47*256→B.btw C=B/256...D=VelocityE=Gravity strengthF=Dino animation counterH=Cactus distance (rand^randomDistance+minimumDistance→H...15→F→D-2→E→H.The screen scrolling seems to be smoother with this#ExprOnWhile 1RectI(0,63,95,1)RectI(31,23,65,1)RectI(31,0,+1,22)!If H--.Create cactus on the cut top screenRect(90,0,+2,23).New cactus distancerand^Q+Z→HEnd.Tests if the top screen's cactus has hit a certain part, if so, make a cactus. (btw pxl-Test(31,22))If I++ and {22*12+L₆+3}ʳ[e]7.Makes a cactus 1 tile wide and 2 tiles highTS(,+1,88,47,Pic1CACT1).Makes it so that the cactus won't be bold⁻3→IEndLbl JUMP.If the dino isn't on the ground or the dino is jumping, apply accelerationIf C-47 or (D<<0)D+E→D+B→BEnd.If the dino is under ground, take away all velocity, put the gravity back to normal, and put the dino on the groundIf C>47-1→D12→E12032→B.If the dino is on the ground, put the gravity back to normalElse!If C-4712→EEnd.If the dino is on the ground, and up is pressed, add an upwards force to the dino's positionIf C≥47 and getKey(4)⁻350→DEnd.If down is pressed, increase the gravityIf getKey(1)50→EEnd.Animation changerDS<(F,20).I realized this later and fixed it, I just forgot to update the codeIf Pic1DINO1-NPic1DINO1→NElsePic1DINO2→NEndEnd.Draw dino2Asm(E5)TS(,,-2,C,N)DispGraphAsm(E1).Undraw everythingTS(,,-2,C,N)RectI(0,63,95,1)RectI(31,23,65,1)WRect(31,0,+1,21)Horizontal-B/256→CEndIf pxl-Test(15,C+4) or pxl-Test(8,*2+C) or pxl-Test(4,C+16) or getKey(15)Goto MENU
« Last Edit: August 07, 2022, 01:29:42 pm by Batprime11 »