Search code examples
pythonpygame

Pygame event loop blocks game if there's no user input


I've fumbled a snake game I'm writing in Python, it has problems with with the main loop it appears

Apologies in advance for any sloppiness, I'm still prototyping:

import sys
import pygame
from pygame.locals import *

from libs.Touch import Touch

def sortPairs(*pairs):
    result = {}
    for pair in pairs:
        result[pair[0]] = pair[1]
        result[pair[1]] = pair[0]
    return result

DIR = {
    'LEFT':  (-1, +0),
    'RIGHT': (+1, +0),
    'UP':    (+0, -1),
    'DOWN':  (+0, +1)
}
DIR['INVERSE'] = sortPairs(
    (DIR['LEFT'], DIR['RIGHT']),
    (DIR['UP'],   DIR['DOWN']))

class Snake:
    def __init__(self,x, y):
        self.dir   = DIR['RIGHT']
        self.nodes = [[x, y]]
    
    def move(self):
        for i in range(len(self.nodes) - 1, 0, -1):
            self.nodes[i][0] = self.nodes[i - 1][0]
            self.nodes[i][1] = self.nodes[i - 1][1]
            
        self.nodes[0][0] += self.dir[0]
        self.nodes[0][1] += self.dir[1]
    
    def turn(self, turn):
        dir = DIR[turn]
        if len(self.nodes) == 1 or dir != DIR['INVERSE'][turn]:
            self.dir = dir
    
    def grow(self):
        self.nodes.appendLeft([
            self.nodes[0][0] + self.dir[0],
            self.nodes[0][1] + self.dir[1]])
    
    def draw(self, surface,x, y, node_size):
        rect = pygame.Rect(
            0, 0, node_size, node_size)
        
        for node in self.nodes:
            rect.left   = x + node[0] * node_size
            rect.top    = y + node[1] * node_size
            rect.right  = rect.left + node_size     
            rect.bottom = rect.top + node_size
            
            
            pygame.draw.rect(
                surface,
                pygame.Color("#FF0000"),
                rect)

class Game:
    def __init__(self, size, tick):
        self.size = size
        self.tick = tick
        self.snake = Snake(0, 0)
        
    def draw(self, surface):
        surface.fill(pygame.Color('#161616'))
        
        cell_size = min(
            surface.get_width(),
            surface.get_height()) / self.size
        
        bounds_stroke = int(cell_size / 10)
        
        bounds = pygame.Rect(
            surface.get_width() / 2,
            surface.get_height() / 2,
            cell_size * self.size - bounds_stroke,
            cell_size * self.size - bounds_stroke)
        bounds.center = (bounds.x, bounds.y)
        
        self.snake.draw(
            surface,
            int(bounds.left),
            int(bounds.top),
            int(cell_size))
        
        pygame.draw.rect(
            surface,
            pygame.Color('#6f91b3'),
            bounds,
            bounds_stroke)
        
    def update(self):
        self.snake.move()
    
    def handle_event(self, event):
        if event.type == MOUSEBUTTONDOWN:
            pygame.mouse.get_rel()
        if event.type == MOUSEBUTTONUP:
            swipe = Touch.reg_swipe(*pygame.mouse.get_rel())
            if swipe != -1:
                self.snake.turn(swipe)
            
        if event.type == QUIT:
            pygame.quit()

def main():
    game = Game(30, 100)
    
    pygame.init()
    
    # Resolution is ignored on Android
    surface = pygame.display.set_mode((640, 480))
    
    clock = pygame.time.Clock()
    
    delta = 0
    
    while True:
        for ev in pygame.event.get():
            game.handle_event(ev)
            
            delta += clock.tick(60)
            
            if delta >= game.tick:
                game.update()
                delta = 0
            
            game.draw(surface)
            pygame.display.flip()

if __name__ == '__main__':
    main()

./libs/Touch.py:

class Touch:
    max_touch_dist = 15
    min_swipe_dist = 50
    
    @staticmethod
    def reg_swipe(rel_x, rel_y):
        min = Touch.min_swipe_dist
        if abs(rel_y) < min:
            if rel_x <= -min:
                return 'LEFT'
            elif rel_x >= min:
                return 'RIGHT'
        if rel_y <= -min:
            return 'UP'
        elif rel_y >= min:
            return 'DOWN'
            
        return -1

I'm using Pydroid on my phone, but testing it on an online interpreter got the same result: the game runs 2 iterations, then freezes, and then only runs if I touch the screen (or trigger an event I imagine), I've compared my code (especially the event loop) to Pygame samples but everything appears fine, so what's going on?


Solution

  • You must run the game logic and draw the scene in the application loop, but not in the event loop. The event loop is executed only when an event occurs, but the application loop is executed every frame and the number of frames is controlled by clock.tick(60).

    def main():
        # [...]
    
        while True:
            for ev in pygame.event.get():
                game.handle_event(ev)
                
            delta += clock.tick(60)    
            if delta >= game.tick:
                game.update()
                delta = 0
                
            game.draw(surface)
            pygame.display.flip()