Exemple #1
0
    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]
Exemple #2
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
Exemple #3
0
    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
Exemple #4
0
    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,
            },
        })
Exemple #5
0
    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
Exemple #6
0
    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))