Calming the Bee Code
Hello, loves!
I entertain myself by seeing how to improve the
beeContentFactory method. Tout le monde déteste l’IA.
The bee is a bit of a mess:
class ContentFactory:
def bee(self, *, name, initial_sayings):
gift_sentences = [
'Oh, thank you!',
'I am most grateful!',
'Here\'s something you may need.',
]
initial_sayer = NameSayer.cycle(name, initial_sayings)
gift_sayer = NameSayer.once(name, gift_sentences)
satisfied_sentences = [
'I\'m just a happy little bee!',
'Hmmm, hmmm, just buzzin along.',
'Nothing to see here, just a bee'
]
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
gift = ContentFactory().receivable(name='delicious honeycomb',
resource='honeycomb.png',
scale=1)
knowledge = SimpleNamespace(
gift=gift,
gift_sayings=gift_sayer,
initial_sayings=initial_sayer,
satisfied_sayings=satisfied_sayer,)
the_bee = Denizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
We might ask ourselves why the initial sayings are a parameter and the others are not. In play, the bee is created in main:
def add_content(layout, dungeon):
factory = ContentFactory()
initial_sayings = ['Where is it?',
'Where can it be?',
'I lost my nice flower!',
'Please help me find my nice flower!']
bee = factory.bee(name='Buzz', initial_sayings=initial_sayings)
...
Certainly we want all the sayings in one place, and the conversions to NameSayer in one place. As written, the Denizen’s state methods know the desired names in the knowledge, gift, gift_sayings, and so on. And any kind of two-phase Denizen will have those same keys in their knowledge. So I think that the details of the messages, and the gift, belong in main more than they do in bee,
We can move the raw string tables to main and have four parameters in the bee code. I’m not sure. Let’s make more of a mess and then clean it up. I’ll move the initial_sayings out of main and into bee. Then a bit of reordering and a rename for consistency and we have:
class ContentFactory:
def bee(self, *, name):
initial_sentences = ['Where is it?',
'Where can it be?',
'I lost my nice flower!',
'Please help me find my nice flower!']
gift_sentences = [
'Oh, thank you!',
'I am most grateful!',
'Here\'s something you may need.',
]
satisfied_sentences = [
'I\'m just a happy little bee!',
'Hmmm, hmmm, just buzzin along.',
'Nothing to see here, just a bee'
]
initial_sayer = NameSayer.cycle(name, initial_sentences)
gift_sayer = NameSayer.once(name, gift_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
gift = ContentFactory().receivable(name='delicious honeycomb',
resource='honeycomb.png',
scale=1)
knowledge = SimpleNamespace(
initial_sayings=initial_sayer,
gift_sayings=gift_sayer,
gift=gift,
satisfied_sayings=satisfied_sayer,
)
the_bee = Denizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
Let’s Extract Method the middle of that, the part that makes the knowledge from the messages. I’ll move the gift part up before the extract.
class ContentFactory:
def bee(self, *, name):
initial_sentences = ['Where is it?',
'Where can it be?',
'I lost my nice flower!',
'Please help me find my nice flower!']
gift_sentences = [
'Oh, thank you!',
'I am most grateful!',
'Here\'s something you may need.',
]
satisfied_sentences = [
'I\'m just a happy little bee!',
'Hmmm, hmmm, just buzzin along.',
'Nothing to see here, just a bee'
]
gift = ContentFactory().receivable(name='delicious honeycomb',
resource='honeycomb.png',
scale=1)
knowledge = self.create_knowledge(name, initial_sentences, gift_sentences, satisfied_sentences, gift)
the_bee = Denizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
def create_knowledge(self, name, initial_sentences, gift_sentences, satisfied_sentences, gift):
initial_sayer = NameSayer.cycle(name, initial_sentences)
gift_sayer = NameSayer.once(name, gift_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
return SimpleNamespace(
initial_sayings=initial_sayer,
gift_sayings=gift_sayer,
satisfied_sayings=satisfied_sayer,
gift=gift,
)
Now the bee code knows what names it wants and what kind of NameSayers are needed, which is true for pretty much any two-phase Denizen.
Let’s move the four initial assignments over to main and make them parameters here. I’m not entirely sure about this move.
I’m aware that I haven’t committed any of this. Let’s do: refactoring bee. And now:
main.py
def add_content(layout, dungeon):
factory = ContentFactory()
initial_sentences = ['Where is it?',
'Where can it be?',
'I lost my nice flower!',
'Please help me find my nice flower!']
gift_sentences = [
'Oh, thank you!',
'I am most grateful!',
'Here\'s something you may need.',
]
satisfied_sentences = [
'I\'m just a happy little bee!',
'Hmmm, hmmm, just buzzin along.',
'Nothing to see here, just a bee'
]
gift = ContentFactory().receivable(name='delicious honeycomb',
resource='honeycomb.png',
scale=1)
bee = factory.bee(name='Buzz',
initial_sentences=initial_sentences,
gift_sentences=gift_sentences,
satisfied_sentences=satisfied_sentences,
gift=gift,
)
cell = Cell(33, 25)
cell.add_content(bee)
...
class ContentFactory:
def bee(self, *, name, initial_sentences, gift_sentences, satisfied_sentences, gift):
knowledge = self.create_knowledge(name, initial_sentences, gift_sentences, satisfied_sentences, gift)
the_bee = Denizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
def create_knowledge(self, name, initial_sentences, gift_sentences, satisfied_sentences, gift):
initial_sayer = NameSayer.cycle(name, initial_sentences)
gift_sayer = NameSayer.once(name, gift_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
return SimpleNamespace(
initial_sayings=initial_sayer,
gift_sayings=gift_sayer,
satisfied_sayings=satisfied_sayer,
gift=gift,
)
Test this. Works in game. Need better tests for bee, I think. Commit. Let’s reflect.
Reflection
There isn’t really much about a bee in the bee method now: only the resource name, arguably the scale. That suggests to me that there is a generic aspect to this, what I’ve been tending to call the two-phase denizen, and a specific aspect, the input sentences and gift, which are provided by main … and the resource info, resources and scale. Our names don’t reflect the actual situation as well as they might:
Denizen is really a TwoPhaseDenizen or a QuestGiver, perhaps. The names inside bee aren’t that bee-like at all. And … this is a bit harder to see … I think we could use some kind of “resource descriptor” that includes the resource name and scale.
And it occurs to me that the initial_sentences are perhaps better called seeking_sentences, and maybe gift_sentences should be giving_sentences. Let’s do those two renames.
class ContentFactory:
def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift)
the_bee = Denizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
def create_knowledge(self, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
seeking_sayer = NameSayer.cycle(name, seeking_sentences)
giving_sayer = NameSayer.once(name, giving_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
return SimpleNamespace(
initial_sayings=seeking_sayer,
gift_sayings=giving_sayer,
satisfied_sayings=satisfied_sayer,
gift=gift,
)
So far so good. Renaming things in the SimpleNamespace will break the Denizen code but it should be easily fixed up.
def create_knowledge(self, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
seeking_sayer = NameSayer.cycle(name, seeking_sentences)
giving_sayer = NameSayer.once(name, giving_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
return SimpleNamespace(
seeking_sayings=seeking_sayer,
giving_sayings=giving_sayer,
satisfied_sayings=satisfied_sayer,
gift=gift,
)
I’m not sure if it fixed up Denizen or not. It did not. I can do it. A few tests need fixing to the new expected names in the namespace.
Reflection
If we were to create a custom class where we now use a SimpleNamespace, I think PyCharm could do a better job of refactoring. We might try that soon.
What Next?
Well, commit, for one thing. Now, let’s rename Denizen to QuestGiverDenizen. Commit.
class QuestGiverDenizen:
def __init__(self, *, name,
knowledge,):
self.name = name
self.knowledge = knowledge
self.state = self.seeking
def interact(self, interactor):
self.state = self.state(interactor)
return False
def seeking(self, interactor):
if interactor.has('a flower'):
if self.knowledge.gift:
for saying in self.knowledge.giving_sayings:
interactor.announce(saying)
interactor.receive_content(self.knowledge.gift)
return self.satisfied
else:
interactor.announce(next(self.knowledge.seeking_sayings))
return self.seeking
def satisfied(self, interactor):
interactor.announce(next(self.knowledge.satisfied_sayings))
return self.satisfied
Ha. Interesting how, as we simplify we notice more things. We have a constant quest item ‘a flower’. The string is sufficient, because the check just looks to see if there is an item in inventory with that name. Let’s put it into the knowledge as quest_item.
class ContentFactory:
def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
'a flower')
the_bee = QuestGiverDenizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
def create_knowledge(self, name, seeking_sentences, giving_sentences, satisfied_sentences, gift, quest_item):
seeking_sayer = NameSayer.cycle(name, seeking_sentences)
giving_sayer = NameSayer.once(name, giving_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
return SimpleNamespace(
quest_item=quest_item,
seeking_sayings=seeking_sayer,
giving_sayings=giving_sayer,
satisfied_sayings=satisfied_sayer,
gift=gift,
)
And, of course:
class QuestGiverDenizen:
def __init__(self, *, name,
knowledge,):
self.name = name
self.knowledge = knowledge
self.state = self.seeking
def interact(self, interactor):
self.state = self.state(interactor)
return False
def seeking(self, interactor):
if interactor.has(self.knowledge.quest_item):
if self.knowledge.gift:
for saying in self.knowledge.giving_sayings:
interactor.announce(saying)
interactor.receive_content(self.knowledge.gift)
return self.satisfied
else:
interactor.announce(next(self.knowledge.seeking_sayings))
return self.seeking
Some tests need updating, same old same old. Green. Test in game just for the thrill. Green, commit.
Let’s sum up. I’m wanting some relaxation.
Summary
Now the QuestGiverDenizen is completely independent of any particular quest item, gift, or messages. It handles any two-phase QuestGiver that we might want to devise. This is pleasing.
The bee method in ContentFactory, with its supporting method, isn’t quite right yet:
class ContentFactory:
def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
'a flower')
the_bee = QuestGiverDenizen(name=name, knowledge=knowledge)
info = SimpleNamespace(bee=the_bee)
def bee_behavior(self, interactor):
result = self.info.bee.interact(interactor)
return result
return Content(name=name, resources=['bee.png'],
scale=0.5, interaction=bee_behavior,
info=info)
def create_knowledge(self, name, seeking_sentences, giving_sentences, satisfied_sentences, gift, quest_item):
seeking_sayer = NameSayer.cycle(name, seeking_sentences)
giving_sayer = NameSayer.once(name, giving_sentences)
satisfied_sayer = NameSayer.random(name, satisfied_sentences)
return SimpleNamespace(
quest_item=quest_item,
seeking_sayings=seeking_sayer,
giving_sayings=giving_sayer,
satisfied_sayings=satisfied_sayer,
gift=gift,
)
I don’t love that long parameter list, and we should probably be passing in a resource ID of some kind, making the bee method more generic, more of a quest giver builder than a bee builder. And I think we should look at using an actual tiny class rather than a SimpleNamespace, because we could probably get better type checking.
All that will be for another time. For this afternoon, we’ve made things a bit better, yet again.
See you next time!