/ ml / supervised / model.py
model.py
  1  # Adapted from: https://halite.io/learn-programming-challenge/tutorials/ml-svm
  2  
  3  import pickle
  4  import random
  5  import time
  6  
  7  import numpy as np
  8  from sklearn.svm import SVC
  9  from tqdm import tqdm
 10  
 11  import hlt
 12  import parse
 13  from hlt import constants
 14  from hlt import positionals
 15  
 16  
 17  class HaliteModel:
 18      MAX_FILES = 100
 19      DIRECTION_ORDER = [positionals.Direction.West,
 20                         positionals.Direction.North,
 21                         positionals.Direction.East,
 22                         positionals.Direction.South]
 23      MOVE_TO_DIRECTION = {
 24          "o": positionals.Direction.Still,
 25          "w": positionals.Direction.West,
 26          "n": positionals.Direction.North,
 27          "e": positionals.Direction.East,
 28          "s": positionals.Direction.South}
 29      OUTPUT_TO_MOVE = {
 30          0: "o",
 31          1: "w",
 32          2: "n",
 33          3: "e",
 34          4: "s"}
 35      MOVE_TO_OUTPUT = {v: k for k, v in OUTPUT_TO_MOVE.items()}
 36  
 37      def __init__(self, weights=None):
 38          if weights is not None:
 39              with open(weights, 'rb') as f:
 40                  self.model = pickle.load(f)
 41          else:
 42              self.model = SVC(class_weight='balanced')
 43  
 44      def train_on_files(self, replay_folder, player_name):
 45          game_data = parse.parse_replay_folder(replay_folder, player_name, max_files=self.MAX_FILES)
 46  
 47          print("Processing Game States")
 48          game_states = []
 49          for g in game_data:
 50              turn_number = 0
 51              for game_map, moves, ships, other_ships, dropoffs, other_dropoffs in g:
 52                  turn_number += 1
 53                  for ship in list(ships.values()):
 54                      if random.random() < .25:
 55                          game_states.append((game_map, moves, ships, other_ships, dropoffs,
 56                                              other_dropoffs, turn_number, ship))
 57  
 58          print("Generating Training Data")
 59          data, labels = [], []
 60          for game_map, moves, ships, other_ships, dropoffs, other_dropoffs, turn_number, ship in tqdm(game_states):
 61              move = "o" if ship.id not in moves else moves[ship.id]
 62              # Throw away movements that take us closer to base. We will let logic take care of returning to base
 63              if move is not "o" and (
 64                      game_map.calculate_distance(ship.position.directional_offset(self.MOVE_TO_DIRECTION[move]),
 65                                                  dropoffs[0].position) <
 66                      game_map.calculate_distance(ship.position, dropoffs[0].position)
 67              ):
 68                  continue
 69  
 70              move_id = self.MOVE_TO_OUTPUT[move]
 71              for rot in range(4):  # do all 4 rotations for each game state
 72                  data.append(self.input_for_ship(game_map,
 73                                                  ship,
 74                                                  [s2.position for s2 in ships.values() if s2.id != ship.id],
 75                                                  [s2.position for s2 in other_ships.values()],
 76                                                  [d.position for d in dropoffs],
 77                                                  [d.position for d in other_dropoffs],
 78                                                  turn_number,
 79                                                  rotation=rot))
 80                  labels.append(np.array(move_id))
 81                  move_id = 0 if move_id == 0 else (move_id % 4) + 1
 82          data = np.array(data)
 83          labels = np.array(labels)
 84  
 85          print("Number of Datapoints: {}".format(len(data)))
 86          print("Number of Features: {}".format(len(data[0])))
 87  
 88          self.train(data, labels)
 89  
 90      def train(self, data, moves):
 91          print("Training Model")
 92          self.model.fit(data, moves)
 93  
 94      # Generate the feature vector
 95      def input_for_ship(self, game_map, ship, my_other_ships, other_ships, my_dropoffs, other_dropoffs, turn_number,
 96                         rotation=0):
 97          result = []
 98  
 99          # game turn
100          percent_done = turn_number / constants.MAX_TURNS
101          result.append(percent_done)
102  
103          # Local area stats
104          for objs in [my_other_ships, other_ships, my_dropoffs, other_dropoffs]:
105              objs_directions = []
106              for d in self.DIRECTION_ORDER:
107                  objs_directions.append(int(game_map.normalize(ship.position.directional_offset(d)) in objs))
108              result += self.rotate_direction_vector(objs_directions, rotation)
109  
110          # directions to highest halite cells at certain distances
111          for distance in range(1, 13):
112              max_halite_cell = self.max_halite_within_distance(game_map, ship.position, distance)
113              halite_directions = self.generate_direction_vector(game_map, ship.position, max_halite_cell)
114              result += self.rotate_direction_vector(halite_directions, rotation)
115  
116          # directions to closest drop off
117          closest_dropoff = my_dropoffs[0]
118          for dropoff in my_dropoffs:
119              if game_map.calculate_distance(ship.position, dropoff) < game_map.calculate_distance(ship.position,
120                                                                                                   closest_dropoff):
121                  closest_dropoff = dropoff
122          dropoff_directions = self.generate_direction_vector(game_map, ship.position, closest_dropoff)
123          result += self.rotate_direction_vector(dropoff_directions, rotation)
124  
125          # local area halite
126          local_halite = []
127          for d in self.DIRECTION_ORDER:
128              local_halite.append(game_map[game_map.normalize(ship.position.directional_offset(d))].halite_amount / 1000)
129          result += self.rotate_direction_vector(local_halite, rotation)
130  
131          # current cell halite indicators
132          for i in range(0, 200, 50):
133              result.append(int(game_map[ship.position].halite_amount <= i))
134          result.append(game_map[ship.position].halite_amount / 1000)
135          return result
136  
137      def predict_move(self, ship, game_map, me, other_players, turn_number):
138          other_ships = [s.position for s in me.get_ships() if s.id != ship.id]
139          opp_ships = [s.position for p in other_players for s in p.get_ships()]
140          my_dropoffs = [d.position for d in list(me.get_dropoffs()) + [me.shipyard]]
141          opp_dropoffs = [d.position for p in other_players for d in p.get_dropoffs()] + \
142                         [p.shipyard.position for p in other_players]
143          data = np.array(self.input_for_ship(game_map,
144                                              ship,
145                                              other_ships,
146                                              opp_ships,
147                                              my_dropoffs,
148                                              opp_dropoffs,
149                                              turn_number))
150          data = data.reshape(1, -1)
151          model_output = self.model.predict(data)[0]
152          return self.MOVE_TO_DIRECTION[self.OUTPUT_TO_MOVE[model_output]]
153  
154      def save(self, file_name=None):
155          if file_name is None:
156              file_name = "model_weights_%f.svc" % time.time()
157          with open(file_name, 'wb') as f:
158              pickle.dump(self.model, f)
159  
160      # finds cell with max halite within certain distance of location
161      def max_halite_within_distance(self, game_map, location, distance):
162          max_halite_cell = location
163          max_halite = 0
164          for dx in range(-distance, distance + 1):
165              for dy in range(-distance, distance + 1):
166                  loc = game_map.normalize(location + hlt.Position(dx, dy))
167                  if game_map.calculate_distance(location, loc) > distance:
168                      continue
169  
170                  # pick cell with max halite
171                  cell_halite = game_map[loc].halite_amount
172                  if cell_halite > max_halite:
173                      max_halite_cell = loc
174                      max_halite = cell_halite
175          return max_halite_cell
176  
177      # generate vector that tells which directions to go to get from ship_location to target
178      def generate_direction_vector(self, game_map, ship_location, target):
179          directions = []
180          for d in self.DIRECTION_ORDER:
181              directions.append(
182                  int(game_map.calculate_distance(game_map.normalize(ship_location.directional_offset(d)), target) <
183                      game_map.calculate_distance(ship_location, target)))
184          return directions
185  
186      def rotate_direction_vector(self, direction_vector, rotations):
187          for i in range(rotations):
188              direction_vector = [direction_vector[-1]] + direction_vector[:-1]
189          return direction_vector