Inventory Improvement
Hello, loves!
Made it work. Now let’s make it right. Yummy! Tout le monde déteste l’IA.
The problem we’ve solved, whose solution we’ll be improving, is the creation of sprites for Content instances that are not in the dungeon when it’s created but that will turn up later. Our sole example is the honeycomb that Buzz Bee gives to Dot when Dot shows up with the right flower.
The general idea behind the solution is that when the KeyedSpriteListMaker is processing dungeon contents (Buzz is a kind of Content), the KSLM asks the Content instance for contained_items, expecting a list of other Content instances, and it makes sprites for those and indexes them into the KeyedSpriteList.
The solution in place is clean on the outside, and dirty on the inside. Outside the code is pretty reasonable:
class KeyedSpriteListMaker:
def create_content_lists(self, dungeon, keyed_sprites):
for cell, content in dungeon.layout.contents.items():
for item in content:
keyed_sprites.make_visible_content_sprite(item, cell)
for contained_item in item.contained_items():
sprite = ContentSpriteMaker(contained_item.resources, contained_item.scale / 16).sprite
sprite.visible = False
keyed_sprites.add_content(contained_item, None, sprite)
We just fetch the contained_items and create a sprite for each one. We might find some duplication there to fix up, but all the real nastiness is inside contained_items:
class Content:
def contained_items(self):
if self.info is not None \
and 'denizen' in vars(self.info) \
and 'gift' in vars(self.info.denizen.knowledge):
return [self.info.denizen.knowledge.gift]
else:
return []
That code is digging down into the specific structure of the QuestGiverDenizen, the only one that presently has extra items to be spritified. That won’t do.
The new scheme will be to add an instance variable to the Content instance, containing the contained_items that one wishes to have sprited, and empty otherwise. I think we’ll do this by Wishful Thinking again. We’ll change the init:
class Content:
def __init__(self, *, name, resources, scale,
interaction=lambda self, interactor: True,
info=None,
subs=None):
We change to this:
class Content:
def __init__(self, *, name, resources, scale,
interaction=lambda self, interactor: True,
info=None,
contained_items=(),
subs=()):
self.name = name
self.resources = resources
self.scale = scale
self.state = 0
self.info = info
self.contained_items = contained_items
self.subs = subs
self.interact_with_player = types.MethodType(interaction, self)
self.dungeon = None
And then when we create the Content for the QuestGiverDenizen, we change this:
class ContentFactory:
def quest_giver(self, *, name, quest_item,
seeking_sentences, giving_sentences, satisfied_sentences,
gift, resources, scale):
def interaction(self, interactor):
event = 'has_item' if interactor.has(quest_item) else 'no_item'
self.info.denizen.event(event, interactor)
return False
knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
quest_item)
denizen = QuestGiverDenizen(knowledge=knowledge)
info = SimpleNamespace(denizen=denizen)
return Content(name=name, resources=resources,
scale=scale, interaction=interaction,
info=info)
We “just” need to pass the gift into the Content creator:
class ContentFactory:
def quest_giver(self, *, name, quest_item,
seeking_sentences, giving_sentences, satisfied_sentences,
gift, resources, scale):
def interaction(self, interactor):
event = 'has_item' if interactor.has(quest_item) else 'no_item'
self.info.denizen.event(event, interactor)
return False
knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
quest_item)
denizen = QuestGiverDenizen(knowledge=knowledge)
info = SimpleNamespace(denizen=denizen)
return Content(name=name, resources=resources,
scale=scale, interaction=interaction,
contained_items=(gift,),
info=info)
And I expect this to work. My expectations are not met because we are expecting a method contained_items(). Let’s stick with that and do this:
class Content:
def __init__(self, *, name, resources, scale,
interaction=lambda self, interactor: True,
info=None,
contained_items=(),
subs=()):
self.name = name
self.resources = resources
self.scale = scale
self.state = 0
self.info = info
self._contained_items = contained_items
self.subs = subs
self.interact_with_player = types.MethodType(interaction, self)
self.dungeon = None
def contained_items(self):
return self._contained_items
I could imagine a property for that but we don’t want to change too much at one go. Try again.

Perfect! Now, any Content instance can have additional Content instances inside itself, for any purpose, and sprites will be provided for its use. And now the code is clean inside and just the same as it was outside.
Let’s sum up.
Summary
I find the process of making something work and then making the code better (and better rinse repeat) gives me usable results earlier than my former practice of not releasing anything until I got everything right, or they threatened to fire me.
I do not see this as a license to abject hackery. The first thing I tried did the work inside the KeyedSpriteListMaker, and while that was suitable at least for an experiment to get things on the screen, it was the wrong code in the wrong place. So we moved the code to the right place, and replaced the try/except version with a more tentative look and see if we can do this version, which was still grievously ad hoc and nasty, but was at least all inside Content.
Then, this afternoon, we moved the data into an instance variable and initialized that variable when we created the bee.
I think we’re in good shape overall, except that there is probably some duplication to be found in KeyedSpriteListMaker. We might look for that in an upcoming session, or we might find it more fruitful to look into our scaling issues, or even to move on to populating the dungeon with more interesting things that can show up in inventory.
Bit by bit, small step by small step, we improve the code as we grow the capability. Yummy.
See you next time!