Kotlin 210 - Ditch Those Collections
GitHub Decentralized Repo
GitHub Centralized Repo
I’m rather sure there is no active use of the
attackers
andtargets
collections. Let’s remove them.
I’ll look for access to these collections. Here are the interesting changes:
fun saucerMissing(): Boolean {
return saucers.isEmpty()
}
fun shipIsPresent(): Boolean {
return ships.isNotEmpty()
}
Those two referred to attackers but now ship and saucer have little collections of their own.
Here’s one that will need a bit of work:
fun canShipEmerge(): Boolean {
for ( target in knownObjects.targets ) {
if ( target is Saucer ) return false
if ( target is Asteroid ) {
val distance = target.position.distanceTo(U.CENTER_OF_UNIVERSE)
if ( distance < U.SAFE_SHIP_DISTANCE ) return false
}
}
for ( attacker in knownObjects.attackers ) {
if ( attacker is Missile ) return false
if ( attacker is Saucer ) return false // already checked but hey
}
return true
}
I’ll save it for later, let’s do the easier ones first.
Remove these tests:
fun `missiles are attackers`() {
fun `remove removes missiles from attackers`() {
fun `remove removes ship from attackers`() {
fun `remove removes ship from targets`() {
fun `remove removes saucer from targets`() {
fun `remove removes asteroid from targets`() {
fun `remove removes saucer from attackers`() {
fun `Asteroid is a target`() {
fun `saucer is an attacker`() {
fun `ship is an attacker`() {
Now we have to deal with this:
fun canShipEmerge(): Boolean {
for ( target in knownObjects.targets ) {
if ( target is Saucer ) return false
if ( target is Asteroid ) {
val distance = target.position.distanceTo(U.CENTER_OF_UNIVERSE)
if ( distance < U.SAFE_SHIP_DISTANCE ) return false
}
}
for ( attacker in knownObjects.attackers ) {
if ( attacker is Missile ) return false
if ( attacker is Saucer ) return false // already checked but hey
}
return true
}
We want to exit if there are any missiles, if the saucer is present, or if asteroids are too close. Let’s just say that. First I do these, which used to check targets or attackers or something:
fun saucerIsPresent(): Boolean {
return saucers.isNotEmpty()
}
fun saucerIsMissing(): Boolean {
return saucers.isEmpty()
}
fun shipIsPresent(): Boolean {
return ships.isNotEmpty()
}
fun shipIsMissing(): Boolean {
return ships.isEmpty()
}
Then:
fun canShipEmerge(): Boolean {
if (knownObjects.saucerIsPresent()) return false
if (knownObjects.missiles.size > 0) return false
for ( asteroid in knownObjects.asteroids ) {
val distance = asteroid.position.distanceTo(U.CENTER_OF_UNIVERSE)
if ( distance < U.SAFE_SHIP_DISTANCE ) return false
}
return true
}
Test. One failed, needed to refer to saucers, not targets. I’ve removed all the setting of targets, got ahead of myself.
@Test
fun `game-centric NO MAKER saucer appears after seven seconds`() {
// cycle receives ELAPSED TIME!
val mix = SpaceObjectCollection()
mix.add(Ship(U.CENTER_OF_UNIVERSE))
mix.add(Asteroid(Point(100.0,100.0)))
val game = Game(mix) // makes game without the standard init
game.cycle(0.1) // ELAPSED seconds
assertThat(mix.size).describedAs("mix size").isEqualTo(2)
assertThat(mix.deferredActions.size).describedAs("deferred size").isEqualTo(1)
val deferred = mix.deferredActions.find { (it as DeferredAction).delay == 7.0 }
assertThat(deferred).isNotNull
game.cycle(7.2) //ELAPSED seconds
val saucer = mix.saucers.find { it is Saucer }
assertThat(saucer).isNotNull
}
I’m green. Let’s finish up targets though. I’ll remove the list and fix the errors.
Once I remove it from the list of lists below, we’re green:
fun allCollections(): List<MutableList<out SpaceObject>> {
return listOf (asteroids, deferredActions, missiles, saucers, ships, splats)
}
Commit: attackers and targets lists removed as no longer needed.
I played the game and noticed that on the last ship, if the saucer is running, you don’t get GAME OVER until the saucer is gone. That’ll be because we don’t take the ship until it’s able to emerge. I’ll let that ride.
Reflection
We now have all the objects that exist broken out into unique sub-collections in SpaceObjectCollection:
class SpaceObjectCollection {
var scoreKeeper = ScoreKeeper()
val asteroids = mutableListOf<Asteroid>()
val deferredActions = mutableListOf<DeferredAction>()
val missiles = mutableListOf<Missile>()
val saucers = mutableListOf<Saucer>()
val ships = mutableListOf<Ship>()
val splats = mutableListOf<Splat>()
Right now, most of the adds go through our class-based when:
private fun addActualSpaceObjects(spaceObject: SpaceObject) {
when (spaceObject) {
is Missile -> add(spaceObject)
is Asteroid -> add(spaceObject)
is Ship -> add(spaceObject)
is Saucer -> add(spaceObject)
is Splat -> add(spaceObject)
}
}
There’s just one use of that, let’s inline it:
fun add(spaceObject: SpaceObject) {
when (spaceObject) {
is Missile -> add(spaceObject)
is Asteroid -> add(spaceObject)
is Ship -> add(spaceObject)
is Saucer -> add(spaceObject)
is Splat -> add(spaceObject)
}
}
As I was saying, most everything goes through that method, but when we make our changes to Transaction, the generic one will be removed entirely.
We now attempt to remove any object that is to be removed, except for DeferredAction, from all the lists, even though only one will contain any given object:
fun remove(deferredAction: DeferredAction) {
deferredActions.remove(deferredAction)
}
fun remove(spaceObject: SpaceObject) {
for (coll in allCollections()) {
coll.remove(spaceObject)
}
}
That, too, should disappear soon.
I look askance at the following methods in SpaceObjectCollection, which seem to me likely to need to go away, and perhaps they are only used in testing:
fun forEachInteracting(action: (SpaceObject) -> Unit) =
spaceObjects().forEach(action)
fun contains(obj: SpaceObject): Boolean {
return spaceObjects().contains(obj)
}
val size get() = spaceObjects().size
We’ll see. This is an incremental refactoring and we are in no hurry to get done. All the changes we make leave the program still working, and a bit more in the direction we want to go.
I fold some one-line functions into one line, which is our house style. Here’s the whole class for your amusement:
class SpaceObjectCollection {
var scoreKeeper = ScoreKeeper()
val asteroids = mutableListOf<Asteroid>()
val deferredActions = mutableListOf<DeferredAction>()
val missiles = mutableListOf<Missile>()
val saucers = mutableListOf<Saucer>()
val ships = mutableListOf<Ship>()
val splats = mutableListOf<Splat>()
fun spaceObjects(): List<SpaceObject> = asteroids + missiles + saucers + ships + splats
// update function below if you add to these
fun allCollections(): List<MutableList<out SpaceObject>> {
return listOf(asteroids, deferredActions, missiles, saucers, ships, splats)
}
fun add(spaceObject: SpaceObject) {
when (spaceObject) {
is Missile -> add(spaceObject)
is Asteroid -> add(spaceObject)
is Ship -> add(spaceObject)
is Saucer -> add(spaceObject)
is Splat -> add(spaceObject)
}
}
fun add(deferredAction: DeferredAction) { deferredActions.add(deferredAction) }
fun add(asteroid: Asteroid) { asteroids.add(asteroid) }
fun add(missile: Missile) { missiles.add(missile) }
fun add(saucer: Saucer) { saucers.add(saucer) }
fun add(ship: Ship) { ships.add(ship) }
fun add(splat: Splat) { splats.add(splat) }
fun addScore(score: Int) { scoreKeeper.addScore(score) }
fun any(predicate: (SpaceObject) -> Boolean): Boolean = spaceObjects().any(predicate)
fun applyChanges(transaction: Transaction) = transaction.applyChanges(this)
fun asteroidCount(): Int = asteroids.size
fun clear() {
scoreKeeper.clear()
for (coll in allCollections()) {
coll.clear()
}
}
fun forEachInteracting(action: (SpaceObject) -> Unit) =
spaceObjects().forEach(action)
fun contains(obj: SpaceObject): Boolean = spaceObjects().contains(obj)
fun pairsToCheck(): List<Pair<SpaceObject, SpaceObject>> {
val pairs = mutableListOf<Pair<SpaceObject, SpaceObject>>()
spaceObjects().indices.forEach { i ->
spaceObjects().indices.minus(0..i).forEach { j ->
pairs.add(spaceObjects()[i] to spaceObjects()[j])
}
}
return pairs
}
fun performWithTransaction(action: (Transaction) -> Unit) {
val trans = Transaction()
action(trans)
applyChanges(trans)
}
fun remove(deferredAction: DeferredAction) { deferredActions.remove(deferredAction) }
fun remove(spaceObject: SpaceObject) {
for (coll in allCollections()) {
coll.remove(spaceObject)
}
}
fun saucerIsMissing(): Boolean = saucers.isEmpty()
fun saucerIsPresent(): Boolean = ! saucerIsMissing()
fun shipIsMissing(): Boolean = ships.isEmpty()
fun shipIsPresent(): Boolean = ! shipIsMissing()
val size get() = spaceObjects().size
}
Not bad at all. Maybe a bit busy, but it’s all pretty simple. I’m pleased with the afternoon’s bit of refactoring. Let’s ship this and work on Transaction tomorrow.
See you then!