Dungeon 109
Some small cleanup items. Then … I don’t know, I’ll have to wait and see what I do.
When I play with the game on my other iPad, I notice three issues:
- The princess’s dot doesn’t show up on the mini-map;
- The Spikes tiles are being fetched from the wrong place;
- There’s another tile that that iPad can’t find.
So let’s fix those items.
The princess does not respond to draw
because she is drawn last, so that she’ll appear on top of monsters and other room contents, like spikes. In GameRunner, she was not being drawn explicitly in this function, but now that I’ve fixed it, she is:
function GameRunner:drawMap(tiny)
fill(0)
stroke(255)
strokeWidth(1)
for i,row in ipairs(self.tiles) do
for j,tile in ipairs(row) do
tile:draw(tiny)
end
end
self.player:drawExplicit(tiny)
end
Now she appears as a small red dot on the map, as intended. Commit: princess shows on small map.
The tiles for the spikes are defined incorrectly, this way:
function Spikes:init(tile, tweenDelay)
self.delay = tweenDelay or tween.delay
self.tile = tile
self.tile:addDeferredContents(self)
self.damageTable = { down={lo=1,hi=1}, up={lo=4,hi=7}}
self.assetTable = { down=asset.documents.Dropbox.trap_down, up=asset.documents.Dropbox.trap_up }
self:up()
self:toggleUpDown()
end
When I moved the assets into the project, I forgot to fix those references, and the ones in the unit tests.
function Spikes:init(tile, tweenDelay)
self.delay = tweenDelay or tween.delay
self.tile = tile
self.tile:addDeferredContents(self)
self.damageTable = { down={lo=1,hi=1}, up={lo=4,hi=7}}
self.assetTable = { down=asset.trap_down, up=asset.trap_up }
self:up()
self:toggleUpDown()
end
I’m reminded of something else I’d like to do with the spikes. When they’re down, they only do one point of damage. I don’t think the message should be “impale” in that case.
function Spikes:actionWith(player)
local co = CombatRound(self,player)
co:appendText("Spikes impale "..player:name().."!")
local damage = math.random(self:damageLo(), self:damageHi())
co:applyDamage(damage)
self.tile.runner:addToCrawl(co:getCommandList())
end
Let’s do this:
function Spikes:actionWith(player)
local co = CombatRound(self,player)
co:appendText("Spikes ".. self.verbs[self.state]..player:name().."!")
local damage = math.random(self:damageLo(), self:damageHi())
co:applyDamage(damage)
self.tile.runner:addToCrawl(co:getCommandList())
end
And this:
function Spikes:init(tile, tweenDelay)
self.delay = tweenDelay or tween.delay
self.tile = tile
self.tile:addDeferredContents(self)
self.damageTable = { down={lo=1,hi=1}, up={lo=4,hi=7}}
self.assetTable = { down=asset.trap_down, up=asset.trap_up }
self.verbs = {down="jab ", up="impale " }
self:up()
self:toggleUpDown()
end
That works as advertised. Commit: Spikes jab or impale, and use correct resources.
Now what’s that other one? It’s in Tile:
local TileWallMarker = AdjustedSprite(asset.documents.Dropbox.Torch_0,vec2(0.6,0.6))
That’s no longer referenced, and if we wanted torches, that’s not the one we’d use. Deleted. Commit: remove unused dropbox reference in Tile.
There’s another thing irritating me, and that’s the popup test results. In fact they’re irritating me in two ways.
First, the print is too large and they don’t all fit on the screen, and, second, they don’t time out fast enough on the iPad.
I change the fontsize
from 50 to 40. That’s just fine.
What makes them go away is this:
function draw()
--speakCodeaUnitTests()
if CodeaUnit and CodeaCount < CodeaCountMax then
CodeaCount = CodeaCount + 1
showCodeaUnitTests()
end
Runner:draw()
end
CodeaCountMax is 200, which is too fast on my fast iPad, and too slow on the slow one. Let’s change it so that the tests stay up until you tap the screen.
function setup()
if CodeaUnit then
codeaTestsVisible(true)
runCodeaUnitTests()
CodeaTestsVisible = true
end
local seed = math.random(1234567)
...
function draw()
--speakCodeaUnitTests()
if CodeaUnit and CodeaTestsVisible then
showCodeaUnitTests()
end
Runner:draw()
end
function touched(aTouch)
CodeaTestsVisible = false
Runner:touched(aTouch)
end
What might be even nicer would be to display them only when they are red or yellow, not when they’re green. Let’s do that.
Here’s how they decide color:
if Console:find("[1-9] Failed") then
stroke(255,0,0)
fill(255,0,0)
elseif Console:find("[1-9] Ignored") then
stroke(255,255,0)
fill(255,255,0)
else
fill(0,255,0, 100)
end
Let’s put similar code right into the display:
function draw()
--speakCodeaUnitTests()
if CodeaUnit and CodeaTestsVisible then
if Console:find("[1-9] Failed") or Console:find("[1-9] Ignored") then
showCodeaUnitTests()
end
end
Runner:draw()
end
That’s not great factoring, but we’re not here to update CodeaUnit, just bend it to our will.
I break a random test to check this one, and sure enough the red message comes back and won’t go away until I touch it. Commit: Test results show only when red or yellow, stay until touched.
So, some small but nice improvements. What else?
Map Enhancements?
I was thinking as I played with the game yesterday that if I were keeping a map manually, I’d mark interesting things on it, such as loot, chests, and the WayDown, if I happened to find it early on. (I generally do not pic up all the loot, as they apply immediately, so once I’m at max on some item, there’s no point grabbing it, and it might be useful later, at least if it’s a Health.
It seems to me that it’s at least worth looking to see what it would take to make items mark themselves on the mini-map when they’ve been seen.
After quite a bit of futzing, I’ve determined that the map is just too small to permit a decent display of even a tick mark where there are good things. Sorry, you’ll just have to remember until I get a better idea. Revert.
Starting Message by Level
Here’s something random. Let’s allow for unique opening messages by level. Presently the same original one plays at all levels.
That’s done by a call in createLevel
:
self.cofloater:runCrawl(self:initialCrawl())
Let’s pass in the level (yes, we have it as a member variable, but it’s better to make it a parameter).
function GameRunner:initialCrawl(level)
local co = CombatRound()
return { co:display("Welcome to the Dungeon."),
co:display("Here you will find great adventure,"),
co:display("fearsome monsters, and fantastic rewards."),
co:display("Some of you may die, but I am willing"),
co:display("to make that sacrifice.")}
end
How would we like to do this? We could just throw in a series of if statements, and that would work just fine. It might be better to make the messages an array of strings. Let’s do that:
function GameRunner:initialCrawl(level)
local co = CombatRound()
local msgs = self:crawlMessages(level)
local result = {}
for i,m in ipairs(msgs) do
table.insert(result, co:display(m))
end
return result
end
function GameRunner:crawlMessages(level)
return { "Welcome to the Dungeon.",
"Here you will find great adventure,",
"fearsome monsters, and fantastic rewards.",
"Some of you may die, but I am willing",
"to make that sacrifice."}
end
This should give me the current behavior. Yes.
Now we just need something a bit more robust.
function GameRunner:crawlMessages(level)
local msgs = { { "Welcome to the Dungeon.",
"Here you will find great adventure,",
"fearsome monsters, and fantastic rewards.",
"Some of you may die, but I am willing",
"to make that sacrifice."},
{ "This is the fearsome Segundo Level,",
"where the monsters are even more terrible,",
"and the rewards harder to earn.",
"This could be the end for you." },
{ "I am frankly amazed that you have made",
"it this far, as feeble and weak as you are.",
"I suppose you must be commended for luck,",
"if not for skill.",
"I suspect you'll not last much longer."},
{ "You have delved deeper here than we",
"could have imagined. There's no telling what",
"you may find, nor what creatures you may",
"encounter.",
"Prepare to die!"}}
if level > #msgs then level = #msgs end
return msgs[level]
end
No doubt in a real game we’d make these texts be come kind of resource, but for now this will work find. I managed to find a WayDown to double check that the new messages come out.
Commit: separate initial crawl for levels 1-4.
I think that’ll do for today.
Summary
Everything went rather smoothly today. What that tells us is that most of the code we encountered was “good enough”: it didn’t get in the way. Most of the places we edited were clear, and where we did pull out a bit of capability, such as converting the single initial crawl to a level-based one, a simple extract did the job for us.
That’s a good sign when the code doesn’t fight our changes. It’s not a sign of perfection by any means, but there’s a discernible difference when changes go in smoothly and when they don’t.
We were fortunate today, in that the things we chose to do were in areas where the code is decent. Well, no, that’s not giving ourselves enough credit. We’ve tried throughout to keep the code modular, single-purpose, and clear, and we’ve often succeeded. We do have some places of concern, notably GameRunner, but we’ve even made a little progress there.
I found myself wondering if we’ve learned all we can learn from this example, but I suspect that’s not the case. Here are a few things that should be “interesting”:
- Smarter monsters
- Weapons
- Armor
- Poison
- Better loot
- Ability to save loot and use later
- Bosses
- Doors and keys
And the biggie: puzzles. I think that one could be quite interesting to do. What one would like would be for the puzzle mechanism, whatever it is, to operate beneath the concern of the GameRunner and, well, everyone.
Think of the Spikes. They are an object of small but non-zero intelligence. They can jab and they can impale. We could certainly imagine another object on some other tile, such that when you step there, it tells all the Spikes to go up and stay up, or go down and stay down. Or a switch that opens a door, revealing a monster … or great treasure!
We’d like those things to behave in a sort of autonomous way, not checked and controlled by the GameRunner or other agency. They’d have their own “intelligence”.
So there are things to learn. And, of course, if we actually wanted the game to be enough fun to play, we’d need quite a bit of enhancement. I’m not sure that’s where we’re headed. We’re here to encounter interesting programming problems and solve them.
Or something.
See you next time!