Kotlin 180: Pieces of Flare
Can you believe that I forgot the flare of the ship’s exhaust when it accelerates? No wonder I can’t win this game.
The original ship drew a triangle at the rear, representing the rocket exhaust. It’s shown as a dashed line in this drawing that I made:
The Lua code that I wrote to draw the ship looked like this:
function Ship:drawLineShip()
line(-3,-2, -3,2)
line(-3,2, -5,4)
line(-5,4, 7,0)
line(7,0, -5,-4)
line(-5,-4,-3,-2)
accel = (accel+1)%3
if U.button.go and accel == 0 then
strokeWidth(1.5)
line(-3,-2, -7,0)
line(-7,0, -3,2)
end
end
We can see that what the code does is draw the triangle one time out of three around the drawing loop, which makes it flicker. I guess we’ll do the same thing. The ship is draw, in the Lua program, with stroke width 1, so seeing that 1.5 there makes me think that I probably didn’t like how it showed up and gave it a little bump. We’ll see.
In the Kotlin ship, the ship’s points are:
private val points = listOf(
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)
)
So we’re at the same scale. Let’s define the exhaust triangle:
private val flare = listOf(
Point(-3.0,-2.0), Point(-7.0,0.0), Point(-3.0, 2.0)
)
Basically I just transcribed the data from Codea Lua, since we’re at the same scale here. Now we need to display the flare. In this version, the ship does not know that it is accelerating. Instead, the Controls calls this function on Ship:
fun accelerate(deltaV: Acceleration) {
velocity = (velocity + deltaV).limitedToLightSpeed()
}
Let’s set a flag in accelerate
and clear it before we call controls:
private var accelerating: Boolean = false
private var displayAcceleration: Int = 0
override fun update(deltaTime: Double, trans: Transaction) {
inHyperspace = false
accelerating = false
dropScale -= U.DROP_SCALE/60.0
if (dropScale < 1.0 ) dropScale = 1.0
controls.control(this, deltaTime, trans)
move(deltaTime)
}
fun accelerate(deltaV: Acceleration) {
accelerating = true
velocity = (velocity + deltaV).limitedToLightSpeed()
}
This does the job. I agree with my former self that the flare isn’t visible enough, so let’s see if we can adjust our strokeWidth similarly, then I’ll make a little video for you. strokeWeight
is 8/30 for the ship (I had a reason for doing it that way but I’m not sure what it was. Magic numbers, gotta hate ‘em.) I should normalize those but for now, I won’t even do the math, I’ll pull out the weight as a const and use it.
private val strokeWeight = 8.0/30.0
fun draw(drawer: Drawer) {
drawer.translate(position)
// drawKillRadius(drawer)
drawer.scale(30.0, 30.0)
drawer.strokeWeight = strokeWeight
drawer.scale(dropScale, dropScale)
drawer.rotate(heading )
drawer.stroke = ColorRGBa.WHITE
drawer.lineStrip(points)
if ( accelerating ) {
displayAcceleration = (displayAcceleration + 1)%3
if ( displayAcceleration == 0 ) {
drawer.strokeWeight = 1.5*strokeWeight
drawer.lineStrip(flare)
}
}
}
In the end, I set the flare’s stroke weight to 2.0 and the game looks like this:
I clipped out the bits where I got killed three times in as many seconds. I might be able to make the case that I can program Asteroids fairly well, but there’s no evidence that I can play the game well.
Anyway, that works.
Reflection
Should I have written some kind of test for that code? I sure can’t see how it would have helped. Sometimes, at least for me, it just doesn’t help, or I can’t see how. I do find, however, that, if I try sometimes, I test what I need. And the more tests I write, the better I like them.
What Else?
Shall we do something more this morning? Here’s the list from yesterday:
- Small saucer (1000 points) appears after 30,000 points. (I’ll never see that.)
- Small saucer shot accuracy improves. (Needs research to see what the original game did.)
- Some write-ups say that small asteroids are faster. (My reading of the code says they are not, but might be random in a range.)
- Saucer zig-zagging could be a bit more random rather than once a second.
- Ship turning seems a bit slow. But will accuracy suffer with a larger rotation step?
- Ship acceleration seems sluggish.
- Hyperspace return away from edges for visibility.
- Allow for non-square windows?
- Improve generality of graphics, stroke width vs scale etc.
- Eliminate magic numbers, moving to Universe.
- Ability to change some settings from keyboard, i.e. make it so that I can win a few points.
- Timing could be improved to be more consistent. Different objects do it differently. OneShot might help?
Let’s do the hyperspace return thing. The issue is this: when you go to hyperspace, sometimes the ship comes out very close to the edge, and you can’t see where it is. I think it would be better if it only appeared about 1000 light years or space miles or international space units, whatever they are, inside the boundary.
Here’s where we calculate the hyperspace return location.
class Ship
fun finalize(): List<ISpaceObject> {
if ( inHyperspace ) {
position = U.randomPoint()
} else {
position = U.CENTER_OF_UNIVERSE
velocity = Velocity.ZERO
heading = 0.0
}
return emptyList()
}
Universe
fun randomPoint() = Point(Random.nextDouble(0.0, UNIVERSE_SIZE), Random.nextDouble(0.0, UNIVERSE_SIZE))
Let’s do a new function or two. Since they’re random, I’m not going to even try to test them. This may be a mistake.
fun randomInsidePoint() = Point(randomInsideDouble(), randomInsideDouble())
fun randomInsideDouble() = 1000.0 + Random.nextDouble(UNIVERSE_SIZE-2000.0)
fun finalize(): List<ISpaceObject> {
if ( inHyperspace ) {
position = U.randomInsidePoint()
} else {
position = U.CENTER_OF_UNIVERSE
velocity = Velocity.ZERO
heading = 0.0
}
return emptyList()
}
Test in game, but I really want to test this with a micro(ish) test as well. That seems to work well. Let’s do write a test and then throw it away.
@Test
fun `randomInsideDouble between 1000 and 9000`() {
for (i in 1..100000) {
var rd = U.randomInsideDouble()
assertThat(rd).isBetween(1000.0, 9000.0)
}
}
Green. Comment the test out.
I forgot to commit the flare. Better commit now. Since both sets of changes were to ship, I’ll just do the one: Ship has exhaust flare; ship hyperspace return is always at least 1000 from the edge.
And Now?
I want to experiment with faster rotation. That’s here, in Controls:
private fun turn(obj: Ship, deltaTime: Double) {
if (left) obj.turnBy(-U.SHIP_ROTATION_SPEED*deltaTime)
if (right) obj.turnBy(U.SHIP_ROTATION_SPEED*deltaTime)
}
Universe
const val SHIP_ROTATION_SPEED = 180.0 // degrees per second
I’ll push that up to 200 just to see how it feels to me. I like it better, will leave it. Commit: adjust ship rotation to 200 degrees per second.
My testing reminds me that I think acceleration is a bit too slow. I adjust it from 1000 to 1200, and that seems better. Commit: Change acceleration from 1000.0 to 1200.0
Let’s call it a morning. Here’s the list of things done and to do, edited, reformatted, and sorted:
Priority = H/M/L; Done = √; Internal Improvement = (i)
- √ Ship exhaust flare
- √ Ship turning seems a bit slow. 180->200. Accuracy seems fine.
- √ Ship acceleration seems sluggish. (1000 -> 1200)
- √ Hyperspace return away from edges for visibility.
- H Check general scale of ship etc against original.
- H Ship should slow à la friction.
- H Sound???
- H Small saucer (1000 points) appears after 30,000 points.
- H Small saucer shot accuracy improves. (Needs research.)
- M Add a sun with gravity?
- M (i) Improve generality of graphics, stroke width vs scale etc.
- M (i) Eliminate magic numbers, moving to Universe.
- M Ability to change some settings from keyboard (cheat codes)
- M (i) Timing code could be improved to be more consistent. OneShot?
- L Some write-ups say that small asteroids are faster. (Needs research)
- L Saucer zig-zagging could be a bit more random.
- L Allow for non-square windows?
Summary
We seem to be nearing that part of a development where there are lots of little things that need doing, and most of them are not very interesting. Fortunately there are also a few fun ones. I think we’ll find that most everything we need to do with be simple and that there will be no surprises or big changes. Heck, I think I could convert this to Spacewar! without big changes. Dare me?
Today’s changes weren’t terribly important but were worth doing. None of them involved extensive change anywhere. The big one was two variables in Ship, one to detect that we’re accelerating and one to flash the flare.
I’m starting to feel the need for something new to do. What should it be? It has to be easy, must include some learning, and must be fun.
See you next time!