Begin implementing LAID
This commit is contained in:
parent
c1fbe9cd80
commit
ea897b0292
@ -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.ExternalLatencyManager
|
||||||
alias Diamondtail.Genometracker
|
alias Diamondtail.Genometracker
|
||||||
alias Diamondtail.Population
|
alias Diamondtail.Population
|
||||||
|
|
||||||
@ -15,7 +16,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
|
Genometracker, ExternalLatencyManager
|
||||||
]
|
]
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
defmodule Diamondtail.Brain do
|
defmodule Diamondtail.Brain do
|
||||||
|
import Diamondtail.Operators.Implication
|
||||||
|
alias Diamondtail.InternalLatencyManager
|
||||||
|
alias Diamondtail.ExternalLatencyManager
|
||||||
alias Diamondtail.Population.Genome
|
alias Diamondtail.Population.Genome
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@ -83,17 +86,13 @@ defmodule Diamondtail.Brain do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_actions(%GameState{type: :q, snakes: snakes, self: self} = state) do
|
def valid_actions(%GameState{type: :q, snakes: snakes, self: self}) do
|
||||||
non_self_snakes = Map.delete(snakes, self)
|
non_self_snakes = Map.delete(snakes, self)
|
||||||
for i <- 0..(4 ** map_size(non_self_snakes)) - 1 do
|
for i <- 0..(4 ** map_size(non_self_snakes)) - 1 do
|
||||||
Enum.with_index(non_self_snakes)
|
Enum.with_index(non_self_snakes)
|
||||||
|> Enum.map(fn {{id, _}, j} ->
|
|> Enum.map(fn {{id, _}, j} ->
|
||||||
{id, Enum.at([:up, :down, :left, :right], rem(floor(i / (4 ** j)), 4))}
|
{id, Enum.at([:up, :down, :left, :right], rem(floor(i / (4 ** j)), 4))}
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(fn {id, action} ->
|
|
||||||
result = apply_actions(state, %{id => action})
|
|
||||||
!snake_dies?(result, id)
|
|
||||||
end)
|
|
||||||
|> Map.new()
|
|> Map.new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -140,7 +139,7 @@ defmodule Diamondtail.Brain do
|
|||||||
# We only win if this is a Q state AND we're still alive, this might still be a draw
|
# We only win if this is a Q state AND we're still alive, this might still be a draw
|
||||||
case state.type do
|
case state.type do
|
||||||
:v -> Float.min_finite()
|
:v -> Float.min_finite()
|
||||||
:q -> if snake_dies?(state, state.self), do: IO.inspect(Float.min_finite()), else: Float.max_finite()
|
:q -> if snake_dies?(state, state.self), do: Float.min_finite(), else: Float.max_finite()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
{score, _alpha, _beta} = valid_actions(state)
|
{score, _alpha, _beta} = valid_actions(state)
|
||||||
@ -241,13 +240,19 @@ defmodule Diamondtail.Brain do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec determine_move(Genome.t(), 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) do
|
def determine_move(genome, board, self, game) do
|
||||||
base_state = GameState.from(board, self)
|
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)
|
||||||
|
|
||||||
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, ceil(genome.search_depth))}
|
{action, GameState.evaluate_state(genome, qstate, depth)}
|
||||||
end, ordered: false)
|
end, ordered: false)
|
||||||
|
|
||||||
if Enum.empty?(options) do
|
if Enum.empty?(options) do
|
||||||
|
|||||||
33
lib/diamondtail/externallatencymanager.ex
Normal file
33
lib/diamondtail/externallatencymanager.ex
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
defmodule Diamondtail.ExternalLatencyManager do
|
||||||
|
use Agent
|
||||||
|
|
||||||
|
def start_link(_) do
|
||||||
|
Agent.start_link(fn -> {0, 0, %{}} end, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_internal_latency(move, latency) do
|
||||||
|
Agent.update(__MODULE__, fn {tot_latency, tot_datapoints, dangling_moves} ->
|
||||||
|
{tot_latency, tot_datapoints, Map.put(dangling_moves, move, latency)}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_total_latency(move, latency) do
|
||||||
|
Agent.update(__MODULE__, fn {tot_latency, tot_datapoints, dangling_moves} ->
|
||||||
|
case Map.pop(dangling_moves, move) do
|
||||||
|
{nil, dangling_moves} -> {tot_latency, tot_datapoints, dangling_moves}
|
||||||
|
{internal_latency, dangling_moves} -> {tot_latency + (latency - internal_latency), tot_datapoints + 1, dangling_moves}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_external_latency() do
|
||||||
|
{tot, dp} = Agent.get(__MODULE__, fn {tot, dp, _} -> {tot, dp} end)
|
||||||
|
tot / dp
|
||||||
|
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)}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
21
lib/diamondtail/internallatencymanager.ex
Normal file
21
lib/diamondtail/internallatencymanager.ex
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
defmodule Diamondtail.InternalLatencyManager do
|
||||||
|
use Agent
|
||||||
|
|
||||||
|
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
|
||||||
|
# Find the highest depth value matching our max latency
|
||||||
|
# TODO
|
||||||
|
3
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -25,7 +25,7 @@ defmodule Diamondtail.Population do
|
|||||||
field :food_distance_weight, float()
|
field :food_distance_weight, float()
|
||||||
field :own_accessible_tiles_weight, float()
|
field :own_accessible_tiles_weight, float()
|
||||||
|
|
||||||
field :search_depth, float()
|
field :latency_headroom, float()
|
||||||
field :value_discount, float()
|
field :value_discount, float()
|
||||||
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),
|
||||||
|
|
||||||
search_depth: random_between(3.0, 5.0),
|
latency_headroom: random_between(20.0, 50.0),
|
||||||
value_discount: random_between(1.0, 20.0) # as percentage
|
value_discount: random_between(1.0, 20.0) # as percentage
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
defmodule Diamondtail.Router do
|
defmodule Diamondtail.Router do
|
||||||
require Logger
|
require Logger
|
||||||
|
alias Diamondtail.ExternalLatencyManager
|
||||||
alias Diamondtail.Population
|
alias Diamondtail.Population
|
||||||
alias Diamondtail.Brain
|
alias Diamondtail.Brain
|
||||||
alias Diamondtail.Genometracker
|
alias Diamondtail.Genometracker
|
||||||
@ -38,11 +39,22 @@ defmodule Diamondtail.Router do
|
|||||||
post "/move" do
|
post "/move" do
|
||||||
game_id = conn.params["game"]["id"]
|
game_id = conn.params["game"]["id"]
|
||||||
player_id = conn.params["you"]["id"]
|
player_id = conn.params["you"]["id"]
|
||||||
|
turn_number = conn.params["turn"]
|
||||||
|
|
||||||
|
player_latency = conn.params["you"]["latency"]
|
||||||
|
if player_latency < conn.params["game"]["latency"] do
|
||||||
|
ExternalLatencyManager.report_total_latency({game_id, player_id, turn_number - 1}, player_latency)
|
||||||
|
end
|
||||||
|
|
||||||
genome = Genometracker.get_genome({game_id, player_id})
|
genome = Genometracker.get_genome({game_id, player_id})
|
||||||
board = conn.params["board"]
|
board = conn.params["board"]
|
||||||
|
game = conn.params["game"]
|
||||||
self = conn.params["you"]
|
self = conn.params["you"]
|
||||||
Logger.debug("Processing move #{game_id} as #{player_id} turn #{conn.params["turn"]}")
|
Logger.debug("Processing move #{game_id} as #{player_id} turn #{turn_number}")
|
||||||
move = Brain.determine_move(genome, board, self)
|
t_start = Time.utc_now()
|
||||||
|
move = Brain.determine_move(genome, board, self, game)
|
||||||
|
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)}")
|
Logger.debug("Our move: #{inspect(move)}")
|
||||||
put_resp_content_type(conn, "application/json")
|
put_resp_content_type(conn, "application/json")
|
||||||
|> send_resp(200, JSON.encode!(move))
|
|> send_resp(200, JSON.encode!(move))
|
||||||
@ -51,8 +63,17 @@ defmodule Diamondtail.Router do
|
|||||||
post "/end" do
|
post "/end" do
|
||||||
game_id = conn.params["game"]["id"]
|
game_id = conn.params["game"]["id"]
|
||||||
player_id = conn.params["you"]["id"]
|
player_id = conn.params["you"]["id"]
|
||||||
|
turn_number = conn.params["turn"]
|
||||||
|
|
||||||
|
player_latency = conn.params["you"]["latency"]
|
||||||
|
if player_latency < conn.params["game"]["latency"] do
|
||||||
|
ExternalLatencyManager.report_total_latency({game_id, player_id, turn_number - 1}, player_latency)
|
||||||
|
end
|
||||||
|
# Don't leave any unresolved moves gathering dust in the latency manager
|
||||||
|
ExternalLatencyManager.clear_dangling_moves(fn {game, player, _} -> game == game_id && player == player_id end)
|
||||||
|
|
||||||
victory? = conn.params["board"]["snakes"] |> Enum.any?(fn %{"id" => snake_id} -> snake_id == player_id end)
|
victory? = conn.params["board"]["snakes"] |> Enum.any?(fn %{"id" => snake_id} -> snake_id == player_id end)
|
||||||
Logger.info("Ending game '#{game_id}' as '#{player_id}' after #{conn.params["turn"]} turns. #{if victory?, do: "We won!", else: "We lost."}")
|
Logger.info("Ending game '#{game_id}' as '#{player_id}' after #{turn_number} turns. #{if victory?, do: "We won!", else: "We lost."}")
|
||||||
Genometracker.game_end({game_id, player_id}, victory?)
|
Genometracker.game_end({game_id, player_id}, victory?)
|
||||||
Logger.info("#{Population.size} genomes remain in the population.")
|
Logger.info("#{Population.size} genomes remain in the population.")
|
||||||
Logger.debug("#{inspect(Population.avg)}")
|
Logger.debug("#{inspect(Population.avg)}")
|
||||||
|
|||||||
8
lib/operators.ex
Normal file
8
lib/operators.ex
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
defmodule Diamondtail.Operators do
|
||||||
|
defmodule Implication do
|
||||||
|
@moduledoc """
|
||||||
|
Implements logical implication, A ~> B is true if B is true or A is false
|
||||||
|
"""
|
||||||
|
def a ~> b, do: !a || b
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user