Skip to content

Commit 8b7cdb9

Browse files
committed
Image demo & PNG exporter adapted + misc MyPy fixes
1 parent f909557 commit 8b7cdb9

File tree

6 files changed

+85
-83
lines changed

6 files changed

+85
-83
lines changed

algorithms/hunt_and_kill.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ def on(self, grid: Grid) -> None:
1818
current_cell: Optional[Cell] = grid.random_cell()
1919

2020
while current_cell is not None:
21-
current_cell = cast(Cell, current_cell) # Mypy doesn't detects while condition
22-
2321
unvisited_neighbors = [neighbor for neighbor in current_cell.neighbors if len(neighbor.links) == 0]
2422
if len(unvisited_neighbors) > 0:
2523
# as long as there are unvisited paths, walk them

algorithms/sidewinder.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from random import choice, randint
2+
from typing import cast
23

34
from algorithms.base_algorithm import Algorithm
45
from base.grid import Grid
6+
from base.cell import Cell
57

68

79
class Sidewinder(Algorithm):
@@ -26,4 +28,4 @@ def on(self, grid: Grid) -> None:
2628
member += member.north
2729
run.clear()
2830
else:
29-
cell += cell.east
31+
cell += cast(Cell, cell.east)

demos/image_demo.py

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,68 @@
1+
import argparse
12
from time import gmtime, strftime
3+
from typing import cast
24

3-
import args
4-
from typing import cast, Union # noqa: F401
5-
6-
from base.grid import Grid
7-
from base.distance_grid import DistanceGrid
85
from base.colored_grid import ColoredGrid
6+
from base.rotator import Rotator
97

108
import pathfinders.dijkstra as Dijkstra
119
import pathfinders.longest_path as LongestPath
1210

13-
from base.rotator import Rotator
14-
15-
from demos.demo_utils import ALGORITHMS, get_exporter, get_rotations, get_algorithm, get_pathfinding
11+
from demos.demo_utils_v2 import ALGORITHM_NAMES, str2bool, available_algorithm, available_exporter
1612

1713

1814
DEFAULT_EXPORTER = "PNGExporter"
1915
AVAILABLE_EXPORTERS = ["PNGExporter"]
20-
21-
22-
def get_coloring() -> bool:
23-
return bool("--coloring" in args.flags)
16+
AVAILABLE_ALGORITHMS = ALGORITHM_NAMES
2417

2518

2619
if __name__ == "__main__":
27-
if len(args.all) < 3:
28-
print("Usage:\nPYTHONPATH=. python3 demos/image_demo.py <rows> <columns> <algorithm> ", end="")
29-
print("[--exporter=<exporter>] [--rotations=<rotations>] [--pathfinding] [--coloring]")
30-
print("Valid algorithms: {}".format("|".join([algorithm.__name__ for algorithm in ALGORITHMS])))
31-
print("Valid exporters: {}".format("|".join(AVAILABLE_EXPORTERS)))
32-
print("Rotations is an integer value measuring number of 90 degree clockwise rotations to perform")
33-
print("Pathfinding flag shows distances between cells")
34-
exit(1)
35-
exporter, exporter_name = get_exporter(AVAILABLE_EXPORTERS, DEFAULT_EXPORTER)
36-
rotations = get_rotations()
37-
pathfinding = get_pathfinding()
38-
rows = int(args.all[0])
39-
columns = int(args.all[1])
40-
algorithm = get_algorithm()
41-
coloring = get_coloring()
42-
print("Algorithm: {}\nRows: {}\ncolumns: {}\nExporter: {}".format(algorithm.__name__, rows, columns, exporter_name))
20+
parser = argparse.ArgumentParser(description="Render a maze")
21+
parser.add_argument("rows", type=int, help="number or rows")
22+
parser.add_argument("columns", type=int, help="number or columns")
23+
parser.add_argument("algorithm", type=str, help="algorithm to use")
24+
parser.add_argument("-e", "--exporter", type=str, default=DEFAULT_EXPORTER, help="maze exporter to use")
25+
parser.add_argument("-f", "--filename", type=str, default=None, help="file name to use")
26+
parser.add_argument("-r", "--rotations", type=int, default=0,
27+
help="number of 90 degree clockwise rotations to perform")
28+
parser.add_argument("-p", "--pathfinding", type=str2bool, default=False, help="whether solve the maze")
29+
parser.add_argument("-c", "--coloring", type=str2bool, help="whether to color the maze solution")
30+
args = parser.parse_args()
31+
32+
rows = args.rows
33+
columns = args.columns
34+
algorithm = available_algorithm(args.algorithm, AVAILABLE_ALGORITHMS)
35+
exporter = available_exporter(args.exporter, AVAILABLE_EXPORTERS)
36+
filename = args.filename if args.filename else strftime("%Y%m%d%H%M%S", gmtime())
37+
rotations = args.rotations
38+
pathfinding = args.pathfinding
39+
coloring = args.coloring
40+
print("Algorithm: {}\nRows: {}\ncolumns: {}\nExporter: {}".format(args.algorithm, rows, columns, args.exporter))
4341
print("90deg Rotations: {}\nPathfinding: {}\nColoring: {}".format(rotations, pathfinding, coloring))
4442

45-
# here coloring takes precedence, because ColoredGrid inherits from DistanceGrid
46-
if coloring:
47-
grid = ColoredGrid(rows, columns) # type: Union[Grid, DistanceGrid, ColoredGrid]
48-
elif pathfinding:
49-
grid = DistanceGrid(rows, columns)
50-
else:
51-
grid = Grid(rows, columns)
43+
# Always use Colored Grid. Just don"t color the output if colored == False
44+
grid = ColoredGrid(rows, columns)
5245

53-
grid = algorithm.on(grid)
46+
algorithm.on(grid)
5447

5548
for num in range(rotations):
56-
grid = Rotator.on(grid)
49+
grid = cast(ColoredGrid, Rotator().on(grid))
50+
51+
# exporter.render(grid, coloring=coloring, filename=filename)
5752

58-
# here pathfinding first, so if also colored we'll see the route colored, else if colored will see all maze painted
53+
# here pathfinding first, so if also colored we"ll see the route colored, else if colored will see all maze painted
5954
if pathfinding:
60-
start_row, start_column, end_row, end_column = LongestPath.calculate(cast(DistanceGrid, grid))
61-
print("Solving maze from row {} column {} to row {} column {}".format(
62-
start_row, start_column, end_row, end_column))
63-
grid = Dijkstra.calculate_distances(cast(DistanceGrid, grid), start_row, start_column, end_row, end_column)
55+
start, end = LongestPath.calculate(grid)
56+
print("Solving maze from row {} column {} to row {} column {}".format(*start, *end))
57+
Dijkstra.calculate_distances(grid, start, end)
6458
elif coloring:
65-
start_row = round(grid.rows / 2)
66-
start_column = round(grid.columns / 2)
67-
print("Drawing colored maze with start row {} column {}".format(start_row, start_column))
68-
start_cell = grid.cell_at(start_row, start_column)
69-
if start_cell is None:
70-
raise IndexError("Invalid start cell row {} column {}".format(start_row, start_column))
71-
grid.distances = start_cell.distances # type: ignore
72-
73-
filename = strftime("%Y%m%d%H%M%S", gmtime())
74-
75-
exporter.render(grid, coloring=coloring, filename=filename)
59+
starting_position = (round(grid.rows / 2), round(grid.columns / 2))
60+
print("Drawing colored maze with start row {} column {}".format(*starting_position))
61+
if grid[starting_position] is None:
62+
raise IndexError("Invalid start cell row {} column {}".format(*starting_position))
63+
grid.distances = grid[starting_position].distances # type: ignore
64+
65+
if coloring or pathfinding:
66+
exporter.render(grid, coloring=coloring, filename=filename)
7667

7768
print("Maze has {} dead-ends".format(len(grid.deadends)))

demos/terminal_demo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
parser.add_argument("-p", "--pathfinding", type=str2bool, default=False, help="whether solve the maze")
2727
args = parser.parse_args()
2828

29+
rows = args.rows
30+
columns = args.columns
2931
algorithm = available_algorithm(args.algorithm, AVAILABLE_ALGORITHMS)
3032
exporter = available_exporter(args.exporter, AVAILABLE_EXPORTERS)
3133
rotations = args.rotations
3234
pathfinding = args.pathfinding
33-
rows = args.rows
34-
columns = args.columns
3535
print("Algorithm: {}\nRows: {}\ncolumns: {}\nExporter: {}".format(args.algorithm, rows, columns, args.exporter))
3636
print("90deg Rotations: {}\nPathfinding: {}".format(rotations, pathfinding))
3737

exporters/png_exporter.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,32 @@
11
from time import gmtime, strftime
2+
from typing import Any, cast, Tuple
3+
24
from PIL import Image, ImageDraw
3-
from typing import Any, cast, TYPE_CHECKING, Union
45

56
from exporters.base_exporter import Exporter
67
from base.colored_grid import ColoredGrid
7-
if TYPE_CHECKING:
8-
from base.grid import Grid # noqa: F401
8+
from base.grid import Grid
99

1010

11-
class PNGExporter(Exporter):
11+
STEP_BACKGROUND = 0
1212

13-
STEP_BACKGROUND = 0
1413

15-
def render(self, grid: Union["Grid", ColoredGrid], **kwargs: Any) -> None:
16-
filename = strftime("%Y%m%d%H%M%S", gmtime())
17-
cell_size = 10
18-
coloring = False
14+
class PNGExporter(Exporter):
1915

20-
for key in kwargs:
21-
if key == "filename":
22-
filename = kwargs[key]
23-
elif key == "cell_size":
24-
cell_size = kwargs[key]
25-
elif key == "coloring":
26-
coloring = kwargs[key]
16+
def render(self, grid: Grid, **kwargs: Any) -> None:
17+
has_color = isinstance(grid, ColoredGrid)
2718

28-
if coloring:
29-
assert isinstance(grid, ColoredGrid)
19+
filename, cell_size, coloring = self._process_kwargs(**kwargs)
20+
image = self._render(grid, cell_size, coloring and has_color)
21+
image.save("{}.png".format(filename), "PNG", optimize=True)
3022

23+
@staticmethod
24+
def _render(grid: Grid, cell_size: int=4, coloring: bool=False) -> Image:
25+
wall_color = (0, 0, 0)
3126
image_width = cell_size * grid.columns
3227
image_height = cell_size * grid.rows
3328

34-
wall_color = (0, 0, 0)
35-
3629
image = Image.new("RGBA", (image_width + 1, image_height + 1), (255, 255, 255))
37-
3830
draw = ImageDraw.Draw(image)
3931

4032
for draw_pass in range(2):
@@ -44,8 +36,11 @@ def render(self, grid: Union["Grid", ColoredGrid], **kwargs: Any) -> None:
4436
x2 = (cell.column + 1) * cell_size
4537
y2 = (cell.row + 1) * cell_size
4638

47-
if draw_pass == self.STEP_BACKGROUND and coloring:
48-
color = cast(ColoredGrid, grid).background_color_for(cell)
39+
if draw_pass == STEP_BACKGROUND and coloring:
40+
if coloring:
41+
color = cast(ColoredGrid, grid).background_color_for(cell)
42+
else:
43+
color = (255, 255, 255)
4944
draw.rectangle((x1, y1, x2, y2), fill=color)
5045
else:
5146
if not cell.north:
@@ -57,4 +52,20 @@ def render(self, grid: Union["Grid", ColoredGrid], **kwargs: Any) -> None:
5752
if not cell.linked_to(cell.south):
5853
draw.line((x1, y2, x2, y2), fill=wall_color, width=1)
5954

60-
image.save("{}.png".format(filename), "PNG", optimize=True)
55+
return image
56+
57+
@staticmethod
58+
def _process_kwargs(**kwargs: Any) -> Tuple[str, int, bool]:
59+
filename = strftime("%Y%m%d%H%M%S", gmtime())
60+
cell_size = 10
61+
coloring = False
62+
for key in kwargs:
63+
if key == "filename":
64+
filename = kwargs[key]
65+
elif key == "cell_size":
66+
cell_size = kwargs[key]
67+
elif key == "coloring":
68+
coloring = kwargs[key]
69+
return filename, cell_size, coloring
70+
71+
# ----

pathfinders/dijkstra.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ def calculate_distances(grid: DistanceGrid, start: Point, end: Point) -> None:
1515
if grid[end] is None:
1616
raise IndexError("Invalid destination cell row {} column {}".format(*end))
1717

18-
grid.distances = grid[start].distances.path_to(grid[end])
18+
grid.distances = grid[start].distances.path_to(grid[end]) # type: ignore

0 commit comments

Comments
 (0)