Exemple #1
0
 async def deathrattle(self):
     await super().deathrattle()
     sd = self.storm_dist
     for dx in range(-sd, sd + 1):
         for dy in range(-sd, sd + 1):
             sq = get_square(self.x + dx, self.y + dy)
             if sq: sq.add_attribute("weather", "normal")
Exemple #2
0
 def move(self, dx: int, dy: int) -> bool:
     sq = get_square(self.x + dx, self.y + dy)
     if sq is not None and sq.can_npc_enter():
         self.x += dx
         self.y += dy
         return True
     return False
Exemple #3
0
    async def movement_tick(self):
        """
        Updates internal state and moves if it is time to do so.
        """
        self.movement_progress += self.sub.power.get_power("engines")
        threshold = get_square(self.x, self.y).difficulty()
        if "blessing" in self.sub.upgrades.keywords:
            # Bound difficulty above by four (normal waters)
            threshold = min(4, threshold)
        if self.movement_progress >= threshold:
            self.movement_progress -= threshold
            direction = self.direction  # Direction can change as result of movement.
            message = await self.move()
            move_status = (
                f"Moved **{self.sub.name()}** in direction **{direction.upper()}**!\n"
                f"**{self.sub.name()}** is now at position **{self.get_position()}**."
            )

            # Do all the puzzles stuff.
            await self.sub.puzzles.movement_tick()

            # Cancel trades, if necessary.
            trade_messages = self.sub.inventory.timeout_trade()

            # Finally, return our movement.
            if message:
                return f"{message}\n{move_status}", trade_messages
            return move_status, trade_messages
        return None, {}
Exemple #4
0
 async def on_tick(self):
     await super().on_tick()
     self.tick_count += 1
     if self.tick_count >= 2:
         self.tick_count -= 2
         # Make all squares which are storm_dist away rough seas.
         # Because we use diagonal distance, this is a square perimeter.
         sd = self.storm_dist
         corners = [(sd, sd), (-sd, -sd)]
         for corner in corners:
             for x in range(-sd, sd + 1):
                 sq = get_square(self.x + x, self.y + corner[1])
                 if sq: sq.add_attribute("weather", "rough")
             for y in range(-sd, sd + 1):
                 sq = get_square(self.x + corner[0], self.y + y)
                 if sq: sq.add_attribute("weather", "rough")
         self.storm_dist += 1
Exemple #5
0
 def scan(self) -> List[str]:
     """
     Perform a scanner sweep of the local area.
     This finds all subs and objects in range, and returns them.
     """
     scanners_range = int(1.5 * self.sub.power.get_power("scanners"))
     if scanners_range < 0:
         return []
     my_position = self.sub.movement.get_position()
     with_distance = "triangulation" in self.sub.upgrades.keywords
     events = explore_submap(my_position,
                             scanners_range,
                             sub_exclusions=[self.sub._name],
                             with_distance=with_distance)
     get_square(*my_position).has_been_scanned(
         self.sub._name, self.sub.power.get_power("scanners"))
     shuffle(events)
     return events
Exemple #6
0
def zoom_in(x: int, y: int, loop) -> DiscordAction:
    if in_world(x, y):
        report = f"Report for square **({x}, {y})**\n"
        # since in_world => get_square : Cell (not None)
        report += get_square(x, y).square_status() + "\n\n"  # type: ignore
        # See if any subs are here, and if so print their status.
        subs_in_square = filtered_teams(
            lambda sub: sub.movement.x == x and sub.movement.y == y)
        for sub in subs_in_square:
            report += sub.status_message(loop) + "\n\n"
        return Message(report)
    return Message("Chosen square is outside the world boundaries!")
Exemple #7
0
 async def move(self) -> str:
     motion = directions[self.direction]
     new_x = self.x + motion[0]
     new_y = self.y + motion[1]
     if not in_world(new_x, new_y):
         # Crashed into the boundaries of the world, whoops.
         self.set_direction(reverse_dir[self.get_direction()])
         return f"Your submarine reached the boundaries of the world, so was pushed back (now facing **{self.direction.upper()}**) and did not move this turn!"
     message, obstacle = get_square(new_x, new_y).on_entry(self.sub)
     if obstacle:
         return message
     self.x = new_x
     self.y = new_y
     return message
Exemple #8
0
def mass_weather(preset : str):
    CHAR_TO_WEATHER = {WEATHER[k].lower(): k.lower() for k in WEATHER}
    try:
        with open(f"weather/{preset}.txt") as f:
            # First, try to load the file.
            map_arr = f.readlines()
            for y in range(len(map_arr)):
                for x in range(len(map_arr[y])):
                    # Then for each square, set the weather accordingly.
                    sq = get_square(x, y)
                    if sq is not None:
                        char = map_arr[y][x].lower()
                        if char in CHAR_TO_WEATHER:
                            sq.attributes["weather"] = CHAR_TO_WEATHER[char]
        return OKAY_REACT
    except:
        return FAIL_REACT
Exemple #9
0
def draw_map(subs: List[Submarine], to_show: List[str],
             show_hidden: bool) -> Tuple[str, List[Dict[str, Any]]]:
    """
    Draws an ASCII version of the map.
    Also returns a JSON of additional information.
    `subs` is a list of submarines, which are marked 0-9 on the map.
    """
    SUB_CHARS = [
        '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '+', '='
    ]
    map_string = ""
    map_json = []
    for y in range(Y_LIMIT):
        row = ""
        for x in range(X_LIMIT):
            square = get_square(x, y)
            if square is None:
                raise SquareOutOfBoundsError((x, y))
            tile_char = square.to_char(to_show, show_hidden,
                                       list(map(lambda sub: sub._name, subs)))
            tile_name = square.map_name(to_show, show_hidden,
                                        list(map(lambda sub: sub._name, subs)))
            if tile_name is not None:
                map_json.append({"x": x, "y": y, "name": tile_name})
            if "n" in to_show:
                npcs_in_square = filtered_npcs(lambda n: n.x == x and n.y == y)
                if len(npcs_in_square) > 0:
                    tile_char = "N"
                    npcs_str = list_to_and_separated(
                        list(map(lambda n: n.name(), npcs_in_square)))
                    map_json.append({"x": x, "y": y, "name": npcs_str})
            for i in range(len(subs)):
                (sx, sy) = subs[i].movement.get_position()
                if sx == x and sy == y:
                    tile_char = SUB_CHARS[i]
                    map_json.append({"x": x, "y": y, "name": subs[i].name()})
            row += tile_char
        map_string += row + "\n"
    return map_string, map_json
Exemple #10
0
    def status(self, loop) -> str:
        message = ""
        power_system = self.sub.power

        if power_system.activated():
            time_until_next = math.inf
            if loop and loop.next_iteration:
                time_until_next = loop.next_iteration.timestamp(
                ) - datetime.datetime.now().timestamp()
            threshold = get_square(self.x, self.y).difficulty()
            turns_until_move = math.ceil(
                max(threshold - self.movement_progress, 0) /
                power_system.get_power("engines"))
            turns_plural = "turns" if turns_until_move > 1 else "turn"
            time_until_move = time_until_next + GAME_SPEED * (
                turns_until_move - 1)
            message += f"Submarine is currently online. {TICK}\n"
            if time_until_next != math.inf:
                message += f"Next game turn will occur in {int(time_until_next)}s.\n"
                message += f"Next move estimated to occur in {int(time_until_move)}s ({turns_until_move} {turns_plural}).\n"
            message += f"Currently moving **{self.direction.upper()}** ({direction_emoji[self.direction]}) and in position **({self.x}, {self.y})**.\n\n"
        else:
            message += f"Submarine is currently offline. {CROSS}\n\n"
        return message
Exemple #11
0
 def get_square(self) -> Optional[Cell]:
     return get_square(self.x, self.y)
Exemple #12
0
def explore_submap(pos: Tuple[int, int],
                   dist: int,
                   sub_exclusions: Collection[str] = (),
                   npc_exclusions: Collection[int] = (),
                   with_distance: bool = False) -> List[str]:
    """
    Explores the area centered around pos = (cx, cy) spanning distance dist.
    Returns all outward_broadcast events (as a list) formatted for output.
    Ignores any NPCs or subs with a name included in exclusions.
    """
    events = []
    (cx, cy) = pos
    # First, map squares.
    for i in range(-dist, dist + 1):
        x = cx + i
        if x < 0 or x >= X_LIMIT:
            continue
        for j in range(-dist, dist + 1):
            y = cy + j
            if y < 0 or y >= Y_LIMIT:
                continue
            this_dist = diagonal_distance((0, 0), (i, j))
            event = get_square(x, y).outward_broadcast(dist - this_dist)
            if event != "":
                direction = determine_direction((cx, cy), (x, y))
                if direction is None:
                    event = f"{event} - in your current square!"
                else:
                    distance_measure = ""
                    if with_distance:
                        distance_measure = f" at a distance of {this_dist} away"
                    event = f"{event} - in direction {direction.upper()}{distance_measure}!"
                events.append(event)

    # Then, submarines.
    for subname in get_subs():
        if subname in sub_exclusions:
            continue

        sub = get_sub(subname)
        sub_pos = sub.movement.get_position()
        sub_dist = diagonal_distance(pos, sub_pos)

        # If out of range, drop it.
        if sub_dist > dist:
            continue

        event = sub.scan.outward_broadcast(dist - sub_dist)
        direction = determine_direction(pos, sub_pos)
        if direction is None:
            event = f"{event} in your current square!"
        else:
            event = f"{event} in direction {direction.upper()}!"
        events.append(event)

    # Finally, NPCs.
    for npcid in get_npcs():
        if npcid in npc_exclusions:
            continue

        npc_obj = get_npc(npcid)
        npc_pos = npc_obj.get_position()
        npc_dist = diagonal_distance(pos, npc_pos)

        if npc_dist > dist:
            continue

        event = npc_obj.outward_broadcast(dist - npc_dist)
        direction = determine_direction(pos, npc_pos)
        if direction is None:
            event = f"{event} in your current square!"
        else:
            event = f"{event} in direction {direction.upper()}!"
        events.append(event)

    return events
Exemple #13
0
 async def on_tick(self):
     await super().on_tick()
     if random.random() > 0.6:
         square = get_square(self.x, self.y)
         if square: square.bury_treasure("specimen")
Exemple #14
0
def add_attribute_to(x : int, y : int, attribute : str, value) -> DiscordAction:
    square = get_square(x, y)
    if square and square.add_attribute(attribute, value):
        return OKAY_REACT
    return FAIL_REACT
Exemple #15
0
 async def deathrattle(self):
     await super().deathrattle()
     for dx in range(-2, 3):
         for dy in range(-2, 3):
             sq = get_square(self.x + dx, self.y + dy)
             if sq: sq.add_attribute("weather", "normal")
Exemple #16
0
 async def on_tick(self):
     await super().on_tick()
     for dx in range(-2, 3):
         for dy in range(-2, 3):
             sq = get_square(self.x + dx, self.y + dy)
             if sq: sq.add_attribute("weather", "storm")
Exemple #17
0
 def get_square(self) -> Cell:
     sq = get_square(self.x, self.y)
     if sq is None:
         raise SubmarineOutOfBoundsError((self.x, self.y))
     return sq
Exemple #18
0
def remove_attribute_from(x : int, y : int, attribute : str) -> DiscordAction:
    square = get_square(x, y)
    if square and square.remove_attribute(attribute):
        return OKAY_REACT
    return FAIL_REACT