Cleaning up a few things - Halite III (2018) AI Coding Competition p.6




Halite III Part 6

Welcome to part 4 of the Halite III competition tutorials. Leading up to this point, we've been working on logic for finding halite and then returning to drop it off. Things are going decently well, but we're finding that we get stuck at the depot, because we're never moving off of the depot after drop off.

...we forgot logic to go back to collecting! When we make our deposit:

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

We can just wait until the move is (0,0). This would mean that we are already at the depot, so then we can just do:

            if move == Direction.Still:
                ship_states[ship.id] = "collecting"

So the full statement becomes:

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

Now the full code is:

# 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

            #halite_dict[Direction.Still] *= 4

            if game.turn_number == 5:
                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))
            if move == Direction.Still:
                ship_states[ship.id] = "collecting"



    # 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, this is much better! That said, there are still collisions! What's going on here? Well, there's absolutely no protection for ships that are doing the naive_navigate back to drop off. These ships are moving, but not reporting their moves to the other ships, or checking the moves of the other ships, so this is causing mayhem! Let's fix that now too. So, my idea of how we could stop ships from running into eachother doesn't actually appear to work. I did try to notify with our list, but this doesn't actually seem to solve the problem.

Looking at the naive_navigate in the hlt/game_map.py, it looks instead like we're meant to check unsafe moves, then, when we move, we mark that move unsafe. I am not sure why this is different than my method, but it does seem to be the case.

In order to know which coordinate that we intend to navigate to, we have to add the intended direction to our current position

ship.position+Position(*Direction.Still)

Naive navigate will take this goal and move us there without crashing. To do this, we can calculate our move with:

game_map.naive_navigate(ship, ship.position+Position(*Direction.Still))

The naive_navigate returns the move itself, so we need to use ship.move() with that. So, all together, we're going to wrap all moves with:

command_queue.append(ship.move(game_map.naive_navigate(ship, ship.position+Position(*Direction.Still))))

Definitely not the most pretty thing I've ever seen, but it works. 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, Position  # helper for moving
import random  # randomly picking a choice for now.
import logging  # logging stuff to console
import math

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

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

            command_queue.append(ship.move(game_map.naive_navigate(ship, ship.position+Position(*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)
            upcoming_position = ship.position + Position(*move)
            if upcoming_position not in position_choices:
                position_choices.append(upcoming_position)
                command_queue.append(ship.move(move))
                if move == Direction.Still:
                    ship_states[ship.id] = "collecting"
            else:
                position_choices.append(ship.position)
                command_queue.append(ship.move(game_map.naive_navigate(ship, ship.position+Position(*Direction.Still))))

    # ship costs 1000, dont make a ship on a ship or they both sink
    if len(me.get_ships()) < math.ceil(game.turn_number / 25):
        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)

The next thing for us to focus on is drop offs. At some point, we'd like to have more ships, but then they get jammed up at the shipyard. One cheap solution here would be to, as the game progresses, allow ourselves to carry more halite before dropping off, but we can... wait: if ship.halite_amount >= constants.MAX_HALITE:... HMMMMMM

...that's not right. I definitely meant to have this not actually be the max. So let's go ahead and fix that now.

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

Okay, but, once we have depots, we need to figure out where to place them, how to distribute works to those depots...etc....but, personally, I am really itching to do machine learning here with pathing, so that's what I am going to be focused on next!

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