How do I stop my rectangles from occasionally not moving to positions they are instructed to go to (in pygame)?

Viewed 70

So I have been creating this game for a while, where rectangles (written in code as mice) move in random directions around a screen, and every loop I move another rectangle that isn't rendered on the screen (named 'mouse_vision') around each mouse, that tells the mouse if it has collided with another mouse(to tell it not to go there), as well as to tell the mouse to move onto the randomly generated blue blocks (named 'food' in the code), that it has collided. However, the mice occasionally ignore the food, and move to a different, random position even though that should be impossible.

I have spent a couple hours trying to debug it (as well as another bug where the code doesn't register that a mouse has collided with some food), however I have made little progress in solving the issue.

As this is quite a specific question, i have not been able to find anyone else with a similar problem.

This is the code where the game tells the mice to move to the food:


    loop5 = 0
    while loop5 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop5][0]):
            direction = 1
        loop5 += 1

    given_mouse_vision.x -= 30
    given_mouse_vision.x -= 30
    loop6 = 0
    while loop6 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop6][0]):
            direction = 4

        loop6 += 1
    given_mouse_vision.x += 30
    given_mouse_vision.y += 30
    loop7 = 0
    while loop7 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop7][0]):
            direction = 3
        loop7 += 1
    given_mouse_vision.y -= 30
    given_mouse_vision.y -= 30

    loop8 = 0
    while loop8 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop8][0]):
            direction = 2
        loop8 += 1

This is the entire code, although the problem could only occur in the 'mouse_animation()' function:

import pygame
import time
import random
import sys
import os

### GENERATION OF FOOD ###
def food_generation():
    food_duplicate = False
    food_rand_generation_loop = 0
    food_rendered = random.randint(1, 12)
    if food_rendered == 1:

        food_random_x = random.randint(0, 36)
        food_random_y = random.randint(0, 20)
        food_mouse_x = 30 * food_random_x
        food_mouse_y = 30 * food_random_y
        food_taken_positions.append([food_mouse_x, food_mouse_y])
        food_n = 0
        while food_n < len(food_taken_positions) - 1:

            if food_taken_positions[food_n][0] == food_taken_positions[-1][0] and food_taken_positions[food_n][1] == \
                    food_taken_positions[-1][1]:
                del food_taken_positions[-1]
                food_duplicate = True
                food_rand_generation_loop -= 1
            food_n += 1

        if not food_duplicate:
            food = pygame.Rect(food_mouse_x, food_mouse_y, 28, 28)

            food_list.append([food, food_mouse_x, food_mouse_y])
        food_rand_generation_loop += 1

### MOVEMENT OF MOUSE ###
def mouse_animation(given_mouse, given_mouse_vision):
    global mouse_movement_counter, can_move_left, can_move_right, can_move_up, can_move_down, mouse_x, mouse_y

    direction = random.randint(1, 5)

    del mice[mouse_loop]
    mice.append([given_mouse, given_mouse_vision, mouse_x, mouse_y])

    given_mouse = mice[-1][0]
    given_mouse_vision = mice[-1][1]



    loop1 = 0
    given_mouse_vision.x += 30
    while loop1 < len(mice) - 1:
        if given_mouse_vision.colliderect(mice[loop1][0]):
            can_move_right = False
        loop1 += 1
    given_mouse_vision.x -= 30

    loop2 = 0
    given_mouse_vision.x -= 30
    while loop2 < len(mice) - 1:
        if given_mouse_vision.colliderect(mice[loop2][0]):
            can_move_left = False
        loop2 += 1
    given_mouse_vision.x += 30

    loop3 = 0
    given_mouse_vision.y += 30
    while loop3 < len(mice) - 1:
        if given_mouse_vision.colliderect(mice[loop3][0]):
            can_move_down = False
        loop3 += 1
    given_mouse_vision.y -= 30

    loop4 = 0
    given_mouse_vision.y -= 30
    while loop4 < len(mice) - 1:
        if given_mouse_vision.colliderect(mice[loop4][0]):
            can_move_up = False
        loop4 += 1
    given_mouse_vision.y += 30

    given_mouse_vision.x += 30

    loop5 = 0
    while loop5 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop5][0]):
            direction = 1

        loop5 += 1
    given_mouse_vision.x -= 30
    given_mouse_vision.x -= 30
    loop6 = 0
    while loop6 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop6][0]):
            direction = 4

        loop6 += 1
    given_mouse_vision.x += 30
    given_mouse_vision.y += 30
    loop7 = 0
    while loop7 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop7][0]):
            direction = 3


        loop7 += 1
    given_mouse_vision.y -= 30
    given_mouse_vision.y -= 30

    loop8 = 0
    while loop8 < len(food_list) - 1:
        if given_mouse_vision.colliderect(food_list[loop8][0]):
            direction = 2

        loop8 += 1

    given_mouse_vision.y += 30

    ie = len(food_list) - 1
    while ie > 0:
        ie -= 1
        if given_mouse.colliderect(food_list[ie][0]):  # FOOD
            del food_list[ie]
            break
        ie -= 1

    if given_mouse.x <= 10:
        can_move_left = False

    if given_mouse.x >= 1100:
        can_move_right = False

    if given_mouse.y <= 10:
        can_move_up = False

    if given_mouse.y >= 600:
        can_move_down = False

    if direction == 1 and mouse_movement_counter <= 0 and can_move_right:  # RIGHT

        given_mouse.x += 30
        given_mouse_vision.x += 30
        mouse_movement_counter += 15
        mouse_x += 30


    elif direction == 2 and mouse_movement_counter <= 0 and can_move_up:  # UP

        given_mouse.y -= 30
        given_mouse_vision.y -= 30
        mouse_movement_counter += 15
        mouse_y -= 30


    elif direction == 3 and mouse_movement_counter <= 0 and can_move_down:  # DOWN

        given_mouse.y += 30
        given_mouse_vision.y += 30
        mouse_movement_counter += 15
        mouse_y += 30


    elif direction == 4 and mouse_movement_counter <= 0 and can_move_left:  # LEFT

        given_mouse.x -= 30
        given_mouse_vision.x -= 30
        mouse_movement_counter += 15
        mouse_x -= 30


    elif can_move_right == False and can_move_up == False and can_move_down == False and can_move_left == False:

        mouse_movement_counter += 15

    else:
        mouse_movement_counter -= 1

    pygame.display.update()

### VARIABLES ###
pygame.init()

clock = pygame.time.Clock()

FPS = 10

screen_width, screen_height = 1140, 630

screen = pygame.display.set_mode((screen_width, screen_height))

food_taken_positions = []

food_list = []

mice = []

taken_positions = []

rand_generation_loop = 0

### GENERATES HOW MANY MICE WILL BE SPAWNED ###
while rand_generation_loop < 100:
    duplicate = False
    random_x = random.randint(0, 36)
    random_y = random.randint(0, 20)
    mouse_x = 30 * random_x
    mouse_y = 30 * random_y
    taken_positions.append([mouse_x, mouse_y])
    n = 0
    while n < len(taken_positions) - 1:
        if taken_positions[n][0] == taken_positions[-1][0] and taken_positions[n][1] == taken_positions[-1][1]:
            del taken_positions[-1]
            duplicate = True
            rand_generation_loop -= 1
        n += 1
    if duplicate == False:
        mouse = pygame.Rect(mouse_x, mouse_y, 30, 30)
        mouse_position = pygame.Rect(mouse_x, mouse_y, 30, 30)

        mice.append([mouse, mouse_position, mouse_x, mouse_y])
    rand_generation_loop += 1

x_location = 0
y_location = 0

mouse_movement_counter = 30
### LOOP THAT EXECUTES THE CODE ###

while True:
    clock.tick(FPS)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.fill((20, 100, 20))
    for food_ in range(len(food_list)):
        pygame.draw.rect(screen, (20, 40, 255), food_list[food_][0])

    for certain_mouse in range(len(mice)):
        pygame.draw.rect(screen, (200, 200, 200),  mice[certain_mouse][0])

    mouse_loop = 0

    while mouse_loop < len(mice):
        can_move_right = True
        can_move_left = True
        can_move_up = True
        can_move_down = True
        given_mouse_vision = pygame.Rect(mouse_x, mouse_y, 30, 30)
        mouse_animation(mice[mouse_loop][0], mice[mouse_loop][1])
        mouse_loop += 1

    food_generation()
    pygame.display.flip()

I have attempted to cut down the code, but apologies if it is still too long.

2 Answers

as well as another bug where the code doesn't register that a mouse has collided with some food

You accidentally decremented the "ie" variable twice in the loop that tests whether a mouse is eating a food:

ie = len(food_list) - 1
while ie > 0:
    
    # ie -= 1                   <--- DELETE
    
    if given_mouse.colliderect(food_list[ie][0]):  # FOOD
        del food_list[ie]
        break
    ie -= 1

I recommend to simplify the mouse_animation method:

def mouse_animation(given_mouse, given_mouse_vision):
    global mouse_movement_counter, mouse_x, mouse_y

    direction = random.randint(1, 5)

    del mice[mouse_loop]
    mice.append([given_mouse, given_mouse_vision, mouse_x, mouse_y])

    given_mouse = mice[-1][0]
    given_mouse_vision = mice[-1][1]

    can_move_left = given_mouse.x > 10 and not any(given_mouse_vision.move(-30, 0).colliderect(m[0]) for m in mice)
    can_move_right = given_mouse.x < 1110 and not any(given_mouse_vision.move(30, 0).colliderect(m[0]) for m in mice)
    can_move_up = given_mouse.y > 10 and not any(given_mouse_vision.move(0, -30).colliderect(m[0]) for m in mice)
    can_move_down = given_mouse.y < 600 and not any(given_mouse_vision.move(0, 30).colliderect(m[0]) for m in mice)
    
    direction = random.choice([(-1, 0), (1, 0), (0, -1), (0, 1)])
    if can_move_left and any(given_mouse_vision.move(-30, 0).colliderect(f[0]) for f in food_list):
        direction = (-1, 0)
    elif can_move_right  and any(given_mouse_vision.move(30, 0).colliderect(f[0]) for f in food_list):
        direction = (1, 0)
    elif can_move_up and any(given_mouse_vision.move(0, -30).colliderect(f[0]) for f in food_list):
        direction = (0, -1)
    elif can_move_down and any(given_mouse_vision.move(0, 30).colliderect(f[0]) for f in food_list):
        direction = (0, 1)

    if mouse_movement_counter <= 0 and direction != None:
        given_mouse.x += 30 * direction[0]
        given_mouse_vision.x += 30 * direction[0]
        mouse_x += 30 * direction[0]
        given_mouse.y += 30 * direction[1]
        given_mouse_vision.y += 30 * direction[1]
        mouse_y += 30 * direction[1]
        mouse_movement_counter += 15

    elif can_move_right == False and can_move_up == False and can_move_down == False and can_move_left == False:
        mouse_movement_counter += 15
    
    else:
        mouse_movement_counter -= 1

    for ie, food in enumerate(food_list):
        if given_mouse.colliderect(food[0]):
            del food_list[ie]
            break

Here is a solution, that introduces the mouse as a class.

import pygame
import random
import sys

### VARIABLES ###
pygame.init()

clock = pygame.time.Clock()
FPS = 1

screen_width, screen_height = 1140, 630
grid_size = 30
screen = pygame.display.set_mode((screen_width, screen_height))

food_list = []
mice = []
mouse_positions = []

NUM_MICE = 100







### GENERATION OF FOOD ###
def food_generation():
    food_placed = False
    while not food_placed:
        food_random_x = grid_size* random.randint(0, screen_width//grid_size)
        food_random_y = grid_size*random.randint(0, screen_height//grid_size)
        if (food_random_x, food_random_y) in food_list:
            continue
        else:
            food_list.append((food_random_x,food_random_y))
            food_placed = True

class Mouse(object):
    
    def __init__(self, mouse_positions, screen_width, screen_height, grid_size):
        mouse_placed = False
        while not mouse_placed:
            mouse_random_x = grid_size* random.randint(0, screen_width//grid_size)
            mouse_random_y = grid_size*random.randint(0, screen_height//grid_size)
            if (mouse_random_x, mouse_random_y) in mouse_positions:
                continue
            else:
                mouse_positions.append((mouse_random_x,mouse_random_y))
                mouse_placed = True
        self.x = mouse_random_x
        self.y = mouse_random_y
        self.screen_width = screen_width
        self.screen_height = screen_height
        self.grid_size = grid_size
        self.check_move(mouse_positions)
        self.see_food = False
        self.food_directions = []
        self.available_directions = []
    
    
        
    def check_move(self, mouse_positions):
        self.available_directions = []
        if not ((self.x - grid_size, self.y) in mouse_positions or self.x == 0):
            self.available_directions.append('left')  
        if not ((self.x + grid_size, self.y) in mouse_positions or self.x >= self.screen_width-grid_size):
            self.available_directions.append('right')
        if not ((self.x, self.y-grid_size) in mouse_positions or self.y == 0):
            self.available_directions.append('up')
        if not ((self.x, self.y+grid_size) in mouse_positions or self.y >= self.screen_height-grid_size):
            self.available_directions.append('down')

    
    def look_for_food(self, food_list):
        self.food_directions = []
        if 'left' in self.available_directions:
            if (self.x-grid_size, self.y) in food_list:
                self.food_directions.append('left')
        if 'right' in self.available_directions:
            if (self.x+grid_size, self.y) in food_list:
                self.food_directions.append('right')
        if 'up' in self.available_directions:
            if (self.x, self.y-grid_size) in food_list:
                self.food_directions.append('up')
        if 'down' in self.available_directions:
            if (self.x, self.y+grid_size) in food_list:
                self.food_directions.append('down')
    
    def move_mouse(self, mouse_positions):
        direction = None
        if len(self.food_directions) > 0:
            direction = random.choice(self.food_directions)
        else:
            if len(self.available_directions) > 0:
                direction = random.choice(self.available_directions)
        if not (direction is None):
            mouse_positions.remove((self.x, self.y))
            if direction == 'left':
                self.x -= self.grid_size
            if direction == 'right':
                self.x += self.grid_size
            if direction == 'up':
                self.y -= self.grid_size
            if direction == 'down':
                self.y += self.grid_size
            mouse_positions.append((self.x, self.y))
        return mouse_positions

    def eat_food(self, food_list):
        if (self.x, self.y) in food_list:
            food_list.remove((self.x, self.y))
        return food_list
    
    def update(self, mouse_positions, food_list):
        self.check_move(mouse_positions)
        self.look_for_food(food_list)
        new_mouse = self.move_mouse(mouse_positions)
        new_food = self.eat_food(food_list)
        return new_mouse, new_food

        
for i in range(NUM_MICE):
    new_mouse = Mouse(mouse_positions, screen_width, screen_height, grid_size)
    mice.append(new_mouse)

while True:
    clock.tick(FPS)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    screen.fill((20, 100, 20))
    food_generation()
    for i,mouse in enumerate(mice):
        new_mouse, new_food = mouse.update(mouse_positions, food_list)
        mouse_positions, food_list = new_mouse, new_food
        pygame.draw.rect(screen, (200-2*i, 200-2*i, 200-2*i),  pygame.Rect(mouse.x, mouse.y, grid_size, grid_size))
        
    for food in food_list:
        pygame.draw.rect(screen, (20, 40, 255), pygame.Rect(food[0], food[1], grid_size, grid_size))
    
    pygame.display.flip()
    pygame.display.update()

Also, if a mouse is close to a food, it will move to the food in the next step, otherwise pick randomly from the available directions. I also factored out the variables for screen dimensions and grid size, so that they can be changed easily globally.

The mouse class holds all the logic for detection of possible directions, sensing food, eating food and updating the mouse movement in one go. Note that you have to increase the yield of the food production to see some food. Maybe it would make sense to also produce food only at positions where there's not a mouse.

I replaced all collision-detections with simple list lookups. The code is a lot more efficient, so I had to decrease the Framerate to make things visible.

EDIT: I also color-coded the mice by index, so it's easier to keep them apart.

Related