Skip to content

Commit d058ff1

Browse files
author
vtm9
committed
Add task packs
1 parent 911e388 commit d058ff1

40 files changed

Lines changed: 1392 additions & 178 deletions

services/app/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ console:
2929
iex -S mix
3030

3131
test:
32-
mix coveralls.json --exclude code_check --max-failures 1
32+
mix coveralls.json --exclude code_check --max-failures 1 --slowest 7
3333

3434
test-code-checkers: export CODEBATTLE_RUN_CODE_CHECK = true
3535
test-code-checkers:

services/app/config/config.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ config :codebattle, Codebattle.DockerLangsPuller, timeout: :timer.hours(7)
9090

9191
config :codebattle, checker_adapter: Codebattle.CodeCheck.DockerChecker
9292
config :codebattle, tournament_match_timeout: 3 * 60
93+
config :codebattle, tasks_provider: Codebattle.GameProcess.TasksQueuesServer
9394

9495
config :codebattle, Codebattle.Analitics, max_size_activity_server: 10_000
9596

services/app/config/test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ config :codebattle, checker_adapter: adapter
4040
config :codebattle, tournament_match_timeout: 1
4141
config :codebattle, Codebattle.Invite, timeout: :timer.seconds(1000)
4242
config :codebattle, Codebattle.Invite, lifetime: :timer.seconds(0)
43+
config :codebattle, tasks_provider: Codebattle.GameProcess.FakeTasksQueuesServer
4344

4445
config :codebattle, :firebase,
4546
sender_id: "ASDF",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
defmodule Codebattle.TaskForm do
2+
@moduledoc false
3+
4+
import Ecto.Changeset
5+
6+
alias Codebattle.Repo
7+
alias Codebattle.Task
8+
9+
def create(params, user) do
10+
new_params =
11+
params
12+
|> Map.merge(%{
13+
"origin" => "user",
14+
"state" => "draft",
15+
"creator_id" => user.id
16+
})
17+
18+
%Task{}
19+
|> changeset(new_params)
20+
|> Repo.insert()
21+
end
22+
23+
def update(task, params, user) do
24+
new_params = params
25+
26+
task
27+
|> changeset(new_params)
28+
|> Repo.update()
29+
end
30+
31+
def changeset(struct, params \\ %{}) do
32+
struct
33+
|> cast(params, [
34+
:examples,
35+
:description_ru,
36+
:description_en,
37+
:name,
38+
:level,
39+
:state,
40+
:origin,
41+
:visibility,
42+
:creator_id
43+
])
44+
|> cast_json_field(params, :input_signature)
45+
|> cast_json_field(params, :output_signature)
46+
|> cast_asserts(params)
47+
|> cast_tags(params)
48+
|> validate_required([
49+
:examples,
50+
:description_en,
51+
:name,
52+
:level,
53+
:input_signature,
54+
:output_signature,
55+
:origin,
56+
:state,
57+
:visibility,
58+
:asserts
59+
])
60+
|> validate_inclusion(:state, Task.states())
61+
|> validate_inclusion(:level, Task.levels())
62+
|> validate_inclusion(:origin, Task.origin_types())
63+
|> validate_inclusion(:visibility, Task.visibility_types())
64+
|> unique_constraint(:name)
65+
end
66+
67+
defp cast_json_field(changeset, params, field) do
68+
case Jason.decode(params[to_string(field)]) do
69+
{:ok, value} -> put_change(changeset, field, value)
70+
{:error, reason} -> add_error(changeset, field, inspect(reason))
71+
end
72+
end
73+
74+
defp cast_tags(changeset, params) do
75+
tags =
76+
params
77+
|> Map.get("tags", "")
78+
|> String.split(",")
79+
|> Enum.map(&String.trim/1)
80+
81+
put_change(changeset, :tags, tags)
82+
end
83+
84+
defp cast_asserts(changeset, params) do
85+
asserts =
86+
params
87+
|> Map.get("asserts", "")
88+
|> String.replace("\r", "")
89+
90+
put_change(changeset, :asserts, asserts)
91+
end
92+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
defmodule Codebattle.TaskPackForm do
2+
@moduledoc false
3+
4+
import Ecto.Changeset
5+
6+
alias Codebattle.Repo
7+
alias Codebattle.TaskPack
8+
9+
def create(params, user) do
10+
new_params =
11+
params
12+
|> Map.merge(%{
13+
"state" => "draft",
14+
"creator_id" => user.id
15+
})
16+
17+
%TaskPack{}
18+
|> changeset(new_params)
19+
|> Repo.insert()
20+
end
21+
22+
def update(task, params, user) do
23+
new_params = params
24+
25+
task
26+
|> changeset(new_params)
27+
|> Repo.update()
28+
end
29+
30+
def changeset(struct, params \\ %{}) do
31+
struct
32+
|> cast(params, [
33+
:name,
34+
:state,
35+
:visibility,
36+
:creator_id
37+
])
38+
|> cast_task_ids(params)
39+
|> validate_required([
40+
:name,
41+
:state,
42+
:visibility,
43+
:creator_id,
44+
:task_ids
45+
])
46+
|> validate_inclusion(:state, TaskPack.states())
47+
|> validate_inclusion(:visibility, TaskPack.visibility_types())
48+
|> unique_constraint(:name)
49+
end
50+
51+
defp cast_task_ids(changeset, params) do
52+
task_ids =
53+
params
54+
|> Map.get("task_ids", "")
55+
|> String.split(",")
56+
|> Enum.map(&String.trim/1)
57+
|> Enum.map(&String.to_integer/1)
58+
59+
put_change(changeset, :task_ids, task_ids)
60+
end
61+
end

services/app/lib/codebattle/game_process/engine/base.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ defmodule Codebattle.GameProcess.Engine.Base do
66
Server,
77
FsmHelpers,
88
ActiveGames,
9-
TasksQueuesServer,
109
Fsm,
1110
Elo,
1211
GlobalSupervisor
@@ -101,7 +100,7 @@ defmodule Codebattle.GameProcess.Engine.Base do
101100
})
102101
end
103102

104-
def get_task(level), do: TasksQueuesServer.get_task(level)
103+
def get_task(level), do: tasks_provider().get_task(level)
105104
def update_rank_async(), do: Codebattle.UsersRankUpdateServer.update()
106105

107106
def store_game_result!(fsm, {winner, winner_result}, {loser, loser_result}) do
@@ -193,6 +192,10 @@ defmodule Codebattle.GameProcess.Engine.Base do
193192
|> Game.changeset(params)
194193
|> Repo.insert()
195194
end
195+
196+
defp tasks_provider do
197+
Application.get_env(:codebattle, :tasks_provider)
198+
end
196199
end
197200
end
198201
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
defmodule Codebattle.GameProcess.FakeTasksQueuesServer do
2+
import Ecto.Query
3+
4+
alias Codebattle.Repo
5+
6+
def get_task(level) do
7+
from(t in Codebattle.Task, where: t.level == ^level) |> Repo.all() |> List.first()
8+
end
9+
end

services/app/lib/codebattle/game_process/tasks_queues_server.ex

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule Codebattle.GameProcess.TasksQueuesServer do
33

44
use GenServer
55

6+
@reshuffle_timeout :timer.hours(7)
7+
68
## Client API
79

810
def start_link(_) do
@@ -13,34 +15,74 @@ defmodule Codebattle.GameProcess.TasksQueuesServer do
1315
GenServer.call(__MODULE__, {:next_task, level})
1416
end
1517

18+
def shuffle_task_ids do
19+
GenServer.cast(__MODULE__, :set_shuffled_task_ids)
20+
end
21+
22+
def reshuffle_task_ids do
23+
GenServer.cast(__MODULE__, :reshuffle_task_ids)
24+
end
25+
1626
## Server callbacks
1727

1828
def init(_) do
19-
levels = ["elementary", "easy", "medium", "hard"]
29+
initial_state = %{
30+
task_ids: %{},
31+
cursors: initial_cursors()
32+
}
33+
34+
Process.send_after(self(), :reshuffle_task_ids, @reshuffle_timeout)
35+
{:ok, initial_state}
36+
end
2037

21-
tasks_queues =
22-
Enum.reduce(levels, %{}, fn level, acc ->
23-
Map.put(acc, level, Codebattle.Task.get_shuffled_tasks(level))
24-
end)
38+
def handle_cast(:set_shuffled_task_ids, state) do
39+
{:noreply, %{state | task_ids: fetch_task_ids()}}
40+
end
2541

26-
{:ok, tasks_queues}
42+
def handle_cast(:reshuffle_task_ids, state) do
43+
Process.send_after(self(), :reshuffled_task_ids, @reshuffle_timeout)
44+
{:noreply, %{state | task_ids: fetch_task_ids()}}
2745
end
2846

29-
def handle_call({:next_task, level}, _from, tasks_queues) do
30-
[next_task, tail_tasks] =
31-
case Map.fetch!(tasks_queues, level) do
32-
[next_task | tail_tasks] ->
33-
[next_task, tail_tasks]
47+
def handle_call({:next_task, level}, _from, state) do
48+
cursor = Map.get(state.cursors, level)
49+
50+
case Map.get(state.task_ids, level) do
51+
[] ->
52+
case fetch_task_ids(level) do
53+
[] ->
54+
{:reply, nil, state}
3455

35-
_ ->
36-
[next_task | tail_tasks] = Codebattle.Task.get_shuffled_tasks(level)
37-
[next_task, tail_tasks]
38-
end
56+
task_ids ->
57+
id = Enum.at(task_ids, 0)
58+
task = Codebattle.Task.get!(id)
59+
new_cursors = Map.put(state.cursors, level, 1)
60+
new_task_ids = Map.put(state.task_ids, level, task_ids)
3961

40-
new_tasks_queues = Map.put(tasks_queues, level, tail_tasks)
62+
{:reply, task, %{state | cursors: new_cursors, task_ids: new_task_ids}}
63+
end
4164

42-
{:reply, next_task, new_tasks_queues}
65+
task_ids ->
66+
id = Enum.at(task_ids, rem(cursor, length(task_ids)))
67+
task = Codebattle.Task.get!(id)
68+
69+
new_cursors = Map.put(state.cursors, level, cursor + 1)
70+
{:reply, task, %{state | cursors: new_cursors}}
71+
end
4372
end
4473

4574
## Helpers
75+
defp initial_cursors do
76+
Enum.reduce(Codebattle.Task.levels(), %{}, fn level, acc ->
77+
Map.put(acc, level, 1)
78+
end)
79+
end
80+
81+
defp fetch_task_ids(level), do: Codebattle.Task.get_shuffled_task_ids(level)
82+
83+
defp fetch_task_ids do
84+
Enum.reduce(Codebattle.Task.levels(), %{}, fn level, acc ->
85+
Map.put(acc, level, fetch_task_ids(level))
86+
end)
87+
end
4688
end

0 commit comments

Comments
 (0)