Skip to content

Commit 06e2688

Browse files
committed
Improve tournaments
1 parent 428a5e8 commit 06e2688

18 files changed

Lines changed: 472 additions & 6 deletions

File tree

apps/codebattle/assets/js/widgets/middlewares/TournamentAdmin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@ export const restartTournament = () => {
282282
channel.push("tournament:restart", {});
283283
};
284284

285+
export const retryTournament = () => {
286+
channel.push("tournament:retry", {});
287+
};
288+
285289
export const startRoundTournament = () => {
286290
channel.push("tournament:start_round", {});
287291
};

apps/codebattle/assets/js/widgets/pages/game/EditorContainer.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ function EditorContainer({
9494
const isPremium = useSelector(selectors.currentUserIsPremiumSelector);
9595
const gameId = useSelector(selectors.gameIdSelector);
9696
const gameMode = useSelector(selectors.gameModeSelector);
97-
const { tournamentId, startsAt } = useSelector(selectors.gameStatusSelector);
97+
const { tournamentId, startsAt, hideBannedPlayerControls } = useSelector(
98+
selectors.gameStatusSelector,
99+
);
98100
const subscriptionType = useSelector(selectors.subscriptionTypeSelector);
99101

100102
const currentUserId = useSelector(selectors.currentUserIdSelector);
@@ -214,6 +216,7 @@ function EditorContainer({
214216
isAdmin,
215217
isPremium,
216218
actionBtnsProps,
219+
hideToolbarControls: hideBannedPlayerControls,
217220
...userSettings,
218221
};
219222

apps/codebattle/assets/js/widgets/pages/game/EditorToolbar.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function EditorToolbar({
4040
langPickerStatus,
4141
actionBtnsProps,
4242
showControlBtns,
43+
hideToolbarControls = false,
4344
isAdmin = false,
4445
isHistory = false,
4546
}) {
@@ -53,16 +54,18 @@ function EditorToolbar({
5354
<div className={toolbarClassNames} role="toolbar">
5455
<div className="d-flex justify-content-between">
5556
<div className={editorSettingClassNames} role="group" aria-label="Editor settings">
56-
<LanguagePicker editor={editor} status={langPickerStatus} />
57+
{!hideToolbarControls && <LanguagePicker editor={editor} status={langPickerStatus} />}
5758
</div>
58-
{showControlBtns && !isHistory && <ModeButtons player={player} />}
59+
{showControlBtns && !isHistory && !hideToolbarControls && (
60+
<ModeButtons player={player} />
61+
)}
5962
</div>
6063

6164
<div className="d-flex justify-content-between">
62-
{showControlBtns && !isHistory && editorState !== "banned" && (
65+
{showControlBtns && !isHistory && !hideToolbarControls && editorState !== "banned" && (
6366
<GameActionButtons {...actionBtnsProps} />
6467
)}
65-
{!showControlBtns && (
68+
{!showControlBtns && !hideToolbarControls && (
6669
<div className="py-2" role="group" aria-label="Report actions">
6770
<GameReportButton userId={player.id} gameId={gameId} />
6871
{isAdmin && (

apps/codebattle/assets/js/widgets/pages/tournament/EditTournament.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ function EditTournament({ tournamentId, taskPackNames = [], userTimezone = "UTC"
198198
break_duration_seconds: tournament.breakDurationSeconds || 42,
199199
use_chat: tournament.useChat !== undefined ? tournament.useChat : true,
200200
use_clan: tournament.useClan !== undefined ? tournament.useClan : false,
201+
exclude_banned_players:
202+
tournament.excludeBannedPlayers !== undefined ? tournament.excludeBannedPlayers : false,
201203
ranking_type: tournament.rankingType || "by_user",
202204
score_strategy: tournament.scoreStrategy || "75_percentile",
203205
meta_json: tournament.meta ? JSON.stringify(tournament.meta, null, 2) : "{}",

apps/codebattle/assets/js/widgets/pages/tournament/TournamentForm.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ function TournamentForm({
7474
break_duration_seconds: initialValues.break_duration_seconds || 42,
7575
use_chat: initialValues.use_chat !== undefined ? initialValues.use_chat : true,
7676
use_clan: initialValues.use_clan !== undefined ? initialValues.use_clan : false,
77+
exclude_banned_players:
78+
initialValues.exclude_banned_players !== undefined
79+
? initialValues.exclude_banned_players
80+
: false,
7781
ranking_type: initialValues.ranking_type || "by_user",
7882
score_strategy: initialValues.score_strategy || "75_percentile",
7983
meta_json: initialValues.meta_json || "{}",
@@ -283,6 +287,20 @@ function TournamentForm({
283287
Use Clan
284288
</label>
285289
</div>
290+
291+
<div className="form-check">
292+
<input
293+
type="checkbox"
294+
id="exclude_banned_players"
295+
name="exclude_banned_players"
296+
className="form-check-input"
297+
checked={formData.exclude_banned_players}
298+
onChange={handleChange}
299+
/>
300+
<label htmlFor="exclude_banned_players" className="form-check-label text-white">
301+
Exclude Banned Players
302+
</label>
303+
</div>
286304
</div>
287305
</div>
288306
</div>
@@ -651,6 +669,7 @@ TournamentForm.propTypes = {
651669
break_duration_seconds: PropTypes.number,
652670
use_chat: PropTypes.bool,
653671
use_clan: PropTypes.bool,
672+
exclude_banned_players: PropTypes.bool,
654673
ranking_type: PropTypes.string,
655674
score_strategy: PropTypes.string,
656675
meta_json: PropTypes.string,

apps/codebattle/assets/js/widgets/pages/tournament/TournamentMainControlButtons.jsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import CustomEventStylesContext from "@/components/CustomEventStylesContext";
1212
import {
1313
cancelTournament,
1414
restartTournament as handleRestartTournament,
15+
retryTournament as handleRetryTournament,
1516
finishRoundTournament as handleFinishRoundTournament,
1617
openUpTournament as handleOpenUpTournament,
1718
showTournamentResults as handleShowResults,
@@ -50,6 +51,7 @@ function TournamentMainControlButtons({
5051
const confirmBtnRef = useRef(null);
5152
const hasCustomEventStyle = useContext(CustomEventStylesContext);
5253
const [restartConfirmationModalShowing, setRestartConfirmationModalShowing] = useState(false);
54+
const [retryConfirmationModalShowing, setRetryConfirmationModalShowing] = useState(false);
5355

5456
const handleStartTournament = useCallback(() => {
5557
handleStartRound("firstRound");
@@ -70,6 +72,16 @@ function TournamentMainControlButtons({
7072
handleRestartTournament();
7173
closeRestartConfirmationModal();
7274
}, [closeRestartConfirmationModal]);
75+
const openRetryConfirmationModal = useCallback(() => {
76+
setRetryConfirmationModalShowing(true);
77+
}, []);
78+
const closeRetryConfirmationModal = useCallback(() => {
79+
setRetryConfirmationModalShowing(false);
80+
}, []);
81+
const confirmRetryTournament = useCallback(() => {
82+
handleRetryTournament();
83+
closeRetryConfirmationModal();
84+
}, [closeRetryConfirmationModal]);
7385

7486
const restartBtnClassName = cn(
7587
"btn text-nowrap ml-lg-2 rounded-left btn-secondary cb-btn-secondary",
@@ -126,6 +138,33 @@ function TournamentMainControlButtons({
126138
</div>
127139
</Modal.Footer>
128140
</Modal>
141+
<Modal
142+
show={retryConfirmationModalShowing}
143+
onHide={closeRetryConfirmationModal}
144+
contentClassName="cb-bg-panel cb-text"
145+
>
146+
<Modal.Header className="cb-border-color" closeButton>
147+
<Modal.Title>Retry tournament</Modal.Title>
148+
</Modal.Header>
149+
<Modal.Body className="cb-border-color">
150+
<div className="d-flex flex-column">
151+
<h4 className="mb-3">Are you sure you want to retry this tournament?</h4>
152+
<p className="mb-0 text-muted">
153+
This will clear tournament games and results, then restore the current player roster.
154+
</p>
155+
</div>
156+
</Modal.Body>
157+
<Modal.Footer className="cb-border-color">
158+
<div className="d-flex justify-content-between w-100">
159+
<Button onClick={closeRetryConfirmationModal} className={cancelBtnClassName}>
160+
Cancel
161+
</Button>
162+
<Button onClick={confirmRetryTournament} className={confirmBtnClassName}>
163+
Retry tournament
164+
</Button>
165+
</div>
166+
</Modal.Footer>
167+
</Modal>
129168
{!streamMode && (
130169
<>
131170
{canStartRound ? (
@@ -235,6 +274,16 @@ function TournamentMainControlButtons({
235274
<FontAwesomeIcon className="mr-2" icon="cog" />
236275
Tournament details
237276
</Dropdown.Item>
277+
<Dropdown.Item
278+
as="button"
279+
disabled={disabled}
280+
key="retry"
281+
className="cb-dropdown-item"
282+
onSelect={openRetryConfirmationModal}
283+
>
284+
<FontAwesomeIcon className="mr-2" icon="redo" />
285+
Retry
286+
</Dropdown.Item>
238287
<Dropdown.Item
239288
as="button"
240289
disabled={disabled}

apps/codebattle/assets/js/widgets/slices/initial.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const defaultGameStatusState = {
107107
timeoutSeconds: null,
108108
durationSec: null,
109109
finishesAt: null,
110+
hideBannedPlayerControls: false,
110111
rematchState: null,
111112
rematchInitiatorId: null,
112113
checking: {},

apps/codebattle/assets/js/widgets/utils/gameRoom.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const getGameStatus = ({
1111
timeoutSeconds,
1212
durationSec,
1313
finishesAt,
14+
hideBannedPlayerControls,
1415
rematchState,
1516
tournamentId,
1617
rematchInitiatorId,
@@ -25,6 +26,7 @@ export const getGameStatus = ({
2526
timeoutSeconds,
2627
durationSec,
2728
finishesAt,
29+
hideBannedPlayerControls,
2830
rematchState,
2931
rematchInitiatorId,
3032
tournamentId,

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ defmodule Codebattle.Tournament.Context do
77
alias Codebattle.Game
88
alias Codebattle.Repo
99
alias Codebattle.Tournament
10+
alias Codebattle.Tournament.Round
1011
alias Codebattle.User
12+
alias Codebattle.UserGameReport
1113
alias Runner.AtomizedMap
1214

1315
@type tournament_id :: pos_integer() | String.t()
@@ -354,6 +356,23 @@ defmodule Codebattle.Tournament.Context do
354356
:ok
355357
end
356358

359+
@spec retry(Tournament.t()) :: :ok
360+
def retry(tournament) do
361+
tournament_info = Tournament.Server.get_tournament_info(tournament.id)
362+
363+
Game.Context.terminate_tournament_games(tournament.id)
364+
365+
:timer.sleep(59)
366+
367+
Tournament.GlobalSupervisor.terminate_tournament(tournament.id)
368+
drop_tournament_tables(tournament_info)
369+
clear_tournament_info_cache(tournament.id)
370+
clear_tournament_history(tournament.id)
371+
Tournament.GlobalSupervisor.start_tournament(tournament)
372+
Codebattle.PubSub.broadcast("tournament:restarted", %{tournament: tournament})
373+
:ok
374+
end
375+
357376
@spec move_upcoming_to_live(Tournament.t()) :: :ok
358377
def move_upcoming_to_live(tournament) do
359378
tournament =
@@ -500,6 +519,14 @@ defmodule Codebattle.Tournament.Context do
500519
end)
501520
end
502521

522+
defp clear_tournament_history(tournament_id) do
523+
Repo.delete_all(from(ugr in UserGameReport, where: ugr.tournament_id == ^tournament_id))
524+
Tournament.TournamentResult.clean_results(tournament_id)
525+
Tournament.TournamentUserResult.clean_results(tournament_id)
526+
Repo.delete_all(from(g in Game, where: g.tournament_id == ^tournament_id))
527+
Repo.delete_all(from(r in Round, where: r.tournament_id == ^tournament_id))
528+
end
529+
503530
def mark_as_live(tournament), do: Map.put(tournament, :is_live, true)
504531

505532
defp get_module(%{type: "top200"}), do: Tournament.Top200

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ defmodule Codebattle.Tournament.Base do
4848
Enum.reduce(users, tournament, &add_player(&2, &1))
4949
end
5050

51+
defp maybe_add_player_clan(tournament, player) do
52+
if tournament.use_clan do
53+
Tournament.Clans.add_players_clan(tournament, player)
54+
end
55+
end
56+
5157
def join(%{state: "waiting_participants"} = tournament, %{users: users} = params) do
5258
player_params = Map.delete(params, :users)
5359
Enum.reduce(users, tournament, &join(&2, Map.put(player_params, :user, &1)))
@@ -318,6 +324,46 @@ defmodule Codebattle.Tournament.Base do
318324

319325
def restart(tournament, _user), do: tournament
320326

327+
def retry(tournament, %{user: user}) do
328+
if can_moderate?(tournament, user) do
329+
Tournament.Round.disable_all_rounds(tournament.id)
330+
331+
reset_players = reset_players_for_retry(tournament)
332+
333+
Enum.each(reset_players, fn player ->
334+
Tournament.Players.put_player(tournament, player)
335+
Tournament.Ranking.add_new_player(tournament, player)
336+
maybe_add_player_clan(tournament, player)
337+
end)
338+
339+
tournament
340+
|> update_struct(%{
341+
meta: reset_meta(tournament.meta),
342+
matches: %{},
343+
break_state: "off",
344+
cheater_ids: [],
345+
players_count: Enum.count(reset_players),
346+
current_round_position: 0,
347+
current_round: nil,
348+
current_round_id: nil,
349+
last_round_ended_at: nil,
350+
last_round_started_at: nil,
351+
finished_at: nil,
352+
started_at: nil,
353+
starts_at: :second |> DateTime.utc_now() |> DateTime.add(300, :second),
354+
winner_ids: [],
355+
top_player_ids: [],
356+
state: "waiting_participants"
357+
})
358+
|> db_save!(:with_ets)
359+
|> tap(&broadcast_tournament_update/1)
360+
else
361+
tournament
362+
end
363+
end
364+
365+
def retry(tournament, _user), do: tournament
366+
321367
def start(tournament, params \\ %{})
322368

323369
def start(%{state: "waiting_participants"} = tournament, %{user: user} = params) do
@@ -798,6 +844,33 @@ defmodule Codebattle.Tournament.Base do
798844
tournament
799845
end
800846

847+
defp reset_players_for_retry(tournament) do
848+
tournament.players
849+
|> Map.values()
850+
|> Enum.reject(& &1.is_bot)
851+
|> Enum.map(&reset_player_for_retry/1)
852+
end
853+
854+
defp reset_player_for_retry(player) do
855+
player
856+
|> Map.merge(%{
857+
draw_index: 1,
858+
last_ranked_round_position: -1,
859+
matches_ids: [],
860+
max_draw_index: 0,
861+
place: 0,
862+
rank: 5432,
863+
rating: 1200,
864+
score: 0,
865+
state: "active",
866+
task_ids: [],
867+
total_duration_sec: 0,
868+
wins_count: 0,
869+
wr_joined_at: nil
870+
})
871+
|> Tournament.Player.new!()
872+
end
873+
801874
defp recalculate_player_wins_count(tournament) do
802875
wins_count_by_user = Tournament.TournamentResult.get_wins_count_by_user(tournament)
803876

0 commit comments

Comments
 (0)