2022-05-08 17:13:21 +03:00
|
|
|
import * as React from "react";
|
2026-10-16 09:37:50 +03:00
|
|
|
import { FunctionComponent, useState } from "react";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import { BoardViewModel, Game, getSowingPath } from "@mancala/core";
|
|
|
|
|
import { Pit } from "mancala.js";
|
2022-07-14 13:38:00 +03:00
|
|
|
import { Context } from "../../context/context";
|
2026-10-16 09:37:50 +03:00
|
|
|
import { getWebTheme } from "../../theme/themes";
|
|
|
|
|
import PitView, { PitMarks } from "./PitView";
|
2022-07-14 08:53:26 +03:00
|
|
|
import StoreView from "./StoreView";
|
2026-10-16 09:37:50 +03:00
|
|
|
import "./BoardView.css";
|
2021-06-29 03:29:46 +03:00
|
|
|
|
2026-10-16 09:37:50 +03:00
|
|
|
// 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.
|
2022-05-08 17:13:21 +03:00
|
|
|
const BoardView: FunctionComponent<{
|
2022-08-01 22:24:48 +03:00
|
|
|
game: Game;
|
2022-05-15 02:01:51 +03:00
|
|
|
context: Context;
|
|
|
|
|
boardViewModel: BoardViewModel;
|
2026-10-16 09:37:50 +03:00
|
|
|
// Show player 2 at the bottom.
|
2022-09-02 00:10:41 +03:00
|
|
|
revert: boolean;
|
2026-10-16 09:37:50 +03:00
|
|
|
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);
|
2022-09-02 00:10:41 +03:00
|
|
|
|
2026-10-16 09:37:50 +03:00
|
|
|
// 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;
|
2026-07-02 23:40:45 +03:00
|
|
|
|
2026-10-16 09:37:50 +03:00
|
|
|
const marksOf = (index: number): PitMarks => ({
|
|
|
|
|
highlight: boardViewModel.pits[index]?.highlight,
|
|
|
|
|
preview: preview[index],
|
|
|
|
|
land: previewLand !== undefined ? index === previewLand : index === landIndex,
|
|
|
|
|
last: index === lastMoveIndex,
|
|
|
|
|
});
|
2026-07-02 23:40:45 +03:00
|
|
|
|
2026-10-16 09:37:50 +03:00
|
|
|
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()];
|
2022-09-02 00:10:41 +03:00
|
|
|
|
2026-10-16 09:37:50 +03:00
|
|
|
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)} />
|
|
|
|
|
);
|
|
|
|
|
};
|
2026-07-02 23:40:45 +03:00
|
|
|
|
2022-05-08 17:13:21 +03:00
|
|
|
return (
|
2026-10-16 09:37:50 +03:00
|
|
|
<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>
|
2022-05-08 17:13:21 +03:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
2021-06-29 03:29:46 +03:00
|
|
|
|
2022-05-08 17:13:21 +03:00
|
|
|
export default BoardView;
|