Forum Archive

Crash when using Sleep() in Scene module

chriswilson

Hi all,

I have been using Pythonista to learn Python for a few months (I'm new to coding) and have been exploring the Scene module since the recent update.

I've made a grid-based game in which players make a white path across the grid, and if successful, all the squares making up the path turn green.

I use a function (see below) to recursively check for success (not sure if this is the best way or not) by checking the neighbours of each tile as the "path"
progresses through the grid.

It worked well until introducing time.sleep() as a very brief delay in order to "animate" the progress of the squares turning green. Now it crashes Pythonista but not in a predictable way, though it does seem to be when the function is running. The function is decorated with @ui.in_background.

I'm just looking for any advice on how to improve my code to prevent this, or whether other people have had a similar problem.

I can post the whole code if needed or if anyone interested.

    @ui.in_background
    def go(self, start_square):
        try:
            self.green_list.remove(start_square)
        except:
            pass
        for square in start_square.white_neighbours(self.squares):
            self.green_list.append(square)
            square.run_action(go_action)
            square.state = 3
            square.color = color4
            sleep(0.004)
            self.go(square)
        if len(self.green_list) == 0:
            sleep(0.004)
            self.check_win()

Cethric

Can you please post your code (preferably on GitHub or other git hosting service) as I can not get enough infomation out of this code to be able to find where the issue occurs.
Can I also recommend that instead of a try: except: clause you check to see if start_square is in self.green_list

JonB

not sure why sleep would be an issue, but one problem you will find when using in_background is that calls are queued up, rather than executing in order. So, ehile you might think you are doing a depth first search, you code really does a breadth first. If your go_action is also in_backgrounded, you might have issues, where code does not execute in the order you planned.

I suspect if you get another touch event while go is running, this could cause issues, because you might be inserting a call to go where it doesn't belong. i.e the loop has queued up 4 calls to go(), but then a touch event wueues up another square to go(), then the 4 calls run, each queueing up other calls, then the extra touch runs, on the original square. Now everything is in a funky state.

You might consider a non-backgrounded function, with ui.animate() to set colors. perhaps with a completion argument to call the next iteration.

Another option would be to use a deque, and rather than recursing, you would add neighbors to the deque. Though recursion makes for nice compact code, it is often frowned upon in real world applications. A method I like for this sort of problem is a single worker loop with a deque. You populate the start square, then loop until the deque is empty. Depending on which side you pop/append you can make this do depth or breadth first.

while len(mydeque):
    square =mydeque.pop()
    #process square.......... change color, etc
    for n in square.white_neighbors():
         if n not in mydeque:
             mydeque.append(n) 
chriswilson

Thanks @Cethric and @JonB for the advice.

I've posted the code here.

chriswilson

I thought I should post the end result of this discussion.

I incorporated the deque idea from @JonB and got the animation effect I needed using the Action.call() method without having to use time.sleep() or a @ui.in_background function.

Here it is in a shortened form to show the concept without unnecessary application-specific stuff:

def go(self, start_square):

    def cascade(node, progress):  # Nested animation function
        if progress == 1:
            node.color = color4 if self.win else color3

    self.green_list.append(start_square)
    index = 0.01
    while self.green_list:
        square = self.green_list.pop(randint(0, len(self.green_list) - 1))
        square.run_action(A.call(cascade, index))
        index += 0.01

        for n in square.white_neighbours(self.squares):
            if n not in self.green_list:
                self.green_list.append(n)

    self.check_win()  # Once list is empty, check win status
ccc
        if progress == 1:
            node.color = color4 if self.win else color3

Could green_list instead be green_set to automatically avoid duplicates?

chriswilson

@ccc
I'm not familiar with that concept.

For simplicity here I have omitted that I used a square.state variable which changes without any additional delay and prevents a square being added to the list once it has already been 'dealt with'. The colour-changing animation then cascades along behind this. The self.check_win method then checks the state of the square at the exit of the grid to determine a win or lose.

Perhaps I should not have omitted this from the code snippet. Mainly wanted to show use of the Action.call() method which was new to me.

Thanks for your input as always! :)

ccc

That concept means conditional expressions or sets?

chriswilson

@ccc
Ah I see what you mean. I often forget to use conditional expressions when I could. As regards sets, I guess I know the concept but I'm not familiar with how to use them. I'll look at this. Thanks.