Pits and stores are wells with scattered stones and count badges, the viewer's store on the right. Below 600 px the board stands upright. Playable pits preview their move on hover, animated pits are ringed and the pit of the last move has a dashed ring.
91 lines
3.9 KiB
TypeScript
91 lines
3.9 KiB
TypeScript
import * as React from "react";
|
|
import { FunctionComponent, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { BoardViewModel, Game, getSowingPath } from "@mancala/core";
|
|
import { Pit } from "mancala.js";
|
|
import { Context } from "../../context/context";
|
|
import { getWebTheme } from "../../theme/themes";
|
|
import PitView, { PitMarks } from "./PitView";
|
|
import StoreView from "./StoreView";
|
|
import "./BoardView.css";
|
|
|
|
// The board as the bottom player sees it: their pits along the bottom from left to right, their store on the right.
|
|
// It fills its container's width.
|
|
const BoardView: FunctionComponent<{
|
|
game: Game;
|
|
context: Context;
|
|
boardViewModel: BoardViewModel;
|
|
// Show player 2 at the bottom.
|
|
revert: boolean;
|
|
topName: string;
|
|
bottomName: string;
|
|
// Makes the bottom player's pits buttons.
|
|
onPitSelect?: (index: number, pit: Pit) => void;
|
|
// The bottom player can move now: their pits with stones are highlighted and preview their move.
|
|
canPlay?: boolean;
|
|
// Board index of the pit the last move was played from.
|
|
lastMoveIndex?: number;
|
|
// Board index where the shown move's last stone landed.
|
|
landIndex?: number;
|
|
}> = ({ game, context, boardViewModel, revert, topName, bottomName, onPitSelect, canPlay, lastMoveIndex, landIndex }) => {
|
|
const { t } = useTranslation();
|
|
const [previewIndex, setPreviewIndex] = useState<number | undefined>(undefined);
|
|
const board = game.mancalaGame.board;
|
|
const themeId = getWebTheme(context.themeManager.theme).id;
|
|
const stones = boardViewModel.pits.map((pit) => pit.stoneCount);
|
|
|
|
// Pits the hovered move drops stones into, with how many each, and where its last stone lands.
|
|
const preview: Record<number, number> = {};
|
|
const path = canPlay && previewIndex !== undefined ? getSowingPath(stones, previewIndex) : [];
|
|
path.forEach((index) => (preview[index] = (preview[index] ?? 0) + 1));
|
|
const previewLand = path.length > 0 ? path[path.length - 1] : undefined;
|
|
|
|
const marksOf = (index: number): PitMarks => ({
|
|
highlight: boardViewModel.pits[index]?.highlight,
|
|
preview: preview[index],
|
|
land: previewLand !== undefined ? index === previewLand : index === landIndex,
|
|
last: index === lastMoveIndex,
|
|
});
|
|
|
|
const topPits = revert ? board.player1Pits : board.player2Pits;
|
|
const bottomPits = revert ? board.player2Pits : board.player1Pits;
|
|
const [topStore, bottomStore] = revert
|
|
? [board.player1BankIndex(), board.player2BankIndex()]
|
|
: [board.player2BankIndex(), board.player1BankIndex()];
|
|
|
|
const pitView = (pit: Pit, position: number, top: boolean) => {
|
|
const stoneCount = stones[pit.index];
|
|
const playable = !top && !!canPlay && stoneCount > 0;
|
|
const pitNumber = (top ? 5 - position : position) + 1;
|
|
return (
|
|
<PitView
|
|
key={pit.index}
|
|
index={pit.index}
|
|
stoneCount={stoneCount}
|
|
top={top}
|
|
position={position}
|
|
themeId={themeId}
|
|
label={t("PitLabel", { name: top ? topName : bottomName, pit: pitNumber, count: stoneCount })}
|
|
marks={marksOf(pit.index)}
|
|
playable={playable}
|
|
onClick={!top && onPitSelect ? () => onPitSelect(pit.index, board.pits[pit.index]) : undefined}
|
|
onPreview={(active) => setPreviewIndex(active ? pit.index : undefined)} />
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="bwrap">
|
|
<div className="board">
|
|
<StoreView index={topStore} stoneCount={stones[topStore]} side="l" themeId={themeId}
|
|
label={t("StoreLabel", { name: topName, count: stones[topStore] })} marks={marksOf(topStore)} />
|
|
{[...topPits].reverse().map((pit, position) => pitView(pit, position, true))}
|
|
{bottomPits.map((pit, position) => pitView(pit, position, false))}
|
|
<StoreView index={bottomStore} stoneCount={stones[bottomStore]} side="r" themeId={themeId}
|
|
label={t("StoreLabel", { name: bottomName, count: stones[bottomStore] })} marks={marksOf(bottomStore)} />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BoardView;
|