Skip to content

Commit

Permalink
Base (#65) -> dev -> master (#66)
Browse files Browse the repository at this point in the history
* refactor: Region to inherit from Polygon + adjusted tests

BREAKING CHANGE: Region is now a Child of Polygon

* fix: __bool__ compatibility with burnysc2  (#64)

* fix: regions and ramps now set each other correctly
  • Loading branch information
eladyaniv01 authored Aug 15, 2020
1 parent 46cde5b commit 60e2d2d
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 155 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_on_setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: BuildOnSetup

on:
push:
branches: [ master, dev ]
branches: [ master, dev, Base ]
pull_request:
branches: [ master, dev ]

Expand Down
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [0.0.51](https://github.com/eladyaniv01/SC2MapAnalysis/compare/v0.0.50...v0.0.51) (2020-08-14)
### [0.0.52](https://github.com/eladyaniv01/SC2MapAnalysis/compare/v0.0.51...v0.0.52) (2020-08-15)

### ⚠ BREAKING CHANGES

* Region is now a Child of Polygon (Refactor)

### Bug Fixes

* regions and ramps now set each other correctly

* mapdata test for plotting ([b987fb6](https://github.com/eladyaniv01/SC2MapAnalysis/commit/b987fb6c29863cf57b30abfa5dad3b152456bcab))

* Base (#65) ([209d6d1](https://github.com/eladyaniv01/SC2MapAnalysis/commit/209d6d1c065893f98ce6bbfaeb34ab38b74e41a9)), closes [#65](https://github.com/eladyaniv01/SC2MapAnalysis/issues/65) [#64](https://github.com/eladyaniv01/SC2MapAnalysis/issues/64)


### [0.0.51](https://github.com/eladyaniv01/SC2MapAnalysis/compare/v0.0.50...v0.0.51) (2020-08-14)

### Features

Expand Down
25 changes: 14 additions & 11 deletions MapAnalyzer/Debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,36 @@
from loguru import logger
from numpy import int64, ndarray

from .constants import COLORS, LOG_FORMAT
from .constants import COLORS, LOG_FORMAT, LOG_MODULE

if TYPE_CHECKING:
from MapAnalyzer.MapData import MapData


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

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


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.logger = logger
self.log_filter = LogFilter("MapAnalyzer")
self.log_filter = LogFilter(module_name=LOG_MODULE, level=loglevel)
self.logger.remove()
self.log_format = LOG_FORMAT
self.logger.add(sys.stderr, format=self.log_format, filter=self.log_filter)
Expand Down Expand Up @@ -66,10 +69,10 @@ 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"] = c
fontdict["color"] = 'black'
fontdict["backgroundcolor"] = 'black'
if c == 'black':
fontdict["backgroundcolor"] = 'white'
# if c == 'black':
# fontdict["backgroundcolor"] = 'white'
plt.text(
reg.center[0],
reg.center[1],
Expand All @@ -78,9 +81,9 @@ def plot_regions(self, fontdict: Dict[str, Union[str, int]]) -> None:
fontdict=fontdict,
)
# random color for each perimeter
x, y = zip(*reg.polygon.perimeter_points)
x, y = zip(*reg.perimeter_points)
plt.scatter(x, y, c=c, marker="1", s=300)
for corner in reg.polygon.corner_points:
for corner in reg.corner_points:
plt.scatter(corner[0], corner[1], marker="v", c="red", s=150)

def plot_vision_blockers(self) -> None:
Expand Down
51 changes: 9 additions & 42 deletions MapAnalyzer/MapData.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def where_all(
point = int(point[0]), int(point[1])

for region in self.regions.values():
if region.inside_p(point):
if region.is_inside_point(point):
results.append(region)
for choke in self.map_chokes:
if choke.is_inside_point(point):
Expand All @@ -275,7 +275,7 @@ def where(
point = int(point[0]), int(point[1])

for region in self.regions.values():
if region.inside_p(point):
if region.is_inside_point(point):
return region
for choke in self.map_chokes:
if choke.is_inside_point(point):
Expand Down Expand Up @@ -356,28 +356,22 @@ def compile_map(self) -> None:
self._calc_grid()
self._calc_regions()
self._calc_vision_blockers()
self._set_map_ramps()
self._calc_chokes()
self._clean_polys()

for poly in self.polygons:
poly.calc_areas()

@staticmethod
def _clean_ramps(region: Region) -> None:
""" utility function to remove over populated ramps """
for mramp in region.region_ramps:
if len(mramp.regions) < 2:
region.region_ramps.remove(mramp)
for ramp in self.map_ramps:
ramp.set_regions()

def _calc_grid(self) -> None:
""" converting the placement grid to our own kind of grid"""
# cleaning the grid and then searching for 2x2 patterned regions
grid = binary_fill_holes(self.placement_arr).astype(int)
# for our grid, mineral walls are considered as a barrier between regions
correct_blockers = []
for point in self.resource_blockers:
grid[int(point[0])][int(point[1])] = 0
if point not in self.resource_blockers:
correct_blockers.append(point)
for n in point.neighbors4:
point_ = Point2((n.rounded[0], n.rounded[1]))
if point_[0] < grid.shape[1] and point_[1] < grid.shape[0]:
Expand Down Expand Up @@ -409,36 +403,7 @@ def _set_map_ramps(self):
array=self.points_to_numpy_array(r.points))
for r in self.bot.game_info.map_ramps]

def _calc_ramps(self, region: Region) -> None:
"""
probably the most expensive operation other than plotting , need to optimize
"""
if len(self.map_ramps) == 0:
self._set_map_ramps()

ramp_nodes = self.get_ramp_nodes()
perimeter_nodes = region.polygon.perimeter_points
result_ramp_indexes = list(set([self.closest_node_idx(n, ramp_nodes) for n in perimeter_nodes]))

for rn in result_ramp_indexes:
# and distance from perimeter is less than ?
ramp = self.get_ramp(node=ramp_nodes[rn])

"""for ramp in map ramps if ramp exists, append the regions if not, create new one"""
if region not in ramp.areas:
ramp.areas.append(region)
region.region_ramps.append(ramp)
ramps = []

for ramp in region.region_ramps:
for p in region.polygon.perimeter_points:
if self.ramp_close_enough(ramp, p, n=8):
ramps.append(ramp)
ramps = list(set(ramps))

region.region_ramps.extend(ramps)
region.region_ramps = list(set(region.region_ramps))
# self._clean_ramps(region)

def _calc_vision_blockers(self) -> None:
"""
Expand Down Expand Up @@ -497,6 +462,8 @@ def _calc_regions(self) -> None:
"""
# some areas are with area of 1, 2 ,5 these are not what we want,
# so we filter those out
# if len(self.map_ramps) == 0:
# self._set_map_ramps()
pre_regions = {}
for i in range(len(self.regions_labels)):
region = Region(
Expand All @@ -513,7 +480,7 @@ def _calc_regions(self) -> None:
if self.max_region_area > region.get_area > self.min_region_area:
region.label = j
self.regions[j] = region
self._calc_ramps(region=region)
# region.calc_ramps()
j += 1

"""Plot methods"""
Expand Down
10 changes: 4 additions & 6 deletions MapAnalyzer/Pather.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from functools import lru_cache
from typing import Optional, Tuple, TYPE_CHECKING

import numpy as np
Expand Down Expand Up @@ -46,7 +45,9 @@ def _add_non_pathables_ground(self, grid: ndarray, include_destructables: bool =
nonpathables.extend(self.map_data.mineral_fields)
for obj in nonpathables:
radius = NONPATHABLE_RADIUS
grid = self.add_influence(p=obj.position, r=radius * obj.radius, arr=grid, weight=np.inf)
if 'mineral' in obj.name.lower():
radius = NONPATHABLE_RADIUS * 1.5
grid = self.add_influence(p=obj.position.rounded, r=radius * obj.radius, arr=grid, weight=np.inf)
for pos in self.map_data.resource_blockers:
radius = RESOURCE_BLOCKER_RADIUS
grid = self.add_influence(p=pos, r=radius, arr=grid, weight=np.inf)
Expand All @@ -57,27 +58,23 @@ def _add_non_pathables_ground(self, grid: ndarray, include_destructables: bool =
self.add_influence(p=rock.position, r=1 * rock.radius, arr=grid, weight=np.inf)
return grid

@lru_cache()
def get_base_pathing_grid(self) -> ndarray:
return np.fmax(self.map_data.path_arr, self.map_data.placement_arr).T

@lru_cache()
def get_climber_grid(self, default_weight: int = 1, include_destructables: bool = True) -> ndarray:
"""Grid for units like reaper / colossus """
grid = self._climber_grid.copy()
grid = np.where(grid != 0, default_weight, np.inf).astype(np.float32)
grid = self._add_non_pathables_ground(grid=grid, include_destructables=include_destructables)
return grid

@lru_cache()
def get_clean_air_grid(self, default_weight: int = 1):
clean_air_grid = np.ones(shape=self.map_data.path_arr.shape).astype(np.float32).T
if default_weight == 1:
return clean_air_grid
else:
return np.where(clean_air_grid == 1, default_weight, 0)

@lru_cache()
def get_air_vs_ground_grid(self, default_weight: int):
grid = np.fmin(self.map_data.path_arr, self.map_data.placement_arr)
air_vs_ground_grid = np.where(grid == 0, 1, default_weight).astype(np.float32)
Expand Down Expand Up @@ -113,6 +110,7 @@ def add_influence(self, p: Tuple[int, int], r: int, arr: ndarray, weight: int =
if len(ri) == 0 or len(ci) == 0:
# this happens when the center point is near map edge, and the radius added goes beyond the edge
self.map_data.logger.debug(OutOfBoundsException(p))
# self.map_data.logger.trace()
return arr

def in_bounds_ci(x):
Expand Down
24 changes: 17 additions & 7 deletions MapAnalyzer/Polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ def __init__(self, polygon):
self.points = None

@property
def free_pct(self):
def free_pct(self) -> float:
if self.points is None:
self.polygon.map_data.logger.warning("BuildablePoints needs to update first")
self.update()
return len(self.points) / len(self.polygon.points)

def update(self):
def update(self) -> None:
parr = self.polygon.map_data.points_to_numpy_array(self.polygon.points)
[self.polygon.map_data.add_influence(p=(unit.position.x, unit.position.y), r=unit.radius, arr=parr, safe=False)
for unit in
Expand Down Expand Up @@ -66,22 +66,32 @@ def __init__(self, map_data: "MapData", array: ndarray) -> None: # pragma: no c
self.indices = self.map_data.points_to_indices(self.points)
self.map_data.polygons.append(self)
self._buildable_points = BuildablePoints(polygon=self)
# self.calc_areas()

@property
def buildable_points(self):
def buildable_points(self) -> BuildablePoints:
self._buildable_points.update()
return self._buildable_points

@property
@lru_cache()
def regions(self) -> List["Region"]:
from MapAnalyzer.Region import Region
if len(self.areas) > 0:
return [r for r in self.areas if isinstance(r, Region)]
return []

def calc_areas(self) -> None:
pass
# this method uses where_all which means
# it should be called at the end of the map compilation when areas are populated
points = [min(self.points), max(self.points)]
areas = self.areas
for point in points:
point = int(point[0]), int(point[1])
new_areas = self.map_data.where_all(point)
if self in new_areas:
new_areas.pop(new_areas.index(self))
areas.extend(new_areas)
self.areas = list(set(areas))

def plot(self, testing: bool = False) -> None: # pragma: no cover
"""
Expand Down Expand Up @@ -150,7 +160,7 @@ def center(self) -> Point2:
cm = self.map_data.closest_towards_point(points=self.clean_points, target=center_of_mass(self.array))
return cm

@lru_cache(100)
@lru_cache()
def is_inside_point(self, point: Union[Point2, tuple]) -> bool:
"""
is_inside_point
Expand All @@ -161,7 +171,7 @@ def is_inside_point(self, point: Union[Point2, tuple]) -> bool:
return True
return False

@lru_cache(100)
@lru_cache()
def is_inside_indices(
self, point: Union[Point2, tuple]
) -> bool: # pragma: no cover
Expand Down
Loading

0 comments on commit 60e2d2d

Please sign in to comment.