Dungeon 168
After a brief digression to build polyominoes, we are back in the dungeon. I’m beginning to wonder why. And: Why XP is The Way.
As of the beginning of this article, number 168 in my dungeon series, I have written over half a million words in this topic, according to this command:
Widebody2:020-dung ron$ find . -name "*.md" -exec wc -w {} + | tail -n1
514510 total
Is that credible? Yes, that’s only a bit over 3000 words per article, and the count does include words of code. So, I’m wondering why I couldn’t have written a hard-boiled programmer novel at ten cents a word and done better than I’m doing here.
Fortunately, this is more of a hobby than a business. It’s a fun hobby, and with income of zero after half a million words, it’s a terrible business.
Oddly enough, I’m not quite bored with the dungeon game yet, although I imagine many readers signed out about 130 articles ago and are just waiting to see if I ever do something else. Or reading the work of someone more interesting.
I suppose one way to monetize this game would be to build it up and release it as a game kit. Someone suggested that to me in a tweet yesterday, let me see if I can find it. Ah, yes, from Mihai Popescu:
Sell the actual game? But build a really robust modding system for other devs! Or sell the game + code.
That could actually be fun. I’m not sure that monetizing should be a priority but I certainly wouldn’t turn down a couple of bucks from people who found the thing interesting.
The best part about a hobby project is that I could drop it tomorrow and not feel badly about it. For now, I think there are things to learn.
What, Pray Tell, Are We Trying to Learn?
Software development, as my friend GeePaw Hill likes to say, is about changing code. Not just writing code, changing it. This is particularly true in a True Agile effort, where we keep the code ready to be used every day from the beginning of the effort into the indefinite future.
To accomplish that, we’re changing code from the first day on. Yes, we do a lot of adding code, but even there, the new code is woven in with the existing code. And if our efforts are to be sustainable, we have to change the very design of the code we’re working on, keeping the design just good enough for today, and then adjusting it to be good enough for tomorrow.
So what I learn from projects like this one is all about changing code. I believe that it’s quite possible, not terribly difficult, to build any program incrementally, enhancing its capability and its design in parallel, for a very long time. And, believing that, I try to influence those within my hearing to work that way.
Why do I do that? I’ll tell you why. Kent Beck once said that all software methods are based on fear, the fear that the method creator most has about building software. My biggest fear is having the project cancelled, because the business people ran out of patience–or money–waiting for the Big Release In The Future. The fix for that concern is to have a small release, but a growing release, with the most important bits already working, ready to go.
That fix won’t solve all the problems I’m afraid of, but it gives me the best chance. Often–almost always–there’s a viable subset of the “whole” product that could be deployed, put into use, providing value and revenue. Having a small but working release always ready gives the business people the best chance of seeing that there’s something to sell here, and thereby keeping the project alive.
I think that’s the fundamental fear that True Agile addresses. I think it’s the fundamental fear, in particular, that XP addresses. There are other fears, such as “If we don’t do enough design up front our design will crumble before we get a product”. XP addresses that fear as well.
And, by the way, no other named Agile method addresses that fear. None. Not any.
But I digress.
Doing extended projects like the games I’ve been doing in Codea lets me work with a code base that gains capability as time passes, and whose initial design is not going to cut it for the long term. It lets me encounter the problems that a larger team will encounter in a larger effort, but at a scale where we can look at them, see what they entail, see how we deal with them.
The problems are the same. There are just more of them, and they are larger. What we learn here, under the microscope, applies to our real-world products and projects.
How do you know it’s the same?
I know it’s the same because I’ve worked on a lot of large efforts in my long career. I’ve worked on operating systems, language compilers, database systems, word processors, payroll software, tax software. And I’m sadly aware of the problems we had in those systems, and those are the same problems that we encounter here in our Dungeon, or in our Spacewar or Asteroids or Space Invaders programs.
It’s all the same. Just more of it.
So we’re studying what happens as we grow software–as we’re changing software–and figuring out what we can do about what happens.
Importantly, one of the things I’m learning, and I hope you are, is that no matter how hard we try, we cannot possibly get it right. We will make mistakes, and some of them will be big enough, and pervasive enough, that we may not ever be able to get around them.
We can’t ever do everything right, and sometimes even if you do everything right, you still lose. That’s the nature of life.
So we’re here to learn to see better and to work better, and maybe to fall in love with learning to see and work better.
You didn’t realize a little dungeon program could be that important, did you? But … maybe it is.
What Shall We Do Today?
We should do some actual work today. What should we do?
Is the Mimic doing everything we want? We’ve been in the middle of using it as a basis for changing animations as monsters change their activity and mood.
Here’s my list of what we want the Mimic to do, from a prior article:
- Hide triggers when the monster wakes up (and might trigger in reverse if it ever goes back to sleep).
- Walk triggers whenever the monster goes to a new square;
- Attack triggers right when the monster attacks the princess;
- Hurt triggers when the princess does damage to the monster;
- Dead triggers when the monster is knocked down.
- Idle runs most of the time when the monster is not hidden, subject to the above triggers.
Let’s review the code, which ought to tell us what it’s doing and when.
function MimicMonsterStrategy:selectMove(range)
if not self.monster.awake then
self.monster:setFirstFrameAnimation("hide")
return "basicDoNotMove"
end
if self.moveCount == 3 then
self.monster:setOneShotAndThenAnimation("hide", "idle")
end
self.moveCount = self.moveCount - 1
if self.moveCount <= 0 then
if range > 2 then
self.monster:setAnimation("idle")
return "basicDoNotMove"
else
self.monster:setAnimation("moving")
return "basicMoveTowardPlayer"
end
else
return "basicDoNotMove"
end
end
This is the main cycle. I think there’s also something about when it attacks.
function Monster:setAttackAnimation()
self:setOneShotAndThenAnimation("attack","idle")
end
That’s good. I don’t see us going back to idle after moving. I think we’d like to use the one-shot trick for moving. Can’t we “just” do this:
if self.moveCount <= 0 then
if range > 2 then
self.monster:setAnimation("idle")
return "basicDoNotMove"
else
self.monster:setOneShotAndThenAnimation("moving", "idle")
return "basicMoveTowardPlayer"
end
else
return "basicDoNotMove"
end
Let’s see how that works. I’ll patch in my code to make only Mimics.
That works as intended. However, I’m not sure he started moving toward me soon enough. Why? Because he starts when I’m 2 tiles away, so he is immediately right beside me. I’d prefer to stay one tile away if I immediately run.
Let’s change that conditional.
if self.moveCount <= 0 then
if range > 3 then
self.monster:setAnimation("idle")
return "basicDoNotMove"
else
self.monster:setOneShotAndThenAnimation("moving", "idle")
return "basicMoveTowardPlayer"
end
else
return "basicDoNotMove"
end
That should look better. Let’s see.
That’s good on the motion, I think, but now I don’t have time to get out of range. Let’s adjust that:
function MimicMonsterStrategy:init(monster)
self.monster = monster
self.moveCount = 4
end
function MimicMonsterStrategy:selectMove(range)
if not self.monster.awake then
self.monster:setFirstFrameAnimation("hide")
return "basicDoNotMove"
end
if self.moveCount == 4 then
self.monster:setOneShotAndThenAnimation("hide", "idle")
end
self.moveCount = self.moveCount - 1
if self.moveCount <= 0 then
if range > 3 then
self.monster:setAnimation("idle")
return "basicDoNotMove"
else
self.monster:setOneShotAndThenAnimation("moving", "idle")
return "basicMoveTowardPlayer"
end
else
return "basicDoNotMove"
end
end
Magic number there, need to fix that. But let’s see how it plays.
That’s better, and interesting. The good news is that we get a chance to back off quickly. That means that if we want to avoid fighting mimics, we can use the strategy of moving onto a chest and then backing away: the mimic is apparently near-sighted and will not see us, and will not follow to attack.
And we can stay away indefinitely even if it does follow us. We even have a chance of confusing it, if we can get an obstacle between ourselves and the mimic. It won’t be able to proceed and we can move beyond three cells, when it will therefore go into idle.
This is good stuff. Remove the “all mimics” hack, and commit.
No wait, let’s fix those magic numbers:
local MimicMovesToIgnorePlayer = 4
local MimicRangeToIgnorePlayer = 3
function MimicMonsterStrategy:init(monster)
self.monster = monster
self.moveCount = MimicMovesToIgnorePlayer
end
function MimicMonsterStrategy:selectMove(range)
if not self.monster.awake then
self.monster:setFirstFrameAnimation("hide")
return "basicDoNotMove"
end
if self.moveCount == MimicMovesToIgnorePlayer then
self.monster:setOneShotAndThenAnimation("hide", "idle")
end
self.moveCount = self.moveCount - 1
if self.moveCount <= 0 then
if range > MimicRangeToIgnorePlayer then
self.monster:setAnimation("idle")
return "basicDoNotMove"
else
self.monster:setOneShotAndThenAnimation("moving", "idle")
return "basicMoveTowardPlayer"
end
else
return "basicDoNotMove"
end
end
Now remove the hack and commit: Mimic ignores player beyond 3 tiles. Mimic idles between moves.
Now What?
We’ve made our program a bit better today, and even improved the code a bit. Nothing as significant as one might expect from the big buildup at the beginning of the article. But unless we have the mimic go back to hiding, we’ve completed the Mimic behavior cycle.
And, of course, there’s really nothing dedicated to the Mimic in that strategy. We could give that strategy to any monster and it would stand still until attacked. More to the point, though, we have completed a full cycle with our new animation logic that supports all those different animations. With that in hand, we could install more monsters that have those animations, and the game would be more interesting.
In fact, one of these first days, I’ll go look for some more creatures with those animations and see about installing them. In a real company, the Monster Art department would get to work.
Let’s review the sticky notes to see what we might want to do.
- Quality Check Behavior Tables
- I think this note meant to check the monster init tables for having all the necessary items. Probably a good idea, but not very exciting.
- Player startActionWithMonster
- That refers to this conditional code in that method:
function Player:startActionWithMonster(aMonster)
local name = aMonster:name()
if name ~= "Mimic" or (name == "Mimic" and aMonster.awake) then
self.runner:initiateCombatBetween(self,aMonster)
else
aMonster.awake = true
end
end
: Most monsters, we just initiate combat: that’s the intention we had when we bump them. In the case of the Mimic, it looks like a chest and we’re trying to open it. I’ll leave this one open. We do need something more general, but I think it’s too soon. The code isn’t calling out for a change yet.
- Death sounds should go in crawl
- This refers to making the monster (or player) make its death sound as triggered by the “is down” message coming up in the crawl. A quick test tells me that the sounds are coming out at the right time. Drop this one.
- Poison. Puzzles. Doors. Traps.
- These items are important. We’ll work on those next time, and I’m going to add one: Story. We need for there to be an underlying story, a kind of quest that we’re on. This will call for a bit of high-level planning and game design. Should be amusing.
- Pain when there are lots of tests
- Sure is. And in fact I have some tests turned off right now, because they take so long to run. I’ll turn them on now and see what happens. Only one test fails:
14: getmetatable returns the class -- AnimatorAnimation:127: need hit animation
: That one is intermittent. I believe it is due to occasionally putting two objects on the same tile. I’ll leave the tests running, so that they’ll irritate me enough to solve the speed problem. Commit: all tests turned on.
- Flickering Torches
- Decor possibility. Not very interesting.
- Monster Deterrent Objects
- Some kind of treasure that you can use to drive monsters away.
- Darkness
- Maybe we should make the dungeon darker, maybe you need a torch to see. Worth an experiment, perhaps.
- Gold and Store
- Maybe you should be able to find gold and spend it to get good stuff.
- Globals and missing names
- We have perennial issues with mistyped names. Perhaps there’s some better way to catch them. I have no great ideas at present.
- Rename _var names
- I briefly tried naming member variables with an underbar. I don’t even remember why. They don’t work well with Codea and didn’t help anyway. I could remove the few that exist.
- Combat Round grumbling
- CombatRound works fine, and recent changes have been easy. But it feels like it’s more complicated than it deserves. I just keep this one on the list in hopes of one day having a better idea.
- Draw needs generalizing esp Contents
- The draw logic is getting a bit ad-hoc, as various things are pulled out of the loop that draws contents. Could use some cleaning up. Might pay off slightly. Not very interesting.
- Game KIt
- I just added this today, Mihai’s idea of turning this project into a game kit that people could use to set up their own games. That could actually be an interesting effort as it would be working on the Making App, as Hill calls it, instead of the Shipping App.
That’s the full remaining list of items. Pretty clearly we need to work on the big stuff. I’d say we should try to devise a general Story for the game, a kind of Point To It All. Then a bit of overall dungeon design, puzzles, traps and so on, to give the game a richness.
And, for today, let’s wrap this up with a very quick summary:
Summary
Almost everything we set out to do goes in nicely. Once in a while I do manage to confuse myself, and once in a while something I set out to do doesn’t work and I have to revert, but I’m never stuck. It’s more that sometimes I’m mot very bright than that the program is resisting.
I think that’s a credit to the ideas that I’ve learned–if not devised–from six decades of programming and two decades of True Agile.
There is no substitute for experience, and most of my readers aren’t up to six decades yet, but we can have good learning experiences every day if we want to.
And I enjoy that, and commend the notion to your attention.
See you next time!