One small improvement. Then I want to consider the bigger picture: if I were starting over, how would I do this program differently?
In our Tuesday night Friday-Coding Zoom Ensemble, I showed the group the multiple dispatch code for distance:
function Tile:distanceFrom(aTile) return self.mapPoint:distanceFromTile(aTile) end function MapPoint:distanceFromTile(aTile) return aTile:distanceFromMapPoint(self) end function Tile:distanceFromMapPoint(aPoint) return self.mapPoint:distanceFromMapPoint(aPoint) end function MapPoint:distanceFromMapPoint(aPoint) return Maps:manhattanDistance(self,aPoint) end
GeePaw Hill pointed out that one of the bounces to MapPoint isn’t needed. In the first method above, the Tile could legitimately send the
That saves a bounce and is a bit more clear. It’s not as fancy, and that’s probably very desirable. Doing as Hill suggested, we get this:
-- deleted function MapPoint:distanceFromTile(aTile) return aTile:distanceFromMapPoint(self) end
-- changed function Tile:distanceFrom(aTile) return self:distanceFromMapPoint(self.mapPoint) end -- same function Tile:distanceFromMapPoint(aPoint) return self.mapPoint:distanceFromMapPoint(aPoint) end -- same function MapPoint:distanceFromMapPoint(aPoint) return Maps:manhattanDistance(self,aPoint) end
I expect all will continue to work. Test.
Lots of tests fail. I am confused. Revert to make sure I didn’t start out with a problem. After the revert, everything is fine. Do the change again, carefully.
The bug, I note is that we have to do this, rather than what was above:
function Tile:distanceFrom(aTile) return aTile:distanceFromMapPoint(self.mapPoint) end
We have to send the message to the tile parameter, not to ourself. If we send it to ourself, the distance will always be zero. I expect this to work better. In fact it does. Delete the other method. Still good. Commit: refactor
OK, that was the little change. God job with the testing Ron, it caught that mistake.
A Larger Question
As my main technical meal today, I want to think about about a larger question, something like:
What have I learned about dungeon crawl programs that would substantially change my design were I to start over?
And of course the related question:
Are there lessons in there that should dictate changes we make to this one over time?
How do we think about this sort of thing? We try to remember bits of code that are odd, things that were difficult to do, things that seem particularly clean. We think about the overall design. We try to see whether there are “layers” or “areas” in place, or trying to come into being.
We should probably draw pictures of the design. I’m resisting doing that because with my current iPad setup, it’s awkward trying to draw and write at the same time on this device. Either is marvelous, but I haven’t sorted out a good way of switching back and forth.
That’s a terrible reason not to draw pictures, if, like me, you find sketches of things to be helpful. OK, I’ll clear a spot on my keyboard tray to draw on.
If I shove the keyboard and mouse up under the desk, I should have room to draw, though I’d prefer desktop height. The L part of the desk, well, between the low Autumn sun and my banana, there’s no space there.
That’s why a drawing is useful: you can scribble down lots of stuff quickly. Now let’s see what the picture might mean in terms of learning.
I like the way that tiles and geometry are broken out, and if I were doing it over again, I’d make that division earlier and more explicit. But that’s based on an assumption, that we’d need to have both square and hex tiles. Still, the notion of tile as a place to be, a place for things and entities to be, and a tile as a thing in space, and a tile as a thing to draw … those are different ideas and they are still glommed together more than is probably ideal.
The Entity-Monster-Player area is pretty decent, but I suspect that if we were to dig into them we’d find some ideas in Player that should be broken out. Monsters have strategies, and those were put in rather late. Doing that sooner might be good … but it might also be a rathole we’d rather not go down.
Second System Syndrome
The second-system effect or second-system syndrome is the tendency of small, elegant, and successful systems to be succeeded by over-engineered, bloated systems, due to inflated expectations and overconfidence. (Wikipedia)
This is real. I’ve fallen into it many times. If I were to do this program over again (pause to hear screams of NO NO NO) I’d still try to do it incrementally, and I’d try hard not to overbuild, but it would be difficult. Ideally, I’d be more careful to separate out concerns, but avoid extending the different concerns’ capability other than in the face of a new feature.
The EventBus came along after a while, and I think I’d put that in place sooner and use it more often. It allows for action at a distance, whether to trigger a message to the crawl or to dull all the Ankle Biters currently running.
I am tempted to have events in the dungeon going on all the time. As it is implemented now, player and monsters take turns. Even if that rule is followed, it might be better to have all the entities programmed as if they were operating independently.
It also might lead to trouble. As things stand, we have a loop over monsters that gives each one its turn to move, and it may be that the logic of Entities is sufficiently async in flavor already.
In a real product, we’d spend more time on the game and less, proportionately, on the engine. We’d have people making art, devising interactions, and so on. There are things in this game that are rarely used, such as the “darkness” feature, which can reduce the distance you can see. As far as I know, there’s thing in the game play that actually triggers that.
Since my purpose with this exercise is in fact exercise, my focus is on how to make the engine support things, not on elaborating the game. But that leads to lots of underused features. In a real app, we’d be under enough pressure that we’d probably lean too far the other way: that’s how it goes too often.
Treasures as Entities?
Treasures are different from Entities. Maybe they should be more like Entities, which might result in some code consolidation and simplification. I’d surely want to experiment with that. That said …
Monsters have a few strategies that they follow, either avoiding the player but hanging around, or moving randomly, or getting angry and moving toward the player. The PathFinder has a very special strategy, following its path to the WayDown. I feel that strategies went in at about the right time, but they could probably be set up better. Maybe Treasures are Monsters with very boring strategies and interesting behavior when you “attack” them by stepping onto their square.
Order of Features
The order in which we do features tends to drive different capabilities in the lower-level objects. Some of the things that came along later might have been discovered in a more timely fashion had we made more of a point of making stories that focused more sharply on important things that were not yet supported.
That might argue for making a list of features, identifying importance to game play and possible difficulty of implementation, and choosing challenging ones. I do look to the future in these exercises but remember, one of my primary purposes is to discover and show what happens when we take things in the order they come, and evolve our design as carefully as we can, without overbuilding, and–hopefully–keeping the design good all the time.
Which brings me to …
Before Agile was even a thing …
I recall that many years ago, the great Ward Cunningham was called in to look at a product that my team and I were working on. What Ward told the owner was, roughly, this:
Ron is clearly a genius. The people are great. The program is mediocre.
My boss said “medium”. Ward told me later that he had said “mediocre”. That’s pretty heavy damnation, in my book. But Ward wasn’t wrong. The causes were many, including …
- I wasn’t very experienced with Smalltalk, and the team were even less so. I wasn’t good enough to realize how far below “good” we were.
- Failure to Work Incrementally
- We were spending too much time building a perfect engine, and should have instead been working more closely with the app developers using the engine. At the time, that notion wasn’t known to us at all.
- Expectations Too High
- Too much was expected of us. A smaller first product might have succeeded. I wasn’t good enough to realize the need, and we all knew it was hard but thought we could probably do it.
- General Cruft
- Because of all the things going on, mostly including my low competence and that of the team, we were generating mediocre code. We wrote code that worked. We had very few defects. But the code just wasn’t habitable. It was just a bit messy everywhere you looked.
Later, along came Agile, and at my then advanced age, I learned better ways of doing things. I fant’sy that had we to do it over again, we likely could have avoided the abject failure that that project was. We could have saved the owner the millions that he lost. There might be fewer people in the world disappointed in me.
And this program, I am not happy to say, seems to me to have more cruft than it should have. Here are some of the things we can see in the code:
Recall that “primitive obsession” is the habit of using the language’s built-in types, numbers, tables, vectors, directly, rather than wrapping them in objects with meaning to the work we’re doing.
A recent example is that Tiles contained a vector
pos, consisting of an integer vector representing the tile’s position in an abstract space. The program manipulates those vectors, using vector operations on them, taking them apart, putting them together, and so on.
This is not good. Much of the power of Object Orientation comes from creating small objects that embody more meaning than plain old primitives. A string becomes a Message in one place, an Announcement in another. A vector becomes a Position in one place, a Direction in another.
I’ve not made as many small objects as would be ideal here in the Dung program. Partly that’s because I’ve had my mind set in “small computer” mode, especially after replicating all those video games. Partly it’s because Lua is a very simple language and has no object hierarchy already provided, so you have to build everything out of twigs and stones you find along the riverbed.
But mostly, it’s on me. I know better. In my defense, I have more years of writing mediocre code than really good code, but you’re supposed to learn, not revert to the old ways just because Lua and iPad.
Geeze, man, don’t beat yourself up!
Now as you read this, you may feel that I’m beating myself up. You may be thinking that if you ever said things like that to yourself, you’d start feeling really bad about yourself. Here comes a big dose of Imposter Syndrome.
But don’t worry about me. I’m really beating up the code in this program. I have a pretty clear dividing line between what a wonderful brilliant person I am, and the sometimes amazingly horrid work that I produce. I don’t take it personally in a negative way. I take it as feedback that I use to focus on understanding what I’ve done, why I’ve done it, and how to do better tomorrow.
If there’s something wrong with my program, I want the problem to be on me. If it was the inevitable result of history, or of gremlins creeping in at night and munging up the code, I’d be helpless. If it’s just that I messed up, super! I have a chance of fixing that!
But we were talking about cruft.
There seem to be a lot of
if statements in the program. In fact there are over 240 of them. That seems like a lot.
“Good” object oriented code avoids
if statements by using objects of different classes to handle optional behavior, and by use of pluggable behavior such as strategy objects or the like.
Now I want to know how many lines of code we have in the program. A very rough count tells me that there are over 8000. So only about three percent or less are
if statements. Maybe I shouldn’t be worrying about that as much as I’m inclined. But it’s still worth looking at them, and worth leaning a bit more away from conditional code.
Tables and For
There are over 100
for statements, and of course that means there are a lot of plain tables of things. Often we do better to create special-purpose collection classes. And I do have those semi-nice table operations that can be used to eliminate explicit
I wanted to write here that I need to “bear down” on writing more object-oriented code, with more objects that can help me, and less use of primitive operations like
for. But bearing down is probably exactly what one shouldn’t do.
It’s probably feelings of pressure and confusion that result in procedural code. And good design ideas don’t come along under pressure or confusion, at least not for me, and I’d bet not for most people. So what I need to do instead is probably to
Step Back More Often
This notion comes in close contact with Hill’s “Many More Much Smaller Steps” notion. When we move in small steps, we have an opportunity to pause after each step, and we can readily lean back to see how things look from a different perspective.
And remember the “Red-Green-Refactor” mantra of TDD. If I had to characterize my standard pattern when I’ve been doing TDD in the Dung program, it has been Red Green Do Another Red Green. I might do better to slow my roll. Maybe I’m letting myself get into tearing along at speed.
Maker App vs Shipping App
This is another Hill notion, coming from his notion “the code works for you, you don’t work for the code”. This means that as we build the app that will be shipped, we should also be building up a tool set that he calls the “Making App”, tools that help us build the Shipping App. I started on that a time or two, but really didn’t get anywhere. I was thinking in terms of tools for “those other folx”, the artists and story makers, not in terms of tools for “us folx”, the ones writing the engine.
One particular need is
Better Tab Management
This is a pretty large Codea application. There are 53 tabs, each containing one or more classes or tests. Now Codea 4, coming soon? to a tablet near you, supposedly has better handling for tabs, but it would sure be good if I could figure out some way to segment the program and use Codea’s simple Dependency feature to separate out relatively stable bits.
The big issue with this is that in incremental development like this, you may wind up modifying any given class anywhere, to support the behavior you’re working on somewhere else. If that class is in a dependent project, you’d have to exit your current project, losing context, and open the other one. And so far, Codea doesn’t want to run multiple instances. If it could, you might be able to keep a library open as well as your app.
I don’t have a solution to this issue but it does get in the way.
Well. That’s interesting. Given this much thinking, a couple-three hours, I don’t see much that I’d absolutely do differently if I were to start over on D2. I’d certainly work for better code, better separation of concerns, especially in the larger objects, and I think I’d make a bit more use of the publish-subscribe model.
But overall, I’m not unhappy with where we are. That’s a good place to be.
And I hope it’s better than “mediocre”. Maybe “adequate”?
See you next time!