def pre_turn(self, driver, vehicle): """Before turning.""" destinations = self.db["destinations"] if destinations: location = driver.location direction = vehicle.db.direction previous, abs_direction, next = destinations[0] name = get_direction(abs_direction)["name"] log.debug("#{}-{}: turn {} {}-{}".format(vehicle.id, self.character, name, previous, next)) driver.execute_cmd("turn {}".format(name)) # Find the final coordinates if necessary final = destinations[-1][2] if isinstance(final, (tuple, list)): final = tuple(final) else: final = (final.x, final.y, final.z) distance = distance_between(destinations[-1][0].x, destinations[-1][0].y, 0, final[0], final[1], 0) # Last turn, get ready to park if len(destinations) <= 2: if "expected" not in self.db: expected = coords_in(destinations[-1][0].x, destinations[-1][0].y, final[2], direction=destinations[-1][1], distance=distance) side = get_direction( direction_between(expected[0], expected[1], 0, final[0], final[1], 0))["name"] log.debug("#{}-{}: expected {} to {}, side={}".format( vehicle.id, self.character, expected, final, side)) self.db["expected"] = expected self.db["side"] = side vehicle.start_monitoring() del destinations[0]
def find_address(self, address, is_dest=False): """Find and return the address. Args: address (str): the address to be found. The address could be in different formats: "89 North Star" "89 North Star, Los Alfaques" "North Star, Los Alfaques" """ log.debug("Try to locate {}".format(address)) match = RE_ADDRESS.search(address) if not match: raise ValueError("the specified address {} doesn't match".format( repr(address))) # Extract the number number = match.group("num").replace(" ", "") if number: number = int(number) else: number = 1 # Extract the road name road = match.group("road").lower().strip() # Extract the city name city = match.group("city") # Try to find all the crossroads serving this street crossroads = Crossroad.get_crossroads_road(road, city) log.debug("Searching for number={}, road={}, city={}".format( number, road, city)) # Recording the address if is_dest: if city: self.address = "{} {} in {}".format(number, road, city) else: self.address = "{} {}".format(number, road) if not crossroads: log.debug("Cannot find a matching crossroad") raise ValueError("cannot find the address '{} {}, {}', " \ "no match found".format(number, road, city)) # Get the first crossroad and start counting from there beginning = crossroads[0] # Look for the distance current = beginning found = False current_number = 0 visited = [] while not found: before = visited[-1] if visited else None infos = [ (k, v) for (k, v) in current.db.exits.items() if \ v["name"].lower() == road and v["crossroad"] is not before] if current in visited or not infos: log.debug(" The expected road number can't be found") raise ValueError("the expected road number ({}) on " \ "{} can't be found".format(number, road)) infos.sort(key=lambda tup: tup[1]["crossroad"].id) direction, info = infos[0] crossroad = info["crossroad"] distance = distance_between(current.x, current.y, 0, crossroad.x, crossroad.y, 0) end_number = (distance - 1) * info.get("interval", 1) * 2 if current_number + end_number >= number: end = current found = True # If the destination is closer to the end crossroad, choose it instead remaining = number - current_number - 1 distance = 1 + remaining // info.get("interval", 1) // 2 projected = end.db.exits[direction]["coordinates"][distance - 1] # If the number is odd, look for the other side of the street if number % 2 == 1: shift = (direction - 2) % 8 else: shift = (direction + 2) % 8 if remaining > end_number / 2: log.debug("We actually are closer from #{}.".format( crossroad.id)) opp_direction = (direction + 4) % 8 if crossroad.db.exits.get(opp_direction, {}).get("crossroad") is current: log.debug("There's a reverse road, use it.") end = crossroad direction = opp_direction break # The number is further ahead, go on visited.append(current) current = crossroad current_number += end_number # If not end, the address couldn't be found if end is None: log.debug("The expected road number can't be found") raise ValueError("the expected road number ({}) on " \ "{} can't be found".format(number, road)) log.debug("Found end=#{}, direction={}, distance={}".format( end.id, direction, distance)) projected = coords_in(projected[0], projected[1], projected[2], shift) room = Room.get_room_at(*projected) if room: log.debug("Found room {} at {} {} {}".format( room, room.x, room.y, room.z)) projected = room # If is_dest, add the path if is_dest: self.path.append((end, direction, projected)) return end
def add_exit(self, direction, crossroad, name, coordinates=None, interval=1): """ Add a new exit in the given direction. Args: direction (int): the direction (0 for east, 1 for south-east...) crossroad (Crossroad): the destination (another crossroad) name (str): name of the exit (like "eight street") coordinates (optional, list): coordinate replacements. interval (optional, int): change the default number interval. If there already was a crossroad in this direction, replace it. The given crossroad has to be logically set (if you give a direction of 0, the crossroad should be straight to the east of the configured position). Otherwise, a ValueError will be raised. The number interval determines the number of numbers per coordinates of a road. By default, it is 2 (meaning, on one coordinate are actually 2 numbers). """ log = logger("crossroad") lower_name = name.lower().strip() x, y, z = self.x, self.y, self.z d_x, d_y, d_z = crossroad.x, crossroad.y, crossroad.z # Since the vertical distance will affect coordinates, we # need to make sure it's properly handled. if direction_between(x, y, 0, d_x, d_y, 0) != direction: raise ValueError("the direction between {} and {} doesn't " \ "match {}".format(self, crossroad, direction)) # Get the distance between self and crossroad log.debug("Connecting crossroad #{} with #{} on {}".format( self.id, crossroad.id, name)) distance = sqrt((d_x - x)**2 + (d_y - y)**2) relative_dist = distance_between(x, y, 0, d_x, d_y, 0) coordinates = list(reversed(coordinates)) if coordinates else [] height = d_z - z slope = float(height) / relative_dist number = 0 left_dir = (direction - 2) % 8 right_dir = (direction + 2) % 8 # Calculate the original number number = 0 crossroads = Crossroad.get_crossroads_road(name) if crossroads: numbers = [] for c in crossroads: if c.tags.get(category="#" + lower_name) is not None: numbers.append(int(c.tags.get(category="#" + lower_name))) else: numbers.append(0) number = max(numbers) log.debug(" Found greatest address number: {}".format(number)) if not coordinates: progress = 0 # Calculate in-between coordinates while progress + 1 < relative_dist: progress += 1 coords = coords_in(x, y, int(z + round(progress * slope)), direction, distance=progress) coordinates.append(coords) # Create the tags of possible coordinates for coords in coordinates: tag = "{} {} {}".format(*coords) if not self.tags.get(tag, category="croad"): self.tags.add(tag, category="croad") # Add the road representation to the crossroad's attribute self.db.exits[direction] = { "coordinates": coordinates, "crossroad": crossroad, "distance": distance, "direction": direction, "interval": interval, "name": name, "slope": slope, } # Add the tag for the road name itself if not self.tags.get(lower_name, category="road"): self.tags.add(lower_name, category="road") # If this road is in the right order, performs a bit more if self.id < crossroad.id: # Add the tag to identify road number category = "#" + lower_name if self.tags.get(category=category) is None: self.tags.add(str(number), category=category) if crossroad.tags.get(category=category) is None: end_number = number + (relative_dist - 1) * interval * 2 log.debug(" Adding tag {} ({}) to #{}".format( end_number, category, crossroad.id)) crossroad.tags.add(str(end_number), category=category) # Add the rooms (tag them to indcate they belong to the road) for i, coords in enumerate(coordinates): t_number = number + interval * (i + 1) * 2 # Find the left room left_coords = coords_in(*coords, direction=left_dir) left_room = Room.get_room_at(*left_coords) left_numbers = tuple(t_number + n for n in range(-interval * 2 + 1, 1, 2)) if left_room: left_room.add_address(left_numbers, name) # Find the right room right_coords = coords_in(*coords, direction=right_dir) right_room = Room.get_room_at(*right_coords) right_numbers = tuple( t_number + n for n in range(-(interval - 1) * 2, 1, 2)) if right_room: right_room.add_address(right_numbers, name) if number == 0: log.debug(" Adding #{} as road origin".format(self.id)) if not self.tags.get(lower_name, category="oroad"): self.tags.add(lower_name, category="oroad") return coordinates
def get_street(cls, x, y, z, city=None): """ Return the street and additional information if found, or None. Args: x (int): the X coordinate. y (int): the Y coordinate. z (int): the Z coordinate. city (optional, str): the city's name to filter by. Returns: A tuple containing the closest crossroad (or None), the street name (or an empty string), and a list with the additional street numbers perpendicular to the street. For instance, if the street is oriented from east to west, the sides of the street are on the north and south, and the rooms or their coordinates are returned in the dictionary. Example: (<Crossroad #3>, "First street", { 2: { "direction": 2, # south "coordinate": (0, -1, 0), "room": <Room In front of the market>, "numbers": (21, 23), }, 6: { "direction": 6, # north "coordinate": (0, 1, 0), "room": <Room In front of the public library>, "numbers": (22, 24), }, }) """ crossroads = cls.get_crossroads_with(x, y, z) if not crossroads: return (None, "no crossroad", []) closest = crossroads[0] # Find the street name infos = [ v for v in closest.db.exits.values() if \ (x, y, z) in v["coordinates"]] if not infos: raise RuntimeError("unexpected: the coordinates {} {} {} " \ "were found in crossroad #{}, but the road leading " \ "this way cannot be found".format(x, y, z, first.id)) road = infos[0]["name"].lower() # Find the first crossroad to this road crossroads = Crossroad.get_crossroads_road(road, city) if not crossroads: return (None, "no first crossroad", []) first = current = crossroads[0] found = False number = 0 visited = [] while not found: before = visited[-1] if visited else None infos = [ (k, v) for (k, v) in current.db.exits.items() if \ v["name"].lower() == road and v["crossroad"] is not before] if current in visited or not infos: return (None, "can't find", []) infos.sort(key=lambda tup: tup[1]["crossroad"].id) direction, info = infos[0] crossroad = info["crossroad"] distance = distance_between(current.x, current.y, 0, crossroad.x, crossroad.y, 0) end_number = (distance - 1) * info["interval"] * 2 if (x, y, z) in info["coordinates"]: d_x, d_y = current.x, current.y distance = distance_between(x, y, 0, d_x, d_y, 0) end_number = distance * info["interval"] * 2 found = True number += end_number visited.append(current) current = crossroad # We now try to find the immediate neighbors interval = info["interval"] left_direction = (direction - 2) % 8 left_coords = coords_in(x, y, z, left_direction) left_room = Room.get_room_at(*left_coords) left_numbers = tuple(number + n for n in range(-interval * 2 + 1, 1, 2)) right_direction = (direction + 2) % 8 right_coords = coords_in(x, y, z, right_direction) right_room = Room.get_room_at(*right_coords) right_numbers = tuple(number + n for n in range(-(interval - 1) * 2, 1, 2)) return (closest, info["name"], { "left": { "direction": left_direction, "coordinates": left_coords, "room": left_room, "numbers": left_numbers, }, "right": { "side": "right", "direction": right_direction, "coordinates": right_coords, "room": right_room, "numbers": right_numbers, }, left_direction: { "direction": left_direction, "coordinates": left_coords, "room": left_room, "numbers": left_numbers, }, right_direction: { "side": "right", "direction": right_direction, "coordinates": right_coords, "room": right_room, "numbers": right_numbers, }, })
def get_road_coordinates(cls, road, city=None, include_sides=True, include_road=True, include_crossroads=True): """ Return all the coordinates of a road name, if found. Args: road (str): the name of the road. city (str, optional): the city name to filter search. include_sides (bool, optional) include the coordinates on either side of the road. include_road (bool, optional): include the road coordinates. include_crossroads (bool, optional) include the crossroads. Returns: A sorted dictionary of coordinates as key and additional information as values. """ coordinates = OrderedDict() crossroads = cls.get_crossroads_road(road, city) if not crossroads: return {} first = current = crossroads[0] number = 0 visited = [] finished = False while not finished: before = visited[-1] if visited else None infos = [ (k, v) for (k, v) in current.db.exits.items() if \ v["name"].lower() == road and v["crossroad"] is not before] if current in visited or not infos: break infos.sort(key=lambda tup: tup[1]["crossroad"].id) direction, info = infos[0] if include_crossroads: coordinates[(current.x, current.y, current.z)] = current crossroad = info["crossroad"] interval = info["interval"] distance = distance_between(current.x, current.y, 0, crossroad.x, crossroad.y, 0) for x, y, z in info["coordinates"]: number += interval * 2 if include_road: coordinates[(x, y, z)] = (number, ) if include_sides: left_direction = (direction - 2) % 8 left_coords = coords_in(x, y, z, left_direction) left_numbers = tuple( number + n for n in range(-interval * 2 + 1, 1, 2)) right_direction = (direction + 2) % 8 right_coords = coords_in(x, y, z, right_direction) right_numbers = tuple( number + n for n in range(-(interval - 1) * 2, 1, 2)) coordinates[left_coords] = left_numbers coordinates[right_coords] = right_numbers visited.append(current) current = crossroad return coordinates
def create_room(self, args): """ Create a room with given exit or coordinates. When using the |w@new room|n command, you have to specify the coordinates of the room to create. This is usually done by providing an exit name as parameter: the room where you are, or the position you are in (if in road building mode) will be used to infer coordinates. You can also set the |w-c|n option, spcifying coordinates for the new room. The |w-r|n or |w--road|n option can be used to change the road that the newly-created room will be connected to. By default, this option is set to |wAUTO,|n meaning the proper address will be determined based on the name of the street you currently are. You can set this to |wNONE|n to disable automatic road lookup, or give it a full road name, like |wfirst street|n. Examples: |w@new room e|n |w@new room sw|n |w@new room -c 5,10,-15|n |w@new room west -r NONE|n |w@new room n -r gray street|n """ # Default parameters are set to None and modified by the context of the caller exit = direction = x = y = z = n_x = n_y = n_z = origin = road = None road_name = " ".join(args.road) prototype = None if args.prototype: prototype = " ".join(args.prototype) # Try to find the prototype try: prototype = PRoom.objects.get(db_key=prototype) except PRoom.DesNotExist: self.msg("The prototype {} doesn't exist.".format(prototype)) return # Do some common checks info = {} if args.exit: if args.exit not in NAME_DIRECTIONS: self.msg("Invalid direction name: {}.".format(args.exit)) return direction = NAME_DIRECTIONS[args.exit] info = get_direction(direction) exit = info["name"] # If caller is in road building mode, use its location building = self.caller.db._road_building if building: x = building["x"] y = building["y"] z = building["z"] room = Room.get_room_at(x, y, z) closest, street, exits = Crossroad.get_street(x, y, z) # If in a room in road building mode, take this room as the origin if room: origin = room elif closest and args.exit: if direction in exits: entry = exits[direction] if entry["room"]: self.msg("There's already a room {} from here: {}.".format( args.exit, entry["room"].key)) return n_x, n_y, n_z = entry["coordinates"] entry["name"] = street road = entry else: self.msg("This direction ({}) isn't a side of this road: {}.".format( exit, street)) return elif closest: self.msg("You are in road building mode on {}, you must provide " \ "an exit name.".format(street)) return else: self.msg("You are in road building mode, but not on a valid road.") return elif inherits_from(self.caller.location, "typeclasses.rooms.Room"): # The caller is in a room origin = self.caller.location x, y, z = origin.x, origin.y, origin.z if any(c is None for c in (x, y, z)): self.msg("You are in a room without valid coordinates.") return # Try to identify whether the new room would be a road if direction is not None: n_x, n_y, n_z = coords_in(x, y, z, direction) elif args.coordinates: n_x, n_y, n_z = args.coordinates if road_name == "AUTO": roads = origin.tags.get(category="road") roads = roads or [] roads = [roads] if isinstance(roads, basestring) else roads for name in roads: # Get all coordinates for this road coordinates = Crossroad.get_road_coordinates(name, include_road=False, include_crossroads=False) if (n_x, n_y, n_z) in coordinates: road = { "name": name, "numbers": coordinates[(n_x, n_y, n_z)], } break elif road_name != "NONE": # Look for the road name coordinates = Crossroad.get_road_coordinates(road_name, include_road=False, include_crossroads=False) if (n_x, n_y, n_z) in coordinates: road = { "name": road_name, "numbers": coordinates[(n_x, n_y, n_z)], } else: self.msg("Cannot find the road named '{}'.".format( road_name)) # Check that the new coordinates are not already used already = Room.get_room_at(n_x, n_y, n_z) if already is not None: self.msg("A room ({}) already exists here, X+{} Y={} Z={}.".format( already.key, n_x, n_y, n_z)) return # Create the new room if prototype: room = prototype.create() else: room = create_object("typeclasses.rooms.Room", "Nowhere") room.x = n_x room.y = n_y room.z = n_z self.msg("Creating a new room: {}(#{}) (X={}, Y={}, Z={}).".format( room.key, room.id, n_x, n_y, n_z)) if road: name = road["name"] numbers = road["numbers"] self.msg("Adding addresses {} {} to the new room.".format( "-".join(str(n) for n in numbers), name)) room.add_address(numbers, name) # Create the exits if needed if exit and origin: if any(e.name == exit for e in origin.exits): self.msg("There already is an exit {} in the room {}.".format( exit, origin.key)) else: aliases = info["aliases"] create_object("typeclasses.exits.Exit", exit, origin, aliases=aliases, destination=room) self.msg("Created {} exit from {} to {}.".format( exit, origin.key, room.key)) # Creating the back exit if info and origin: back = info["opposite_name"] if any(e.name == back for e in room.exits): self.msg("There already is an exit {} in the room {}.".format( back, room.key)) else: aliases = info["opposite_aliases"] create_object("typeclasses.exits.Exit", back, room, aliases=aliases, destination=origin) self.msg("Created {} exit from {} to {}.".format(back, room.key, origin.key))