From c213507aecf27c4239b5c11dbac26c246d69a44a Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Sun, 9 Nov 2025 14:32:59 +0100 Subject: [PATCH] some tweaks --- lib/diamondtail/brain.ex | 62 ++++++++++++++++++++++------------- lib/diamondtail/population.ex | 8 +++-- lib/diamondtail/router.ex | 2 +- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/lib/diamondtail/brain.ex b/lib/diamondtail/brain.ex index 3ce06bd..fbd46db 100644 --- a/lib/diamondtail/brain.ex +++ b/lib/diamondtail/brain.ex @@ -68,14 +68,17 @@ defmodule Diamondtail.Brain do else {id, s} end - end) + end) |> Map.new() end) |> resolve_state() end - def valid_actions(%GameState{type: :v, snakes: snakes, self: self}) do + def valid_actions(%GameState{type: :v, snakes: snakes, self: self} = state) do if Map.has_key?(snakes, self) do - [:up, :down, :left, :right] + [:up, :down, :left, :right] |> Enum.filter(fn action -> + result = apply_actions(state, action) + !snake_dies?(result, self) + end) else [] end @@ -102,7 +105,9 @@ defmodule Diamondtail.Brain do Enum.count(enemies) * genome.enemy_alive_weight + self.health * genome.own_health_weight + + self.length * genome.own_length_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.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 @@ -124,10 +129,10 @@ defmodule Diamondtail.Brain do valid_actions = valid_actions(state) if valid_actions == [] do - if Map.has_key?(state.snakes, state.self) do - Float.max_finite() - else - Float.min_finite() + # Terminal state + case state.type do + :v -> Float.min_finite() + :q -> Float.max_finite() end else {score, _alpha, _beta} = valid_actions(state) @@ -174,20 +179,26 @@ defmodule Diamondtail.Brain do end) # Eliminate snakes |> Map.update(:snakes, nil, fn snakes -> - Enum.filter(snakes, fn {snake_id, snake} -> - # Die if health is less than or equal to 0 - snake.health > 0 && - # Die if head is out of bounds - snake.head.x >= 0 && snake.head.x < state.width && - snake.head.y >= 0 && snake.head.y < state.height && - # 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.filter(snakes, fn {snake_id, _snake} -> + !snake_dies?(state, snake_id) end) |> Map.new() end) end + defp snake_dies?(state, snake_id) do + snake = state.snakes[snake_id] + + # Die if health is less than or equal to 0 + snake.health <= 0 || + # Die if head is out of bounds + snake.head.x < 0 || snake.head.x >= state.width || + snake.head.y < 0 || snake.head.y >= state.height || + # 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) + end + defp apply_action_to_snake(snake, move) do delta = case move do :up -> %{x: 0, y: 1} @@ -211,11 +222,18 @@ defmodule Diamondtail.Brain do {action, GameState.evaluate_state(genome, qstate, ceil(genome.search_depth))} end, ordered: false) - {:ok, {action, score}} = Enum.max_by(options, fn {:ok, {_, s}} -> s end) + if Enum.empty?(options) do + %{ + move: :left, + shout: "I'm dead, good game!" + } + else + {:ok, {action, score}} = Enum.max_by(options, fn {:ok, {_, s}} -> s end) - %{ - move: action, - shout: "#{inspect(options |> Enum.map(fn {:ok, v} -> v end) |> Enum.to_list)} (#{score})" - } + %{ + move: action, + shout: "#{inspect(options |> Enum.map(fn {:ok, v} -> v end) |> Enum.to_list)} (#{score})" + } + end end end diff --git a/lib/diamondtail/population.ex b/lib/diamondtail/population.ex index 942435f..7cca268 100644 --- a/lib/diamondtail/population.ex +++ b/lib/diamondtail/population.ex @@ -16,7 +16,9 @@ defmodule Diamondtail.Population do field :enemy_alive_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 :food_distance_weight, float() @@ -57,10 +59,12 @@ defmodule Diamondtail.Population do def random() do %Genome{ mutation_chance: random_between(0.1, 0.2), - mutation_max_amplitude: random_between(0.1, 0.05), + mutation_max_amplitude: random_between(0.2, 0.05), enemy_alive_weight: random_between(-100.0, -200.0), own_health_weight: random_between(0.7, 1.0), - enemy_health_weight: random_between(-0.8, -0.4), + 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 diff --git a/lib/diamondtail/router.ex b/lib/diamondtail/router.ex index 150a35c..ef65ac0 100644 --- a/lib/diamondtail/router.ex +++ b/lib/diamondtail/router.ex @@ -18,7 +18,7 @@ defmodule Diamondtail.Router do |> send_resp(200, JSON.encode!(%{ apiversion: "1", author: "kodicraft", - color: "#8cd9ff", + color: "#4684ff", head: "trans-rights-scarf", tail: "hook", version: Mix.Project.config()[:version]