More Content
I have a bit of time at the keyboard. Let’s improve the dungeon contents code a bit.
Let’s allow for more than one content item. We can do that with a test, I think.
def test_contents_is_collection(self):
layout = DungeonLayout(10, 10)
room_cell = layout.at(5, 5)
room = Room([room_cell])
layout.add_room(room)
dungeon = Dungeon(layout)
dungeon.place_content_at(room_cell,"treasure")
dungeon.place_content_at(room_cell,"more treasure")
contents = dungeon.contents_at(room_cell.xy)
assert len(contents) == 2
I don’t like that I need this much setup for the test. We’ll think of something to do about that. First make it work.
I think this will break DungeonView, and that reminds me that our original test doesn’t test very much:
def test_place_content(self):
layout = DungeonLayout(10, 10)
room_cell = layout.at(5, 5)
room = Room([room_cell])
layout.add_room(room)
dungeon = Dungeon(layout)
dungeon.place_content_at(room_cell,"treasure")
I think we’ll probably just let that one get removed, the second one will do the job.
But I think we want more than just a list. We’ll see…
class Dungeon:
def __init__(self, layout):
self.layout = layout
self.player_cell = None
self.contents = defaultdict(list)
def place_content_at(self, cell, content):
self.contents[cell.xy].append(content)
def contents_at(self, cell):
return self.contents[cell.xy]
Tests pass but this won’t really do. Let’s first fix up DungeonView to work, and maybe we can see how to test it so that when we change this again we’ll be testing the display. Here it is now:
def draw_contents(self):
adventurer_cell = self.dungeon.player_cell
cx = adventurer_cell.x*cell_size + cell_size//2
cy = adventurer_cell.y*cell_size + cell_size//2
arcade.draw_circle_filled(cx, cy, cell_size//2, arcade.color.RED)
for k, v in self.dungeon.contents.items():
x,y = k
cx = x*cell_size + cell_size//2
cy = y*cell_size + cell_size//2
arcade.draw_circle_filled(cx, cy, cell_size//2, arcade.color.GREEN)
I want a list to come back and I want it to be empty if there are no contents in the specified cell, but as things stand, we’ll be creating empty lists for each cell, which seems somewhat wasteful. We want this code to use contents_at, and to iterate that list.
No. In fact this is in the wrong place. I don’t think we want to draw contents at the dungeon level at all: We should draw it when we draw the individual cell. That happens in RoomView, I think:
No. I had forgotten. We put all the RoomView results into the ShapeList that is drawn rapidly all at once. I think for now we’ll do the contents here, much as the code above does it. Fix it up:
def draw_contents(self):
adventurer_cell = self.dungeon.player_cell
cx = adventurer_cell.x*cell_size + cell_size//2
cy = adventurer_cell.y*cell_size + cell_size//2
arcade.draw_circle_filled(cx, cy, cell_size//2, arcade.color.RED)
for coords, content in self.dungeon.contents.items():
x,y = coords
cx = x*cell_size + cell_size//2
cy = y*cell_size + cell_size//2
for item in content:
arcade.draw_circle_filled(cx, cy, cell_size//2, arcade.color.GREEN)
Of course this isn’t good for handling more than one item, at least not yet. We need a ContentItem that can draw itself.
Change the test to require that. I go a bit wild and have this test:
def test_contents_is_collection(self):
layout = DungeonLayout(10, 10)
room_cell = layout.at(5, 5)
room = Room([room_cell])
layout.add_room(room)
dungeon = Dungeon(layout)
item_1 = ContentItem("treasure")
shape = [(0.33, 0.66), (0.66, 0.66), (0.5, 0.33)]
item_2 = ContentItem("more treasure")
dungeon.place_content_at(room_cell,item_1)
dungeon.place_content_at(room_cell,item_2)
contents = dungeon.contents_at(room_cell)
assert len(contents) == 2
With this little class:
class ContentItem:
def __init__(self, name, shape=None):
self.name = name
if shape:
self.shape = shape
else:
self.shape = [(0.5,0.66), (0.66, 0.33),(0.33, 0.33)]
def draw(self, cx, cy, size):
sized = [self.size_point(pair, cx, cy, size) for pair in self.shape]
arcade.draw_polygon_outline(sized, arcade.color.GREEN)
def size_point(self, point, cx, cy, size):
x,y = point
return cx-size//2+x*size, cy-size//2+y*size
The cryptic size_point converts the input point, which is the center of the tile, to the lower left, so that my hand-crafted triangles come out centered. Messy but it’s just a rope across the chasm, we’ll make a bridge of it later. In DungeonView:
def draw_contents(self):
adventurer_cell = self.dungeon.player_cell
ax = adventurer_cell.x*cell_size + cell_size//2
ay = adventurer_cell.y*cell_size + cell_size//2
arcade.draw_circle_filled(ax, ay, cell_size//2, arcade.color.RED)
for coords, content in self.dungeon.contents.items():
x,y = coords
cx = x*cell_size + cell_size//2
cy = y*cell_size + cell_size//2
for item in content:
item.draw(cx, cy, size=cell_size)
We call back to the item to draw itself, and we get what was intended:

That’ll do for a quick shot this afternoon. Let’s reflect.
Reflection
We have more objects in nearly the right place. Content is in the Dungeon, which seems sensible for now. A content item can draw itself. It’s probably just an abstract class or duck-typed thing that can understand how to draw but we’ll see.
I think we need to sort out whether we’re going to focus on lower left corner or center throughout. We’ll want the items to draw a texture, probably, and whatever they do they should return some kind of item that we can store in a sprite list so as to draw them as a batch. Drawing them one at a time will work but it is inefficient and not much harder to make the list. Might even be easier: we’ll see.
A nice little step toward something we want. I stumbled a bit, mostly due to confusion between center and corner, but it all went nicely.
I love little steps like this. Can start any time, stop just about any time, do a little, then wander off. Life is good.
See you next time!