| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- '''
- @author: olivier.massot, 2019
- '''
- import heapq
- import time
- class Queue():
- def __init__(self):
- self.items = []
-
- def __bool__(self):
- return bool(self.items)
- def __repr__(self):
- return str(self.items)
- def values(self):
- return (v for _, v in self.items)
- def put(self, item, priority):
- heapq.heappush(self.items, (priority, item))
- def fput(self, item, priority):
- while priority in [p for p, _ in self.items]:
- priority += 1
- self.put(item, priority)
- def get(self):
- return heapq.heappop(self.items)[1]
-
- class PathNode(tuple):
- def __new__(self, x, y, parent=None):
- n = tuple.__new__(self, (x, y))
- n.parent = parent
- n.cost = 0
- return n
-
- def __repr__(self):
- return f"<{self[0]}, {self[1]}, c:{self.cost}>"
- def ancestors(self):
- ancestors = []
- p = self.parent
- while p:
- ancestors.insert(0, p)
- p = p.parent
- return ancestors
-
-
- class Waypoint(tuple):
- def __new__(self, x, y, neighbors=None):
- n = tuple.__new__(self, (x, y))
- n.neighbors = neighbors or []
- return n
- def __repr__(self):
- return f"<({self[0]},{self[1]}), n:{self.neighbors}>"
- class Grid():
-
- def __init__(self, grid, start, target):
- rows = grid.strip().split("\n")
- self.w = len(rows[0])
- self.h = len(rows)
- self.cells = {(x, y): c for y, row in enumerate(rows) for x, c in enumerate(list(row))}
- self.control_room = target
- self.kirk = start
-
- self.neighbors = {p: self.get_neighbors(*p) for p in self.cells}
- self.update_corners()
-
- def _repr_cell(self, p, show=[]):
- if p == self.kirk:
- return "T"
- elif p == self.control_room:
- return "C"
- elif p in show:
- return "x"
- else:
- return self.cells[p]
-
- def graph(self, show=[]):
- return "\n".join(["".join([self._repr_cell((x, y), show) for x in range(self.w)]) for y in range(self.h)])
-
- @staticmethod
- def dist(from_, to_):
- xa, ya = from_
- xb, yb = to_
- return abs(xa - xb) + abs(ya - yb)
-
- def get_neighbors(self, x, y, diags=False):
- neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
- if diags:
- neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
- return [(x, y) for x, y in neighs if 0 <= x < self.w and 0 <= y < self.h]
- def line(self, x1, y1, x2, y2):
- # special case
- if (x1, y1) == (x2, y2):
- return [(x1, y1)]
- # diagonal symmetry
- vertically_oriented = (abs(y2 - y1) > abs(x2 - x1))
- if vertically_oriented:
- y1, x1, y2, x2 = x1, y1, x2, y2
- # horizontal symmetry
- reversed_sym = (x1 > x2)
- if reversed_sym:
- x2, y2, x1, y1 = x1, y1, x2, y2
- # angle
- dx, dy = x2 - x1, y2 - y1
- alpha = (abs(dy) / dx)
- offset = 0.0
- step = 1 if dy > 0 else -1
- result = []
- y = y1
- for x in range(x1, x2 + 1):
- if vertically_oriented:
- result.append((y, x))
- else:
- result.append((x, y))
- offset += alpha
- if offset > 0.5:
- y += step
- offset -= 1.0
- if reversed_sym:
- result.reverse()
- return result
- def _get_quarters(self, x, y):
- return {(x - 1, y - 1): [(x - 1, y), (x - 1, y - 1), (x, y - 1)],
- (x + 1, y - 1): [(x, y - 1), (x + 1, y - 1), (x + 1, y)],
- (x + 1, y + 1): [(x + 1, y), (x + 1, y + 1), (x, y + 1)],
- (x - 1, y + 1): [(x, y + 1), (x - 1, y + 1), (x - 1, y)]}
-
- def update_corners(self):
- corners = set()
- for p, c in self.cells.items():
- if c == "#":
- for corner, quarter in self._get_quarters(*p).items():
- if all(self.cells.get(n, "#") != "#" for n in quarter):
- corners.add(corner)
- self.corners = list(corners)
-
- def print_nodes(self, node):
-
- def _get_parent(node):
- parents = []
- if node.parent:
- parents.insert(0, node.parent)
- parents = _get_parent(node.parent) + parents
- return parents
-
- path_to = _get_parent(node)
- print(node, path_to)
-
- def way(self, start, target, known_only=False):
- nodes = Queue()
- origin = PathNode(*start)
- nodes.put(origin, 0)
-
- waypoints = set(self.corners) | {start, target}
- neighbors = {}
- passable = ["."] if known_only else [".", "?"]
-
- for waypoint in waypoints:
- neighbors[waypoint] = []
- for other in waypoints:
- if other is waypoint:
- continue
- sight = self.line(*other, *waypoint)[1:-1]
- if all(self.cells.get(p, "#") in passable for p in sight) and not any(w in sight for w in waypoints):
- neighbors[waypoint].append((other, Grid.dist(waypoint, other)))
-
- while nodes:
- current = nodes.get()
-
- # self.print_nodes(current)
-
- if current == target:
- path = []
- previous = current
- while previous:
- if previous != start:
- path.insert(0, previous)
- previous = previous.parent
- return path
- for n, dist in neighbors[current]:
- if n in current.ancestors():
- continue
- cost = current.cost + dist
- # cost = current.cost + 1
- priority = cost + Grid.dist(n, target)
-
- node = PathNode(*n, current)
- node.cost = cost
- nodes.put(node, priority)
-
- return None
- def path(self, start, target, known_only=False):
- nodes = Queue()
- origin = PathNode(*start)
- nodes.put(origin, 0)
- neighbors = []
- its, break_on, broken = 0, 10000000, False
- self.costs = {}
- for p, c in self.cells.items():
- cost = 0
- if c == "#":
- cost = -1
- elif known_only and c == "?":
- cost = -1
- else:
- cost = 2 if c == "?" else 1
- self.costs[p] = cost
-
- while nodes:
- current = nodes.get()
-
- if current == target or broken:
- path = []
- previous = current
- while previous:
- if previous != start:
- path.insert(0, previous)
- previous = previous.parent
- return path
- neighbors = self.neighbors[current]
- for x, y in neighbors:
- if (x, y) == current.parent:
- continue
- its += 1
- if its > break_on:
- broken = True
- break
- if self.costs[(x, y)] < 0:
- continue
-
- cost = current.cost + self.costs[(x, y)]
- priority = cost + Grid.dist((x, y), target)
-
- node = PathNode(x, y, current)
- node.cost = cost
- nodes.put(node, priority)
-
- return None
- def optim_path(self, origin, target, known_only=False, first_steps=False):
- way = self.way(origin, target, known_only)
- print(way)
- if way:
- if first_steps:
- path = self.path(origin, way[0], known_only)
- else:
- way.insert(0, origin)
- path = sum([self.path(start, target, known_only) for start, target in zip(way, way[1:])], [])
- else:
- path = self.path(origin, target, known_only)
- return path
-
- def explore(self):
-
- buffer = [self.kirk]
- tested = set()
-
- while buffer:
- new_buffer = []
- for p in buffer:
- for n in self.neighbors[p]:
- if n in tested:
- continue
- c = self.cells[n]
- if c == "?":
- return n
- if c != "#":
- new_buffer.append(n)
- tested.add(n)
- buffer = new_buffer
-
- return None
-
- raw = """
- ##############################
- #............................#
- #.#######################.#..#
- #.......................#.#..#
- #.....#.................#.#..#
- #.#######################.#..#
- #.....##......##......#....###
- #...####..##..##..##..#..#...#
- #.........##......##.....#...#
- ?##########################.##
- ?......#......#..............#
- ???....#.....................#
- ???.#..####################..#
- ???..........................#
- ???###########################
- """
- start = (4, 11)
- target = (6, 3)
- grid = Grid(raw, start, target)
- print(grid.graph(grid.corners))
- print("\n\n")
- t0 = time.time()
- path = grid.optim_path(start, target, True)
- print(time.time() - t0)
-
- print(len(path))
- print(grid.graph(path))
- print(path)
- # t0 = time.time()
- # path = grid.path(start, target, True)
- # print(time.time() - t0)
- #
- # print(len(path))
- # print(grid.graph(path))
- # print(path)
|