Deep Learning - Halite II 2017 Artificial Intelligence Competition p.4




Hello and welcome to a tutorial on deep learning with Halite II. In this tutorial and the seqsequent ones, I will be breaking down some of our options for using neural networks, or even many other classifiers, for playing Halite II.

To do AI, in general, you need a fixed featureset and fixed labels. The featureset is an input vector to the neural network and the labels will be the output layer. Featuresets must all be identical, and each index in each featureset must be of identical type to the next featureset's index.

To make this as simple as possible, I'll just set up a simple example:

Let's say you've got a game, and you have a model where you want to know what each ship should do. Features might be things like how many planets you already have, how many enemy planets there are, and how many empty planets there are. You might also want to know how many enemy ships there are and how many ships you have.

So with this, you might have a featureset/input vector that is built like:

[how_many_our_owned_planets, how_many_enemy_planets, how_many_empty_planets, how_many_enemy_ships, how_many_team_ships]

Those features, and their order, must remain the same every time. Beyond those types of features, other features are usually useful like distances to other ships/planets. So maybe you want to track the top 3, 5, or whatever.

[how_many_our_owned_planets,
how_many_enemy_planets, 
how_many_empty_planets, 
how_many_enemy_ships, 
how_many_team_ships,
distance_to_enemy_ship1,
distance_to_enemy_ship2,
distance_to_enemy_ship3,
distance_to_enemy_planet1,
distance_to_enemy_planet2,
distance_to_enemy_planet3,]

What if the enemy only has 2 planets, not three? We still need a value in that spot. In general, -99 can work. Or 0, but -99 seems to be a good value from what I am finding. There are obviously many more features to consider. How many ships at each of the planets, size/resources of those planets....etc. Okay so that's input vector. Now we have output vector. Same as in the input vector, this must always be the same, and this should be the choice you want to make.

For example, a super simple output vector might be:

[Attack enemy ships, mine a planet]

So, if the "action" you want to take is attack, then your output vector would be [1,0]. If the action should be mine, then it would be [0,1]

Another option for an output vector could be far more complex. You could instead make the output vector entities, like:

[team_planet_1,
team_planet_2,
team_planet_3,
empty_planet_1,
empty_planet_2,
empty_planet_3,
enemy_planet_1,
enemy_planet_2,
enemy_planet_3,
enemy_ship_1,
enemy_ship_2,
enemy_ship_3,]

Where each of those is a choice you could make, and you can only make one. So, the question is, where would we like to go? For example, [0,0,0,0,0,0,0,0,0,0,1,0] would mean "navigate" to enemy ship 2...which means attack that ship.

Okay, now how do we actually gather this data and train a model to play? The Halite II challenge has a ML Starter Bot, which learns from replay files, where you can watch replay files from Gold tiered players to attempt to mimic them. For example, the ML starter bot tries to determine how to best allocate ships to each of the planets. While the replay files are a worthy thing to check out, and I plan to possibly cover them, I think they're an added layer of complexity, and we don't actually *need* them. If you are interested, I still personally had some trouble reading the replay files. Here's a snippet to get you started:

with open(REPLAY_FILE_PATH, 'rb') as game:
    game_data = game.read()
    game_data = zstd.decompress(game_data)
    game_json_data = json.loads(game_data.decode('utf-8'))
    for frame in game_json_data['frames'][1:300]:
        # destroyed planets, players, spawned players 
        events = frame['events']
        # every planet by id, docked ship(list of ids)
        # production remaining
        # health, owner, 
        planets = frame['planets']
        ships = frame['ships']
        for ship in ships:
            for shipid in ships[ship]:
                print(ships[ship][shipid])

The replay files are compressed json files, and they contain basically all log information. From here, you can parse and translate ship movements and decisions based on the game map, then attempt to learn from this.

What I'd like to do is take a simple bot that attacks and mines planets, and have it compete against itself with a high degree of randomness. From here, take the winning player's moves for the games, and attempt to train a model to learn from them. This process can be repeated over and over, iteratively improving our AI.

Let's begin with our script:

import hlt
import logging
from collections import OrderedDict
import numpy as np
import random
import os

VERSION = 1

We're bringing in hlt to use the built-in functions, logging for logging, we want to have an ordered dictionary, numpy for number crunching things, random to make random choices, and os to delete some files to start fresh.

We'll have two of these bots compete against each other, so one will be version #1, the other version #2, but they'll be the same bot. Next, we'll define some constants:

HM_ENT_FEATURES = 5
PCT_CHANGE_CHANCE = 30
DESIRED_SHIP_COUNT = 20

The first is how many features per type of entity are we interested in. I plan to track features like "open planets" and "enemy planets," for example, so the HM_ENT_FEATURES constant is how many to care about per feature type. The PCT_CHANGE_CHANCE is what % chance I want to change a ship's current plans. So, if a ship is attacking, there is a 30% chance it could change it's mind. Next, we'll setup some basic game data:

game = hlt.Game("Charles{}".format(VERSION))
logging.info("CharlesBot-{} Start".format(VERSION))

Finally, like before, I would like to keep track of "plans" for our ships:

ship_plans = {}

For each of the scripts, I am wanting to track moves, and then we'll save the winner's moves at the end. To do this, we'll be appending moves to a file, which needs to be cleared on every new-game, so we'll do:

if os.path.exists("c{}_input.vec".format(VERSION)):
    os.remove("c{}_input.vec".format(VERSION))

if os.path.exists("c{}_out.vec".format(VERSION)):
    os.remove("c{}_out.vec".format(VERSION))

Okay, let's begin the game, and start iterating through frames!

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

Thinking of various features that might be useful, some of them aren't really on a per-ship basis, we can just grab them once a frame. We can certainly grab how many ships we and our enemies have, for example:

    team_ships = game_map.get_me().all_ships()
    all_ships = game_map._all_ships()
    enemy_ships = [ship for ship in game_map._all_ships() if ship not in team_ships]

    my_ship_count = len(team_ships)
    enemy_ship_count = len(enemy_ships)
    all_ship_count = len(all_ships)

We can also gather planet information:

    my_id = game_map.get_me().id

    empty_planet_sizes = {}
    our_planet_sizes = {}
    enemy_planet_sizes = {}
    
    for p in game_map.all_planets():
        radius = p.radius
        if not p.is_owned():
            empty_planet_sizes[radius] = p
        elif p.owner.id == game_map.get_me().id:
            our_planet_sizes[radius] = p
        elif p.owner.id != game_map.get_me().id:
            enemy_planet_sizes[radius] = p

    hm_our_planets = len(our_planet_sizes)
    hm_empty_planets = len(empty_planet_sizes)
    hm_enemy_planets = len(enemy_planet_sizes)
    
        
    empty_planet_keys = sorted([k for k in empty_planet_sizes])[::-1]
    our_planet_keys = sorted([k for k in our_planet_sizes])[::-1]
    enemy_planet_keys= sorted([k for k in enemy_planet_sizes])[::-1]

Now that we have this data, the rest is information we really need on a per-ship basis. For example, we care about the closest "X" enemies to each ship, not just a random "X" enemies. Same with planets, we're really interested in the closest ones, in order by distance.

Beginning the per-ship loop, we start by checking if the ship is docked:

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

You can opt to do this...or not. This will just leave docked ships docked, but, since we have a random chance of changing, it might make more sense to allow ships to leave dock again. It's really up to you.

            shipid = ship.id
            change = False
            if random.randint(1,100) <= PCT_CHANGE_CHANCE:
                change = True

Now we determine if we'd like to change this ship's plans, if it has any.

Next, we're ready to compute a bunch of features. Here are the ones I intend to track:

            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()]
            closest_empty_planet_distances = [distance 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()]
            
            closest_my_planets = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0].is_owned() and (entities_by_distance[distance][0].owner.id == game_map.get_me().id)]
            closest_my_planets_distances = [distance for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0].is_owned() and (entities_by_distance[distance][0].owner.id == game_map.get_me().id)]

            closest_enemy_planets = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0] not in closest_my_planets and entities_by_distance[distance][0] not in closest_empty_planets]
            closest_enemy_planets_distances = [distance for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0] not in closest_my_planets and entities_by_distance[distance][0] not in closest_empty_planets]
    
            
            closest_team_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] in team_ships]
            closest_team_ships_distances = [distance for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Ship) and entities_by_distance[distance][0] in team_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]
            closest_enemy_ships_distances = [distance 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]

            largest_empty_planet_distances = []
            largest_our_planet_distances = []
            largest_enemy_planet_distances = []

            for i in range(HM_ENT_FEATURES):
                try: largest_empty_planet_distances.append(key_by_value(entities_by_distance, empty_planet_sizes[empty_planet_keys[i]]))
                except:largest_empty_planet_distances.append(-99)

                try: largest_our_planet_distances.append(key_by_value(entities_by_distance, our_planet_sizes[our_planet_keys[i]]))
                except:largest_our_planet_distances.append(-99)

                try: largest_enemy_planet_distances.append(key_by_value(entities_by_distance, enemy_planet_sizes[enemy_planet_keys[i]]))
                except:largest_enemy_planet_distances.append(-99)
                

            entity_lists = [fix_data(closest_empty_planet_distances),
                            fix_data(closest_my_planets_distances),
                            fix_data(closest_enemy_planets_distances),
                            fix_data(closest_team_ships_distances),
                            fix_data(closest_enemy_ships_distances),
                            fix_data(empty_planet_keys),
                            fix_data(our_planet_keys),
                            fix_data(enemy_planet_keys),
                            fix_data(largest_empty_planet_distances),
                            fix_data(largest_our_planet_distances),
                            fix_data(largest_enemy_planet_distances)]

Okay so some of those are pretty long list comprehensions, but each of them is relatively similar. The first one is the entity itself, and the 2nd is the list of distances, so that we can have both the closest "X" planets, but also know their distances, which we'll use as actual input. To build these, we're making use of a couple helper-functions:

def key_by_value(dictionary, value):

    for k,v in dictionary.items():
        if v[0] == value:
            return k
    return -99


def fix_data(data):
    new_list = []
    last_known_idx = 0
    for i in range(HM_ENT_FEATURES):
        try:
            if i < len(data):
                last_known_idx=i
            new_list.append(data[last_known_idx])
        except:
            new_list.append(0)
            
    return new_list

The key_by_value function grabs a dictionary's key by a value, used to grab an entity's distance (which is a key in the dictionary) by the entity itself. The fix_data function simply appends to the lists of data if there aren't enough entities. For example, if we set HM_ENT_FEATURES to 5, this would mean we expect to have things like 5 enemy planets. There are going to be times when we simply don't have 5 enemy planets, so that function just pads out whatever values we do have.

With all of this data, we can actually build our input vector:

            input_vector = []
            
            for i in entity_lists:
                for ii in i[:HM_ENT_FEATURES]:
                    input_vector.append(ii)

            input_vector += [my_ship_count,
                             enemy_ship_count,
                             hm_our_planets,
                             hm_empty_planets,
                             hm_enemy_planets]

Now, let's choose our output vector:

            if my_ship_count > DESIRED_SHIP_COUNT:
                output_vector = 3*[0]
                output_vector[0] = 1
                ship_plans[ship.id] = output_vector

If we have more ships than we intend to have, then we should just go on assault. I am mainly setting this to avoid timing out. You have 2000ms to compute *all* of your ships' moves for a round. Naturally, computing all of these features per ship is going to compound the more ships we have, so, eventually, we could either "break" the loop if we're taking too long....or we can just send our surplus into battle. I like the second idea better!

            elif change or ship.id not in ship_plans:
                '''
                pick new "plan"
                '''
                output_vector = 3*[0]
                output_vector[random.randint(0,2)] = 1
                ship_plans[ship.id] = output_vector

Now, if we randomly chose to come up with a new plan for this ship, or if this ship doesn't already have a plan, we want to make one. For now, this is just a random choice. When we've got a trained model? Rather than going with a random choice, we'll feed our trained model the input vector, and go with whatever output vector it chooses.

Of course, some ships might have plans already, so we have:

            else:
                '''continue to execute existing plan'''
                output_vector = ship_plans[ship.id]

Okay, let's act on our choices:

            try:
                # ATTACK ENEMY SHIP #
                if np.argmax(output_vector) == 0:
                    '''
                    type: 0
                    Find closest enemy ship, and attack!
                    '''
                    if not isinstance(closest_enemy_ships[0], int):
                        navigate_command = ship.navigate(
                                    ship.closest_point_to(closest_enemy_ships[0]),
                                    game_map,
                                    speed=int(hlt.constants.MAX_SPEED),
                                    ignore_ships=False)

                        if navigate_command:
                            command_queue.append(navigate_command)

Simple attack choice. Next, what about mining one of our own planets?

                # MINE ONE OF OUR PLANETS #
                elif np.argmax(output_vector) == 1:
                    '''
                    type: 1
                    Mine closest already-owned planet
                    '''
                    if not isinstance(closest_my_planets[0], int):
                        target =  closest_my_planets[0]

                        if len(target._docked_ship_ids) < target.num_docking_spots:
                            if ship.can_dock(target):
                                command_queue.append(ship.dock(target))
                            else:
                                navigate_command = ship.navigate(
                                            ship.closest_point_to(target),
                                            game_map,
                                            speed=int(hlt.constants.MAX_SPEED),
                                            ignore_ships=False)

                                if navigate_command:
                                    command_queue.append(navigate_command)
                        else:
                            #attack!
                            if not isinstance(closest_enemy_ships[0], int):
                                navigate_command = ship.navigate(
                                            ship.closest_point_to(closest_enemy_ships[0]),
                                            game_map,
                                            speed=int(hlt.constants.MAX_SPEED),
                                            ignore_ships=False)

                                if navigate_command:
                                    command_queue.append(navigate_command)
                            

                                
                    elif not isinstance(closest_empty_planets[0], int):
                        target =  closest_empty_planets[0]
                        if ship.can_dock(target):
                            command_queue.append(ship.dock(target))
                        else:
                            navigate_command = ship.navigate(
                                        ship.closest_point_to(target),
                                        game_map,
                                        speed=int(hlt.constants.MAX_SPEED),
                                        ignore_ships=False)

                            if navigate_command:
                                command_queue.append(navigate_command)

                    #attack!
                    elif not isinstance(closest_enemy_ships[0], int):
                        navigate_command = ship.navigate(
                                    ship.closest_point_to(closest_enemy_ships[0]),
                                    game_map,
                                    speed=int(hlt.constants.MAX_SPEED),
                                    ignore_ships=False)

                        if navigate_command:
                            command_queue.append(navigate_command)

Here, we attempt to mine one of our own planets. If we want to dock at our own planet, but there are no more spots, then we just go attack. IF we don't have any owned planets, then we default to hunt for an empty one, but, if that fails too because there are none, we attack!

Finally:

                # FIND AND MINE AN EMPTY PLANET #
                elif np.argmax(output_vector) == 2:
                    '''
                    type: 2
                    Mine an empty planet. 
                    '''
                    if not isinstance(closest_empty_planets[0], int):
                        target =  closest_empty_planets[0]
                        
                        if ship.can_dock(target):
                            command_queue.append(ship.dock(target))
                        else:
                            navigate_command = ship.navigate(
                                        ship.closest_point_to(target),
                                        game_map,
                                        speed=int(hlt.constants.MAX_SPEED),
                                        ignore_ships=False)

                            if navigate_command:
                                command_queue.append(navigate_command)

                    else:
                        #attack!
                        if not isinstance(closest_enemy_ships[0], int):
                            navigate_command = ship.navigate(
                                        ship.closest_point_to(closest_enemy_ships[0]),
                                        game_map,
                                        speed=int(hlt.constants.MAX_SPEED),
                                        ignore_ships=False)

                            if navigate_command:
                                command_queue.append(navigate_command)

We try to find an empty planet. If there are none, we attack!

The end of the script just closes off the trys, saves the moves, and sends game commands:

            except Exception as e:
                logging.info(str(e))
                
            with open("c{}_input.vec".format(VERSION),"a") as f:
                f.write(str( [round(item,3) for item in input_vector] ))
                f.write('\n')

            with open("c{}_out.vec".format(VERSION),"a") as f:
                f.write(str(output_vector))
                f.write('\n')

        except Exception as e:
            logging.info(str(e))

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

Full code:

import hlt
import logging
from collections import OrderedDict
import numpy as np
import random
import os

VERSION = 1

HM_ENT_FEATURES = 5
PCT_CHANGE_CHANCE = 30
DESIRED_SHIP_COUNT = 20

game = hlt.Game("Charles{}".format(VERSION))
logging.info("CharlesBot-{} Start".format(VERSION))

ship_plans = {}

if os.path.exists("c{}_input.vec".format(VERSION)):
    os.remove("c{}_input.vec".format(VERSION))

if os.path.exists("c{}_out.vec".format(VERSION)):
    os.remove("c{}_out.vec".format(VERSION))



def key_by_value(dictionary, value):
    for k,v in dictionary.items():
        if v[0] == value:
            return k
    return -99


def fix_data(data):
    new_list = []
    last_known_idx = 0
    for i in range(HM_ENT_FEATURES):
        try:
            if i < len(data):
                last_known_idx=i
            new_list.append(data[last_known_idx])
        except:
            new_list.append(0)
            
    return new_list



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

    team_ships = game_map.get_me().all_ships()
    all_ships = game_map._all_ships()
    enemy_ships = [ship for ship in game_map._all_ships() if ship not in team_ships]

    my_ship_count = len(team_ships)
    enemy_ship_count = len(enemy_ships)
    all_ship_count = len(all_ships)
    
    my_id = game_map.get_me().id

    empty_planet_sizes = {}
    our_planet_sizes = {}
    enemy_planet_sizes = {}

    for p in game_map.all_planets():
        radius = p.radius
        if not p.is_owned():
            empty_planet_sizes[radius] = p
        elif p.owner.id == game_map.get_me().id:
            our_planet_sizes[radius] = p
        elif p.owner.id != game_map.get_me().id:
            enemy_planet_sizes[radius] = p

    hm_our_planets = len(our_planet_sizes)
    hm_empty_planets = len(empty_planet_sizes)
    hm_enemy_planets = len(enemy_planet_sizes)

    empty_planet_keys = sorted([k for k in empty_planet_sizes])[::-1]
    our_planet_keys = sorted([k for k in our_planet_sizes])[::-1]
    enemy_planet_keys= sorted([k for k in enemy_planet_sizes])[::-1]

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

            shipid = ship.id
            change = False
            if random.randint(1,100) <= PCT_CHANGE_CHANCE:
                change = True


            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()]
            closest_empty_planet_distances = [distance 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()]
            
            closest_my_planets = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0].is_owned() and (entities_by_distance[distance][0].owner.id == game_map.get_me().id)]
            closest_my_planets_distances = [distance for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0].is_owned() and (entities_by_distance[distance][0].owner.id == game_map.get_me().id)]

            closest_enemy_planets = [entities_by_distance[distance][0] for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0] not in closest_my_planets and entities_by_distance[distance][0] not in closest_empty_planets]
            closest_enemy_planets_distances = [distance for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Planet) and entities_by_distance[distance][0] not in closest_my_planets and entities_by_distance[distance][0] not in closest_empty_planets]
    
            
            closest_team_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] in team_ships]
            closest_team_ships_distances = [distance for distance in entities_by_distance if isinstance(entities_by_distance[distance][0], hlt.entity.Ship) and entities_by_distance[distance][0] in team_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]
            closest_enemy_ships_distances = [distance 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]

            largest_empty_planet_distances = []
            largest_our_planet_distances = []
            largest_enemy_planet_distances = []

            for i in range(HM_ENT_FEATURES):
                try: largest_empty_planet_distances.append(key_by_value(entities_by_distance, empty_planet_sizes[empty_planet_keys[i]]))
                except:largest_empty_planet_distances.append(-99)

                try: largest_our_planet_distances.append(key_by_value(entities_by_distance, our_planet_sizes[our_planet_keys[i]]))
                except:largest_our_planet_distances.append(-99)

                try: largest_enemy_planet_distances.append(key_by_value(entities_by_distance, enemy_planet_sizes[enemy_planet_keys[i]]))
                except:largest_enemy_planet_distances.append(-99)
                

            entity_lists = [fix_data(closest_empty_planet_distances),
                            fix_data(closest_my_planets_distances),
                            fix_data(closest_enemy_planets_distances),
                            fix_data(closest_team_ships_distances),
                            fix_data(closest_enemy_ships_distances),
                            fix_data(empty_planet_keys),
                            fix_data(our_planet_keys),
                            fix_data(enemy_planet_keys),
                            fix_data(largest_empty_planet_distances),
                            fix_data(largest_our_planet_distances),
                            fix_data(largest_enemy_planet_distances)]


            input_vector = []
            
            for i in entity_lists:
                for ii in i[:HM_ENT_FEATURES]:
                    input_vector.append(ii)

            input_vector += [my_ship_count,
                             enemy_ship_count,
                             hm_our_planets,
                             hm_empty_planets,
                             hm_enemy_planets]


            if my_ship_count > DESIRED_SHIP_COUNT:
                '''ATTACK: [1,0,0]'''
                
                output_vector = 3*[0] #[0,0,0]
                output_vector[0] = 1 #[1,0,0]
                ship_plans[ship.id] = output_vector

            elif change or ship.id not in ship_plans:
                '''
                pick new "plan"
                '''
                output_vector = 3*[0]
                output_vector[random.randint(0,2)] = 1
                ship_plans[ship.id] = output_vector

            else:
                '''continue to execute existing plan'''
                output_vector = ship_plans[ship.id]



            try:
                
                # ATTACK ENEMY SHIP #
                if np.argmax(output_vector) == 0: #[1,0,0]
                    '''
                    type: 0
                    Find closest enemy ship, and attack!
                    '''
                    if not isinstance(closest_enemy_ships[0], int):
                        navigate_command = ship.navigate(
                                    ship.closest_point_to(closest_enemy_ships[0]),
                                    game_map,
                                    speed=int(hlt.constants.MAX_SPEED),
                                    ignore_ships=False)

                        if navigate_command:
                            command_queue.append(navigate_command)



                # MINE ONE OF OUR PLANETS #
                elif np.argmax(output_vector) == 1:
                    '''
                    type: 1
                    Mine closest already-owned planet
                    '''
                    if not isinstance(closest_my_planets[0], int):
                        target =  closest_my_planets[0]

                        if len(target._docked_ship_ids) < target.num_docking_spots:
                            if ship.can_dock(target):
                                command_queue.append(ship.dock(target))
                            else:
                                navigate_command = ship.navigate(
                                            ship.closest_point_to(target),
                                            game_map,
                                            speed=int(hlt.constants.MAX_SPEED),
                                            ignore_ships=False)

                                if navigate_command:
                                    command_queue.append(navigate_command)
                        else:
                            #attack!
                            if not isinstance(closest_enemy_ships[0], int):
                                navigate_command = ship.navigate(
                                            ship.closest_point_to(closest_enemy_ships[0]),
                                            game_map,
                                            speed=int(hlt.constants.MAX_SPEED),
                                            ignore_ships=False)

                                if navigate_command:
                                    command_queue.append(navigate_command)
                            

                                
                    elif not isinstance(closest_empty_planets[0], int):
                        target =  closest_empty_planets[0]
                        if ship.can_dock(target):
                            command_queue.append(ship.dock(target))
                        else:
                            navigate_command = ship.navigate(
                                        ship.closest_point_to(target),
                                        game_map,
                                        speed=int(hlt.constants.MAX_SPEED),
                                        ignore_ships=False)

                            if navigate_command:
                                command_queue.append(navigate_command)

                    #attack!
                    elif not isinstance(closest_enemy_ships[0], int):
                        navigate_command = ship.navigate(
                                    ship.closest_point_to(closest_enemy_ships[0]),
                                    game_map,
                                    speed=int(hlt.constants.MAX_SPEED),
                                    ignore_ships=False)

                        if navigate_command:
                            command_queue.append(navigate_command)


                            
            
                # FIND AND MINE AN EMPTY PLANET #
                elif np.argmax(output_vector) == 2:
                    '''
                    type: 2
                    Mine an empty planet. 
                    '''
                    if not isinstance(closest_empty_planets[0], int):
                        target =  closest_empty_planets[0]
                        
                        if ship.can_dock(target):
                            command_queue.append(ship.dock(target))
                        else:
                            navigate_command = ship.navigate(
                                        ship.closest_point_to(target),
                                        game_map,
                                        speed=int(hlt.constants.MAX_SPEED),
                                        ignore_ships=False)

                            if navigate_command:
                                command_queue.append(navigate_command)

                    else:
                        #attack!
                        if not isinstance(closest_enemy_ships[0], int):
                            navigate_command = ship.navigate(
                                        ship.closest_point_to(closest_enemy_ships[0]),
                                        game_map,
                                        speed=int(hlt.constants.MAX_SPEED),
                                        ignore_ships=False)

                            if navigate_command:
                                command_queue.append(navigate_command)




            except Exception as e:
                logging.info(str(e))
                
            with open("c{}_input.vec".format(VERSION),"a") as f:
                f.write(str( [round(item,3) for item in input_vector] ))
                f.write('\n')

            with open("c{}_out.vec".format(VERSION),"a") as f:
                f.write(str(output_vector))
                f.write('\n')

        except Exception as e:
            logging.info(str(e))

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

Now that we have a bot to randomly play, we need to setup a way to get this AI to play itself, and log the winner's moves, which is what we'll be doing in the next tutorial.

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