2022-10-31 13:25:32 +00:00
|
|
|
import { Accessor, Component, createSignal, For, Show } from "solid-js";
|
2022-11-02 13:18:18 +00:00
|
|
|
import { Board as BoardData, Coordinate, Move, PieceType, Side } from "../events";
|
2022-10-31 13:25:32 +00:00
|
|
|
import './Board.css';
|
2022-11-02 13:18:18 +00:00
|
|
|
import Button, { ButtonContainer } from "./Button";
|
2022-10-31 13:25:32 +00:00
|
|
|
|
|
|
|
interface Props {
|
|
|
|
board: Accessor<BoardData>;
|
|
|
|
moves: Accessor<Move[]>;
|
|
|
|
makeMove: (move: Move) => void;
|
2022-11-02 13:18:18 +00:00
|
|
|
side: Side;
|
2022-10-31 13:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const PIECE_CHARS: Record<PieceType, string> = {
|
|
|
|
'king': '♔',
|
|
|
|
'queen': '♕',
|
|
|
|
'rook': '♜',
|
|
|
|
'knight': '♞',
|
|
|
|
'bishop': '♝',
|
|
|
|
'pawn': '♙',
|
|
|
|
};
|
|
|
|
|
|
|
|
const Board: Component<Props> = (props) => {
|
|
|
|
const [selectedSquare, setSelectedSquare] = createSignal<number | null>(null);
|
2022-11-02 13:18:18 +00:00
|
|
|
const [promotionMove, setPromotionMove] = createSignal<Move | null>(null);
|
2022-10-31 13:25:32 +00:00
|
|
|
|
|
|
|
const validMoves = () => {
|
|
|
|
const selected = selectedSquare();
|
|
|
|
if (selected !== null) {
|
|
|
|
return props.moves().filter(move => (move.from.rank * 8 + move.from.file) === selected);
|
|
|
|
} else {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-02 13:18:18 +00:00
|
|
|
const board = () => {
|
|
|
|
if (props.side === 'white') {
|
|
|
|
return props.board().slice().reverse();
|
|
|
|
} else {
|
|
|
|
return props.board();
|
|
|
|
}
|
|
|
|
};
|
2022-10-31 13:25:32 +00:00
|
|
|
|
2022-11-02 13:18:18 +00:00
|
|
|
return <>
|
|
|
|
<div class="board">
|
|
|
|
{board().map((piece, i_) => {
|
|
|
|
const i = props.side === 'white' ? 63 - i_ : i_;
|
|
|
|
const coord: Coordinate = {
|
|
|
|
rank: Math.floor(i / 8),
|
|
|
|
file: i % 8,
|
|
|
|
};
|
|
|
|
const isLight = () => ((i % 8) + Math.floor(i / 8)) % 2 == 0;
|
|
|
|
const hasMoves = () => props.moves().find((move) => move.from.rank === coord.rank && move.from.file === coord.file) !== undefined;
|
|
|
|
const targetMove = () => validMoves().find(move => move.to.rank === coord.rank && move.to.file === coord.file);
|
|
|
|
const isTarget = () => targetMove() !== undefined;
|
|
|
|
|
|
|
|
return <>
|
|
|
|
<div classList={{
|
|
|
|
square: true,
|
|
|
|
light: isLight(),
|
|
|
|
dark: !isLight(),
|
|
|
|
black: piece?.side === 'black',
|
|
|
|
white: piece?.side === 'white',
|
|
|
|
selectable: hasMoves() || isTarget(),
|
|
|
|
}} onClick={() => {
|
|
|
|
if (hasMoves()) {
|
|
|
|
if (selectedSquare() === i) {
|
|
|
|
setSelectedSquare(null);
|
|
|
|
} else {
|
|
|
|
setSelectedSquare(i);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const target = targetMove();
|
|
|
|
console.log(target);
|
|
|
|
if (target) {
|
|
|
|
if (target.promotions) {
|
|
|
|
setPromotionMove(target);
|
|
|
|
} else {
|
|
|
|
props.makeMove(target);
|
|
|
|
setSelectedSquare(null);
|
|
|
|
setPromotionMove(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}}>
|
|
|
|
<Show when={selectedSquare() === i}><div class="borderSelected" /></Show>
|
|
|
|
<Show when={isTarget()}><div class="borderTarget" /></Show>
|
|
|
|
<Show when={piece !== null}>{PIECE_CHARS[piece!.ty]}</Show>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
<Show when={promotionMove() !== null}>
|
|
|
|
<ButtonContainer>
|
|
|
|
<For each={promotionMove()?.promotions!}>
|
|
|
|
{(promotion, i) => <Button onClick={() => {
|
|
|
|
props.makeMove({
|
|
|
|
...promotionMove()!,
|
|
|
|
promotions: [promotion],
|
|
|
|
});
|
2022-10-31 13:25:32 +00:00
|
|
|
setSelectedSquare(null);
|
2022-11-02 13:18:18 +00:00
|
|
|
setPromotionMove(null);
|
|
|
|
}}>
|
|
|
|
{promotion.ty}
|
|
|
|
</Button>}
|
|
|
|
</For>
|
|
|
|
</ButtonContainer>
|
|
|
|
</Show>
|
|
|
|
</>
|
2022-10-31 13:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export default Board;
|