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
private
members. Everything ispublic
. In fact, thereās not a single mention of theprivate
keyword 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.
Anywayā¦
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!
A short-lived celebration
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.
A bug?
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.
Another bug?
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 x
between a
and 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!
Keyboard/Joypad/Mouse control
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
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.
An 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.
Back-pedaling
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.