Crash when using Sleep() in Scene module
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()
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
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)
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
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
if progress == 1: node.color = color4 if self.win else color3
Could green_list instead be green_set to automatically avoid duplicates?
I'm not familiar with that concept.
For simplicity here I have omitted that I used a
square.statevariable 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_winmethod 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! :)
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.