One of my current projects is a little turn-based strategy game, somewhat similar to X-Com. (It's actually closer to Lords of Chaos, one of X-Com's predecessors.) I handle states with a basic stack, where the game calls the 'run' method of the state at the top of the stack, then updates the graphics subsystem, pumps the event queue, etc.
This is a very clean design for most things. However I now have the problem that during the AITurnState, when a computer player is making its moves, there are certain tasks that take a long time to complete and the system requires that the system returns from the 'run' method periodically (10-30 times a second) so that the main engine can perform housekeeping tasks. This is awkward because I want to be able to write clean linear code like this:
for creature in computerPlayer.creatures:
for possibleTactic in allAITactics:
evaluate(possibleTactic)
path = FindPathTo(self.chosenTactic.location)
for step in path:
move(step.x, step.y)
Yet during this code, which might take 10 seconds or so to complete normally, I'm going to want to return from inside each of those 3 for loops to allow the screen to update, to register the human player's mouse movement, etc.
One option would be to write this as a generator, and add 'yield none' liberally through the routine. Then I'd call it like so:
class AITurnState:
def __init__():
# initialise the generator with the creature list, etc
self.ai = AIGenerator(creatures, world, otherContext)
def run():
"""this is called roughly 10-30 times a second"""
try:
# Do a bit more of the AI
self.ai.next()
# Not finished yet, so please call us again
return "state-continue"
except StopIteration:
# All done; pop us off the state-stack
return "state-finished"
Another way that might sit more comfortably with C++ coders would be to do this as a couple of threads.
The first way to use threads for this might be for AITurnState.run() to do little but wait for a second thread to update the AI's creatures. Two problems here are that I'd have to use a mutex so that the graphics system in the first thread didn't try and draw a creature that was moving, or something like that. The second problem would be that a creature could perform 2 actions in the 2nd thread before the 1st thread has had time to display the results of the first, which will result in jerky motion when the player is trying to watch the computer in action.
Another option would be to use an Event object for the 2nd thread to signal the first that it could go through the update routine again. That would take place and then the Event would be reset, allowing the 2nd thread to continue with the AI. Only one of the 2 threads would be active at any time and they would be pausing in well-defined places. I see this as being a standard sort of producer-consumer type of system, although having to explicitly manage the yielding is not the elegant design I would like if at all possible.
Sadly this is all starting to sound very complicated! So before I embark on implementing one of these schemes, I thought I'd post here to see if anybody with experience of this sort of problem had any hints to lend, especially if that experience applied directly to idiomatic Pythonic features that can make life easier (hence the choice of this forum).