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