The stories left to do, from yesterday, are:

  • Ship wraps around in toroidal space (0,0)..(xMax,yMax)
  • Ship accelerates along its forward axis;
  • Ship enforces a maximum speed;
  • Ship can rotate;

Yesterday, working on wrapping, I built this tiny object:

class Universe(val xMax: Double, val yMax: Double) {
    private fun wrap(low: Double, actual: Double, high: Double): Double {
        return if (actual > high) actual%high
        else if (actual < low) high + actual%high // mod is negative
        else actual
    }
    fun wrap(c:Coordinates): Coordinates {
        val wx = wrap(0.0, c.x, xMax)
        val wy = wrap(0.0, c.y, yMax)
        return Coordinates(wx,wy)
    }
}

There’s some question in my mind whether this is quite the place for things like this, but I don’t see a better place … yet.

Last night at the Tuesday meeting of the Friday Night Zoom Ensemble, Hill said something about an interface, maybe Geometry, and passing the Universe, as Geometry, to a method wrap, on Coordinates, which would then call back to the Universe/Geometry to do the work. At least that’s what I understood about what he was saying: if it’s a bad idea, I probably misunderstood.

Let’s try that, in our universe tests:

    @Test
    fun coordinateWraps() {
        val u = Universe(400.0, 300.0)
        val c = Coordinates(-11.0, 415.0)
        val w = c.wrap(u)
        val e = Coordinates(389.0, 15.0)
        assertThat(w).isEqualTo(e)
    }

That’ll whine about there being no wrap on Coordinates, I reckon.

Kotlin: Unresolved reference: wrap

I noticed that IDEA has shown wrap in red, I think even before I compiled, so it probably already knew that I needed wrap. I’ll try to stay alert for that: I’m just beginning to get used to all the stuff it thinks about. Anyway, it’ll help:

    fun wrap(u: Universe): Coordinates {
        return u.wrap(this)
    }

The test fails thus:

Expected :Coordinates(x=389.0, y=15.0)
Actual   :Coordinates(x=389.0, y=115.0)

I’ll bet that’s my mistake … review the test.

    @Test
    fun coordinateWraps() {
        val u = Universe(400.0, 300.0)
        val c = Coordinates(-11.0, 415.0)
        val w = c.wrap(u)
        val e = Coordinates(389.0, 15.0)
        assertThat(w).isEqualTo(e)
    }

Right. I was thinking 400 when I wrote the 415. Change that to 315 and the test should run as intended.

Right. Let’s commit: Coordinate can wrap via universe.

Got a really useful warning from the commit. Best warning I’ve ever seen:

Warning:(4, 22) Actual value of parameter 'low' is always '0.0'

That’s referring to the Universe class, and I was just thinking about it this morning as I brushed my teeth etc. I am referring to the low value in the wrap functions, but in fact I fully intend only to need the high values: all universes will start from (0,0). Let’s clean that up.

class Universe(val xMax: Double, val yMax: Double) {
    private fun wrap(actual: Double, high: Double): Double {
        return if (actual > high) actual%high
        else if (actual < 0.0) high + actual%high // mod is negative
        else actual
    }
    fun wrap(c:Coordinates): Coordinates {
        val wx = wrap(c.x, xMax)
        val wy = wrap(c.y, yMax)
        return Coordinates(wx,wy)
    }
}

That’s better, fewer parameters to worry about.

Interface

Now I’ll mess around with the Interface part of Hill’s idea, which should allow a narrower parameter class in the call to wrap. I’m not sure just how we do it, so this will take some research. I’m starting at 0838.

I think I probably start with this, at the top of Universe.kt:

interface Geometry {
    fun wrap(c: Coordinates)
}

Now can I change the method in Coordinates to expect a Geometry?

    fun wrap(u: Universe): Geometry {
        return u.wrap(this)
    }

Seems like. But I bet it won’t run. Right, IDEA is already complaining. I have to indicate that my implementation of wrap is in service of Geometry. Time for some searching. Time is 0843.

It’s 0856 and I have something working:

interface Geometry {
    fun wrap(c: Coordinates): Coordinates
}

class Universe (val xMax: Double, val yMax: Double) : Geometry{
    private fun wrap(actual: Double, high: Double): Double {
        return if (actual > high) actual % high
        else if (actual < 0.0) high + actual % high // mod is negative
        else actual
    }
    override fun wrap(c:Coordinates): Coordinates {
        val wx = wrap(c.x, xMax)
        val wy = wrap(c.y, yMax)
        return Coordinates(wx,wy)
    }
}

And in Coordinates:

data class Coordinates(val x: Double,  val y: Double) {
    fun wrap(g: Geometry): Coordinates {
        return g.wrap(this)
    }
}

I guess that’s better … if and when Universe gets more methods, Coordinates won’t be tempted to use them. Personally, I don’t see much advantage, but I think I’ve done the thing Hill intended. If not, he’ll surely tell me.

OK, time to regroup and reflect.

Reflection

It makes sense (to me) that Coordinates should know how to wrap, and that they should defer that action to a Geometry of the Universe kind of thing. We could implement the wrapping part directly in Coordinates, I suppose, and just fetch the xMax and yMax from Universe. That might be better … but I think it’s the Universe that wraps around, not the coordinate. The universe might be a Klein bottle instead of a torus, and the Coordinates shouldn’t have to change to deal with that.

So things are probably close to where they should be. So lets look at the story:

  • Ship wraps around in toroidal space (0,0)..(xMax,yMax)

We have a coordinate wrap but in Ship we have this:

    fun move(timeMS: Double) {
        val scale = timeMS/1000.0
        val scaled = velocity*scale
        val newPlace = Coordinates(x + scaled.dx, y + scaled.dy)
        coords = newPlace
    }

Well, this is interesting, isn’t it? What we have here right now is that we are doing some work for Coordinates, that is, computing new values based on adding a velocity increment to it. This is really a job for Coordinates, one might think.

We cannot add two Coordinates with meaning (unless we squint just right and pretend that a coordinate is a vector) but we can add a Velocity to a coordinate.

Ideally we might want to write this:

    fun move(timeMS: Double) {
        val scale = timeMS/1000.0
        val scaled = velocity*scale
        val newPlace = coords + scaled
        val wrapped = newPlace.wrap(universe)
        coords = newPlace
    }

IDEA tells me, of course, that I can’t add a velocity to a coordinates, and that I don’t have the universe to pass it on.

I do think that a Ship should know its universe, so we can finesse that for a moment:

        val universe = Universe(4000.0, 3000.0)

And in Coordinates, we can implement plus like this:

    fun plus(v: Velocity): Coordinates {
        return Coordinates(x + v.dx, y + v.dy)
    }

I expect my move test to run now. Well, except for this:

Kotlin: 'operator' modifier is required on 'plus' in 'org.geepawhill.starter.Coordinates'\

Right:

    operator fun plus(v: Velocity): Coordinates {
        return Coordinates(x + v.dx, y + v.dy)
    }

Tests are green. Let’s commit. I’ll make up some kind of decent message: “Coordinates can add a velocity; Ship uses that. Something in Universe too.”

Not a great message but I just want the save point.

Now I did a not quite the thing thing:

    fun move(timeMS: Double) {
        val universe = Universe(4000.0, 3000.0)
        val scale = timeMS/1000.0
        val scaled = velocity*scale
        val newPlace = coords + scaled
        val wrapped = newPlace.wrap(universe)
        coords = newPlace
    }

I added the wrap to Ship, but I don’t have a test for it. I’m not a stickler for doing TDD by rote but we really should have a test that requires that. We have this ship test:

    @Test
    fun shipMoves() {
        val ship = Ship(coords = Coordinates(0.0, 0.0))
        ship.setVelocity(1000.0, 0.0)
        ship.move(timeMS = 200.0)
        assertThat(ship.x).isEqualTo(200.0)
        assertThat(ship.y).isEqualTo(0.0)
    }

Let’s do another, with a wrap. We are presently using a fake universe inside Ship, 4000x3000. We’ll assume that for this test.

    @Test
    fun shipMovesWrapped() {
        val ship = Ship(coords = Coordinates(10.0, 2990.0))
        // universe is 4000x3000
        ship.setVelocity(-15.0, 25.0)
        ship.move(timeMS = 1000.0)
        assertThat(ship.x).isEqualTo(3995.0)
        assertThat(ship.y).isEqualTo(15.0)
    }

I think this is right. We’re going -15 in x, which will take us to -5, which should be 4000-5, or 3995. And in y, we’re going 25, so we’ll wind up at 3015, which should wrap to 15.

Run the test. Surprisingly, at least to me, I get:

Expected :3995.0
Actual   :-5.0

Grr, what have I done wrong?

Well, first, this:

    fun move(timeMS: Double) {
        val universe = Universe(4000.0, 3000.0)
        val scale = timeMS/1000.0
        val scaled = velocity*scale
        val newPlace = coords + scaled
        val wrapped = newPlace.wrap(universe)
        coords = newPlace
    }

Really should have set to wrapped, not newPlace. But I wonder why both checks didn’t fail. Anyway, fix that:

    fun move(timeMS: Double) {
        val universe = Universe(4000.0, 3000.0)
        val scale = timeMS/1000.0
        val scaled = velocity*scale
        val newPlace = coords + scaled
        val wrapped = newPlace.wrap(universe)
        coords = wrapped
    }

Test. Test is green. But I am not satisfied and want a stronger test.

    @Test
    fun shipMovesWrappedAgain() {
        val ship = Ship(coords = Coordinates(3990.0, 10.0))
        // universe is 4000x3000
        ship.setVelocity(15.0, -25.0)
        ship.move(timeMS = 1000.0)
        assertThat(ship.x).isEqualTo(5.0)
        assertThat(ship.y).isEqualTo(2985.0)
    }

This is green. I am content. I still wonder about that other number being correct but I’m not going to chase it.

Commit: “Ship wraps on move”. Story done, almost. But now we have a new story:

  • Ship needs to have a Universe;

Of course there’s another issue looming: as this game gets more interesting, should we ever manage that feat, the universe will contain things like ships and bucketoids and such, and will be managing their collisions or battles or whatever they do. I’ll be faced, as it seems I often am, with a circular connection, universe knowing ships and ships knowing the universe. Maybe that’s just fine. We’ll see.

For now, anyway, let’s extend ship and its tests to include a universe.

class Ship(val universe: Universe, var coords: Coordinates) {

That should drive out the setting in all my various sets. I think I can probably use some clever IDEA tool to help me here if I need it.

That went fairly quickly by hand. And I even remembered to remove this from move:

        val universe = Universe(4000.0, 3000.0)

Commit: Ship now created with a Universe.

Let’s reflect.

Reflection

I’m certainly moving deliberately if not just flat slowly. Part of that is probably due to unfamiliarity with IDEA and Kotlin, but with what I’ve been doing so far, I’ve been quite sure what Kotlin needs me to say, with exception of the interface thing, which I just had to learn.

Hill said something last night about noticing a difference between how he thinks, as a long-time user of languages with strict typing, and what he observes in me, as a long-time user of languages with duck typing, which mostly don’t care what you refer to, just ensuring at run time that the method you called is there.

He and Chet said something about it seems like I just type things expecting that I’ll fill in the blanks in due time. And, I very much do that. Sometimes I refer to it as programming by intention, where you just type in what you intend the system to do, and then you drill down and make it do that.

So I suspect / hypothesize / wonder if the strictness of a language like Kotlin actually—I don’t want to say forces—encourages a more deliberate, thoughtful, perhaps even more bottom-up way of working. I’ll stay open to that possibility and pay attention to it.

That said, I think I’m going slower in terms of lines of code per unit time. Part of that is due to the fact that I’m trying very hard to get these little base-level objects “right”, so I’m going over them again and again. But that doesn’t satisfy me as an explanation for my seemingly slow pace.

In my defense, I want to refer to some code we looked at last night by another member of the ensemble, who shall remain nameless (coughchetcough). Said member had implemented a little game in homage to the Aladdin ride at Disney, where, if you stand in just the right place, a camel will spit on you. His game has a camel, spitting, and you try to catch the drop in a bucket.

In his game, the code that moves the um projectile is written in line and is quite ad hoc, multiplying the um not to put too fine a point on it spittle’s x and y coordinates by hand-crafted values that make it arc downward. We all agreed that, of course, we wouldn’t leave it that way, that we’ve factor out something sooner or later, making it better. And we remarked that inexperienced (or overloaded) programmers will often write code like that and then leave it that way, to the detriment of the design.

But there’s no denying that the unnamed individual has a game working, done in less than a day, and I’m 8 articles in and have nothing that looks like a game to show. Still in my defense, I’m here to learn Kotlin and IDEA and write about that learning, using my Ship and Universe and what all as a substrate for that learning and writing.

Or, possibly, I’m just very very slow.

Let’s see what our story file looks like now:

  • Ship wraps around in toroidal space (0,0)..(xMax,yMax)
  • Ship needs to have a Universe;
  • Ship accelerates along its forward axis;
  • Ship enforces a maximum speed;
  • Ship can rotate;

I’ve been here almost exactly two hours, so let’s call it a morning and move on to eating things.

Summary

Despite having no visible game to demonstrate, I’ve got some objects that do things. In particular, I have a Ship object that knows how to move in a universe, taking advantage of the universe’s special geometry (toroidal). I could quite easily set up my bucket-ship to fly across the screen, wrapping around.

I am so tempted to do that. But I’m trying to work without looking at the graphics, so I’ll hold off a bit longer.

I think the code I’m creating is pretty decent Kotlin, perhaps even intermediate-level good. I’d not say expert-level, and in fact I don’t even know what expert-level Kotlin would look like. I’ve never seen any that I know of. But I have nicely factored very small objects, and that’s a plus in my book. And, I even have two operators defined, and at least one use of an overridden function name with differing parameters. (I am not sure that’s a good usage in my case, but it certainly works as advertised).

People are messaging me with advice and things to read. Thanks to those who have done so, and welcome to any of you who’d like to do so.

See you next time!