Kotlin 246 - Maybe a bit of game?
GitHub Decentralized Repo
GitHub Centralized Repo
GitHub Flat Repo
The new flat version can draw a ship and an asteroid, both poorly. It can move them along the screen. Let’s see if we can ‘refactor’ toward an actual design for the game.
So far, the “design” is interesting, in that despite there being two very different things on the screen, a ship and an asteroid, there’s only one kind of thing internally, a data class SpaceObject
. This is in high contrast with our previous two versions, which have separate classes for Asteroid and Ship (and Missile and Saucer). Right now, the moving and drawing is explicit, in the main program:
extend {
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.RED
deltaTime = seconds - lastTime
lastTime = seconds
if (controls_accelerate) {
ship.dy += 120.0*deltaTime
}
move(ship, width + 0.0, height + 0.0, deltaTime)
move(asteroid, width + 0.0, height + 0.0, deltaTime)
draw(ship, drawer)
draw(asteroid, drawer)
drawer.fontMap = font
drawer.fill = ColorRGBa.WHITE
drawer.text("Asteroids™", width / 2.0, height / 2.0)
}
That last bit just displays “Asteroids” on the screen, and should be removed. I think that we should allow the main to compute the deltaTime, and that the rest of what’s in there should be in the game’s cycle. Let’s extract that to a new file, which I’ll call Game, in a function gameCycle
.
First, I’ll extract it as a function. Command-Option-M.
private fun Program.gameCycle(
ship: SpaceObject,
deltaTime: Double,
asteroid: SpaceObject
) {
if (controls_accelerate) {
ship.dy += 120.0 * deltaTime
}
move(ship, width + 0.0, height + 0.0, deltaTime)
move(asteroid, width + 0.0, height + 0.0, deltaTime)
draw(ship, drawer)
draw(asteroid, drawer)
}
I don’t like that. It’s passing ship and asteroid separately. I really want there to be a list or array of all the game objects. I’ll undo that and work here a bit more.
I create an array of the asteroid and ship, up in the program
part of main:
val ship = SpaceObject(
SpaceObjectType.SHIP,
512.0,
512.0,
100.0,
-90.0,
45.0,
)
val asteroid = SpaceObject(
SpaceObjectType.ASTEROID,
300.0,
300.0,
74.0,
40.0,
)
val spaceObjects = arrayOf(asteroid, ship)
Then, in the extend
part:
extend {
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.RED
deltaTime = seconds - lastTime
lastTime = seconds
if (controls_accelerate) {
ship.dy += 120.0*deltaTime
}
for (so in spaceObjects) { move(so, width, height, deltaTime) }
for (so in spaceObjects) { draw(so, drawer) }
// drawer.fontMap = font
// drawer.fill = ColorRGBa.WHITE
// drawer.text("Asteroids™", width / 2.0, height / 2.0)
}
Now I’ll just extract the two for
statements as gameCycle
. I need to think about how to manage the controls, but this part should be immediate. IDEA reformats for me. Not really what I’d have done, but I’ll allow it:
extend {
drawer.fill = ColorRGBa.WHITE
drawer.stroke = ColorRGBa.RED
deltaTime = seconds - lastTime
lastTime = seconds
if (controls_accelerate) {
ship.dy += 120.0*deltaTime
}
gameCycle(spaceObjects, deltaTime)
// drawer.fontMap = font
// drawer.fill = ColorRGBa.WHITE
// drawer.text("Asteroids™", width / 2.0, height / 2.0)
}
}
}
private fun Program.gameCycle(
spaceObjects: Array<SpaceObject>,
deltaTime: Double
) {
for (so in spaceObjects) {
move(so, width, height, deltaTime)
}
for (so in spaceObjects) {
draw(so, drawer)
}
}
Of course it made the function private but that’s OK, we’ll fix that right up. I’m not sure what that Program
prefix is about but I feel confident that we’ll sort it out quickly.
I don’t see a refactoring that’ll do this for me. I’ll just cut and paste into a Game file.
Grr. Doing that shows me that I need a raft more parameters, width, height, and drawer. I’ll just punch them in for now. Change signature and get this in Game:
fun gameCycle(
spaceObjects: Array<SpaceObject>,
width: Int,
height: Int,
drawer: Drawer,
deltaTime: Double
) {
for (so in spaceObjects) {
move(so, width, height, deltaTime)
}
for (so in spaceObjects) {
draw(so, drawer)
}
}
Back in the main:
gameCycle(spaceObjects,width,height,drawer, deltaTime)
Game should “run”. And it does. Commit: initial gameCycle function.
I’m ignoring a few warnings, that shipFlare
and font
are never used, and that initializing deltatime
is redundant, unnecessary, and not needed.
I’m not entirely unhappy about passing all those parameters. An alternative would be to have some globals containing width and height, but parameters are generally a better way to go with such things, and since there’s only one call, it seems quite reasonable.
OK, can’t really tolerate that so
variable name. Fix that up:
fun gameCycle(
spaceObjects: Array<SpaceObject>,
width: Int,
height: Int,
drawer: Drawer,
deltaTime: Double
) {
for (spaceObject in spaceObjects) {
move(spaceObject, width, height, deltaTime)
}
for (spaceObject in spaceObjects) {
draw(spaceObject, drawer)
}
}
Commit: rename loop variables.
I think we’ll take a break here, so let’s sum up. Maybe these short little articles are better, or at least less painful.
Summary
I think it’s worth noting that with no extra hassle, the game cycle so far comes down to just looping over the space objects without regard to type. We surely will need to be type-sensitive, but for now, things are amazingly compact.
I think this is going to be a far more interesting experiment than I had anticipated. I thought it would turn out to be a really messy program that would be quite discernibly “worse” than an OO program, even if more compact. Now, I’m not so sure whether it will be worse.
I wish more people were visibly interested in this. Maybe when we’re all done, I’ll just write one summary article and beg people to read it. Meanwhile, if you’re one of the few readers here, please do let me know.
And if you’re not reading this but would like me to write about something else, let me know that. Ask me questions or something.
See you “all” next time!