GitHub Repo

Continuing to work on creating a centralized version of Asteroids, for comparison with the current decentralized design. Amazing the things I don’t know how to do.

I haven’t worked on a team doing production code for years, haven’t even coached one. Even when teaching development classes with Chet, I let him manage the environment. So this project is basically my first even somewhat real program in many years. It’s amazing what I don’t know.

I don’t know how to set up Gradle or Maven or whatever it takes to set up an environment. I really only know the very basics of Git and GitHub. I can commit, push, rollback current changes. Much beyond that and I’m calling tech support. The thing with issues like these is that I need them seldom, almost never. Folx who work with the stuff every day learn them, and after a few tries, I learn them too, but there’s a lot that hasn’t come up often enough to get into my brain yet.

Today, I’m trying to sort out a decent way to work on a centralized design for Asteroids. I think what I should really do is to have two separate versions, at home and on GitHub. It would be possible to do a branch in the single repo, but the branch could never be merged. So I guess I want to fork the existing repo and then create a new GitHub repo for that one. But I’m not sure of the proper approach.

What I did:

  1. Create new repo on GitHub, asteroids-centralized.
  2. Use IDEA to clone that new repo.
  3. git remote add upstream https://github.com/RonJeffries/openrndr-ship-1
  4. git fetch upstream (I do not know what this did.)
  5. git pull upstream HEAD

I manage to create a configuration that runs the tests in the new version, and they run green. I can’t seem to get it to run the main. It’s giving me a null pointer exception here:

fun main() = application {
    configure {
        width = U.WINDOW_SIZE
        height = width
    }

The exception line number is the first line of the code above, the fun main.

I hesitate to do much work without the ability to run the game.

Later that day …

GeePaw Hill paired with me for a couple of hours at least, trying to sort the problem. We even rebuilt the whole program again, cloning a new repo and such, to no avail. GeePaw worked on it later yesterday and discovered that my main file was missing its package statement, which allowed IDEA to set up a run config.

The next day …

I did that this morning, and continued to get the null pointer exception: IDEA was trying to restart the program with the -XstartOnFirstThread flag set. I put that flag into the config and now the game runs.

Now I’ve done something else wrong, and IDEA is trying to commit all kinds of bizarre files. I think I’ve reverted most everything. I’ll try pulling from GitHub.

Still weird things happening.

I’m going to try to take one tiny step toward centralization and then wait until I can get some tech support. I have a feeling that I’m going to want to start over creating this fork, but we’ll see.

Tentative Plan

When this is done, the game will create and maintain all the objects. Objects like ScoreKeeper will exist in a more rudimentary form, perhaps, just a simple object that contains score and shows it. Other objects, like WaveMaker and ShipChecker/ShipMaker, will not exist other than perhaps as methods on game, or conceivably little helper objects. They will not be “in the mix”: only active objects: missiles, ship, saucer, and asteroids will be.

When the centralization is complete, or nearly so, there will be no need for any of the beginInteractions, interactWith, finishInteractions at all. Game will be keeping all the counts, creating explosions, whatever has to be done. In particular, the interaction among the active objects will be done in a loop like the sketch I created a day or so ago.

So it seems to me that a good way to work for a while will be to remove the special non-active objects from the mix, replacing them with game code, one at a time.

Let’s start with the quarter, since it creates the mix and we’ll be changing that all the time.

class Quarter(private val controls: Controls, private val shipCount: Int = 4): ISpaceObject, InteractingSpaceObject {
    override fun update(deltaTime: Double, trans: Transaction) {
        trans.clear()
        val scoreKeeper = ScoreKeeper(shipCount)
        trans.add(scoreKeeper)
        trans.add(WaveMaker())
        trans.add(SaucerMaker())
        val shipPosition = U.CENTER_OF_UNIVERSE
        val ship = Ship(shipPosition, controls)
        val shipChecker = ShipChecker(ship, scoreKeeper)
        trans.add(shipChecker)
    }

    override val subscriptions: Subscriptions = Subscriptions()
    override fun callOther(other: InteractingSpaceObject, trans: Transaction) {}
}

This is invoked here:

        program {
            val font = loadFont("data/fonts/default.otf", U.FONT_SIZE)
            val controls = Controls()
            val game = Game().also { it.createInitialContents(controls) }
            keyboard.keyDown.listen {
                when (it.name) {
                    "d" -> {controls.left = true}
                    "f" -> {controls.right = true}
                    "j" -> {controls.accelerate = true}
                    "k" -> {controls.fire = true}
                    "space" -> {controls.hyperspace = true}
                    "q" -> { game.insertQuarter(controls)}
                }
            }

And here’s insertQuarter:

    fun insertQuarter(controls: Controls) {
        add(Quarter(controls))
    }

    fun add(newObject: ISpaceObject) = knownObjects.add(newObject)

In the odd style of the decentralized game, game adds the quarter object which then runs on update and adds in all the stuff after clearing the transaction. (That just sets a flag in the transaction which causes it to clear out the SpaceObjectCollection before applying the rest.)

So instead of insertQuarter adding the Quarter, we can create it and execute it. We’ll keep the object: it’s a perfectly reasonable helper; or we can fold the code into the insert. I think we’ll go in two passes, but fold the code in. That approach will make our new centralized program tend to be too monolithic, but that, in turn, will drive refactoring that makes more sense in the centralized context.

Policy
Tend toward folding things into game rather than retaining special objects.

I see that there are two uses of Quarter in Game:

    fun createInitialContents(controls: Controls) {
        add(Quarter(controls,0))
    }

    fun insertQuarter(controls: Controls) {
        add(Quarter(controls))
    }

The first one sets up the game in “attract mode”. I’m not sure quite how I’ll deal with that, but I think it’ll become clear in just a moment. We want to change things so that we execute the quarter’s update, rather than put it into the mix:

    fun insertQuarter(controls: Controls) {
        val quarter = Quarter(controls)
        val trans = Transaction()
        quarter.update(0.0, trans)
        knownObjects.applyChanges(trans)
    }

I can test this much to see if the game will start when I hit the “q” key. It does, and of course I immediately get killed. I am terrible at this game. Now the change to createInitialContents is also clear.

Do the tests run? I could commit if so. They do. Commit: insertQuarter does not add Quarter to the mix.

I can change the other method similarly:

    fun createInitialContents(controls: Controls) {
        val quarter = Quarter(controls, 0)
        val trans = Transaction()
        quarter.update(0.0, trans)
        knownObjects.applyChanges(trans)
    }

The difference is that zero parameter, the ship count. The attract mode doesn’t create a new ship.

Green and game starts properly. Commit: Quarter no longer added to the mix.

Now, Quarter no longer needs to be any kind of space object. Let’s try that.

class Quarter(private val controls: Controls, private val shipCount: Int = 4) {
    fun update(deltaTime: Double, trans: Transaction) {
        trans.clear()
        val scoreKeeper = ScoreKeeper(shipCount)
        trans.add(scoreKeeper)
        trans.add(WaveMaker())
        trans.add(SaucerMaker())
        val shipPosition = U.CENTER_OF_UNIVERSE
        val ship = Ship(shipPosition, controls)
        val shipChecker = ShipChecker(ship, scoreKeeper)
        trans.add(shipChecker)
    }
}

That works. It doesn’t need deltaTime, let’s change the signature. That will ripple into some tests.

class Quarter(private val controls: Controls, private val shipCount: Int = 4) {
    fun update(trans: Transaction) {
        trans.clear()
        val scoreKeeper = ScoreKeeper(shipCount)
        trans.add(scoreKeeper)
        trans.add(WaveMaker())
        trans.add(SaucerMaker())
        val shipPosition = U.CENTER_OF_UNIVERSE
        val ship = Ship(shipPosition, controls)
        val shipChecker = ShipChecker(ship, scoreKeeper)
        trans.add(shipChecker)
    }
}

IDEA finds them all and removes the parameter. Tests are green. Commit: Quarter is no longer a space object. update does not expect deltaTime.

Let’s sum up. You’ll see why in a moment:

Summary

IDEA is still trying to commit all kinds of strange files. I’ve done something wrong with this repo. I’ll wait for advice from GeePaw, but I suspect I’m going to have to fork again to get this to work well. I can’t be going through a list of 97 files checking the two or three that I really want to commit.

So I’ll stop working until I have a clean project to work on.

However, all the administrative hassles aside, we’ve seen that removing this special object from the mix and bringing its capability directly under Game was quite easy. It would be equally easy to inline the Quarter update method and remove the whole class. According to the Policy above, I think we’ll do that once I get a clean repo.

I think today’s small chunk of work will have to be redone on a fresh fork, once I get that set up. Slowly I am learning how to do these admin tasks, but I prefer to pair on them with someone more expert than I am, because I am really quite naive. I tell myself this is just ignorance, not stupidity. Ignorance can be fixed.

Despite all the hassles, I think this is going pretty well. I think the change to centralization will be quite interesting. I hope you’ll follow along!