class EnvironmentTests(TestCase, ArrayMixin): """ Tests for L{game.environment.Environment}. """ def setUp(self): """ Create an L{Environment} attached to a L{Clock} so that its behavior is deterministic. """ self.clock = Clock() self.environment = Environment(1, self.clock) self.environment.start() def test_terrain(self): """ An L{Environment} should start with an empty terrain array. """ self.assertEquals(self.environment.terrain.dict(), {}) def test_createPlayer(self): """ L{Environment.createPlayer} should instantiate a L{Player} and broadcast it to all registered observers. """ position = Vector(1, 2, 3) speed = 20 observer = PlayerVisibilityObserver() self.environment.addObserver(observer) player = self.environment.createPlayer(position, speed) self.assertEqual(observer.createdPlayers, [player]) self.assertEqual(player.getPosition(), position) self.assertEqual(player.speed, speed) self.assertEqual(player.seconds, self.environment.seconds) def test_removePlayer(self): """ L{Environment.removePlayer} should broadcast C{playerRemoved} to all registered observers. """ position = Vector(1, 2, 3) speed = 20 observer = PlayerVisibilityObserver() self.environment.addObserver(observer) player = self.environment.createPlayer(position, speed) self.environment.removePlayer(player) self.assertEqual(observer.createdPlayers, observer.removedPlayers) def test_setInitialPlayer(self): """ L{Environment.setInitialPlayer} should change the environment's C{initialPlayer} attribute from C{None} to its argument. """ self.assertIdentical(self.environment.initialPlayer, None) player = object() self.environment.setInitialPlayer(player) self.assertIdentical(self.environment.initialPlayer, player) def test_setNetwork(self): """ L{Environment.setNetwork} changes the environment's C{network} attribute from C{None} to its argument. """ self.assertIdentical(self.environment.network, None) network = object() self.environment.setNetwork(network) self.assertIdentical(self.environment.network, network)
class NetworkController(AMP): """ A controller which responds to AMP commands to make state changes to local model objects. @ivar modelObjects: A C{dict} mapping identifiers to model objects. @ivar clock: A provider of L{IReactorTime} which will be used to update the model time. """ environment = None def __init__(self, clock): self.modelObjects = {} self.clock = clock def addModelObject(self, identifier, modelObject): """ Associate a network identifier with a model object. """ self.modelObjects[identifier] = modelObject modelObject.addObserver(self) def directionChanged(self, modelObject): """ Notify the network that a local model object changed direction. @param modelObject: The L{Player} whose direction has changed. """ d = self.callRemote( SetMyDirection, direction=modelObject.direction, y=modelObject.orientation.y) d.addCallback(self._gotNewPosition, modelObject) # XXX Add an errback def _gotNewPosition(self, position, player): """ Update a L{Player}'s position based on new data from the server. @param player: The L{Player} whose position to change. @param position: Dict with C{x} and C{y} keys, whose values should be integers specifying position. """ player.setPosition(Vector(position['x'], position['y'], position['z'])) def createInitialPlayer(self, environment, identifier, position, speed): """ Create this client's player as the initial player in the given environment and add it to the model object mapping. """ player = environment.createPlayer(position, speed) environment.setInitialPlayer(player) self.addModelObject(identifier, player) def introduce(self): """ Greet the server and register the player model object which belongs to this client and remember the identifier with which it responds. """ d = self.callRemote(Introduce) def cbIntroduce(box): granularity = box['granularity'] position = Vector(box['x'], box['y'], box['z']) speed = box['speed'] self.environment = Environment(granularity, self.clock) self.environment.setNetwork(self) self.createInitialPlayer( self.environment, box['identifier'], position, speed) return self.environment d.addCallback(cbIntroduce) return d def objectByIdentifier(self, identifier): """ Look up a pre-existing model object by its network identifier. @type identifier: C{int} @raise KeyError: If no existing model object has the given identifier. """ return self.modelObjects[identifier] def identifierByObject(self, modelObject): """ Look up the network identifier for a given model object. @raise ValueError: If no network identifier is associated with the given model object. @rtype: L{int} """ for identifier, object in self.modelObjects.iteritems(): if object is modelObject: return identifier raise ValueError("identifierByObject passed unknown model objects") def setDirectionOf(self, identifier, direction, x, y, z, orientation): """ Set the direction of a local model object. @type identifier: L{int} @type direction: One of the L{game.direction} direction constants @see: L{SetDirectionOf} """ player = self.objectByIdentifier(identifier) player.setDirection(direction) player.setPosition(Vector(x, y, z)) player.orientation.y = orientation return {} SetDirectionOf.responder(setDirectionOf) def newPlayer(self, identifier, x, y, z, speed): """ Add a new L{Player} object to the L{Environment} and start tracking its identifier on the network. @param identifier: The network-level identifier of the player. @param x: The x position of the new L{Player}. @param y: The y position of the new L{Player}. @param z: The z position of the new L{Player}. """ player = self.environment.createPlayer(Vector(x, y, z), speed) self.modelObjects[identifier] = player return {} NewPlayer.responder(newPlayer) def removePlayer(self, identifier): """ Remove an existing L{Player} object from the L{Environment} and stop tracking its identifier on the network. @param identifier: The network-level identifier of the player. """ self.environment.removePlayer(self.objectByIdentifier(identifier)) del self.modelObjects[identifier] return {} RemovePlayer.responder(removePlayer) def setTerrain(self, x, y, z, voxels): """ Add new terrain information to the environment. @param x: The x coordinate where the given voxels begin. @param y: The y coordinate where the given voxels begin. @param z: The z coordinate where the given voxels begin. @param voxels: An L{numpy.array} specifying terrain information starting at the specified location and proceeding in the positive direction along all axes """ self.environment.terrain.set(x, y, z, voxels) return {} SetTerrain.responder(setTerrain)