Custom Bot - Halite II 2017 Artificial Intelligence Competition p.3




Welcome to part 3 of the Halite tutorials. So far, we've found a way to drastically improve our rank just by making a slight modification to the starter code given to us. At this point, I notice there are a few issues. One issue is that my ships often "forget" their objective and give the impression that they're changing their minds. We need to remember our objectives beyond the single turn. Also, we noticed that the ends of matches had our ships often just sitting there idle, stacking up until the end of the match. We might as well use our ships to attack. Doing this means we can lower enemy numbers, but we can also over-take planets that our enemy owns and make them our own, furthering our chances of winning.

We could continue to patch the starter bot with our various ideas, but I tend to prefer to start with my own logic, so I am going to start fresh. Let's start off by determining our goals/overall logic:

  • If we can dock, let's dock and mine a planet.
  • Else if there are empty/un-owned planets, let's head towards the closest one.
  • Else if there are enemy ships, let's head towards the cloest one and attack!

Okay, that's the core of our logic. Let's build this out! I am still going to make use of the hlt directory, since those utility scripts are super useful. Eventually, you may opt to re-write them, but they should suffice for quite a while.

import hlt
import logging
from collections import OrderedDict
game = hlt.Game("SentdeBot-V1")
logging.info("Starting SentdeBot")

Okay, so the only new thing we're importing here is going to be the OrderedDict object from collections, which helps us to insure that our dictionaries remain in order. While Python3.6, which is what Halite is using, does attempt to keep order now, this order should not be relied upon, so we wont. We initialize the game, and begin our log. I am referring to this bot as SentdeBot, specifically SentdeBot-V1 for this first version. Now we begin the main game loop:

while True:
    game_map = game.update_map()
    command_queue = []

Nothing new here, same as the starter bot. The next bit of code is also hopefully familiar:

    for ship in game_map.get_me().all_ships():
        shipid = ship.id
        if ship.docking_status != ship.DockingStatus.UNDOCKED:
            # Skip this ship
            continue

Now, we're going to build some lists to contain information about the state of the game. To begin, we can grab entities by distance using the built-in functionality from the starter bot:

        entities_by_distance = game_map.nearby_entities_by_distance(ship)

This isn't in order, however, so we can order it:

        entities_by_distance = OrderedDict(sorted(entities_by_distance.items(), key=lambda t: t[0]))

The nearby_entities_by_distance created a dictionary where the keys were the distances (...risky choice, but we'll take it for now), so we can sort by the key as we do above.

Once we've done this, we can look through this list for ships vs planets, and build lists for those as well. Let's so an example of building a list of empty planets, of course sorted by distance:

        closest_empty_planets = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and not entities_by_distance[distance][0].is_owned()]

Okay, that was a long one. Basically, we're iterating through the entities_by_distance and, if the entity is a planet, and isn't owned, we add it to our list!

Next, we could do some navigating, but, while we're building lists, let's build the lists for finding enemy ships too. In theory, if there's an empty planet, we really do not care at all about enemy ships, at least with this version, since we want to head to the empty planet. If we wanted our script to run as quickly as possible, we might hold off on building this list, but, there wont be empty planets for long, and you'll be running these lists every time anyway. In the name of keeping things all together, let's keep these lists together for now:

        team_ships = game_map.get_me().all_ships()
        closest_enemy_ships = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Ship) and entities_by_distance[distance][0] not in team_ships]

Above, we gather all of our ships, then we determine enemy ships by iterating over the entities_by_distance, and saving to our list any entities that both are ships and they are not in our team ships.

Alright, let's get back to our logic. First questions: Are there empty planets? Can we dock? Let's try!

        if len(closest_empty_planets) > 0:
            target_planet = closest_empty_planets[0]
            if ship.can_dock(target_planet):
                command_queue.append(ship.dock(target_planet))

If not, let's navigate to that closest planet:

            else:
                navigate_command = ship.navigate(
                            ship.closest_point_to(target_planet),
                            game_map,
                            speed=int(hlt.constants.MAX_SPEED),
                            ignore_ships=False)

                if navigate_command:
                    command_queue.append(navigate_command)

If there are no empty planets, what now? Oh, right, ATTACK!!!

        elif len(closest_enemy_ships) > 0:
            target_ship = closest_enemy_ships[0]
            navigate_command = ship.navigate(
                        ship.closest_point_to(target_ship),
                        game_map,
                        speed=int(hlt.constants.MAX_SPEED),
                        ignore_ships=False)

            if navigate_command:
                command_queue.append(navigate_command)

End with sending of the commands:

    game.send_command_queue(command_queue)

Full code up to this point:

import hlt
import logging
from collections import OrderedDict
game = hlt.Game("SentdeBot-V1")
logging.info("Starting SentdeBot")

while True:
    game_map = game.update_map()
    command_queue = []
    
    for ship in game_map.get_me().all_ships():
        shipid = ship.id
        if ship.docking_status != ship.DockingStatus.UNDOCKED:
            # Skip this ship
            continue

        entities_by_distance = game_map.nearby_entities_by_distance(ship)
        entities_by_distance = OrderedDict(sorted(entities_by_distance.items(), key=lambda t: t[0]))
        
        closest_empty_planets = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and not entities_by_distance[distance][0].is_owned()]

        team_ships = game_map.get_me().all_ships()
        closest_enemy_ships = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Ship) and entities_by_distance[distance][0] not in team_ships]

        # If there are any empty planets, let's try to mine!
        if len(closest_empty_planets) > 0:
            target_planet = closest_empty_planets[0]
            if ship.can_dock(target_planet):
                command_queue.append(ship.dock(target_planet))
            else:
                navigate_command = ship.navigate(
                            ship.closest_point_to(target_planet),
                            game_map,
                            speed=int(hlt.constants.MAX_SPEED),
                            ignore_ships=False)

                if navigate_command:
                    command_queue.append(navigate_command)

        # FIND SHIP TO ATTACK!
        elif len(closest_enemy_ships) > 0:
            target_ship = closest_enemy_ships[0]
            navigate_command = ship.navigate(
                        ship.closest_point_to(target_ship),
                        game_map,
                        speed=int(hlt.constants.MAX_SPEED),
                        ignore_ships=False)

            if navigate_command:
                command_queue.append(navigate_command)

    game.send_command_queue(command_queue)
    # TURN END
# GAME END

Rank prior to this was around ~1000, and now I am down in the 690s. Of course, once a bunch of people are using this, you probably shouldn't expect to get below 700 with it for long!

It might be wise to move the team_ships = game_map.get_me().all_ships() line up to above the for loop. This one doesn't need to be calculated for every ship.

Alright, I hope you have enjoyed! I suspect rule-based bots are going to win this competition, but I just can't help myself, I am going to be digging into ML-Based bots next.

The next tutorial:





  • Introduction - Halite II 2017 Artificial Intelligence Competition p.1
  • Modifying Starter Bot - Halite II 2017 Artificial Intelligence Competition p.2
  • Custom Bot - Halite II 2017 Artificial Intelligence Competition p.3
  • Deep Learning - Halite II 2017 Artificial Intelligence Competition p.4
  • Training Data Deep Learning - Halite II 2017 Artificial Intelligence Competition p.5
  • Training Model Deep Learning - Halite II 2017 Artificial Intelligence Competition p.6
  • Deploying Model Deep Learning - Halite II 2017 Artificial Intelligence Competition p.7