Skip to content

Commit 95797a5

Browse files
committed
Add active game_id
1 parent af084e7 commit 95797a5

File tree

12 files changed

+163
-30
lines changed

12 files changed

+163
-30
lines changed

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ const initTournamentChannel = (dispatch, isAdminWidged = false) => {
2626
};
2727

2828
const onJoinSuccess = response => {
29+
console.log(response)
2930
if (isAdminWidged) {
31+
// Handle active_game_id if it exists in the response
32+
if (response.activeGameId) {
33+
dispatch(actions.setAdminActiveGameId(response.activeGameId));
34+
}
3035
dispatch(
3136
actions.setTournamentData({
3237
...response.tournament,
@@ -44,6 +49,7 @@ const initTournamentChannel = (dispatch, isAdminWidged = false) => {
4449
dispatch(actions.updateTournamentMatches(compact(response.matches)));
4550
dispatch(actions.setTournamentTaskList(compact(response.tasksInfo)));
4651
dispatch(actions.setReports(compact(response.reports)));
52+
4753
};
4854

4955
channel.join().receive('ok', onJoinSuccess).receive('error', onJoinFailure);
@@ -330,7 +336,11 @@ export const getTask = (taskId, onSuccess) => () => {
330336
});
331337
};
332338

333-
export const pushActiveMatchToStream = (gameId) => () => {
339+
export const pushActiveMatchToStream = (gameId) => (dispatch) => {
340+
// Update the Redux state immediately for instant UI feedback
341+
dispatch(actions.setAdminActiveGameId(gameId));
342+
343+
// Send the update to the server
334344
channel
335345
.push('tournament:stream:active_game', { gameId })
336346
.receive('error', error => console.error(error));

services/app/apps/codebattle/assets/js/widgets/pages/tournament/TournamentAdminWidget.jsx

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,48 @@ import { connectToTournament, requestMatchesForRound} from '../../middlewares/To
77
import * as selectors from '../../selectors';
88
import { pushActiveMatchToStream } from '../../middlewares/TournamentAdmin';
99

10+
// Define CSS for active game animation
11+
const activeGameStyles = `
12+
@keyframes pulse {
13+
0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); }
14+
70% { box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
15+
100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); }
16+
}
17+
18+
.active-game {
19+
position: relative;
20+
animation: pulse 1.5s infinite;
21+
border: 2px solid #ffc107 !important;
22+
}
23+
24+
.active-game-indicator {
25+
display: inline-block;
26+
margin-left: 3px;
27+
animation: rotate 1.5s linear infinite;
28+
}
29+
30+
@keyframes rotate {
31+
from { transform: rotate(0deg); }
32+
to { transform: rotate(360deg); }
33+
}
34+
`;
35+
1036
function TournamentAdminWidget() {
37+
// Add style element for animations
38+
useEffect(() => {
39+
const styleElement = document.createElement('style');
40+
styleElement.textContent = activeGameStyles;
41+
document.head.appendChild(styleElement);
42+
43+
return () => {
44+
document.head.removeChild(styleElement);
45+
};
46+
}, []);
1147
const tournamentId = Gon.getAsset('tournament_id');
1248
const dispatch = useDispatch();
1349

1450
const tournament = useSelector(selectors.tournamentSelector);
51+
const tournamentAdmin = useSelector(selectors.tournamentAdminSelector);
1552
const [playerMatches, setPlayerMatches] = useState({});
1653

1754
useEffect(() => {
@@ -85,15 +122,16 @@ function TournamentAdminWidget() {
85122

86123
// Render match buttons for a specific player
87124
const renderPlayerMatchButtons = (playerId) => {
88-
const matches = playerMatches[playerId] || [];
125+
const allMatches = playerMatches[playerId] || [];
89126

90-
if (matches.length === 0) {
127+
if (allMatches.length === 0) {
91128
return <span className="text-muted">No matches</span>;
92129
}
93130

131+
94132
return (
95133
<div className="d-flex flex-wrap gap-1">
96-
{matches.map(match => {
134+
{allMatches.map(match => {
97135
// Determine button color based on match state
98136
let buttonClass = 'btn-outline-secondary';
99137
if (match.state === 'finished') {
@@ -104,14 +142,20 @@ function TournamentAdminWidget() {
104142
buttonClass = 'btn-warning';
105143
}
106144

145+
// Check if this is the active game
146+
const isActiveGame = tournamentAdmin.activeGameId && match.gameId === tournamentAdmin.activeGameId;
147+
// No need for inline styles as we're using CSS animations
148+
const buttonStyle = {};
149+
107150
return (
108151
<button
109152
key={match.id}
110153
onClick={() => dispatch(pushActiveMatchToStream(match.gameId))}
111-
className={`btn ${buttonClass} btn-sm me-1 mb-1`}
112-
title={`Match ID: ${match.id}, State: ${match.state}, Started: ${new Date(match.startedAt).toLocaleTimeString()}`}
154+
className={`btn ${buttonClass} btn-sm me-1 mb-1 ${isActiveGame ? 'active-game' : ''}`}
155+
title={`${isActiveGame ? '⭐ ACTIVE GAME - ' : ''}Match ID: ${match.id}, State: ${match.state}, Started: ${new Date(match.startedAt).toLocaleTimeString()}`}
156+
style={buttonStyle}
113157
>
114-
#{match.gameId}
158+
#{match.gameId}{isActiveGame ? <span className="active-game-indicator">🔄</span> : ''}
115159
</button>
116160
);
117161
})}

services/app/apps/codebattle/assets/js/widgets/selectors/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ export const userRankingSelector = userId => state => (state.tournament.ranking?
430430
export const tournamentIdSelector = state => state.tournament.id;
431431

432432
export const tournamentSelector = state => state.tournament;
433+
export const tournamentAdminSelector = state => state.tournamentAdmin;
433434

434435
export const currentUserIsTournamentOwnerSelector = state => state.tournament.creatorId === state.user.currentUserId;
435436

services/app/apps/codebattle/assets/js/widgets/slices/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import reports, { actions as reportsActions } from './reports';
1818
import stairwayGame, { actions as stairwayGameActions } from './stairway';
1919
import storeLoaded, { actions as storeLoadedActions } from './store';
2020
import tournament, { actions as tournamentActions } from './tournament';
21+
import tournamentAdmin, { actions as tournamentAdminActions } from './tournamentAdmin';
2122
import tournamentPlayer, { actions as tournamentPlayerActions } from './tournamentPlayer';
2223
import user, { actions as userActions } from './user';
2324
import usersInfo, { actions as usersInfoActions } from './usersInfo';
@@ -44,6 +45,7 @@ export const actions = {
4445
...stairwayGameActions,
4546
...storeLoadedActions,
4647
...tournamentActions,
48+
...tournamentAdminActions,
4749
...tournamentPlayerActions,
4850
...userActions,
4951
...usersInfoActions,
@@ -70,6 +72,7 @@ export default {
7072
stairwayGame,
7173
storeLoaded,
7274
tournament,
75+
tournamentAdmin,
7376
tournamentPlayer,
7477
user,
7578
usersInfo,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,9 @@ export default {
512512
alerts: {},
513513
},
514514
tournament: initialTournament,
515+
tournamentAdmin: {
516+
activeGameId: null,
517+
},
515518
tournamentPlayer: defaultTournamentPlayerParams,
516519
editor: {
517520
meta: initialMeta,

services/app/apps/codebattle/assets/js/widgets/slices/tournament.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ const tournament = createSlice({
7676
state.playersPageNumber = payload;
7777
},
7878
updateTournamentChannelState: (state, { payload }) => {
79-
state.channel.online = payload;
79+
if (state.channel) {
80+
state.channel.online = payload;
81+
}
8082
},
8183
setTournamentPlayers: (state, { payload }) => {
8284
state.players = payload;
@@ -91,9 +93,10 @@ const tournament = createSlice({
9193
if (state.type === TournamentTypes.show) {
9294
state.showBots = !state.showBots;
9395
}
94-
},
95-
},
96-
});
96+
}
97+
}
98+
}
99+
);
97100

98101
const { actions, reducer } = tournament;
99102

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createSlice } from '@reduxjs/toolkit';
2+
3+
import initial from './initial';
4+
5+
const tournamentAdminSlice = createSlice({
6+
name: 'tournamentAdmin',
7+
initialState: initial.tournamentAdmin,
8+
reducers: {
9+
setAdminActiveGameId: (state, { payload }) => {
10+
state.activeGameId = payload;
11+
},
12+
},
13+
});
14+
15+
const { actions, reducer } = tournamentAdminSlice;
16+
17+
export { actions };
18+
19+
export default reducer;

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,8 @@ defmodule Codebattle.Tournament.Base do
652652
end)
653653

654654
# Extract just the game parameters for bulk creation
655-
game_creation_params = Enum.map(game_params, fn {params, _players, _match_id} -> params end)
655+
game_creation_params =
656+
Enum.map(game_params, fn {params, _players, _match_id} -> params end)
656657

657658
# Create games in bulk
658659
created_games = Game.Context.bulk_create_games(game_creation_params)
@@ -907,6 +908,7 @@ defmodule Codebattle.Tournament.Base do
907908
defp maybe_start_round_timer(%{round_timeout_seconds: nil} = tournament), do: tournament
908909
# We don't want to run a timer for the swiss type, because all games already have a timeout
909910
defp maybe_start_round_timer(%{state: "active", type: "swiss"} = tournament), do: tournament
911+
910912
defp maybe_start_round_timer(%{state: "active", type: "top200"} = tournament), do: tournament
911913

912914
defp maybe_start_round_timer(tournament) do
@@ -943,12 +945,16 @@ defmodule Codebattle.Tournament.Base do
943945
defp get_game_timeout(tournament, task) do
944946
cond do
945947
tournament.tournament_timeout_seconds ->
946-
max(tournament.tournament_timeout_seconds - DateTime.diff(DateTime.utc_now(), tournament.started_at), 10)
948+
max(
949+
tournament.tournament_timeout_seconds -
950+
DateTime.diff(DateTime.utc_now(), tournament.started_at),
951+
10
952+
)
947953

948954
FunWithFlags.enabled?(:tournament_custom_timeout) ->
949955
get_custom_round_timeout_seconds(tournament, task)
950956

951-
use_waiting_room?(tournament) or tournament.type in ["squad"] ->
957+
use_waiting_room?(tournament) or tournament.type in ["top200"] ->
952958
min(seconds_to_end_round(tournament), tournament.match_timeout_seconds)
953959

954960
:default ->

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ defmodule Codebattle.Tournament.Top200 do
88
@impl Tournament.Base
99
def complete_players(tournament) do
1010
# just for the UI test
11-
# users =
12-
# Codebattle.User
13-
# |> Codebattle.Repo.all()
14-
# |> Enum.filter(&(&1.is_bot == false and &1.subscription_type != :admin))
15-
# |> Enum.take(199)
16-
17-
# add_players(tournament, %{users: users})
18-
tournament
11+
users =
12+
Codebattle.User
13+
|> Codebattle.Repo.all()
14+
|> Enum.filter(&(&1.is_bot == false and &1.subscription_type != :admin))
15+
|> Enum.take(199)
16+
17+
add_players(tournament, %{users: users})
18+
# tournament
1919
end
2020

2121
@impl Tournament.Base

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,36 @@ defmodule CodebattleWeb.TournamentAdminChannel do
1111

1212
@default_ranking_size 200
1313

14+
# Agent for storing tournament_id to active_game_id mapping
15+
def start_games_agent do
16+
Agent.start(fn -> %{} end, name: __MODULE__.GamesAgent)
17+
end
18+
19+
# Store active_game_id for a specific tournament_id in the Agent
20+
def store_active_game(tournament_id, game_id) do
21+
# Ensure agent is started
22+
if Process.whereis(__MODULE__.GamesAgent) == nil do
23+
start_games_agent()
24+
end
25+
26+
Agent.update(__MODULE__.GamesAgent, fn games_map ->
27+
Map.put(games_map, tournament_id, game_id)
28+
end)
29+
end
30+
31+
# Get the active_game_id for a specific tournament_id from the Agent
32+
def get_active_game(tournament_id) do
33+
# Ensure agent is started
34+
if Process.whereis(__MODULE__.GamesAgent) == nil do
35+
start_games_agent()
36+
end
37+
38+
Agent.get(__MODULE__.GamesAgent, fn games_map ->
39+
dbg(games_map)
40+
Map.get(games_map, tournament_id)
41+
end)
42+
end
43+
1444
def join("tournament_admin:" <> tournament_id, _payload, socket) do
1545
current_user = socket.assigns.current_user
1646

@@ -241,6 +271,7 @@ defmodule CodebattleWeb.TournamentAdminChannel do
241271
def handle_in("tournament:ranking:request", _params, socket) do
242272
tournament_info = socket.assigns.tournament_info
243273
ranking = Tournament.Ranking.get_page(tournament_info, 1, @default_ranking_size)
274+
244275
{:reply, {:ok, %{ranking: ranking}}, socket}
245276
end
246277

@@ -259,6 +290,11 @@ defmodule CodebattleWeb.TournamentAdminChannel do
259290
def handle_in("tournament:stream:active_game", payload, socket) do
260291
tournament_id = socket.assigns.tournament_info.id
261292

293+
# Store game_id in Agent when streaming active game
294+
if payload["game_id"] do
295+
store_active_game(tournament_id, payload["game_id"])
296+
end
297+
262298
Codebattle.PubSub.broadcast("tournament:stream:active_game", %{
263299
game_id: payload["game_id"],
264300
tournament_id: tournament_id
@@ -286,12 +322,13 @@ defmodule CodebattleWeb.TournamentAdminChannel do
286322
end
287323

288324
def handle_info(%{event: "tournament:updated", payload: payload}, socket) do
325+
# if payload.tournament.type in ["swiss", "arena"] do
326+
# []
327+
# else
289328
matches =
290-
if payload.tournament.type in ["swiss", "arena"] do
291-
[]
292-
else
293-
Helpers.get_matches(socket.assigns.tournament_info)
294-
end
329+
Helpers.get_matches(socket.assigns.tournament_info)
330+
331+
# end
295332

296333
players =
297334
if payload.tournament.type in ["swiss", "arena"] do
@@ -397,8 +434,11 @@ defmodule CodebattleWeb.TournamentAdminChannel do
397434

398435
# end
399436

437+
active_game_id = tournament.id |> get_active_game() |> dbg()
438+
400439
%{
401440
tasks_info: tasks_info,
441+
active_game_id: active_game_id,
402442
reports: UserGameReport.list_by_tournament(tournament.id, limit: 300),
403443
tournament: Helpers.prepare_to_json(tournament),
404444
ranking: Tournament.Ranking.get_page(tournament, 1, @default_ranking_size),

0 commit comments

Comments
 (0)