Commit 7b069b1d authored by Jude Hungerford's avatar Jude Hungerford

add final contest entry code and readme

parents
(* OCaml Starter for Halite III on Halite.io
This code is public domain. There is no warranty.
*)
open Type;;
let version_string = "_v0.20.j";;
(* Add desires to state.persist.desire_map for all my ships *)
let ships_choose_desired_moves state =
List.iter (fun p_ship ->
p_ship.desired_moves <- [];
p_ship.move_decided <- false;
Mole.update_assignment state p_ship;
(*Debug.debug_assignment p_ship;*)
) (Game.my_p_ships state);
Mole.update_blockade_and_sabotage_targets state;
Mole.ships_update_destinations state;
Mole.print_contested state;
(*
if state.num_players = 4 then (
*)
Iguana.time_fn Inspire.choose_inspiration_relevant_moves state;
(*
);
*)
List.iter (fun p_ship ->
if p_ship.desired_moves = [] then (
Mole.choose_desired_moves state p_ship;
)
) (Game.my_p_ships state);
Mole.update_friendly_obstacles state;
List.iter (fun p_ship ->
if p_ship.desired_moves = [] then (
Mole.choose_desired_moves state p_ship;
)
) (Game.my_p_ships state);
;;
(* Receive state and return commands *)
let mybot_fun state =
(*Debug.debug "begin";*)
(*Debug.debug (Printf.sprintf "my_id = %d\n" state.my_id);*)
Mole.begin_turn state;
(*ignore (Sim.new_sim_state state);*)
Mole.clear_obstacles state;
Mole.update_enemy_obstacles state;
Mole.create_area_searches state;
state.persist.total_halite_available <- Iguana.sum_grid state.map;
Mole.update_future_dropoff_point state;
Mole.update_crash_search state;
Mole.update_all_enemy_search state;
Iguana.time_fn Iguana.update_ships_near state;
state.persist.inspired_condensed_map <- Iguana.make_inspired_condensed_map state;
Mole.update_stupidity state;
List.iter (Mole.update_assignment state) (Game.my_p_ships state);
ships_choose_desired_moves state;
Iguana.squash_then_stretch_all_desires state;
let ship_commands = Mole.finalise_ship_commands state in
let other_commands = Mole.finalise_other_commands state in
ship_commands @ other_commands
;;
(* Run bot functions with state data taken from stdin *)
let run pregame_process bot =
try (
(* log to file *)
Debug.setup_logging "Mole" version_string;
let state = Game.new_state() in
Random.init(max (state.const.game_seed - 9823) (state.const.game_seed + 1303));
(* I was getting poor quality random results until I started discarding
* the first few.
*)
for i = 0 to 1000 do
ignore(Random.int(i+10));
done;
Networking.process_initial_tokens state;
pregame_process state;
Networking.send_string ("Mole" ^ version_string);
Networking.done_sending();
let max_seconds_elapsed = ref 0. in
while true do
Networking.parse_input state;
let commands = bot state in
let seconds_elapsed = (Game.time_seconds_elapsed_this_turn state) in
Debug.debug (Printf.sprintf "Seconds elapsed = %f\n" seconds_elapsed);
max_seconds_elapsed := max !max_seconds_elapsed seconds_elapsed;
Debug.debug (Printf.sprintf "Max seconds elapsed = %f\n" !max_seconds_elapsed);
Networking.send_commands commands;
Debug.debug "Finished turn.\n";
done
)
with e ->
Debug.error (Printexc.to_string e);
Debug.close_files();
;;
run Mole.pregame_process mybot_fun;;
Salticid Halite 3 contest entry
-------------------------------
This was the code I submitted for my final entry into the Halite 3 programming contest. In the last week before the deadline, the better versions generally achieved a rank above 50, but rarely above 40.
The code is a bit of a mess. I don't recommend reading it. If you want to, start with MyBot.ml and follow any functions which interest you from there. There's a lot of dead code, so reading through whole files will not give you much idea of how anything works.
The fine details really affect the final performance. Some "improvements" I tried - which really seemed to work locally - caused a drop from around rank 40 to around rank 80. There were a few things I was happy to be able to include in my entry:
- Assignments are set for all ships before any moves are calculated, and it spends some time attempting to iteratively improve on these assignments (e.g spreading ships out so they don't all target the same region).
- An attempt is made to iteratively improve inspiration-relevant orders to maximise my own inspiration while minimising that of others. This could have used a lot more testing and troubleshooting, but it definitely improved performance.
- Risk-taking behaviour of opponents is tracked and used to improve the chance of occupying threatened tiles without crashing. I think this could have been made to work better than it does, but I was happy that it helped at all.
There were many things I ran out of time to implement, or left half-implemented and disabled. It seems I could probably have benefited from slowing down on the new features, tidying up the code, and consolidating the core logic to eliminate specific erroneous behaviour. I kept putting those things off, enjoying the opportunity to be undisciplined and begin implementing every fun and exciting idea which seemed promising at the time - safe in the knowledge that I never have to touch this code again. I don't think this is the correct practice, but it's what I did, and I have enjoyed the contest immensely. :)
Thank you to all contestents and to everyone who contributed to running this excellent programming competition!
The website can be found at halite.io
(* OCaml Starter for Halite III on Halite.io
This code is public domain. There is no warranty.
*)
open Type;;
let out_chan = ref stderr;;
let flog_chan = ref stderr;;
let input_out_chan = ref stderr;;
let data_chan = ref stderr;;
let file_left_open = ref false;;
let setup_logging name tag =
let filename = Printf.sprintf "%s_%s.log" name tag in
out_chan := open_out filename;
let input_filename = Printf.sprintf "%s_%s_input.log" name tag in
input_out_chan := open_out input_filename;
let flog_filename = Printf.sprintf "%s_%s.flog" name tag in
flog_chan := open_out flog_filename;
output_string !flog_chan "[\n";
let data_filename = Printf.sprintf "%s_%s.data" name tag in
data_chan := open_out data_filename;
file_left_open := true
;;
let close_files () =
if !file_left_open then (
close_out !out_chan;
output_string !flog_chan "\n]\n";
close_out !flog_chan;
close_out !input_out_chan;
close_out !data_chan;
)
;;
let log_input s =
output_string !input_out_chan s;
flush !input_out_chan
;;
let debug s =
output_string !out_chan s;
flush !out_chan
;;
let flog_colour turn row col r g b =
output_string !flog_chan (Printf.sprintf "{\"t\":%d, \"x\":%d, \"y\":%d, \"color\":\"#%02x%02x%02x\"},\n" (turn - 1) col row r g b);
flush !flog_chan
;;
let flog_msg turn row col s =
output_string !flog_chan (Printf.sprintf "{\"t\":%d, \"x\":%d, \"y\":%d, \"msg\":\"%s\"},\n" (turn - 1) col row s);
flush !flog_chan
;;
let info s =
output_string !out_chan s;
flush !out_chan
;;
let data s =
output_string !data_chan s;
flush !data_chan
;;
let error s =
output_string stderr ("ERROR: " ^ s ^ "\n");
flush stderr
;;
let string_of_p_ship p_ship =
let row, col = p_ship.entity.position in
Printf.sprintf "Ship: owner = %d; id = %d; position = %d, %d; halite = %d; num desired moves = %d\n" p_ship.entity.owner p_ship.entity.id row col p_ship.entity.halite (List.length p_ship.desired_moves)
;;
let string_of_dir = function
| North -> "North"
| East -> "East"
| South -> "South"
| West -> "West"
| Still -> "Still"
;;
let string_of_command = function
| Generate -> "g"
| Construct ship_id -> "c " ^ (string_of_int ship_id) ^ " "
| Move (ship_id, dir) ->
"m " ^ (string_of_int ship_id) ^ " " ^ begin match dir with
| North -> "n"
| East -> "e"
| South -> "s"
| West -> "w"
| Still -> "o"
end ^ " "
;;
let string_of_desire desire =
let row, col = desire.destination in
(Printf.sprintf "p_ship = %s; command = %s; destination = %d, %d; weight = %f\n" (string_of_p_ship desire.p_ship) (string_of_command desire.command) row col desire.weight);
;;
let string_of_desire_list l =
List.fold_left (fun acc desire ->
acc ^ ", " ^ string_of_desire desire
) "" l
;;
let string_of_assignment = function
| Collect -> "Collect"
| Return -> "Return"
| Construct -> "Construct"
| Blockade -> "Blockade"
| Sabotage -> "Sabotage"
;;
let string_of_medium_destination p_ship =
match p_ship.medium_destination with
| None -> "no destination"
| Some (row, col) ->
Printf.sprintf "destination %d, %d" row col
;;
let string_of_int_int_option = function
| None -> "None"
| Some (row, col) ->
Printf.sprintf "Some %d, %d" row col
;;
let string_of_target = function
| None -> "no target"
| Some target -> begin match target with
| Base (entity, _) -> let row, col = entity.position in Printf.sprintf "Base at %d, %d" row col
| Harvest (p_ship, _) -> let row, col = p_ship.entity.position in Printf.sprintf "Ship at %d, %d" row col
| Crash_site (row, col) -> Printf.sprintf "Crash site at %d, %d" row col
end
;;
let string_of_p_ship_assignment p_ship =
match p_ship.assignment with
| Collect -> "Collect " ^ (string_of_medium_destination p_ship)
| Return -> "Return"
| Construct -> "Construct " ^ (string_of_medium_destination p_ship)
| Blockade -> "Blockade " ^ (string_of_target p_ship.target)
| Sabotage -> "Sabotage" ^ (string_of_target p_ship.target)
;;
let debug_assignment p_ship =
debug (Printf.sprintf "Ship = %s, assignment = %s\n\n" (string_of_p_ship p_ship) (string_of_p_ship_assignment p_ship));
;;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
open Type;;
let new_magic = function
| None ->
{
advisable_dropoff_quality_ratio = Fraction (15, 10);
advisable_dropoff_continuation_ratio = Fraction(14, 10);
ship_build_payoff_mul = Fraction (14, 10);
dangerous_load_mul = Integer (16);
enemy_dropoff_exclusion_radius = 6;
friendly_dropoff_exclusion_radius = 8;
endgame_high_halite = 450;
crash_warn_downplay = 0.2;
min_ships_before_dropoff = 11;
}
| Some state ->
let adqr, adcr, sbpm =
if state.num_players = 4 then
(
Fraction (16, 10),
Fraction(14, 10),
Fraction(14, 10)
)
else (
let reduction = 0 (* state.width / 8 - 4 *) in
Fraction (16 - reduction, 10),
Fraction(14 - reduction, 10),
Fraction(12, 10)
)
in
let radius_reduction = 5 - (state.width / 8 - 5) in
{
advisable_dropoff_quality_ratio = adqr;
advisable_dropoff_continuation_ratio = adcr;
ship_build_payoff_mul = sbpm;
dangerous_load_mul = Integer (16);
enemy_dropoff_exclusion_radius = 12 - radius_reduction;
friendly_dropoff_exclusion_radius = 13 - radius_reduction;
endgame_high_halite = 450;
crash_warn_downplay = 0.25;
min_ships_before_dropoff = 11;
}
;;
let mul_int i magic =
match magic with
| Fraction (num, denom) ->
i * num / denom
| Integer num ->
i * num
| Float f ->
int_of_float ((float_of_int i) *. f)
;;
This diff is collapsed.
(* OCaml Starter for Halite III on Halite.io
This code is public domain. There is no warranty.
*)
open Type;;
let log_input = true;;
let read_tokens () =
let raw_input = read_line () in
if log_input then Debug.log_input (raw_input ^ "\n");
let input = String.trim raw_input in
String.split_on_char ' ' input
;;
let rec get_token state =
match state.tokens with
| [] ->
state.tokens <- read_tokens();
get_token state
| token :: tail ->
state.tokens <- tail;
token
;;
let get_int_token state =
int_of_string (get_token state)
;;
let strip_first_char str =
if str = "" then "" else
String.sub str 1 ((String.length str) - 1)
;;
let strip_last_char str =
if str = "" then "" else
String.sub str 0 ((String.length str) - 1)
;;
let process_constant state left right =
match strip_first_char (strip_last_char left) with
| "CAPTURE_ENABLED" -> state.const.capture_enabled <- bool_of_string right
| "CAPTURE_RADIUS" -> state.const.capture_radius <- int_of_string right
| "DEFAULT_MAP_HEIGHT" -> state.const.default_map_height <- int_of_string right
| "DEFAULT_MAP_WIDTH" -> state.const.default_map_width <- int_of_string right
| "DROPOFF_COST" -> state.const.dropoff_cost <- int_of_string right
| "DROPOFF_PENALTY_RATIO" -> state.const.dropoff_penalty_ratio <- int_of_string right
| "EXTRACT_RATIO" -> state.const.extract_ratio <- int_of_string right
| "FACTOR_EXP_1" -> state.const.factor_exp_1 <- float_of_string right
| "FACTOR_EXP_2" -> state.const.factor_exp_2 <- float_of_string right
| "INITIAL_ENERGY" -> state.const.initial_energy <- int_of_string right
| "INSPIRATION_ENABLED" -> state.const.inspiration_enabled <- bool_of_string right
| "INSPIRATION_RADIUS" -> state.const.inspiration_radius <- int_of_string right
| "INSPIRATION_SHIP_COUNT" -> state.const.inspiration_ship_count <- int_of_string right
| "INSPIRED_BONUS_MULTIPLIER" -> state.const.inspired_bonus_multiplier <- float_of_string right
| "INSPIRED_EXTRACT_RATIO" -> state.const.inspired_extract_ratio <- int_of_string right
| "INSPIRED_MOVE_COST_RATIO" -> state.const.inspired_move_cost_ratio <- int_of_string right
| "MAX_CELL_PRODUCTION" -> state.const.max_cell_production <- int_of_string right
| "MAX_ENERGY" -> state.const.max_energy <- int_of_string right
| "MAX_PLAYERS" -> state.const.max_players <- int_of_string right
| "MAX_TURNS" -> state.const.max_turns <- int_of_string right
| "MAX_TURN_THRESHOLD" -> state.const.max_turn_threshold <- int_of_string right
| "MIN_CELL_PRODUCTION" -> state.const.min_cell_production <- int_of_string right
| "MIN_TURNS" -> state.const.min_turns <- int_of_string right
| "MIN_TURN_THRESHOLD" -> state.const.min_turn_threshold <- int_of_string right
| "MOVE_COST_RATIO" -> state.const.move_cost_ratio <- int_of_string right
| "NEW_ENTITY_ENERGY_COST" -> state.const.new_entity_energy_cost <- int_of_string right
| "PERSISTENCE" -> state.const.persistence <- float_of_string right
| "SHIPS_ABOVE_FOR_CAPTURE" -> state.const.ships_above_for_capture <- int_of_string right
| "STRICT_ERRORS" -> state.const.strict_errors <- bool_of_string right
| "game_seed" -> state.const.game_seed <- int_of_string right
| _ -> ()
;;
let rec process_constants state tokens =
match tokens with
| pair :: tail ->
begin match String.split_on_char ':' pair with
| left :: right :: [] ->
process_constant state left right;
process_constants state tail
| _ -> ()
end
| [] -> ()
;;
let process_json state json =
let preprocess = strip_first_char (strip_last_char json) in
let tokens = String.split_on_char ',' preprocess in
process_constants state tokens;
;;
let new_entity etype pid id row col halite =
{
entity_type = etype;
id = id;
owner = pid;
position = (row, col);
halite = halite;
}
;;
let new_shipyard pid row col =
(new_entity Shipyard pid pid row col 0)
;;
let new_ship pid id row col halite =
(new_entity Ship pid id row col halite)
;;
let new_dropoff pid id row col =
(new_entity Dropoff pid id row col 0)
;;
let rec process_player_data state num_players =
if num_players < 1 then ()
else (
let player_id = get_int_token state in
let player_col = get_int_token state in
let player_row = get_int_token state in
state.shipyards <- (new_shipyard player_id player_row player_col) :: state.shipyards;
process_player_data state (num_players - 1)
)
;;
let parse_map state =
state.map <- Array.init state.height (fun i -> Array.init state.width (fun j -> 0));
for row = 0 to state.height - 1 do
for col = 0 to state.width - 1 do
state.map.(row).(col) <- get_int_token state
done
done
;;
let process_players_and_map state =
state.num_players <- get_int_token state;
state.energy <- Array.init state.num_players (fun _ -> state.const.initial_energy);
state.my_id <- get_int_token state;
process_player_data state state.num_players;
state.width <- get_int_token state;
state.height <- get_int_token state;
parse_map state
;;
let process_initial_tokens state =
let json = String.trim (read_line ()) in
process_json state json;
process_players_and_map state;
;;
let parse_input state =
state.turn <- get_int_token state;
Debug.debug (Printf.sprintf "==== Turn %03d ====\n" state.turn);
state.last_update <- Unix.gettimeofday ();
state.dropoffs <- [];
state.ships <- [];
state.prev_map <- Array.map (Array.map (fun i -> i)) state.map;
for count = 0 to state.num_players - 1 do
let player_id = get_int_token state in
let num_ships = get_int_token state in
let num_dropoffs = get_int_token state in
state.energy.(player_id) <- get_int_token state;
for _ = 0 to num_ships - 1 do
let ship_id = get_int_token state in
let col = get_int_token state in
let row = get_int_token state in
let halite = get_int_token state in
state.ships <- new_ship player_id ship_id row col halite :: state.ships;
done;
for _ = 0 to num_dropoffs - 1 do
let id = get_int_token state in
let col = get_int_token state in
let row = get_int_token state in
state.dropoffs <- new_dropoff player_id id row col :: state.dropoffs;
done
done;
let num_map_updates = get_int_token state in
for _ = 0 to num_map_updates - 1 do
let col = get_int_token state in
let row = get_int_token state in
let halite = get_int_token state in
state.map.(row).(col) <- halite
done
;;
let send_string s =
Debug.debug (Printf.sprintf "Sending %s\n" s);
Printf.printf "%s" s
;;
let done_sending () =
send_string "\n";
flush stdout;
;;
let string_of_command = function
| Generate -> "g"
| Construct ship_id -> "c " ^ (string_of_int ship_id) ^ " "
| Move (ship_id, dir) ->
"m " ^ (string_of_int ship_id) ^ " " ^ begin match dir with
| North -> "n"
| East -> "e"
| South -> "s"
| West -> "w"
| Still -> "o"
end ^ " "
;;
let send_commands commands =
List.iter (fun command ->
send_string (string_of_command command)
) commands;
done_sending ()
;;
open Printf;;
let write_pnm filename data =
let width = Array.length data.(0) in
let height = Array.length data in
let chan = open_out filename in
fprintf chan "P3 %d %d 255\n" width height;
Array.iter
(Array.iter (fun (r, g, b) ->
fprintf chan "%d %d %d\n" r g b;
)) data;
ignore (close_out chan)
;;
open Type;;
let highest_risk risk_array =
Array.fold_left (fun (best_did, best_didnot) (did, didnot) ->
if did > best_did && (best_didnot = 0) then did, didnot
else (
let best_score = float_of_int best_did /. float_of_int best_didnot in
let score = float_of_int did /. float_of_int didnot in
if score > best_score then did, didnot else best_did, best_didnot
)
) (0, 1000) risk_array
;;
let friendly_occupied state position =
List.exists (fun ship -> ship.position = position) state.ships
;;
let crash_dangers state p_ship (row, col) =
let risk_type =
(
if friendly_occupied state (row, col) then
state.persist.crash_risk_occupied
else state.persist.crash_risk_unoccupied
)
in
let dangers = List.fold_left (fun acc e_ship ->
(* conditional is a safeguard, should not be needed with could_inspire *)
if p_ship.entity.owner != e_ship.entity.owner then (
let distance = Game.calculate_distance state (row, col) e_ship.entity.position in
if distance < 2 then e_ship :: acc
else acc
)
else acc
) [] p_ship.could_inspire in
(*
let owners = List.fold_left (fun acc danger ->
if not List.exists (fun owner ->
owner = danger.entity.owner
) acc then danger.entity.owner :: acc else acc
) [] dangers in
List.length owners > 1
|| (
*)
List.map (fun e_ship ->
let risk_array = risk_type.(e_ship.entity.owner) in
let this_did, this_didnot = risk_array.(min state.persist.risk_turns e_ship.crash_risk_wait) in
if this_didnot = 0 then (
e_ship, 1., 1., 0 (* FIXME kind of muddling through here, check this later... *)
)
else (
let highest_did, highest_didnot = highest_risk risk_array in
let highest = float_of_int highest_did /. float_of_int highest_didnot in
let this_score = float_of_int this_did /. float_of_int this_didnot in
e_ship, this_score, highest, this_didnot
)
) dangers
(* ) *)
;;
let crash_likelihood_alert p_ship state (row, col) =
let risk_type =
(
if friendly_occupied state (row, col) then
state.persist.crash_risk_occupied
else state.persist.crash_risk_unoccupied
)
in
let dangers = List.fold_left (fun acc e_ship ->
(* conditional is a safeguard, should not be needed with could_inspire *)
if p_ship.entity.owner != e_ship.entity.owner then (
let distance = Game.calculate_distance state e_ship.entity.position p_ship.entity.position in
if distance < 3 then e_ship :: acc
else acc
)
else acc
) [] p_ship.could_inspire in
List.exists (fun e_ship ->
e_ship.entity.position = (row, col)
) dangers
||
(*
let owners = List.fold_left (fun acc danger ->
if not List.exists (fun owner ->
owner = danger.entity.owner
) acc then danger.entity.owner :: acc else acc
) [] dangers in
List.length owners > 1
|| (
*)
List.exists (fun e_ship ->
let risk_array = risk_type.(e_ship.entity.owner) in
let this_did, this_didnot = risk_array.(min state.persist.risk_turns e_ship.crash_risk_wait) in
this_didnot = 0
|| (
let highest_did, highest_didnot = highest_risk risk_array in
let highest = float_of_int highest_did /. float_of_int highest_didnot in
let this_score = float_of_int this_did /. float_of_int this_didnot in
this_score >= highest *. state.persist.magic.crash_warn_downplay (* FIXME magic *)
)
) dangers
(* ) *)
;;
let get_adjacent grid loc =
let size = (Array.length grid, Array.length grid.(0)) in
List.map (fun offset ->
Game.wrap_grid_position size (Game.add_position loc offset)
) Game.list_move_offsets
;;
let simple_bfs_visit dist loc next q =
let p_row, p_col = loc in
List.iter (fun (n_row, n_col) ->
if dist.(n_row).(n_col) = max_int then (
dist.(n_row).(n_col) <- dist.(p_row).(p_col) + 1;
Queue.push (n_row, n_col) q;
)
) next
;;
let avoid_visit dist avoid loc next q =
let p_row, p_col = loc in
List.iter (fun (n_row, n_col) ->
if dist.(n_row).(n_col) = max_int && (not (List.exists (fun (tr, tc) -> tr = n_row && (tc = n_col)) avoid)) then (
dist.(n_row).(n_col) <- dist.(p_row).(p_col) + 1;
Queue.push (n_row, n_col) q;
)
) next
;;
let voronoi_visit dist owned loc next q =
let p_row, p_col = loc in
List.iter (fun (n_row, n_col) ->
if dist.(n_row).(n_col) = max_int then (
dist.(n_row).(n_col) <- dist.(p_row).(p_col) +