Hello, loves!

Regrettably awake at 0445, I look for something to code. Tout le monde déteste l’IA.

It happens. Probably shouldn’t have eaten that granola bar right before bed. Anyway, I’m a morning person so let’s see what we can do about our QuestGiver and the bee version thereof.

Like everything in the Dungeon except Dot (who is thus far nothing at all), Buzz the Bee is a kind of Content:

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,
        )

We may have to add another QuestGiverDenizen to tease apart generality from specifics, but I think there are things we can do without that. Let’s look at another couple of Content constructors:

    def button(self, *, name):
        def button(self, interactor):
            self.state = (self.state+1)%len(self.resources)
            interactor.publish('control', self.name, state=self.state)
            interactor.publish('state_number', self.name, content=self, state=self.state)
            return True
        resource1 = 'floor button/Button (1).png'
        resource2 = 'floor button/Button (2).png'
        resources = [resource1, resource2]
        scale = 0.75
        return Content(name=name,
                       resources=resources,
                       scale=scale,
                       interaction=button)

    def receivable(self, *, name, resource, scale):
        def interaction(self, interactor):
            interactor.receive_content(self)
            return True
        return Content(name=name,
                       resources=[resource],
                       scale=scale,
                       interaction=interaction)

Let’s compare those two. The major difference that I see is that the interaction parameter in receivable is named interaction, and in button it is named button. In the bee, it’s named bee_behavior. I’m leaning toward standardizing on interaction, since in every case the name is local to the method and has no need to have a specialized name. Let’s try that in bee. And let’s always put it first.

class ContentFactory:
    def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
        def interaction(self, interactor):
            return self.info.bee.interact(interactor)
        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)
        return Content(name=name, resources=['bee.png'],
                       scale=0.5, interaction=interaction,
                       info=info)

I inlined the result as well. Let’s also name the thing we interact with denizen, both the local value and the key in the info.

class ContentFactory:
    def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
        def interaction(self, interactor):
            return self.info.denizen.interact(interactor)
        knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                                          'a flower')
        denizen = QuestGiverDenizen(name=name, knowledge=knowledge)
        info = SimpleNamespace(denizen=denizen)
        return Content(name=name, resources=['bee.png'],
                       scale=0.5, interaction=interaction,
                       info=info)

    def create_knowledge(self, name, seeking_sentences, giving_sentences, satisfied_sentences, gift, quest_item):
        ...

Commit that: refactoring.

There are three things in that method still specific to bee: the quest_item, the string a flower, the resource file name, and the scale. I am hating the long parameter list but since all the parameters follow the *, they are required and known to PyCharm and the compiler, so we can’t forget them. But what might we do?

The QuestGiverDenizen always requires those three kinds of sentences, so we could pass those in as some kind of object …

I was just reading some Python documentation about the types module, and it reminded me of the namedtuple, which is similar to SimpleNamespace but actually defines a type. There seems to be a leaning toward using namedtuple over SimpleNamespace. A bit more digging and I see there is also typing.NamedTuple, which allows type hinting in the definition. I don’t always use type hinting but on some days I rather like it.

I’m not ready to package up the sentences separately from the rest. I think there may come to be a more general kind of state machine than the very specific one we have in QuestGiverDenizen, but today is not that day and that day may never come. I do think we should have a little Resource object, with a list of image file names and a scale. But not yet.

Let’s work toward a ContentFactory general method for a QuestGiver, and have the bee, call it. To do that, first we’ll extract three variables and move them to the top.

class ContentFactory:
    def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
        quest_item = 'a flower'
        resources = ['bee.png']
        scale = 0.5
        def interaction(self, interactor):
            return self.info.denizen.interact(interactor)

        knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                                          quest_item)
        denizen = QuestGiverDenizen(name=name, knowledge=knowledge)
        info = SimpleNamespace(denizen=denizen)
        return Content(name=name, resources=resources,
                       scale=scale, interaction=interaction,
                       info=info)

Now extract a method quest_giver:

class ContentFactory:
    def bee(self, *, name, seeking_sentences, giving_sentences, satisfied_sentences, gift):
        quest_item = 'a flower'
        resources = ['bee.png']
        scale = 0.5
        return self.quest_giver(name, quest_item, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                                resources, scale)

    def quest_giver(self, name, quest_item, seeking_sentences, giving_sentences, satisfied_sentences, gift, resources,
                    scale):
        def interaction(self, interactor):
            return self.info.denizen.interact(interactor)

        knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                                          quest_item)
        denizen = QuestGiverDenizen(name=name, knowledge=knowledge)
        info = SimpleNamespace(denizen=denizen)
        return Content(name=name, resources=resources,
                       scale=scale, interaction=interaction,
                       info=info)

Now, we could go in at least two ways. One, we could have main just produce a quest_giver, or we could have this helper method bee now include all the details for bee, and just call the quest_giver.

Let’s see what it would look like in main. Right now, the whole bee creation is inline in add_content:

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', seeking_sentences=initial_sentences, giving_sentences=gift_sentences,
                      satisfied_sentences=satisfied_sentences, gift=gift)
    cell =  Cell(33, 25)
    cell.add_content(bee)
    item = factory.receivable(name="a red key", resource='keyRed.png', scale=0.5)
    cell = Cell(29, 28)
    cell.add_content(item)
    ...

Extract in main, add_bee:

def add_content(layout, dungeon):
    factory = ContentFactory()
    add_bee(factory)
    item = factory.receivable(name="a red key", resource='keyRed.png', scale=0.5)
    cell = Cell(29, 28)
    cell.add_content(item)

def add_bee(factory):
    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', seeking_sentences=initial_sentences, giving_sentences=gift_sentences,
                      satisfied_sentences=satisfied_sentences, gift=gift)
    cell = Cell(33, 25)
    cell.add_content(bee)

Change this to call quest_giver. bring over the code from ContentFactory to do it:

def add_bee(factory):
    seeking_sentences = ['Where is it?',
                         'Where can it be?',
                         'I lost my nice flower!',
                         'Please help me find my nice flower!']
    giving_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)
    quest_item = 'a flower'
    resources = ['bee.png']
    scale = 0.5
    bee = factory.quest_giver("Buzz", quest_item, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                            resources, scale)
    cell = Cell(33, 25)
    cell.add_content(bee)

This works. Commit.

I think I’d like that quest_giver method to require keyword parameters. Can ChangeSignature do that? It can. Super!

main.py
def add_bee(factory):
    seeking_sentences = ['Where is it?',
                         'Where can it be?',
                         'I lost my nice flower!',
                         'Please help me find my nice flower!']
    giving_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)
    quest_item = 'a flower'
    resources = ['bee.png']
    scale = 0.5
    bee = factory.quest_giver(name="Buzz", quest_item=quest_item, seeking_sentences=seeking_sentences,
                              giving_sentences=giving_sentences, satisfied_sentences=satisfied_sentences, gift=gift,
                              resources=resources, scale=scale)
    cell = Cell(33, 25)
    cell.add_content(bee)

class ContentFactory:
    def quest_giver(self, *, name, quest_item, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                    resources, scale):
        def interaction(self, interactor):
            return self.info.denizen.interact(interactor)

        knowledge = self.create_knowledge(name, seeking_sentences, giving_sentences, satisfied_sentences, gift,
                                          quest_item)
        denizen = QuestGiverDenizen(name=name, knowledge=knowledge)
        info = SimpleNamespace(denizen=denizen)
        return Content(name=name, resources=resources,
                       scale=scale, interaction=interaction,
                       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,
        )

It’s 0616 hours. Let’s sum up, and maybe I can get a few Z’s in.

Summary

What we have now is better, I would say, in these regards:

  1. We have a general-purpose quest-giving Denizen, which can be fed any set of things to say, a thing to look for in Dot’s inventory, and a gift to give her.
  2. We have a general-purpose ContentFactory method for making a Content item containing a quest denizen of any kind.
  3. That method is itself in two parts, with a method that it calls to build the knowledge component that the denizen needs.

What I am troubled by includes:

  1. There are eight parameters in the quest_giver method, and six in the create_knowledge.
  2. The construction of our bee quest_giver is 26 lines long, and it’s not even well folded.
  3. The whole package is about 50 lines of code, and every new quest giver will add another 25 or so.

Why I’m not terribly troubled includes:

  1. Of the 26 lines in the bee creation, over 20 are unique to the bee, either as its script, or specific image names and scale and such.
  2. If and when we see opportunities to make it simpler or easier, we’ll surely be able to put them in without great difficulty.

I’ll publish this later in the morning. See you soon!