Split into multiple files, add eslint, prettier
This commit is contained in:
parent
2dcf6da9e6
commit
f417229526
4
.eslintignore
Normal file
4
.eslintignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
# don't ever lint node_modules
|
||||
node_modules
|
||||
# don't lint build output (make sure it's set to your correct build folder name)
|
||||
dist
|
11
.eslintrc.js
Normal file
11
.eslintrc.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
};
|
3
.prettierignore
Normal file
3
.prettierignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -39,5 +39,11 @@
|
|||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
||||
"@typescript-eslint/parser": "^5.8.1",
|
||||
"eslint": "^8.5.0",
|
||||
"prettier": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
|
103
src/App.test.tsx
103
src/App.test.tsx
|
@ -1,100 +1,5 @@
|
|||
import { validateBoardState } from './App';
|
||||
test('it works', () => {
|
||||
expect(2 + 2).toEqual(4);
|
||||
})
|
||||
|
||||
test('validates valid states', () => {
|
||||
const inputs = [
|
||||
{
|
||||
"a": {
|
||||
3: {
|
||||
kind: "king",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"b": {
|
||||
5: {
|
||||
kind: "rook",
|
||||
color: "white",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": {
|
||||
8: {
|
||||
kind: "bishop",
|
||||
color: "black",
|
||||
}
|
||||
},
|
||||
"e": {
|
||||
4: {
|
||||
kind: "pawn",
|
||||
color: "white",
|
||||
},
|
||||
5: {
|
||||
kind: "knight",
|
||||
color: "white",
|
||||
},
|
||||
7: {
|
||||
kind: "queen",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inputs.map((input) => {
|
||||
const result = validateBoardState(input);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test('does not validate invalid states', () => {
|
||||
const inputs = [
|
||||
{
|
||||
"j": {
|
||||
3: {
|
||||
kind: "king",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"b": {
|
||||
9: {
|
||||
kind: "rook",
|
||||
color: "white",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": {
|
||||
8: {
|
||||
kind: "bishop",
|
||||
color: "black",
|
||||
}
|
||||
},
|
||||
"e": {
|
||||
4: {
|
||||
kind: "pawn",
|
||||
color: "white",
|
||||
},
|
||||
5: {
|
||||
type: "knight",
|
||||
color: "white",
|
||||
},
|
||||
7: {
|
||||
kind: "queen",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inputs.map((input) => {
|
||||
const result = validateBoardState(input);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
export { }
|
||||
|
|
269
src/App.tsx
269
src/App.tsx
|
@ -1,272 +1,6 @@
|
|||
import React from "react";
|
||||
import Board, { BoardState } from "./Board";
|
||||
import './App.css';
|
||||
|
||||
type COLOR = "black" | "white";
|
||||
type PIECE = "pawn" | "rook" | "knight" | "bishop" | "queen" | "king";
|
||||
type FILE = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h";
|
||||
type RANK = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
|
||||
type Coordinates = {
|
||||
rank: RANK;
|
||||
file: FILE;
|
||||
}
|
||||
|
||||
type OnSelectPiece = (selected: Selected) => Promise<void>;
|
||||
type OnMovePiece = (selected: Selected, destination: Coordinates) => Promise<void>;
|
||||
type OnSelectDestination = (destination: Coordinates) => Promise<void>;
|
||||
|
||||
interface BoardProps {
|
||||
playerColor: COLOR;
|
||||
boardState: BoardState;
|
||||
onSelectPiece: OnSelectPiece;
|
||||
onMovePiece: OnMovePiece;
|
||||
};
|
||||
|
||||
interface CellProps {
|
||||
boardState: BoardState;
|
||||
playerColor: COLOR;
|
||||
rank: RANK;
|
||||
file: FILE;
|
||||
selected: Selected | null,
|
||||
onSelectPiece: OnSelectPiece;
|
||||
onSelectDestination: OnSelectDestination;
|
||||
}
|
||||
|
||||
type PieceState = {
|
||||
kind: PIECE;
|
||||
color: COLOR;
|
||||
}
|
||||
|
||||
type Selected = {
|
||||
piece: PieceState,
|
||||
location: Coordinates,
|
||||
}
|
||||
|
||||
type FileState = {
|
||||
[key in RANK]?: PieceState;
|
||||
}
|
||||
|
||||
type BoardState = {
|
||||
[key in FILE]?: FileState;
|
||||
}
|
||||
|
||||
const RANKS: RANK[] = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8,
|
||||
];
|
||||
|
||||
const FILES: FILE[] = [
|
||||
"a", "b", "c", "d", "e", "f", "g", "h",
|
||||
];
|
||||
|
||||
const PIECES: PIECE[] = [
|
||||
"pawn", "rook", "knight", "bishop", "queen", "king"
|
||||
];
|
||||
|
||||
const COLORS: COLOR[] = [
|
||||
"white", "black"
|
||||
]
|
||||
|
||||
const COLORS_AS_STRINGS: string[] = COLORS.map((color) => color as string);
|
||||
const PIECES_AS_STRINGS: string[] = PIECES.map((piece) => piece as string);
|
||||
const FILES_AS_STRINGS: string[] = FILES.map((file) => file as string);
|
||||
const RANKS_AS_NUMBERS: number[] = RANKS.map((rank) => rank as number);
|
||||
|
||||
const iconFor = ({ kind, color }: PieceState): String => {
|
||||
switch (kind) {
|
||||
case "pawn":
|
||||
return color === "white" ? "♙" : "♟︎";
|
||||
case "rook":
|
||||
return color === "white" ? "♖" : "♜";
|
||||
case "knight":
|
||||
return color === "white" ? "♘" : "♞";
|
||||
case "bishop":
|
||||
return color === "white" ? "♗" : "♝";
|
||||
case "queen":
|
||||
return color === "white" ? "♕" : "♛";
|
||||
case "king":
|
||||
return color === "white" ? "♔" : "♚";
|
||||
}
|
||||
}
|
||||
|
||||
const validateBoardState = (input: unknown): BoardState | null => {
|
||||
if (typeof (input) !== "object" || Array.isArray(input) || input === null) {
|
||||
console.error("Input is not keyed object");
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasInvalidShape = Object.entries(input).some(([file, ranks]) => {
|
||||
if (typeof (file) !== "string") {
|
||||
console.error("file is not string");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!FILES_AS_STRINGS.includes(file)) {
|
||||
console.error("file is not included in valid files")
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof (ranks) !== "object" || Array.isArray(ranks) || ranks === null) {
|
||||
console.error("ranks is not a keyed object");
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.entries(ranks).some(([rank, pieces]) => {
|
||||
if (typeof (rank) !== "string") {
|
||||
console.error("rank is not a string");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!RANKS_AS_NUMBERS.includes(Number(rank))) {
|
||||
console.error("rank is not included in valid ranks");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof (pieces) !== "object" || Array.isArray(pieces) || pieces === null) {
|
||||
console.error("piece state is not a keyed object");
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.entries(pieces).some(([key, value]) => {
|
||||
if (typeof (key) !== "string") {
|
||||
console.error("piece key is not a string");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!["kind", "color"].includes(key)) {
|
||||
console.error("piece key is not included in valid keys")
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key === "kind" && (typeof (value) !== "string" || !PIECES_AS_STRINGS.includes(value))) {
|
||||
console.error("kind's value is not a string, or not a valid piece");
|
||||
return true;
|
||||
} else if (key === "color" && (typeof (value) !== "string" || !COLORS_AS_STRINGS.includes(value))) {
|
||||
console.error("colors' value is not a string, or not a valid color");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (hasInvalidShape) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return input as BoardState;
|
||||
}
|
||||
|
||||
const Cell = ({ boardState, playerColor, rank, file, selected, onSelectDestination, onSelectPiece }: CellProps): JSX.Element => {
|
||||
const filed = boardState[file];
|
||||
|
||||
const onEmptyClick = async () => {
|
||||
return await onSelectDestination({ rank, file });
|
||||
};
|
||||
|
||||
if (typeof (filed) === "undefined") {
|
||||
return <td className="icon-cell" onClick={onEmptyClick}></td>;
|
||||
}
|
||||
|
||||
const piece = filed[rank];
|
||||
|
||||
if (typeof (piece) === "undefined") {
|
||||
return <td className="icon-cell" onClick={onEmptyClick}></td>;
|
||||
}
|
||||
|
||||
const onClick = async () => {
|
||||
const selected: Selected = {
|
||||
piece: { ...piece },
|
||||
location: {
|
||||
file,
|
||||
rank,
|
||||
},
|
||||
};
|
||||
|
||||
return await onSelectPiece(selected);
|
||||
};
|
||||
|
||||
const maybeOnClick = piece.color === playerColor ? onClick : undefined;
|
||||
|
||||
const classNames = selected !== null
|
||||
&& selected.piece.kind === piece.kind
|
||||
&& selected.piece.color === piece.color
|
||||
&& selected.location.file === file
|
||||
&& selected.location.rank === rank
|
||||
? ["icon-cell", "selected"]
|
||||
: ["icon-cell"];
|
||||
|
||||
return <td className={classNames.join(" ")} onClick={maybeOnClick}><span className="icon">{iconFor(piece)}</span></td>;
|
||||
}
|
||||
|
||||
const Board = ({ boardState, onMovePiece, onSelectPiece: superOnSelectPiece, playerColor }: BoardProps): JSX.Element => {
|
||||
const [selected, setSelected] = React.useState<Selected | null>(null);
|
||||
|
||||
const onSelectPiece: OnSelectPiece = async (selected) => {
|
||||
setSelected(selected);
|
||||
|
||||
return await superOnSelectPiece(selected);
|
||||
};
|
||||
|
||||
const onSelectDestination = async (destination: Coordinates): Promise<void> => {
|
||||
if (selected === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const output = await onMovePiece(selected, destination);
|
||||
|
||||
setSelected(null);
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const files = [...FILES];
|
||||
const ranks = [...RANKS];
|
||||
|
||||
if (playerColor === "white") {
|
||||
ranks.reverse();
|
||||
} else {
|
||||
files.reverse();
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="chess-board">
|
||||
<tr>
|
||||
<td></td>
|
||||
{files.map((file) => (
|
||||
<td key={file}>{file.toUpperCase()}</td>
|
||||
))}
|
||||
<td></td>
|
||||
</tr>
|
||||
{ranks.map((rank) => (
|
||||
<tr key={rank}>
|
||||
<td>{rank}</td>
|
||||
{files.map((file) => (
|
||||
<Cell
|
||||
key={file}
|
||||
boardState={boardState}
|
||||
playerColor={playerColor}
|
||||
file={file}
|
||||
rank={rank}
|
||||
selected={selected}
|
||||
onSelectDestination={onSelectDestination}
|
||||
onSelectPiece={onSelectPiece}
|
||||
/>
|
||||
))}
|
||||
<td>{rank}</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<td></td>
|
||||
{files.map((file) => (
|
||||
<td key={file}>{file.toUpperCase()}</td>
|
||||
))}
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
const App = (): JSX.Element => {
|
||||
const boardState: BoardState = {
|
||||
"a": {
|
||||
|
@ -311,4 +45,3 @@ const App = (): JSX.Element => {
|
|||
}
|
||||
|
||||
export default App;
|
||||
export { validateBoardState };
|
||||
|
|
198
src/Board.tsx
Normal file
198
src/Board.tsx
Normal file
|
@ -0,0 +1,198 @@
|
|||
import React from "react";
|
||||
|
||||
type COLOR = "black" | "white";
|
||||
type PIECE = "pawn" | "rook" | "knight" | "bishop" | "queen" | "king";
|
||||
type FILE = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h";
|
||||
type RANK = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
|
||||
|
||||
type Coordinates = {
|
||||
rank: RANK;
|
||||
file: FILE;
|
||||
}
|
||||
|
||||
type PieceState = {
|
||||
kind: PIECE;
|
||||
color: COLOR;
|
||||
}
|
||||
|
||||
type Selected = {
|
||||
piece: PieceState,
|
||||
location: Coordinates,
|
||||
}
|
||||
|
||||
type OnSelectPiece = (selected: Selected) => Promise<void>;
|
||||
type OnMovePiece = (selected: Selected, destination: Coordinates) => Promise<void>;
|
||||
type OnSelectDestination = (destination: Coordinates) => Promise<void>;
|
||||
|
||||
interface BoardProps {
|
||||
playerColor: COLOR;
|
||||
boardState: BoardState;
|
||||
onSelectPiece: OnSelectPiece;
|
||||
onMovePiece: OnMovePiece;
|
||||
}
|
||||
|
||||
interface CellProps {
|
||||
boardState: BoardState;
|
||||
playerColor: COLOR;
|
||||
rank: RANK;
|
||||
file: FILE;
|
||||
selected: Selected | null,
|
||||
onSelectPiece: OnSelectPiece;
|
||||
onSelectDestination: OnSelectDestination;
|
||||
}
|
||||
|
||||
type FileState = {
|
||||
[key in RANK]?: PieceState;
|
||||
}
|
||||
|
||||
export type BoardState = {
|
||||
[key in FILE]?: FileState;
|
||||
}
|
||||
|
||||
const RANKS: RANK[] = [
|
||||
1, 2, 3, 4, 5, 6, 7, 8,
|
||||
];
|
||||
|
||||
const FILES: FILE[] = [
|
||||
"a", "b", "c", "d", "e", "f", "g", "h",
|
||||
];
|
||||
|
||||
const PIECES: PIECE[] = [
|
||||
"pawn", "rook", "knight", "bishop", "queen", "king"
|
||||
];
|
||||
|
||||
const COLORS: COLOR[] = [
|
||||
"white", "black"
|
||||
]
|
||||
|
||||
const iconFor = ({ kind, color }: PieceState): string => {
|
||||
switch (kind) {
|
||||
case "pawn":
|
||||
return color === "white" ? "♙" : "♟︎";
|
||||
case "rook":
|
||||
return color === "white" ? "♖" : "♜";
|
||||
case "knight":
|
||||
return color === "white" ? "♘" : "♞";
|
||||
case "bishop":
|
||||
return color === "white" ? "♗" : "♝";
|
||||
case "queen":
|
||||
return color === "white" ? "♕" : "♛";
|
||||
case "king":
|
||||
return color === "white" ? "♔" : "♚";
|
||||
}
|
||||
};
|
||||
|
||||
const Cell = ({ boardState, playerColor, rank, file, selected, onSelectDestination, onSelectPiece }: CellProps): JSX.Element => {
|
||||
const filed = boardState[file];
|
||||
|
||||
const onEmptyClick = async () => {
|
||||
return await onSelectDestination({ rank, file });
|
||||
};
|
||||
|
||||
if (typeof (filed) === "undefined") {
|
||||
return <td className="icon-cell" onClick={onEmptyClick}></td>;
|
||||
}
|
||||
|
||||
const piece = filed[rank];
|
||||
|
||||
if (typeof (piece) === "undefined") {
|
||||
return <td className="icon-cell" onClick={onEmptyClick}></td>;
|
||||
}
|
||||
|
||||
const onClick = async () => {
|
||||
const selected: Selected = {
|
||||
piece: { ...piece },
|
||||
location: {
|
||||
file,
|
||||
rank,
|
||||
},
|
||||
};
|
||||
|
||||
return await onSelectPiece(selected);
|
||||
};
|
||||
|
||||
const maybeOnClick = piece.color === playerColor ? onClick : undefined;
|
||||
|
||||
const classNames = selected !== null
|
||||
&& selected.piece.kind === piece.kind
|
||||
&& selected.piece.color === piece.color
|
||||
&& selected.location.file === file
|
||||
&& selected.location.rank === rank
|
||||
? ["icon-cell", "selected"]
|
||||
: ["icon-cell"];
|
||||
|
||||
return <td className={classNames.join(" ")} onClick={maybeOnClick}><span className="icon">{iconFor(piece)}</span></td>;
|
||||
};
|
||||
|
||||
const Board = ({ boardState, onMovePiece, onSelectPiece: superOnSelectPiece, playerColor }: BoardProps): JSX.Element => {
|
||||
const [selected, setSelected] = React.useState<Selected | null>(null);
|
||||
|
||||
const onSelectPiece: OnSelectPiece = async (selected) => {
|
||||
setSelected(selected);
|
||||
|
||||
return await superOnSelectPiece(selected);
|
||||
};
|
||||
|
||||
const onSelectDestination = async (destination: Coordinates): Promise<void> => {
|
||||
if (selected === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const output = await onMovePiece(selected, destination);
|
||||
|
||||
setSelected(null);
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const files = [...FILES];
|
||||
const ranks = [...RANKS];
|
||||
|
||||
if (playerColor === "white") {
|
||||
ranks.reverse();
|
||||
} else {
|
||||
files.reverse();
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="chess-board">
|
||||
<tr>
|
||||
<td></td>
|
||||
{files.map((file) => (
|
||||
<td key={file}>{file.toUpperCase()}</td>
|
||||
))}
|
||||
<td></td>
|
||||
</tr>
|
||||
{ranks.map((rank) => (
|
||||
<tr key={rank}>
|
||||
<td>{rank}</td>
|
||||
{files.map((file) => (
|
||||
<Cell
|
||||
key={file}
|
||||
boardState={boardState}
|
||||
playerColor={playerColor}
|
||||
file={file}
|
||||
rank={rank}
|
||||
selected={selected}
|
||||
onSelectDestination={onSelectDestination}
|
||||
onSelectPiece={onSelectPiece}
|
||||
/>
|
||||
))}
|
||||
<td>{rank}</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<td></td>
|
||||
{files.map((file) => (
|
||||
<td key={file}>{file.toUpperCase()}</td>
|
||||
))}
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default Board;
|
||||
export {
|
||||
PIECES, COLORS, FILES, RANKS,
|
||||
};
|
99
src/validate.test.ts
Normal file
99
src/validate.test.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { validateBoardState } from './validate';
|
||||
|
||||
test('validates valid states', () => {
|
||||
const inputs = [
|
||||
{
|
||||
"a": {
|
||||
3: {
|
||||
kind: "king",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"b": {
|
||||
5: {
|
||||
kind: "rook",
|
||||
color: "white",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": {
|
||||
8: {
|
||||
kind: "bishop",
|
||||
color: "black",
|
||||
}
|
||||
},
|
||||
"e": {
|
||||
4: {
|
||||
kind: "pawn",
|
||||
color: "white",
|
||||
},
|
||||
5: {
|
||||
kind: "knight",
|
||||
color: "white",
|
||||
},
|
||||
7: {
|
||||
kind: "queen",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inputs.map((input) => {
|
||||
const result = validateBoardState(input);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
test('does not validate invalid states', () => {
|
||||
const inputs = [
|
||||
{
|
||||
"j": {
|
||||
3: {
|
||||
kind: "king",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"b": {
|
||||
9: {
|
||||
kind: "rook",
|
||||
color: "white",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"c": {
|
||||
8: {
|
||||
kind: "bishop",
|
||||
color: "black",
|
||||
}
|
||||
},
|
||||
"e": {
|
||||
4: {
|
||||
kind: "pawn",
|
||||
color: "white",
|
||||
},
|
||||
5: {
|
||||
type: "knight",
|
||||
color: "white",
|
||||
},
|
||||
7: {
|
||||
kind: "queen",
|
||||
color: "black",
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
inputs.map((input) => {
|
||||
const result = validateBoardState(input);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
79
src/validate.ts
Normal file
79
src/validate.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { COLORS, PIECES, FILES, RANKS, BoardState } from "./Board";
|
||||
|
||||
const COLORS_AS_STRINGS: string[] = COLORS.map((color) => color as string);
|
||||
const PIECES_AS_STRINGS: string[] = PIECES.map((piece) => piece as string);
|
||||
const FILES_AS_STRINGS: string[] = FILES.map((file) => file as string);
|
||||
const RANKS_AS_NUMBERS: number[] = RANKS.map((rank) => rank as number);
|
||||
|
||||
const validateBoardState = (input: unknown): BoardState | null => {
|
||||
if (typeof (input) !== "object" || Array.isArray(input) || input === null) {
|
||||
console.error("Input is not keyed object");
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasInvalidShape = Object.entries(input).some(([file, ranks]) => {
|
||||
if (typeof (file) !== "string") {
|
||||
console.error("file is not string");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!FILES_AS_STRINGS.includes(file)) {
|
||||
console.error("file is not included in valid files")
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof (ranks) !== "object" || Array.isArray(ranks) || ranks === null) {
|
||||
console.error("ranks is not a keyed object");
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.entries(ranks).some(([rank, pieces]) => {
|
||||
if (typeof (rank) !== "string") {
|
||||
console.error("rank is not a string");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!RANKS_AS_NUMBERS.includes(Number(rank))) {
|
||||
console.error("rank is not included in valid ranks");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof (pieces) !== "object" || Array.isArray(pieces) || pieces === null) {
|
||||
console.error("piece state is not a keyed object");
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.entries(pieces).some(([key, value]) => {
|
||||
if (typeof (key) !== "string") {
|
||||
console.error("piece key is not a string");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!["kind", "color"].includes(key)) {
|
||||
console.error("piece key is not included in valid keys")
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key === "kind" && (typeof (value) !== "string" || !PIECES_AS_STRINGS.includes(value))) {
|
||||
console.error("kind's value is not a string, or not a valid piece");
|
||||
return true;
|
||||
} else if (key === "color" && (typeof (value) !== "string" || !COLORS_AS_STRINGS.includes(value))) {
|
||||
console.error("colors' value is not a string, or not a valid color");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (hasInvalidShape) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return input as BoardState;
|
||||
};
|
||||
|
||||
export {
|
||||
validateBoardState,
|
||||
};
|
Loading…
Reference in a new issue