Unable to avoid the networking issue any longer, I have made a couple of forays into how to add networking to “Pitcher’s Duel”. One way or another, Twisted will be involved, as they have a very reputable code base, and using the standard lib modules does not seem to have any relative merits. They all have the same major limitations.

The principle limitation is that the network ‘library/engine/framework’ wants to own the main loop. That is fine for a server, it is a PITA for other sorts of software. I wrote a post on the pygame mailing list this morning summarizing the approaches to networking that I have identified, and may copy that post into the blog.

For now, I wish to describe one architecture for accomodating Twisted’s ownership of the main loop. Since the reactor, once started, never returns, the game logic has to be managed via callbacks from the reactor. Game state must be maintained in class variables, object instance variables, global variables, or generator local variables. Generators provide a relatively graceful way to code the ‘main’ function, where game state for the whole game is maintained in locals.

In lieu of the ‘intro’, ‘stage 1’, and ‘stage 2’ labels below, you could use ‘foyer’, ‘room 1’, ‘room 2’ or some other model, and have logic in the generator to order them according to user behavior.

def Engine(object): def go(self): # set up stuff reactor.callLater(0, self.iterate) def iterate(self): # do game frame update/display etc if done: engine.retval = 'whatever' reactor.callLater(0, continue_main) else: reactor.callLater(0, self.iterate) def main(): # generator global engine # set up intro engine = Engine() engine.interface = intro_iface engine.objects = [intro_pic] reactor.callLater(0, engine.go) yield None # execution resumes here after intro # set up stage 1 engine.interface = stage1_iface engine.objects = [characterObj] reactor.callLater(0, engine.go) yield None # execution resumes here after stage 1 if engine.retval != 'skip2': # set up stage 2 engine.interface = stage2_iface .... reactor.callLater(0, engine.go) yield None # setup stage 3 engine.interface = stage3_iface ..... reactor.callLater(0, engine.go) yield None # done reactor.stop() yield None continue_main = main().next # creates iterator continue_main() # runs to first yield, installing first engine in reactor reactor.run()

The above code reuses the same engine, resetting it between uses, but it could use multiple engines, in case you needed to specialize an engine for some purpose. Transferring resuts from an engine run is handled by use of a ‘retval‘ attribute on the engine, but global variables or function attributes would work as well.

This post is long enough, so more will follow in the next post. Feel free to comment, particularly if you know a better way to structure such a program.