mancala/apps/frontend/src/components/board/PitView.tsx
Halit Aksoy f8ce8f215f feat(frontend): show hints in games against the bot
With hints on, the player's pits show whether their move gives an extra turn
or how many stones it takes, and a line under the board says what the last
move did and why. A Hints switch appears only in games against the bot.
2026-10-16 14:58:56 +03:00

75 lines
2.5 KiB
TypeScript

import * as React from "react";
import { FunctionComponent } from "react";
import { PitHighlight } from "@mancala/core";
import Stones from "./Stones";
export type PitMarks = {
highlight?: PitHighlight;
// Stones a hovered move would drop here.
preview?: number;
// The hovered move's last stone lands here, or the shown move's did.
land?: boolean;
// The last move was played from here.
last?: boolean;
// What playing this pit would do, in games against the bot with hints on, e.g. "Extra turn".
hint?: string;
};
export function getMarkClasses(marks: PitMarks): string {
const highlight = marks.highlight === "capture" ? " cap" : marks.highlight === "selected" ? " sel"
: marks.highlight ? " drop" : "";
return highlight + (marks.preview ? " pv" : "") + (marks.land ? " land" : "") + (marks.last ? " last" : "");
}
// A pit in the top or bottom row. Position is its column from the left, 0-5. The viewer's pits are buttons.
const PitView: FunctionComponent<{
index: number;
stoneCount: number;
top: boolean;
position: number;
themeId: string;
label: string;
marks: PitMarks;
// The viewer can play this pit now.
playable?: boolean;
// Set for pits the viewer can click.
onClick?: () => void;
// Called with true when a playable pit is hovered or focused, with false when that ends.
onPreview?: (active: boolean) => void;
}> = ({ index, stoneCount, top, position, themeId, label, marks, playable, onClick, onPreview }) => {
const className = "pit" + (playable ? " mine" : "") + getMarkClasses(marks);
const style = {
"--hr": top ? "1" : "2",
"--hc": String(position + 2),
"--vr": String(position + 2),
"--vc": top ? "2" : "1",
} as React.CSSProperties;
const content = (
<>
<Stones count={stoneCount} seed={index * 5.3 + 1} themeId={themeId} />
<span className={top ? "badge t" : "badge b"}>{stoneCount}</span>
{!!marks.preview && <span className="pvchip">{`+${marks.preview}`}</span>}
{!!marks.hint && <span className="hintchip">{marks.hint}</span>}
</>
);
if (!onClick) {
return <div className={className} style={style} role="img" aria-label={label}>{content}</div>;
}
return (
<button
className={className}
style={style}
aria-label={label}
aria-disabled={!playable}
onClick={onClick}
onMouseEnter={() => playable && onPreview?.(true)}
onMouseLeave={() => onPreview?.(false)}
onFocus={() => playable && onPreview?.(true)}
onBlur={() => onPreview?.(false)}>
{content}
</button>
);
};
export default PitView;