Author Topic: Game optimizations?  (Read 376 times)

0 Members and 1 Guest are viewing this topic.

Offline Batprime11

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 6
  • Rating: +0/-0
    • View Profile
    • TICalc
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  :banghead:, 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→Z
Pic1DINO1→N
.ANIM 1
Goto MENU
prgmTILESPRT
Lbl TXXT
Text(20→B*256+3
Text r₁
Text(6659
Text r₂
Text(8195
Text r₃
Return
Lbl PAUSE
For(35565)
End
Return
[0000000000004143→Pic1DINO1
[7EFFDFFFFFF0FCE0
[677F7F3F1F0C080C
[E0F8E8C080C00000
.ANIM 2
[0000000000004143→Pic1DINO2
[7EFFDFFFFFF0FCE0
[677F7F3F1F080C00
[E0F8E8C0C0C080C0
.CACTUS
[001858D8D8DADBDB→Pic1CACT1
[DB7E3C1818181818
Lbl OPTN
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)
Text(26*256+35
Text max(Q-1,1)→Q►Dec
PAUSE()
End
If B=26 and getKey(3)
Text(26*256+35
Text min(Q+1,65534)→Q►Dec
PAUSE()
End
If B=20 and getKey(2)
Text(20*256+33
Text max(Z-1,1)→Z►Dec

PAUSE()
End
If B=20 and getKey(3)
Text(20*256+33
Text min(Z+1,65534)→Z►Dec
PAUSE()
End
If getKey(15) or (B=32 and getKey(54))
Pause 400
Goto MENU
End
If getKey(4)
3
Asm(E5
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
Lbl MENU
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
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
#ExprOn
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
If I++ and {22*12+L₆+3}ʳ[e]7
TS(,+1,88,47,Pic1CACT1
⁻3→I
End
Lbl JUMP
If C-47 or (D<<0)
D+E→D+B→B
End
If C>47
-1→D
12→E
12032→B
Else!If C-47
12→E
End
If C≥47 and getKey(4)
⁻350→D
End
If getKey(1)
50→E
End
DS<(F,20)
If Pic1DINO1=N
Pic1DINO2→N
Else
Pic1DINO1→N
End
End
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)
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)
Goto MENU
Sig Last Updated: Aug 28 2022

TI-83/84+ Programmer

My current projects:
Minecraft 2D
The BasAxe
 
Finished:
Chrome Dino
Block Run
Gravity dude

My ticalc profile:
https://www.ticalc.org/archives/files/authors/117/11744.html

My Discord account:
batprime11#3495

My (Empty) Github:
https://github.com/Batprime11

My omnimaga account:
bruh...

Offline E37

  • LV6 Super Member (Next: 500)
  • ******
  • Posts: 329
  • Rating: +21/-0
  • Trial and error is the best teacher
    • View Profile
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
Goto MENU

...
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
[001858D8D8DADBDB→Pic1CACT1
[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.
Goto MENU
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

Lbl MENU
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.
...
Goto MENU

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. ':3: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.

Offline Batprime11

  • LV1 Newcomer (Next: 20)
  • *
  • Posts: 6
  • Rating: +0/-0
    • View Profile
    • TICalc
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→Z
Pic1DINO1→N
Goto MENU
...
8x8 sprite stacking routine I made
Takes 5 arguments: Width, height, X, Y, Pointer to start of sprite data.
...
prgmTILESPRT

Lbl TXXT
Text(20→B*256+3)
Text r₁
Text(6659)
Text r₂
Text(8195)
Text r₃
Return

Lbl PAUSE
For(35565)
End
Return

.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 OPTN
ClrDraw
PAUSE()
TXXT("Min Dist","Min Offset","Back")
Text(40*256)
Text "Ducking coming soon!

IRect(3,20,32)
Text(20*256+33)
Text Z►Dec
Text(26*256+35)
Text Q►Dec
DispGraph

While 1

.If the cursor is at the right place and the left arrow key is pressed
If B=26 and getKey(2)
Text(26*256+35
.If the random distance is greater than 1, if so, decrease the random distance
Text max(Q-1,1)→Q►Dec
PAUSE()
End
If B=26 and getKey(3)
Text(26*256+35
Text min(Q+1,65534)→Q►Dec
PAUSE()
End

.If the minimum distance is greater than 1, if so, decrease the minimum distance
If B=20 and getKey(2)
Text(20*256+33)
Text max(Z-1,1)→Z►Dec

PAUSE()
End
If B=20 and getKey(3)
Text(20*256+33)
Text min(Z+1,65534)→Z►Dec
PAUSE()
End

.Go to the menu if [clear] or back is pressed
If getKey(15) or (B=32 and getKey(54))
Pause 400
Goto MENU
End
If getKey(4)
3Asm(E5)
IRect(,B,30,6)
Asm(E1)
IRect(,max(B-6,20)→B,30,6)
PAUSE()
End
If getKey(1)
3Asm(E5)
IRect(,B,30,6)
Asm(E1)
IRect(,min(B+6,32)→B,30,6)
PAUSE()
End
DispGraph
End

Lbl MENU
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)
3Asm(E5)
IRect(,B,27,7)
Asm(E1)
IRect(,max(B-6,20)→B,27,7)
D++
PAUSE()
End
If getKey(1)
3Asm(E5)
IRect(,B,27,7)
Asm(E1)
IRect(,min(B+6,32)→B,27,7)
D--
PAUSE()
End
DispGraph
.Quit if [clear] is pressed
If getKey(15)
Goto QQ
End
EndIf getKey(54)

.Checks cursor location for where to go
!If B-32
Lbl QQ
Fix 4
Return
End
!If B-26
Goto OPTN
End

Lbl MLOOP
ClrDraw
.I is a counter so I don't get bold cacti
1→I
.Dino location
47*256→B
.btw C=B/256
...
D=Velocity
E=Gravity strength
F=Dino animation counter
H=Cactus distance (rand^randomDistance+minimumDistance→H
...
15→F→D-2→E→H
.The screen scrolling seems to be smoother with this
#ExprOn
While 1
RectI(0,63,95,1)
RectI(31,23,65,1)
RectI(31,0,+1,22)
!If H--
.Create cactus on the cut top screen
Rect(90,0,+2,23)
.New cactus distance
rand^Q+Z→H
End
.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 high
TS(,+1,88,47,Pic1CACT1)
.Makes it so that the cactus won't be bold
⁻3→I
End
Lbl JUMP
.If the dino isn't on the ground or the dino is jumping, apply acceleration
If C-47 or (D<<0)
D+E→D+B→B
End
.If the dino is under ground, take away all velocity, put the gravity back to normal, and put the dino on the ground
If C>47
-1→D
12→E
12032→B
.If the dino is on the ground, put the gravity back to normal
Else!If C-47
12→E
End
.If the dino is on the ground, and up is pressed, add an upwards force to the dino's position
If C≥47 and getKey(4)
⁻350→D
End
.If down is pressed, increase the gravity
If getKey(1)
50→E
End
.Animation changer
DS<(F,20)
.I realized this later and fixed it, I just forgot to update the code
If Pic1DINO1-N
Pic1DINO1→N
Else
Pic1DINO2→N
End
End
.Draw dino
2Asm(E5)
TS(,,-2,C,N)
DispGraph
Asm(E1)
.Undraw everything
TS(,,-2,C,N)
RectI(0,63,95,1)
RectI(31,23,65,1)
WRect(31,0,+1,21)
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)
Goto MENU
« Last Edit: August 07, 2022, 01:29:42 pm by Batprime11 »
Sig Last Updated: Aug 28 2022

TI-83/84+ Programmer

My current projects:
Minecraft 2D
The BasAxe
 
Finished:
Chrome Dino
Block Run
Gravity dude

My ticalc profile:
https://www.ticalc.org/archives/files/authors/117/11744.html

My Discord account:
batprime11#3495

My (Empty) Github:
https://github.com/Batprime11

My omnimaga account:
bruh...