79 lines
3.6 KiB
TypeScript
79 lines
3.6 KiB
TypeScript
|
|
import * as React from "react";
|
|||
|
|
import { FunctionComponent } from "react";
|
|||
|
|
import { useTranslation } from "react-i18next";
|
|||
|
|
import { getSeriesScore, Seat, SeriesGame } from "@mancala/core";
|
|||
|
|
|
|||
|
|
// The most boxes the strip shows; earlier games collapse into a "+N" box.
|
|||
|
|
const MAX_BOXES = 8;
|
|||
|
|
|
|||
|
|
// Above the board from the second game of a series (docs/specs/rematch, R3): the series score and one box per game,
|
|||
|
|
// each with its winner's colour, the stores from the bottom player's side, a dot for who moved first and how it
|
|||
|
|
// ended. Finished games open their replay in a new tab; the game being played is marked.
|
|||
|
|
const SeriesStrip: FunctionComponent<{
|
|||
|
|
// Finished games, oldest first.
|
|||
|
|
games: SeriesGame[],
|
|||
|
|
// A game of the series is being played after them.
|
|||
|
|
hasCurrentGame: boolean,
|
|||
|
|
bottomSeat: Seat,
|
|||
|
|
bottomName: string,
|
|||
|
|
topName: string,
|
|||
|
|
}> = ({ games, hasCurrentGame, bottomSeat, bottomName, topName }) => {
|
|||
|
|
const { t } = useTranslation();
|
|||
|
|
const score = getSeriesScore(games, bottomSeat);
|
|||
|
|
const finishedBoxes = MAX_BOXES - (hasCurrentGame ? 1 : 0);
|
|||
|
|
const hiddenCount = games.length > finishedBoxes ? games.length - finishedBoxes + 1 : 0;
|
|||
|
|
const shown = games.slice(hiddenCount);
|
|||
|
|
|
|||
|
|
const renderGame = (game: SeriesGame, index: number) => {
|
|||
|
|
const number = hiddenCount + index + 1;
|
|||
|
|
const bottomStore = bottomSeat === "player1" ? game.player1Store : game.player2Store;
|
|||
|
|
const topStore = bottomSeat === "player1" ? game.player2Store : game.player1Store;
|
|||
|
|
const winner = game.result === "player1" || game.result === "player2"
|
|||
|
|
? (game.result === bottomSeat ? "mine" : "theirs") : "draw";
|
|||
|
|
const ending = game.endReason === "timeout" ? t("EndedOnTime") : game.endReason === "left" ? t("EndedByLeaving") : undefined;
|
|||
|
|
const bottomFirst = game.firstSeat === bottomSeat;
|
|||
|
|
const firstMark = <span className="sfirst" title={t("MovedFirst")} />;
|
|||
|
|
const label = [
|
|||
|
|
t("SeriesGameNumber", { number }),
|
|||
|
|
`${bottomName} ${bottomStore} – ${topStore} ${topName}`,
|
|||
|
|
`${t("MovedFirst")}: ${bottomFirst ? bottomName : topName}`,
|
|||
|
|
ending,
|
|||
|
|
].filter(Boolean).join(", ");
|
|||
|
|
return (
|
|||
|
|
<li key={game.gameId}>
|
|||
|
|
<a className={"sg " + winner} href={`/game/${game.gameId}`} target="_blank" rel="noopener" aria-label={label}>
|
|||
|
|
<span className="sn">{ending ? `${number} · ${ending}` : number}</span>
|
|||
|
|
<span className="ss">{bottomFirst && firstMark}{bottomStore}–{topStore}{!bottomFirst && firstMark}</span>
|
|||
|
|
</a>
|
|||
|
|
</li>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<nav className="series" aria-label={t("Series")}>
|
|||
|
|
<div className="sscore">
|
|||
|
|
<span className="slabel">{t("Series")}</span>
|
|||
|
|
<span className="sname">{bottomName}</span>
|
|||
|
|
<b>{score.wins} – {score.losses}</b>
|
|||
|
|
<span className="sname">{topName}</span>
|
|||
|
|
</div>
|
|||
|
|
<ol className="sgames">
|
|||
|
|
{hiddenCount > 0 && (
|
|||
|
|
<li><span className="sg more" title={t("SeriesEarlierGames", { count: hiddenCount })}>+{hiddenCount}</span></li>
|
|||
|
|
)}
|
|||
|
|
{shown.map(renderGame)}
|
|||
|
|
{hasCurrentGame && (
|
|||
|
|
<li aria-current="true">
|
|||
|
|
<span className="sg now">
|
|||
|
|
<span className="sn">{games.length + 1}</span>
|
|||
|
|
<span className="ss">{t("SeriesCurrentGame")}</span>
|
|||
|
|
</span>
|
|||
|
|
</li>
|
|||
|
|
)}
|
|||
|
|
</ol>
|
|||
|
|
</nav>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default SeriesStrip;
|