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")
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
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, {}
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
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
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!")
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
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
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
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
def get_square(self) -> Optional[Cell]: return get_square(self.x, self.y)
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
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")
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
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")
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")
def get_square(self) -> Cell: sq = get_square(self.x, self.y) if sq is None: raise SubmarineOutOfBoundsError((self.x, self.y)) return sq
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