Small Steps
Hello, loves!
OK, let’s learn how to do the thing we already did. I plan to go in Very Small Steps. The morning’s spike pays off, it seems to me.
I think we’ll start this time by trying to add a single sprite to the content sprite list, on command. Why? Because last time, adding a new sprite seemed not to work. So this time, we’ll focus there first.
We’ll do it on the K keystroke, as before. Just a lot less ambitiously.
class DungeonView:
...
elif symbol == arcade.key.K:
self.display_path()
...
def display_path(self):
my_resources = '/Users/ron/Desktop/DungeonTiles/png/objects/'
item = ContentFactory().decor(name="skel", resource=my_resources + 'Skeleton1.png', scale=0.5)
cell = Cell(31, 28)
self.make_view_and_sprite(cell, item)
Where did I get this new make_view_and_sprite method? I extracted it from the working method create_content_lists:
class DungeonView:
def create_content_lists(self):
for room in self.dungeon.rooms:
self.content_views_by_room[room] = []
self.content_views = dict()
self.content_sprite_list = arcade.SpriteList()
for cell, content in self.dungeon.contents.items():
for item in content:
self.make_view_and_sprite(cell, item)
def make_view_and_sprite(self, cell, item):
resources = item.resources
scale = item.scale
view = ContentView(cell, item, resources, scale)
view.sprite.position = cell.center_position(cell_size)
self.content_views[item] = view
self.content_views_by_room[cell.room].append(view)
self.content_sprite_list.append(view.sprite)
And this works. When I type ‘K’, I get a skull next to Dot, because she starts at (32, 28).

That’s promising. Makes me wonder what went wrong last time. Some error. Don’t know, don’t care. This is just what we wanted.
We add content in Dungeon:
class Dungeon:
def place_content_at(self, cell, content):
self.contents[cell].append(content)
If we had a method on Dungeon, callable from the layout, passing the layout as a parameter, Dungeon could add content and call back to have the View add it, using this new make_view_and_sprite method.
So in the K key, let’s call a method on Dungeon, show_path_to_item, passing the item name and the View.
class DungeonView:
elif symbol == arcade.key.K:
self.dungeon.show_path_to('a red key', self)
And in Dungeon build that method like the display_path we just wrote.
class Dungeon:
def show_path_to(self, item, layout):
my_resources = '/Users/ron/Desktop/DungeonTiles/png/objects/'
item = ContentFactory().decor(name="skel", resource=my_resources + 'Skeleton1.png', scale=0.5)
cell = Cell(31, 28)
self.place_content_at(cell, item)
layout.make_view_and_sprite(cell, item)
That continues to work. Let’s get a path.
def show_path_to(self, item_name, layout):
my_resources = '/Users/ron/Desktop/DungeonTiles/png/objects/'
item = ContentFactory().decor(name="skel", resource=my_resources + 'Skeleton1.png', scale=0.5)
path = self.find_path_to(item_name)
for cell in path:
self.place_content_at(cell, item)
layout.make_view_and_sprite(cell, item)
Basically, this is repeated Wishful Thinking. We’ll provide a trivial path:
def show_path_to(self, item_name, layout):
my_resources = '/Users/ron/Desktop/DungeonTiles/png/objects/'
item = ContentFactory().decor(name="skel", resource=my_resources + 'Skeleton1.png', scale=0.5)
path = self.find_path_to(item_name)
for cell in path:
self.place_content_at(cell, item)
layout.make_view_and_sprite(cell, item)
def find_path_to(self, item_name):
return [Cell(31, 28), Cell(31,27)]
Try that. As intended, we get two skulls:

Life is good. I think we should TDD the real find_path_to method. Just seems right.
I’m glad I made that decision. In the path tests, kind of by accident, I find these:
def test_find_path(self):
layout = DungeonLayout(5, 1)
map = '''1 2'''
origin = Cell(0,0)
target = Cell(4, 0)
path = layout.find_path(origin, target, layout.rooms)
assert path == [Cell(x,0) for x in reversed(range(5))]
def test_find_path_bigger(self):
layout = DungeonLayout(5, 5)
map = '''1 2'''
origin = Cell(0,0)
target = Cell(4, 0)
path = layout.find_path(origin, target, layout.rooms)
assert path == [Cell(x,0) for x in reversed(range(5))]
That tells me that if I can find the cell we want, we already have a method to build the path. A smarter man than I might have remembered that. I have source code and tests to do that sort of thing for me, and it often works.
So we want a method to find a cell whose content includes an item whose name we have. Dungeon has a dictionary from cell to a list of content. Let’s TDD against that first.
def test_find_cell_containing_name(self):
layout = DungeonLayout(10, 10)
dungeon = Dungeon(layout)
room_cell_1 = Cell(5, 5)
room_cell_2 = Cell(5, 6)
room = Room([room_cell_1, room_cell_2])
factory = ContentFactory()
c1 = factory.decor(name="no", resource='none', scale=0.5)
dungeon.place_content_at(room_cell_1,c1)
c2 = factory.decor(name="yes", resource='none', scale=0.5)
dungeon.place_content_at(room_cell_2,c2)
c3 = factory.decor(name="maybe", resource='none', scale=0.5)
dungeon.place_content_at(room_cell_2,c3)
found = dungeon.find_cell_containing('yes')
assert found == room_cell_2
I think this should be doable. We need the method:
def find_cell_containing(self, item_name):
cell = None
for candidate, items in self.contents.items():
for item in items:
if item.name == item_name:
cell = candidate
return cell
The test passes. Now back to the find_path_to method:
def find_path_to(self, item_name):
target = self.find_cell_containing(item_name)
if target:
path = self.layout.find_path(target, self.player_cell, self.rooms)
return path
else:
return []
This isn’t quite what we want, but it’s close. We really want accessible cells, not all rooms, but I found that the existing find_path wants rooms. This should, I believe, appear to work. I am nearly correct.

Not much of a surprise that it drew a straight line, because the find_path just picks cells in rooms. (What is that good for now?)
And only slight surprise that I had to walk around again to get the items illuminated. I don’t think we know which cells are illuminated. If we did we could use that info to illuminate our sprites.
But for now, we have acceptable progress to commit, summarize, and have a nice break. Commit: path to designated content in process.
Summary
OK. That’s nearly good. We have a reasonable flow of activity among view, dungeon, and layout. I think I prefer the callback to layout to using PubSub as we did this morning. Either should work, but PubSub should probably be considered deeper in the bag of tricks than just passing oneself to a method that can call us back.
The existing find_path in Layout is used to create paths between Suites of Rooms. We should create a better one for our purpose. I think ideally we’d add some intelligence to Flooder for the purpose.
We find the cell in question by searching content. We do not check to see if the cell in question is accessible, but if it isn’t, we’ll get no path. (And in terms of the game, that will often be a very serious error.)
It’s tempting to have cells be able to answer questions like “are you accessible from this other cell”, but while that would be easy enough to program, it would also involve running a flood fill, and those can be pretty costly. So we’ll hold off on that until we have a real demand.
I think illumination needs a semi-serious look. It seemed to make sense to just set the visible bit in the sprite, but that doesn’t seem to be quite the thing when you want to know later whether to illuminate something. And we don’t want to illuminate the breadcrumbs that are not in illuminated areas, so I think we have to know the situation cell by cell. No big deal, we’ll get there.
This morning’s spike informed my work this afternoon. I didn’t really follow the same path much at all. Instead, I was able to see steps that needed to be taken and to find places that made sense for placing those steps. That is exactly why we do a spike, and exactly why the common wisdom is to throw them away and to it all over. It seems always to turn out much better.
And next time, better still, I fondly hope. See you then!