Skip to content

Commit cd03b71

Browse files
committed
fix
1 parent a5f5205 commit cd03b71

2 files changed

Lines changed: 104 additions & 4 deletions

File tree

apps/codebattle/lib/codebattle/tournament/strategy/top200.ex

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,17 +216,22 @@ defmodule Codebattle.Tournament.Top200 do
216216
end
217217
end
218218

219-
# Финал турнира. Места 9..N уже корректны: ранжирование после раунда 7 расставило
220-
# игроков по сумме очков, а вылетевшие после 5-го раунда (раунд 4) плей-офф не играли,
221-
# поэтому их сумма = очки за 5 раундов и они стоят на 9..N. Полный пересчёт результатов
222-
# (reset+rebuild) не нужен — переставляем ТОЛЬКО топ-8: их места задаёт сетка финалов.
219+
# Финал турнира. Полный пересчёт результатов (reset+rebuild) не нужен — расставляем места
220+
# в два шага:
221+
# 1) топ-8 по сетке финалов получают места 1..8 (assign_final_bracket_places);
222+
# 2) всем остальным проставляем места 9..N по сумме очков (assign_eliminated_places).
223+
#
224+
# Шаг 2 ОБЯЗАТЕЛЕН: set_ranking перед этим нумерует ВСЁ поле (1..N) по сумме очков, и эти
225+
# места вылетевших пересекаются с 1..8 из сетки → в лидерборде появляются дубли мест
226+
# (топ-8 vs вылетевшие). Поэтому вылетевших надо перенумеровать ровно с 9.
223227
#
224228
# Результаты раунда 7 здесь уже в TournamentResult: на любом пути завершения их пишет
225229
# finish_tournament (base) через upsert_results перед вызовом compute_final_standings.
226230
# Поэтому assign_final_bracket_places определяет победителей пар по реальным очкам.
227231
def compute_final_standings(tournament) do
228232
tournament
229233
|> assign_final_bracket_places()
234+
|> assign_eliminated_places()
230235
|> recalculate_player_wins_count()
231236
end
232237

@@ -262,6 +267,38 @@ defmodule Codebattle.Tournament.Top200 do
262267
tournament
263268
end
264269

270+
# Все, кроме топ-8 финалистов, получают места 9..N по сумме очков (тай-брейк: меньшее
271+
# общее время, затем меньший id — как в set_ranking). Без этого вылетевшие сохраняют
272+
# места из set_ranking по всему полю (1..N), пересекающиеся с 1..8 сетки → дубли в
273+
# лидерборде. Перенумеровываем ровно тех, кого попадёт в tournament_user_results
274+
# (не-боты, не-читеры), чтобы места шли подряд без дыр; читеров доставляет upsert.
275+
defp assign_eliminated_places(tournament) do
276+
bracket_ids = final_bracket_ids(tournament)
277+
cheater_ids = MapSet.new(tournament.cheater_ids || [])
278+
start_place = MapSet.size(bracket_ids) + 1
279+
280+
tournament
281+
|> get_players()
282+
|> Enum.reject(&(&1.is_bot || MapSet.member?(bracket_ids, &1.id) || MapSet.member?(cheater_ids, &1.id)))
283+
|> Enum.sort_by(fn p -> {-score_value(p.score), p.total_duration_sec || 0, p.id} end)
284+
|> Enum.with_index(start_place)
285+
|> Enum.each(fn {player, place} ->
286+
Tournament.Players.put_player(tournament, %{player | place: place})
287+
end)
288+
289+
tournament
290+
end
291+
292+
# id восьми финалистов плей-офф (пары раунда 7) — те, кому assign_final_bracket_places
293+
# уже выдал места 1..8. Если раунд 7 не доигран, пар нет → пустое множество, и тогда
294+
# assign_eliminated_places честно перенумеровывает всё поле с 1.
295+
defp final_bracket_ids(tournament) do
296+
case round_player_pairs(tournament, 7) do
297+
[_, _, _, _] = pairs -> pairs |> List.flatten() |> MapSet.new()
298+
_ -> MapSet.new()
299+
end
300+
end
301+
265302
# Финальное место в плей-офф задаёт И place, И draw_index = 9 - place. Так 1-е место
266303
# получает уникальный максимум draw_index (8), и подсветка «живых» (draw_index == max)
267304
# оставляет ровно чемпиона. Места 2..8 идут по убыванию, как и положено сетке.

apps/codebattle/test/codebattle/tournament/entire/top200_test.exs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,69 @@ defmodule Codebattle.Tournament.Entire.Top200Test do
350350
assert Tournament.Players.get_player(tournament, 2).score == 10
351351
assert Tournament.Players.get_player(tournament, 9).score == 50
352352
end
353+
354+
test "вылетевшие перенумеровываются в 9..N — нет дублей мест с топ-8 сетки" do
355+
# Регрессия: set_ranking нумерует ВСЁ поле (1..N) по сумме очков, поэтому вылетевшие
356+
# приходят в compute_final_standings с местами, пересекающимися с 1..8 сетки (на проде
357+
# это давало дубли мест 3..8 у топ-8 в лидерборде). Должны стать 9..N без коллизий.
358+
tournament = insert_top200_tournament()
359+
players_table = Tournament.Players.create_table(tournament.id)
360+
361+
finals =
362+
inline_matches([
363+
%Match{id: 1, player_ids: [1, 2], round_position: 7, state: "game_over"},
364+
%Match{id: 2, player_ids: [3, 4], round_position: 7, state: "game_over"},
365+
%Match{id: 3, player_ids: [5, 6], round_position: 7, state: "game_over"},
366+
%Match{id: 4, player_ids: [7, 8], round_position: 7, state: "game_over"}
367+
])
368+
369+
tournament = %{tournament | players_table: players_table, current_round_position: 7, matches: finals}
370+
371+
# Победители финалов по очкам раунда 7: 1, 3, 5, 7.
372+
record_scores(tournament.id, 7, [
373+
{1, 100},
374+
{2, 10},
375+
{3, 100},
376+
{4, 10},
377+
{5, 100},
378+
{6, 10},
379+
{7, 100},
380+
{8, 10}
381+
])
382+
383+
Enum.each(1..8, fn id ->
384+
Tournament.Players.put_player(
385+
tournament,
386+
Player.new!(%{id: id, name: "p#{id}", state: "active", score: 0, place: 0})
387+
)
388+
end)
389+
390+
# Вылетевшие 9..14: НАРОЧНО с местами 3..8 (как их пронумеровал set_ranking по полю) —
391+
# ровно те, что пересекаются с сеткой. Сумма очков убывает с id → ожидаемый порядок 9..14.
392+
Enum.each([{9, 60, 3}, {10, 50, 4}, {11, 40, 5}, {12, 30, 6}, {13, 20, 7}, {14, 10, 8}], fn {id, score, place} ->
393+
Tournament.Players.put_player(
394+
tournament,
395+
Player.new!(%{id: id, name: "p#{id}", state: "active", score: score, place: place})
396+
)
397+
end)
398+
399+
Top200.compute_final_standings(tournament)
400+
401+
place_of = fn id -> Tournament.Players.get_player(tournament, id).place end
402+
403+
# Топ-8 — по сетке финалов (победитель пары — лучшее место).
404+
assert place_of.(1) == 1
405+
assert place_of.(3) == 3
406+
assert place_of.(5) == 5
407+
assert place_of.(7) == 7
408+
409+
# Вылетевшие — строго 9..14 по убыванию суммы очков, БЕЗ пересечения с 1..8.
410+
assert Enum.map(9..14, place_of) == [9, 10, 11, 12, 13, 14]
411+
412+
# Главное: места всего поля уникальны и образуют ровно 1..14 — дублей нет.
413+
all_places = Enum.map(1..14, place_of)
414+
assert Enum.sort(all_places) == Enum.to_list(1..14)
415+
end
353416
end
354417

355418
describe "TournamentUserResult.upsert_results/1 — top200 (регрессия на потерю рейтинга)" do

0 commit comments

Comments
 (0)