The Repo on GitHub

We have adjusted can_drop to check scent. Let’s do can_take. Should be much the same. I’ll force myself to test this one-line change.

By giving the can_take method a scent threshold, I hope to reduce the pecking away at large clumps just a bit. In particular, there is one configuration where I would like to avoid a pickup:

_BB
_BB
_R_

In this situation, a Bot facing North sees just _BB, and the pattern for can_take allows taking any block you see that has space on at least one side. The Bot cannot see that there are two more blocks right behind the ones it does see, and the result is that a Bot can peck away at the corner of a clump. I’d like to fix that. Let’s consider the scent of that pattern:

021
032
000

The scent, if that is all that’s there, is 8. Let’s not pick up unless the scent is 7 or lower, and see what happens.

Let’s begin with a couple of tests.

    def test_knowledge_take_decision(self):
        location = Location(10, 10)
        direction = Direction.NORTH
        knowledge = Knowledge(location, direction)
        knowledge.vision = [('B', 10, 9)]
        knowledge.scent = Knowledge.take_threshold
        assert knowledge.can_take

    def test_knowledge_take_decision_high_scent(self):
        location = Location(10, 10)
        direction = Direction.NORTH
        knowledge = Knowledge(location, direction)
        knowledge.vision = [('B', 10, 9)]
        knowledge.scent = Knowledge.take_threshold + 1
        assert not knowledge.can_take

I retrofitted the first test from an existing one, and then added the second one to check the case where the scent is too high. Those both fail, of course, because there is no threshold. I’ll set that value:

class Knowledge:
    drop_threshold = 4
    take_threshold = 7

Now one passes and the other fails. Fix up can_take:

    @property
    def can_take(self):
        is_scent_ok = self.scent <= self.take_threshold
        return is_scent_ok and self.vision.match_forward_and_one_side('B', '_')

    @property
    def can_drop(self):
        is_scent_ok = self.scent >= self.drop_threshold
        return is_scent_ok and self.vision.match_forward_and_one_side('_', 'B')

Tests are green. Run the game, see what happens. After some time running, here is what we see:

three pretty solid clumps

We have three pretty solid clumps there, and at the moment, no bots carrying a block. Once in a while a bot will find a block that it can pick off, carries it around a while, then drops it somewhere. I have the game running at 10 cycles every tenth of a second, so I’d have to slow it down to see in more detail what is happening.

My eye-ball assessment is that it is clumping better now, with the take limitation, than it was without it. But the behavior I’d really like to evoke, still with “ridiculously simple” rules, would be for the bots to eventually gather one big pile of blocks and, ideally, not pick up any blocks from that pile. (If they did, they’d probably have to just put the block back in the pile, since they will only drop a block next to another one. I suppose there is some small chance that they could build a sort of path out from a pile and then snip off part of it. I’m not sure whether that could happen or not.)

One interesting aspect of these kinds of rules is that the overall behavior of the bots is hard to predict. It’s a rudimentary form of “emergence”, where simple rules lead to complex behavior. One of the most famous examples of that is “Boids”, a program that simulates flocking behavior. Well worth looking it up and perhaps trying it yourself.

I think I’ve accomplished what I set out to do this morning. I’ll display this to the Zoom gang tonight and see what ideas we come up with. See you next time!