add stuff, try to fix bad behaviour

This commit is contained in:
Kodi Craft 2025-11-10 09:38:58 +01:00
parent c213507aec
commit c1fbe9cd80
Signed by: kodi
GPG Key ID: 69D9EED60B242822
2 changed files with 53 additions and 18 deletions

View File

@ -55,7 +55,6 @@ defmodule Diamondtail.Brain do
|> Map.update(:snakes, nil, fn snakes ->
Map.update(snakes, state.self, nil, &apply_action_to_snake(&1, move))
end)
# |> resolve_state()
end
def apply_actions(%GameState{type: :q} = state, moves) do
@ -84,17 +83,22 @@ defmodule Diamondtail.Brain do
end
end
def valid_actions(%GameState{type: :q, snakes: snakes, self: self}) do
def valid_actions(%GameState{type: :q, snakes: snakes, self: self} = state) do
non_self_snakes = Map.delete(snakes, self)
for i <- 0..(4 ** map_size(non_self_snakes)) - 1 do
Enum.with_index(non_self_snakes)
|> Enum.map(fn {{id, _}, j} ->
{id, Enum.at([:up, :down, :left, :right], rem(floor(i / (4 ** j)), 4))}
end)
|> Enum.filter(fn {id, action} ->
result = apply_actions(state, %{id => action})
!snake_dies?(result, id)
end)
|> Map.new()
end
end
@spec evaluate_state(Genome.t(), GameState.t(), non_neg_integer(), float() | nil, float() | nil) :: float()
def evaluate_state(genome, state, depth, alpha \\ Float.min_finite, beta \\ Float.max_finite)
def evaluate_state(genome, %GameState{type: :v} = state, 0, _, _) do
# If we're dead, the result is always super duper negative
@ -104,11 +108,14 @@ defmodule Diamondtail.Brain do
self = state.snakes[state.self]
Enum.count(enemies) * genome.enemy_alive_weight +
Enum.count(enemies, &(&1.length >= self.length)) * genome.enemy_longer_weight +
self.health * genome.own_health_weight +
self.length * genome.own_length_weight +
length(available_tiles_from(state, self.head)) * genome.own_accessible_tiles_weight +
Enum.sum_by(enemies, &(&1.health)) * genome.enemy_health_weight +
(Enum.sum_by(enemies, &(&1.length)) / (Enum.count(enemies) + 1)) * genome.enemy_length_weight +
(Enum.map(enemies, &(abs(&1.head.x - self.head.x) + abs(&1.head.y - self.head.y))) |> Enum.min(&<=/2, fn -> 0 end)) * genome.enemy_head_distance_weight +
(Enum.flat_map(enemies, &(if &1.length >= self.length, do: [abs(&1.head.x - self.head.x) + abs(&1.head.y - self.head.y)], else: [])) |> Enum.min(&<=/2, fn -> 0 end)) * genome.longer_enemy_head_distance_weight +
(Enum.flat_map(enemies, &(if &1.length < self.length, do: [abs(&1.head.x - self.head.x) + abs(&1.head.y - self.head.y)], else: [])) |> Enum.min(&<=/2, fn -> 0 end)) * genome.shorter_enemy_head_distance_weight +
(Enum.map(food, &(abs(&1.x - self.head.x) + abs(&1.y - self.head.y))) |> Enum.min(&<=/2, fn -> 0 end)) * genome.food_distance_weight
else
Float.min_finite()
@ -130,9 +137,10 @@ defmodule Diamondtail.Brain do
valid_actions = valid_actions(state)
if valid_actions == [] do
# Terminal state
# We only win if this is a Q state AND we're still alive, this might still be a draw
case state.type do
:v -> Float.min_finite()
:q -> Float.max_finite()
:q -> if snake_dies?(state, state.self), do: IO.inspect(Float.min_finite()), else: Float.max_finite()
end
else
{score, _alpha, _beta} = valid_actions(state)
@ -188,6 +196,9 @@ defmodule Diamondtail.Brain do
defp snake_dies?(state, snake_id) do
snake = state.snakes[snake_id]
if snake == nil do
true
else
# Die if health is less than or equal to 0
snake.health <= 0 ||
# Die if head is out of bounds
@ -196,7 +207,8 @@ defmodule Diamondtail.Brain do
# Die if head is in a tail
Enum.any?(state.snakes, fn {_, %{body: body}} -> Enum.member?(body, snake.head) end) ||
# Die if head is on head of a DIFFERENT snake of equal or greater length
Enum.any?(state.snakes, fn {id, %{head: h, length: l}} -> snake.head == h && snake.length <= l && id != snake_id end)
Enum.any?(state.snakes, fn {id, %{head: h, length: l}} -> snake.head == h && l >= snake.length && id != snake_id end)
end
end
defp apply_action_to_snake(snake, move) do
@ -211,6 +223,22 @@ defmodule Diamondtail.Brain do
|> Map.update(:body, nil, fn body -> List.delete_at(body, 0) ++ [snake.head] end)
|> Map.update(:head, nil, fn %{x: x, y: y} -> %{x: x + delta.x, y: y + delta.y} end)
end
def available_tiles_from(state, %{x: x, y: y} = base_pos, explored \\ []) do
Enum.reduce([%{x: x + 1, y: y}, %{x: x - 1, y: y}, %{x: x, y: y + 1}, %{x: x, y: y - 1}], [base_pos | explored], fn pos, acc ->
if Enum.member?(acc, pos) || in_solid_or_out_of_bounds?(state, pos) do
acc
else
available_tiles_from(state, pos, acc)
end
end)
end
defp in_solid_or_out_of_bounds?(state, %{x: x, y: y} = pos) do
x >= state.width || y >= state.height ||
x < 0 || y < 0 ||
Enum.any?(state.snakes, fn {_, %{body: b, head: h}} -> h == pos || Enum.member?(b, pos) end)
end
end
@spec determine_move(Genome.t(), map(), map()) :: %{move: :up | :down | :left | :right, shout: String.t()}

View File

@ -15,12 +15,15 @@ defmodule Diamondtail.Population do
field :mutation_max_amplitude, float()
field :enemy_alive_weight, float()
field :enemy_longer_weight, float()
field :own_health_weight, float()
field :own_length_weight, float()
field :enemy_health_weight, float()
field :enemy_length_weight, float()
field :enemy_head_distance_weight, float()
field :longer_enemy_head_distance_weight, float()
field :shorter_enemy_head_distance_weight, float()
field :food_distance_weight, float()
field :own_accessible_tiles_weight, float()
field :search_depth, float()
field :value_discount, float()
@ -61,17 +64,21 @@ defmodule Diamondtail.Population do
mutation_chance: random_between(0.1, 0.2),
mutation_max_amplitude: random_between(0.2, 0.05),
enemy_alive_weight: random_between(-100.0, -200.0),
enemy_longer_weight: random_between(-50.0, -30.0), # Try to stay longer than our enemies to avoid dying to head-to-head collisions
own_health_weight: random_between(0.7, 1.0),
own_length_weight: random_between(5, 10),
enemy_health_weight: random_between(-0.8, -0.4), # Summed
enemy_length_weight: random_between(-10, -5), # Averaged
# both of the following are compared to the nearest object, to avoid overrewarding when there are more enemies/foods
enemy_head_distance_weight: random_between(1, 2), # maximize distance, reward staying away
food_distance_weight: random_between(-2, -1), # minimize distance, reward staying near
longer_enemy_head_distance_weight: random_between(0.3, 1), # maximize distance, reward staying away
shorter_enemy_head_distance_weight: random_between(-1, -0.3), # minimize distance, reward staying near to try to go in for kills
food_distance_weight: random_between(-0.5, -0.1), # minimize distance, reward staying near, don't just stick next to food though
search_depth: random_between(3.0, 7.0),
value_discount: random_between(1.0, 10.0) # as percentage
own_accessible_tiles_weight: random_between(0.7, 1),
search_depth: random_between(3.0, 5.0),
value_discount: random_between(1.0, 20.0) # as percentage
}
end