(Apologies for double send but I forgot the subject line)

Dear Twisted users,

I recently found myself implementing a design pattern that I think twisted.pb was specifically designed to address. I think I'm not using pb correctly so I'd like advice. This is a somewhat longish post because I need to describe the problem I'm trying to solve.

I have done internet searches on Stack Overflow and this list but have not found the answer to my question. If I've missed something kindly direct me to the appropriate reference.

I want to implement something functionally equivalent to a network chess game. I first consider how I would do this on a single computer with no network (maybe this is bad thinking). Each piece in the game is represented by an instance of class Agent. Each agent has a .graphics attribute which is an instance of a class from a GUI toolkit library or equivalent. Whenever an agent in the game needs to do something there will be business logic executed by the game objects proper (ie the agents) which will invoke methods on the .graphics objects to update the screen. This sort of structure seems natural as it allows easy integration of drag/drop, mouse click detection etc. It also nicely separates the real business logic from the GUI.

Now I want to run over the network. The question is how should I set up references between the client and server objects?

Surely the server will maintain a set of objects representing the pieces in the game. It seems reasonable that each user's program will have a corresponding set of objects (with .graphics attributes). The issue is, what do we mean by "corresponding" and how do these objects talk to one another? Following is my idea so far:

Each instance of AgentClient has a .server attribute which is a remote reference to an instance of AgentServer, and each instance of AgentServer has a .clients attribute which is a list of remote references to instances of AgentClient.

class AgentServer(pb.referenceable):
   
    def remote_move(self, targetSquare):
        """Handle move request from client"""
        if self.thisMoveIsLegal(targetSquare):
            self.position = targetSquare
            for client in self.clients:
                client.callRemote("move", targetSquare)
   
    def thisMoveIsLegal(self, targetSquare):
        <check that this is a legal move>
   
class AgentClient(pb.referenceable):
   
    def requestMove(self, targetSquare):
        """Tell server we'd like to move"""
        self.server.callRemote("move", targetSquare)
   
    def remote_move(self, targetSquare):
        """Server told us we moved"""
        self.position = targetSquare
        self.graphics.setNewPosition(targetSquare)

This isn't THAT bad. The client's requestMove is thin and unecessary (I put it there for illustration). Still I need to have two separate classes with corresponding methods to handle moving the piece. This seems like the kind of thing I could twisted.pb to solve more cleanly if I only would look in the right place.

This problem gets even worse when I think about how to birth new in-game objects. It would have to look like this:

class PlayerServer(pb.referenceable):
   
    def newAgent(self, asker):
        """Client told us it wants a new Agent"""
        if self.thisIsLegal():
            a = AgentServer()
            self.agents.append(a)
            for client in self.clients:
                d = client.callRemote("newAgent", a)
                d.addCallback(lambda obj: a.clients.append(obj))
   
class PlayerClient(bp.referenceable):
   
    def requestNewAgent(self):
        """Tell the server we want to spawn a new Agent"""
        self.server.callRemote("newAgent", self)
   
    def newAgent(self, serverObj):
        a = AgentClient()
        self.agents.append(a)
        a.server = serverObj
        return a

This just looks wrong. Any advice?

Thank you in advance for your help.

Regards,
Daniel Sank