commit 309a46c778549716433a5c38770db5a83f6ca7eb Author: Gabriel Augendre Date: Thu Aug 10 21:31:16 2017 +0200 Add existing .py files diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb6f929 --- /dev/null +++ b/.gitignore @@ -0,0 +1,193 @@ + +# Created by https://www.gitignore.io/api/python,pycharm,osx + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# End of https://www.gitignore.io/api/python,pycharm,osx diff --git a/bender.py b/bender.py new file mode 100644 index 0000000..d29cd40 --- /dev/null +++ b/bender.py @@ -0,0 +1,198 @@ +import sys +import math +from enum import Enum + + +class Coordinate: + def __init__(self, r, c): + self.r = r + self.c = c + + def __str__(self): + return str((self.r, self.c)) + + +class Direction(Enum): + SOUTH = 1 + EAST = 2 + NORTH = 3 + WEST = 4 + + +class Game: + def __init__(self, city_map): + self.city_map = city_map + self.bender = Bender(self.city_map) + + def run(self): + while self.bender.alive: + self.bender.step() + + +class Bender: + def __init__(self, city_map, position=None): + self.direction = Direction.SOUTH + self._position = position + self.fury = False + self.alive = True + self.city_map = city_map + self.invert = False + self.instructions = [] + + if not self.position: + for i, r in enumerate(self.city_map): + if '@' in r: + r, c = i, ''.join(r).find('@') + self.position = Coordinate(r, c) + @property + def position(self): + return self._position + + @position.setter + def position(self, value): + self._position = value + # self.print_map() + + def print_map(self, file=sys.stderr): + for r, row in enumerate(self.city_map): + if r != self.position.r: + line = ''.join(row) + else: + line = ''.join(row[:self.position.c]) + '*' + ''.join(row[self.position.c+1:]) + print(line, file=file) + + def step(self): + next_item = self.get_next_item() + + if next_item == '#' or (next_item == 'X' and not self.fury): + self.bypass_obstacle() + self.step() + else: + self.step_forward() + + if next_item == 'X' and self.fury: + self.city_map[self.position.r][self.position.c] = ' ' + + elif next_item == 'B': + self.fury = not self.fury + + elif next_item == 'T': + self.beam_me_up() + + elif next_item == 'I': + self.invert = not self.invert + + elif next_item == 'S': + self.direction = Direction.SOUTH + + elif next_item == 'E': + self.direction = Direction.EAST + + elif next_item == 'N': + self.direction = Direction.NORTH + + elif next_item == 'W': + self.direction = Direction.WEST + + elif next_item == '$': + self.alive = False + for instruction in self.instructions: + print(instruction) + + self.check_loop() + + def check_loop(self): + if len(self.instructions) > 1000: + print('LOOP') + self.alive = False + + def beam_me_up(self): + b_r, b_c = self.position.r, self.position.c + + for i, row in enumerate(self.city_map): + if 'T' in row: + t_r, t_c = i, ''.join(row).find('T') + if t_r != b_r or t_c != b_c: + self.position = Coordinate(t_r, t_c) + return + + def bypass_obstacle(self): + if not self.invert: + self.direction = Direction.SOUTH + if not self.is_obstacle(self.get_next_item()): + return + + self.direction = Direction.EAST + if not self.is_obstacle(self.get_next_item()): + return + + self.direction = Direction.NORTH + if not self.is_obstacle(self.get_next_item()): + return + + self.direction = Direction.WEST + if not self.is_obstacle(self.get_next_item()): + return + + else: + self.direction = Direction.WEST + if not self.is_obstacle(self.get_next_item()): + return + + self.direction = Direction.NORTH + if not self.is_obstacle(self.get_next_item()): + return + + self.direction = Direction.EAST + if not self.is_obstacle(self.get_next_item()): + return + + self.direction = Direction.SOUTH + if not self.is_obstacle(self.get_next_item()): + return + + def step_forward(self): + if self.direction == Direction.SOUTH: + self.position.r += 1 + self.instructions.append('SOUTH') + elif self.direction == Direction.NORTH: + self.position.r -= 1 + self.instructions.append('NORTH') + elif self.direction == Direction.EAST: + self.position.c += 1 + self.instructions.append('EAST') + elif self.direction == Direction.WEST: + self.position.c -= 1 + self.instructions.append('WEST') + + # self.print_map() + + def get_next_item(self): + if self.direction == Direction.SOUTH: + return self.city_map[self.position.r + 1][self.position.c] + elif self.direction == Direction.NORTH: + return self.city_map[self.position.r - 1][self.position.c] + elif self.direction == Direction.EAST: + return self.city_map[self.position.r][self.position.c + 1] + elif self.direction == Direction.WEST: + return self.city_map[self.position.r][self.position.c - 1] + + def is_obstacle(self, next_item): + if self.fury: + return next_item == '#' + else: + return next_item == '#' or next_item == 'X' + + +def main(): + l, c = [int(i) for i in input().split()] + city_map = [] + for i in range(l): + city_map.append(list(input())) + + game = Game(city_map) + game.run() + + +if __name__ == '__main__': + main() diff --git a/cgx_formatter.py b/cgx_formatter.py new file mode 100644 index 0000000..4322613 --- /dev/null +++ b/cgx_formatter.py @@ -0,0 +1,42 @@ +import sys +import re + + +class Element: + pass + + +class Primitive(Element): + def __init__(self, value): + self.value = value + + +class Block(Element): + def __init__(self, lst=None): + if lst is None: + lst = [] + self.content = lst + + +class KeyValue(Element): + def __init__(self, key, value): + self.key = key + self.value = value + + +def parse(cgx_input): + return cgx_input + + +def main(): + n = int(input()) + cgx_input = '' + for i in range(n): + cgx_input += input().strip() + + output = parse(cgx_input) + + print(output) + +if __name__ == '__main__': + main() diff --git a/community_graph.py b/community_graph.py new file mode 100644 index 0000000..2b3157d --- /dev/null +++ b/community_graph.py @@ -0,0 +1,45 @@ +def main(): + points = {} + min_abs, max_abs = 0, 0 + min_ord, max_ord = 0, 0 + + n = int(input()) + for i in range(n): + x, y = [int(j) for j in input().split()] + points[str_point(x, y)] = True + + if x > max_abs: + max_abs = x + elif x < min_abs: + min_abs = x + + if y > max_ord: + max_ord = y + elif y < min_ord: + min_ord = y + + print_graph(points, min_abs, max_abs, min_ord, max_ord) + + +def print_graph(points, min_abs, max_abs, min_ord, max_ord): + for row in range(max_ord + 1, min_ord - 2, -1): + for col in range(min_abs - 1, max_abs + 2): + if points.get(str_point(col, row)): + print('*', end='') + elif row == 0 and col == 0: + print('+', end='') + elif row == 0: + print('-', end='') + elif col == 0: + print('|', end='') + else: + print('.', end='') + print() + + +def str_point(x, y): + return str(x) + ';' + str(y) + + +if __name__ == '__main__': + main() diff --git a/community_speed_limit.py b/community_speed_limit.py new file mode 100644 index 0000000..1a3eacc --- /dev/null +++ b/community_speed_limit.py @@ -0,0 +1,42 @@ +import sys +from collections import OrderedDict + + +def main(): + speed_limit = int(input()) + n = int(input()) + vehicles = OrderedDict() + for i in range(n): + plaque, distance, timestamp = input().split() + distance = int(distance) + timestamp = int(timestamp) + vehicles.setdefault(plaque, []).append((distance, timestamp)) + + found_excess = False + for k, v in vehicles.items(): + overspeeds = find_over_speed(v, speed_limit) + for overspeed in overspeeds: + found_excess = True + print(k, overspeed[1]) + + if not found_excess: + print('OK') + + +def find_over_speed(vehicle, speed_limit): + distance, timestamp = vehicle[0] + overspeeds = [] + + for data in vehicle[1:]: + dist_delta = data[0] - distance + time_delta = (data[1] - timestamp) / 3600 + speed = dist_delta / time_delta + if speed > speed_limit: + overspeeds.append((speed, data[0])) + distance, timestamp = data + + return overspeeds + + +if __name__ == '__main__': + main() diff --git a/dont_panic_2.py b/dont_panic_2.py new file mode 100644 index 0000000..8af4ecd --- /dev/null +++ b/dont_panic_2.py @@ -0,0 +1,55 @@ +import sys +import math +from collections import defaultdict + +# Auto-generated code below aims at helping you parse +# the standard input according to the problem statement. + +# nb_floors: number of floors +# width: width of the area +# nb_rounds: maximum number of rounds +# exit_floor: floor on which the exit is found +# exit_pos: position of the exit on its floor +# nb_total_clones: number of generated clones +# nb_additional_elevators: ignore (always zero) +# nb_elevators: number of elevators +nb_floors, width, nb_rounds, exit_floor, exit_pos, nb_total_clones, nb_additional_elevators, nb_elevators = [int(i) for + i in + input().split()] +elevators = defaultdict(int) +for i in range(nb_elevators): + # elevator_floor: floor on which this elevator is found + # elevator_pos: position of the elevator on its floor + elevator_floor, elevator_pos = [int(j) for j in input().split()] + elevators[elevator_floor] = elevator_pos + +print(elevators, file=sys.stderr) +# game loop +while True: + # clone_floor: floor of the leading clone + # clone_pos: position of the leading clone on its floor + # direction: direction of the leading clone: LEFT or RIGHT + clone_floor, clone_pos, direction = input().split() + clone_floor = int(clone_floor) + clone_pos = int(clone_pos) + + action = "WAIT" + + if clone_pos == width - 1 or clone_pos == 0: + action = 'BLOCK' + elif clone_floor == exit_floor: + if clone_pos > exit_pos and direction == 'RIGHT': + action = 'BLOCK' + elif clone_pos < exit_pos and direction == 'LEFT': + action = 'BLOCK' + elif clone_pos > elevators[clone_floor] and direction == 'RIGHT': + action = 'BLOCK' + elif clone_pos < elevators[clone_floor] and direction == 'LEFT': + action = 'BLOCK' + + print(action) + + # Write an action using print + # To debug: print("Debug messages...", file=sys.stderr) + + # action: WAIT or BLOCK diff --git a/dwarfs_standing_on_the_shoulders_of_giants.py b/dwarfs_standing_on_the_shoulders_of_giants.py new file mode 100644 index 0000000..663cd5e --- /dev/null +++ b/dwarfs_standing_on_the_shoulders_of_giants.py @@ -0,0 +1,21 @@ +def main(): + n = int(input()) # the number of relationships of influence + influences = {} + for i in range(n): + # x: a relationship of influence between two people (x influences y) + x, y = [int(j) for j in input().split()] + influences.setdefault(x, []).append(y) + + all_lengths = [dfs(influences, x) for x in influences] + print(max(all_lengths)) + + +def dfs(graph, node, depth=1): + for succ in graph[node]: + if succ in graph: + return dfs(graph, succ, depth + 1) + return depth + 1 + + +if __name__ == '__main__': + main() diff --git a/fizzbuzz.py b/fizzbuzz.py new file mode 100644 index 0000000..74158ee --- /dev/null +++ b/fizzbuzz.py @@ -0,0 +1,22 @@ +from collections import OrderedDict + + +def main(): + mapping = OrderedDict({ + 3: 'Fizz', + 5: 'Buzz' + }) + for i in range(1, 101): + s = '' + for k, v in mapping.items(): + if i % k == 0: + s += v + + if s == '': + s = i + + print(s) + + +if __name__ == '__main__': + main() diff --git a/mars_lander_2.py b/mars_lander_2.py new file mode 100644 index 0000000..1142edb --- /dev/null +++ b/mars_lander_2.py @@ -0,0 +1,59 @@ +import sys +import math + +# Auto-generated code below aims at helping you parse +# the standard input according to the problem statement. + +# the number of points used to draw the surface of Mars. +surface_n = int(input()) + +prev_x, prev_y = (-1, -1) +start_x, start_y, end_x, end_y = (-1, -1, -1, -1) + +for i in range(surface_n): + # land_x: X coordinate of a surface point. (0 to 6999) + # land_y: Y coordinate of a surface point. + # By linking all the points together in a sequential fashion, you form the surface of Mars. + + land_x, land_y = [int(j) for j in input().split()] + if land_y == prev_y: + start_x, start_y = prev_x, prev_y + + if start_x != -1 and land_y != prev_y: + end_x, end_y = prev_x, prev_y + + prev_x, prev_y = land_x, land_y + + +# game loop +while True: + # h_speed: the horizontal speed (in m/s), can be negative. + # v_speed: the vertical speed (in m/s), can be negative. + # fuel: the quantity of remaining fuel in liters. + # rotate: the rotation angle in degrees (-90 to 90). + # power: the thrust power (0 to 4). + x, y, h_speed, v_speed, fuel, rotate, power = [int(i) for i in input().split()] + + if x < start_x: + s_angle = -30 + s_power = 4 + elif x > end_x: + s_angle = 30 + s_power = 4 + else: + if h_speed > 10: + s_angle = 22 + s_power = 4 + elif h_speed < -10: + s_angle = -22 + s_power = 4 + else: + if y > end_y + 500: + s_power = 3 + else: + s_power = 4 + s_angle = 0 + + + # rotate power. rotate is the desired rotation angle. power is the desired thrust power. + print("{} {}".format(s_angle, s_power)) diff --git a/network_cabling.py b/network_cabling.py new file mode 100644 index 0000000..2ab6944 --- /dev/null +++ b/network_cabling.py @@ -0,0 +1,19 @@ +import sys +import math +import statistics + +n = int(input()) +verticals = [] +horizontals = [] +for i in range(n): + x, y = [int(j) for j in input().split()] + horizontals.append(x) + verticals.append(y) + +median = round(statistics.median(verticals)) + +total = max(horizontals) - min(horizontals) +for y in verticals: + total += abs(y - median) + +print(total) diff --git a/polynomial.py b/polynomial.py new file mode 100644 index 0000000..5ad3581 --- /dev/null +++ b/polynomial.py @@ -0,0 +1,43 @@ +from itertools import zip_longest + + +class Polynomial: + def __init__(self, *args): + if len(args) == 0: + args = (0,) + self.coeffs = args + + def __repr__(self): + return 'Polynomial{}'.format(repr(self.coeffs)) + + def __str__(self): + xs = ('{}*x^{}'.format(self.coeffs[i], i) for i in range(len(self.coeffs))) + return ' + '.join(xs) + + def __len__(self): + return len(self.coeffs) - 1 + + def __add__(self, other): + return Polynomial(*(x + y for x, y in zip_longest(self.coeffs, other.coeffs, fillvalue=0))) + + def __sub__(self, other): + return Polynomial(*(x - y for x, y in zip_longest(self.coeffs, other.coeffs, fillvalue=0))) + + +def main(): + p1 = Polynomial(1, 2, 3) + print(p1) + print(len(p1)) + p2 = Polynomial(2, 3, 4, 5) + print(repr(p2)) + print(p2) + print(len(p2)) + print(p1 + p2) + print(p1 - p2) + p3 = Polynomial() + print(repr(p3)) + p4 = Polynomial(0) + print(p4) + +if __name__ == '__main__': + main() diff --git a/super_computer.py b/super_computer.py new file mode 100644 index 0000000..b517d02 --- /dev/null +++ b/super_computer.py @@ -0,0 +1,20 @@ +task_number = int(input()) +calculus = [] +for i in range(task_number): + start, duration = [int(j) for j in input().split(' ')] + + calculus.append({ + 'start': start, + 'end': start + duration - 1, + }) + +calculus.sort(key=lambda x: x['end']) +selected = 1 +last_selected = calculus[0] + +for calc in calculus[1:]: + if calc['start'] > last_selected['end']: + selected += 1 + last_selected = calc + +print(selected) diff --git a/teads.py b/teads.py new file mode 100644 index 0000000..1bcb9f9 --- /dev/null +++ b/teads.py @@ -0,0 +1,52 @@ +import sys +import math + + +def main(): + n = int(input()) + + people = {} + for i in range(n): + xi, yi = [int(j) for j in input().split()] + pxi = people.setdefault(xi, set()) + pyi = people.setdefault(yi, set()) + pxi.add(yi) + pyi.add(xi) + + # perr('people', people) + + min_length = math.inf + # perr('people', people) + for start in people: + res = {} + dfs3(people, start, res) + # perr('res', res) + inner_max_length = max(res.values()) + + if inner_max_length < min_length: + min_length = inner_max_length + + print(min_length) + + +def dfs3(graph: dict, start: str, res: dict, depth: int = 0, visited: set = None) -> None: + if visited is None: + visited = set() + visited.add(start) + + depth += 1 + + nxt = set(graph.get(start, [])) - visited + for succ in nxt: + distance = res.get(succ) + if (distance and depth < distance) or not distance: + res[succ] = depth + dfs3(graph, succ, res, depth, visited) + + +def perr(*values): + print(*values, file=sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/the_gift.py b/the_gift.py new file mode 100644 index 0000000..ef9ad5a --- /dev/null +++ b/the_gift.py @@ -0,0 +1,33 @@ +import sys +import math + + +def main(): + oods_number = int(input()) + gift_cost = int(input()) + budgets = [] + for i in range(oods_number): + budgets.append(int(input())) + + budgets.sort() + total = sum(budgets) + + if total < gift_cost: + print("IMPOSSIBLE") + return + + results = [] + for i, budget in enumerate(budgets): + ideal = int((gift_cost - sum(results)) / (oods_number - i)) + if budget < ideal: + results.append(budget) + else: + results.append(ideal) + + results.sort() + + for res in results: + print(res) + +if __name__ == '__main__': + main() diff --git a/the_labyrinth.py b/the_labyrinth.py new file mode 100644 index 0000000..d7f756a --- /dev/null +++ b/the_labyrinth.py @@ -0,0 +1,287 @@ +import sys +import math +from collections import deque + +WALL = '#' +EMPTY = '.' +FOG = '?' +COMMAND = 'C' +START = 'T' + +LEFT = 'LEFT' +RIGHT = 'RIGHT' +UP = 'UP' +DOWN = 'DOWN' + +TARGET_NODE = { + COMMAND: None, + START: None, + 'explore': None +} + + +class Node: + def __init__(self, r, c, g=0, h=0, parent=None, maze=None): + if maze is None: + maze = [[]] + self._maze = maze + self.r = r + self.c = c + self.g = g + self.h = h + self.parent = parent + self.colored = False + + def __str__(self): + return 'Node({}, {}, {})'.format(self.r, self.c, self.type) + + def __repr__(self): + return self.__str__() + + @property + def f(self): + return self.g + self.h + + def __lt__(self, other): + return self.f < other.f + + def __le__(self, other): + return self.f <= other.f + + def __gt__(self, other): + return self.f > other.f + + def __ge__(self, other): + return self.f >= other.f + + def __eq__(self, other): + return self.r == other.r and self.c == other.c + + @property + def type(self): + return self._maze[self.r][self.c] + + def direction_to(self, other): + if other.r > self.r: + return DOWN + elif other.r < self.r: + return UP + elif other.c > self.c: + return RIGHT + elif other.c < self.c: + return LEFT + + def distance_2_to(self, other): + return (self.c - other.c) ** 2 + (self.r - other.r) ** 2 + + +def main(): + # r: number of rows. + # c: number of columns. + # a: number of rounds between the time the alarm countdown is activated and the time the alarm goes off. + rows, columns, a = [int(i) for i in input().split()] + target = COMMAND + + # visited = [] + # current_node = None + # last_intersection = None + steps_remaining = 0 + while True: + # kr: row where Kirk is located. + # kc: column where Kirk is located. + kr, kc = [int(i) for i in input().split()] + maze = [] + start_node = None + command_node = None + for i in range(rows): + row = input() + + if START in row and not start_node: + start_node = Node(i, row.index(START), maze=maze) + TARGET_NODE[START] = start_node + if COMMAND in row and not command_node: + command_node = Node(i, row.index(COMMAND), maze=maze) + TARGET_NODE[COMMAND] = command_node + + maze.append(row) + + current_node = Node(kr, kc, 0, 0, None, maze=maze) + + if steps_remaining > 0: + steps_remaining -= 1 + perr('steps_remaining', steps_remaining) + + else: + if current_node.type == COMMAND: + target = START + perr(maze) + + if target == COMMAND: + path_target = remaining_exploration(maze, current_node, rows, columns) + if not path_target: + path_target = TARGET_NODE[COMMAND] + + else: + path_target = TARGET_NODE[START] + + path = [] + + perr('TARGET', target) + perr('Algorithm target', path_target) + perr('current', current_node) + + found = a_star_search(current_node, path_target, maze, rows, columns) + + walker = found + perr('walker', walker) + while walker is not None: + path.append(walker) + walker = walker.parent + + path.reverse() + perr('path', path) + + prev = None + for node in path: + if prev: + print(prev.direction_to(node)) + steps_remaining = len(path) - 2 + prev = node + + +def bfs_search(current_node, path_target, maze, rows, columns): + perr('STARTING BFS') + queue = deque([current_node]) + visited = [] + found = None + + while len(queue) > 0 and not found: + item = queue.popleft() + visited.append(item) + successors = generate_successors(maze, item, rows, columns) + perr('visited', visited) + for s in successors: + perr('s', s) + if s == path_target: + perr('FOUND TARGET') + found = s + break + elif s.type == '#': + visited.append(s) + elif path_target.type != COMMAND and s.type == COMMAND: + visited.append(s) + else: + skip = False + for o in queue: + if s == o and o < s: + skip = True + break + + for c in visited: + if s == c and c < s: + skip = True + break + if not skip: + queue.append(s) + + return found + + +def a_star_search(current_node, path_target, maze, rows, columns): + perr('STARTING A*') + open_list = deque([current_node]) + closed_list = [] + found = None + + # A* loop + while len(open_list) > 0 and not found: + q = min(open_list) + open_list.remove(q) + successors = generate_successors(maze, q, rows, columns) + perr('q', q) + for s in successors: + if s == path_target: + perr('FOUND TARGET') + found = s + break + + if s.type == '#': + h = math.inf + elif path_target.type != COMMAND and s.type == COMMAND: + h = math.inf + else: + # TODO: need a better heuristic for last 2 levels (zigzags are baaad) + h = s.distance_2_to(path_target) + s.h = h + + skip = False + for o in open_list: + if s == o and o < s: + skip = True + break + + for c in closed_list: + if s == c and c < s: + skip = True + break + + if not skip: + open_list.append(s) + + closed_list.append(q) + + perr('FOUND A* PATH') + return found + + +def generate_successors(maze, node, max_rows, max_columns, with_walls=False, with_fog=False): + successors = [] + successor_g = node.g + 1 + + if (node.c - 1) >= 0: + succ = Node(node.r, node.c - 1, g=successor_g, parent=node, maze=maze) + if not (succ.type == WALL and not with_walls or succ.type == FOG and not with_fog): + successors.append(succ) + + if (node.r + 1) < max_rows: + succ = Node(node.r + 1, node.c, g=successor_g, parent=node, maze=maze) + if not (succ.type == WALL and not with_walls or succ.type == FOG and not with_fog): + successors.append(succ) + + if (node.c + 1) < max_columns: + succ = Node(node.r, node.c + 1, g=successor_g, parent=node, maze=maze) + if not (succ.type == WALL and not with_walls or succ.type == FOG and not with_fog): + successors.append(succ) + + if (node.r - 1) >= 0: + succ = Node(node.r - 1, node.c, g=successor_g, parent=node, maze=maze) + if not (succ.type == WALL and not with_walls or succ.type == FOG and not with_fog): + successors.append(succ) + + return successors + + +def remaining_exploration(maze, node, max_rows, max_columns): + remaining = deque([node]) + visited = [] + while len(remaining) > 0: + cur = remaining.popleft() + visited.append(cur) + neighbors = generate_successors(maze, cur, max_rows, max_columns, with_walls=False, with_fog=True) + for n in neighbors: + if n.type == FOG: + if cur.type == COMMAND: + return cur.parent + else: + return cur + if n not in remaining and n not in visited: + remaining.append(n) + + return None + + +def perr(*values, **kwargs): + print(*values, file=sys.stderr, flush=True, **kwargs) + +if __name__ == '__main__': + main()