DeepwaterCreations / arena-of-crabs

An arcade-ish game about fighting crabs. Meant to be a prequel/warmup for Deosil Blessing, my gameboy-era TLoZ tribute.
2 stars 0 forks source link

Game crashes when I knife the wall #2

Closed DeepwaterCreations closed 8 years ago

DeepwaterCreations commented 8 years ago

How to reproduce: I need to have the knife overlap one of the single-tile corner walls, and then I need to release the knife key to end the slash and put it away. What happens is that I get an error in the drawable class file's drawAll function, on the very last line, in the PyGame sprite group draw call. Apparently, Knife at this point is no longer a member of the group.

Due to the nature of draw calls, this is hard to put a constructive breakpoint in. However, I have sussed out that when the knife slash ends, in endAttack(), it calls self.setVisible(False), which removes it from the drawgroup.

What I don't get is why this causes problems, or what those corner tiles have to do with anything. Some kind of threading issue? But I'm not actually using any threads.

DeepwaterCreations commented 8 years ago

Interestingly enough, it only works when I'm pressed into the corner - not actively moving, but lined up with the walls. Something to do with the knife sprite's coordinates, then?

DeepwaterCreations commented 8 years ago

Definitely the coordinates. If I change them right before I set visibility at the swing's end, nothing breaks. On the other hand, if I set them explicitly to coordinates where it breaks at that same place, I can't do a full swing anywhere without crashing. But what's so special about these particular sets of coordinates? Just that they're multiples of 64, and I usually don't happen to have coordinates that are exactly that? But why didn't this happen in the regular corners? (Or did it and I just never noticed?)

DeepwaterCreations commented 8 years ago

I went back to a pre-corners commit, and told it to set the coordinates before hiding the sprite as before, and it didn't break UNTIL... a crab appeared? Until the knife overlapped with the crab? Can I even fix this? What the heck is going on?

DeepwaterCreations commented 8 years ago

I could scotch-tape fix it by explicitly setting the coordinates to something innocuous every time the knife swing ends. I don't want to, but I could.

DeepwaterCreations commented 8 years ago

It's not when it overlaps with a crab. In the old commit, it almost seems random. Sometimes it breaks, sometimes not.

DeepwaterCreations commented 8 years ago

No it isn't random. It happens consistently in the lower-left corner. But also other places, and I'm not sure what. But not necessarily the other corners. :?

DeepwaterCreations commented 8 years ago

Coordinates where it breaks in the current map, deosil from the upper right: 512, 64 512, 384 64, 384 64, 64 Note that whichever sub-corner I stand in, the coordinates for a given corner are the same. It's the same tile the knife enters, after all.

DeepwaterCreations commented 8 years ago

In the old branch, when I explicitly set coordinates to a given corner, I also have to stand in that corner - and not the others - for it to reliably break. In the new branch, if I explicitly set broken coordinates, it'll break whatever.

DeepwaterCreations commented 8 years ago

More investigation: The knife, when set to not be visible, is being correctly removed from the group. I stepped through and checked. At least, for some level of "correctly". What's wacky is how in the drawAll loop, if I print Drawable.drawable_group.sprites(), it shows a list that includes <Knife sprite(in 0 groups)>. So the group thinks the Knife is still a member, but the Knife thinks it has no groups. In fact, my observation in the latter situation is repeated in the former, just after the call to Drawable.drawable_group.remove(self).

I see also that "print self" in the setVisible() call doesn't say anything about a Knife. It just gives me <rect(64, 384, 64, 64)>...

DeepwaterCreations commented 8 years ago

Har-hum. So I've confirmed that normally, there's no <Knife sprite(in 0 groups)>. Either it's in 1 group, or it isn't listed at all.

Maybe I oughta check what order these things are being called in, and from where. Did I already look to see if the updateImage() call in drawable is what sets it to invisible?

DeepwaterCreations commented 8 years ago

Internally, the LayeredUpdates class has a self._spritelist and a self.spritedict. When it breaks, it's because it has the sprite in its _spritelist, but not the dict. FFR, the file is /usr/lib/python2.7/site-packages/pygame/sprite.py

Also don't forget I can do $pygame2 -m pdb main.py to have the debugger be invoked automatically when my program crashes.

DeepwaterCreations commented 8 years ago

It IS the rect thing! I think! The place where all goes awry is in the group's remove_internal method. It calls self._spritelist.remove(sprite), and _spritelist has the <Knife sprite(in 1 groups)>, but if I print "sprite", I get rect(64, 384, 64, 64)... ...and after that line, _spritelist still has the <Knife sprite(in 1 groups)> in it. (I think the line where it becomes (in 0 groups) is just after this function call.)

Yet for some reason, it knows to pop the knife sprite from the dict based on the rect, even though there's also a wall sprite with an identical rect...? And I can print sprite._force and get 640 back, which is definitely an attribute on Knife. An Entity is, after all, a subclass of Rect. So... hmm.

Yeah, hold on. p sprite and print sprite give two different things. The former is the Knife, the latter is the rect. So I still don't know for sure why the removal isn't working.

DeepwaterCreations commented 8 years ago

Confirmed that when I'm not in the corner, the self._spritelist.remove(sprite) works fine. I can't step into it. What's the difference? Why can't the dang thing give me some feedback when it tries to remove the sprite from the list and fails?

DeepwaterCreations commented 8 years ago

Nope, wait, it IS the wall thing. When I examine the _spritelist to get the index of sprite, it normally returns 12, but when I'm in the corner, it returns 7. 7 is the index of a wall sprite. Also, the list's count of sprite is 2, not 1. So it's finding the wall first and removing that instead of the knife. Unfortunately, it's not clear what I can do about this short of preventing the knife from exactly occupying the wall space. Which doesn't seem like an ideal solution, because I could very easily wind up with, say, 64x64 ground tiles that I want Cricket to be able to walk over.

What if I stop making self.rect = self? Would that cure it? I guess I need to know more about how Python handles object values and classes. Why does it only look at the rect properties to determine equivalency? Can I override that? If I do, will it cause a mess for object collision?

Yeah. I'm betting there's some is_equal method or something that's a part of the base Python object class or whatever it has, and that Rect overrides it, and that by making my Entities into Rects, they're using that behavior when I very much don't want them to.

DeepwaterCreations commented 8 years ago

Yeah, 'operator.eq(a,b)' is a thing. Not clear where the .py for Rect is, but I'ma go out on a limb and assume it uses this. I can't just override this in Entity: I sometimes want the Rect behavior, sometimes not, depending on context. I think my only recourse is to (ugh) make Entity not a Rect after all. Sprites can't also be Rects, and since Sprites already HAVE Rects, I'll keep that relationship.

I always sort of figured that self.rect = self line was asking for trouble. Oh well, it would only have gotten harder to fix as time went on. At least I caught it as soon as I did.

DeepwaterCreations commented 8 years ago

So the plan is to make Entity no longer a Rect. This is gonna cause all kinds of heartache. I reference Rect properties on Entity all over the dang place. And Python being Python, I suppose it'll be annoyingly easy to miss some of them.

DeepwaterCreations commented 8 years ago

Let's hear it for Vim and regular expressions! I fixed it with this: %s/\<\(\h\+\)\.\(x\|y\|width\|height\|top\|bottom\|left\|right\|topleft\|bottomleft\|topright\|bottomright\|midtop\|midleft\|midbottom\|midright\|center\|centerx\|centery\|w\|h\)\>/\1.rect.\2/gc That matches any series of alphabet character and underscores, followed by a dot, followed by one of the various rect properties I care about, and replaces it with the first part of the match, followed by ".rect.", followed by the second part of the match. So foo.width becomes foo.rect.width, bar.y becomes bar.rect.y, and so forth.
Actually, I fixed it with a version that only matched self.whatever first, then realized I had places where I referred to other objects' rect properties, so I had to amed the regex and go again. This had the weakness that it would replace foo.rect.x with foo.rect.rect.x. If I spent the time, I could probably make it match any beginning string but "rect", but at this point I was getting tired of fiddling with it, so I just stepped through with the confirmation option and gave each individual case the yea or nay manually.

Still heaps better than if I'd done it the hard way.

DeepwaterCreations commented 8 years ago

Confirmed fixed.