Moving to drop off halite - Halite III (2018) AI Coding Competition p.5




Halite III Part 5

We're still not the most intelligent here, but the logic is...beginning to work. The next major thing we should do is to try to stop the ships from bouncing around so much. We ought to weight the current position a bit higher so the ship stays still to collect more halite. Maybe a weight of 4x.

Interestingly, I thought I could just do something like halite_dict[Direction.Still] *= 4 before taking the max ... but this doesn't work. Nor does (0, 0), resulting in key errors... Instead, I went with:

            for direction in position_dict:
                position = position_dict[direction]
                halite_amount = game_map[position].halite_amount
                if position_dict[direction] not in position_choices:
                    if direction == Direction.Still:
                        halite_amount *= 4
                    halite_dict[direction] = halite_amount

Full code up to this point:

# Python 3.6
import hlt  #main halite stuff
from hlt import constants  # halite constants
from hlt.positionals import Direction  # helper for moving
import random  # randomly picking a choice for now.
import logging  # logging stuff to console

game = hlt.Game()  # game object
# Initializes the game
game.ready("Sentdebot")

while True:
    # This loop handles each turn of the game. The game object changes every turn, and you refresh that state by
    game.update_frame()
    # You extract player metadata and the updated map metadata here for convenience.
    me = game.me

    '''comes from game, game comes from before the loop, hlt.Game points to networking, which is where you will
    find the actual Game class (hlt/networking.py). From here, GameMap is imported from hlt/game_map.py.

    open that file to seee all the things we do with game map.'''
    game_map = game.game_map  # game map data. Recall game is

    # A command queue holds all the commands you will run this turn. You build this list up and submit it at the
    #   end of the turn.
    command_queue = []

    # specify the order we know this all to be
    direction_order = [Direction.North, Direction.South, Direction.East, Direction.West, Direction.Still]

    position_choices = []
    for ship in me.get_ships():
        # cardinals get surrounding cardinals using get_all_cardinals in positionals.py.
        # reading this file, I can see they will be in order of:
        # [Direction.North, Direction.South, Direction.East, Direction.West]

        # maps with position orders, but also gives us all the surrounding possitions
        position_options = ship.position.get_surrounding_cardinals() + [ship.position]

        # we will be mapping the "direction" to the actual position

        # position_dict = {(0, -1): Position(8, 15), (0, 1): Position(8, 17), (1, 0): Position(9, 16), (-1, 0): Position(7, 16), (0, 0): Position(8, 16)}
        position_dict = {}

        # maps the direction choice with halite
        # halite_dict = {(0, -1): 708, (0, 1): 492, (1, 0): 727, (-1, 0): 472, (0, 0): 0}
        halite_dict = {}

        for n, direction in enumerate(direction_order):
            position_dict[direction] = position_options[n]

        for direction in position_dict:
            position = position_dict[direction]
            halite_amount = game_map[position].halite_amount
            if position_dict[direction] not in position_choices:
                if direction == Direction.Still:
                    halite_amount *= 4
                halite_dict[direction] = halite_amount

        if game.turn_number == 15:
            logging.info(position_dict)
            logging.info(halite_dict)
            logging.info(max(halite_dict, key=halite_dict.get))

        directional_choice = max(halite_dict, key=halite_dict.get)
        position_choices.append(position_dict[directional_choice])
        command_queue.append(
            ship.move(directional_choice))

    # ship costs 1000, dont make a ship on a ship or they both sink
    if me.halite_amount >= 1000 and not game_map[me.shipyard].is_occupied:
        command_queue.append(me.shipyard.spawn())

    # Send your moves back to the game environment, ending this turn.
    game.end_turn(command_queue)

This works...sort of. Now we need to return to the shipyard to drop off our Halite! We're basically going to do the same thing as Halite has in part of their tutorial. Before the while loop begins for the game, we want to define a variable, a dictionary, that will track our ships and their state. A ship's state, for now, should be something like collecting and depositing. When collecting, the ship should employ the logic that we've been working on so far. A ship should continue collecting until it reaches some n threshold of halite that we deem worthy of depositing. At this point, the ship's state should become depositing. After a deposit, however, the ship's state should return again to collecting.

Before the while loop then, we'll start with defining ship_states = {}

ship_states = {}
while True:
    ...

Now, to begin, it's likely most ships aren't going to have a state at all. As we iterate over the ships, if they aren't in the dictionary, then the ship is new. We can default define the state for these ships as collecting:

    for ship in me.get_ships():
        if ship.id not in ship_states:
            ship_states[ship.id] = "collecting"

Now, we want to check to see if the ship's status is collecting. If it is, then we do all of the logic we've done so far:

    for ship in me.get_ships():
        if ship.id not in ship_states:
            ship_states[ship.id] = "collecting"

        if ship_states[ship.id] == "collecting":
            # cardinals get surrounding cardinals using get_all_cardinals in positionals.py.
            # reading this file, I can see they will be in order of:
            # [Direction.North, Direction.South, Direction.East, Direction.West]

            # maps with position orders, but also gives us all the surrounding possitions
            position_options = ship.position.get_surrounding_cardinals() + [ship.position]

            # we will be mapping the "direction" to the actual position

            # position_dict = {(0, -1): Position(8, 15), (0, 1): Position(8, 17), (1, 0): Position(9, 16), (-1, 0): Position(7, 16), (0, 0): Position(8, 16)}
            position_dict = {}

            # maps the direction choice with halite
            # halite_dict = {(0, -1): 708, (0, 1): 492, (1, 0): 727, (-1, 0): 472, (0, 0): 0}
            halite_dict = {}

            for n, direction in enumerate(direction_order):
                position_dict[direction] = position_options[n]

            for direction in position_dict:
                position = position_dict[direction]
                halite_amount = game_map[position].halite_amount
                if position_dict[direction] not in position_choices:
                    if direction == Direction.Still:
                        halite_amount *= 4
                    halite_dict[direction] = halite_amount

            if game.turn_number == 15:
                logging.info(position_dict)
                logging.info(halite_dict)
                logging.info(max(halite_dict, key=halite_dict.get))

            directional_choice = max(halite_dict, key=halite_dict.get)
            position_choices.append(position_dict[directional_choice])

            command_queue.append(
                ship.move(directional_choice))

Now, we need some threshold to reach in order to deposit. There are halite constants, one of which is the MAX_HALITE value. We could wait until our ships reach this value, or we can deposit sooner. Always filling up your ships completely could plausibly put your ship at a risk and not be the greatest strategy.

When ships collide, they both sink. This may sound like it's equally detrimental to both players. Players, however, could monitor your ships, and, if they believe you have more halite than one of their ships that's close to you, they might suicide into you, because this would hurt you more than them.

In the early game as well, it's more likely that expansion is more important than filling up entirely, so you probably will want to drop off sooner than when full. The tutorial uses the max divided by 4, so let's start with that too. I really do not know the answer, except that certain variables should dictate it, like how fast could the enemy get to you, how badly you need the halite right this moment, how much waste it will be to return...etc.

So, for now, let's add:

            if ship.halite_amount >= constants.MAX_HALITE:
                ship_states[ship.id] = "depositing"

Our loop becomes:

    for ship in me.get_ships():
        if ship.id not in ship_states:
            ship_states[ship.id] = "collecting"

        if ship_states[ship.id] == "collecting":
            # cardinals get surrounding cardinals using get_all_cardinals in positionals.py.
            # reading this file, I can see they will be in order of:
            # [Direction.North, Direction.South, Direction.East, Direction.West]

            # maps with position orders, but also gives us all the surrounding possitions
            position_options = ship.position.get_surrounding_cardinals() + [ship.position]

            # we will be mapping the "direction" to the actual position

            # position_dict = {(0, -1): Position(8, 15), (0, 1): Position(8, 17), (1, 0): Position(9, 16), (-1, 0): Position(7, 16), (0, 0): Position(8, 16)}
            position_dict = {}

            # maps the direction choice with halite
            # halite_dict = {(0, -1): 708, (0, 1): 492, (1, 0): 727, (-1, 0): 472, (0, 0): 0}
            halite_dict = {}

            for n, direction in enumerate(direction_order):
                position_dict[direction] = position_options[n]

            for direction in position_dict:
                position = position_dict[direction]
                halite_amount = game_map[position].halite_amount
                if position_dict[direction] not in position_choices:
                    if direction == Direction.Still:
                        halite_amount *= 4
                    halite_dict[direction] = halite_amount

            if game.turn_number == 15:
                logging.info(position_dict)
                logging.info(halite_dict)
                logging.info(max(halite_dict, key=halite_dict.get))

            directional_choice = max(halite_dict, key=halite_dict.get)
            position_choices.append(position_dict[directional_choice])

            command_queue.append(
                ship.move(directional_choice))

            if ship.halite_amount >= constants.MAX_HALITE:
                ship_states[ship.id] = "depositing"

Now, we need 1 last handling for these depositing ships.

        elif ship_states[ship.id] == "depositing":
            move = game_map.naive_navigate(ship, me.shipyard.position)
            command_queue.append(ship.move(move))

Okay, so, all together:

# Python 3.6
import hlt  #main halite stuff
from hlt import constants  # halite constants
from hlt.positionals import Direction  # helper for moving
import random  # randomly picking a choice for now.
import logging  # logging stuff to console

game = hlt.Game()  # game object
# Initializes the game
game.ready("Sentdebot")

ship_states = {}
while True:
    # This loop handles each turn of the game. The game object changes every turn, and you refresh that state by
    game.update_frame()
    # You extract player metadata and the updated map metadata here for convenience.
    me = game.me

    '''comes from game, game comes from before the loop, hlt.Game points to networking, which is where you will
    find the actual Game class (hlt/networking.py). From here, GameMap is imported from hlt/game_map.py.

    open that file to seee all the things we do with game map.'''
    game_map = game.game_map  # game map data. Recall game is

    # A command queue holds all the commands you will run this turn. You build this list up and submit it at the
    #   end of the turn.
    command_queue = []

    # specify the order we know this all to be
    direction_order = [Direction.North, Direction.South, Direction.East, Direction.West, Direction.Still]

    position_choices = []
    for ship in me.get_ships():
        if ship.id not in ship_states:
            ship_states[ship.id] = "collecting"

        if ship_states[ship.id] == "collecting":
            # cardinals get surrounding cardinals using get_all_cardinals in positionals.py.
            # reading this file, I can see they will be in order of:
            # [Direction.North, Direction.South, Direction.East, Direction.West]

            # maps with position orders, but also gives us all the surrounding possitions
            position_options = ship.position.get_surrounding_cardinals() + [ship.position]

            # we will be mapping the "direction" to the actual position

            # position_dict = {(0, -1): Position(8, 15), (0, 1): Position(8, 17), (1, 0): Position(9, 16), (-1, 0): Position(7, 16), (0, 0): Position(8, 16)}
            position_dict = {}

            # maps the direction choice with halite
            # halite_dict = {(0, -1): 708, (0, 1): 492, (1, 0): 727, (-1, 0): 472, (0, 0): 0}
            halite_dict = {}

            for n, direction in enumerate(direction_order):
                position_dict[direction] = position_options[n]

            for direction in position_dict:
                position = position_dict[direction]
                halite_amount = game_map[position].halite_amount
                if position_dict[direction] not in position_choices:
                    if direction == Direction.Still:
                        halite_amount *= 4
                    halite_dict[direction] = halite_amount

            if game.turn_number == 15:
                logging.info(position_dict)
                logging.info(halite_dict)
                logging.info(max(halite_dict, key=halite_dict.get))

            directional_choice = max(halite_dict, key=halite_dict.get)
            position_choices.append(position_dict[directional_choice])

            command_queue.append(
                ship.move(directional_choice))

            if ship.halite_amount >= constants.MAX_HALITE:
                ship_states[ship.id] = "depositing"

        elif ship_states[ship.id] == "depositing":
            move = game_map.naive_navigate(ship, me.shipyard.position)
            command_queue.append(ship.move(move))


    # ship costs 1000, dont make a ship on a ship or they both sink
    if me.halite_amount >= 1000 and not game_map[me.shipyard].is_occupied:
        command_queue.append(me.shipyard.spawn())

    # Send your moves back to the game environment, ending this turn.
    game.end_turn(command_queue)

Okay, so this is further improving, but we've quickly created a backup at the depot, and are ending up completely stuck often! Let's solve this and work more on our logic in the next tutorial!

The next tutorial:





  • Introduction - Halite III (2018) AI Coding Competition p.1
  • Running Locally and getting surrounding halite - Halite III (2018) AI Coding Competition p.2
  • Moving towards most halite - Halite III (2018) AI Coding Competition p.3
  • Trying to not run into ourselves - Halite III (2018) AI Coding Competition p.4
  • Moving to drop off halite - Halite III (2018) AI Coding Competition p.5
  • Cleaning up a few things - Halite III (2018) AI Coding Competition p.6