Finish implementing LAID
This commit is contained in:
parent
ea897b0292
commit
23b507dc4c
@ -2,6 +2,7 @@ defmodule Diamondtail.Application do
|
|||||||
# See https://hexdocs.pm/elixir/Application.html
|
# See https://hexdocs.pm/elixir/Application.html
|
||||||
# for more information on OTP Applications
|
# for more information on OTP Applications
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
alias Diamondtail.InternalLatencyManager
|
||||||
alias Diamondtail.ExternalLatencyManager
|
alias Diamondtail.ExternalLatencyManager
|
||||||
alias Diamondtail.Genometracker
|
alias Diamondtail.Genometracker
|
||||||
alias Diamondtail.Population
|
alias Diamondtail.Population
|
||||||
@ -16,7 +17,7 @@ defmodule Diamondtail.Application do
|
|||||||
{Plug.Cowboy, scheme: :http, plug: Diamondtail.Router, options: [port: 4001]},
|
{Plug.Cowboy, scheme: :http, plug: Diamondtail.Router, options: [port: 4001]},
|
||||||
{Task.Supervisor, name: Diamondtail.TaskSupervisor},
|
{Task.Supervisor, name: Diamondtail.TaskSupervisor},
|
||||||
{Population, 1..10 |> Enum.map(fn _ -> Population.Genome.random end) |> Enum.to_list},
|
{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
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
defmodule Diamondtail.Brain do
|
defmodule Diamondtail.Brain do
|
||||||
import Diamondtail.Operators.Implication
|
|
||||||
alias Diamondtail.InternalLatencyManager
|
alias Diamondtail.InternalLatencyManager
|
||||||
alias Diamondtail.ExternalLatencyManager
|
alias Diamondtail.ExternalLatencyManager
|
||||||
alias Diamondtail.Population.Genome
|
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()}
|
@spec determine_move(Genome.t(), map(), map(), map()) :: %{move: :up | :down | :left | :right, shout: String.t()}
|
||||||
def determine_move(genome, board, self, game) do
|
def determine_move(genome, board, self, game) do
|
||||||
|
start_time = Time.utc_now()
|
||||||
base_state = GameState.from(board, self)
|
base_state = GameState.from(board, self)
|
||||||
external_latency = ExternalLatencyManager.get_external_latency()
|
external_latency = ExternalLatencyManager.get_external_latency()
|
||||||
max_latency = game["timeout"]
|
max_latency = game["timeout"]
|
||||||
target_latency = max_latency - external_latency - genome.latency_headroom
|
target_latency = max_latency - external_latency - genome.latency_headroom
|
||||||
|
|
||||||
depth = InternalLatencyManager.best_depth(map_size(base_state.snakes), target_latency)
|
depth = InternalLatencyManager.best_depth(map_size(base_state.snakes), target_latency)
|
||||||
|
Logger.debug("Using a depth of #{depth}")
|
||||||
|
|
||||||
options = GameState.valid_actions(base_state) |>
|
options = GameState.valid_actions(base_state) |>
|
||||||
Task.async_stream(fn action ->
|
Task.async_stream(fn action ->
|
||||||
qstate = GameState.apply_actions(base_state, action)
|
qstate = GameState.apply_actions(base_state, action)
|
||||||
{action, GameState.evaluate_state(genome, qstate, depth)}
|
{action, GameState.evaluate_state(genome, qstate, depth)}
|
||||||
end, ordered: false)
|
end, ordered: false, timeout: :infinity)
|
||||||
|
|
||||||
if Enum.empty?(options) do
|
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,
|
move: :left,
|
||||||
shout: "I'm dead, good game!"
|
shout: "I'm dead, good game!"
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{:ok, {action, score}} = Enum.max_by(options, fn {:ok, {_, s}} -> s end)
|
{: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,
|
move: action,
|
||||||
|
|||||||
@ -22,12 +22,17 @@ defmodule Diamondtail.ExternalLatencyManager do
|
|||||||
|
|
||||||
def get_external_latency() do
|
def get_external_latency() do
|
||||||
{tot, dp} = Agent.get(__MODULE__, fn {tot, dp, _} -> {tot, dp} end)
|
{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
|
end
|
||||||
|
|
||||||
def clear_dangling_moves(predicate \\ fn -> true end) do
|
def clear_dangling_moves(predicate \\ fn -> true end) do
|
||||||
Agent.update(__MODULE__, fn {tot_latency, tot_datapoints, dangling_moves} ->
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,21 +1,46 @@
|
|||||||
defmodule Diamondtail.InternalLatencyManager do
|
defmodule Diamondtail.InternalLatencyManager do
|
||||||
use Agent
|
use Agent
|
||||||
|
require Logger
|
||||||
|
|
||||||
def start_link(_) do
|
def start_link(_) do
|
||||||
Agent.start_link(fn -> %{} end, name: __MODULE__)
|
Agent.start_link(fn -> %{} end, name: __MODULE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
def best_depth(player_count, max_latency) do
|
def best_depth(player_count, max_latency) do
|
||||||
Agent.get(__MODULE__, fn records ->
|
Logger.debug("Looking for the best depth for #{player_count} players within #{max_latency}ms")
|
||||||
if Map.has_key?(records, player_count) do
|
records = Agent.get(__MODULE__, &(&1))
|
||||||
|
if Map.has_key?(records, player_count) do
|
||||||
# Find the highest depth value matching our max latency
|
# Find the highest depth value matching our max latency
|
||||||
# TODO
|
Enum.reduce_while(records[player_count], 1, fn {tot, reg}, acc ->
|
||||||
3
|
# TODO: "learning"
|
||||||
|
if (tot / reg) > max_latency do
|
||||||
|
{:halt, acc - 2}
|
||||||
|
else
|
||||||
|
{:cont, acc + 2}
|
||||||
|
end
|
||||||
|
end)
|
||||||
else
|
else
|
||||||
# We've never seen this player count before, we'll learn how fast we are on this player count after this is done
|
# 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
|
# For now we'll return 1 so we don't die
|
||||||
1
|
1
|
||||||
end
|
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_latencies(player_count) do
|
||||||
|
Agent.get(__MODULE__, &(&1[player_count]))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -77,7 +77,7 @@ defmodule Diamondtail.Population do
|
|||||||
|
|
||||||
own_accessible_tiles_weight: random_between(0.7, 1),
|
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
|
value_discount: random_between(1.0, 20.0) # as percentage
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -52,7 +52,9 @@ defmodule Diamondtail.Router do
|
|||||||
self = conn.params["you"]
|
self = conn.params["you"]
|
||||||
Logger.debug("Processing move #{game_id} as #{player_id} turn #{turn_number}")
|
Logger.debug("Processing move #{game_id} as #{player_id} turn #{turn_number}")
|
||||||
t_start = Time.utc_now()
|
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)
|
internal_latency = Time.diff(t_start, Time.utc_now(), :millisecond)
|
||||||
ExternalLatencyManager.report_internal_latency({game_id, player_id, turn_number}, internal_latency)
|
ExternalLatencyManager.report_internal_latency({game_id, player_id, turn_number}, internal_latency)
|
||||||
Logger.debug("Our move: #{inspect(move)}")
|
Logger.debug("Our move: #{inspect(move)}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user