Skip to content

Commit 739768e

Browse files
VitalyVitaly
authored andcommitted
Add upcoming tournaments runner
1 parent 0499ac8 commit 739768e

File tree

17 files changed

+306
-61
lines changed

17 files changed

+306
-61
lines changed

docker-compose.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
version: "3.3"
2-
31
services:
42
app:
53
build:

services/app/apps/codebattle/assets/css/style.scss

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,7 +2785,3 @@ a:hover {
27852785
width: auto;
27862786
height: auto;
27872787
}
2788-
2789-
body {
2790-
background-image: url('../static/images/main_background.png');
2791-
}

services/app/apps/codebattle/lib/codebattle/application.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ defmodule Codebattle.Application do
2121
{Codebattle.TasksImporter, []},
2222
{Codebattle.UsersRankUpdateServer, []},
2323
{Codebattle.Bot.GameCreator, []},
24+
{Codebattle.Tournament.UpcomingRunner, []},
2425
{Codebattle.ImageCache, []},
2526
{Codebattle.Repo, []},
2627
{Registry, keys: :unique, name: Codebattle.Registry},

services/app/apps/codebattle/lib/codebattle/tournament/context.ex

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,63 @@ defmodule Codebattle.Tournament.Context do
109109
)
110110
end
111111

112+
@spec get_waiting_participants_to_start_candidates() :: list(Tournament.t())
113+
def get_waiting_participants_to_start_candidates do
114+
Enum.filter(get_live_tournaments(), fn tournament ->
115+
tournament.state == "waiting_participants" &&
116+
tournament.grade != "open" &&
117+
tournament.starts_at
118+
119+
# &&
120+
# DateTime.compare(tournament.starts_at, DateTime.utc_now()) == :lt
121+
end)
122+
end
123+
124+
@spec get_upcoming_to_live_candidate(non_neg_integer()) :: Tournament.t() | nil
125+
def get_upcoming_to_live_candidate(starts_at_delay_mins) do
126+
delay_time = DateTime.add(DateTime.utc_now(), starts_at_delay_mins, :minute)
127+
128+
Repo.one(
129+
from(t in Tournament,
130+
limit: 1,
131+
order_by: t.id,
132+
where:
133+
t.state == "upcoming" and
134+
t.starts_at < ^delay_time
135+
)
136+
)
137+
end
138+
139+
@spec get_upcoming_tournaments(%{
140+
date_from: DateTime.t(),
141+
date_to: DateTime.t()
142+
}) :: list(Tournament.t())
143+
def get_upcoming_tournaments(filter) do
144+
%{date_from: date_from, date_to: date_to} = filter
145+
146+
Repo.all(
147+
from(t in Tournament,
148+
order_by: t.id,
149+
where:
150+
t.starts_at > ^date_from and
151+
t.starts_at < ^date_to and
152+
t.grade != "open" and
153+
t.state == "upcoming"
154+
)
155+
)
156+
end
157+
158+
@spec get_live_tournaments_for_user(User.t()) :: list(Tournament.t())
159+
def get_live_tournaments_for_user(user) do
160+
get_live_tournaments()
161+
|> Enum.filter(fn tournament ->
162+
tournament.grade != "open" ||
163+
Tournament.Helpers.can_access?(tournament, user, %{})
164+
end)
165+
|> Enum.uniq_by(& &1.id)
166+
|> Enum.sort_by(& &1.id, :desc)
167+
end
168+
112169
@spec get_live_tournaments() :: list(Tournament.t())
113170
def get_live_tournaments do
114171
Tournament.GlobalSupervisor
@@ -120,7 +177,7 @@ defmodule Codebattle.Tournament.Context do
120177
|> Enum.map(fn {id, _, _, _} -> Tournament.Context.get(id) end)
121178
|> Enum.filter(fn
122179
nil -> false
123-
tournament -> tournament.state in ["waiting_participants", "active"]
180+
tournament -> tournament.state in ["waiting_participants", "active", "finished"]
124181
end)
125182
end
126183

@@ -166,6 +223,11 @@ defmodule Codebattle.Tournament.Context do
166223
Tournament.Server.handle_event(tournament_id, event_type, params)
167224
end
168225

226+
@spec get_live_tournament_players(Tournament.t()) :: [Tournament.Player.t()]
227+
def get_live_tournament_players(tournament) do
228+
Tournament.Players.get_players(tournament)
229+
end
230+
169231
@spec update(Tournament.t(), map()) :: {:ok, Tournament.t()} | {:error, Ecto.Changeset.t()}
170232
def update(tournament, params) do
171233
tournament
@@ -223,6 +285,18 @@ defmodule Codebattle.Tournament.Context do
223285
:ok
224286
end
225287

288+
@spec move_upcoming_to_live(Tournament.t()) :: :ok
289+
def move_upcoming_to_live(tournament) do
290+
tournament
291+
|> Tournament.changeset(%{state: "waiting_participants"})
292+
|> Repo.update!()
293+
294+
:timer.sleep(1000)
295+
296+
Tournament.GlobalSupervisor.start_tournament(tournament)
297+
:ok
298+
end
299+
226300
defp prepare_tournament_params(params) do
227301
params =
228302
params

services/app/apps/codebattle/lib/codebattle/tournament/strategy/base.ex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ defmodule Codebattle.Tournament.Base do
258258
end
259259
end
260260

261+
def cancel(tournament, params \\ %{})
262+
261263
def cancel(tournament, %{user: user}) do
262264
if can_moderate?(tournament, user) do
263265
new_tournament = tournament |> update_struct(%{state: "canceled"}) |> db_save!()
@@ -271,6 +273,15 @@ defmodule Codebattle.Tournament.Base do
271273
end
272274
end
273275

276+
def cancel(tournament, _params) do
277+
new_tournament = tournament |> update_struct(%{state: "canceled"}) |> db_save!()
278+
279+
Game.Context.terminate_tournament_games(tournament.id)
280+
Tournament.GlobalSupervisor.terminate_tournament(tournament.id)
281+
282+
new_tournament
283+
end
284+
274285
def restart(tournament, %{user: user}) do
275286
if can_moderate?(tournament, user) do
276287
Tournament.Round.disable_all_rounds(tournament.id)
@@ -298,6 +309,8 @@ defmodule Codebattle.Tournament.Base do
298309

299310
def restart(tournament, _user), do: tournament
300311

312+
def start(tournament, params \\ %{})
313+
301314
def start(%{state: "waiting_participants"} = tournament, %{user: user} = params) do
302315
if can_moderate?(tournament, user) do
303316
start(tournament, Map.delete(params, :user))

services/app/apps/codebattle/lib/codebattle/tournament/tournament.ex

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ defmodule Codebattle.Tournament do
2222
:current_round_position,
2323
:description,
2424
:event_id,
25+
:grade,
2526
:id,
2627
:is_live,
2728
:last_round_ended_at,
2829
:last_round_started_at,
29-
:level,
3030
:match_timeout_seconds,
3131
:matches,
3232
:meta,
@@ -55,15 +55,60 @@ defmodule Codebattle.Tournament do
5555

5656
@access_types ~w(public token)
5757
@break_states ~w(on off)
58+
@grades ~w(open rookie challenger pro elite masters grand_slam)
5859
@levels ~w(elementary easy medium hard)
5960
@public_types ~w(swiss)
6061
@ranking_types ~w(by_clan by_user)
6162
@score_strategies ~w(75_percentile win_loss)
62-
@states ~w(waiting_participants canceled active timeout finished)
63+
@states ~w(upcoming waiting_participants canceled active timeout finished)
6364
@task_providers ~w(level task_pack all)
6465
@task_strategies ~w(random sequential)
6566
@types ~w(swiss)
6667

68+
@grade_points %{
69+
# Open tournaments
70+
# All tasks are existing
71+
# Casual/unranked mode, no points awarded
72+
# Tasks: free play, any level, not ranked, created by user
73+
"open" => [],
74+
75+
# Rookie — every 1 hours
76+
# All tasks are existing
77+
# Tasks: 5 existing easy tasks
78+
# Designed for frequent play and grinding
79+
"rookie" => [8, 4, 2],
80+
81+
# Challenger — daily
82+
# All tasks are existing
83+
# Tasks: 3 existing easy tasks + 1 existing medium task
84+
# Daily backbone tournaments for steady point growth
85+
"challenger" => [64, 32, 16, 8, 4, 2],
86+
87+
# Pro — weekly
88+
# All tasks are existing
89+
# Tasks: 4 existing easy tasks + 2 existing medium tasks
90+
# Mid-level weekly tournaments with more challenges
91+
"pro" => [128, 64, 32, 16, 8, 4, 2],
92+
93+
# Elite — every two weeks
94+
# All tasks are existing
95+
# Tasks: 5 existing easy tasks + 3 existing medium tasks
96+
# Advanced difficulty and higher prestige
97+
"elite" => [256, 128, 64, 32, 16, 8, 4, 2],
98+
99+
# Masters — once per month on the 21st (evening, two per day)
100+
# All tasks are new
101+
# Tasks: 5 easy tasks + 2 medium tasks
102+
# Monthly major tournaments with fresh content
103+
"masters" => [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2],
104+
105+
# Grand Slam — four times per year (21 Mar, 21 Jun, 21 Sep, 21 Dec)
106+
# All tasks are new
107+
# Tasks: 5 easy tasks + 3 medium tasks + 1 hard task
108+
# Seasonal finals, always ends the season
109+
"grand_slam" => [2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2]
110+
}
111+
67112
@default_match_timeout Application.compile_env(:codebattle, :tournament_match_timeout)
68113

69114
schema "tournaments" do
@@ -78,6 +123,7 @@ defmodule Codebattle.Tournament do
78123
field(:current_round_position, :integer, default: 0)
79124
field(:description, :string)
80125
field(:finished_at, :utc_datetime)
126+
field(:grade, :string, default: "open")
81127
field(:labels, {:array, :string})
82128
field(:last_round_ended_at, :naive_datetime)
83129
field(:last_round_started_at, :naive_datetime)
@@ -89,9 +135,9 @@ defmodule Codebattle.Tournament do
89135
field(:players, AtomizedMap, default: %{})
90136
field(:players_limit, :integer)
91137
field(:ranking_type, :string, default: "by_user")
92-
field(:score_strategy, :string, default: "75_percentile")
93138
field(:round_timeout_seconds, :integer)
94139
field(:rounds_limit, :integer, default: 1)
140+
field(:score_strategy, :string, default: "75_percentile")
95141
field(:show_results, :boolean, default: true)
96142
field(:started_at, :utc_datetime)
97143
field(:starts_at, :utc_datetime)
@@ -139,6 +185,7 @@ defmodule Codebattle.Tournament do
139185
:current_round_position,
140186
:description,
141187
:event_id,
188+
:grade,
142189
:last_round_ended_at,
143190
:last_round_started_at,
144191
:level,
@@ -173,12 +220,13 @@ defmodule Codebattle.Tournament do
173220
])
174221
|> validate_inclusion(:access_type, @access_types)
175222
|> validate_inclusion(:break_state, @break_states)
223+
|> validate_inclusion(:grade, @grades)
176224
|> validate_inclusion(:level, @levels)
225+
|> validate_inclusion(:ranking_type, @ranking_types)
226+
|> validate_inclusion(:score_strategy, @score_strategies)
177227
|> validate_inclusion(:state, @states)
178228
|> validate_inclusion(:task_provider, @task_providers)
179229
|> validate_inclusion(:task_strategy, @task_strategies)
180-
|> validate_inclusion(:ranking_type, @ranking_types)
181-
|> validate_inclusion(:score_strategy, @score_strategies)
182230
|> validate_inclusion(:type, @types)
183231
|> validate_number(:match_timeout_seconds, greater_than_or_equal_to: 1)
184232
|> validate_required([:name, :starts_at])
@@ -203,11 +251,11 @@ defmodule Codebattle.Tournament do
203251
end
204252

205253
def access_types, do: @access_types
206-
def levels, do: @levels
207254
def public_types, do: @public_types
208255
def ranking_types, do: @ranking_types
209256
def score_strategies, do: @score_strategies
210257
def task_providers, do: @task_providers
211258
def task_strategies, do: @task_strategies
212259
def types, do: @types
260+
def grade_points, do: @grade_points
213261
end
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
defmodule Codebattle.Tournament.UpcomingRunner do
2+
@moduledoc """
3+
This module is responsible for running every minute tournaments from schedule
4+
"""
5+
use GenServer
6+
7+
alias Codebattle.Tournament
8+
9+
@tournament_run_upcoming Application.compile_env(:codebattle, :tournament_run_upcoming)
10+
@worker_timeout 30_000
11+
12+
@upcoming_time_before_live_mins 10_000
13+
14+
@spec start_link([]) :: GenServer.on_start()
15+
def start_link(_) do
16+
GenServer.start_link(__MODULE__, :noop, name: __MODULE__)
17+
end
18+
19+
@impl GenServer
20+
def init(_state) do
21+
if @tournament_run_upcoming do
22+
Process.send_after(self(), :run_upcoming, @worker_timeout)
23+
end
24+
25+
{:ok, :noop}
26+
end
27+
28+
@impl GenServer
29+
def handle_info(:run_upcoming, state) do
30+
run_upcoming()
31+
start_or_cancel_waiting_participants()
32+
33+
Process.send_after(self(), :run_upcoming, @worker_timeout)
34+
35+
{:noreply, state}
36+
end
37+
38+
def handle_info(_, state), do: {:noreply, state}
39+
40+
def run_upcoming do
41+
case Tournament.Context.get_upcoming_to_live_candidate(@upcoming_time_before_live_mins) do
42+
%Tournament{} = tournament ->
43+
Tournament.Context.move_upcoming_to_live(tournament)
44+
:ok
45+
46+
_ ->
47+
:noop
48+
end
49+
end
50+
51+
def start_or_cancel_waiting_participants do
52+
case Tournament.Context.get_waiting_participants_to_start_candidates() do
53+
tournaments when is_list(tournaments) ->
54+
Enum.each(
55+
tournaments,
56+
fn
57+
%{players_count: pc} = t when pc > 0 ->
58+
Tournament.Context.handle_event(t.id, :start, %{})
59+
60+
%{players_count: 0} = t ->
61+
Tournament.Context.handle_event(t.id, :cancel, %{})
62+
end
63+
)
64+
65+
:ok
66+
67+
_ ->
68+
:noop
69+
end
70+
end
71+
end

services/app/apps/codebattle/lib/codebattle/user.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ defmodule Codebattle.User do
3737
:lang,
3838
:locale,
3939
:name,
40+
:points,
4041
:rank,
4142
:rating,
4243
:sound_settings,
@@ -69,6 +70,7 @@ defmodule Codebattle.User do
6970
field(:locale, :string)
7071
field(:name, :string)
7172
field(:password_hash, :string)
73+
field(:points, :integer, default: 0)
7274
field(:public_id, :binary_id)
7375
field(:rank, :integer, default: 5432)
7476
field(:rating, :integer, default: 1200)
@@ -147,6 +149,7 @@ defmodule Codebattle.User do
147149
lang: Application.get_env(:codebattle, :default_lang_slug),
148150
rating: 0,
149151
locale: "en",
152+
points: 0,
150153
rank: 0,
151154
sound_settings: %SoundSettings{}
152155
}

0 commit comments

Comments
 (0)