philRG

Hypersonic Draft

Jan 18th, 2021 (edited)
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.61 KB | None | 0 0
  1. import heapq
  2. from queue import PriorityQueue
  3. import sys
  4.  
  5. sys.setrecursionlimit(20000)
  6.  
  7.  
  8. def eprint(msg):
  9.     return
  10.     print(msg, file=sys.stderr, flush=True)
  11.  
  12.  
  13. def iprint(msg):
  14.     # return
  15.     print(msg, file=sys.stderr, flush=True)
  16.  
  17.  
  18. PLAYER = 0
  19. BOMB = 1
  20. OBJECT = 2
  21. XTRARANGE = 1
  22. XTRABOMB = 2
  23. COUNTDOWN = 8
  24.  
  25.  
  26. class Action:
  27.     def __init__(self, type, target):
  28.         self.type = type
  29.         self.target = target
  30.  
  31.     def __repr__(self):
  32.         return "{} {}".format(self.type, self.target)
  33.  
  34.  
  35. class Pos(object):
  36.     def __init__(self, x, y):
  37.         self.x = x
  38.         self.y = y
  39.  
  40.     def distance(self, other):
  41.         return abs(self.x - other.x) + abs(self.y - other.y)
  42.  
  43.     # fonction pour transformer les coordonnées d'une cellule pour pouvoir utiliser l'algorithme AStar
  44.     def transpose(self):
  45.         return self.x, game.grid.height - 1 - self.y
  46.  
  47.     def __repr__(self):
  48.         return f'{self.x} {self.y}'
  49.  
  50.  
  51. class Cell(Pos):
  52.     def __init__(self, x, y, reachable=True, value=None):
  53.         """Initialize new cell.
  54.  
  55.        @param reachable is cell reachable? not a wall?
  56.        @param x cell x coordinate
  57.        @param y cell y coordinate
  58.        @param g cost to move from the starting cell to this cell.
  59.        @param h estimation of the cost to move from this cell
  60.                 to the ending cell.
  61.        @param f f = g + h
  62.        """
  63.         super().__init__(x, y)
  64.         self.value = value
  65.         self.reachable = reachable
  66.         self.x = x
  67.         self.y = y
  68.         self.parent = None
  69.         self.g = 0
  70.         self.h = 0
  71.         self.f = 0
  72.  
  73.     def update(self, value, bombs):
  74.         self.value = value
  75.         if value in ['X', '0', '1', '2'] or self in bombs:
  76.             self.reachable = False
  77.  
  78.     def is_safe_from(self, bomb):
  79.         if self.x == bomb.x:
  80.             if self.distance(bomb) > bomb.range:
  81.                 return True
  82.             else:
  83.                 y_range = range(bomb.y, self.y + 1) if bomb.y < self.y else range(bomb.y, self.y - 1, -1)
  84.                 for y in y_range:
  85.                     c = game.grid.get_cell(self.x, y)
  86.                     if not c.reachable:
  87.                         return True
  88.         elif self.y == bomb.y:
  89.             if self.distance(bomb) > bomb.range:
  90.                 return True
  91.             else:
  92.                 x_range = range(bomb.x, self.x + 1) if bomb.x < self.x else range(bomb.x, self.x - 1, -1)
  93.                 for x in x_range:
  94.                     c = game.grid.get_cell(x, self.y)
  95.                     if not c.reachable:
  96.                         return True
  97.         return False
  98.  
  99.     def __hash__(self):
  100.         return hash((self.x, self.y))
  101.  
  102.     def __lt__(self, other):
  103.         return self.f < other.f
  104.  
  105.  
  106. class AStar(object):
  107.     def __init__(self):
  108.         # open list
  109.         self.opened = []
  110.         heapq.heapify(self.opened)
  111.         # visited cells list
  112.         self.closed = set()
  113.         # grid cells
  114.         self.cells = []
  115.         self.grid_height = None
  116.         self.grid_width = None
  117.  
  118.     def init_grid(self, width, height, walls, start, end):
  119.         """Prepare grid cells, walls.
  120.        @param width grid's width.
  121.        @param height grid's height.
  122.        @param walls list of wall x,y tuples.
  123.        @param start grid starting point x,y tuple.
  124.        @param end grid ending point x,y tuple.
  125.        """
  126.         self.grid_height = height
  127.         self.grid_width = width
  128.         for x in range(self.grid_width):
  129.             for y in range(self.grid_height):
  130.                 if (x, y) in walls:
  131.                     reachable = False
  132.                 else:
  133.                     reachable = True
  134.                 self.cells.append(Cell(x, y, reachable))
  135.         self.start = self.get_cell(*start)
  136.         self.end = self.get_cell(*end)
  137.  
  138.     def get_heuristic(self, cell):
  139.         """Compute the heuristic value H for a cell.
  140.  
  141.        Distance between this cell and the ending cell multiply by 10.
  142.  
  143.        @returns heuristic value H
  144.        """
  145.         return 10 * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y))
  146.  
  147.     def get_cell(self, x, y):
  148.         """Returns a cell from the cells list.
  149.  
  150.        @param x cell x coordinate
  151.        @param y cell y coordinate
  152.        @returns cell
  153.        """
  154.         return self.cells[x * self.grid_height + y]
  155.  
  156.     def get_adjacent_cells(self, cell):
  157.         """Returns adjacent cells to a cell.
  158.  
  159.        Clockwise starting from the one on the right.
  160.  
  161.        @param cell get adjacent cells for this cell
  162.        @returns adjacent cells list.
  163.        """
  164.         cells = []
  165.         if cell.x < self.grid_width - 1:
  166.             cells.append(self.get_cell(cell.x + 1, cell.y))
  167.         if cell.y > 0:
  168.             cells.append(self.get_cell(cell.x, cell.y - 1))
  169.         if cell.x > 0:
  170.             cells.append(self.get_cell(cell.x - 1, cell.y))
  171.         if cell.y < self.grid_height - 1:
  172.             cells.append(self.get_cell(cell.x, cell.y + 1))
  173.         return cells
  174.  
  175.     def get_path(self):
  176.         cell = self.end
  177.         path = [(cell.x, cell.y)]
  178.         while cell.parent and cell.parent is not self.start:
  179.             cell = cell.parent
  180.             path.append((cell.x, cell.y))
  181.  
  182.         path.append((self.start.x, self.start.y))
  183.         path.reverse()
  184.         return path
  185.  
  186.     def update_cell(self, adj, cell):
  187.         """Update adjacent cell.
  188.  
  189.        @param adj adjacent cell to current cell
  190.        @param cell current cell being processed
  191.        """
  192.         adj.g = cell.g + 10
  193.         adj.h = self.get_heuristic(adj)
  194.         adj.parent = cell
  195.         adj.f = adj.h + adj.g
  196.  
  197.     def solve(self):
  198.         """Solve maze, find path to ending cell.
  199.  
  200.        @returns path or None if not found.
  201.        """
  202.         # add starting cell to open heap queue
  203.         heapq.heappush(self.opened, (self.start.f, self.start))
  204.         while len(self.opened):
  205.             # pop cell from heap queue
  206.             f, cell = heapq.heappop(self.opened)
  207.             # add cell to closed list so we don't process it twice
  208.             self.closed.add(cell)
  209.             # if ending cell, return found path
  210.             if cell is self.end:
  211.                 return self.get_path()
  212.             # get adjacent cells for cell
  213.             adj_cells = self.get_adjacent_cells(cell)
  214.             for adj_cell in adj_cells:
  215.                 if adj_cell.reachable and adj_cell not in self.closed:
  216.                     if (adj_cell.f, adj_cell) in self.opened:
  217.                         # if adj cell in open list, check if current path is
  218.                         # better than the one previously found
  219.                         # for this adj cell.
  220.                         if adj_cell.g > cell.g + 10:
  221.                             self.update_cell(adj_cell, cell)
  222.                     else:
  223.                         self.update_cell(adj_cell, cell)
  224.                         # add adj cell to open list
  225.                         heapq.heappush(self.opened, (adj_cell.f, adj_cell))
  226.  
  227.     # Similitude transformation: Translate X Axis (- height) * X-Axis symmetry
  228.     def transpose(self, cells):
  229.         return [(c.x, self.height - 1 - c.y) for c in cells]
  230.  
  231.  
  232. #######################@
  233.  
  234.  
  235. class Player(Pos):
  236.     def __init__(self, id, x, y, remaining_bombs, bombs_range):
  237.         super().__init__(x, y)
  238.         self.id = id
  239.         self.remaining_bombs = remaining_bombs
  240.         self.bombs_range = bombs_range
  241.  
  242.     def boxes_in_range(self, bomb_range):
  243.         damage = 0
  244.         boxes = ['0', '1', '2']
  245.         for r in range(1, bomb_range):
  246.             x1, x2, y1, y2 = self.x + r, self.x - r, self.y + r, self.y - r
  247.             if 0 < x1 < self.grid.width and self.grid.get_cell(x1, self.y).value in boxes:
  248.                 damage += 1
  249.             if 0 < x2 < self.grid.width and self.grid.get_cell(x2, self.y).value in boxes:
  250.                 damage += 1
  251.             if 0 < y1 < self.grid.height and self.grid.get_cell(self.x, y1).value in boxes:
  252.                 damage += 1
  253.             if 0 < y2 < self.grid.height and self.grid.get_cell(self.x, y2).value in boxes:
  254.                 damage += 1
  255.         return damage
  256.  
  257.     def __str__(self):
  258.         return "Player id {} ({},{}) -> remaining_bombs: {}, bombs_range: {}".format(self.id, self.x, self.y,
  259.                                                                                      self.remaining_bombs,
  260.                                                                                      self.bombs_range)
  261.  
  262.  
  263. class Bomb(Pos):
  264.     def __init__(self, owner_id, x, y, countdown, brange):
  265.         super().__init__(x, y)
  266.         self.owner_id = owner_id
  267.         self.countdown = countdown
  268.         self.range = brange
  269.  
  270.     def __str__(self):
  271.         return "Bomb owner_id {} ({},{}) -> countdown: {}, range: {}".format(self.owner_id, self.x, self.y,
  272.                                                                              self.countdown, self.range)
  273.  
  274.  
  275. class Object(Pos):
  276.     def __init__(self, x, y, type):
  277.         super().__init__(x, y)
  278.         self.type = type
  279.  
  280.     def __str__(self):
  281.         type_name = "Extra Range +1 Bomb" if self.type == 1 else "Extra Bomb"
  282.         return "Object ({},{}) -> type: {}".format(self.owner_id, self.x, self.y, self.type_name)
  283.  
  284.  
  285. class Grid:
  286.     def __init__(self, width=None, height=None):
  287.         self.cells = []
  288.         self.walls = []
  289.         self.width, self.height = width, height
  290.  
  291.     def init(self, w, h):
  292.         self.width, self.height = w, h
  293.         for y in range(h):
  294.             for x in range(w):
  295.                 self.cells.append(Cell(x, y, reachable=True, value='.'))
  296.  
  297.     def get_cell(self, x, y):
  298.         if self.width > x >= 0 and self.height > y >= 0:
  299.             return self.cells[x + self.width * y]
  300.         return None
  301.  
  302.  
  303. class Game:
  304.     def __init__(self):
  305.         self.bombs = []
  306.         self.players = []
  307.         self.objects = []
  308.         self.grid = Grid()
  309.         self.me, self.my_id = None, None
  310.         self.reachable_cells = []
  311.  
  312.     def init(self):
  313.         line = input()
  314.         iprint(line)
  315.         width, height, self.my_id = [int(i) for i in line.split()]
  316.         self.grid.init(width, height)
  317.  
  318.     def reset(self):
  319.         self.bombs.clear()
  320.         self.objects.clear()
  321.         self.reachable_cells.clear()
  322.  
  323.     def update(self):
  324.         grid_input = []
  325.         for i in range(self.grid.height):
  326.             line = input()
  327.             iprint(line)
  328.             grid_input.append([c for c in line])
  329.  
  330.         entities = int(input())
  331.         iprint(entities)
  332.         for i in range(entities):
  333.             line = input()
  334.             iprint(line)
  335.             entity_type, owner, x, y, param_1, param_2 = [int(j) for j in line.split()]
  336.             if entity_type == PLAYER:
  337.                 self.players.append(Player(owner, x, y, remaining_bombs=param_1, bombs_range=param_2))
  338.             elif entity_type == BOMB:
  339.                 self.bombs.append(Bomb(owner, x, y, countdown=param_1, brange=param_2))
  340.             else:
  341.                 self.objects.append(Object(x, y, type=param_1))
  342.  
  343.         self.me = [p for p in self.players if p.id == self.my_id][0]
  344.  
  345.         for y in range(self.grid.height):
  346.             for x in range(self.grid.width):
  347.                 value = grid_input[y][x]
  348.                 cell = self.grid.get_cell(x, y)
  349.                 cell.update(value, self.bombs)
  350.                 if not cell.reachable:
  351.                     self.grid.walls.append((cell.x, cell.y))
  352.         # print()
  353.  
  354.     def getDijkstraPath(self, source, target):
  355.         # constitution du meilleur chemin pour se rendre vers le point cible
  356.         pf = AStar()
  357.         pf.init_grid(self.grid.width, self.grid.height, self.transpose(self.grid.walls), source.transpose(),
  358.                      target.transpose())
  359.         path = pf.solve()
  360.         # print("Calcul chemin de  {} vers {} : {}".format(fromCell, destCell, path), file=sys.stderr)
  361.         return self.transpose(path) if path else None
  362.  
  363.     # Similitude transformation: Translate X Axis (- height) * X-Axis symmetry
  364.     def transpose(self, cells):
  365.         return [(c[0], self.grid.height - 1 - c[1]) for c in cells]
  366.  
  367.     def get_adjacent_cells(self, cell):
  368.         return [c for c in self.grid.cells if c.distance(cell) == 1 and c.value == '.']
  369.  
  370.     def dfs(self, graph, start, goal):
  371.         visited = []
  372.         path = []
  373.         fringe = PriorityQueue()
  374.         fringe.put((0, start, path, visited))
  375.  
  376.         while not fringe.empty():
  377.             depth, current_node, path, visited = fringe.get()
  378.  
  379.             if current_node == goal:
  380.                 return path + [current_node]
  381.  
  382.             visited = visited + [current_node]
  383.  
  384.             child_nodes = graph[current_node]
  385.             for node in child_nodes:
  386.                 if node not in visited:
  387.                     if node == goal:
  388.                         return path + [node]
  389.                     depth_of_node = len(path)
  390.                     fringe.put((-depth_of_node, node, path + [node], visited))
  391.         return path
  392.  
  393.     def dfs_recursive(self, graph, start, goal, path=None):
  394.         if path is None:
  395.             path = [start]
  396.         if start == goal:
  397.             # yield path
  398.             return path
  399.         for neighbour in graph.get(start, [])[::-1]:
  400.             if neighbour not in path:
  401.                 # yield from dfs_recursive(graph, neighbour, goal, path + [neighbour])
  402.                 self.dfs_recursive(graph, neighbour, goal, path + [neighbour])
  403.         return []
  404.  
  405.     '''Evaluate damage of bomb b to boxes'''
  406.  
  407.     def bomb_damage(self, b):
  408.         damage = 0
  409.         boxes = ['0', '1', '2']
  410.         for r in range(1, b.range):
  411.             x1, x2, y1, y2 = b.x + r, b.x - r, b.y + r, b.y - r
  412.             if 0 < x1 < self.grid.width and self.grid.get_cell(x1, b.y).value in boxes:
  413.                 damage += 1
  414.             if 0 < x2 < self.grid.width and self.grid.get_cell(x2, b.y).value in boxes:
  415.                 damage += 1
  416.             if 0 < y1 < self.grid.height and self.grid.get_cell(b.x, y1).value in boxes:
  417.                 damage += 1
  418.             if 0 < y2 < self.grid.height and self.grid.get_cell(b.x, y2).value in boxes:
  419.                 damage += 1
  420.         return damage
  421.  
  422.  
  423.     ### TODO Predict explosions in chains
  424.     def eval(self, action):
  425.         score = 0
  426.         my_pos = self.grid.get_cell(self.me.x, self.me.y)
  427.         if action.type == 'BOMB':
  428.             new_bomb = Bomb(self.me.id, self.me.x, self.me.y, COUNTDOWN, self.me.bombs_range)
  429.             score = score - 1000 if not action.target.is_safe_from(new_bomb) else score
  430.         else:  # MOVE
  431.             reachable_cells = [my_pos] + [c for c, d in self.reachable_cells if c != action.target]
  432.             new_bomb = Bomb(self.me.id, action.target.x, action.target.y, COUNTDOWN, self.me.bombs_range)
  433.             score += len([c for c in reachable_cells if c.is_safe_from(new_bomb)])  # count of reachable cells to move away
  434.             path = self.getDijkstraPath(self.me, action.target)
  435.             score += 2 * len([o for o in self.objects if o in path and o.type in [1, 2]])
  436.         score += self.bomb_damage(new_bomb)
  437.         return score
  438.  
  439.     def build_output(self):
  440.         edges_list = [*self.grid.cells]
  441.         graph = {}
  442.         while edges_list:
  443.             edge = edges_list.pop(0)
  444.             graph[edge] = set(self.get_adjacent_cells(edge))
  445.  
  446.         # Keep a list of reachable cells and distances
  447.         # reachable_cells = [c for c in game.grid.cells if game.dfs(graph, my_pos, c)]
  448.         my_pos = game.grid.get_cell(self.me.x, self.me.y)
  449.         for c in self.grid.cells:
  450.             if c != my_pos:
  451.                 path = self.getDijkstraPath(my_pos, c)
  452.                 if path is not None:
  453.                     self.reachable_cells.append((c, len(path)))
  454.  
  455.         actions_candidates = []
  456.         # Keep a list of bomb positions and damages
  457.         actions = ['BOMB', 'MOVE'] if game.me.remaining_bombs else ['MOVE']
  458.         for c, distance in self.reachable_cells:
  459.             actions_candidates += [(Action(action, c), distance) for action in actions]
  460.         actions_score = [(action, distance, game.eval(action)) for action, distance in actions_candidates]
  461.         best_score = max(actions_score, key=lambda a: a[2])[2]
  462.         best_actions = [(action, distance) for action, distance, score in actions_score if score == best_score]
  463.         self.action = min(best_actions, key=lambda a: a[1])[0]
  464.  
  465.     def output(self):
  466.         print(self.action)
  467.  
  468.     def debug(self):
  469.         for p in self.players:
  470.             eprint(p)
  471.         eprint(self.grid)
  472.  
  473.  
  474. game = Game()
  475.  
  476. game.init()
  477. # game loop
  478. while True:
  479.     game.reset()
  480.     game.update()
  481.     game.build_output()
  482.     game.output()
  483.  
Add Comment
Please, Sign In to add comment