Source code for MapAnalyzer.Debugger

import inspect
import os
import sys
import warnings
from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING, Union

import numpy as np
from loguru import logger
from numpy import ndarray
from sc2 import BotAI
from sc2.position import Point2, Point3

from .constants import COLORS, LOG_FORMAT, LOG_MODULE

if TYPE_CHECKING:
    from MapAnalyzer.MapData import MapData


class LocalLogFilter:
    def __init__(self, module_name: str, level: str = "ERROR") -> None:
        self.module_name = module_name
        self.level = level

    def __call__(self, record: Dict[str, Any]) -> bool:
        levelno = logger.level(self.level).no
        if self.module_name.lower() in record["name"].lower():
            return record["level"].no >= levelno
        return False


class LogFilter:
    def __init__(self, level: str = "ERROR") -> None:
        self.level = level

    def __call__(self, record: Dict[str, Any]) -> bool:
        levelno = logger.level(self.level).no
        if 'sc2.' not in record["name"].lower():
            return record["level"].no >= levelno
        return False


[docs]class MapAnalyzerDebugger: """ MapAnalyzerDebugger """ def __init__(self, map_data: "MapData", loglevel: str = "ERROR") -> None: self.map_data = map_data self.warnings = warnings self.warnings.filterwarnings('ignore', category=DeprecationWarning) self.warnings.filterwarnings('ignore', category=RuntimeWarning) self.local_log_filter = LocalLogFilter(module_name=LOG_MODULE, level=loglevel) self.log_format = LOG_FORMAT self.log_filter = LogFilter(level=loglevel) logger.add(sys.stderr, format=self.log_format, filter=self.log_filter) @staticmethod def scatter(*args, **kwargs): import matplotlib.pyplot as plt plt.scatter(*args, **kwargs) @staticmethod def show(): import matplotlib.pyplot as plt plt.show() @staticmethod def close(): import matplotlib.pyplot as plt plt.close(fig='all') @staticmethod def save(filename: str) -> bool: for i in inspect.stack(): if 'test_suite.py' in str(i): logger.info(f"Skipping save operation on test runs") logger.debug(f"index = {inspect.stack().index(i)} {i}") return True import matplotlib.pyplot as plt full_path = os.path.join(os.path.abspath("."), f"{filename}") plt.savefig(f"{filename}.png") logger.debug(f"Plot Saved to {full_path}") def plot_regions(self, fontdict: Dict[str, Union[str, int]]) -> None: """""" import matplotlib.pyplot as plt for lbl, reg in self.map_data.regions.items(): c = COLORS[lbl] fontdict["color"] = 'black' fontdict["backgroundcolor"] = 'black' # if c == 'black': # fontdict["backgroundcolor"] = 'white' plt.text( reg.center[0], reg.center[1], reg.label, bbox=dict(fill=True, alpha=0.9, edgecolor=fontdict["backgroundcolor"], linewidth=2), fontdict=fontdict, ) # random color for each perimeter x, y = zip(*reg.perimeter_points) plt.scatter(x, y, c=c, marker="1", s=300) for corner in reg.corner_points: plt.scatter(corner[0], corner[1], marker="v", c="red", s=150)
[docs] def plot_vision_blockers(self) -> None: """ plot vbs """ import matplotlib.pyplot as plt for vb in self.map_data.vision_blockers: plt.text(vb[0], vb[1], "X") x, y = zip(*self.map_data.vision_blockers) plt.scatter(x, y, color="r")
[docs] def plot_normal_resources(self) -> None: """ # todo: account for gold minerals and rich gas """ import matplotlib.pyplot as plt for mfield in self.map_data.mineral_fields: plt.scatter(mfield.position[0], mfield.position[1], color="blue") for gasgeyser in self.map_data.normal_geysers: plt.scatter( gasgeyser.position[0], gasgeyser.position[1], color="yellow", marker=r"$\spadesuit$", s=500, edgecolors="g",
)
[docs] def plot_chokes(self) -> None: """ compute Chokes """ import matplotlib.pyplot as plt for choke in self.map_data.map_chokes: x, y = zip(*choke.points) cm = choke.center if choke.is_ramp: fontdict = {"family": "serif", "weight": "bold", "size": 15} plt.text(cm[0], cm[1], f"R<{[r.label for r in choke.regions]}>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.4, edgecolor="cyan", linewidth=8)) plt.scatter(x, y, color="w") elif choke.is_vision_blocker: fontdict = {"family": "serif", "size": 10} plt.text(cm[0], cm[1], f"VB<>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.3, edgecolor="red", linewidth=2)) plt.scatter(x, y, marker=r"$\heartsuit$", s=100, edgecolors="b", alpha=0.3) else: fontdict = {"family": "serif", "size": 10} plt.text(cm[0], cm[1], f"C<{choke.id}>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.3, edgecolor="red", linewidth=2)) plt.scatter(x, y, marker=r"$\heartsuit$", s=100, edgecolors="r", alpha=0.3) walls = [choke.side_a, choke.side_b] x, y = zip(*walls) fontdict = {"family": "serif", "size": 5} if 'unregistered' not in str(choke.id).lower(): plt.text(choke.side_a[0], choke.side_a[1], f"C<{choke.id}sA>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.5, edgecolor="green", linewidth=2)) plt.text(choke.side_b[0], choke.side_b[1], f"C<{choke.id}sB>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.5, edgecolor="red", linewidth=2)) else: plt.text(choke.side_a[0], choke.side_a[1], f"sA>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.5, edgecolor="green", linewidth=2)) plt.text(choke.side_b[0], choke.side_b[1], f"sB>", fontdict=fontdict, bbox=dict(fill=True, alpha=0.5, edgecolor="red", linewidth=2)) plt.scatter(x, y, marker=r"$\spadesuit$", s=50, edgecolors="b", alpha=0.5)
def plot_overlord_spots(self): import matplotlib.pyplot as plt for spot in self.map_data.overlord_spots: plt.scatter(spot[0], spot[1], marker="X", color="black")
[docs] def plot_map( self, fontdict: dict = None, figsize: int = 20 ) -> None: """ Plot map """ if not fontdict: fontdict = {"family": "serif", "weight": "bold", "size": 25} import matplotlib.pyplot as plt plt.figure(figsize=(figsize, figsize)) self.plot_regions(fontdict=fontdict) # some maps has no vision blockers if len(self.map_data.vision_blockers) > 0: self.plot_vision_blockers() self.plot_normal_resources() self.plot_chokes() fontsize = 25 plt.style.use("ggplot") plt.imshow(self.map_data.region_grid.astype(float), origin="lower") plt.imshow(self.map_data.terrain_height, alpha=1, origin="lower", cmap="terrain") x, y = zip(*self.map_data.nonpathable_indices_stacked) plt.scatter(x, y, color="grey") ax = plt.gca() for tick in ax.xaxis.get_major_ticks(): tick.label1.set_fontsize(fontsize) tick.label1.set_fontweight("bold") for tick in ax.yaxis.get_major_ticks(): tick.label1.set_fontsize(fontsize) tick.label1.set_fontweight("bold") plt.grid()
def plot_influenced_path(self, start: Union[Tuple[float, float], Point2], goal: Union[Tuple[float, float], Point2], weight_array: ndarray, large: bool = False, smoothing: bool = False, name: Optional[str] = None, fontdict: dict = None) -> None: import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import make_axes_locatable from matplotlib.cm import ScalarMappable if not fontdict: fontdict = {"family": "serif", "weight": "bold", "size": 20} plt.style.use(["ggplot", "bmh"]) org = "lower" if name is None: name = self.map_data.map_name arr = weight_array.copy() path = self.map_data.pathfind(start, goal, grid=arr, large=large, smoothing=smoothing, sensitivity=1) ax: plt.Axes = plt.subplot(1, 1, 1) if path is not None: path = np.flipud(path) # for plot align logger.info("Found") x, y = zip(*path) ax.scatter(x, y, s=3, c='green') else: logger.info("Not Found") x, y = zip(*[start, goal]) ax.scatter(x, y) influence_cmap = plt.cm.get_cmap("afmhot") ax.text(start[0], start[1], f"Start {start}") ax.text(goal[0], goal[1], f"Goal {goal}") ax.imshow(self.map_data.path_arr, alpha=0.5, origin=org) ax.imshow(self.map_data.terrain_height, alpha=0.5, origin=org, cmap='bone') arr = np.where(arr == np.inf, 0, arr).T ax.imshow(arr, origin=org, alpha=0.3, cmap=influence_cmap) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.05) sc = ScalarMappable(cmap=influence_cmap) sc.set_array(arr) sc.autoscale() cbar = plt.colorbar(sc, cax=cax) cbar.ax.set_ylabel('Pathing Cost', rotation=270, labelpad=25, fontdict=fontdict) plt.title(f"{name}", fontdict=fontdict, loc='right') plt.grid() def plot_influenced_path_pyastar(self, start: Union[Tuple[int, int], Point2], goal: Union[Tuple[int, int], Point2], weight_array: ndarray, allow_diagonal=False, name: Optional[str] = None, fontdict: dict = None) -> None: import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import make_axes_locatable from matplotlib.cm import ScalarMappable if not fontdict: fontdict = {"family": "serif", "weight": "bold", "size": 20} plt.style.use(["ggplot", "bmh"]) org = "lower" if name is None: name = self.map_data.map_name arr = weight_array.copy() path = self.map_data.pathfind_pyastar(start, goal, grid=arr, sensitivity=1, allow_diagonal=allow_diagonal) ax: plt.Axes = plt.subplot(1, 1, 1) if path is not None: path = np.flipud(path) # for plot align logger.info("Found") x, y = zip(*path) ax.scatter(x, y, s=3, c='green') else: logger.info("Not Found") x, y = zip(*[start, goal]) ax.scatter(x, y) influence_cmap = plt.cm.get_cmap("afmhot") ax.text(start[0], start[1], f"Start {start}") ax.text(goal[0], goal[1], f"Goal {goal}") ax.imshow(self.map_data.path_arr, alpha=0.5, origin=org) ax.imshow(self.map_data.terrain_height, alpha=0.5, origin=org, cmap='bone') arr = np.where(arr == np.inf, 0, arr).T ax.imshow(arr, origin=org, alpha=0.3, cmap=influence_cmap) divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.05) sc = ScalarMappable(cmap=influence_cmap) sc.set_array(arr) sc.autoscale() cbar = plt.colorbar(sc, cax=cax) cbar.ax.set_ylabel('Pathing Cost', rotation=270, labelpad=25, fontdict=fontdict) plt.title(f"{name}", fontdict=fontdict, loc='right') plt.grid() @staticmethod def draw_influence_in_game(bot: BotAI, grid: np.ndarray, lower_threshold: int, upper_threshold: int, color: Tuple[int, int, int], size: int) -> None: height: float = bot.get_terrain_z_height(bot.start_location) for x, y in zip(*np.where((grid > lower_threshold) & (grid < upper_threshold))): pos: Point3 = Point3((x, y, height)) if grid[x, y] == np.inf: val: int = 9999 else: val: int = int(grid[x, y]) bot.client.debug_text_world(str(val), pos, color, size)