Skip to content

Commit

Permalink
Merge branch 'search': Load new games on awaiting players tv screen
Browse files Browse the repository at this point in the history
  • Loading branch information
ryoung786 committed Nov 27, 2023
2 parents ef0ac57 + 770abc1 commit 73329a9
Show file tree
Hide file tree
Showing 8,707 changed files with 8,916 additions and 8,648 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
68 changes: 59 additions & 9 deletions lib/jeopardy/j_archive.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
defmodule Jeopardy.JArchive do
@moduledoc false
@moduledoc """
Finding and loading a specific game from the archive.
"""

import Ecto.Query

alias Jeopardy.JArchive.Downloader
alias Jeopardy.JArchive.GameIndex
alias Jeopardy.Repo

@jarchive_path Application.app_dir(:jeopardy, "priv/jarchive")
@completed_seasons_path Application.app_dir(:jeopardy, "priv/jarchive/completed_seasons")

def path, do: Application.get_env(:jeopardy, :jarchive_dir, @jarchive_path)
def completed_seasons_path, do: @completed_seasons_path

@spec load_game(any) :: {:ok, Jeopardy.JArchive.RecordedGame.t()} | {:error, any}
def load_game(:random) do
default_path = Application.app_dir(:jeopardy, "priv/jarchive")
path = Application.get_env(:jeopardy, :jarchive_dir, default_path)

file_name =
path
path()
|> File.ls!()
|> Enum.shuffle()
|> Enum.drop_while(fn filename -> !String.ends_with?(filename, ".json") end)
Expand All @@ -18,10 +30,7 @@ defmodule Jeopardy.JArchive do
end

def load_game(game_id) do
default_path = Application.app_dir(:jeopardy, "priv/jarchive")
path = Application.get_env(:jeopardy, :jarchive_dir, default_path)

with {:ok, file} <- File.read(Path.join(path, "#{game_id}.json")),
with {:ok, file} <- File.read(Path.join(path(), "#{game_id}.json")),
{:ok, json} <- Jason.decode(file) do
json
|> Jeopardy.JArchive.RecordedGame.changeset()
Expand All @@ -30,4 +39,45 @@ defmodule Jeopardy.JArchive do
_ -> {:error, "Game does not exist"}
end
end

def get_game_index(id) do
Repo.get_by(GameIndex, game_id: id)
end

def choose_game(filters \\ []) do
q = GameIndex
q = if filters[:decades], do: where(q, [g], g.decade in ^filters[:decades]), else: q
q = if filters[:difficulty], do: where(q, [g], g.difficulty in ^filters[:difficulty]), else: q

game =
q
|> order_by(fragment("RANDOM()"))
|> limit(1)
|> Repo.one()

if game, do: load_game(game.game_id), else: {:error, "No games exist"}
end

def reset_archive do
File.rm_rf!(path())
File.mkdir_p(completed_seasons_path())
Repo.delete_all(GameIndex)

Downloader.download_all_seasons()
end

def search(query_string, filters \\ []) do
# wrap in double quotes to force phrases, otherwise characters like hyphens
# and periods are interpreted as column delimiters
query_string = "\"#{query_string}\""

q = from(game in GameIndex, where: fragment("game_index MATCH ?", ^query_string))
q = if filters[:decades], do: where(q, [g], g.decade in ^filters[:decades]), else: q
q = if filters[:difficulty], do: where(q, [g], g.difficulty in ^filters[:difficulty]), else: q

q
|> select([:air_date, :rank])
|> order_by(asc: :rank)
|> Repo.all()
end
end
29 changes: 18 additions & 11 deletions lib/jeopardy/j_archive/downloader.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
defmodule Jeopardy.JArchive.Downloader do
@moduledoc false

alias Jeopardy.JArchive
alias Jeopardy.JArchive.GameIndex
alias Jeopardy.Repo

require Logger

@req Req.new(base_url: "https://j-archive.com")
@jarchive_path Application.app_dir(:jeopardy, "priv/jarchive")
@completed_seasons_path Application.app_dir(:jeopardy, "priv/jarchive/completed_seasons")

@spec download_all_seasons() :: :ok
def download_all_seasons do
Expand All @@ -24,27 +27,31 @@ defmodule Jeopardy.JArchive.Downloader do
|> game_ids_from_season_html()

game_ids
|> Task.async_stream(&download_and_parse_game/1, max_concurrency: 5)
|> Task.async_stream(&download_and_parse_game(&1, season_id, force_download: opts[:force_download]),
max_concurrency: 5
)
|> Enum.to_list()

if season_finished_airing?(game_ids) do
File.touch(Path.join(@completed_seasons_path, "#{season_id}"))
File.touch(Path.join(JArchive.completed_seasons_path(), "#{season_id}"))
end
end
end

@spec download_and_parse_game(any, [{:force_download, boolean}]) :: :ok | {:error, File.posix()}
def download_and_parse_game(game_id, opts \\ [force_download: false]) do
def download_and_parse_game(game_id, season_id, opts \\ [force_download: false]) do
if game_already_downloaded?(game_id) && !opts[:force_download] do
Logger.info("Skipping download, game was cached", game_id: game_id)
else
Logger.info("Downloading game", game_id: game_id)

Req.get!(@req, url: "showgame.php", params: [game_id: game_id]).body
|> Floki.parse_document!()
|> Jeopardy.JArchive.Parser.parse_game()
|> Jason.encode!()
|> then(&File.write(Path.join(@jarchive_path, "#{game_id}.json"), &1))
json =
Req.get!(@req, url: "showgame.php", params: [game_id: game_id]).body
|> Floki.parse_document!()
|> Jeopardy.JArchive.Parser.parse_game(season_id)

File.write(Path.join(JArchive.path(), "#{game_id}.json"), Jason.encode!(json))
game_id |> GameIndex.from_json(json) |> Repo.insert()
end
end

Expand Down Expand Up @@ -84,7 +91,7 @@ defmodule Jeopardy.JArchive.Downloader do

@spec game_already_downloaded?(any) :: boolean
defp game_already_downloaded?(game_id) do
File.exists?(Path.join(@jarchive_path, "#{game_id}.json"))
File.exists?(Path.join(JArchive.path(), "#{game_id}.json"))
end

@spec season_finished_airing?(list(any)) :: boolean
Expand Down
84 changes: 84 additions & 0 deletions lib/jeopardy/j_archive/game_index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
defmodule Jeopardy.JArchive.GameIndex do
@moduledoc false
use TypedEctoSchema

import Ecto.Changeset

alias Jeopardy.JArchive

@primary_key {:id, :id, autogenerate: true, source: :rowid}
typed_schema "game_index" do
field :game_id, :integer
field :year, :integer
field :decade, :integer
field :season, :string
field :difficulty, :string
field :air_date, :date
field :comments, :string
field :contestant_1, :string
field :contestant_2, :string
field :contestant_3, :string
field :rank, :float, virtual: true
end

defp changeset(attrs) do
cast(%__MODULE__{}, attrs, [
:game_id,
:year,
:decade,
:season,
:difficulty,
:air_date,
:comments,
:contestant_1,
:contestant_2,
:contestant_3
])
end

defp difficulty(nil = _comments), do: "normal"

defp difficulty(comments) do
re_very_hard = ~r/(greatest of all time)|(masters)|(battle of the decades)|(all-star)|(all star)|(ibm challenge)/i
re_hard = ~r/(tournament of champions)|(professor)/i
re_easy = ~r/kids|celebrity|teen/i

cond do
Regex.match?(re_very_hard, comments) -> "very_hard"
Regex.match?(re_hard, comments) -> "hard"
Regex.match?(re_easy, comments) -> "easy"
:else -> "normal"
end
end

def from_json(game_id, json) do
{:ok, game} =
json
|> Jeopardy.JArchive.RecordedGame.changeset()
|> Ecto.Changeset.apply_action(:load)

from_recorded_game(game_id, game)
end

def from_recorded_game(game_id) do
{:ok, game} = JArchive.load_game(game_id)
from_recorded_game(game_id, game)
end

def from_recorded_game(game_id, game) do
attrs = %{
game_id: game_id,
year: game.air_date.year,
decade: 10 * div(game.air_date.year, 10),
season: game.season,
difficulty: difficulty(game.comments),
air_date: game.air_date,
comments: game.comments,
contestant_1: Enum.at(game.contestants, 0),
contestant_2: Enum.at(game.contestants, 1),
contestant_3: Enum.at(game.contestants, 2)
}

changeset(attrs)
end
end
5 changes: 3 additions & 2 deletions lib/jeopardy/j_archive/parser.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Jeopardy.JArchive.Parser do
@moduledoc false
def parse_game(html) do
def parse_game(html, season_id) do
%{
jeopardy: Enum.map(1..6, &parse_round_category(html, :jeopardy, &1)),
double_jeopardy: Enum.map(1..6, &parse_round_category(html, :double_jeopardy, &1)),
Expand All @@ -16,7 +16,8 @@ defmodule Jeopardy.JArchive.Parser do
},
air_date: parse_air_date(html),
contestants: html |> Floki.find("#contestants .contestants a") |> Enum.map(&Floki.text/1),
comments: html |> Floki.find("#game_comments") |> Floki.text()
comments: html |> Floki.find("#game_comments") |> Floki.text(),
season: season_id
}
end

Expand Down
3 changes: 2 additions & 1 deletion lib/jeopardy/j_archive/recorded_game.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule Jeopardy.JArchive.RecordedGame do
field :air_date, :date
field :comments, :string
field :contestants, {:array, :string}
field :season, :string

embeds_one :categories, Categories, primary_key: false do
field :jeopardy, {:array, :string}
Expand All @@ -30,7 +31,7 @@ defmodule Jeopardy.JArchive.RecordedGame do

def changeset(attrs) do
%__MODULE__{}
|> cast(attrs, ~w/air_date comments contestants/a)
|> cast(attrs, ~w/air_date comments contestants season/a)
|> cast_embed(:categories, with: &categories_changeset/2)
|> cast_embed(:final_jeopardy, with: &final_jeopardy_changeset/2)
|> cast_embed(:jeopardy)
Expand Down
18 changes: 17 additions & 1 deletion lib/jeopardy_web/components/tv/awaiting_players.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule JeopardyWeb.Components.Tv.AwaitingPlayers do
@moduledoc false
use JeopardyWeb.FSMComponent

alias Jeopardy.FSM.Messages.JArchiveGameLoaded
alias Jeopardy.FSM.Messages.PlayerAdded
alias Jeopardy.FSM.Messages.PlayerRemoved
alias Jeopardy.FSM.Messages.PodiumSigned
Expand All @@ -10,7 +11,13 @@ defmodule JeopardyWeb.Components.Tv.AwaitingPlayers do
def assign_init(socket, game) do
player_names = game.players |> Map.keys() |> Enum.sort()
signatures = Map.new(game.players, fn {name, player} -> {name, player.signature} end)
assign(socket, players: player_names, original_players: player_names, signatures: signatures)

assign(socket,
players: player_names,
original_players: player_names,
signatures: signatures,
air_date: game.jarchive_game.air_date
)
end

def handle_game_server_msg(%PlayerRemoved{name: name}, socket) do
Expand All @@ -25,6 +32,10 @@ defmodule JeopardyWeb.Components.Tv.AwaitingPlayers do
{:ok, assign(socket, signatures: Map.put(socket.assigns.signatures, podium.name, podium.signature))}
end

def handle_game_server_msg(%JArchiveGameLoaded{} = game, socket) do
{:ok, assign(socket, air_date: game.air_date)}
end

def handle_event("remove-player", %{"player" => name}, socket) do
case Jeopardy.GameServer.action(socket.assigns.code, :remove_player, name) do
{:ok, game} ->
Expand All @@ -47,6 +58,11 @@ defmodule JeopardyWeb.Components.Tv.AwaitingPlayers do
{:noreply, socket}
end

def handle_event("change-game", _, socket) do
Jeopardy.GameServer.action(socket.assigns.code, :load_game, :random)
{:noreply, socket}
end

# JS interactions

defp remove_player(name) do
Expand Down
4 changes: 4 additions & 0 deletions lib/jeopardy_web/components/tv/awaiting_players.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
<.button :if={Enum.count(@players) >= 2} phx-click="start-game" phx-target={@myself}>
Start Game
</.button>
<div class="absolute bottom-2 text-base font-sans flex gap-4">
<span>Game aired <%= Calendar.strftime(@air_date, "%b %d, %Y") %></span>
<.button class="btn-xs" phx-click="change-game" phx-target={@myself}>Change</.button>
</div>
</.clue>
</div>
<span />
Expand Down
Loading

0 comments on commit 73329a9

Please sign in to comment.