philRG

BFS d'Halloween :-) (FC2020 reloaded)

Sep 25th, 2021 (edited)
102
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.75 KB | None | 0 0
  1. import math
  2. import random
  3. import sys
  4. import time
  5. from copy import copy
  6. from typing import Tuple, List, Optional
  7. from collections import deque
  8. from numpy import array, where
  9.  
  10.  
  11. def idebug(*args):
  12.     # return
  13.     print(*args, file=sys.stderr, flush=True)
  14.  
  15.  
  16. def debug(*args):
  17.     # return
  18.     print(*args, file=sys.stderr, flush=True)
  19.  
  20.  
  21. class Potion:
  22.     def __init__(self, potion_id, delta, price):
  23.         self.potion_id = potion_id
  24.         self.delta = delta
  25.         self.price = price
  26.  
  27.     def __repr__(self):
  28.         return f'{self.potion_id} {str(self.delta)} {self.price}'
  29.  
  30.  
  31. class Spell:
  32.     def __init__(self, cast_id, delta, castable):
  33.         self.cast_id = cast_id
  34.         self.delta = delta
  35.         self.castable = castable
  36.  
  37.     def __copy__(self):
  38.         return Spell(self.cast_id, self.delta.copy(), self.castable)
  39.  
  40.     def __hash__(self):
  41.         return hash((str(self.delta), self.castable))
  42.  
  43.     def __repr__(self):
  44.         return f'{self.cast_id} {str(self.delta)} {self.castable}'
  45.  
  46.  
  47. class Action:
  48.     def __init__(self, action_type, action_id=None, delta=None):
  49.         self.action_type = action_type
  50.         self.action_id = action_id
  51.         self.delta = delta
  52.  
  53.     def __hash__(self):
  54.         # return hash((self.action_type, self.action_id, tuple(map(tuple, self.delta))))
  55.         return hash((self.action_type, self.action_id))
  56.  
  57.     def __repr__(self):
  58.         if self.action_type == 'CAST':
  59.             return f'CAST {self.action_id}'
  60.         elif self.action_type == 'BREW':
  61.             return f'BREW {self.action_id}'
  62.         else:
  63.             return 'REST'
  64.  
  65.  
  66. def bfs_paths(witch_id: int, potion: Potion):
  67.     if witch_id == 0:
  68.         inv: array = my_inv.copy()
  69.         spells: List = [copy(s) for s in my_spells]
  70.     else:
  71.         inv: array = op_inv.copy()
  72.         spells: List = [copy(s) for s in op_spells]
  73.     depth = 0
  74.     queue = deque([(spells, inv, depth, [])])
  75.     nodes_count = 0
  76.     new_start = time.time()
  77.     visited_states = []
  78.     while queue:
  79.         spells, inv, depth, path = queue.popleft()
  80.         actions = [Action('CAST', s.cast_id, s.delta) for s in spells if s.castable and all((s.delta + inv >= 0))]
  81.         actions.append(Action('REST'))
  82.         # la petite fourmi se deplace pour chercher son chemin
  83.         for action in actions:
  84.             new_spells: List = [copy(s) for s in spells]
  85.             if action.action_type == 'CAST':
  86.                 # if inventory is not full after cast, add new recipe, and set castable flag to False
  87.                 new_inv: array = inv + action.delta
  88.                 if sum(new_inv) > 10:
  89.                     continue
  90.                 for s in new_spells:
  91.                     if action.action_id == s.cast_id:
  92.                         s.castable = False
  93.             else:   # action.action_type == REST
  94.                 new_inv = inv
  95.                 for s in new_spells:
  96.                     s.castable = True
  97.             if all((new_inv + potion.delta >= 0)):
  98.                 debug(f'BFS player {witch_id} - nodes_count = {nodes_count} - elapsed_time = {round((time.time() - new_start) * 1000, 2)} ms')
  99.                 brew_action = Action('BREW', potion.potion_id)
  100.                 yield path + [action, brew_action]
  101.             else:
  102.                 gamestate = (new_spells, new_inv, depth + 1)
  103.                 # if gamestate not in visited_states:
  104.                 node = (*gamestate, path + [action])
  105.                 queue.append(node)
  106.                 visited_states.append(gamestate)
  107.                 # debug(f'node = {node}')
  108.                 # debug(f'game state = {gamestate}')
  109.                 nodes_count += 1
  110.     debug(f'witch {witch_id} = no solution found!')
  111.  
  112.  
  113. def shortest_path(witch_id: int, potion: Potion):
  114.     try:
  115.         return next(bfs_paths(witch_id, potion))
  116.     except StopIteration:
  117.         return None
  118.  
  119. # somme = lambda a, b: [[sum(x) for x in list(zip(a, b))]]
  120.  
  121. last_turn_brewed = False
  122.  
  123. # game loop
  124. while True:
  125.     action_count = int(input())  # the number of spells and recipes in play
  126.     start = time.time()
  127.     idebug(action_count)
  128.     my_spells, op_spells = [], []
  129.     my_inv, op_inv = [], []
  130.     potions = []
  131.     for i in range(action_count):
  132.         line = input()
  133.         idebug(line)
  134.         inputs = line.split()
  135.         action_id = int(inputs[0])  # the unique ID of this spell or recipe
  136.         action_type = inputs[1]  # in the first league: BREW; later: CAST, OPPONENT_CAST, LEARN, BREW
  137.         delta = array([int(inputs[2]), int(inputs[3]), int(inputs[4]), int(inputs[5])])
  138.         price = int(inputs[6])  # the price in rupees if this is a potion
  139.         tome_index = int(inputs[7])  # in the first two leagues: always 0; later: the index in the tome if this is a tome spell, equal to the read-ahead tax; For brews, this is the value of the current urgency bonus
  140.         tax_count = int(inputs[8])  # in the first two leagues: always 0; later: the amount of taxed tier-0 ingredients you gain from learning this spell; For brews, this is how many times you can still gain an urgency bonus
  141.         castable = inputs[9] != "0"  # in the first league: always 0; later: 1 if this is a castable player spell
  142.         repeatable = inputs[10] != "0"  # for the first two leagues: always 0; later: 1 if this is a repeatable player spell
  143.         if action_type == 'CAST':
  144.             my_spells.append(Spell(action_id, delta, castable))
  145.         elif action_type == 'OPPONENT_CAST':
  146.             op_spells.append(Spell(action_id, delta, castable))
  147.         elif action_type == 'BREW':
  148.             potions.append(Potion(action_id, delta, price))
  149.     for i in range(2):
  150.         # inv_0: tier-0 ingredients in inventory
  151.         # score: amount of rupees
  152.         line = input()
  153.         idebug(line)
  154.         inv_0, inv_1, inv_2, inv_3, score = [int(j) for j in line.split()]
  155.         if i == 0:
  156.             my_inv, my_score = array([inv_0, inv_1, inv_2, inv_3]), score
  157.         else:
  158.             op_inv, op_score = array([inv_0, inv_1, inv_2, inv_3]), score
  159.  
  160.     # debug(f'my spells = {my_spells}')
  161.  
  162.     # in the first league: BREW <id> | WAIT; later: BREW <id> | CAST <id> [<times>] | LEARN <id> | REST | WAIT
  163.     # potion_to_brew = random.choice(potions)
  164.     potion_to_brew = min(potions, key=lambda p: p.price)
  165.     debug(f'potion to brew: {potion_to_brew}')
  166.  
  167.     if all((my_inv + potion_to_brew.delta >= 0)):
  168.         action = f'BREW {potion_to_brew.potion_id}'
  169.         last_turn_brewed = True
  170.     elif last_turn_brewed:
  171.         action = 'REST'
  172.         last_turn_brewed = False
  173.     else:
  174.         actions = shortest_path(witch_id=0, potion=potion_to_brew)
  175.         action = actions[0]
  176.         debug(f'path to potion {potion_to_brew} = {actions}')
  177.     print(action)
  178.     debug(f'elapsed time = {round((time.time() - start) * 1000)} ms')
  179.  
Add Comment
Please, Sign In to add comment