diff --git a/lib/diamondtail/application.ex b/lib/diamondtail/application.ex index 52306ae..801acad 100644 --- a/lib/diamondtail/application.ex +++ b/lib/diamondtail/application.ex @@ -2,6 +2,7 @@ defmodule Diamondtail.Application do # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @moduledoc false + alias Diamondtail.InternalLatencyManager alias Diamondtail.ExternalLatencyManager alias Diamondtail.Genometracker alias Diamondtail.Population @@ -16,7 +17,7 @@ defmodule Diamondtail.Application do {Plug.Cowboy, scheme: :http, plug: Diamondtail.Router, options: [port: 4001]}, {Task.Supervisor, name: Diamondtail.TaskSupervisor}, {Population, 1..10 |> Enum.map(fn _ -> Population.Genome.random end) |> Enum.to_list}, - Genometracker, ExternalLatencyManager + Genometracker, ExternalLatencyManager, InternalLatencyManager ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/diamondtail/brain.ex b/lib/diamondtail/brain.ex index 06d14be..ab34618 100644 --- a/lib/diamondtail/brain.ex +++ b/lib/diamondtail/brain.ex @@ -1,5 +1,4 @@ defmodule Diamondtail.Brain do - import Diamondtail.Operators.Implication alias Diamondtail.InternalLatencyManager alias Diamondtail.ExternalLatencyManager alias Diamondtail.Population.Genome @@ -242,26 +241,32 @@ defmodule Diamondtail.Brain do @spec determine_move(Genome.t(), map(), map(), map()) :: %{move: :up | :down | :left | :right, shout: String.t()} def determine_move(genome, board, self, game) do + start_time = Time.utc_now() base_state = GameState.from(board, self) external_latency = ExternalLatencyManager.get_external_latency() max_latency = game["timeout"] target_latency = max_latency - external_latency - genome.latency_headroom depth = InternalLatencyManager.best_depth(map_size(base_state.snakes), target_latency) + Logger.debug("Using a depth of #{depth}") options = GameState.valid_actions(base_state) |> Task.async_stream(fn action -> qstate = GameState.apply_actions(base_state, action) {action, GameState.evaluate_state(genome, qstate, depth)} - end, ordered: false) + end, ordered: false, timeout: :infinity) if Enum.empty?(options) do + end_time = Time.utc_now() + InternalLatencyManager.register_latency(map_size(base_state.snakes), depth, Time.diff(end_time, start_time, :millisecond)) %{ move: :left, shout: "I'm dead, good game!" } else {:ok, {action, score}} = Enum.max_by(options, fn {:ok, {_, s}} -> s end) + end_time = Time.utc_now() + InternalLatencyManager.register_latency(map_size(base_state.snakes), depth, Time.diff(end_time, start_time, :millisecond)) %{ move: action, diff --git a/lib/diamondtail/externallatencymanager.ex b/lib/diamondtail/externallatencymanager.ex index e86d6aa..ae07cff 100644 --- a/lib/diamondtail/externallatencymanager.ex +++ b/lib/diamondtail/externallatencymanager.ex @@ -22,12 +22,17 @@ defmodule Diamondtail.ExternalLatencyManager do def get_external_latency() do {tot, dp} = Agent.get(__MODULE__, fn {tot, dp, _} -> {tot, dp} end) - tot / dp + if dp == 0 do + # Some guesstimate + 100 + else + tot / dp + end end def clear_dangling_moves(predicate \\ fn -> true end) do Agent.update(__MODULE__, fn {tot_latency, tot_datapoints, dangling_moves} -> - {tot_latency, tot_datapoints, Map.filter(dangling_moves, fn {k, _} -> !predicate.(k) end)} + {tot_latency, tot_datapoints, Map.reject(dangling_moves, fn {k, _} -> predicate.(k) end)} end) end end diff --git a/lib/diamondtail/internallatencymanager.ex b/lib/diamondtail/internallatencymanager.ex index 1c46f7e..591c0df 100644 --- a/lib/diamondtail/internallatencymanager.ex +++ b/lib/diamondtail/internallatencymanager.ex @@ -1,21 +1,46 @@ defmodule Diamondtail.InternalLatencyManager do use Agent + require Logger def start_link(_) do Agent.start_link(fn -> %{} end, name: __MODULE__) end def best_depth(player_count, max_latency) do - Agent.get(__MODULE__, fn records -> - if Map.has_key?(records, player_count) do + Logger.debug("Looking for the best depth for #{player_count} players within #{max_latency}ms") + records = Agent.get(__MODULE__, &(&1)) + if Map.has_key?(records, player_count) do # Find the highest depth value matching our max latency - # TODO - 3 + Enum.reduce_while(records[player_count], 1, fn {tot, reg}, acc -> + # TODO: "learning" + if (tot / reg) > max_latency do + {:halt, acc - 2} + else + {:cont, acc + 2} + end + end) else # We've never seen this player count before, we'll learn how fast we are on this player count after this is done # For now we'll return 1 so we don't die 1 end + end + + def register_latency(player_count, depth, internal_latency) do + Logger.debug("Registering that a search of depth #{depth} with #{player_count} players took #{internal_latency}ms") + Agent.update(__MODULE__, fn records -> + Map.update(records, player_count, [], fn list -> + index = floor((depth - 1) / 2) # Floor should be avoidable but we do it anyway to be sure + if index >= length(list) do + list ++ [{internal_latency, 1}] + else + List.update_at(list, index, fn {tot, reg} -> {tot + internal_latency, reg + 1} end) + end + end) end) end + + def get_latencies(player_count) do + Agent.get(__MODULE__, &(&1[player_count])) + end end diff --git a/lib/diamondtail/population.ex b/lib/diamondtail/population.ex index 326c39f..7c40c2f 100644 --- a/lib/diamondtail/population.ex +++ b/lib/diamondtail/population.ex @@ -77,7 +77,7 @@ defmodule Diamondtail.Population do own_accessible_tiles_weight: random_between(0.7, 1), - latency_headroom: random_between(20.0, 50.0), + latency_headroom: random_between(80.0, 100.0), value_discount: random_between(1.0, 20.0) # as percentage } end diff --git a/lib/diamondtail/router.ex b/lib/diamondtail/router.ex index 5091a07..06302a8 100644 --- a/lib/diamondtail/router.ex +++ b/lib/diamondtail/router.ex @@ -52,7 +52,9 @@ defmodule Diamondtail.Router do self = conn.params["you"] Logger.debug("Processing move #{game_id} as #{player_id} turn #{turn_number}") t_start = Time.utc_now() - move = Brain.determine_move(genome, board, self, game) + # nolink so internal latency is still reported + move_task = Task.Supervisor.async_nolink(Diamondtail.TaskSupervisor, fn -> Brain.determine_move(genome, board, self, game) end) + move = Task.await(move_task) internal_latency = Time.diff(t_start, Time.utc_now(), :millisecond) ExternalLatencyManager.report_internal_latency({game_id, player_id, turn_number}, internal_latency) Logger.debug("Our move: #{inspect(move)}")