Let’s plug away, learning IDEA, Kotlin, and whatever, while also seeing how much TDD we can do on an essentially graphics-oriented game. AND: I notice something odd.

I was thinking this morning about differences between people and between genders, in part because I’m reading Cryptonomicon, and in part because in the morning I often think about strange things. Because I am somewhat more wise than I used to be, I’ll not take you down that path, which is perilous at best. But one thing that I know about myself is what parts of things I like, and what parts I don’t like.

I am happy when I have good tools in my hands, making something that is challenging, but for which I have the tools, physical and mental, that I need. I am less happy when it gets down to the other 90 percent of the work where you are just cranking it out. And I am less happy when the tools do not yet fall readily to my hands, so that although I can see what needs to be done, I can’t see how to make the tools do the thing.

In short, I’m not good at grunt work, slogging through. When I was much younger, six decades or so, I thought that I was more like a sharp knife than a hammer. These days, the blade is probably not that sharp, but sometimes I can make up for it in experience.

How does that apply to the Kotlin Bucket Game, you may be wondering. Well, we are in a phase of learning IDEA and Kotlin where frankly I could move twice as fast in Codea Lua, my most recent tool of choice. We’re getting close to a place where I will probably be able to write Lua in Kotlin, or FORTRAN in Kotlin, or something like that, but not yet write Kotlin as she should be written. Same with IDEA. Right now, most of the time, it’s serving me about as well as a weak version of Sublime Text, my main text editor. (Yes, I know that I can set up Sublime keybindings in IDEA. I just might do that one of these days.)

So … if someone were to ask me “Are we having fun yet?”, I’d have to say no. But we’re getting closer.

So, if you’re reading these articles and you see me doing things “the wrong way” in Kotlin or IDEA (or thinking), please feel free to reply to a Tweet and let me know. Or email me at ronjeffries at acm dot org. Or try a DM, and in so doing try not to seem like a beautiful woman from somewhere exotic who is just looking for someone good to talk to. I get so many of those DMs … mostly from women with names like BruceWayne followed by a large integer. Curious how they name themselves over there. But I digress.

Anyway, got something to suggest or ask, tweet me up. You can’t embarrass me, I already know how foolish I am. And you can’t tick me off, unless we need to discuss politics, which we won’t.

Neat Development!!.

GeePaw Hill has noticed what I’m doing and is moved to comment. I’ll include here links to his tweets and blogs relating to what I’m doing, and I’ll also respond to his tweets and blogs with words in my own tweets and articles.

This should be fun. Here’s his first thread.

Today …

I plan to work more on my Bucket class, which probably needs renaming to better show that it is a FlyingSpaceBucket. I’m working toward the Bucket having a position (Coordinates), a velocity, and being subject to acceleration. I think I’ll have small classes for velocity and acceleration, and I’m trying to TDD all the bucket flight stuff, all its motion in space, rather than follow my more usual practice of looking at the screen and tweaking numbers and code until it flies right.

We’ll see how that goes.

I plan to start with something that GeePaw mentioned on our Slack. My nascent Coordinates class is this:

class Bucket(var coords: Coordinates) {

    fun getX(): Double {
        return coords.x
    }

    fun getY(): Double {
        return coords.y
    }
}

I had intended to have the Bucket x and y be pseudo properties of Bucket, referring down to coords. I couldn’t figure out how to do that. GeePaw showed me, so I’ll change my test:

    @Test
    fun bucketExists() {
        val startingPos = Coordinates(100.0,200.0)
        val b = Bucket(startingPos)
        assertThat(b.getX()).isEqualTo(100.0)
        assertThat(b.getY()).isEqualTo(200.0)
    }

That will become, first:

    @Test
    fun bucketExists() {
        val startingPos = Coordinates(100.0,200.0)
        val b = Bucket(startingPos)
        assertThat(b.x).isEqualTo(100.0)
        assertThat(b.getY()).isEqualTo(200.0)
    }

The compiler won’t let me do the .x, of course. In good old Codea, I could just run the code and find out but here I have to please some folx over at IntelliJ before I can even try stuff.

The change to Bucket is this:

class Bucket(var coords: Coordinates) {
    val x get() = coords.x

    fun getY(): Double {
        return coords.y
    }
}

I’m not sure whether we’ll need a setter or not. We might. I’m not even entirely sure that I want these properties. Anyway, change the y similarly:

    @Test
    fun bucketExists() {
        val startingPos = Coordinates(100.0,200.0)
        val b = Bucket(startingPos)
        assertThat(b.x).isEqualTo(100.0)
        assertThat(b.y).isEqualTo(200.0)
    }

class Bucket(var coords: Coordinates) {
    val x get() = coords.x
    val y get() = coords.y
}

OK, we’re really moving now. Well, actually, we’re not. I think that a Bucket needs a velocity, which means that we need a Velocity class.

Velocity is of course a vector (dx,dy), so I think our Bucket will just know how to get those … here’s my test now:

    @Test
    fun bucketExists() {
        val startingPos = Coordinates(100.0,200.0)
        val b = Bucket(startingPos)
        assertThat(b.x).isEqualTo(100.0)
        assertThat(b.y).isEqualTo(200.0)
        assertThat(b.dx).isEqualTo(0.0)
        assertThat(b.dy).isEqualTo(0.0)
    }

I don’t plan to create the Bucket (can I please rename this class to Ship soon, this is no longer funny) with an explicit velocity, so I’ll need to initialize the class. I’ve never done that. I think it’s done with an init function.

The init notion seems to be a dead end. I find that this will pass my test:

class Bucket(var coords: Coordinates) {
    var velocity = Pair(0.0,0.0)

    val x get() = coords.x
    val y get() = coords.y
    val dx get() = velocity.first
    val dy get() = velocity.second
}

Now I have in mind that my Velocity will have behavior, so I’ll go ahead and build it, but then I want to stop and think about what I’m doing.

In particular, why so bottom up? But first let’s get Velocity done.

class Velocity(var dx: Double = 0.0, var dy: Double = 0.0) {

}

And the Bucket becomes:

class Bucket(var coords: Coordinates) {
    var velocity = Velocity()

    val x get() = coords.x
    val y get() = coords.y
    val dx get() = velocity.dx
    val dy get() = velocity.dy
}

And the test is green. And the cat needs food. BRB.

I’m Not Working As I Usually Do

In this program, I have no objects that do anything, yet I already have classes Bucket, Coordinates, and Velocity. If I were doing this in Codea Lua, I would be more likely to have Bucket had x and y member variables, and probably dx and dy, and a method to move by adding dx dy to x and y, and an accelerate method to add d2x and d2y to dx and dy, and the thing could fly off the screen. Then I’d make it wrap around.

Here, I’ve created all these little classes. Now, I think these little classes are probably good, and in the fullness of time in Lua I’d probably factor them out … but …

Something about this language and tool are making me do things in a different order.

Is that good, or bad? I don’t think we can know, not yet at least, but as to the extent to which it’s my unfamiliarity with the language, or the insistence of the tool that everything hook up before you can even try to run, I’m not sure. My guess is that it is mostly the strictness of the assemblage, which wants every i crossed and t dotted before you can be permitted to run your code to see what it does.

The effect of this is to change the way TDD works for me. In Lua, I type a bit of test and run it and of course it says there’s no such thing as Coordinates. Here, I don’t get to run it (although I could try) and it’s already in my face about Coordinates.

It’s changing my rhythm. It’s enforcing a new rhythm. I’m trying not to like it or dislike it, just to notice it. And it’s definitely noticeable.

For now, let’s see what we want to do about moving the Bucket. No, let’s first rename the thing to Ship. It’ll still look like a bucket but that’s for the graphics department to deal with.

There’s surely a rename class up in this, er, thing …

OK, that’s nice. It even renamed my BucketTest to ShipTest and renamed the files as well. I approve. Ship is now:

class Ship(var coords: Coordinates) {
    var velocity = Velocity()

    val x get() = coords.x
    val y get() = coords.y
    val dx get() = velocity.dx
    val dy get() = velocity.dy
}

And so on. Anyway …

Moving the Ship

The Ship has a velocity now, and coordinates. We’ve not said what the units are, and we may never need to. But we need to think about what motion will mean.

We know that in the game, we’ll be getting a pulse every so often, I think nominally 50 or 60 times a second. Whenever that pulse hits, we’ll want to step the ship a bit. In essence, we’ll add its velocity (dx,dy) to its coordinates (x1,y1) to get new coordinates (x2,y2). However, suppose that x and y are miles, from some origin in space. Then dx and dy are probably miles per hour, or miles per second, some miles per time unit. But when we get a pulse, it won’t have been an hour from the previous pulse, it’ll be a sixtieth of a second or something. So we’ll have to scale the ship’s nominal velocity by the amount of time since last pulse.

If velocity is one mile per second, and we get a pulse after 1/2 a second, we should add velocity/2, not velocity. And so on.

I want to know how frequently we’re going to get these pulses and what units they are in. Fortunately, in the running mode, we are fielding pulses.

The main program includes this:

        override fun handle(now: Long) {
            deltaTime = (now - lastFrame).toDouble() / 1000000.0
            totalTime += deltaTime
            lastFrame = now
            pulse(deltaTime, totalTime)
        }

And this:

    private fun pulse(deltaTime: Double, totalTime: Double) {
        input.popTo(deltaTime, this::handleKey)
        moveLittleBucket(totalTime)
    }

So if I were to fling a print in there and run, I should be able to see what deltaTime looks like. Doing that tells me that deltaTime tends to be about 16.66, which looks suspiciously like it might be in units of milliseconds.

We can fold the constants any way we want. Let’s suppose that we’ll keep velocity in (Space Units) per second. A Space Unit will be a measure of distance. I’ve been thinking that on a 4:3 screen, the SpaceUnits might be 4000x3000. And I think it might take four seconds at a reasonable speed to drift across the screen, so that a reasonable velocity for a ship is about 1000 SU/second. At a guess, that’ll be a bit on the slow side.

Also, we’re going to hit a design issue shortly.

When the ship changes position, do we want it to get new coordinates, or do we want its coordinates to change. It makes sense to do the former, but it will also mean that 60 times a second, when we calculate a new position, we’ll create a new Coordinates instance and throw away the old one. That will create garbage rapidly. Same with Velocity. If every time we accelerate the ship, we adjust velocity, we probably don’t want to be creating and destroying new Velocity objects all the time.

So I think we’ll update our existing coords and velocity, overriding the x and y in the instances. We’ll see how that looks. In a sense I don’t like doing it, because invariants are better but I can’t quite swallow such an obvious waste of cells. I think this is a flaw in the design, but it’ll be pretty well isolated.

I hope. Anyway, this won’t be my biggest mistake, I’m sure.

Let’s write a test.

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

I’ve given myself a clue there in the timeMS bit. That method doesn’t exist yet, nor does setVelocity. Let’s fix that. I start with this:

class Ship(var coords: Coordinates) {

    var velocity = Velocity()

    val x get() = coords.x
    val y get() = coords.y
    val dx get() = velocity.dx
    val dy get() = velocity.dy

    fun setVelocity(setDX: Double, setDY: Double) {
        dx = setDX
        dy = setDY
    }
}

The compiler objects that dx and dy are val and cannot be assigned. No surprise there. I think we just change them to var and provide a setter.

class Ship(var coords: Coordinates) {

    var velocity = Velocity()

    val x get() = coords.x
    val y get() = coords.y
    var dx
        get() = velocity.dx
        set(value) {velocity.dx = value}
    var dy
        get() = velocity.dy
        set(value) {velocity.dy = value}

    fun setVelocity(setDX: Double, setDY: Double) {
        dx = setDX
        dy = setDY
    }
}

I think the compiler is satisfied with the get and set. It doesn’t like my creation of the ship with no coordinates. I had in mind defaulting that but let’s change the test instead.

    @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)
    }

Still not happy, there’s no move. Let’s let IDEA provide it. It provides an empty method, of course, which causes the test to fail:

org.opentest4j.AssertionFailedError: 
expected: 200.0
 but was: 0.0

That was what I expected. Now to implement move. I’ll sneak up on it:

    fun move(timeMS: Double) {
        setCoords(newCoords)
    }

Hm I notice that here I’m positing a coordinates object, which I thought we were trying to avoid creating. I knew that was a mistake, but let’s perpetuate it:

    fun move(timeMS: Double) {
        setCoords(newX, newY)
    }

I can implement that:

    fun setCoords(newX: Double, newY: Double) {
        x = newX
        y = newY
    }

Compiler hates that. Add the sets:

    var x 
        get() = coords.x
        set(value: Double) {coords.x = value}
    var y 
        get() = coords.y
        set(value: Double) {coords.y = value}

Compiler is happy. Let’s get newX and newY …


    fun move(timeMS: Double) {
        val newX = 0.0
        val newY = 0.0
        setCoords(newX, newY)
    }

This should let the test run and fail again with 0 instead of 200. And it does. I could make it pass by setting newX to 200, of course. But I think I can write this out now …

    fun move(timeMS: Double) {
        val scale = timeMS/1000.0
        val scaledDX = dx*scale
        val scaledDY = dy*scale
        val newX = x + scaledDX
        val newY = y + scaledDY
        setCoords(newX, newY)
    }

Woot! My test is green. The ship moves.

I’ve been at this writing and coding for about two hours. Very slow progress, but writing everything down including my every though has taken up most of the time. Let’s reflect, and maybe call it a morning.

Reflection

We’ve accomplished some good stuff. We now have Coordinates class and Velocity class, as well as Ship née Bucket, and we can move the Ship by applying a suitable fraction of its velocity, which is now assumed to be in SpaceUnits per second. There are probably a few thousand SpaceUnits across and up the screen. (I don’t suppose we’ll actually do units qua units, we’ll just honor them in the code with constants like scale above. We might promote that value to a pantheon of UniversalValues or something.)1

I made the “decision” not to create new Coordinates every time I move the ship, and we’re now reaping the trouble from that:

    fun move(timeMS: Double) {
        val scale = timeMS/1000.0
        val scaledDX = dx*scale
        val scaledDY = dy*scale
        val newX = x + scaledDX
        val newY = y + scaledDY
        setCoords(newX, newY)
    }

Here we are way up in ship, and because we want the x’s and y’s broken out, we didn’t just say something like:

        coords = coords + velocity*scale

If we were to do that, we’d create a new Velocity instance via the scaling, and a new Coordinates instance via the +, and we wanted to avoid that. The cost is truly nasty code that would be easy to get wrong. All it would take is a mistaken x for y and the code would be broken. And even if it isn’t broken, it just isn’t as clear as it might be.

But dayum, it’s hard to want to create and toss away all those little objects.

However … what we have here is a speculative optimization of the code, and even though it’s obvious that we will in fact avoid all those creations and deletions … its far from clear whether the performance impact is high or low. I’ve always said “trust your garbage collector”, and I sure didn’t follow my own advice here.

We should probably fix this.

Feelings …

I’m beginning to feel a bit of ease as I work. I’m starting to settle into a pattern of knowing what I’m doing. I wouldn’t say that I’m using Kotlin / IDEA well, but I’m not stumbling as often or as badly.

I consider that to be good news. I feel less tense, less irritated, less stupid. Two out of three of those are probably accurate.

I think that for my penance this morning I’ll read about defining operators in Kotlin, and next time, probably change these little objects to be immutable. I think that’ll be a good thing.

Until then, tweet me up or otherwise message me with advice, criticism, whatever works for you. Kindness preferred, of course.

See you next time!



  1. Man flings a little French and a little Latin into a single paragraph, you gotta respect that.