diff --git a/.github/workflows/development.yaml b/.github/workflows/development.yaml index 442515c..fe381c6 100644 --- a/.github/workflows/development.yaml +++ b/.github/workflows/development.yaml @@ -20,7 +20,7 @@ jobs: run: pip install flake8 && flake8 test: - name: Test ${{ matrix.platform }} Python ${{ matrix.python-version }} + name: Test on ${{ matrix.platform }} Python ${{ matrix.python-version }} needs: lint strategy: max-parallel: 6 @@ -38,6 +38,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -r requirements_build.txt pip install -r requirements_test.txt - name: Build extensions run: make build_ext @@ -52,7 +53,31 @@ jobs: git-branch: main parallel: true - finish: + examples: + name: Run examples on Python ${{ matrix.python-version }} + needs: lint + strategy: + max-parallel: 6 + matrix: + python-version: [ "3.10", "3.11"] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install package + run: make install-mpl + - name: Test examples + run: | + make install; + for i in examples/*.py; do + python $i && echo "$i is ok" || echo "$i fails"; + done; + + finish-coverage: name: Finalizing needs: test runs-on: ubuntu-latest @@ -61,4 +86,4 @@ jobs: uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} - parallel-finished: true + parallel-finished: true diff --git a/examples/building_path.py b/examples/building_path.py index d45e40f..68382ae 100644 --- a/examples/building_path.py +++ b/examples/building_path.py @@ -2,10 +2,11 @@ Usage example showing how to map hexagons with different properties, calculate path and rendering result with a debug front end. """ +import matplotlib.pyplot as plt + import geks from geks.front import FrontMPL - # Create rectangular map of unblocked hexagons with pointy top default_value = {"price": 1, "blocked": False} hm = geks.RectHexmap(default_value, dims=(12, 8), flat=True) @@ -22,34 +23,37 @@ # Set rugged terrain by drawing a circle swamps = geks.hex_circle(geks.Hex((3, 0)), 1) for sw in swamps: - if sw in hm.map: - hm.map[sw]["price"] = 5 + if sw in hm: + hm[sw]["price"] = 5 # Draw hexagons according to their values -for he, value in hm.map.items(): +for he, value in hm.items(): mpl.plot_hex( he, fill=value["blocked"] or value["price"] > 1, - fillcolor="grey" if value["blocked"] else "lightgreen", - edgecolor="lightgrey", + facecolor="grey" if value["blocked"] else "lightgreen", + edgecolor="black", ) # Highlight start and target hexagons start = geks.Hex((0, 0)) target = geks.Hex((11, 2)) -mpl.plot_hex(start, fill=True, fillcolor="lightblue") -mpl.plot_hex(target, fill=True, fillcolor="orange") +mpl.plot_hex(start, facecolor="lightblue") +mpl.plot_hex(target, facecolor="orange") # Build path -path, nstep, nit = geks.dijkstra_path( +path, nstep = geks.dijkstra_path( hm, start, target, distance=40, - block_func=lambda y, x: hm.map[x]["blocked"], # walls condition - cost_func=lambda y, x: hm.map[x]["price"], # cost of movement + block_func=lambda y, x: hm[x]["blocked"], # walls condition + cost_func=lambda y, x: hm[x]["price"], # cost of movement ) -mpl.plot_path(path, color="darkblue") +mpl.plot_path(path, lw=2, color="darkblue") -# Show results -mpl.show() +# Save resulting image +mpl.fig.set_size_inches(10, 8) +plt.gca().invert_yaxis() +plt.autoscale(enable=True) +plt.savefig("example.png") diff --git a/examples/heights_and_rivers.py b/examples/heights_and_rivers.py index 7a43d4a..53cdeba 100644 --- a/examples/heights_and_rivers.py +++ b/examples/heights_and_rivers.py @@ -1,6 +1,8 @@ +import numpy as np +import matplotlib.pyplot as plt + import geks from geks.front import FrontMPL -import numpy as np # Prepare height map hm = geks.RoundHexmap(0, 32) @@ -24,6 +26,10 @@ # Generate 10 rivers for edges in geks.generate_rivers(hm, 10, ocean_alt, source_min_alt): - mpl.plot_border(edges, color="#1F3C41", width=1.2) + mpl.plot_border(edges, color="#1F3C41", lw=1.2) -mpl.show() +# Save resulting image +mpl.fig.set_size_inches(16, 16) +plt.gca().invert_yaxis() +plt.autoscale(enable=True) +plt.savefig("example.png", transparent=True) diff --git a/geks/__init__.py b/geks/__init__.py index d97c4eb..745d593 100644 --- a/geks/__init__.py +++ b/geks/__init__.py @@ -1,26 +1,24 @@ from .hexagon import Hex +from .edge import HexEdge from .hexmap import RoundHexmap, RectHexmap from .layout import Layout from .pathfinding import dijkstra_scan, dijkstra_path -from .heightmap import ( - gen_heightmap, - fill_sinks_py, - fill_sinks_cy, - altitude_cdf, -) +from .heightmap import gen_heightmap, fill_sinks_cy, fill_sinks_py, \ + altitude_cdf from .rivers import generate_rivers from .geometry import hex_distance, hex_line, hex_ring, hex_circle, hex_rotate __all__ = [ "Hex", + "HexEdge", "RoundHexmap", "RectHexmap", "Layout", "dijkstra_scan", "dijkstra_path", "gen_heightmap", - "fill_sinks_py", "fill_sinks_cy", + "fill_sinks_py", "altitude_cdf", "generate_rivers", "hex_distance", diff --git a/geks/front/front_mpl.py b/geks/front/front_mpl.py index b04c02a..84bebab 100644 --- a/geks/front/front_mpl.py +++ b/geks/front/front_mpl.py @@ -16,6 +16,7 @@ def __init__(self, layout): self.layout = layout self.fig, self.ax = plt.subplots(1) self.ax.set_aspect("equal") + self.ax.set(frame_on=False) self.ax.set_yticklabels([]) self.ax.set_xticklabels([]) @@ -27,26 +28,12 @@ def __init__(self, layout): lambda x: print(self.layout.pixel2hex((x.xdata, x.ydata)).round()), ) - def show(self): - """Shows rendered objects with selected matplotlib backend""" - plt.gca().invert_yaxis() - plt.autoscale(enable=True) - plt.show() - - def plot_hex( - self, he, fill=False, fillcolor=None, edgecolor=None, alpha=1 - ): + def plot_hex(self, he, **kwargs): """Renders hexagon""" - patch = Polygon( - self.layout.hex_corners(he), - fill=fill, - facecolor=fillcolor, - edgecolor=edgecolor, - alpha=alpha, - ) + patch = Polygon(self.layout.hex_corners(he), **kwargs) self.ax.add_patch(patch) - def plot_hexmap(self, hm, colors, ignore_func=lambda x: False): + def plot_hexmap(self, hm, colors, ignore_func=lambda x: False, **kwargs): """Renders collection of hexagons""" cmap = LinearSegmentedColormap.from_list("terrain", colors) @@ -54,34 +41,40 @@ def plot_hexmap(self, hm, colors, ignore_func=lambda x: False): for he, val in hm.items(): if ignore_func(he): continue - patch = Polygon(self.layout.hex_corners(he), color=cmap(val)) + patch = Polygon( + self.layout.hex_corners(he), + color=cmap(val), + **kwargs + ) patches.append(patch) pc = PatchCollection(patches, match_original=True) self.ax.add_collection(pc) - def plot_path(self, path, width=1, color=None): + def plot_path(self, path, **kwargs): """Renders a trace connecting given hexagons""" if not path or len(path) < 2: return xy = np.array([self.layout.hex2pixel(he) for he in path]) - self.ax.plot(xy[:, 0], xy[:, 1], linewidth=width, color=color) + self.ax.plot(xy[:, 0], xy[:, 1], **kwargs) - def plot_edge(self, he, di, width=1, color=None): + def plot_edge(self, he, di, **kwargs): """Renders hexagon's edge in given direction""" edge = self.layout.hex_edge(he, di) - self.ax.plot(edge[:, 0], edge[:, 1], color=color, linewidth=width) + self.ax.plot(edge[:, 0], edge[:, 1], **kwargs) - def plot_border(self, edges, width=1, color=None): + def plot_border(self, edges, **kwargs): if len(edges) == 0: return xy = np.array([self.layout.hex_edge(e) for e in edges]) xy = xy.reshape(-1, xy.shape[-1]) xy = np.delete(xy, np.arange(1, xy.shape[0] - 1, 2), axis=0) - self.ax.plot(xy[:, 0], xy[:, 1], linewidth=width, color=color) + self.ax.plot(xy[:, 0], xy[:, 1], **kwargs) - def label(self, he, text, color=None): + def label(self, he, text, **kwargs): """Renders text inside hexagon""" pos = self.layout.hex2pixel(he) - self.ax.annotate( - text, pos, ha="center", va="center", color=color - ) + if "ha" not in kwargs: + kwargs["ha"] = "center" + if "va" not in kwargs: + kwargs["va"] = "center" + self.ax.annotate(text, pos, **kwargs) diff --git a/geks/hexmap.py b/geks/hexmap.py index f4012f8..7f1e60d 100644 --- a/geks/hexmap.py +++ b/geks/hexmap.py @@ -130,7 +130,7 @@ def center(self): """Returns central hexagon.""" center = self.dims // 2 if self.flat: - pass + center[1] -= center[0] // 2 else: center[0] -= center[1] // 2 return Hex(center) diff --git a/geks/rivers.py b/geks/rivers.py index cd6c7dc..9493c27 100644 --- a/geks/rivers.py +++ b/geks/rivers.py @@ -77,8 +77,7 @@ def generate_rivers( ocean_alt, source_min_alt, source_min_dist=2, - edgify=True, - curv=None, + curv=None ): """ Generates rivers from the given minimal altitude to the ocean level or the @@ -96,8 +95,6 @@ def generate_rivers( Minimal altitude of river sources source_min_dist : int, default: 2 Minimal distance between river sources - edgify : bool, default: True - Plot rivers through hexagons' edges curv : int or None, default: None If None, edgified river curvature increases as river gets closer to the ocean. Otherwise curvature will be fixed, the higher the value is. @@ -120,9 +117,6 @@ def generate_rivers( pool = [he for he in pool if he not in busy] it += 1 - if not edgify: - return rivers - # Direct rivers through edges of hexagons and handle mergers all_edges = [] for river in rivers: diff --git a/requirements_test.txt b/requirements_test.txt index de04b77..d14e63a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,2 @@ -Cython>=3.0.2 -numpy>=1.23.5 pytest>=7.4.0 pytest-cov>=4.1.0 diff --git a/test/test_borders.py b/test/test_borders.py new file mode 100644 index 0000000..13facea --- /dev/null +++ b/test/test_borders.py @@ -0,0 +1,11 @@ +import geks + + +def test_edge_equal(): + he = geks.Hex((0, 0)) + di = geks.Hex((1, 1)) + edge0 = geks.HexEdge(he, di, 1) + + ne = he + di + edge1 = geks.HexEdge(ne, he - ne, 1) + assert edge0 == edge1 diff --git a/test/test_rivers.py b/test/test_rivers.py index 5f61b7a..8dbe238 100644 --- a/test/test_rivers.py +++ b/test/test_rivers.py @@ -1,14 +1,7 @@ import geks -def test_river_centers(): - hm = geks.RectHexmap(None, (40, 20)) - geks.gen_heightmap(hm) - geks.fill_sinks_cy(hm) - geks.generate_rivers(hm, 5, -1, 150, edgify=False) - - -def test_river_edges(): +def test_river(): hm = geks.RectHexmap(None, (40, 20)) geks.gen_heightmap(hm) geks.fill_sinks_cy(hm)