def on_game_start(self, config):
        """
        Read in config and perform any initial setup here
        """
        gamelib.debug_write('Configuring your custom algo strategy...')
        self.config = config
        global FILTER, ENCRYPTOR, DESTRUCTOR, PING, EMP, SCRAMBLER
        self.filter,self.encryptor,self.destructor,self.ping,self.emp,self.scrambler = [i for i in range(6)]
        FILTER = config["unitInformation"][0]["shorthand"]
        ENCRYPTOR = config["unitInformation"][1]["shorthand"]
        DESTRUCTOR = config["unitInformation"][2]["shorthand"]
        PING = config["unitInformation"][3]["shorthand"]
        EMP = config["unitInformation"][4]["shorthand"]
        SCRAMBLER = config["unitInformation"][5]["shorthand"]

        self.defences = Defences(config)
class AlgoStrategy(gamelib.AlgoCore):
    def __init__(self):
        super().__init__()
        random.seed()

    def on_game_start(self, config):
        """
        Read in config and perform any initial setup here
        """
        gamelib.debug_write('Configuring your custom algo strategy...')
        self.config = config
        global FILTER, ENCRYPTOR, DESTRUCTOR, PING, EMP, SCRAMBLER
        self.filter, self.encryptor, self.destructor, self.ping, self.emp, self.scrambler = [i for i in range(6)]
        FILTER = config["unitInformation"][0]["shorthand"]
        ENCRYPTOR = config["unitInformation"][1]["shorthand"]
        DESTRUCTOR = config["unitInformation"][2]["shorthand"]
        PING = config["unitInformation"][3]["shorthand"]
        EMP = config["unitInformation"][4]["shorthand"]
        SCRAMBLER = config["unitInformation"][5]["shorthand"]

        self.unique_locs = []
        self.emp_num = 0
        self.defences = Defences(config)
        self.prev_state = None

    def on_turn(self, turn_state):
        """
        This function is called every turn with the game state wrapper as
        an argument. The wrapper stores the state of the arena and has methods
        for querying its state, allocating your current resources as planned
        unit deployments, and transmitting your intended deployments to the
        game engine.
        """
        game_state = gamelib.AdvancedGameState(self.config, turn_state)

        gamelib.debug_write('Performing turn {} of your custom algo strategy'.format(game_state.turn_number))
        game_state.suppress_warnings(True)  # Uncomment this line to suppress warnings.

        emp_cheese = True if self.emp_num > 1 and len(self.unique_locs) == 1 else False
        if emp_cheese:
            d, f = self.can_block(self.unique_locs[0], copy.deepcopy(turn_state))
        self.defences.build_template(game_state, self.prev_state, emp_cheese)
        self.prev_state = copy.deepcopy(game_state)

        if game_state.turn_number != 0:
            try:
                self.best_spawn(game_state)
            except Exception:
                self.attack(game_state)

        game_state.submit_turn()

    def can_block(self, loc, state: gamelib.AdvancedGameState):

        target = state.game_map.BOTTOM_LEFT if loc[1] > 13 else state.game_map.BOTTOM_RIGHT
        path = state.find_path_to_edge(loc, target)

        for i in path:
            if i[1] < 17:
                f = -1 if target == state.game_map.BOTTOM_RIGHT else 1
                return [i[0], 13], [i[0] + f, 13]

        return None, None

    def on_initial_action(self, game_state):
        if 'events' in game_state:
            if 'spawn' not in game_state['events'] or len(game_state['events']['spawn']) == 0:
                return
            # if 'spawn' in curr['events']:
            self.emp_num = 0
            self.unique_locs = []
            for unit in game_state['events']['spawn']:
                loc, typ, _, player = unit

                if typ == EMP and player == 2 and loc[1] > 13:
                    # debug_write(loc, player)
                    self.emp_num += 1
                    if loc not in self.unique_locs:
                        self.unique_locs.add(loc)

    def best_spawn(self, state):
        self.time_start = time.time()
        locs = [[24, 10], [3, 10], [13, 0], [11, 2], [22, 8]]
        units = [EMP, PING, SCRAMBLER]
        bits = state.get_resource(state.BITS)

        # og_map = copy.deepcopy(state.game_map)

        best = (0, None)
        for loc in locs:
            for unit in units:
                cost = 3 if unit == EMP else 1
                if not state.can_spawn(unit, loc):
                    continue

                if time.time() - self.time_start < 2:
                    val = self.simulate(copy.deepcopy(state), unit, loc, int(bits / cost))
                    if val > best[0]:
                        best = (val, (loc, unit, int(bits / cost)))
                    else:
                        break

        top = 9 if state.turn_number <= 6 else 15

        if best[0] > 0:
            loc, unit, num = best[1]
            if best[0] > num * cost or state.get_resource(state.BITS) >= top:
                state.attempt_spawn(unit, loc, num)
        else:
            self.attack(state)

    def simulate(self, state: gamelib.AdvancedGameState, unit_type, spawn_loc=(13, 0), num_units=1):
        map = state.game_map

        """
        TODO: ENCRYPTORS
        """
        target_edge = map.TOP_RIGHT if spawn_loc[0] < 14 else map.TOP_LEFT

        path = state.find_path_to_edge(spawn_loc, target_edge)

        total_dmg = 0
        total_cores = 0

        frames = 2 if unit_type == PING else 4
        dmg = 0 if unit_type == SCRAMBLER else (1 if unit_type == PING else 3)

        for i in range(num_units):
            map.add_unit(unit_type, spawn_loc)
        idx = 0

        remaining = map[spawn_loc]

        map.remove_unit(spawn_loc)
        pings = 0
        while idx < len(path):

            loc = path[idx]

            for i in remaining:
                i.x = loc[0]
                i.y = loc[1]
                map[loc].append(i)

            for i in range(frames):
                defense_dmg = 4 * len(state.get_attackers(loc, 0))
                our_dmg = len(map[loc]) * dmg

                initial = len(map[loc])

                if map[loc]:
                    target = state.get_target(map[loc][0])
                else:
                    break

                if target:
                    target.stability -= our_dmg
                    total_dmg += our_dmg
                    total_cores += (our_dmg / target.max_stability) * target.cost
                    if target.stability < 0:
                        map.remove_unit([target.x, target.y])

                dead = 0
                for i in range(initial):

                    if defense_dmg <= 0 or dead == len(map[loc]):
                        break

                    # Killed one, did they kill more
                    if defense_dmg >= map[loc][dead].stability:
                        defense_dmg -= map[loc][dead].stability
                        dead += 1

                    # They didn't kill this one
                    else:
                        map[loc][dead].stability -= defense_dmg
                        break

                # kill the dead ones
                for i in range(dead):
                    map[loc].pop(0)

            remaining = map[loc]
            pings = remaining

            if len(remaining) == 0:
                break

            map.remove_unit(loc)

            idx += 1

            if loc[1] > 14:
                mod_path = state.find_path_to_edge(spawn_loc, target_edge)
                if path != mod_path and loc in mod_path:
                    idx = mod_path.index(loc) + 1
                    path = mod_path

        return 3.5 * len(pings) + total_cores

    # Attack with EMPs lol
    def attack(self, state: gamelib.AdvancedGameState):
        emp_loc = [3, 10]
        while state.can_spawn(EMP, emp_loc, 1):
            state.attempt_spawn(EMP, emp_loc, 1)

    # Build our static defence template
    def build_defences(self, state: gamelib.AdvancedGameState):
        # When we're at full build, do nothing!
        while state.get_resource(state.CORES) > 0 and not self.defences.build_done():
            loc, defence_type = self.defences.get_next_defence()
            if state.can_spawn(defence_type, loc):
                state.attempt_spawn(defence_type, loc)

    # Check if we need to do an anti-blackbeard defence
    def anti_blackbeard(self, state: gamelib.AdvancedGameState):
        return True

    def blackbeard(self, state: gamelib.AdvancedGameState, left=False):
        # Do initial first turn setup
        # Spawn front destructors and encryptors
        destr = [[23, 13], [24, 13]]
        encr = [[23, 12], [24, 12], [25, 13], [23, 11]]
        for i in destr:
            if state.can_spawn(DESTRUCTOR, i):
                state.attempt_spawn(DESTRUCTOR, i)

        for i in encr:
            if state.can_spawn(ENCRYPTOR, i):
                state.attempt_spawn(ENCRYPTOR, i)

        end = 12 if state.turn_number > 0 else 19

        # open = False
        # spawn remaining filters
        for i in range(22, end, -1):
            # if not state.contains_stationary_unit([i,i-12]) and state.get_resource(state.CORES) < 1:
            #     open = True
            if state.can_spawn(FILTER, [i, i - 12]):
                state.attempt_spawn(FILTER, [i, i - 12])

        # if we have extra cores use some of them18,13,9,5 then filters between
        prio = [[1, 13], [1, 12], [0, 13], [18, 10], [18, 11], [13, 11], [9, 10], [9, 11], [5, 11]]
        which = [DESTRUCTOR, DESTRUCTOR, FILTER, DESTRUCTOR, FILTER, DESTRUCTOR, DESTRUCTOR, FILTER, DESTRUCTOR]

        for i in range(len(prio)):
            if state.get_resource(state.CORES) > 4:
                if state.can_spawn(which[i], prio[i]):
                    state.attempt_spawn(which[i], prio[i])

        i = 0
        # Try to spawn some random stuff
        while i < 10 and state.get_resource(state.CORES) > 6:
            row = random.randint(10, 11)
            col = random.randint(13 - row, 19)
            type = DESTRUCTOR if random.randint(0, 1) else FILTER
            if state.can_spawn(type, [row, col]):
                state.attempt_spawn(type, [row, col])

        if state.turn_number == 0:
            state.attempt_spawn(PING, [13, 0], 2)
            state.attempt_spawn(PING, [14, 0], 2)
        # elif open:
        #     state.attempt_spawn(EMP, [17, 3])
        elif state.turn_number % 2:
            bits = state.get_resource(state.BITS)
            if bits > 4:
                state.attempt_spawn(PING, [14, 0], 4)
            state.attempt_spawn(PING, [13, 0], int(state.get_resource(state.BITS)))

    def filter_blocked_locations(self, locations, game_state):
        filtered = []
        for location in locations:
            if not game_state.contains_stationary_unit(location):
                filtered.append(location)
        return filtered
class AlgoStrategy(gamelib.AlgoCore):
    def __init__(self):
        super().__init__()
        random.seed()

    def on_game_start(self, config):
        """
        Read in config and perform any initial setup here
        """
        gamelib.debug_write('Configuring your custom algo strategy...')
        self.config = config
        global FILTER, ENCRYPTOR, DESTRUCTOR, PING, EMP, SCRAMBLER
        self.filter,self.encryptor,self.destructor,self.ping,self.emp,self.scrambler = [i for i in range(6)]
        FILTER = config["unitInformation"][0]["shorthand"]
        ENCRYPTOR = config["unitInformation"][1]["shorthand"]
        DESTRUCTOR = config["unitInformation"][2]["shorthand"]
        PING = config["unitInformation"][3]["shorthand"]
        EMP = config["unitInformation"][4]["shorthand"]
        SCRAMBLER = config["unitInformation"][5]["shorthand"]

        self.defences = Defences(config)

    def on_turn(self, turn_state):
        """
        This function is called every turn with the game state wrapper as
        an argument. The wrapper stores the state of the arena and has methods
        for querying its state, allocating your current resources as planned
        unit deployments, and transmitting your intended deployments to the
        game engine.
        """
        game_state = gamelib.AdvancedGameState(self.config, turn_state)

        gamelib.debug_write('Performing turn {} of your custom algo strategy'.format(game_state.turn_number))
        game_state.suppress_warnings(True)  #Uncomment this line to suppress warnings.

        self.defences.build_template(game_state)

        if game_state.turn_number != 0:
            self.attack(game_state)

        game_state.submit_turn()


    def attack(self, state: gamelib.AdvancedGameState):
        emp_loc = [3, 10]
        while state.can_spawn(EMP, emp_loc, 1):
            state.attempt_spawn(EMP, emp_loc, 1)

    def build_defences(self, state: gamelib.AdvancedGameState):
        # When we're at full build, do nothing!
        while state.get_resource(state.CORES) > 0 and not self.defences.build_done():
            loc, defence_type = self.defences.get_next_defence()
            if state.can_spawn(defence_type, loc):
                state.attempt_spawn(defence_type, loc)

    # Spawns as many pings/scramblers at the given location
    def attack_hole(self, state: gamelib.AdvancedGameState, rush_loc, rush_unit):
        if state.can_spawn(rush_unit, rush_loc):
            # Find out the proper amount for the amount of bits to spend
            state.attempt_spawn(rush_unit, rush_loc, state.my_bits())

    # Finds the side where they have the most defences. Checks the first 5 rows and determines
    # if it's left-heavy or right-heavy. Returns left-heavy by default
    def scan_opponent_defences(self, state: gamelib.AdvancedGameState):
        return True

    # Sawtooth defence
    def sawtooth(self, state: gamelib.AdvancedGameState):
        # Find the defences that were destroyed
        destroyed = self.find_destroyed_defences(state)



    # Find the defences that were destroyed last term
    def find_destroyed_defences(self, state: gamelib.AdvancedGameState):
        return []

    # Set up the classic sawtooth defence
    def sawtooth_setup(self, state: gamelib.AdvancedGameState):
        # Almost symmetrical setup, just need filter on <14, 10>
        dest_locs = [[2, 11], [6, 11], [11, 11], [16, 11], [21, 11], [25, 11]]
        filter_locs = [[0, 13], [1, 12], [26, 12], [27, 13]]

        for dest in destr_locs:
            if state.can_spawn(DESTRUCTOR, dest):
                state.attempt_spawn(DESTRUCTOR, dest)

        for f in filter_locs:
            if state.can_spawn(FILTER, f):
                state.attempt_spawn(FILTER, f)

    # Idea behind escort strategy --> 2 EMPs + 2 Scramblers + Rerouting with filters
    # Finds out the optimal side to attack from as well
    # For now, hardcode it to be the LHS
    def escort_strategy(self, state: gamelib.AdvancedGameState, defensive_side='left'):
        # See if we need to use more filters here
        # This should be dynamic, depending on the board state, kind of like
        # What stage of the game are we in
        filter_locs = [[6, 10], [11, 10], [16, 10], [22, 10]]

        # Spawn 2 EMPs and 2 Scramblers. Again, this should be dynamic
        emp_locs = [[3, 10], [3, 10]]
        scrambler_locs = [[4, 9], [4, 9]]

        for e in emp_locs:
            if state.can_spawn(EMP, e):
                state.attempt_spawn(EMP, e)

        for s in scrambler_locs:
            if state.can_spawn(SCRAMBLER, s):
                state.attempt_spawn(SCRAMBLER, s)


    def blackbeard(self, state: gamelib.AdvancedGameState, left=False):
        # Do initial first turn setup
        # Spawn front destructors and encryptors
        destr = [[23,13], [24, 13]]
        encr = [[23,12], [24,12], [25,13], [23,11]]
        for i in destr:
            if state.can_spawn(DESTRUCTOR, i):
                state.attempt_spawn(DESTRUCTOR, i)

        for i in encr:
            if state.can_spawn(ENCRYPTOR, i):
                state.attempt_spawn(ENCRYPTOR, i)

        end = 12 if state.turn_number > 0 else 19

        # open = False
        #spawn remaining filters
        for i in range(22,end,-1):
            # if not state.contains_stationary_unit([i,i-12]) and state.get_resource(state.CORES) < 1:
            #     open = True
            if state.can_spawn(FILTER, [i,i-12]):
                state.attempt_spawn(FILTER, [i,i-12])

        # if we have extra cores use some of them18,13,9,5 then filters between
        prio = [[1,13], [1,12], [0,13],[18,10], [18,11], [13,11], [9,10], [9,11],[5,11]]
        which = [DESTRUCTOR, DESTRUCTOR, FILTER, DESTRUCTOR, FILTER, DESTRUCTOR, DESTRUCTOR, FILTER, DESTRUCTOR]

        for i in range(len(prio)):
            if state.get_resource(state.CORES) > 4:
                if state.can_spawn(which[i], prio[i]):
                    state.attempt_spawn(which[i], prio[i])

        i = 0
        # Try to spawn some random stuff
        while i < 10 and state.get_resource(state.CORES) > 6:
            row = random.randint(10,11)
            col = random.randint(13-row, 19)
            type = DESTRUCTOR if random.randint(0,1) else FILTER
            if state.can_spawn(type, [row,col] ):
                state.attempt_spawn(type, [row,col])

        if state.turn_number == 0:
            state.attempt_spawn(PING, [13,0],2)
            state.attempt_spawn(PING, [14,0],2)
        # elif open:
        #     state.attempt_spawn(EMP, [17, 3])
        elif state.turn_number % 2:
            bits = state.get_resource(state.BITS)
            if bits > 4:
                state.attempt_spawn(PING, [14,0], 4)
            state.attempt_spawn(PING, [13,0], int(state.get_resource(state.BITS)))


    def filter_blocked_locations(self, locations, game_state):
        filtered = []
        for location in locations:
            if not game_state.contains_stationary_unit(location):
                filtered.append(location)
        return filtered
예제 #4
0
class AlgoStrategy(gamelib.AlgoCore):
    def __init__(self):
        super().__init__()
        random.seed()
        self.prev_state = None

    def on_game_start(self, config):
        """
        Read in config and perform any initial setup here
        """
        gamelib.debug_write('Configuring your custom algo strategy...')
        self.config = config
        global FILTER, ENCRYPTOR, DESTRUCTOR, PING, EMP, SCRAMBLER
        self.filter, self.encryptor, self.destructor, self.ping, self.emp, self.scrambler = [
            i for i in range(6)
        ]
        FILTER = config["unitInformation"][0]["shorthand"]
        ENCRYPTOR = config["unitInformation"][1]["shorthand"]
        DESTRUCTOR = config["unitInformation"][2]["shorthand"]
        PING = config["unitInformation"][3]["shorthand"]
        EMP = config["unitInformation"][4]["shorthand"]
        SCRAMBLER = config["unitInformation"][5]["shorthand"]

        self.defences = Defences(config)
        self.time_start = 0

    def on_turn(self, turn_state):
        """
        This function is called every turn with the game state wrapper as
        an argument. The wrapper stores the state of the arena and has methods
        for querying its state, allocating your current resources as planned
        unit deployments, and transmitting your intended deployments to the
        game engine.
        """
        game_state = gamelib.AdvancedGameState(self.config, turn_state)

        gamelib.debug_write(
            'Performing turn {} of your custom algo strategy'.format(
                game_state.turn_number))
        game_state.suppress_warnings(
            True)  # Uncomment this line to suppress warnings.

        self.build_defences(game_state, self.prev_state)

        if game_state.turn_number != 0:
            self.prev_state = copy.deepcopy(game_state)
            self.best_spawn(game_state)

        game_state.submit_turn()

    def attack(self, state: gamelib.AdvancedGameState):
        emp_loc = [3, 10]
        while state.can_spawn(EMP, emp_loc, 1):
            state.attempt_spawn(EMP, emp_loc, 1)

    def build_defences(self, state: gamelib.AdvancedGameState, prev_state):
        self.defences.build_template(state, prev_state)

    # Spawns as many pings/scramblers at the given location
    def attack_hole(self, state: gamelib.AdvancedGameState, rush_loc,
                    rush_unit):
        if state.can_spawn(rush_unit, rush_loc):
            # Find out the proper amount for the amount of bits to spend
            state.attempt_spawn(rush_unit, rush_loc, state.my_bits())

    # Finds the side where they have the most defences. Checks the first 5 rows and determines
    # if it's left-heavy or right-heavy. Returns left-heavy by default
    def scan_opponent_defences(self, state: gamelib.AdvancedGameState):
        return True

    # Sawtooth defence
    def sawtooth(self, state: gamelib.AdvancedGameState):
        # Find the defences that were destroyed
        destroyed = self.find_destroyed_defences(state)

    # Find the defences that were destroyed last term
    def find_destroyed_defences(self, state: gamelib.AdvancedGameState):
        return []

    # Set up the classic sawtooth defence
    def sawtooth_setup(self, state: gamelib.AdvancedGameState):
        # Almost symmetrical setup, just need filter on <14, 10>
        dest_locs = [[2, 11], [6, 11], [11, 11], [16, 11], [21, 11], [25, 11]]
        filter_locs = [[0, 13], [1, 12], [26, 12], [27, 13]]

        for dest in dest_locs:
            if state.can_spawn(DESTRUCTOR, dest):
                state.attempt_spawn(DESTRUCTOR, dest)

        for f in filter_locs:
            if state.can_spawn(FILTER, f):
                state.attempt_spawn(FILTER, f)

    # Idea behind escort strategy --> 2 EMPs + 2 Scramblers + Rerouting with filters
    # Finds out the optimal side to attack from as well
    # For now, hardcode it to be the LHS
    def escort_strategy(self,
                        state: gamelib.AdvancedGameState,
                        defensive_side='left'):
        # See if we need to use more filters here
        # This should be dynamic, depending on the board state, kind of like
        # What stage of the game are we in
        filter_locs = [[6, 10], [11, 10], [16, 10], [22, 10]]

        # Spawn 2 EMPs and 2 Scramblers. Again, this should be dynamic
        emp_locs = [[3, 10], [3, 10]]
        scrambler_locs = [[4, 9], [4, 9]]

        for e in emp_locs:
            if state.can_spawn(EMP, e):
                state.attempt_spawn(EMP, e)

        for s in scrambler_locs:
            if state.can_spawn(SCRAMBLER, s):
                state.attempt_spawn(SCRAMBLER, s)

    def blackbeard(self, state: gamelib.AdvancedGameState, left=False):
        # Do initial first turn setup
        # Spawn front destructors and encryptors
        destr = [[23, 13], [24, 13]]
        encr = [[23, 12], [24, 12], [25, 13], [23, 11]]
        for i in destr:
            if state.can_spawn(DESTRUCTOR, i):
                state.attempt_spawn(DESTRUCTOR, i)

        for i in encr:
            if state.can_spawn(ENCRYPTOR, i):
                state.attempt_spawn(ENCRYPTOR, i)

        end = 12 if state.turn_number > 0 else 19

        # open = False
        # spawn remaining filters
        for i in range(22, end, -1):
            # if not state.contains_stationary_unit([i,i-12]) and state.get_resource(state.CORES) < 1:
            #     open = True
            if state.can_spawn(FILTER, [i, i - 12]):
                state.attempt_spawn(FILTER, [i, i - 12])

        # if we have extra cores use some of them18,13,9,5 then filters between
        prio = [[1, 13], [1, 12], [0, 13], [18, 10], [18, 11], [13, 11],
                [9, 10], [9, 11], [5, 11]]
        which = [
            DESTRUCTOR, DESTRUCTOR, FILTER, DESTRUCTOR, FILTER, DESTRUCTOR,
            DESTRUCTOR, FILTER, DESTRUCTOR
        ]

        for i in range(len(prio)):
            if state.get_resource(state.CORES) > 4:
                if state.can_spawn(which[i], prio[i]):
                    state.attempt_spawn(which[i], prio[i])

        i = 0
        # Try to spawn some random stuff
        while i < 10 and state.get_resource(state.CORES) > 6:
            row = random.randint(10, 11)
            col = random.randint(13 - row, 19)
            type = DESTRUCTOR if random.randint(0, 1) else FILTER
            if state.can_spawn(type, [row, col]):
                state.attempt_spawn(type, [row, col])

        if state.turn_number == 0:
            state.attempt_spawn(PING, [13, 0], 2)
            state.attempt_spawn(PING, [14, 0], 2)
        # elif open:
        #     state.attempt_spawn(EMP, [17, 3])
        elif state.turn_number % 2:
            bits = state.get_resource(state.BITS)
            if bits > 4:
                state.attempt_spawn(PING, [14, 0], 4)
            state.attempt_spawn(PING, [13, 0],
                                int(state.get_resource(state.BITS)))

    def filter_blocked_locations(self, locations, game_state):
        filtered = []
        for location in locations:
            if not game_state.contains_stationary_unit(location):
                filtered.append(location)
        return filtered

    def best_spawn(self, state):
        self.time_start = time.time()
        locs = [[24, 10], [3, 10], [13, 0], [11, 12], [15, 12]]
        units = [PING, EMP, SCRAMBLER]
        bits = state.get_resource(state.BITS)

        best = (0, None)
        for loc in locs:
            for unit in units:
                cost = 3 if unit == EMP else 1
                if not state.can_spawn(unit, loc):
                    continue

                if time.time() - self.time_start < 2:
                    val = self.simulate(copy.deepcopy(state), unit, loc,
                                        int(bits / cost))
                    if val > best[0]:
                        best = (val, (loc, unit, int(bits / cost)))
                    else:
                        break

        if best[0] > 0:
            loc, unit, num = best[1]
            if best[0] > num * 2:
                state.attempt_spawn(unit, loc, num)
        else:
            self.attack(state)

    def simulate(self,
                 state: gamelib.AdvancedGameState,
                 unit_type,
                 spawn_loc=(13, 0),
                 num_units=1):
        map = state.game_map
        """
        TODO: ENCRYPTORS
        """
        target_edge = map.TOP_RIGHT if spawn_loc[0] < 14 else map.TOP_LEFT

        path = state.find_path_to_edge(spawn_loc, target_edge)

        total_dmg = 0
        total_cores = 0

        frames = 2 if unit_type == PING else 4
        dmg = 0 if unit_type == SCRAMBLER else (1 if unit_type == PING else 3)

        for i in range(num_units):
            map.add_unit(unit_type, spawn_loc)
        idx = 0

        remaining = map[spawn_loc]

        map.remove_unit(spawn_loc)
        pings = 0
        while idx < len(path):

            loc = path[idx]

            for i in remaining:
                i.x = loc[0]
                i.y = loc[1]
                map[loc].append(i)

            for i in range(frames):
                defense_dmg = 4 * len(state.get_attackers(loc, 0))
                our_dmg = len(map[loc]) * dmg

                initial = len(map[loc])

                if map[loc]:
                    target = state.get_target(map[loc][0])
                else:
                    break

                if target:
                    target.stability -= our_dmg
                    total_dmg += our_dmg
                    total_cores += (our_dmg /
                                    target.max_stability) * target.cost
                    if target.stability < 0:
                        map.remove_unit([target.x, target.y])

                dead = 0
                for i in range(initial):

                    if defense_dmg <= 0 or dead == len(map[loc]):
                        break

                    # Killed one, did they kill more
                    if defense_dmg >= map[loc][dead].stability:
                        defense_dmg -= map[loc][dead].stability
                        dead += 1

                    # They didn't kill this one
                    else:
                        map[loc][dead].stability -= defense_dmg
                        break

                # kill the dead ones
                for i in range(dead):
                    map[loc].pop(0)

            remaining = map[loc]
            pings = remaining

            if len(remaining) == 0:
                break

            map.remove_unit(loc)

            idx += 1

            if loc[1] > 14:
                mod_path = state.find_path_to_edge(spawn_loc, target_edge)
                if path != mod_path and loc in mod_path:
                    idx = mod_path.index(loc) + 1
                    path = mod_path

        return 3.5 * len(pings) + total_cores