fix: tests and infer props

This commit is contained in:
Cozma Rares
2023-08-05 15:37:06 +03:00
parent dfc222b688
commit 68fdf7dd8d
3 changed files with 126 additions and 54 deletions
+17 -21
View File
@@ -1,31 +1,27 @@
// NOTE: will work as long as React keeps the props as the first argument in the functional components
type ArgumentTypes<F extends Function> = F extends (
props: infer TT,
...args: any
) => any
? TT
: never;
type ArgumentTypes<F extends React.FC<any>> = F extends React.FC<infer TT>
? TT
: never;
type Head<T extends Array<Function>> = T extends [
infer TT extends Function,
...any
type Head<T extends Array<React.FC<any>>> = T extends [
infer TT extends React.FC<any>,
...any
]
? TT
: never;
? TT
: never;
type Tail<T extends Array<Function>> = T extends [
any,
...infer TT extends Array<Function>
type Tail<T extends Array<React.FC<any>>> = T extends [
any,
...infer TT extends Array<React.FC<any>>
]
? TT
: never;
? TT
: never;
type UglyInferProps<T extends Array<Function>> = T extends [Function]
? ArgumentTypes<Head<T>>
: ArgumentTypes<Head<T>> & UglyInferProps<Tail<T>>;
type UglyInferProps<T extends Array<React.FC<any>>> = T extends [React.FC<any>]
? ArgumentTypes<Head<T>>
: ArgumentTypes<Head<T>> & UglyInferProps<Tail<T>>;
type Prettify<T> = { [K in keyof T]: T[K] } & {};
type InferProps<T extends Array<Function>> = Prettify<UglyInferProps<T>>;
type InferProps<T extends Array<React.FC<any>>> = Prettify<UglyInferProps<T>>;
export default InferProps;
+89 -17
View File
@@ -1,28 +1,52 @@
import { describe, expect, test } from "vitest";
import Chess, { InternalMove, MOVE_FLAGS, Square } from "../engine";
import Chess, {
COLOR,
Color,
InternalMove,
MOVE_FLAGS,
Square,
} from "../engine";
type TestMove = Omit<InternalMove, "piece">;
type ExpectedMoves = Record<
string,
{
square: Square;
moves: InternalMove[];
moves: Array<TestMove>;
}[]
>;
function runTest(fen: string, expectedMoves: ExpectedMoves) {
function runTest(fen: string, expectedMoves: ExpectedMoves, color?: Color) {
const chess = Chess.load(fen);
Object.keys(expectedMoves).forEach((key) =>
test(key.split("_").join(" "), () =>
expectedMoves[key].forEach(({ square, moves: expectedMoves }) => {
const moves = chess.getMovesForSquare(square);
test(
key
.split("_")
.concat([color ?? ""])
.join(" "),
() =>
expectedMoves[key].forEach(({ square, moves: expectedMoves }) => {
const moves: Array<TestMove> = chess
.getMovesForSquare(square)
.map(({ from, to, flags, promotion }) => {
return promotion
? {
from,
to,
flags,
promotion,
}
: { from, to, flags };
});
expect(moves).toHaveLength(expectedMoves.length);
moves.forEach((move) => expect(expectedMoves).toContainEqual(move));
expectedMoves.forEach((expectedMove) =>
expect(moves).toContainEqual(expectedMove)
);
})
expect(moves).toHaveLength(expectedMoves.length);
moves.forEach((move) => expect(expectedMoves).toContainEqual(move));
expectedMoves.forEach((expectedMove) =>
expect(moves).toContainEqual(expectedMove)
);
})
)
);
}
@@ -32,8 +56,8 @@ function runTests(
expectedMovesW: ExpectedMoves,
expectedMovesB: ExpectedMoves
) {
runTest(fen.replace("%", "w"), expectedMovesW);
runTest(fen.replace("%", "b"), expectedMovesB);
runTest(fen.replace("%", "w"), expectedMovesW, COLOR.WHITE);
runTest(fen.replace("%", "b"), expectedMovesB, COLOR.BLACK);
}
describe("pawn moves", () => {
@@ -55,6 +79,30 @@ describe("pawn moves", () => {
{ from: "b7", to: "b8", flags: MOVE_FLAGS.PROMOTION, promotion: "b" },
{ from: "b7", to: "b8", flags: MOVE_FLAGS.PROMOTION, promotion: "r" },
{ from: "b7", to: "b8", flags: MOVE_FLAGS.PROMOTION, promotion: "q" },
{
from: "b7",
to: "a8",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "n",
},
{
from: "b7",
to: "a8",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "b",
},
{
from: "b7",
to: "a8",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "r",
},
{
from: "b7",
to: "a8",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "q",
},
],
},
],
@@ -99,6 +147,30 @@ describe("pawn moves", () => {
{ from: "g2", to: "g1", flags: MOVE_FLAGS.PROMOTION, promotion: "b" },
{ from: "g2", to: "g1", flags: MOVE_FLAGS.PROMOTION, promotion: "r" },
{ from: "g2", to: "g1", flags: MOVE_FLAGS.PROMOTION, promotion: "q" },
{
from: "g2",
to: "h1",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "n",
},
{
from: "g2",
to: "h1",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "b",
},
{
from: "g2",
to: "h1",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "r",
},
{
from: "g2",
to: "h1",
flags: MOVE_FLAGS.PROMOTION | MOVE_FLAGS.CAPTURE,
promotion: "q",
},
],
},
],
@@ -131,7 +203,7 @@ describe("pawn moves", () => {
};
runTests(
"7k/1Pp1p2p/2P2p2/4p3/3P4/2P5/P5p1/K7 % - - 0 1",
"r6k/1Pp1p2p/2P2p2/4p3/3P4/2P5/P5p1/K6R % - - 0 1",
expectedMovesW,
expectedMovesB
);
@@ -213,7 +285,7 @@ describe("knight moves", () => {
square: "c3",
moves: [
{ from: "c3", to: "e4", flags: MOVE_FLAGS.NORMAL },
{ from: "c3", to: "b5", flags: MOVE_FLAGS.CAPTURE },
{ from: "c3", to: "b5", flags: MOVE_FLAGS.NORMAL },
{ from: "c3", to: "a4", flags: MOVE_FLAGS.NORMAL },
{ from: "c3", to: "a2", flags: MOVE_FLAGS.CAPTURE },
{ from: "c3", to: "b1", flags: MOVE_FLAGS.NORMAL },
@@ -225,7 +297,7 @@ describe("knight moves", () => {
};
runTests(
"n7/1n6/8/1K1kN3/8/2n5/N5N1/7N % - - 0 1",
"n7/Kn6/8/3kN3/8/2n5/N5N1/7N % - - 0 1",
expectedMovesW,
expectedMovesB
);
+20 -16
View File
@@ -11,7 +11,7 @@ export const PIECE = Object.freeze({
} as const);
function isPieceValid(string: string): boolean {
return (Object.values(PIECE) as string[]).includes(string);
return (Object.values(PIECE) as Array<string>).includes(string);
}
export const PIECE_PROMOTION = Object.freeze([
@@ -34,7 +34,7 @@ export type Piece = {
color: Color;
};
export type Board = (Piece | null)[];
export type Board = Array<Piece | null>;
// prettier-ignore
export const SQUARES = Object.freeze([
@@ -331,12 +331,16 @@ function generatePawnMoves(
color: Color,
board: Readonly<Board>,
enPassantSquare: Square
): InternalMove[] {
): Array<InternalMove> {
if (board[position] == null) return [];
const moves: InternalMove[] = [];
const moves: Array<InternalMove> = [];
const generatePromotionMoves = (from: number, to: number, flag: MoveFlag) => {
const generatePromotionMoves = (
from: number,
to: number,
flag?: MoveFlag
) => {
const fromAlgebraic = algebraic(from);
const toAlgebraic = algebraic(to);
@@ -346,7 +350,7 @@ function generatePawnMoves(
from: fromAlgebraic,
to: toAlgebraic,
promotion: piece,
flags: MOVE_FLAGS.PROMOTION | flag,
flags: MOVE_FLAGS.PROMOTION | (flag ?? 0),
})
);
};
@@ -356,7 +360,7 @@ function generatePawnMoves(
if (board[nextPosition] == null)
if (rank(nextPosition) == PAWN_MOVE_INFO[color].promotion)
generatePromotionMoves(position, nextPosition, MOVE_FLAGS.NORMAL);
generatePromotionMoves(position, nextPosition);
else {
moves.push({
piece: { type: PIECE.PAWN, color },
@@ -409,13 +413,13 @@ export function generatePieceMoves(
piece: Piece,
board: Readonly<Board>,
enPassantSquare: Square
): InternalMove[] {
): Array<InternalMove> {
if (piece.type == PIECE.PAWN)
return generatePawnMoves(position, piece.color, board, enPassantSquare);
const type = piece.type; // hacked the type system
const generateOnce = () => {
const moves: InternalMove[] = [];
const moves: Array<InternalMove> = [];
PIECE_MOVE_INFO[type].moves.forEach(({ offset, excludedFiles }) => {
if (excludedFiles.includes(file(position))) return;
@@ -446,7 +450,7 @@ export function generatePieceMoves(
};
const generateMultiple = () => {
const moves: InternalMove[] = [];
const moves: Array<InternalMove> = [];
PIECE_MOVE_INFO[type].moves.forEach(({ offset, excludedFiles }) => {
if (excludedFiles.includes(file(position))) return;
@@ -509,9 +513,9 @@ export default class Chess {
private _fullMoves;
private _kings;
private _moves: InternalMove[] = [];
private _attacks: number[] = [];
private _history: Readonly<{ fen: string; san: string }>[] = [];
private _moves: Array<InternalMove> = [];
private _attacks: Array<number> = [];
private _history: Array<Readonly<{ fen: string; san: string }>> = [];
private _enableProcessMoves = true;
private _boardPositionCounter = new Map<string, number>();
@@ -702,7 +706,7 @@ export default class Chess {
if (this._castling.b & MOVE_FLAGS.Q_CASTLE) castling += "q";
if (castling == "") castling = "-";
const arr: any[] = [
const arr: Array<any> = [
position.substring(1), // remove first '/'
this._turn,
castling,
@@ -743,7 +747,7 @@ export default class Chess {
return str;
}
getMoves(): InternalMove[] {
getMoves(): Array<InternalMove> {
return this._moves;
}
@@ -765,7 +769,7 @@ export default class Chess {
}, this);
}
getMovesForSquare(square: Square): InternalMove[] {
getMovesForSquare(square: Square): Array<InternalMove> {
return this._moves.filter(({ from }) => from == square);
}