The purpose of a game is to be fun, and so whenever there was an opportunity to choose between “fun” and “realism”, then I sided with “fun”. If done well, then this can be invisible, and make players feel great. I have a recollection of reading about the Daytona arcade game that the physics engine was tweaked to make it so when an AI players car crashed and flew into the air, the laws of physics made it very common for it to fly over the player’s car because it made players feel like that did some great driving. This is a quick look at how the physics in a simple 2D game were fudged to make the game more fun.
Two Birds One Stone is not exactly a simulation game, but it is also conceptually very simple:
You have a slingshot which can launch a single stone, there are two (or more) birds flying backwards and forwards at an unchanging speed. There is no wind, and you can always predict exactly what is going to happen in the future once you know where the birds are deciding to turn around. Once you have launched the stone, there is no further input from you, and the physics engine shows how the stone and the birds interact, if all the birds get hit by something, then you win. You can characterize a game completely by the time before you launch the stone and the velocity you launch the stone at.
You can actually play a very old version of the game as described above online. This version is missing all of the polish and while I found this version fun, playing it now after playing the real thing feels very clunky. It isn’t a bad game, but it can be made significantly better.
Solving The Game
One of the nice side effects of having a fixed point physics engine is that you can rely on it to always give the same results every time given the same inputs. While this can often be the case with floating point, in my time I have seen:
- Floating point code which gives slightly different results depending on the alignment of the input (e.g. if the code is being SIMD-ified, then it might calculate
(a1+a2+a3)+(a4+a5+a6+a6)+(a8)
for one alignment but(a1+a2+a3+a4)+(a5+a6+a7+a8)
for another. - Floating point code which gives different results in debug and release versions – incredibly common, might be able to be mitigated with the right compiler flags, but that isn’t something I want to do.
- Floating point code which is annoying to debug – when things behave differently, noting where they diverge is always annoying because the differences happen in the least significant bits which you have probably taught yourself to ignore.
But with fixed point code, as long as we aren’t overflowing, then our multiplies always have exact specified results which you can even easily calculate with a pen and paper if you want. The precision loss from rounding is easy to understand (when you do a multiply, you scale things down, and that is where you lose precision, and when you do adds/subtracts then there is no precision loss unlike floating point).
As a result, it is easy to write code which:
- Takes the game state and duplicates it.
- Simulates what would happen if the stone was released in N frames with a velocity of (dy, dx).
So as soon as the player releases the stone, we can immediately know if they are going to win or not. Being a simple game, this calculation is cheap, but not free. There would be ways of speeding it up (I’m sure it would be possible to find a closed form solution for if/when the stone would hit a bird while if the bird continued in the same direction, and since the bird’s movement is predictable, it would just be a slight complication to run this as many times as needed for the birds changing direction until we get to a place where either both birds are hit, or we can prove that one bird can never be hit.
Launch Time Fudge
So, if the player launches the stone, we quickly work out if this will result in a win or not. If it is a win, then we don’t do anything. If on the other hand the player is going to lose, we quickly run a few more scenarios – could we win if we applied a tiny force to the stone right at launch time? The game searches as many random “tiny forces” as it can before running out of time, and if any of them result in winning, then we apply that force. I have never been able to notice the force being applied, and had to have a debug printout telling me when I was being helped.
Even though it is a very subtle change, this changes the gameplay immensely. Without it, the game feels hard and punishing. With it, the game feels fun, and it feels like things are happening as you want them to. It is difficult to describe, but I saw the difference when I had a bug in this code and it wasn’t working properly – even though I didn’t know there was a bug, it didn’t feel as “fun” as it had felt in the past, and as soon as the bug was fixed, it felt great to play again.
It is easy to think of this as cheating, but when your only input to the game is a touch screen where you poke your big fat finger, expecting people to have the precision to select exactly how many pixels they want to pull the sling back, and exactly which frame to release seems unfair. If a person has an idea as to how they could hit all the birds, and they aim the stone close to the direction of that, then it seems right that the game should work for the player. Just like auto-correct for typing doesn’t mean that you don’t know how to spell – the input device isn’t precise, so you work to find the intention of the user and respect that.
Secondary Fudge
The problem with calculating the fudge factor at launch time is that you have exactly one frame to do all the calculation. If you are too slow, then the game will pause when you release the stone, which is not good at all. Launch time is a very good time to apply a fudge factor though, because it is very difficult to notice it – if you shot a stone straight up, and then on the way down it came down at an angle and miraculously hit both birds, then it would be pretty obvious that something was cooking the books. But if the book cooking happens right when the stone is transitioning from stationary to moving, then it is hard to spot.
At one stage, I didn’t think I would have the processor time budget to be able to do the launch time fudge, so I came up with an alternative. This alternative worked mostly the same way, except:
- It applied the mystery force when the first bird was hit by the stone.
- It tried one different mystery force for each frame of the game.
This smooths out the calculation, and also only applies the fudge factor at a time when things are supposed to be changing. The drawbacks are:
- If you don’t hit any birds, then it never does anything.
- If you hit a bird quickly, then there isn’t much time to try different fudge factors.
- It is nowhere near as good as the launch time fudge in terms of making the game fun.
The game just had this secondary fudge for a while in development, and it is only sometimes able to help.
Since the code for both types of fudge was written, it made sense to just have both, once the performance of the launch time fudge could be ensured. So the secondary fudge will only do something if the launch time fudge fails to find a way of winning, but without launch time fudge you still hit one bird. I don’t have statistics for when the secondary fudge actually does something, but I imagine that it is not very often. With my playing, launch time fudge can be operating half the time or more.
Literally Breaking News
While doing screenshots and videos for marketing, I managed to crash the game. The crash was related to the secondary fudge and proved to be incredibly difficult to reproduce (because I was doing video recording, all I got was a line number, which was a function only used by the secondary fudge, and reachable from 4 different locations). I first “proved” that none of those 4 cases could cause a crash. Then I thought harder about my assumptions and came up with 3 theories as to how the crash could be happening. I tested each of these theories as soon as I thought of them, and each one was wrong. I stared some more and then gave up and removed secondary fudge. Not a huge loss, but a little bit frustrating.
Finishing Up
Try playing the old crusty web version without any fudge, and if you can try the actual Two Birds One Stone game, then have a go and see how different it feels (and all the physics numbers are tweaked too, so it will feel different for other reasons). I have watched my 3 year old twins (who rarely use anything with a touch screen and are still able to be entertained by pressing the “Caps Lock” button on a keyboard and watching the light go on and off) have both been able to have success in the game with these tweaks.