Skip to content

Commit

Permalink
Merge pull request #160 from the-orange-alliance/fgc-2024/fix-scoring
Browse files Browse the repository at this point in the history
Initial fixes and event monitor
  • Loading branch information
kyle-flynn authored Sep 25, 2024
2 parents eec3578 + 8054ae4 commit 9e1abaf
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 116 deletions.
3 changes: 1 addition & 2 deletions back-end/api/sql/seasons/fgc_2024.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ ALTER TABLE "ranking" ADD COLUMN foodSecuredPoints INT;

ALTER TABLE "match_detail" ADD COLUMN redResevoirConserved;
ALTER TABLE "match_detail" ADD COLUMN redFoodProduced;
ALTER TABLE "match_detail" ADD COLUMN redFoodSecured;
ALTER TABLE "match_detail" ADD COLUMN redRobotOneParked;
ALTER TABLE "match_detail" ADD COLUMN redRobotTwoParked;
ALTER TABLE "match_detail" ADD COLUMN redRobotThreeParked;
Expand All @@ -23,7 +22,6 @@ ALTER TABLE "match_detail" ADD COLUMN redEc6 INT;

ALTER TABLE "match_detail" ADD COLUMN blueResevoirConserved;
ALTER TABLE "match_detail" ADD COLUMN blueFoodProduced;
ALTER TABLE "match_detail" ADD COLUMN blueFoodSecured;
ALTER TABLE "match_detail" ADD COLUMN blueRobotOneParked;
ALTER TABLE "match_detail" ADD COLUMN blueRobotTwoParked;
ALTER TABLE "match_detail" ADD COLUMN blueRobotThreeParked;
Expand All @@ -42,3 +40,4 @@ ALTER TABLE "match_detail" ADD COLUMN blueEc6 INT;

ALTER TABLE "match_detail" ADD COLUMN coopertition;
ALTER TABLE "match_detail" ADD COLUMN fieldBalanced;
ALTER TABLE "match_detail" ADD COLUMN foodSecured;
19 changes: 5 additions & 14 deletions front-end/src/api/events/match-update-event.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import { Match, MatchState } from '@toa-lib/models';
import { Match } from '@toa-lib/models';
import { useRecoilCallback } from 'recoil';
import { matchStateAtom, matchOccurringAtom } from 'src/stores/recoil';
import { matchOccurringAtom } from 'src/stores/recoil';

export const useMatchUpdateEvent = () => {
return useRecoilCallback(
({ snapshot, set }) =>
async (newMatch: Match<any>) => {
const state = await snapshot.getPromise(matchStateAtom);
if (state >= MatchState.MATCH_COMPLETE) {
// Don't update anything.
return;
} else {
set(matchOccurringAtom, newMatch);
}
}
);
return useRecoilCallback(({ set }) => async (newMatch: Match<any>) => {
set(matchOccurringAtom, newMatch);
});
};
2 changes: 1 addition & 1 deletion front-end/src/api/use-match-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const deleteMatches = async (
): Promise<void> => apiFetcher(`match/${eventKey}/${tournamentKey}`, 'DELETE');

export const useMatchAll = (
key?: MatchKey
key?: MatchKey | null
): SWRResponse<Match<any>, ApiResponseError> =>
useSWR<Match<any>>(
key ? `match/all/${key.eventKey}/${key.tournamentKey}/${key.id}` : '',
Expand Down
12 changes: 11 additions & 1 deletion front-end/src/app-routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ const RefereeApp = lazy(() =>
const RedReferee = lazy(() =>
import('./apps/referee').then((m) => ({ default: m.RedReferee }))
);
0;
const BlueReferee = lazy(() =>
import('./apps/referee').then((m) => ({ default: m.BlueReferee }))
);
Expand All @@ -114,6 +113,11 @@ const AudienceDisplay = lazy(() =>
}))
);

// Misc routes
const EventMonitor = lazy(() =>
import('./apps/event-monitor').then((m) => ({ default: m.EventMonitor }))
);

// Unised Routes
// const AccountManager = lazy(() => import('./apps/AccountManager'));
// const RefereeApp = lazy(() => import('./apps/Referee/Referee'));
Expand Down Expand Up @@ -320,6 +324,12 @@ const AppRoutes: AppRoute[] = [
path: '/audience-display-manager',
group: 0,
element: AudienceDisplayManager
},
{
name: 'Event Monitor',
path: '/event-monitor',
group: 0,
element: EventMonitor
}
// {
// name: 'Account Manager',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,9 @@ export const Breakdown: ResultsBreakdown<MatchDetails>[] = [
icon: <Lock fontSize='inherit' />,
title: 'Food Secured Points',
color: '#000000',
resultCalc: (match, alliance) => {
resultCalc: (match) => {
if (!match.details) return '0';
const [redFoodSecured, blueFoodSecured] = getFoodSecuredPoints(
match.details
);
const pts = alliance === 'red' ? redFoodSecured : blueFoodSecured;
const pts = getFoodSecuredPoints(match.details);
return pts > 0 ? `+${pts}` : `${pts}`;
}
},
Expand Down
229 changes: 229 additions & 0 deletions front-end/src/apps/event-monitor/event-monitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { Card, CardContent, CardHeader, Grid, Typography } from '@mui/material';
import { FC, useEffect, useState } from 'react';
import { DefaultLayout } from 'src/layouts/default-layout';
import { MatchSocketEvent, MatchKey } from '@toa-lib/models';
import { io } from 'socket.io-client';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import ErrorIcon from '@mui/icons-material/Error';
import { useMatchAll } from 'src/api/use-match-data';
import { useTeamIdentifiersForEventKey } from 'src/hooks/use-team-identifier';

interface MonitorCardProps {
field: number;
url: string;
}

const MonitorCard: FC<MonitorCardProps> = ({ field, url }) => {
const [connected, setConnected] = useState(false);
const [key, setKey] = useState<MatchKey | null>(null);
const [status, setStatus] = useState('STANDBY');
const { data: match } = useMatchAll(key);
const identifiers = useTeamIdentifiersForEventKey(key?.eventKey);
useEffect(() => {
const socket = createSocket();
socket.on('connect', handleConnect);
socket.on('disconnect', handleDisconnect);
socket.on(MatchSocketEvent.PRESTART, handlePrestart);
socket.on(MatchSocketEvent.START, handleStart);
socket.on(MatchSocketEvent.ABORT, handleAbort);
socket.on(MatchSocketEvent.END, handleEnd);
socket.on(MatchSocketEvent.COMMIT, handleCommit);
socket.connect();
socket.emit('rooms', ['match']);
return () => {
socket.off(MatchSocketEvent.PRESTART, handlePrestart);
socket.off(MatchSocketEvent.START, handleStart);
socket.off(MatchSocketEvent.ABORT, handleAbort);
socket.off(MatchSocketEvent.END, handleEnd);
socket.off(MatchSocketEvent.COMMIT, handleCommit);
};
}, []);

const handleConnect = () => setConnected(true);
const handleDisconnect = () => setConnected(false);

const handlePrestart = (key: MatchKey) => {
setKey(key);
setStatus('PRESTART');
};
const handleStart = () => {
setStatus('IN PROGRESS');
};
const handleAbort = () => {
setStatus('ABORTED');
};
const handleEnd = () => {
setStatus('COMPLETE');
};
const handleCommit = () => {
setStatus('COMMITTED');
};

const createSocket = (autoConnect: boolean = false, token: string = '') => {
return io(`ws://${url}`, {
rejectUnauthorized: false,
transports: ['websocket'],
query: { token },
autoConnect
});
};
return (
<Card>
<CardHeader
title={`Field ${field}`}
subheader={
connected
? match
? `${match?.name} - ${status}`
: status
: 'OFFLINE'
}
avatar={
connected ? (
<CheckCircleIcon color='success' />
) : (
<ErrorIcon color='error' />
)
}
/>
<CardContent>
<Grid container>
<Grid item xs={8}>
<Grid container>
<Grid item xs={4}>
<Typography align='left' className='red'>
{match && match.participants ? (
<>
<span
className={`flag-icon flag-icon-${match.participants[0].team?.countryCode}`}
/>{' '}
{identifiers[match?.participants?.[0].teamKey]}
</>
) : (
'---'
)}
</Typography>
</Grid>
<Grid item xs={4} />
<Grid item xs={4}>
<Typography align='right' className='blue'>
{match && match.participants ? (
<>
{identifiers[match?.participants?.[3].teamKey]}{' '}
<span
className={`flag-icon flag-icon-${match.participants[3].team?.countryCode}`}
/>
</>
) : (
'---'
)}
</Typography>
</Grid>

<Grid item xs={4}>
<Typography align='left' className='red'>
{match && match.participants ? (
<>
<span
className={`flag-icon flag-icon-${match.participants[1].team?.countryCode}`}
/>{' '}
{identifiers[match?.participants?.[1].teamKey]}
</>
) : (
'---'
)}
</Typography>
</Grid>
<Grid item xs={4}>
<Typography align='center'>vs.</Typography>
</Grid>
<Grid item xs={4}>
<Typography align='right' className='blue'>
{match && match.participants ? (
<>
{identifiers[match?.participants?.[4].teamKey]}{' '}
<span
className={`flag-icon flag-icon-${match.participants[4].team?.countryCode}`}
/>
</>
) : (
'---'
)}
</Typography>
</Grid>

<Grid item xs={4}>
<Typography align='left' className='red'>
{match && match.participants ? (
<>
<span
className={`flag-icon flag-icon-${match.participants[2].team?.countryCode}`}
/>{' '}
{identifiers[match?.participants?.[2].teamKey]}
</>
) : (
'---'
)}
</Typography>
</Grid>
<Grid item xs={4} />
<Grid item xs={4}>
<Typography align='right' className='blue'>
{match && match.participants ? (
<>
{identifiers[match?.participants?.[5].teamKey]}{' '}
<span
className={`flag-icon flag-icon-${match.participants[5].team?.countryCode}`}
/>
</>
) : (
'---'
)}
</Typography>
</Grid>

<Grid item xs={4}>
<Typography align='center' className='red'>
{match ? match.redScore : '--'}
</Typography>
</Grid>
<Grid item xs={4} />
<Grid item xs={4}>
<Typography align='center' className='blue'>
{match ? match.blueScore : '--'}
</Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</CardContent>
</Card>
);
};

export const EventMonitor: FC = () => {
return (
<DefaultLayout title='Event Monitor'>
<Grid container spacing={3}>
<Grid item xs={12} sm={4}>
<MonitorCard field={2} url='192.168.80.121:8081' />
</Grid>
<Grid item xs={12} sm={4}>
<MonitorCard field={3} url='192.168.80.131:8081' />
</Grid>
<Grid item xs={12} sm={4}>
<MonitorCard field={4} url='192.168.80.141:8081' />
</Grid>
<Grid item xs={12} sm={4}>
<MonitorCard field={1} url='192.168.80.111:8081' />
</Grid>
<Grid item xs={12} sm={4}>
<div>HERE</div>
</Grid>
<Grid item xs={12} sm={4}>
<MonitorCard field={5} url='192.168.80.151:8081' />
</Grid>
</Grid>
</DefaultLayout>
);
};
3 changes: 3 additions & 0 deletions front-end/src/apps/event-monitor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { EventMonitor } from './event-monitor';

export { EventMonitor };
7 changes: 6 additions & 1 deletion front-end/src/apps/scorekeeper/hooks/use-prestart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
MatchSocketEvent,
MatchState
} from '@toa-lib/models';
import { matchOccurringAtom, socketConnectedAtom } from 'src/stores/recoil';
import {
currentTournamentKeyAtom,
matchOccurringAtom,
socketConnectedAtom
} from 'src/stores/recoil';
import { patchMatch, patchMatchParticipants } from 'src/api/use-match-data';
import { DateTime } from 'luxon';
import { once, sendPrestart, sendUpdate } from 'src/api/use-socket';
Expand Down Expand Up @@ -54,6 +58,7 @@ export const usePrestartCallback = () => {
}
};
set(matchOccurringAtom, currentMatch);
set(currentTournamentKeyAtom, currentMatch.tournamentKey);
}
fieldControl?.prestartField?.();
// Once we recieve the prestart response, immediately send update to load socket with match
Expand Down
16 changes: 16 additions & 0 deletions front-end/src/hooks/use-team-identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ export const useTeamIdentifiers = (): Record<number, string> => {
);
};

export const useTeamIdentifiersForEventKey = (
eventKey: string | null | undefined
): Record<number, string> => {
const identifier = useRecoilValue(teamIdentifierAtom);
const { data: teams } = useTeamsForEvent(eventKey);
return useMemo(
() =>
teams
? Object.fromEntries(
teams.map((t) => [t.teamKey, String(t[identifier])])
)
: {},
[teams, identifier]
);
};

export const useTeamIdentifierRecord = (
teams: Team[]
): Record<number, string> => {
Expand Down
Loading

0 comments on commit 9e1abaf

Please sign in to comment.