An issue in the design troubles me. I’m not sure whether it really should trouble me, but I think something needs to be done. I really find this hard to think about. I’ll discuss why at the end. And I’ll tell you what you should do.
While I’m not trying to be a fanatic about it, I am trying to track pretty closely with what the original Asteroids game did, from a look and feel viewpoint. I use modern technology to do it, but I want the game to look like the original. And, if I can ever work out how to add sound, to sound like it as well.
The original game, like most games of that era, was very tightly coded to align with the capabilities of the hardware available, especially the display, which was generally the limiting factor in what could be done. Asteroids had a display with an addressing space from 0-1023 in both x and y. It was a vector-oriented display, not a bit-map, and the hardware could draw a line from any point to any other on the screen. There were various drawing instructions, including a “short” line drawing command that only allowed two bits of accuracy in x and y. And the display had a rather complicated way of scaling so that you could specify a local and global scale factor as you drew. The global scales available ranged from 1/128 to 128 in powers of two, and the local scale ranged from 1, 1/2, 1/4, down to 1/512. I imagine that was useful in drawing asteroids of different sizes, since you could have just one display list and scale it. Similarly with alphanumerics as you could scale those to any desired size.
Think of all the fun ways you could set things up so that you could use mostly the short vector commands instead of the large ones because you only had 8K of memory, counting the 4K taken up by the definitions of the asteroids, ship, saucer, letters of the alphabet, and so on.
The game actually had 17 different definitions for drawing the ship, since it can rotate. No vector rotation in those days, it was all pre-computed. And the 17 definitions? Those were just for one 90-degree segment: the other three were done by adjusting the sign bits in the vector drawing commands at the last minute.
Why 17? I do not know.
Now, within these limits, the game used the various scaling powers of two to decide how big things were. All the objects were defined compactly, at the smallest scale that would contain the detail. We’ve seen some of that here, since we use the actual values from the original game to define our objects.
Here’s the Ship,, for example:
Point(-3.0, -2.0), Point(-3.0, 2.0), Point(-5.0, 4.0), Point(7.0, 0.0), Point(-5.0, -4.0), Point(-3.0, -2.0)
The objects were scaled variously, to make them the “right size” for the game. The Ship was scaled by 2, making it about 24 “pixels” long and 16 high. The asteroids were scaled 4, 8, and 16 ( believe), making them 32, 64, and 128 in diameter, since the rock definitions are in a 4x4 square. I’m not sure about saucer scaling, I’ll look it up, but it, too, is drawn at two scales, I think 1 and 1/2.
If the above numbers are correct, a full-scale asteroid should be 1/8 as wide as the screen. Mine are just about exactly 1/10 of a screen width.
The Thing Is …
In setting up the game this time, I suspect my focus was primarily on getting Kotlin and OPENRNDR to do anything useful, but for whatever reason, I didn’t pay much attention to the scale of things on the screen, and they are not consistent with the original game. And I made what I think is a mistake, in that I defined my game universe to be ten thousand by ten thousand pixels … and then scaled it downward into 800 or 1000 to fit on my screen … and then adjusted the scale of the objects up more or less at random, settling on a scale of 30.
I made no general provision for defining the scales of things relative to each other, and in fact I made the ship too big or the asteroids too small. And if that were not bad enough, I defined the kill radii pretty randomly, in terms of game coordinates (the 0-10,000 ones) but to make sense those values have to relate to the size of the thing on the screen.
Then a couple of days ago I set out to fix some of this and I don’t think I made it better. For example, the kill radius of an Asteroid is:
override val killRadius: Double = 1000.0 * U.DRAW_SCALE/30.0,
What’s U.DRAW_SCALE, you ask?
const val DRAW_SCALE = 15.0
But on the other hand, the Ship’s kill radius is just this:
override val killRadius: Double = 150.0
Why isn’t it the same style as the other? Clearly Asteroids are trying to be defined to behave consistently if the DRAW_SCALE changes, and Ship isn’t.
SplatView is adjusted, perhaps even correctly:
private val ratio = U.DRAW_SCALE/30.0
Saucer is not adjusted:
private val ratio = U.DRAW_SCALE/30.0
This Is Not Good Code
I don’t see any way to look at this situation and like it. There are some things that should be true but that are not:
- Game should scale to screen size
- If we’re emulating the original, the game should have the same proportions as the original, the same sense of crowding. If we do change the size of the window, probably everything should resize proportionately. (We could imagine increasing the size of the universe but that’s not really in the spirit of replication that we’re striving for.)
- Object sizes should follow window size
- If we do change window size, all the objects should adjust in size proportionately. For that to happen, they should all be driven similarly, ideally directly from the window size. As things are now, some of them might adjust together, and some clearly would need specific edits, which would not be obvious nor easy to find.
- Object sizes should be proportional to the original
- If the original smallest asteroid was larger than the ship, the same should be true here. If I’m correct that the original ship is drawn at scale 2, it is 28 long (-7..7) counting flare, and if I’m correct that the asteroids are drawn at powers of two, then the smallest asteroid should be 32 wide.
I think my asteroid
scalevalues are 2, 4, and 8. If that’s the case, then the largest asteroid has a width of 64 units and if they are scaled at 15, they should be 960 pixels wide. The smallest will have a width of 16 and will be 16*15 or 240 pixels. An experiment convinces me that that’s the case. So … if the ship with flare has a length of 14, and it is to be unit scaled, and to be just a bit smaller than the smallest asteroid, then its scale must be 2 and it should be 28 long, or 420 at scale 15. However, in the game at present it is scaled by one, and is therefore 210 pixels long.
And so on and so on scooby-dooby-dooby. It just gets harder to think about the more I think about it. If you are not confused by this point, either I have not done my job in “explaining” this, or you are a lot smarter than I am. Both are quite possible.
Point is, even though I have tweaked the numbers so that everything is sized correctly, I’m not following the original game’s sizing, which must have been
I should first verify the scales used in the original, so that I can know for certain that I’m tracking them (or know for certain that I’m not). I should then provide some standard way of defining the objects’ scale, probably with integers like 2, 4 8, 16, that draw them the right size. And then, I should determine a sensible overall scale to fix the game space into the window space.
For that last step, it might make sense to change the width of the internal game space from 10,000 to 1024. We could convert to integer as well, but that’s probably not necessary and would be inconvenient since OPENRNDR is oriented around Double, not Int.
And, somewhere in all this, it would be nice if the binary values 2,4, 8, …, 1024 turned up and if 1024, in particular, appeared one more time when we scale the game space to the real screen.
For today, I’m going to settle for the thinking and some experiments on the screen. But I observe that I am really having trouble keeping all this clear in my mind. There are the original coordinate values, ranging over numbers like -4 to +4 or -7 to +7, the original game scale factors, 2, 4, 8, 16, whose association with each object I need to write down or otherwise find a way to remember, and my own game’s scale factors, 15, which I pulled out of some orifice somewhere, and then 2,4,8 for asteroids, and no notable scale for the ship but 2 for the saucer (I think in the original it was 1 and 1/2.) I think the large saucer is supposed to be about the same size as the ship. (It is 10 wide in base form.)
I have neither a carefully filled-out table of values, nor a smooth and easy to remember theory for all this, and the result is that I just keep smashing it with a hammer to get it to look right, with the resulting code being full of random-appearing numbers for scale and kill radius and so on.
I need to do a better job. I need to get this written into a form I can work from consistently, and then work the code into a sensible consistent form as well.
The program works fine. The scale on the screen is about right, and the kill radii are all close enough to make everything work right. Why am I agonizing over this?
It’s because if I ever have to read this code again, or someone wants to pull the code and play with it, it’s hard to understand and very prone to errors in scaling, because the scaling all looks pretty hand-crafted, or hammer-bashed, depending on how harshly you want to think about it. Either way, changing how things work or adding some new objects would come down to more ad hoc bashing and wedging to make it work right.
And that’s not OK with me. I want to be able to be at least satisfied with this code, if not absolutely proud of it. There are parts I really like, like the independent objects. But the scaling … I do not like it, Sam I Am, and I’m going to figure out a way to fix it so that I can at least tolerate it, if not like it.
Is there a lesson for you in here? I hesitate to say. I will say this: in my work, my real programming work back when I did real programming work, I always wanted to be proud of the code I wrote. It wasn’t really that I was concerned for future me, or future you, that would have to deal with it. It was more a matter of “If I made it, it should be discernibly good”: a point of personal pride.
But … when the code gets good enough along the dimension of personal pride, it is also more understandable for future me or future you, so that the point of personal pride also supports the company’s business needs.
So … what should you do? You should do as you feel is best. What I’m going to do is work until I have this thing clear in my mind, and then I’m going to make it right.
Back to the drawing board. See you next time!