diff --git a/lib/alternis/dictionary_engine.ex b/lib/alternis/dictionary_engine.ex index 6832a75..dd332db 100644 --- a/lib/alternis/dictionary_engine.ex +++ b/lib/alternis/dictionary_engine.ex @@ -7,7 +7,7 @@ defmodule Alternis.Engines.DictionaryEngine do @implementation Application.compile_env!(:alternis, :dictionary_engine) def impl, do: @implementation - @callback find_word(word :: String.t(), GameLanguage.t()) :: Word.t() | nil - @callback secret(GameLanguage.t()) :: String.t() | nil - @callback secret(GameLanguage.t(), map) :: String.t() | nil + @callback find_word(GameLanguage.t()) :: Word.t() | nil + @callback find_word(GameLanguage.t(), word :: String.t()) :: Word.t() | nil + @callback find_word(GameLanguage.t(), options :: map) :: Word.t() | nil end diff --git a/lib/alternis/engines/dictionary_engine_impl.ex b/lib/alternis/engines/dictionary_engine_impl.ex index a8e0d61..1870765 100644 --- a/lib/alternis/engines/dictionary_engine_impl.ex +++ b/lib/alternis/engines/dictionary_engine_impl.ex @@ -8,35 +8,31 @@ defmodule Alternis.Engines.DictionaryEngine.Impl do alias Alternis.Repo alias Alternis.Word - @spec find_word(String.t(), GameLanguage.t()) :: Word.t() | nil - def find_word(lemma, language) do - from( - w in Word, - join: d in Dictionary, - on: d.id == w.dictionary_id, - where: - d.language == ^language and - w.lemma == ^String.downcase(lemma), - select_merge: %{language: d.language}, - order_by: w.frequency, - limit: 1 - ) - |> Repo.one() + def find_word(language, opts \\ %{min: 4, max: 9}) + + @spec find_word(GameLanguage.t(), map) :: Word.t() | nil + def find_word(language, %{min: min, max: max}) do + condition = + dynamic(fragment("LENGTH(lemma) > ?", ^min) and fragment("LENGTH(lemma) < ?", ^max)) + + conditionally_find_word(language, condition) end - @spec secret(GameLanguage.t(), map) :: String.t() | nil - def secret(language, opts \\ %{min: 4, max: 9}) do - import Ecto.Query + @spec find_word(GameLanguage.t(), String.t()) :: Word.t() | nil + def find_word(language, word) when not is_nil(word) do + condition = dynamic([w], w.lemma == ^String.downcase(word)) + conditionally_find_word(language, condition) + end + defp conditionally_find_word(language, condition) do Repo.one( from w in Word, join: d in Dictionary, on: d.id == w.dictionary_id, - where: - d.language == ^language and - fragment("LENGTH(lemma) > ?", ^opts.min) and fragment("LENGTH(lemma) < ?", ^opts.max), - select: w.lemma, - order_by: fragment("RANDOM()"), + where: ^condition, + where: d.language == ^language, + select_merge: %{language: d.language}, + order_by: w.frequency, limit: 1 ) end diff --git a/lib/alternis/engines/game_engine_impl.ex b/lib/alternis/engines/game_engine_impl.ex index 28e20ff..3c0509f 100644 --- a/lib/alternis/engines/game_engine_impl.ex +++ b/lib/alternis/engines/game_engine_impl.ex @@ -13,12 +13,13 @@ defmodule Alternis.Engines.GameEngine.Impl do alias Alternis.GameSettings alias Alternis.Guess alias Alternis.Repo + alias Alternis.Word @spec create(User.t(), GameSettings.t()) :: {:ok, Game.id()} | {:error, map} def create(user, settings = %GameSettings{secret: nil}) do - case DictionaryEngine.impl().secret(settings.language) do - nil -> {:error, %{reason: :secret_not_found, settings: settings}} - secret -> create(user, %{settings | secret: secret}) + case DictionaryEngine.impl().find_word(settings.language) do + nil -> {:error, %{reason: :word_not_found, settings: settings}} + %Word{lemma: word} -> create(user, %{settings | secret: word}) end end diff --git a/lib/alternis/game_settings.ex b/lib/alternis/game_settings.ex index 66c4315..89bd4ed 100644 --- a/lib/alternis/game_settings.ex +++ b/lib/alternis/game_settings.ex @@ -32,7 +32,7 @@ defmodule Alternis.GameSettings do def validate_in_dictionary(changeset, field) when is_atom(field) do validate_change(changeset, field, fn field, secret -> - case DictionaryEngine.impl().find_word(secret, changeset.changes.language) do + case DictionaryEngine.impl().find_word(changeset.changes.language, secret) do nil -> [{field, "word not in dictionary"}] _ -> [] end diff --git a/lib/alternis/guess.ex b/lib/alternis/guess.ex index 58683a7..b87fbcc 100644 --- a/lib/alternis/guess.ex +++ b/lib/alternis/guess.ex @@ -48,7 +48,7 @@ defmodule Alternis.Guess do def validate_word_in_dictionary(changeset, language) do validate_change(changeset, :word, fn _field, word -> - case DictionaryEngine.impl().find_word(word, language) do + case DictionaryEngine.impl().find_word(language, word) do nil -> [{:word, "word not in dictionary"}] _ -> [] end diff --git a/lib/alternis/landing.ex b/lib/alternis/landing.ex index 452e272..bf69e11 100644 --- a/lib/alternis/landing.ex +++ b/lib/alternis/landing.ex @@ -65,7 +65,7 @@ defmodule Alternis.Landing do end defp language_of(secret, language) do - case DictionaryEngine.impl().find_word(secret, language) do + case DictionaryEngine.impl().find_word(language, secret) do nil -> nil word -> word.language end diff --git a/lib/alternis_web/live/game_live/game_component.ex b/lib/alternis_web/live/game_live/game_component.ex index e433fe2..1085348 100644 --- a/lib/alternis_web/live/game_live/game_component.ex +++ b/lib/alternis_web/live/game_live/game_component.ex @@ -1,28 +1,40 @@ defmodule AlternisWeb.GameLive.GameComponent do use Phoenix.Component - # use AlternisWeb, :live_component - attr :game, :map, required: true attr :guesses, :list, required: true + slot :header + slot :footer - def render(assigns) do + def timeline(assigns) do ~H"""
- Language: <%= Recase.to_pascal(@game.language.value) %> -
  • - Secret: - <%= String.pad_leading("", String.length(@game.secret), "*") %> + <%= render_slot(@header) %> + + <%= render_slot(@footer) %> +
  • + """ + end - - + attr :players, :list, default: [] + slot :header + + def players(assigns) do + ~H""" +
    + <%= render_slot(@header) %> + <%= for player <- @players do %> +
  • + <%= player.username %> +
  • + <% end %>
    """ end diff --git a/lib/alternis_web/live/game_live/game_list_component.ex b/lib/alternis_web/live/game_live/game_list_component.ex index 6e9a709..2cc3658 100644 --- a/lib/alternis_web/live/game_live/game_list_component.ex +++ b/lib/alternis_web/live/game_live/game_list_component.ex @@ -3,19 +3,13 @@ defmodule AlternisWeb.GameLive.GameListComponent do use AlternisWeb, :verified_routes attr :games, :list, default: [] + slot :header - def render(assigns) do + def board(assigns) do ~H""" <%= unless @games == [] do %> - - - - - - - - + <%= render_slot(@header) %> <% end %> <%= for game <- @games do %> diff --git a/lib/alternis_web/live/game_live/guess_form_component.html.heex b/lib/alternis_web/live/game_live/guess_form_component.html.heex index 91504da..d223886 100644 --- a/lib/alternis_web/live/game_live/guess_form_component.html.heex +++ b/lib/alternis_web/live/game_live/guess_form_component.html.heex @@ -14,9 +14,7 @@ <%= text_input f, :word, autofocus: true %> <%= error_tag f, :word %> -
    - <%= submit "Guess", phx_disable_with: "Guessing...", disabled: !@changeset.valid? %> -
    + <%= submit "Guess", phx_disable_with: "Guessing...", disabled: !@changeset.valid? %> diff --git a/lib/alternis_web/live/game_live/index.ex b/lib/alternis_web/live/game_live/index.ex index d48c396..8c32575 100644 --- a/lib/alternis_web/live/game_live/index.ex +++ b/lib/alternis_web/live/game_live/index.ex @@ -1,6 +1,8 @@ defmodule AlternisWeb.GameLive.Index do use AlternisWeb, :live_view + import AlternisWeb.GameLive.GameListComponent + alias Alternis.GameSettings alias Alternis.Landing diff --git a/lib/alternis_web/live/game_live/index.html.heex b/lib/alternis_web/live/game_live/index.html.heex index 6d49817..af148a6 100644 --- a/lib/alternis_web/live/game_live/index.html.heex +++ b/lib/alternis_web/live/game_live/index.html.heex @@ -13,6 +13,15 @@ <% end %> - +<.board games={@games}> + <:header> + + + + + + + +<%= live_redirect "New Game", to: ~p"/games/new" %> diff --git a/lib/alternis_web/live/game_live/players_component.ex b/lib/alternis_web/live/game_live/players_component.ex deleted file mode 100644 index 8c4ed9d..0000000 --- a/lib/alternis_web/live/game_live/players_component.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule AlternisWeb.GameLive.PlayersComponent do - use Phoenix.Component - - attr :players, :list, default: [] - - def render(assigns) do - ~H""" -
    - <%= for player <- @players do %> -
  • - <%= player.username %> -
  • - <% end %> -
    - """ - end -end diff --git a/lib/alternis_web/live/game_live/show.ex b/lib/alternis_web/live/game_live/show.ex index 29fa4c7..1e80053 100644 --- a/lib/alternis_web/live/game_live/show.ex +++ b/lib/alternis_web/live/game_live/show.ex @@ -1,6 +1,8 @@ defmodule AlternisWeb.GameLive.Show do use AlternisWeb, :live_view + import AlternisWeb.GameLive.GameComponent + alias Alternis.Landing alias Alternis.Game.GameState.{Expired, Finished} diff --git a/lib/alternis_web/live/game_live/show.html.heex b/lib/alternis_web/live/game_live/show.html.heex index e0fce55..fe0e63f 100644 --- a/lib/alternis_web/live/game_live/show.html.heex +++ b/lib/alternis_web/live/game_live/show.html.heex @@ -1,8 +1,10 @@ -<%= if @game.in_progress? do %> -

    Active Players:

    - - -<% end %> +<.players players={@players}> + <:header> + <%= if @game.in_progress? do %> +

    Active Players:

    + <% end %> + +

    Game History

    @@ -20,11 +22,20 @@ <% end %> <%= live_redirect "Back", id: "Back", to: ~p"/games" %> diff --git a/test/alternis/engines/dictionary_engine_test.exs b/test/alternis/engines/dictionary_engine_test.exs index 0c1e15e..3165802 100644 --- a/test/alternis/engines/dictionary_engine_test.exs +++ b/test/alternis/engines/dictionary_engine_test.exs @@ -20,17 +20,17 @@ defmodule Alternis.Engines.DictionaryEngine.ImplTest do end test "finds word in dictionary" do - assert %Word{lemma: "secret"} = DictionaryEngine.Impl.find_word("secret", English) - refute DictionaryEngine.Impl.find_word("nonexistent", English) + assert %Word{lemma: "secret"} = DictionaryEngine.Impl.find_word(English, "secret") + refute DictionaryEngine.Impl.find_word(English, "nonexistent") end test "finds word in dictionary in non english" do - assert %Word{lemma: "секрет"} = DictionaryEngine.Impl.find_word("секрет", Russian) + assert %Word{lemma: "секрет"} = DictionaryEngine.Impl.find_word(Russian, "секрет") end test "finds word in dictionary case insensitive" do - assert %Word{lemma: "secret"} = DictionaryEngine.Impl.find_word("Secret", English) - assert %Word{lemma: "секрет"} = DictionaryEngine.Impl.find_word("Секрет", Russian) + assert %Word{lemma: "secret"} = DictionaryEngine.Impl.find_word(English, "Secret") + assert %Word{lemma: "секрет"} = DictionaryEngine.Impl.find_word(Russian, "Секрет") end end end diff --git a/test/alternis/engines/game_engine_test.exs b/test/alternis/engines/game_engine_test.exs index 1edded7..2319bed 100644 --- a/test/alternis/engines/game_engine_test.exs +++ b/test/alternis/engines/game_engine_test.exs @@ -8,6 +8,7 @@ defmodule Alternis.Engines.GameEngine.ImplTest do alias Alternis.Guess alias Alternis.Match alias Alternis.Repo + alias Alternis.Word alias Ecto.ShortUUID import Alternis.AccountsFixtures @@ -18,7 +19,7 @@ defmodule Alternis.Engines.GameEngine.ImplTest do describe "create/1 with engine generated secret" do setup do - expect(DictionaryEngine.Mock, :secret, fn _language -> "secret" end) + expect(DictionaryEngine.Mock, :find_word, fn _language -> %Word{lemma: "secret"} end) {:ok, user: user_fixture(), settings: build(:game_settings, secret: nil)} end @@ -30,12 +31,12 @@ defmodule Alternis.Engines.GameEngine.ImplTest do describe "create/1 with engine failing to generate a secret" do setup do - expect(DictionaryEngine.Mock, :secret, fn _language -> nil end) + expect(DictionaryEngine.Mock, :find_word, fn _language -> nil end) {:ok, user: user_fixture(), settings: build(:game_settings, secret: nil)} end test "fails creating game", %{user: user, settings: settings} do - assert {:error, %{reason: :secret_not_found}} = GameEngine.Impl.create(user, settings) + assert {:error, %{reason: :word_not_found}} = GameEngine.Impl.create(user, settings) end end
    LanguageSecret
    LanguageSecret