Making: Dungeon 191
Let’s set some direction. I’m concerned that I’m off track on what is of value to work on. Possibly it’s residual fear from decades ago.
Here’s my concern. Most of the ideas that I have for a Codea Making App are a bit “out there”. Here’s the list I made a couple of articles ago. I think I’ll score the list items, HML, assessing how valuable each item would be to me, the prototypical Codea programmer.
- H What globals does the app create;
- H Given a method, what is its calling sequence;
- H Given a class, what other classes does it instantiate;
- M How many classes are there;
- M How many methods are there;
- M Where are the free functions; do they need better grouping;
- M Given a method, what classes implement it;
- M Given a partial method name, what methods match it;
- M What variables are used but never assigned;
- M What variables are assigned but never used;
- M What is the distribution of number of methods by class;
- M Find pattern in source code; (new)
- L How many free functions are there;
- L How many tests do we have;
- L How many expects do we have;
- L What is the distribution of method length;
Some of these can be done, moderately well, with Codea’s limited Find/Replace capability. (I am almost completely off Replace, since it has ravaged my code a couple of times. But Find is pretty safe and useful.)
The question I’m facing this Monday morning is whether there is enough fun and learning in these ideas. If there is, I’ll enjoy working on them and we may find some good lessons. If there isn’t, we’d all be better off doing something else.
I did install the GlobalAnalyzer class a day or so back. Let’s wire up a button for that.
GA = GlobalAnalyzer() parameter.action("NewGlobals", function() GA:printNewGlobals() end)
So that’s nice, and we can get some somewhat useful info. At game start, we see this:
New Globals Bus GA Runner
GA is our GlobalAnalyzer. Bus and Runner are no surprise. After playing a game we can press the button again. We get nothing printed. Great, the game isn’t creating spurious globals (for a change).
I noticed while playing that the Mimic won’t fight back if you don’t move away from it after bumping it open. It lets you hit it, and it animates, but it seems not to attack. Let’s commit our new global button and then look into that: Button to display new globals.
Here are some relevant bits of the
MimicMonsterStrategy = class() local MimicMovesToIgnorePlayer = 4 local MimicRangeToIgnorePlayer = 3 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
Well, thur’s yer problem raht thur. We don’t move until we have had enough turns to count down the moveCount, which gives the player time to dart away from the Mimic. But if she’s going to attack it, it shouldn’t just stand there. We need to let him free if she attacks.
When the player moves onto a monster, the TileArbiter sends the player
startActionWithMonster. We already have special action if we step onto a sleeping Mimic:
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
I think we need to send another message when we initiate combat, so that the Monster can be alerted to the situation. We could do that here, but perhaps we should do it in the initiate combat method.
function GameRunner:initiateCombatBetween(attacker, defender) if attacker:willBeDead() or defender:willBeDead() then return end CombatRound(attacker,defender):attack() end
We have an interesting situation here at present. Neither Monsters nor Payers know they are in combat. Instead, Monsters have a movement strategy that will move them toward the player, and if they are close enough to step on the player, that starts a combat round. And the Player can elect to try to move onto a Monster’s tile, and that’s an attack.
Let’s send a message from the initiate method to the defender. Hell, let’s send it to them both.
function GameRunner:initiateCombatBetween(attacker, defender) if attacker:willBeDead() or defender:willBeDead() then return end attacker:beginCombat() defender:beginCombat() CombatRound(attacker,defender):attack() end
Player will ignore this:
function Player:beginCombat() end
Monsters will inform their strategy that there is combat, if it knows the method:
function Monster:beginCombat() if self.strategy.beginCombat then self.strategy:beginCombat() end end
THIS IS A BIT OF HACKERY. WE MUST DISCUSS THIS.
function MimicMonsterStrategy:beginCombat() self.moveCount = 0 end
This should make the Mimic take offense when we attack it.
Yeah, well, that’s not the right member name. It’s _movementStrategy, thanks for that Jeffries.
function Monster:beginCombat() if self._movementStrategy.beginCombat then self._movementStrategy:beginCombat() end end
OK, that works as advertised. Commit: Mimic no longer tolerates immediate attacks.
Now what about that check to see if the strategy understands
beginCombat? We can remove it, at a certain cost, which is that all the monster strategies will have to understand (and generally ignore) the message. Is that better, or worse.
Better, because it’s invasive to check an object to see if it understand something. Worse, because it will add a constraint to all existing and future monster strategies, and it’s one that will not be obvious. Plus, it will add duplication.
We could give all those strategies a superclass, and implement
beginCombat there, and override it in the subclasses. Some of my colleagues tell me that’s a bad idea. Here chez Ronald, we allow it. Let’s see what it would be like:
MonsterStrategy = class() function MonsterStrategy:beginCombat() -- override to change monster state end
Now we make all the strategies inherit:
CalmMonsterStrategy = class(MonsterStrategy) NastyMonsterStrategy = class(MonsterStrategy) HangoutMonsterStrategy = class(MonsterStrategy) PathMonsterStrategy = class(MonsterStrategy) MimicMonsterStrategy = class(MonsterStrategy) function MimicMonsterStrategy:beginCombat() -- override self.moveCount = 0 end
And now we can remove that nasty check:
function Monster:beginCombat() self._movementStrategy:beginCombat() end
Test, commit: made beginCombat a default no-op in MonsterStrategy base class.
It has been a couple of hours of work, let’s sum up. We have a bit to discuss on this beginCombat thing.
I feel pretty good about the basic idea of sending
beginCombat to the attack and defender when a combat round starts. We just have the one case of an entity wanting to know but the message seems generic enough to just send it always. We ignore it in Player, but it’s there if we need it, and we send it to the monster’s movement strategy, unconditionally.
We had three ways of doing this:
- Send only to strategies that impllement the method;
- Send it to all strategies and implement in all strategies;
- Send to all, implement empty behavior in a (new) superclass, override as needed.
I chose door number 3, the override-if-you-want-it approach. Of course, to do it, I had to make a change to each of the strategy classes, to make it inherit from the new “abstract” superclass. So I didn’t exactly save edits, but I do save method count in each of those classes.
I will state right here that this is a questionable decision, one that each team needs to make for themselves, taking into account their level fo comfort with such things, the language they’re using and so on. For me, this is OK. I can tell you some smart folks who would disagree.
As for the “Making App” notion, we’ve got some code that will display classes and functions, and other code that will tell us for every tab what classes it defines and which classes it instantiates. We’ve got a button to count globals and keep track of new ones.
What we do not have, and I rather miss it, is a sense of what would really be valuable in our work. I got distracted by the class finding code, and learned some things. We got a nice little exercise of bringing legacy code under tests and under control. We may have gone too far on that but how do you know you’ve gone far enough unless sometimes you go too far.
But while it’s interesting, it’s not very valuable. I already knew there were some big and highly connected objects. No exciting new discoveries have come out of these tools.
And I got distracted by trying to devise ways of working on the Making App and the Shipping App “at the same time”, and I don’t really have much of a need to do that.
So I think that for the past five days I’ve been wandering even more than usual.
Probably, I should buckle down on the Learning Level, and see what new capabilities we really need.
Or I could do something else. If you have suggestions, let me know.
See you next time!