1

I'm writing a program to plays chess against the computer. I have a Pygame window that shows the chessboard and lets you select your move. Once you have played your move the engine starts calculating the moves' tree, to find the best move. When it has finished it replays with its move. The problem is that while the engine is "thinking" the Pygame window is not updated for a while (it can be a lot, even a minute or more). And the OS prompts the message "not responding" on the window after 5 seconds of inactivity. Here is a Minimum Reproducible Example:

import pygame
from time import sleep

def engine_play():
    # Here the engine searches for the best move for some time
    for _ in range(1000000000):
        a = 1+1


pygame.init()
clock = pygame.time.Clock()
clock.tick(60)
WIN = pygame.display.set_mode((500, 500))

engine_play()

# When the engine stops searching we get the events 
pygame.event.get()

When the engine stops and pygame.event.get() is called, the message disappears and everything is fine. The main problem is that if you click on the window during this time Windows warns you that the program is not responding and asks you if you want to close it or to wait.

Does anyone know how to fix this? Thanks in advance!

  • Multithreading: Let the engine run a a separate thread and your main thread continues the main loop. – MegaIng Apr 07 '21 at 12:10
  • See also [Pygame window not responding when not refreshing for some time](https://stackoverflow.com/questions/56747229/pygame-window-not-responding-when-not-refreshing-for-some-time/56747864#56747864) – sloth Apr 07 '21 at 13:17

2 Answers2

3

You will need to make sure the operating system window events are being handled, or you do get that dreaded "Not Responding" thing, exactly because, well, you're not responding to events.

In general, games always have a main loop which runs all the time and pumps events, updates game logic, draws the screen and so on. (Depending on the complexity of the app and how you design things, menus might have another loop, but that's beside the point.)

Using threads

To run things concurrently in your program, you might think you can pump these events in another thread, but that's not safe to do with Pygame, so you'll instead need to do all the other things in a secondary thread:

Caution: pygame.event.pump() should only be called in the thread that initialized pygame.display.

Using threads will get a little hairy since there's no direct way to return a value from the thread, and knowing whether a thread is done also takes an event. Something like this (might be buggy) should do the trick.

import threading

def engine_play(finish_event, output_list):
    # Here the engine searches for the best move for some time
    a = 0
    for _ in range(1000000000):
        a += _
    output_list.append(a)  # could also use a queue
    finish_event.set()

def run_engine_play():
    finish_event = threading.Event()
    output_list = []
    thread = threading.Thread(target=engine_play, args=(finish_event, output_list))
    thread.start()
    return (finish_event, output_list)

finish_event, output_list = run_engine_play()

while True:
    for event in pygame.event.get():
        pass  # handle events...
    if finish_event.is_set():
        print(output_list[0])

Using futures

You could abstract away the complexity of threads by using concurrent.futures, but the main point stands: you have a task (a future) you'll need to wait to finish.

Using a generator

You can turn engine_play() into a generator function to avoid using threads, but you'll need to take care that the engine function yields control back to the main loop every now and then.


def engine_play():
    # Here the engine searches for the best move for some time
    a = 0
    for _ in range(1000000000):
        a += _
        if _ % 1000 == 0:  # every now and then, let the loop do other things
             yield None  # Not done yet...
    yield a

# Instantiate the generator
engine_play_gen = engine_play()


while True:
    for event in pygame.event.get():
        pass  # handle events...
    val = next(engine_play_gen)  # let the engine do its thing
    if val is not None:
        print(val)
AKX
  • 152,115
  • 15
  • 115
  • 172
-1

A bit of a messier solution, but it disregards the use of threads. In your engine_play function, you run a loop. Waiting for the loop to finish before updating causes a huge amount of delay in your program, enough to make the OS think your program is not responding

Here is where it gets more interesting. The OS thinks you're responding as long as you're handling events. So all you have to do is make some event calls intermittently throughout your program. Example:

def engine_play():
    # Here the engine searches for the best move for some time
    for _ in range(1000000000):
        a = a+1
        if a%500 == 0:
            pygame.event.get()

This function calls the event poller every 500 cycles. This is enough time to make the OS think you're still respinding, even though you're not.

The problem with the above code is that you're throwing away events. If you want to catch these, make an event queue of your own, and handle from there:

queue = []
def engine_play():
    global queue
    # Here the engine searches for the best move for some time
    for _ in range(1000000000):
        a = a+1
        if a%500 == 0:
            queue += pygame.event.get()
...
while running:
    for event in (pygame.event.get()+queue):
        queue = []
        #handle events

In the above program, It maintains a list of all the events that were missed, and handles them along with current events the moment the program returns to the mainloop.

User 12692182
  • 927
  • 5
  • 16
  • Thanks a lot! I implemented your solution and solved the problem. – Enrico Bussetti Apr 07 '21 at 14:39
  • I am pretty sure it is enough to call `pygame.event.poll()`. This doesn't throw away the queue, so that `get` still has the full queue available later on. – MegaIng Apr 07 '21 at 15:09
  • `get` has a queue size limit of 128, so events will ultimately get deleted, especially if the search program lasts for a much longer time – User 12692182 Apr 07 '21 at 17:33