Skip to content

Commit

Permalink
Merge pull request #38 from leoamigood/common_refactor
Browse files Browse the repository at this point in the history
Refactoring DictionaryEngine secret into find_word
  • Loading branch information
leoamigood authored Feb 8, 2023
2 parents 817e0a8 + af536c8 commit 25b1302
Show file tree
Hide file tree
Showing 16 changed files with 105 additions and 96 deletions.
6 changes: 3 additions & 3 deletions lib/alternis/dictionary_engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
40 changes: 18 additions & 22 deletions lib/alternis/engines/dictionary_engine_impl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions lib/alternis/engines/game_engine_impl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/alternis/game_settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/alternis/guess.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/alternis/landing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 29 additions & 17 deletions lib/alternis_web/live/game_live/game_component.ex
Original file line number Diff line number Diff line change
@@ -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"""
<div>
<strong>Language:</strong> <%= Recase.to_pascal(@game.language.value) %>
<li>
<strong>Secret:</strong>
<%= String.pad_leading("", String.length(@game.secret), "*") %>
<%= render_slot(@header) %>
<ul id="game-guesses" phx-update="append">
<%= for guess <- @guesses do %>
<li id={guess.id} style={if guess.exact?, do: "color:green"}>
<%= guess.user.username %>: <%= guess.word %> - bulls: <%= length(guess.bulls) %>, cows: <%= length(
guess.cows
) %>
</li>
<% end %>
</ul>
<%= render_slot(@footer) %>
</div>
"""
end

<ul id="game-guesses" phx-update="append">
<%= for guess <- @guesses do %>
<li id={guess.id} style={if guess.exact?, do: "color:green"}>
<%= guess.user.username %>: <%= guess.word %> - bulls: <%= length(guess.bulls) %>, cows: <%= length(
guess.cows
) %>
</li>
<% end %>
</ul>
</li>
attr :players, :list, default: []
slot :header

def players(assigns) do
~H"""
<div>
<%= render_slot(@header) %>
<%= for player <- @players do %>
<li>
<%= player.username %>
</li>
<% end %>
</div>
"""
end
Expand Down
12 changes: 3 additions & 9 deletions lib/alternis_web/live/game_live/game_list_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
<table>
<%= unless @games == [] do %>
<thead>
<tr>
<th>Language</th>
<th>Secret</th>
<th></th>
</tr>
</thead>
<%= render_slot(@header) %>
<% end %>
<tbody id="games">
<%= for game <- @games do %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
<%= text_input f, :word, autofocus: true %>
<%= error_tag f, :word %>

<div>
<%= submit "Guess", phx_disable_with: "Guessing...", disabled: !@changeset.valid? %>
</div>
<%= submit "Guess", phx_disable_with: "Guessing...", disabled: !@changeset.valid? %>
</.focus_wrap>
</.form>
</div>
2 changes: 2 additions & 0 deletions lib/alternis_web/live/game_live/index.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule AlternisWeb.GameLive.Index do
use AlternisWeb, :live_view

import AlternisWeb.GameLive.GameListComponent

alias Alternis.GameSettings
alias Alternis.Landing

Expand Down
11 changes: 10 additions & 1 deletion lib/alternis_web/live/game_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
</.modal>
<% end %>

<AlternisWeb.GameLive.GameListComponent.render games={@games} />
<.board games={@games}>
<:header>
<thead>
<tr>
<th>Language</th>
<th>Secret</th>
</tr>
</thead>
</:header>
</.board>

<span><%= live_redirect "New Game", to: ~p"/games/new" %></span>
17 changes: 0 additions & 17 deletions lib/alternis_web/live/game_live/players_component.ex

This file was deleted.

2 changes: 2 additions & 0 deletions lib/alternis_web/live/game_live/show.ex
Original file line number Diff line number Diff line change
@@ -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}

Expand Down
31 changes: 21 additions & 10 deletions lib/alternis_web/live/game_live/show.html.heex
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<%= if @game.in_progress? do %>
<h2>Active Players:</h2>

<AlternisWeb.GameLive.PlayersComponent.render players={@players} />
<% end %>
<.players players={@players}>
<:header>
<%= if @game.in_progress? do %>
<h2>Active Players:</h2>
<% end %>
</:header>
</.players>

<h1>Game History</h1>

Expand All @@ -20,11 +22,20 @@
<% end %>

<ul>
<AlternisWeb.GameLive.GameComponent.render game={@game} guesses={@guesses} />

<%= if @game.in_progress? do %>
<span><.link patch={~p"/games/#{@game}/guess"}>Place a guess</.link></span>
<% end %>
<.timeline guesses={@guesses}>
<:header>
<strong>Language:</strong>
<%= Recase.to_pascal(@game.language.value) %>
<br/>
<strong>Secret:</strong>
<%= String.pad_leading("", String.length(@game.secret), "*") %>
</:header>
<:footer>
<%= if @game.in_progress? do %>
<span><.link patch={~p"/games/#{@game}/guess"}>Place a guess</.link></span>
<% end %>
</:footer>
</.timeline>
</ul>

<span><%= live_redirect "Back", id: "Back", to: ~p"/games" %></span>
10 changes: 5 additions & 5 deletions test/alternis/engines/dictionary_engine_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 4 additions & 3 deletions test/alternis/engines/game_engine_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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

Expand Down

0 comments on commit 25b1302

Please sign in to comment.