Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions apps/scouting/backend/src/fuel/calculations/fuel-averaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@ const NO_FUEL_COLLECTED = 0;
const FIRST_SECTION_AMOUNT = 1;
const LAST_SECTION_LENGTH = 1;

/**
* @param sections consists of sections that contains a list of timestamps in ms
* @returns mean ball amount
*/
/** Recursively calculates mean ball amount from timestamp sections. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you delete this? I think it is helpfull

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ye agreed, this one should stay since its not necesarily obvious what the sections are

const calculateBallAmount = (
sections: number[][],
shotLength: number,
): number => {
// Base Case 1
if (shotLength <= EMPTY_INTERVAL_DURATION) {
return NO_FUEL_COLLECTED;
}
// Base Case 2: Happens if no section is long enough for the shot length
if (sections.length === LAST_SECTION_LENGTH) {
const onlySection = firstElement(sections);
const ballAmount = onlySection.length;
Expand Down Expand Up @@ -67,6 +62,7 @@ const correctSectionToTimeFromEnd = (sections: number[]) => {
.map((timestamp) => endTimestamp - timestamp);
};

/** Calculates fuel statistics by averaging across multiple matches. */
export const calculateFuelByAveraging = (
shot: ShootEvent,
isPass: boolean,
Expand All @@ -86,7 +82,7 @@ export const calculateFuelByAveraging = (
return {
shot: shotAmount,
passed: shotAmount,
positions: [shot.startPosition],
positions: shot.positions,
};
}
const scoredAmount = calculateBallAmount(
Expand All @@ -101,6 +97,6 @@ export const calculateFuelByAveraging = (
scored: scoredAmount,
shot: shotAmount,
missed: shotAmount - scoredAmount,
positions: [shot.startPosition],
positions: shot.positions,
};
};
7 changes: 5 additions & 2 deletions apps/scouting/backend/src/fuel/calculations/fuel-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
import type { FuelObject, ShootEvent } from "@repo/scouting_types";
import type { BPS } from "../fuel-object";

/** Filters timestamps that fall within the shooting interval. */
const getIncludedShots = (section: number[], shot: ShootEvent) => {
return section.filter(
(timestamp) =>
timestamp > shot.interval.start && timestamp < shot.interval.end,
);
};

/** Calculates fuel statistics based on match-specific BPS data. */
export const calculateFuelByMatch = (
shot: ShootEvent,
isPass: boolean,
Expand All @@ -24,7 +27,7 @@ export const calculateFuelByMatch = (
return {
shot: shotAmount,
passed: shotAmount,
positions: [shot.startPosition],
positions: shot.positions,
};
}

Expand All @@ -34,6 +37,6 @@ export const calculateFuelByMatch = (
shot: shotAmount,
scored: scoredAmount,
missed: shotAmount - scoredAmount,
positions: [shot.startPosition],
positions: shot.positions,
};
};
7 changes: 4 additions & 3 deletions apps/scouting/backend/src/fuel/fuel-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import type {
import { calculateFuelByAveraging } from "./calculations/fuel-averaging";
import { calculateFuelByMatch } from "./calculations/fuel-match";
import { ALLIANCE_ZONE_WIDTH_PIXELS } from "@repo/rebuilt_map";
import { firstElement } from "@repo/array-functions";

export interface BPS {
events: { shoot: number[]; score: number[] }[];
match: Match;
}

const isShotPass = (positionPixels: Point) =>
positionPixels.x > ALLIANCE_ZONE_WIDTH_PIXELS;
const isShotPass = (positions: Point[]) =>
firstElement(positions).x > ALLIANCE_ZONE_WIDTH_PIXELS;

const emptyFuelObject: FuelObject = {
shot: 0,
Expand All @@ -40,7 +41,7 @@ export const createFuelObject = (
value.match.number === match.number && value.match.type === match.type,
);

const isPass = isShotPass(shot.startPosition);
const isPass = isShotPass(shot.positions);

const partialFuel = sameMatch
? calculateFuelByMatch(shot, isPass, sameMatch)
Expand Down
6 changes: 6 additions & 0 deletions apps/scouting/frontend/src/scouter/components/stopwatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ interface StopwatchProps {
originTime: number;
disabled: boolean;
size?: "default" | "compact";
onStart?: () => void;
onStop?: () => void;
}

const Stopwatch: React.FC<StopwatchProps> = ({
addCycleTimeSeconds,
originTime,
disabled,
size = "default",
onStart,
onStop,
}) => {
const [isRunning, setIsRunning] = useState(false);
const [elapsedTime, setElapsedTime] = useState(INITIAL_TIME_MILLISECONDS);
Expand Down Expand Up @@ -71,6 +75,7 @@ const Stopwatch: React.FC<StopwatchProps> = ({

startTimeRef.current = Date.now() - elapsedTime;
setIsRunning(true);
onStart?.();
};

const stop = () => {
Expand All @@ -87,6 +92,7 @@ const Stopwatch: React.FC<StopwatchProps> = ({

setIsRunning(false);
reset();
onStop?.();
};

const formatTime = () => {
Expand Down
52 changes: 52 additions & 0 deletions apps/scouting/frontend/src/scouter/hooks/usePositionRecording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// בס"ד
import { useRef, useEffect, useCallback } from "react";
import type { Point } from "@repo/scouting_types";
import { defaultPoint } from "../components/ScoreMap";

const DEFAULT_RECORDING_INTERVAL_MS = 1000;

export const usePositionRecording = (
currentPosition: Point | undefined,
recordingIntervalMs: number = DEFAULT_RECORDING_INTERVAL_MS,
): {
recordedPositionsRef: { current: Point[] };
start: () => void;
stop: () => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider defining an interface for this

} => {
const recordedPositionsRef = useRef<Point[]>([]);
const positionIntervalRef = useRef<number | null>(null);
const currentPositionRef = useRef<Point | undefined>(undefined);

useEffect(() => {
currentPositionRef.current = currentPosition;
}, [currentPosition]);

const start = useCallback(() => {
if (positionIntervalRef.current !== null) {
clearInterval(positionIntervalRef.current);
}
recordedPositionsRef.current = [];
const initialPosition = currentPositionRef.current ?? { ...defaultPoint };
recordedPositionsRef.current.push(initialPosition);

positionIntervalRef.current = window.setInterval(() => {
const currentPos = currentPositionRef.current ?? { ...defaultPoint };
recordedPositionsRef.current.push(currentPos);
}, recordingIntervalMs);
}, [recordingIntervalMs]);

const stop = useCallback(() => {
if (positionIntervalRef.current === null) return;
clearInterval(positionIntervalRef.current);
positionIntervalRef.current = null;
}, []);

useEffect(() => {
return () => {
if (positionIntervalRef.current === null) return;
clearInterval(positionIntervalRef.current);
};
}, []);

return { recordedPositionsRef, start, stop };
};
13 changes: 12 additions & 1 deletion apps/scouting/frontend/src/scouter/pages/tabs/AutoTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Stopwatch from "../../components/stopwatch";
import { MovementForm } from "../../components/MovementForm";
import type { TabProps } from "../ScoutMatch";
import { defaultPoint } from "../../components/ScoreMap";
import { usePositionRecording } from "../../hooks/usePositionRecording";
import { isEmpty } from "@repo/array-functions";

export const AutoTab: FC<TabProps> = ({
setForm,
Expand All @@ -15,6 +17,7 @@ export const AutoTab: FC<TabProps> = ({
}) => {
const [mapPosition, setMapPosition] = useState<Point>();
const [mapZone, setMapZone] = useState<Alliance>(alliance);
const { recordedPositionsRef, start, stop } = usePositionRecording(mapPosition);

return (
<div className="flex flex-row h-full w-full gap-3">
Expand All @@ -31,16 +34,24 @@ export const AutoTab: FC<TabProps> = ({
addCycleTimeSeconds={(cycle) => {
setForm((prevForm) => {
const prevEvents = prevForm.auto.shootEvents;
const positions = isEmpty(recordedPositionsRef.current)
? [mapPosition ?? { ...defaultPoint }]
: [...recordedPositionsRef.current];

prevEvents.push({
interval: cycle,
startPosition: mapPosition ?? { ...defaultPoint },
positions,
});

recordedPositionsRef.current = [];
return prevForm;
});
}}
originTime={originTime}
disabled={mapPosition === undefined}
size="compact"
onStart={start}
onStop={stop}
/>
<MovementForm
setMovement={(value) => {
Expand Down
21 changes: 15 additions & 6 deletions apps/scouting/frontend/src/scouter/pages/tabs/ShiftTab.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// בס"ד

import { useState, type FC } from "react";
import type { TabProps } from "../ScoutMatch";
import { ScoreMap, defaultPoint } from "../../components/ScoreMap";
import type { Alliance, Point, ShiftType } from "@repo/scouting_types";
import { MovementForm } from "../../components/MovementForm";
import Stopwatch from "../../components/stopwatch";
import { usePositionRecording } from "../../hooks/usePositionRecording";
import { isEmpty } from "@repo/array-functions";

interface ShiftTabProps extends TabProps {
tabIndex: number;
shiftType: ShiftType;
}


export const ShiftTab: FC<ShiftTabProps> = ({
setForm,
tabIndex,
Expand All @@ -23,6 +23,7 @@ export const ShiftTab: FC<ShiftTabProps> = ({
}) => {
const [mapPosition, setMapPosition] = useState<Point>();
const [mapZone, setMapZone] = useState<Alliance>(alliance);
const { recordedPositionsRef, start, stop } = usePositionRecording(mapPosition);

const handleSetForm = (cycle: { start: number; end: number }) => {
setForm((prevForm) => {
Expand All @@ -32,10 +33,17 @@ export const ShiftTab: FC<ShiftTabProps> = ({
: shiftType === "transition"
? prevForm.tele.transitionShift.shootEvents
: prevForm.tele.endgameShift.shootEvents;

const positions = isEmpty(recordedPositionsRef.current)
? [mapPosition ?? { ...defaultPoint }]
: [...recordedPositionsRef.current];

prevEvents.push({
interval: cycle,
startPosition: mapPosition ?? { ...defaultPoint },
positions,
});

recordedPositionsRef.current = [];
return prevForm;
});
};
Expand All @@ -52,17 +60,18 @@ export const ShiftTab: FC<ShiftTabProps> = ({
</div>
<div className="flex flex-col items-center gap-0.5 sm:gap-1 shrink-0 w-32 sm:w-36 min-h-0 py-0.5 sm:py-1">
<Stopwatch
addCycleTimeSeconds={(cycle) => {
handleSetForm(cycle);
}}
addCycleTimeSeconds={handleSetForm}
originTime={originTime}
disabled={mapPosition === undefined}
size="compact"
onStart={start}
onStop={stop}
/>
<MovementForm
setMovement={(value) => {
setForm((prevForm) => ({
...prevForm,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

tele: { ...prevForm.tele, movement: value },
}));
}}
Expand Down
Loading
Loading