Kotlin 134.5: Let's clear some weeds.
Things are a bit ragged here and there. Let’s just trim them up a bit.
Maybe we should go after constants. In Flyer.kt, we have this random code:
const val SPEED_OF_LIGHT = 5000.0
const val UNIVERSE_SIZE = 10000.0
typealias Point = Vector2
typealias Velocity = Vector2
typealias Acceleration = Vector2
There are also some utility functions in the file:
fun Point.cap(): Point {
return Point(this.x.cap(), this.y.cap())
}
fun Velocity.limitedToLightSpeed(): Velocity {
val speed = this.length
return if (speed < SPEED_OF_LIGHT) this
else this*(SPEED_OF_LIGHT/speed)
}
fun Double.cap(): Double {
return (this + UNIVERSE_SIZE) % UNIVERSE_SIZE
}
Unfortunately, with constants, we can’t do this:
const val UNIVERSE_CENTER = Point(UNIVERSE_SIZE/2, UNIVERSE_SIZE/2)
Turns out that const val
can only be primitives.
There might be some extension functions elsewhere in the code as well.
I found this approach recommended:
object DbConstants {
const val TABLE_USER_ATTRIBUTE_EMPID = "_id"
const val TABLE_USER_ATTRIBUTE_DATA = "data"
}
Then you refer to things as DbConstants.TABLE_USER_ATTRIBUTE_EMPID
, which is clearly 10X better than "_id"
.
I guess we should have an object U
for our constants. Let’s try that. I’ll leave the code where it is, for now, in Flyer.kt.
That goes easily so far, with just a few places needing to say U.
. Commit: Move universal constants to U. Presumably now I can put a Point in there.
object U {
const val SPEED_OF_LIGHT = 5000.0
const val UNIVERSE_SIZE = 10000.0
val CENTER_OF_UNIVERSE = Point(UNIVERSE_SIZE/2, UNIVERSE_SIZE/2)
}
Now I can, I suppose, say:
class ShipFinalizer : IFinalizer {
override fun finalize(flyer: Flyer): List<IFlyer> {
if ( flyer.deathDueToCollision())
flyer.position = U.CENTER_OF_UNIVERSE
else
flyer.position = Point(random(0.0,U.UNIVERSE_SIZE), random(0.0, U.UNIVERSE_SIZE))
return emptyList()
}
}
Tests agree, so far so good.
I can look for some other literal constants.
private fun newShip(controls: Controls): Flyer {
return Flyer.ship(U.CENTER_OF_UNIVERSE, controls)
}
I find this:
private fun tooClose(other:IFlyer): Boolean {
return (Point(5000.0, 5000.0).distanceTo(other.position) < safeShipDistance)
}
This is the safety check for putting the ship back in. I think it needs to refer to ship.position
. Bit of a bug there. We have no tests for the hyperspace. Bad Ron, no biscuit.
private fun tooClose(other:IFlyer): Boolean {
return (ship.position.distanceTo(other.position) < U.SAFE_SHIP_DISTANCE)
}
I’ll test this in the game for now. No biscuit for sure. It seems to be working OK, which isn’t quite what we’d like to say about our testing. I make a note: test hyperspace return testing.
There’s probably some way to put an irritating note right into the code, but my yellow stickies will do just fine.
What other literals can I seek and destroy?
@Test
fun `capping works low`() {
val ship = Flyer.ship( Vector2(1.0, U.UNIVERSE_SIZE/2))
ship.velocity = Vector2(-120.0, -120.0)
ship.update(tick)
assertThat(ship.position.x).isEqualTo(U.UNIVERSE_SIZE-1)
assertThat(ship.position.y).isEqualTo(U.UNIVERSE_SIZE/2 - 2)
}
As I look at these, I think that the constants aren’t helping, especially the /2
ones. We’ll let it go for now.
fun createContents(controls: Controls) {
val ship = newShip(controls)
add(ship)
add(ShipMonitor(ship))
add(ScoreKeeper())
add(LifetimeClock())
for (i in 0..7) {
val pos = Point(random(0.0, 10000.0), random(0.0,10000.0))
val vel = Velocity(1000.0, 0.0).rotate(random(0.0,360.0))
val asteroid = Flyer.asteroid(pos,vel )
add(asteroid)
}
}
I can change these as I did the one above, but I think we should have a random point defined in U:
fun randomPoint() = Point(random(0.0, UNIVERSE_SIZE), random(0.0, UNIVERSE_SIZE))
Then use it here and in ShipFinalizer.
fun createContents(controls: Controls) {
val ship = newShip(controls)
add(ship)
add(ShipMonitor(ship))
add(ScoreKeeper())
add(LifetimeClock())
for (i in 0..7) {
val pos = U.randomPoint()
val vel = Velocity(1000.0, 0.0).rotate(random(0.0,360.0))
val asteroid = Flyer.asteroid(pos,vel )
add(asteroid)
}
}
class ShipFinalizer : IFinalizer {
override fun finalize(flyer: Flyer): List<IFlyer> {
if ( flyer.deathDueToCollision())
flyer.position = U.CENTER_OF_UNIVERSE
else
flyer.position = U.randomPoint()
return emptyList()
}
}
That’s more than enough for now. I smell food. I’ll pick this up tomorrow.
What Else?
What about the chance of ship destruction on emergence from hyperspace? Here’s the code, in ShipMonitor, that handles emergence:
WaitingForSafety -> {
if (safeToEmerge) {
toBeCreated = listOf(shipReset())
HaveSeenShip
} else {
startCheckingForSafeEmergence()
WaitingForSafety
}
}
private fun shipReset(): IFlyer {
ship.velocity = Velocity.ZERO
return ship
}
Suppose we were to materialize a missile together with the ship, there in toBeCreated
.
I try a spike and wind up with this:
WaitingForSafety -> {
if (safeToEmerge) {
val ship = shipReset()
val ret = mutableListOf(ship)
if (random(0.0, 1.0) > 0.90) {
val destroyer = Flyer.asteroid(ship.position, Velocity.ZERO, 100.0, 0)
val splat = Flyer.splat(destroyer)
ret.add(destroyer)
ret.add(splat)
}
toBeCreated = ret
HaveSeenShip
} else {
startCheckingForSafeEmergence()
WaitingForSafety
}
}
What this now does is that whenever the ship rezzes, whether or not it was a hyperspace call or a collision that took it out, it has a 10 percent chance that we’ll drop into the if. If we drop into the if, we rez a destroyer asteroid and a splat at the same location as the ship. The destroyer destroys the ship, and the splat leaves a mark.
For a complete implementation, I’d have to check to see if we’re emerging from hyperspace. I’m too tired to think about this, so I’ll roll it back and save this tiny article.
A few good steps, carried forward in #135.