feat(P5B.T4): TicTacToeBoard + ConnectFourBoard render-only components
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { type Cell, tttWinningLine } from '@chat-app/shared/chat';
|
||||
|
||||
interface Props {
|
||||
board: Cell[];
|
||||
myPlayerIdx: 0 | 1 | null;
|
||||
disabled: boolean;
|
||||
onMove: (cell: number) => void;
|
||||
}
|
||||
|
||||
const MARK = ['×', '○'] as const;
|
||||
|
||||
export function TicTacToeBoard({ board, myPlayerIdx, disabled, onMove }: Props) {
|
||||
const winLine = tttWinningLine(board);
|
||||
const winSet = new Set<number>(winLine ?? []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mx-auto grid w-full max-w-md grid-cols-3 gap-2 p-4"
|
||||
style={{ aspectRatio: '1 / 1' }}
|
||||
role="grid"
|
||||
aria-label="Tic-Tac-Toe"
|
||||
>
|
||||
{board.map((cell, i) => {
|
||||
const filled = cell !== null;
|
||||
const inWin = winSet.has(i);
|
||||
const canClick = !disabled && !filled && myPlayerIdx !== null;
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
onClick={() => canClick && onMove(i)}
|
||||
disabled={!canClick}
|
||||
aria-label={'Feld ' + (i + 1) + (filled ? ' belegt' : ' frei')}
|
||||
className={
|
||||
'flex aspect-square items-center justify-center rounded-xl border-2 text-5xl font-bold transition ' +
|
||||
(inWin
|
||||
? 'border-emerald-400 bg-emerald-400/20 text-emerald-200'
|
||||
: filled
|
||||
? cell === 0
|
||||
? 'border-rose-500/60 bg-rose-500/10 text-rose-300'
|
||||
: 'border-sky-500/60 bg-sky-500/10 text-sky-300'
|
||||
: canClick
|
||||
? 'cursor-pointer border-line bg-surface-2 text-fg-muted hover:bg-surface-3 hover:text-fg'
|
||||
: 'cursor-not-allowed border-line bg-surface-2 text-fg-muted opacity-60')
|
||||
}
|
||||
>
|
||||
{filled ? MARK[cell as 0 | 1] : ''}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user