Before I try to improve the way the Shield adjusts its image and mask, I feel that I need a better understanding of how things work. We’ll use tests to learn and record.

There’s a lot of magic going on in the code that carves bits out of the Shield and its mask:

``````    def process_shot_collision(self, shot, explosion_mask):
collider = Collider(self, shot)
if collider.colliding():

offset_x = (shot.rect.w - expl_rect.w) // 2
adjust_image_to_center = collider_offset + Vector2(offset_x, 0)

self._map.blit(surf, rect)
``````

Let me list things that I know, think I know, and wonder about, things that I need to understand and correlate if I’m to have code that makes sense.

• What are the various rectangles, of the shield, the shot, the collider overlap, the overlap mask, and the explosions?
• How can we best understand these and relate them to each other?

``````overlap_mask: Mask = collider.overlap_mask()

class Collider:

def offset(self):
offset = Vector2(self.right_rect.topleft) - Vector2(self.left_rect.topleft)
return offset
``````

The document says:

Returns a Mask, the same size as this mask, containing the overlapping set bits between this mask and other.

The call requires an offset, an x,y pair describing the offset of the “right” argument from the “left”. I think the code above would be more clear if we were to use the centers of the masks, rather than the topleft. Will it work that way? Not if that’s the only change we make: I just tried it.

Consider a big rectangle and a little one, the little one offset a ways down and to the right from the big one’s top left. Clearly big.topleft - little.topleft is not equal to big.center - little.center. Still, it would be “nice” if we could use centers, since in our code we think about the centers of things.

Let’s try some tests. I was going to put them in with the Collider tests but let’s make a new set of tests called testmasking.

After some messing about, and after learning that the file has to be named “test_masking”, I have this first test:

``````

@pytest.fixture
def make_missile(self):
surf = Surface((3, 3))
# ***
# * *
# ***
surf.set_colorkey("black")
surf.fill("white", surf.get_rect())
surf.set_at((1,1), "black")

@pytest.fixture
def make_target(self):
surf = Surface((8, 8))
surf.set_colorkey("black")
surf.fill("white", surf.get_rect())

def test_raw_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)
``````

I’m not sure those fixtures are going to help much. I’m trying to learn how to use them well by using them poorly.

So that test has a target consisting of an 8x8 solid mask, and a missile, a 3x3 one solid except for a hole in the middle. I am not sure these are going to tell me everything I want to know, but it’s a start. My plan is to move the missile mask around on the target and read out what I get.

I figure after I do that a bit, I’ll know what I really want to test.

In fact, I want to change the missile right now, to a T shape:

``````    @pytest.fixture
def make_missile(self):
surf = Surface((3, 3))
# ***
#  *
#  *
surf.set_colorkey("black")
surf.fill("white", surf.get_rect())
surf.set_at((0, 1), "black")
surf.set_at((0, 2), "black")
surf.set_at((2, 1), "black")
surf.set_at((2, 2), "black")
``````

That should let me try this and get an empty mask back:

``````    def test_empty_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)
for x in range(rect.w):
for y in range(rect.h):
``````

Now if we move one to the left, we should hit just two bits, (7, 0) and (7, 1). Let’s try that.

I am mistaken, the offsets there need to be 7 and 6: The offset is the top left coordinate, not the step. These tests run:

``````    def test_empty_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)
for x in range(rect.w):
for y in range(rect.h):

def test_top_right_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)
``````

I think I understand the offset in the overlap_mask call. But I want my objects to be positioned by their centers. How will that affect what we’ll want our ImageMasher to do?

Let’s test-drive a little method that will erase part of our big mask, where it is touched by the small mask, starting from the centers of the two masks. What even is the center of an 8 x 8 mask?

Note
I don’t get to the erasure part yet. I got bogged down in bit counting, but I have some tests that are leading me to what may be a good place, as you’ll see in the Summary just below.

Let’s check the centers:

``````    def test_what_are_centers(self, make_missile, make_target):
missile = make_missile
assert missile.rect.center == (1, 1)
target = make_target
assert target.rect.center == (4, 4)
``````

OK, interesting, it rounds up. Let’s imagine that our missile is centered on the target center and test what bits are set.

After a little while, I have more tests and not as much understanding as I’d like. I’m tired, though, so will report all my tests and tentative conclusions.

## Summary

``````class TestMasking:

@pytest.fixture
def make_missile(self):
surf = Surface((3, 3))
# ***
#  *
#  *
surf.set_colorkey("black")
surf.fill("white", surf.get_rect())
surf.set_at((0, 1), "black")
surf.set_at((0, 2), "black")
surf.set_at((2, 1), "black")
surf.set_at((2, 2), "black")

@pytest.fixture
def make_target(self):
surf = Surface((8, 8))
surf.set_colorkey("black")
surf.fill("white", surf.get_rect())

def test_what_are_centers(self, make_missile, make_target):
missile = make_missile
assert missile.rect.center == (1, 1)
target = make_target
assert target.rect.center == (4, 4)

def test_raw_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)
assert rect.w == 8
assert rect.h == 8

def test_empty_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)
for x in range(rect.w):
for y in range(rect.h):

def test_top_right_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)

def test_center_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
assert rect.topleft == (0, 0)

missile = make_missile
target = make_target
missile.rect.center = (20, 20)
assert missile.rect.topleft == (19, 19)
assert rect.topleft == (0, 0)

missile = make_missile
target = make_target
missile.rect.center = (7, 0)
assert missile.rect.topleft == (6, -1)
assert target.rect.topleft == (0, 0)
assert rect.topleft == (0, 0)

def test_use_both_rects_in_overlap(self, make_missile, make_target):
missile = make_missile
target = make_target
target.rect.center = (100, 200)
assert target.rect.topleft == (100-4, 200-4)
missile.rect.center = (103, 196)
offset = Vector2(missile.rect.topleft) - Vector2(target.rect.topleft)
assert offset == Vector2(6, -1)
assert rect == (0, 0, 8, 8)  # always relative
assert rect.topleft == (0, 0)
``````

I think the main point here is that these tests are leading me to more and more understanding of how the overlap mask works. I think they probably could use improvement, but I expect that after just a few more, I can write a decent ImageMasher or whatever mask-mangling object I need. We’ll see.

The following are just notes, not necessarily coherent, because my thoughts and understanding are not yet coherent.

Almost all my work with these things has been fiddling with the rects, but then I use the mask to get the overlap and check it.

That last test is doing roughly what I think I want in the real thing, setting the rects by their centers and then offsetting by differencing the toplefts. I may be wrong about wanting that.

I have drawn some pictures on graph cards and counted cells to get my numbers. My tentative conclusion is that I can ignore the fact that I’m moving my objects via their center, and just use the topleft values of the rectangles to compute the offsets for the `overlap_mask` call. If I’m to erase the bits, however, I’ll need the offset again.

I think my next test, when I come back to this, should be to erase some bits.

Then we’ll try positioning a separate pattern, using the centers and whatever else we need from the overlap_mask, and erasing the pattern. That will be amusing.

I just noticed that when I `get_rect` from a mask, I can include `center=(x,y)`. That might come in handy.