#!/glimmer/bin/python """Allows communication between LocalWorlds via a WorldServer""" import socket, sys, select from mysockets import MySocket sys.path.append('../model') from LocalWorld import LocalWorld from Instruction import Instruction sys.path.append('../gui') import BoardScreen from WorldSpec import WorldSpec import SpeciesDatabase import Direction ''' Some discussion of the client: We can't leave the client in a busy loop all the time, because without threads the GUI attached to it will be rather unresponsive. My proposal: Instead of one busy loop, we add to WorldClient a "poll" method that checks the sockets and informs the GUI what it should do. In other cases, if the client would transmit data over the network (because a cell is clicked or something (call to SetSpecies()), we go ahead and do the transmission and get the response. Hopefully this will remain responsive... ''' class WorldClient(MySocket): def __init__(self): '''Parameters: serverhostname, the name of the machine that is the host port: an integer in [0 65535], gui object still needs implementation. Preconditions: there must be a worldserver at serverhostname using port port must not be in use by procedures not related to isimGrin [unspecified behavior if broken] Postconditions: This will connect to the server at serverhostname at port and be able to communicate with it. This will be able to recongize if the server is full.''' """Constructs a WorldClient object Socket is defaulted at the localhost with port # 32333""" '''as far as I can tell, we need to include the following things worldSpecifications: (see worldspec.py): a species database x and y sizes for the board the default species of the board ''' '''tell the client about the worldspecifications!''' self.client = None self.cases = [\ (str, self.stringhandler), \ (sys.stdin, self.stdinhandler),\ (self.client, lambda :self.choosehandler(self.read(self.client))),\ (list, self.listhandler)\ ] def connect(self, hostname, port = 32323): '''use connect to get the world from the server and generate it''' self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client.connect((hostname, port)) self.ws = self.read(self.client) print("got stuff from server") assert(isinstance(self.ws, WorldSpec)) self.world = LocalWorld.fromWorldSpec(self.ws) assert(isinstance(self.world, LocalWorld)) self.sockets = [sys.stdin, self.client] SpeciesDatabase.SpeciesDatabase.setInstance(self.world.specDatabase) print("connected") '''we somehow failed to connect to the server''' '''generate the LocalWorld stuff''' def addListener(self, listener): '''add a listener (most likely the GUI), and start the main loop''' self.listener = listener def disconnect(self): '''Shut down the client socket. Procedure will send a None object to the server.''' self.client.shutdown(socket.SHUT_RDWR) self.client.close() def setSpecies(self, x, y, species): '''setSpecies: accept a species setting instruction from the listener, pack it into an instruction, and send it to the server. Return True if the server accepts the change, else return False''' i = Instruction(Direction.C, species, (x, y)) self.write(self.client, i) return self.read(self.client) def getSpecies(self, x, y): '''return the species at some location in the default world pulled from the server when we first joined''' return self.world.getSpecies(x, y) def serveraccessor(self): return self.client def update(self): """See if there is anything new on the network to give the client.""" read, write, err = select.select(self.sockets, [], []) for r in read: self.choosehandler(r) def choosehandler(self, obj): '''compare obj to the first item in each tuple of case and execute the second item if they match. There should probably be a separate handler for sockets''' if obj == self.client: self.choosehandler(self.read(obj)) else: for expected, response in self.cases: try: if isinstance(obj, expected): response(obj) except TypeError as e: if obj == expected: response() def listhandler(self, li): '''handles lists: checks that every object in ls is an instruction before sending it to update method. Will have no effect if any element of ls is not an instruction.''' if [isinstance(obj, Instruction) for obj in li]: self.listener.update(li) #tell the observers to update #should we do something with this list if it does not fit #the preconditions? Maybe a return that alarms of failure? def stringhandler(self, st): '''print strings to stdout, for now''' print(st) #GUI.display(st) def stdinhandler(self): '''send stdin to the server, or disconnect''' inp = sys.stdin.readline().strip() if inp == "quit": self.disconnect() else: print ("sending to server") self.write(self.client, inp) def step(self): read, write, err = select.select(self.sockets, [], [], 0) while(read != []): for r in read: self.choosehandler(r) read, write, err = select.select(self.sockets, [], [], 0) if __name__=='__main__': """Checks that valid command line args were received""" if len(sys.argv) == 2: WorldClient(str(sys.argv[1])).main() elif len(sys.argv) == 3: WorldClient(str(sys.argv[1]), int(sys.argv[2])).main() else: print("Usage: ./client.py hostname [port]") sys.exit(1) '''we can handle more different types of objects from the server by writing more handlers. What design pattern is this? We should probably do it right...'''