Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added WPA/BPA display for final rounds #179

Merged
merged 8 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
58 changes: 58 additions & 0 deletions client/src/components/Result/Result.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Box, Tooltip } from "@mui/material";
import {
bestPossibleAverage,
worstPossibleAverage,
formatAttemptResult,
incompleteMean,
} from "../../lib/attempt-result";

const Result = ({ result, field, eventId, roundFormat }) => {
maxidragon marked this conversation as resolved.
Show resolved Hide resolved
return (
<>
{result.average === 0 && field === "average"
? roundFormat.id === "a"
? result.attempts.length > 3 && (
maxidragon marked this conversation as resolved.
Show resolved Hide resolved
<Box component="span" sx={{ opacity: 0.5 }}>
<Tooltip title="Best possible average">
<span>
{formatAttemptResult(
bestPossibleAverage(
result.attempts.map((attempt) => attempt.result)
),
eventId
)}
</span>
</Tooltip>
{" / "}
<Tooltip title="Worst possible average">
<span>
{formatAttemptResult(
worstPossibleAverage(
result.attempts.map((attempt) => attempt.result)
),
eventId
)}
</span>
</Tooltip>
</Box>
)
: roundFormat.id === "m" &&
result.attempts.length > 1 && (
<Tooltip title="Mean after 2 solves">
<Box component="span" sx={{ opacity: 0.5 }}>
{formatAttemptResult(
incompleteMean(
result.attempts.map((attempt) => attempt.result),
eventId
),
eventId
)}
</Box>
</Tooltip>
)
: formatAttemptResult(result[field], eventId)}
</>
);
};

export default Result;
17 changes: 15 additions & 2 deletions client/src/components/ResultsProjector/ResultsProjector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { times } from "../../lib/utils";
import { formatAttemptResult } from "../../lib/attempt-result";
import { orderedResultStats, paddedAttemptResults } from "../../lib/result";
import RecordTagBadge from "../RecordTagBadge/RecordTagBadge";
import Result from "../Result/Result";

const styles = {
cell: {
Expand Down Expand Up @@ -66,7 +67,14 @@ function getNumberOfRows() {
return Math.floor((window.innerHeight - 64 - 56) / 67);
}

function ResultsProjector({ results, format, eventId, title, exitUrl }) {
function ResultsProjector({
results,
format,
eventId,
title,
exitUrl,
roundFormat,
}) {
const [status, setStatus] = useState(STATUS.SHOWING);
const [topResultIndex, setTopResultIndex] = useState(0);

Expand Down Expand Up @@ -223,7 +231,12 @@ function ResultsProjector({ results, format, eventId, title, exitUrl }) {
recordTag={result[recordTagField]}
hidePr
>
{formatAttemptResult(result[field], eventId)}
<Result
result={result}
field={field}
eventId={eventId}
roundFormat={roundFormat}
/>
</RecordTagBadge>
</TableCell>
))}
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Round/Round.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ function Round() {
if (!data) return <Loading />;
if (error) return <Error error={error} />;
const { round } = data;

return (
<>
{loading && <Loading />}
Expand All @@ -127,6 +126,7 @@ function Round() {
eventId={round.competitionEvent.event.id}
title={`${round.competitionEvent.event.name} - ${round.name}`}
exitUrl={`/competitions/${competitionId}/rounds/${roundId}`}
roundFormat={round.format}
/>
}
/>
Expand All @@ -140,6 +140,7 @@ function Round() {
format={round.format}
eventId={round.competitionEvent.event.id}
competitionId={competitionId}
roundFormat={round.format}
/>
}
/>
Expand Down
9 changes: 8 additions & 1 deletion client/src/components/RoundResults/RoundResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import RoundResultDialog from "./RoundResultDialog";

const DEFAULT_VISIBLE_RESULTS = 100;

function RoundResults({ results, format, eventId, competitionId }) {
function RoundResults({
results,
format,
eventId,
competitionId,
roundFormat,
}) {
const smScreen = useMediaQuery((theme) => theme.breakpoints.up("sm"));

const [selectedResult, setSelectedResult] = useState(null);
Expand Down Expand Up @@ -35,6 +41,7 @@ function RoundResults({ results, format, eventId, competitionId }) {
eventId={eventId}
competitionId={competitionId}
onResultClick={handleResultClick}
roundFormat={roundFormat}
/>
</Grid>
{!showAll && (
Expand Down
10 changes: 8 additions & 2 deletions client/src/components/RoundResults/RoundResultsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { times } from "../../lib/utils";
import { formatAttemptResult } from "../../lib/attempt-result";
import { orderedResultStats, paddedAttemptResults } from "../../lib/result";
import RecordTagBadge from "../RecordTagBadge/RecordTagBadge";
import Result from "../Result/Result";

const styles = {
cell: {
Expand All @@ -41,7 +42,7 @@ const styles = {
};

const RoundResultsTable = memo(
({ results, format, eventId, competitionId, onResultClick }) => {
({ results, format, eventId, competitionId, onResultClick, roundFormat }) => {
const smScreen = useMediaQuery((theme) => theme.breakpoints.up("sm"));
const mdScreen = useMediaQuery((theme) => theme.breakpoints.up("md"));

Expand Down Expand Up @@ -130,7 +131,12 @@ const RoundResultsTable = memo(
}}
>
<RecordTagBadge litePr recordTag={result[recordTagField]}>
{formatAttemptResult(result[field], eventId)}
<Result
maxidragon marked this conversation as resolved.
Show resolved Hide resolved
result={result}
field={field}
eventId={eventId}
roundFormat={roundFormat}
/>
</RecordTagBadge>
</TableCell>
))}
Expand Down
50 changes: 50 additions & 0 deletions client/src/lib/attempt-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,56 @@ export function formatAttemptResult(attemptResult, eventId) {
return centisecondsToClockFormat(attemptResult);
}

/**
* Calculate BPA (Best Possible Average) for the given attempts.
* @example
* bestPossibleAverage([3642, 3102, 3001, 2992]); // => 3032
* bestPossibleAverage([6111, -1, -1, 6000]); // => -1
* bestPossibleAverage([4822, 4523, 4233, -1]; // => 4526
*/
export function bestPossibleAverage(times) {
let bpa;
console.log(times);
maxidragon marked this conversation as resolved.
Show resolved Hide resolved
const validTimes = times.filter((attempt) => attempt > -1);
if (validTimes.length < 3) {
bpa = -1;
} else {
const sortedTimes = validTimes.slice().sort((a, b) => a - b);
bpa = mean(sortedTimes[0], sortedTimes[1], sortedTimes[2]);
}
return bpa;
}
/**
* Calculate WPA (Worst Possible Average) for the given attempts.
* @example
* worstPossibleAverage([3642, 3102, 3001, 2992]); // => 3248
* worstPossibleAverage([6111, -1, -1, 6000]); // => -1
* worstPossibleAverage([6111, -1, 6000, 5999]); // => -1
*/
export function worstPossibleAverage(times) {
let wpa;
if (times.some((time) => time === -1 || time === -2)) {
wpa = -1;
} else {
const sortedTimes = times.slice().sort((a, b) => a - b);
wpa = mean(sortedTimes[1], sortedTimes[2], sortedTimes[3]);
}
return wpa;
}

export function incompleteMean(times, eventId) {
let mo2;
if (times.some((time) => time === -1 || time === -2)) {
mo2 = -1;
} else {
if (eventId === "333fm") {
times = times.map((attemptResult) => attemptResult * 100);
}
mo2 = (times[0] + times[1]) / 2;
}
return mo2;
}

function formatMbldAttemptResult(attemptResult) {
const { solved, attempted, centiseconds } =
decodeMbldAttemptResult(attemptResult);
Expand Down
38 changes: 38 additions & 0 deletions client/src/lib/tests/attempt-result.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
best,
bestPossibleAverage,
average,
formatAttemptResult,
decodeMbldAttemptResult,
Expand All @@ -12,6 +13,8 @@ import {
applyTimeLimit,
applyCutoff,
isWorldRecord,
worstPossibleAverage,
incompleteMean,
} from "../attempt-result";

describe("best", () => {
Expand Down Expand Up @@ -554,3 +557,38 @@ describe("applyCutoff", () => {
expect(applyCutoff(attempts, cutoff)).toEqual([1000, 799, 1200, 1000, 900]);
});
});

describe("worstPossibleAverage", () => {
it("returns -1 if any attempt result is DNF", () => {
const attemptResults = [1000, -1, 1200, 1300];
expect(worstPossibleAverage(attemptResults)).toEqual(-1);
});

it("calculate worst possible average correctly", () => {
const attemptResults = [3642, 3102, 3001, 2992];
expect(worstPossibleAverage(attemptResults)).toEqual(3248);
});
});

describe("bestPossibleAverage", () => {
it("returns -1 if two attempts result are DNF's", () => {
const attemptResults = [1000, -1, 1200, -1];
expect(bestPossibleAverage(attemptResults)).toEqual(-1);
});

it("calculate BPA correctly", () => {
const attemptResults = [3642, 3102, 3001, 2992];
expect(bestPossibleAverage(attemptResults)).toEqual(3032);
});
});

describe("incompleteMean", () => {
it("returns -1 if any attempt result is DNF", () => {
const attemptResults = [21, -1];
expect(incompleteMean(attemptResults, "333fm")).toEqual(-1);
});
it("calculate mean of 2 correctly", () => {
const attemptResults = [21, 23];
expect(incompleteMean(attemptResults, "333fm")).toEqual(2200);
});
});
Loading