Search code examples
pythonpython-3.xnumpypygamesimulation

Pygame program crashes when tabbing out of focus


I'm running a basic simulation of a pendulum but whenever I tab out, the pygame window stops responding (and eventually crashes) but the console continues to output like the program is running as normal; or at least the maths for simulating the pendulum works but not the pygame side of it.

I'm not sure where to even start for fixing it, I assume its some small error in the actual program setup rather than the code for simulating the pendulum itself and I'm just missing something.

Here is the code:

import pygame
import numpy as np


pygame.init()
WIDTH = 800
HEIGHT = 500
res = (WIDTH, HEIGHT)
screen = pygame.display.set_mode(res)
clock = pygame.time.Clock()
WHITE = [255, 255, 255]
RED = [255,0,0]
BLACK = [0, 0, 0]
BLUE = [0, 0, 155]


class Pendulum:
    def __init__(self, theta0, L, tMax, dt, scale, pivotRadius, bobRadius, pivot, bobMass):
        self.g = 9.81
        self.t = np.arange(0, tMax, dt)  
        self.theta0 = theta0 
        self.L = L  
        self.tMax = tMax
        self.dt = dt  
        self.pivot = pivot
        self.bobMass = bobMass
        self.scale = scale
        self.pivotRadius = pivotRadius
        self.bobRadius = bobRadius
        self.screen = screen
        self.width = scale
        self.height = scale

    def simulatePendulum(self):
        g = 9.81
        t = np.arange(0, self.tMax, self.dt)
        theta = np.zeros_like(t)
        omega = np.zeros_like(t)
        theta[0] = self.theta0
        x = np.zeros_like(t)
        y = np.zeros_like(t)

        for i in range(len(t)-1):
            omega[i+1] = omega[i] + self.dt*(-g/self.L * np.sin(theta[i]))
            theta[i+1] = theta[i] + self.dt*omega[i+1]
            x[i+1] = self.L*np.sin(theta[i+1])
            y[i+1] = self.L*np.cos(theta[i+1])

            self.draw(x[i+1], y[i+1])   
        return x[i+1], y[i+1]

    def draw(self, x, y):
        bobPos = (x+400,y+300)
        pivotPos = (self.pivot[0]+400, self.pivot[1]+300)
        print(bobPos)
        screen.fill(WHITE)
        pygame.draw.line(self.screen, RED, pivotPos, bobPos, 2)
        pygame.draw.circle(self.screen, BLUE, pivotPos, 5)
        pygame.draw.circle(self.screen, BLACK, bobPos, 10)
        pygame.display.update()
       
    
def main():
    theta0 = np.pi/4
    L = 100
    tMax = 10000
    dt = 0.01
    scale = 1
    pivotRadius = 0.02
    bobRadius = 0.05
    pivot =(0,0)
    bobMass=1
    p = Pendulum(theta0, L, tMax, dt, scale, pivotRadius, bobRadius, pivot, bobMass)

    done = False
    while not done:
        event_list = pygame.event.get()
        for event in event_list:
            if event.type == pygame.QUIT:
                done = True

        screen.fill((255, 255, 255))
        x, y = p.simulatePendulum()
        p.draw(x, y)
        
        clock.tick(60)
    pygame.quit()
main()


Solution

  • Your problem has absolutely nothing to do with "tabbing out of focus". The problem is for i in range(len(t)-1): and tMax = 10000. Therefore, you have a loop that updates the display 10000 times within the application loop. All the time the loop is dunning, the events are not handled and this causes the system to freeze. Also see PyGame window not responding after a few seconds.

    To solve the problem, do not calculate 10000 angles and positions of the pendulum in a loop, but calculate the next position of the pendulum depending on the current angle and position in simulatePendulum and update the display once per frame:

    import pygame
    import numpy as np
    
    pygame.init()
    WIDTH = 800
    HEIGHT = 500
    res = (WIDTH, HEIGHT)
    screen = pygame.display.set_mode(res)
    clock = pygame.time.Clock()
    
    class Pendulum:
        def __init__(self, theta0, L, dt, scale, pivotRadius, bobRadius, pivot, bobMass):
            self.g = 9.81
            self.theta0 = theta0 
            self.L = L  
            self.dt = dt  
            self.pivot = pivot
            self.bobMass = bobMass
            self.scale = scale
            self.pivotRadius = pivotRadius
            self.bobRadius = bobRadius
            self.screen = screen
            self.width = scale
            self.height = scale
            self.omega = 0
            self.theta = self.theta0
            self.x = 0
            self.y = 0
    
        def simulatePendulum(self):
            g = 9.81
            self.omega += self.dt*(-g/self.L * np.sin(self.theta))
            self.theta += self.dt*self.omega
            self.x = self.L*np.sin(self.theta)
            self.y = self.L*np.cos(self.theta)
    
        def draw(self):
            bobPos = (self.x+400, self.y+300)
            pivotPos = (self.pivot[0]+400, self.pivot[1]+300)
            screen.fill("white")
            pygame.draw.line(self.screen, "red", pivotPos, bobPos, 2)
            pygame.draw.circle(self.screen, "blue", pivotPos, 5)
            pygame.draw.circle(self.screen, "black", bobPos, 10)
            pygame.display.update()
           
        
    def main():
        theta0 = np.pi/4
        L, dt, bobMass = 100, 0.1, 1
        scale, pivotRadius, bobRadius = 1,  0.02, 0.05
        pivot = (0,0)
        p = Pendulum(theta0, L, dt, scale, pivotRadius, bobRadius, pivot, bobMass)
    
        done = False
        while not done:
            event_list = pygame.event.get()
            for event in event_list:
                if event.type == pygame.QUIT:
                    done = True
    
            p.simulatePendulum()
            p.draw()        
            clock.tick(60)
    
        pygame.quit()
    main()