Skip to main content
The game loop is the heart of Serenity Valley, handling all game logic, event processing, and rendering in a continuous cycle.

Main Game Loop

The game loop is implemented in the run() method of the Game class (game.py:199-254):
def run(self):
    print "Beginning run sequence."
    # The main game loop
    #
    while True:
        # Limit frame speed to 30 FPS
        #
        self.time_passed = self.clock.tick(30)
        
        # If too long has passed between two frames, don't
        # update (the game must have been suspended for some
        # reason, and we don't want it to "jump forward"
        # suddenly)
        #
        if self.time_passed > 100:
            continue
       
        active = False 
        for entry in self.textEntries:
            if entry.clicked:
                active = True
        
        # Event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.quit()
            elif event.type == pygame.KEYDOWN and not active:
                if event.key == pygame.K_SPACE:
                    self.paused = not self.paused
                elif event.key == pygame.K_g:
                    self.options['draw_grid'] = not self.options['draw_grid']
            elif (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
                for button in self.buttons:
                    button.mouse_click_event(event.pos)
                for entry in self.textEntries:
                    entry.mouse_click_event(event.pos)
        
        # Update and draw
        if not self.paused:
            msg1 = ''
            msg2 = ''
            self.mboard_text = [msg1, msg2]
            self.draw()
        
        # Flip the display buffer
        pygame.display.flip()

Frame Rate Control

The game uses pygame.time.Clock to maintain a consistent frame rate:
self.clock = pygame.time.Clock()
The clock limits the game to 30 frames per second (FPS):
self.time_passed = self.clock.tick(30)
The tick() method automatically delays the loop to maintain 30 FPS, returning the milliseconds that passed since the last call.

Frame Skip Prevention

If more than 100ms passes between frames (indicating the game was suspended), the frame is skipped to prevent physics “jumping”:
if self.time_passed > 100:
    continue

Event Handling

The game processes three main types of events:

1. Quit Events

if event.type == pygame.QUIT:
    self.quit()

2. Keyboard Events

Keyboard events are only processed when text entries are not active:
elif event.type == pygame.KEYDOWN and not active:
    if event.key == pygame.K_SPACE:
        self.paused = not self.paused
    elif event.key == pygame.K_g:
        self.options['draw_grid'] = not self.options['draw_grid']
  • Press SPACE to pause/unpause the game
  • Press G to toggle the grid overlay

3. Mouse Events

Left mouse button clicks are dispatched to interactive widgets:
elif (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
    for button in self.buttons:
        button.mouse_click_event(event.pos)
    for entry in self.textEntries:
        entry.mouse_click_event(event.pos)

Update and Draw Cycle

The game only updates and draws when not paused:
if not self.paused:
    msg1 = ''
    msg2 = ''
    self.mboard_text = [msg1, msg2]
    self.draw()
The draw() method (game.py:186-197) handles all rendering:
def draw(self):
    # Draw background image
    self.draw_background()
    
    # Decide if we should draw grid
    if self.options['draw_grid']:
        self.draw_grid()
        
    self.tboard.draw()
    
    for obj in self.world:
        obj.draw()
All drawing happens to an off-screen buffer. The final pygame.display.flip() swaps the buffers to display the rendered frame.

Pause Functionality

The pause system is controlled by a boolean flag:
self.paused = False  # Initialized in __init__ (game.py:122)
When paused:
  • Events are still processed (allowing unpause)
  • Game logic and rendering are skipped
  • The display continues to show the last rendered frame
The pause mechanism is simple but effective - it completely halts game updates while keeping the event system responsive.

Game Loop Flow

The loop continues indefinitely until a QUIT event is received, at which point sys.exit() is called.

Build docs developers (and LLMs) love