Skip to content

Commit 3ceca56

Browse files
committed
Fix tournament
1 parent cf83642 commit 3ceca56

File tree

13 files changed

+173
-641
lines changed

13 files changed

+173
-641
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ defmodule Codebattle.Game.Context do
231231
end
232232
end
233233

234-
@spec trigger_timeout(game_id) :: :ok
234+
@spec trigger_timeout(game_id) :: {:ok, Game.t()}
235235
def trigger_timeout(game_id) do
236236
game_id |> get_game!() |> Engine.trigger_timeout()
237237
end

services/app/apps/codebattle/lib/codebattle/game/engine.ex

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,12 @@ defmodule Codebattle.Game.Engine do
356356
|> Repo.update!()
357357
end
358358

359+
@spec update_game!(Game.t()) :: Game.t()
360+
@spec update_game!(Game.t(), map()) :: Game.t()
359361
def update_game!(%Game{} = game) do
360362
case Repo.get(Game, game.id) do
361363
nil ->
362-
:ok
364+
game
363365

364366
game ->
365367
game
@@ -371,7 +373,7 @@ defmodule Codebattle.Game.Engine do
371373
def update_game!(%Game{} = game, params) do
372374
case Repo.get(Game, game.id) do
373375
nil ->
374-
:ok
376+
game
375377

376378
game ->
377379
game
@@ -386,37 +388,40 @@ defmodule Codebattle.Game.Engine do
386388

387389
def trigger_timeout(%Game{state: "game_over"} = game) do
388390
terminate_game_after(game, 1)
391+
{:ok, game}
389392
end
390393

391394
def trigger_timeout(%Game{} = game) do
392395
Logger.debug("Trigger timeout for game: #{game.id}")
393396
{:ok, {old_game_state, new_game}} = fire_transition(game.id, :timeout, %{})
394397

395-
case {old_game_state, new_game.state} do
396-
{old_state, "timeout"}
397-
when old_state in ["waiting_opponent", "playing"] ->
398-
Codebattle.PubSub.broadcast("game:finished", %{game: new_game})
399-
400-
update_game!(new_game, %{
401-
state: get_state(new_game),
402-
players: get_game_players(new_game),
403-
duration_sec: new_game.duration_sec,
404-
finishes_at: new_game.finishes_at
405-
})
398+
new_game =
399+
case {old_game_state, new_game.state} do
400+
{old_state, "timeout"}
401+
when old_state in ["waiting_opponent", "playing"] ->
402+
Codebattle.PubSub.broadcast("game:finished", %{game: new_game})
403+
404+
update_game!(new_game, %{
405+
state: get_state(new_game),
406+
players: get_game_players(new_game),
407+
duration_sec: new_game.duration_sec,
408+
finishes_at: new_game.finishes_at
409+
})
406410

407-
if game.tournament_id do
408-
terminate_game_after(game, 1)
409-
else
410-
terminate_game_after(game, 15)
411-
end
411+
if game.tournament_id do
412+
terminate_game_after(game, 1)
413+
else
414+
terminate_game_after(game, 15)
415+
end
412416

413-
store_playbook_async(game)
417+
store_playbook_async(game)
418+
new_game
414419

415-
_ ->
416-
:noop
417-
end
420+
_ ->
421+
new_game
422+
end
418423

419-
:ok
424+
{:ok, new_game}
420425
end
421426

422427
defp maybe_fire_playing_game_side_effects(%{state: "playing"} = game) do

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ defmodule Codebattle.Tournament.GlobalSupervisor do
66

77
require Logger
88

9+
@tournament_info_table :tournament_info_cache
10+
911
def start_link(_) do
1012
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
1113
end
1214

1315
@impl true
1416
def init(_) do
17+
if :ets.whereis(@tournament_info_table) == :undefined do
18+
:ets.new(@tournament_info_table, [:named_table, :set, :public, read_concurrency: true])
19+
end
20+
1521
Supervisor.init([], strategy: :one_for_one)
1622
end
1723

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ defmodule Codebattle.Tournament.Server do
102102
# SERVER
103103
def init(tournament_id) do
104104
# Create tournament_info_cache table if it doesn't exist
105-
if :ets.whereis(@tournament_info_table) == :undefined do
106-
:ets.new(@tournament_info_table, [:named_table, :set, :public, read_concurrency: true])
107-
end
108105

109106
players_table = Tournament.Players.create_table(tournament_id)
110107
matches_table = Tournament.Matches.create_table(tournament_id)

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

Lines changed: 26 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -391,21 +391,6 @@ defmodule Codebattle.Tournament.Base do
391391
match = get_match(tournament, params.ref)
392392
winner_id = pick_game_winner_id(match.player_ids, params.player_results)
393393

394-
player_results =
395-
Map.new(params.player_results, fn {player_id, result} ->
396-
{player_id,
397-
Map.put(
398-
result,
399-
:score,
400-
get_score(
401-
tournament.score_strategy,
402-
match.level,
403-
result.result_percent,
404-
params.duration_sec
405-
)
406-
)}
407-
end)
408-
409394
params.player_results
410395
|> Map.keys()
411396
|> Enum.each(fn player_id ->
@@ -414,20 +399,13 @@ defmodule Codebattle.Tournament.Base do
414399
if player do
415400
player = %{
416401
player
417-
| score: player.score + player_results[player_id].score,
418-
lang: params.player_results[player_id].lang,
402+
| lang: params.player_results[player_id].lang,
419403
wins_count:
420404
player.wins_count +
421-
if(player_results[player_id].result == "won", do: 1, else: 0)
405+
if(params.player_results[player_id].result == "won", do: 1, else: 0)
422406
}
423407

424408
Tournament.Players.put_player(tournament, player)
425-
426-
Tournament.Ranking.update_player_result(
427-
tournament,
428-
player,
429-
player_results[player_id].score
430-
)
431409
end
432410
end)
433411

@@ -436,7 +414,7 @@ defmodule Codebattle.Tournament.Base do
436414
| state: params.game_state,
437415
winner_id: winner_id,
438416
duration_sec: params.duration_sec,
439-
player_results: player_results,
417+
player_results: params.player_results,
440418
finished_at: DateTime.utc_now(:second)
441419
}
442420

@@ -861,7 +839,6 @@ defmodule Codebattle.Tournament.Base do
861839
|> maybe_finish_waiting_room()
862840
|> set_stats()
863841
|> set_winner_ids()
864-
# |> db_save!()
865842
|> maybe_save_event_results()
866843
|> db_save!(:with_ets)
867844
|> broadcast_tournament_finished()
@@ -1056,99 +1033,40 @@ defmodule Codebattle.Tournament.Base do
10561033
tournament
10571034
else
10581035
# Process matches in parallel with Task.async_stream
1059-
match_results =
1060-
matches_to_finish
1061-
|> Task.async_stream(
1062-
fn match ->
1063-
duration_sec = NaiveDateTime.diff(finished_at, match.started_at)
1064-
1065-
# Get player results and trigger timeout
1066-
player_results = improve_player_results(tournament, match, duration_sec)
1067-
Game.Context.trigger_timeout(match.game_id)
1068-
1069-
# Create new match with timeout state
1070-
new_match = %{
1071-
match
1072-
| state: "timeout",
1073-
player_results: player_results,
1074-
duration_sec: duration_sec,
1075-
finished_at: finished_at
1076-
}
1077-
1078-
# Return match and player data for batch processing
1079-
{new_match, player_results}
1080-
end,
1081-
max_concurrency: System.schedulers_online() * 2,
1082-
timeout: 10_000
1083-
)
1084-
|> Enum.to_list()
1036+
matches_to_finish
1037+
|> Task.async_stream(
1038+
fn match ->
1039+
# trigger game timeout and set player results
1040+
{:ok, game} = Game.Context.trigger_timeout(match.game_id)
1041+
1042+
# Create new match with timeout state
1043+
new_match = %{
1044+
match
1045+
| state: "timeout",
1046+
player_results: Game.Helpers.get_player_results(game),
1047+
duration_sec: game.duration_sec,
1048+
finished_at: finished_at
1049+
}
10851050

1086-
# Batch update matches and collect player updates
1087-
player_updates =
1088-
Enum.reduce(match_results, %{}, fn {:ok, {new_match, player_results}}, acc ->
1089-
# Update match in tournament
1051+
# Return match and player data for batch processing
10901052
Tournament.Matches.put_match(tournament, new_match)
10911053

10921054
# Broadcast match update
10931055
Codebattle.PubSub.broadcast("tournament:match:upserted", %{
10941056
tournament: tournament,
10951057
match: new_match
10961058
})
1097-
1098-
# Collect player updates
1099-
Enum.reduce(player_results, acc, fn {player_id, result}, player_acc ->
1100-
player_score = result.score
1101-
player_lang = result.lang
1102-
1103-
# credo:disable-for-next-line Credo.Check.Refactor.Nesting
1104-
Map.update(player_acc, player_id, %{score: player_score, lang: player_lang}, fn existing ->
1105-
%{score: existing.score + player_score, lang: player_lang}
1106-
end)
1107-
end)
1108-
end)
1109-
1110-
# Batch update player scores
1111-
Enum.each(player_updates, fn {player_id, updates} ->
1112-
player = Tournament.Players.get_player(tournament, player_id)
1113-
1114-
if player do
1115-
Tournament.Players.put_player(tournament, %{
1116-
player
1117-
| score: player.score + updates.score,
1118-
lang: updates.lang
1119-
})
1120-
end
1121-
end)
1059+
end,
1060+
max_concurrency: System.schedulers_online() * 2,
1061+
timeout: 10_000
1062+
)
1063+
|> Stream.run()
11221064

11231065
tournament
11241066
end
11251067
end
11261068

1127-
defp improve_player_results(tournament, match, duration_sec) do
1128-
case Game.Context.fetch_game(match.game_id) do
1129-
{:ok, %{is_live: true} = game} ->
1130-
game
1131-
|> Game.Helpers.get_player_results()
1132-
|> Map.new(fn {player_id, result} ->
1133-
{player_id,
1134-
Map.put(
1135-
result,
1136-
:score,
1137-
get_score(
1138-
tournament.score_strategy,
1139-
match.level,
1140-
result.result_percent,
1141-
duration_sec
1142-
)
1143-
)}
1144-
end)
1145-
1146-
{:error, _reason} ->
1147-
%{}
1148-
end
1149-
end
1150-
1151-
defp maybe_add_award(game_params, tournament) do
1069+
defp maybe_add_award(game_params, %{type: "show"} = tournament) do
11521070
tournament.meta
11531071
|> Map.get(:rounds_config)
11541072
|> case do
@@ -1168,6 +1086,8 @@ defmodule Codebattle.Tournament.Base do
11681086
end
11691087
end
11701088

1089+
defp maybe_add_award(game_params, _tournament), do: game_params
1090+
11711091
defp maybe_set_free_task(game_params, %Tournament{type: "show", task_strategy: "sequential"} = tournament, player) do
11721092
task_id = Enum.at(tournament.round_task_ids, Enum.count(player.task_ids))
11731093

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ defmodule Codebattle.Tournament.Swiss do
2020
def reset_meta(meta), do: meta
2121

2222
@impl Tournament.Base
23-
def finish_round_after_match?(%{current_round_position: current_round_position} = tournament) do
24-
matches = get_round_matches(tournament, current_round_position)
23+
def finish_round_after_match?(tournament) do
24+
if tournament.players_count < 128 do
25+
matches = get_round_matches(tournament, tournament.current_round_position)
2526

26-
Enum.all?(matches, &(&1.state != "playing"))
27+
Enum.all?(matches, &(&1.state != "playing"))
28+
else
29+
false
30+
end
2731
end
2832

2933
@impl Tournament.Base

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ defmodule Codebattle.Tournament.TournamentResult do
6464
task_id,
6565
count(*),
6666
case
67-
when level = 'elementary' THEN 30.0
67+
when level = 'elementary' THEN 50.0
6868
when level = 'easy' THEN 100.0
69-
when level = 'medium' THEN 300.0
70-
when level = 'hard' THEN 1000.0
69+
when level = 'medium' THEN 150.0
70+
when level = 'hard' THEN 200.0
7171
end AS base_score,
7272
array_agg(duration_sec),
7373
max(duration_sec) as max_duration,
@@ -226,8 +226,8 @@ defmodule Codebattle.Tournament.TournamentResult do
226226
},
227227
where: r.tournament_id == ^tournament.id,
228228
group_by: [r.user_id, r.user_name, c.id],
229-
order_by: [asc_nulls_first: sum(r.score)],
230-
windows: [overall_partition: [order_by: [asc_nulls_first: sum(r.score), asc: sum(r.duration_sec)]]]
229+
order_by: [desc: sum(r.score)],
230+
windows: [overall_partition: [order_by: [desc_nulls_last: sum(r.score), asc: sum(r.duration_sec)]]]
231231
)
232232

233233
Repo.all(query)

services/app/apps/codebattle/lib/codebattle_web/channels/game_channel.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ defmodule CodebattleWeb.GameChannel do
2525

2626
active_game_id = Tournament.Helpers.get_active_game_id(tournament, user_id)
2727

28+
# TODO: think about active_game_id
2829
{user_id, active_game_id}
2930
else
3031
{nil, nil}

services/app/apps/codebattle/lib/codebattle_web/live/components/tournament/create_form.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,7 @@ defmodule CodebattleWeb.Live.Tournament.CreateFormComponent do
264264
<div class="d-flex flex-column flex-md-row flex-lg-row flex-xl-row justify-content-between mt-3">
265265
<div class="d-flex flex-column justify-content-between w-auto">
266266
<%= label(f, :rounds_limit) %>
267-
<%= select(f, :rounds_limit, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 137, 200],
268-
class: "custom-select"
269-
) %>
267+
<%= select(f, :rounds_limit, Enum.to_list(1..42), class: "custom-select") %>
270268
<%= error_tag(f, :rounds_limit) %>
271269
</div>
272270
</div>

services/app/apps/codebattle/test/codebattle/game/context_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ defmodule Codebattle.Game.ContextTest do
2727
game_topic = "game:#{game_id}"
2828
Codebattle.PubSub.subscribe(game_topic)
2929

30-
:ok = Game.Context.trigger_timeout(game_id)
30+
{:ok, _new_game} = Game.Context.trigger_timeout(game_id)
3131

3232
assert_received %Message{
3333
event: "game:finished",

0 commit comments

Comments
 (0)