Skip to content

Commit

Permalink
Hockey scoring summary
Browse files Browse the repository at this point in the history
  • Loading branch information
dumbmatter committed Apr 15, 2024
1 parent ade2a3b commit 58b9574
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 179 deletions.
8 changes: 0 additions & 8 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,9 @@ indicator of if trade will be accepted
allow undoing a trade

shootouts
- hockey
- need football/baseball scoringSummary style - build up from events?
- scoring summary
- isScoringPlay -> formatScoringSummaryEvent
- confirm for all sports
- check what fast-forward options are shown during shootout
- no injuries during shootout
- if still tied, keep going one at a time
- make sure it's not all 0, all sports should have some min prob of success
- don't track any stats
- no clock
- show shootout result somewhere
- other sports - scoring log
- upgrades
Expand Down
6 changes: 3 additions & 3 deletions src/ui/components/BoxScore.baseball.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,18 +290,18 @@ const ScoringSummary = ({
<tbody>
{processedEvents.map((event, i) => {
let quarterHeader: ReactNode = null;
const currentInning =
const currentPeriod =
event.type === "shootoutShot" ? "Shootout" : event.inning;
if (
event.inning !== prevInning ||
(event.t !== prevT && currentInning !== "Shootout")
(event.t !== prevT && currentPeriod !== "Shootout")
) {
prevInning = event.inning;
prevT = event.t;
quarterHeader = (
<tr>
<td className="text-body-secondary" colSpan={4}>
{currentInning === "Shootout" ? (
{currentPeriod === "Shootout" ? (
"Home run derby"
) : (
<>
Expand Down
210 changes: 109 additions & 101 deletions src/ui/components/BoxScore.hockey.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
memo,
Fragment,
type MouseEvent,
type ReactNode,
useState,
useMemo,
} from "react";
import ResponsiveTableWrapper from "./ResponsiveTableWrapper";
import { getCols, helpers, processPlayerStats } from "../util";
Expand Down Expand Up @@ -157,35 +157,34 @@ const StatsTable = ({
const processEvents = (events: PlayByPlayEventScore[]) => {
const processedEvents: (PlayByPlayEventScore & {
score: [number, number];
noPoints: boolean;
})[] = [];
const score = [0, 0] as [number, number];
let score = [0, 0] as [number, number];
let shootout = false;

for (const event of events) {
if (event.hide) {
continue;
if (!shootout && event.type === "shootoutShot") {
shootout = true;
score = [0, 0];
}

score[event.t] += 1;
let numPts = 0;
if (event.type === "goal" || event.made) {
numPts += 1;
}

score[event.t] += numPts;

processedEvents.push({
...event,
score: helpers.deepCopy(score),
noPoints: numPts === 0,
});
}

return processedEvents;
};

const getCount = (events: PlayByPlayEventScore[]) => {
let count = 0;
for (const event of events) {
if (!event.hide) {
count += 1;
}
}
return count;
};

const goalTypeTitle = (goalType: "ev" | "sh" | "pp" | "en" | "pn") => {
switch (goalType) {
case "ev":
Expand All @@ -201,30 +200,35 @@ const goalTypeTitle = (goalType: "ev" | "sh" | "pp" | "en" | "pn") => {
}
};

const ScoringSummary = memo(
({
events,
numPeriods,
teams,
}: {
count: number;
events: PlayByPlayEventScore[];
numPeriods: number;
teams: [Team, Team];
}) => {
let prevQuarter: number;
const processedEvents = processEvents(events);
const ScoringSummary = ({
processedEvents,
numPeriods,
teams,
}: {
processedEvents: ReturnType<typeof processEvents>;
numPeriods: number;
teams: [Team, Team];
}) => {
let prevQuarter: number | "Shootout";

if (processedEvents.length === 0) {
return <p>None</p>;
}
if (processedEvents.length === 0) {
return <p>None</p>;
}

return (
<table className="table table-sm border-bottom">
<tbody>
{processedEvents.map((event, i) => {
let quarterHeader: ReactNode = null;
const currentPeriod =
event.type === "shootoutShot" ? "Shootout" : event.quarter;
if (currentPeriod !== prevQuarter) {
prevQuarter = currentPeriod;

return (
<table className="table table-sm border-bottom">
<tbody>
{processedEvents.map((event, i) => {
let quarterText = "???";
if (event.quarter > numPeriods) {
let quarterText;
if (event.type === "shootoutShot") {
quarterText = "Shootout";
} else if (event.quarter > numPeriods) {
const overtimes = event.quarter - numPeriods;
if (overtimes > 1) {
quarterText = `${helpers.ordinal(overtimes)} overtime`;
Expand All @@ -237,70 +241,70 @@ const ScoringSummary = memo(
)}`;
}

let quarterHeader: ReactNode = null;
if (event.quarter !== prevQuarter) {
prevQuarter = event.quarter;
quarterHeader = (
<tr>
<td className="text-body-secondary" colSpan={5}>
{quarterText}
</td>
</tr>
);
}
quarterHeader = (
<tr>
<td className="text-body-secondary" colSpan={5}>
{quarterText}
</td>
</tr>
);
}

return (
<Fragment key={i}>
{quarterHeader}
<tr>
<td>{teams[event.t].abbrev}</td>
<td>
{event.t === 0 ? (
<>
<b>{event.score[0]}</b>-
<span className="text-body-secondary">
{event.score[1]}
</span>
</>
) : (
<>
<span className="text-body-secondary">
{event.score[0]}
</span>
-<b>{event.score[1]}</b>
</>
)}
</td>
<td>{formatClock(event.clock)}</td>
<td title={goalTypeTitle(event.goalType)}>
{event.goalType.toUpperCase()}
</td>
<td style={{ whiteSpace: "normal" }}>
{event.shotType === "reboundShot"
? "Rebound shot"
: helpers.upperCaseFirstLetter(event.shotType)}{" "}
by {event.names[0]}
{event.names.length > 1 ? (
<>
{" "}
<span className="text-body-secondary">
(assist: {event.names.slice(1).join(", ")})
return (
<Fragment key={i}>
{quarterHeader}
<tr>
<td>{teams[event.t].abbrev}</td>
<td>
{event.score.map((pts, i) => {
return (
<Fragment key={i}>
<span
className={
!event.noPoints && event.t === i
? "fw-bold"
: event.noPoints && event.t === i
? "text-danger"
: "text-body-secondary"
}
>
{pts}
</span>
</>
) : null}
</td>
</tr>
</Fragment>
);
})}
</tbody>
</table>
);
},
(prevProps, nextProps) => {
return prevProps.count === nextProps.count;
},
);
{i === 0 ? "-" : null}
</Fragment>
);
})}
</td>
<td>
{currentPeriod !== "Shootout"
? formatClock(event.clock)
: null}
</td>
<td title={goalTypeTitle(event.goalType)}>
{event.goalType.toUpperCase()}
</td>
<td style={{ whiteSpace: "normal" }}>
{event.shotType === "reboundShot"
? "Rebound shot"
: helpers.upperCaseFirstLetter(event.shotType)}{" "}
by {event.names[0]}
{event.names.length > 1 ? (
<>
{" "}
<span className="text-body-secondary">
(assist: {event.names.slice(1).join(", ")})
</span>
</>
) : null}
</td>
</tr>
</Fragment>
);
})}
</tbody>
</table>
);
};

const BoxScore = ({
boxScore,
Expand All @@ -311,13 +315,17 @@ const BoxScore = ({
forceRowUpdate: boolean;
Row: any;
}) => {
const processedEvents = useMemo(
() => processEvents(boxScore.scoringSummary),
[boxScore.scoringSummary],
);

return (
<div className="mb-3">
<h2>Scoring Summary</h2>
<ScoringSummary
key={boxScore.gid}
count={getCount(boxScore.scoringSummary)}
events={boxScore.scoringSummary}
processedEvents={processedEvents}
numPeriods={boxScore.numPeriods ?? 4}
teams={boxScore.teams}
/>
Expand Down
Loading

0 comments on commit 58b9574

Please sign in to comment.