Mr.Python Mr.Python - 3 months ago 13
Python Question

variable that is set to True in if statement is never becomes False

I have a list of rectangles coordinates, that I'm iterating over to test for collisions with each one. The list is Like so:

self.rectangle_list = [(200, 30, 100, 10), (200, 60, 100, 10), (200, 90, 100, 10), (200, 120, 100, 10), (200, 150, 100, 10)]
. My code for the for loop is below.

mouse_x, mouse_y = event_obj.pos # mouse_x, mouse_y are the coordinates of the mouse.
for rects in self.rectangle_list:
x, y, w, h = rects
if x <= mouse_x <= x + w and y <= mouse_y <= y + h:
self.hovering = True
else:
self.hovering = False
print(self.hovering)


When I print out
self.hovering
, The only time It changes to True is when The mouse cursor is in the coordinates of the very last rectangle in the list.

When I move
self.hovering
under the
if
statement it works, but never sets
self.hovering
back to False while the
if
condition is not meet.

example code to reproduce the problem is bellow:

import pygame as pg


class RenderRects:
def __init__(self, surface, rects_to_render=0):
self.surface = surface
self.rects_to_render = rects_to_render
self.rectangle_list = []
self.hovering = False

def render_rects(self):
y_padding = 0
for rects in range(self.rects_to_render):
y_padding += 30
menu_items_rect = (200, y_padding, 100, 10)
pg.draw.rect(self.surface, (255, 0, 0), menu_items_rect)
if len(self.rectangle_list) > 5:
del self.rectangle_list[4:]
self.rectangle_list.append(menu_items_rect)

def check_for_rect_collision(self, event_obj):
#-----------------Where problem is-----------#
mx, my = event_obj.pos
for rects in self.rectangle_list:
x, y, w, h = rects
if x <= mx <= x + w and y <= my <= y + h:
self.hovering = True
else:
self.hovering = False
print(self.hovering)
#-----------------Where problem is-----------#

def update_rects(self, event_obj):
if event_obj.type == pg.MOUSEMOTION:
self.check_for_rect_collision(event_obj)

def main():
WIDTH = 800
HEIGHT = 600
display = pg.display.set_mode((WIDTH, HEIGHT))

R = RenderRects(display, rects_to_render=5)

running = True
while running:
for e in pg.event.get():
if e.type == pg.QUIT:
running = False
pg.quit()
quit()

R.update_rects(e)

display.fill((255, 255, 255))
R.render_rects()
pg.display.flip()

if __name__ == '__main__':
main()

Answer

Your if statement has a problem:

if x <= mouse_x <= x + w and y <= mouse_y <= y + h:
                self.hovering = True

You can't chain less than/greater than as you do in: x <= mouse_x <= x + w. This really gets translated to:

if x <= (mouse_x <= x + w) ....

And since True == 1 and False == 0, this means that if mouse_x <= x + w is True, x <= (mouse_x <= x + w) really becomes x <= 1


Edit -- Added additional problem explanation (credit to Michael Hoff for the suggestion)

You also have a problem with your loop. In the loop, for every rectangle pair, you set the variable self.hovering. This means that you are continuously overwriting the value of self.hovering with the status of the current rectangle --- not if any of the rectangles are hovering.

Instead, since you care if self.hovering is ever True, you should only set the value in the True case:

self.hovering = False # assume it's not hovering at first
for rects in self.rectangle_list:
    x, y, w, h = rects
    if x <= mouse_x and mouse_x <= x + w and y <= mouse_y  and mouse_y <= y + h:
        self.hovering = True # now it will always be True

While this solves the loop issue, it's still a little bit inefficient, as it will continue looping over the pairs even after you find one that makes self.hovering = True. To stop looping when you find a "good" pair, you can use break, which just prematurely ends a loop.

self.hovering = False # assume it's not hovering at first
for rects in self.rectangle_list:
    x, y, w, h = rects
    if x <= mouse_x and mouse_x <= x + w and y <= mouse_y  and mouse_y <= y + h:
        self.hovering = True # now it will always be True
        break # exit the loop, since we've found what we're looking for