diff --git a/back-end/api/src/controllers/Match.ts b/back-end/api/src/controllers/Match.ts index 4f3e69e1..fcec4998 100644 --- a/back-end/api/src/controllers/Match.ts +++ b/back-end/api/src/controllers/Match.ts @@ -59,7 +59,7 @@ router.post( matchMakerArgs, config ); - logger.info('mathmaker complete - sending results'); + logger.info('matchmaker complete - sending results'); res.send(matches); } catch (e) { return next(e); diff --git a/front-end/package-lock.json b/front-end/package-lock.json index 7b7b3b1f..64f9b584 100644 --- a/front-end/package-lock.json +++ b/front-end/package-lock.json @@ -18,7 +18,6 @@ "@octokit/core": "^5.0.1", "@toa-lib/client": "file:../lib/client", "@toa-lib/models": "file:../lib/models", - "@types/uuid": "^10.0.0", "@vitejs/plugin-react": "^4.2.1", "luxon": "^3.3.0", "react": "^18.2.0", @@ -36,6 +35,7 @@ "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@types/socket.io-client": "^3.0.0", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "editorconfig": "^2.0.0", @@ -2005,7 +2005,8 @@ "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.1.0", @@ -8607,7 +8608,8 @@ "@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==" + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true }, "@typescript-eslint/eslint-plugin": { "version": "6.1.0", diff --git a/front-end/src/apps/schedules/match-gen/random-matches.tsx b/front-end/src/apps/schedules/match-gen/random-matches.tsx index 2ef8c652..24c7a4a1 100644 --- a/front-end/src/apps/schedules/match-gen/random-matches.tsx +++ b/front-end/src/apps/schedules/match-gen/random-matches.tsx @@ -6,6 +6,7 @@ import { Tournament, assignMatchTimes } from '@toa-lib/models'; +import { FGCSchedule } from '@toa-lib/models'; import { FC, useState } from 'react'; import { createMatchSchedule } from 'src/api/use-match-data'; import { MatchMakerQualityDropdown } from 'src/components/dropdowns/match-maker-dropdown'; @@ -53,7 +54,14 @@ export const RandomMatches: FC = ({ teamKeys, name }); - onCreateMatches(assignMatchTimes(matches, scheduleItems)); + onCreateMatches( + assignMatchTimes( + eventSchedule.hasPremiereField + ? FGCSchedule.FGC2024.assignFields(matches) + : matches, + scheduleItems + ) + ); showSnackbar('MatchMaker executed successfully.'); setLoading(false); } catch (e) { diff --git a/front-end/src/apps/schedules/options/default-options.tsx b/front-end/src/apps/schedules/options/default-options.tsx index 51f61700..cb0c49e1 100644 --- a/front-end/src/apps/schedules/options/default-options.tsx +++ b/front-end/src/apps/schedules/options/default-options.tsx @@ -1,4 +1,12 @@ -import { Grid, TextField } from '@mui/material'; +import { + FormControl, + Grid, + InputLabel, + MenuItem, + Select, + SelectChangeEvent, + TextField +} from '@mui/material'; import { EventSchedule, calculateTotalMatches } from '@toa-lib/models'; import { ChangeEvent, FC, useEffect } from 'react'; @@ -35,13 +43,50 @@ export const DefaultScheduleOptions: FC = ({ }); }; + const handleSelectChange = (event: SelectChangeEvent) => { + if (!eventSchedule) return; + const { name, value } = event.target; + onChange({ + ...eventSchedule, + [name]: Boolean(value) + }); + }; + return ( theme.spacing(2) }} > - + + + Premiere Field + + + + + + + = ({ type='number' /> - + = ({ type='number' /> - + = ({ type='number' /> - + = ({ eventSchedule, savedMatches }) => { tournament={tournament} onCreateMatches={handleCreateMatches} /> - {matchesToDisplay && ( + {matchesToDisplay.length > 0 && ( <> = ({ matches, teams }) => { return [ e.name, e.fieldNumber, - DateTime.fromISO(e.startTime).toLocaleString(DateTime.DATETIME_FULL), + DateTime.fromISO(e.scheduledTime).toLocaleString( + DateTime.DATETIME_FULL + ), ...participants ]; }} diff --git a/lib/models/src/fgc/Schedule.ts b/lib/models/src/fgc/Schedule.ts index 127b438f..79b127ca 100644 --- a/lib/models/src/fgc/Schedule.ts +++ b/lib/models/src/fgc/Schedule.ts @@ -1,4 +1,10 @@ +import { DateTime } from 'luxon'; import { Match } from '../base/Match.js'; +import { + EventSchedule, + generateScheduleItems as generateDefaultScheduleItems, + ScheduleItem +} from '../base/Schedule.js'; export namespace FGC2023 { // We can safely assume for FGC2023 there will be 5 fields. @@ -30,3 +36,155 @@ export namespace FGC2023 { return newMatches; } } + +export namespace FGC2024 { + export function assignFields(matches: Match[]) { + // FGC2024 will have 5 fields. + // Assign them as such, while keeping 3 match concurrency. + const newMatches: Match[] = []; + for (let i = 0; i < matches.length; i++) { + const match = matches[i]; + let fieldNumber = 5; + const matchIndex = i % 6; + switch (matchIndex) { + case 0: + fieldNumber = 1; + break; + case 1: + fieldNumber = 4; + break; + case 2: + fieldNumber = 3; + break; + case 3: + fieldNumber = 2; + break; + case 4: + fieldNumber = 5; + break; + default: + fieldNumber = 3; + } + newMatches.push({ ...match, fieldNumber }); + } + return newMatches; + } +} + +// We'll leave this in for scientific purposes, but it turns out I'm actually a genius and don't need this. +// export function generateScheduleWithPremiereField( +// oldSchedule: EventSchedule +// ): ScheduleItem[] { +// const items = generateDefaultScheduleItems(oldSchedule); +// const schedule = JSON.parse(JSON.stringify(oldSchedule)); +// let index = 0; +// let normalIndex = 0; +// let premiereIndex = 0; +// let [prevItem] = items; +// let breakPadding = 0; +// let breakIndex = 0; + +// let needsBufferMatch = false; +// let bufferCount = 0; +// let dayPremiereTime = 0; +// let dayNormalTime = 0; +// for (const item of items) { +// if (prevItem.day !== item.day) { +// schedule.days[prevItem.day].endTime = DateTime.fromISO(prevItem.startTime) +// .plus({ minutes: prevItem.duration }) +// .toISO(); +// premiereIndex = 0; +// normalIndex = 0; +// index = 0; +// breakPadding = 0; +// breakIndex = 0; +// dayPremiereTime = 0; +// dayNormalTime = 0; +// needsBufferMatch = false; +// } + +// if (item.isMatch) { +// // For all matches that are NOT on the final day. FIRST GLOBAL ONLY. +// // if ( +// // item.day + 1 === schedule.days.length && +// // schedule.type === 'Qualification' +// // ) { +// // item.duration = FGC_SIDE_FIELDS_CYCLE_TIME; +// // item.startTime = schedule.days[item.day].startTime +// // .add(7 * premiereIndex + breakPadding, 'minutes') +// // .toISOString(); +// // index++; +// // if (index % 3 === 0) { +// // index = 0; +// // premiereIndex++; +// // } +// // // TODO - Test and make sure this reflects on the fields. +// // } else { +// if (!needsBufferMatch) { +// if (index % 7 < 4) { +// item.duration = schedule.cycleTime; +// item.startTime = +// DateTime.fromISO(schedule.days[item.day].startTime) +// .plus({ minutes: 10 * normalIndex + breakPadding }) +// .toISO() ?? ''; +// dayNormalTime += item.duration / 2; +// // console.log("CREATING NORMAL MATCH", index, item.duration, dayPremiereTime, dayNormalTime); +// } else { +// item.duration = schedule.cycleTime; +// item.startTime = +// DateTime.fromISO(schedule.days[item.day].startTime) +// .plus({ minutes: item.duration * premiereIndex + breakPadding }) +// .toISO() ?? ''; +// dayPremiereTime += item.duration; +// premiereIndex++; +// // console.log("CREATING PREMIERE MATCH", index, item.duration, dayPremiereTime, dayNormalTime); +// } +// if (index % 7 === 1) { +// normalIndex++; +// } +// if (index % 7 === 6) { +// // console.log("NEW MATCH PAIRS"); +// index = 0; +// normalIndex++; +// bufferCount = 0; +// needsBufferMatch = +// dayPremiereTime - dayNormalTime === schedule.cycleTime; +// } else { +// index++; +// } +// } else { +// // console.log("BUFFER MATCH"); +// item.duration = schedule.cycleTime; +// item.startTime = +// DateTime.fromISO(schedule.days[item.day].startTime) +// .plus({ minutes: item.duration * normalIndex + breakPadding }) +// .toISO() ?? ''; +// dayPremiereTime = 0; +// dayNormalTime = 0; +// bufferCount++; +// needsBufferMatch = bufferCount < 2; +// if (!needsBufferMatch) { +// normalIndex++; +// } +// } +// // } +// } else { +// const thisBreak = schedule.days[item.day].breaks[breakIndex]; +// schedule.days[item.day].breaks[breakIndex].startTime = DateTime.fromISO( +// prevItem.startTime +// ).plus({ minutes: prevItem.duration }); +// schedule.days[item.day].breaks[breakIndex].endTime = DateTime.fromISO( +// thisBreak.startTime +// ).plus({ minutes: thisBreak.duration }); +// breakPadding += item.duration; +// breakIndex++; +// } +// prevItem = item; +// } +// if (items.length > 0) { +// schedule.days[prevItem.day].endTime = DateTime.fromISO( +// prevItem.startTime +// ).plus({ minutes: prevItem.duration }); +// } +// return items; +// }