Alright, time to look at some code.
Argh, my eyes! Well, a few things quickly become painfully apparent:
I wasn’t really into “compilation units”. Everything is dumped into one big source file. (*)
I did not believe in
privatemembers. Everything is
public. In fact, there’s not a single mention of the
privatekeyword anywhere in the CI1 code. (**)
I didn’t know or care about constructor member initializer lists ( Constructors and member initializer lists - cppreference.com ). Everything is manually initialized with assignments. (***)
No spaces after commas or assignments. (****)
There’s a pointer to UVE being passed around literally everywhere. I guess back then I didn’t believe in global variables either. (*****)
* Well, it’s “big” by 1999 standards. CIU has a couple of files around the 10K line mark – but there’s a good reason for that, I promise!
** To be fair, I do see a few
private keywords in UVE’s source code. So I was definitely aware of it. Regardless, there’s a good case to be made for making everything public by default, especially for small projects, because it minimizes the amount of boilerplate/scaffolding code (namely, “getter” and “setter” functions). Certain very prominent programmers still advocate the “everything public by default” approach.
*** This reminds me of the time I was around 10 years old, and I was desperately trying to make a “sliding puzzle” game without knowing what arrays were (spoiler alert: I didn’t get very far). Or the time I took part in a programming competition without knowing what recursion was (spoiler alert: I didn’t get very far either). Or the time I didn’t know how heaps work (spoiler alert: I still don’t).
**** I still don’t bother with that – it just slows things down. But nowadays at least I’m using automatic code formatting, which takes care of that for me.
***** Although there’s some merit to that idea, the programming burden to pay is sometimes too high.
I spend the next few hours furiously hacking away at the code, trying to get it to a compilable state. I’ve already explained more about this stage:
Finally, success! It compiles!
I start up a new game, and I am immediately greeted by the following:
It turns out that this is an RNG issue. Due to patently bad programming, CI1 sometimes requests random numbers with a negative range, and the CIU randomizer doesn’t handle that case correctly. If you ask for a random number between 0 and 1, you get correct results (always 0, by the way), but if you ask for a random number between 0 and -1, then you get 2 gazillion back. CIU is a much better behaved game and never asks for negative ranges, so this issue remained dormant for years. But it’s been exposed now!
I spend some time very carefully fixing the bug in the CIU randomizer to make sure it still generates the same numbers (otherwise all the randomly-generated CIU missions would change!).
Completely by accident, as part of the debugging process, I notice there is something strange with the smoke/fire trails of asteroids: The first puff of smoke is always spawned at (0,0), which for CI1 is the top-left of the screen, regardless of where the asteroid is.
Incredulously, I load up the original game to check. And sure enough, there it is:
* I couldn’t capture the screenshot at the exact right time, so this is only a re-creation.
Show of hands – did anyone ever notice this? I certainly didn’t.
I check the code and it’s a case of “incomplete initialization”, very similar to what has been plaguing CIU (and previous episodes) for many years whenever you join a multi-player game while it’s paused.
The fix is very simple, but I mull over whether this should be actually fixed. I’m hesitant to change code behaviour, both as a matter of principle, and both out of fear I’ll miss some delicate dependency and inadvertently break something in the process.
I finally decide that yes, it will indeed will be fixed. So there you have it, (at least) one way in which CI1 is better than CIU! You’re welcome.
I also notice that some bullets are not facing the correct way:
This turns out to be an incompatibility in CI1’s
wrap(x,a,b) function, which wraps number
b inclusive, and CIU’s corresponding function, where
b is actually excluded from the range. I stupidly thought I’d just delete the old function and let the new one take over, because – hey, newer function, right? It’s got to be better!
Well, now I know better. Mental note: Do not touch the code unless absolutely necessary!
Having addressed the immediately apparent problems, I now decide the next thing I should tackle is adding more input methods to the game. I spend some time wiring up controls for keyboard(*), joypad and mouse. Thankfully I’m using a gutted CIU codebase, which has comprehensive controller support, so the process is relatively painless…
… with one exception: I spend an embarrassingly long time trying to get the spacecraft to stop exactly where the mouse points. In the original game, your spacecraft does not stop instantly, but rather decelerates and gradually comes to a stop, and I wanted to preserve this behaviour even when using the mouse.
“But the stopping distance is simply the sum of the speed/acceration arithmetic series! There are formulas for that! Look it up! You even used that same formula to make spaceships decelerate properly at the end of a trip in CIU! It’s easy!”
Nope. Still had to calculate it iteratively
* As a side-effect, I discover CIU has been accidentally including keyboard controls for two players all this time .
High score entry was one of the first things to be commented out while trying to get the code to compile. I originally had no intention of putting it back in because it seemed to be slow, inelegant, and altogether a relic from another era. Not to mention it would be a hassle to get working, because it’s specialized code. It would be much easier for the name to be automatically filled-in from the player’s profile (as is done in CI2-CI5).
What eventually changed my mind is that there are two players that might need to enter different names. And also, of course, preserving the original as accurately as possible.
interesting annoying thing about this screen is that the boxes containing the letters are not actually bitmaps, but they are drawn “on the spot” using GDI (ugh). There’s no good way to modernize this, so I’ll convert them to actual bitmaps.(*) For now I’ve just used some placeholder graphics – I’ll revisit this when it’s time to work on the GUI.
* This is much cleaner design, anyway, and much more consistent. I don’t know what I was thinking when I used GDI… I’ll chalk this one up to inexperience.
Yeah, that’s been thrown in the garbage. It didn’t work properly and it broke compatibility for very little benefit. I’ve decided to kick this particular can down the road to my future self when (if?) it’s time to re-master 1X games (such as Piggly or Loco). I’m going to hate myself then, I’m sure.