def test_BASESETTINGS_getset_defaultsdosomething(): a = BaseSettings( {"test_default1": { "type": "string", "default": "value1" }}) ov = "value1" nv = a.get("test_default1") assert nv == ov, "defaults are populating correctly in absence of a set"
def test_BASESETTINGS_getset_defaultsdontoverride(): a = BaseSettings( {"test_default2": { "type": "string", "default": "value2" }}) ov = "value1" a.set("test_default2", ov) nv = a.get("test_default2") assert nv == ov, "set is overriding default arguments when supplied"
class BaseFloor: def __init__(self, attributes=None): """init() with no parameters or init(dict) can specify a dictionary of attributes""" self.settings = BaseSettings({ "id": { "type": "string", "validation": "", "default": "", "comment": "A unique string identifying the object. Generally not human friendly.", }, "height": { "type": "float", "validation": "gt_zero", "default": 3.00, "comment": "The height of the floor.", }, "elevation": { "type": "float", "validation": "", "default": 0.00, "comment": "The distance of the bottom-most point of the object from the ground.", }, "elevation_top": { "type": "float", "validation": "", "default": 0.00, "comment": "The distance of the top-most point of the object from the ground.", }, "label": { "type": "string", "validation": "", "default": "", "comment": "A human friendly descriptor of the object.", }, "carrying": { "type": "list", "validation": "", "default": [], "comment": "A list of the people contained within the floor object.", }, "building": { "type": "building", "validation": "", "default": None, "comment": "A reference to the parent building object.", }, "is_floor": { "type": "boolean", "validation": "", "default": True, "comment": "A boolean indicating whether we are a floor object or not.", }, }) if attributes is not None: for key in attributes: self.set(key, attributes[key]) self.set("id", SOLE.new_id("BaseFloor")) def __str__(self): """allow print() to function in some intelligible way""" return "{}".format(self.settings) def set(self, name, value): """set() will set the given attribute for the object. Will perform basic sanity checks on the attribute itself.""" self.settings.set(name, value) return self def get(self, name): """get(attr) will return attribute attr for the object or empty string if not""" return self.settings.get(name) def tick(self): """tick() will advance one step for this object and any/all objects contained by it""" SOLE.log("[{}] BaseFloor->tick()".format(self.get("id")), SOLE.LOG_INFO) for p in self.get("carrying"): p.tick() return def add_to_request_queue(self, floor_id): """add_to_request_queue() will pass on the request for floor_id to associated elevator for floor""" self.get("building").get("elevators")[0].add_to_request_queue(floor_id)
class BaseElevator: def __init__(self, attributes=None): """init() with no parameters or init(dict) can specify a dictionary of attributes""" self.settings = BaseSettings( { "height": { "type": "float", "validation": "gt_zero", "default": 2.44, "comment": "The height of the elevator itself.", }, "elevation": { "type": "float", "validation": "", "default": 0.00, "comment": "The elevation of the top-most point of the elevator.", }, "destination": { "type": "floor", "validation": "", "default": None, "comment": "The current destination floor that we are moving towards. Set to None when not in motion.", }, "velocity": { "type": "float", "validation": "", "default": 0.00, "comment": "Positive velocity means upwards, negative means downward, 0 is at rest", }, "label": { "type": "string", "validation": "", "default": "", "comment": "A friendly identifier for the elevator", }, "maximum_up_speed": { "type": "float", "validation": "gt_zero", "default": 1.00, "comment": "A maximum upward velocity for the elevator, must be >0", }, "maximum_down_speed": { "type": "float", "validation": "lt_zero", "default": -1.00, "comment": "A maximum downward velocity for the elevator, must be <0", }, "carrying": { "type": "list", "validation": "", "default": [], "comment": "A list of Person objects presently within the elevator", }, "building": { "type": "building", "validation": "", "default": None, "comment": "A reference to the current building object the elevator is contained within.", }, "floor_requests": { "type": "list", "validation": "", "default": [], "comment": "A list of floor requests in sequential order.", }, "status": { "type": "string", "validation": "", "default": "idle", "comment": "The current status of the elevator.", }, "status_percent": { "type": "float", "validation": "is_percent", "default": 1.00, "comment": "A float inside [0,1] indicating percentage completion of status.", }, "unloading_time_needed": { "type": "float", "validation": "gt_zero", "default": 5.00, "comment": "Real world seconds that it takes to unload the elevator.", }, "loading_time_needed": { "type": "float", "validation": "gt_zero", "default": 5.00, "comment": "Real world seconds that it takes to load the elevator.", }, "id": { "type": "string", "validation": "", "default": "", "comment": "A unique string identifying the object. Generally not human friendly.", }, } ) if attributes is not None: for key in attributes: self.set(key, attributes[key]) self.set("id", SOLE.new_id("BaseElevator")) def __str__(self): """allow print() to function in some intelligible way""" return str(self.__class__) + ": " + str(self.__dict__) def set(self, name, value): """set() will set the given attribute for the object. Will perform basic sanity checks on the attribute itself.""" self.settings.set(name, value) return self def get(self, name): """get(attr) will return attribute attr for the object or empty string if not""" return self.settings.get(name) def change_velocity(self, velocity): """change_velocity(velocity) changes elevator velocity. Pass positive float for up, negative for down, and 0 for stop.""" if velocity == 0: self.set("velocity", velocity) elif velocity > 0: self.set("velocity", self.get("maximum_up_speed")) elif velocity < 0: self.set("velocity", self.get("maximum_down_speed")) else: assert (False, "unmatched velocity") def move(self): """move() moves the elevator by one unit of velocity.""" self.set( "elevation", ( self.get("elevation") + (self.get("velocity") / SOLE.tick_ratio_to_real_time) ), ) def unload(self): """unload() iterates through the elevators carrying attribute and dumps out passengers""" SOLE.log("[{}] BaseElevator->unload()".format(self.get("id")), SOLE.LOG_INFO) elevation = self.get("elevation") b = self.get("building") floor_id = b.at_elevation(elevation) floor = b.ref_to(floor_id) carrying = self.get("carrying") if type(carrying) == list: for p in carrying: if p.get("destination") == floor: SOLE.log( "Unloaded {} from the elevator at the {}".format( p.get("label"), floor.get("label") ) ) p.unload(self, floor) def load(self): """load() iterates through the floors carrying attribute and loads up passenges""" SOLE.log("[{}] BaseElevator->load()".format(self.get("id")), SOLE.LOG_INFO) elevation = self.get("elevation") b = self.get("building") floor_id = b.at_elevation(elevation) floor = b.ref_to(floor_id) carrying = floor.get("carrying") if type(carrying) == list: for p in carrying: SOLE.log( "Loaded {} into the elevator from the {}".format( p.get("label"), floor.get("label") ) ) p.load(self, floor) def add_to_request_queue(self, floor): """add_to_request_queue(floor_id) will add floor_id to the list of floors to travel to""" SOLE.log( "[{}] BaseElevator->add_to_request_queue({})".format(self.get("id"), floor), SOLE.LOG_INFO, ) self.get("floor_requests").append(floor) def queue(self): """queue() will iterate through the queue of floor_requests if there is no current desination_floor""" SOLE.log("[{}] BaseElevator->queue()".format(self.get("id")), SOLE.LOG_INFO) if self.get("destination") is None: if type(self.get("floor_requests")) == list: if len(self.get("floor_requests")) > 0: floor_requests = self.get("floor_requests") destination = floor_requests.pop(0) if destination is None: pass else: self.set("destination", destination) SOLE.log( "{} moving to {}".format( self.get("label"), destination.get("label") ), SOLE.LOG_NOTICE, ) def tick(self): """tick() will advance one step for this object and any/all objects contained by it""" SOLE.log("[{}] BaseElevator->tick()".format(self.get("id")), SOLE.LOG_INFO) SOLE.log( "[{}] BaseElevator->tick() elevation={:.2f} status={} status_percent={:.0%}".format( self.get("id"), self.get("elevation"), self.get("status"), self.get("status_percent"), ), SOLE.LOG_DEBUG, ) # iterate through each of the people we are carrying and let them tick() for p in self.get("carrying"): p.tick() valid_statuses = ( "idle", "unloading", "loading", "moving", "doors_opening", "doors_open", "doors_closing" ) b = self.get("building") elevation = self.get("elevation") destination = self.get("destination") status = self.get("status") status_percent = self.get("status_percent") # from unloading state, we can either continue unloading or start loading if status == "unloading": self.unload() if status_percent >= 1.00: self.set("status", "loading") self.set("status_percent", 0) return self.set("status", "unloading") self.set( "status_percent", min( status_percent + 1 / ( self.get("unloading_time_needed") * SOLE.tick_ratio_to_real_time ), 1, ), ) return # from loading state, we can either continue loading or start idle if status == "loading": self.load() if status_percent >= 1.00: self.set("status", "idle") self.set("status_percent", 0) return self.set("status", "loading") self.set( "status_percent", min( status_percent + 1 / (self.get("loading_time_needed") * SOLE.tick_ratio_to_real_time), 1, ), ) return # from idle state, we can either continue idle or start moving if status == "idle": if destination is None: # check if there is any new destinations to move to self.queue() destination = self.get("destination") if destination is None: # continue to wait if there is still no destination floor self.set(status, "idle") self.set(status_percent, 1.00) return if destination is not None: # there is a destination floor so start moving next tick self.set("status", "moving") self.set("status_percent", 0.00) return if status == "moving": destination_elevation = destination.get("elevation") distance = destination_elevation - elevation velocity = self.get("velocity") SOLE.log( "BaseElevator (moving) my_elevation={:.2f} destination={} destination_elevation={:.2f} distance={:.2f} velocity={:.2f}".format( self.get("elevation"), self.get("destination").get("id"), destination_elevation, distance, velocity, ), SOLE.LOG_DEBUG, ) # if we've arrived then stop and unload next tick if distance == 0: SOLE.log( "{} has arrived at {}".format( self.get("label"), destination.get("label") ), SOLE.LOG_NOTICE, ) self.set("destination", None) self.change_velocity(0) self.set("status", "unloading") self.set("status_percent", 0) return if (distance > 0) and ( distance < (velocity / SOLE.tick_ratio_to_real_time) ): SOLE.log( "{} has arrived at {}".format( self.get("label"), destination.get("label") ), SOLE.LOG_NOTICE, ) self.set("destination", None) self.set("elevation", destination_elevation) self.change_velocity(0) self.set("status", "unloading") self.set("status_percent", 0) return elif (distance < 0) and ( distance > (velocity / SOLE.tick_ratio_to_real_time) ): SOLE.log( "{} has arrived at {}".format( self.get("label"), destination.get("label") ), SOLE.LOG_NOTICE, ) self.set("destination", None) self.set("elevation", destination_elevation) self.change_velocity(0) self.set("status", "unloading") self.set("status_percent", 0) return else: self.change_velocity(distance) velocity = self.get("velocity") self.move() self.set("status", "moving") self.set( "status_percent", 1.00 ) # TOFIX: NEED TO KEEP TRACK OF RELATIVE DISTANCE TRAVELLED FOR % TO WORK return return
def test_BASESETTINGS_getset_iscopy(): a = BaseSettings({"test_iscopy": {"type": "string"}}) ov = "foo" a.set("test_iscopy", ov) nv = a.get("test_iscopy") assert nv is ov, "set/get are returning a copy rather than the original object"
def test_BASESETTINGS_getset_list(): a = BaseSettings({"test_list": {"type": "list"}}) v = [1, 2, 3] a.set("test_list", v) assert a.get("test_list") == v, "set/get don't match for list type"
def test_BASESETTINGS_getset_dict(): a = BaseSettings({"test_dict": {"type": "dict"}}) v = {"a": "b", "c": "d"} a.set("test_dict", v) assert a.get("test_dict") == v, "set/get don't match for dict type"
def test_BASESETTINGS_getset_float_intok(): a = BaseSettings({"test_float": {"type": "float"}}) v = 3 a.set("test_float", v) assert a.get("test_float") == v, "set/get doesn't cast int type"
def test_BASESETTINGS_getset_float(): a = BaseSettings({"test_float": {"type": "float"}}) v = 3.14159 a.set("test_float", v) assert a.get("test_float") == v, "set/get don't match for float type"
def test_BASESETTINGS_getset_string(): a = BaseSettings({"test_string": {"type": "string"}}) v = "var" a.set("test_string", v) assert a.get("test_string") == v, "set/get don't match for string type"
class BaseBuilding: def __init__(self, attributes=None): """init() with no parameters or init(dict) can specify a dictionary of attributes""" self.settings = BaseSettings({ "id": { "type": "string", "validation": "", "default": "", "comment": "A unique string identifying the object. Generally not human friendly.", }, "label": { "type": "string", "validation": "", "default": "", "comment": "A human friendly descriptor of the object.", }, "height": { "type": "float", "validation": "", "default": 0.00, "comment": "The overall height of the building.", }, "floors": { "type": "list", "validation": "", "default": [], "comment": "The floors contained in the building.", }, "elevators": { "type": "list", "validation": "", "default": [], "comment": "The elevators contained in the building.", }, "_elevation_of": { "type": "dict", "validation": "", "default": {}, "comment": "A mapping of objects->elevations.", }, "_at_elevation": { "type": "dict", "validation": "", "default": {}, "comment": "A mapping of elevations->objects.", }, "_ref_to": { "type": "dict", "validation": "", "default": {}, "comment": "A mapping of ids->objects.", }, }) if attributes is not None: for key in attributes: self.set(key, attributes[key]) self.set("id", SOLE.new_id("BaseBuilding")) running_height = 0 floors = self.get("floors") if type(floors) == list: for f in floors: floor_id = f.get("id") f.set("elevation", running_height) running_height += f.get("height") f.set("elevation_top", running_height) f.set("building", self) self.set("height", running_height) elevators = self.get("elevators") if type(elevators) == list: for e in elevators: e.set("building", self) def __str__(self): """allow print() to function in some intelligible way""" return "{}".format(self.settings) def set(self, name, value): """set() will set the given attribute for the object. Will perform basic sanity checks on the attribute itself.""" self.settings.set(name, value) return self def get(self, name): """get(attr) will return attribute attr for the object or empty string if not""" return self.settings.get(name) def elevation_of(self, object_id): eo = self.get("_elevation_of") if object_id is None: return 0 if object_id in eo: return eo[object_id] else: return None def at_elevation(self, height): ae = self.get("_at_elevation") if height in ae: return ae[height] else: return None def ref_to(self, object_id): rt = self.get("_ref_to") if object_id in rt: return rt[object_id] else: return None def rebuild_internal_tracking(self): """rebuild_internal_tracking() maintains height tables. it is called every tick()""" _elevation_of = dict() _at_elevation = dict() _ref_to = dict() running_height = 0 floors = self.get("floors") if type(floors) == list and len(floors) > 0: for f in floors: floor_id = f.get("id") floor_elevation = f.get("elevation") _ref_to[floor_id] = f _elevation_of[floor_id] = floor_elevation _at_elevation[floor_elevation] = floor_id # carrying = f.get("carrying") # if type(carrying) == list and len(carrying) > 0: # for p in carrying: # person_id = p.get("id") # person_elevation = floor_elevation # p.set("elevation", person_elevation) # _ref_to[person_id] = p # _elevation_of[person_id] = person_elevation self.set("_elevation_of", _elevation_of) self.set("_at_elevation", _at_elevation) elevators = self.get("elevators") if type(elevators) == list: for e in elevators: elevator_id = e.get("id") elevator_elevation = e.get("elevation") _ref_to[elevator_id] = e _elevation_of[elevator_id] = elevator_elevation # carrying = e.get("carrying") # if type(carrying) == list and len(carrying) > 0: # for p in carrying: # person_id = p.get("id") # person_elevation = elevator_elevation # p.set("elevation", person_elevation) # _ref_to[person_id] = p # _elevation_of[person_id] = person_elevation e.set("building", self) self.set("_ref_to", _ref_to) def tick(self): """tick() will advance one step for this object and any/all objects contained by it""" SOLE.log("[{}] BaseBuilding->tick()".format(self.get("id")), SOLE.LOG_INFO) self.rebuild_internal_tracking() for f in self.get("floors"): f.tick() for e in self.get("elevators"): e.tick() # for e in elevator: e.tick() # for p in people: p.tick() # for f in floors: f.tick() return
class BasePerson: def __init__(self, attributes=None): """init() with no parameters or init(dict) can specify a dictionary of attributes""" self.settings = BaseSettings( { "id": { "type": "string", "validation": "", "default": "", "comment": "A unique string identifying the object. Generally not human friendly.", }, "height": { "type": "float", "validation": "gt_zero", "default": 1.77, "comment": "The height of the person.", }, "location": { "type": "reference", "validation": "", "default": None, "comment": "A reference to the object the person is contained in, whether floor or elevator.", }, "building": { "type": "building", "validation": "", "default": None, "comment": "A reference to the parent building.", }, "destination": { "type": "reference", "validation": "", "default": None, "comment": "A reference to the floor/elevator that we are destined to.", }, "label": { "type": "string", "validation": "", "default": sole_baseperson_random_name(), "comment": "A human friendly descriptor of the object.", }, "status": { "type": "string", "validation": "in_list", "default": "idle", "valid_list": [ "idle", "requesting", "entering", "aboard", "leaving", ], "comment": "The current status of the person.", }, "status_percent": { "type": "float", "validation": "is_percent", "default": 1.00, "comment": "A float inside [0,1] indicating percentage completion of the status.", }, } ) if attributes is not None: for key in attributes: self.set(key, attributes[key]) self.set("id", SOLE.new_id("BasePerson")) def __str__(self): """allow print() to function in some intelligible way""" return "{}".format(self.settings) def __del__(self): """track destruction of object""" SOLE.log("[{}] BasePerson->destroyed".format(self.get("id")), SOLE.LOG_INFO) def set(self, name, value): """set() will set the given attribute for the object. Will perform basic sanity checks on the attribute itself.""" self.settings.set(name, value) return self def get(self, name): """get(attr) will return attribute attr for the object or empty string if not""" return self.settings.get(name) def unload(self, elevator, floor): """unload() to remove person from elevator.""" # Remove person from elevator's carrying list carrying = elevator.get("carrying") carrying.remove(self) # Check the destination for the person destination_floor = self.get("destination_floor") if floor == destination_floor: SOLE.log( "[{}] BasePerson reached destination={}".format( self.get("id"), destination_floor.get("id") ), SOLE.LOG_INFO, ) SOLE.log( "{} reached destination {}".format( self.get("label"), destination_floor.get("label") ), SOLE.LOG_NOTICE, ) self.set("location", None) del self else: # Set person's location attribute to the current floor. self.set("location", floor) def load(self, elevator, floor): """load() will add person to an elevator.""" # Remove person from floors carrying list floor.get("carrying").remove(self) # Get the destination for the person destination_floor = self.get("destination_floor") # Set person's location attribute to the elevator self.set("location", elevator) elevator.add_to_request_queue(destination_floor) def tick(self): """tick() will advance one step for this object and any/all objects contained by it""" SOLE.log("[{}] BasePerson->tick()".format(self.get("id")), SOLE.LOG_INFO) carrying = self.get("carrying") if carrying is not None: self.set("elevation", self.get("carrying").get("elevation"))