This commit is contained in:
Maciej Caderek
2022-02-01 00:08:15 +01:00
parent 6ca3836586
commit 013796a2ed
17 changed files with 433 additions and 571 deletions

View File

@@ -6,8 +6,12 @@
<meta name="viewport" content="width=device-width" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<body class="light">
<div class="layout">
<div id="setup" class="setup-box"></div>
<div id="board" class="board-box"></div>
<div id="moves" class="moves-box"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

27
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"@types/chess.js": "^0.11.2",
"@types/gif.js": "^0.2.2",
"chess.js": "^0.12.0",
"common-tags": "^1.8.2",
"gif.js": "^0.2.0",
"h264-mp4-encoder": "^1.0.12",
"hammerjs": "^2.0.8",
@@ -21,6 +22,7 @@
"webm-writer": "^1.0.0"
},
"devDependencies": {
"@types/common-tags": "^1.8.1",
"@types/hammerjs": "^2.0.41",
"@types/node": "^17.0.8",
"@types/webfontloader": "^1.6.34",
@@ -46,6 +48,12 @@
"resolved": "https://registry.npmjs.org/@types/chess.js/-/chess.js-0.11.2.tgz",
"integrity": "sha512-ZgT0f5Dm1x+kjA/RhCQpBM2NoUGkdk9JFdwncW7/GdqzHCOkLsOTq4k/EKRiRTCFLXR4fTKk8j7KNOmzzHQVgg=="
},
"node_modules/@types/common-tags": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz",
"integrity": "sha512-20R/mDpKSPWdJs5TOpz3e7zqbeCNuMCPhV7Yndk9KU2Rbij2r5W4RzwDPkzC+2lzUqXYu9rFzTktCBnDjHuNQg==",
"dev": true
},
"node_modules/@types/gif.js": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@types/gif.js/-/gif.js-0.2.2.tgz",
@@ -74,6 +82,14 @@
"resolved": "https://registry.npmjs.org/chess.js/-/chess.js-0.12.0.tgz",
"integrity": "sha512-eF1xf4j88r/AwIRxqV1FXZbVgMt4yUx05xrL+ZMqUrY+snFrUxGVHs0VgEd3AvngujDE9th7XZqqCwEKEQ7mWQ=="
},
"node_modules/common-tags": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
"integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/esbuild": {
"version": "0.13.15",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",
@@ -2889,6 +2905,12 @@
"resolved": "https://registry.npmjs.org/@types/chess.js/-/chess.js-0.11.2.tgz",
"integrity": "sha512-ZgT0f5Dm1x+kjA/RhCQpBM2NoUGkdk9JFdwncW7/GdqzHCOkLsOTq4k/EKRiRTCFLXR4fTKk8j7KNOmzzHQVgg=="
},
"@types/common-tags": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz",
"integrity": "sha512-20R/mDpKSPWdJs5TOpz3e7zqbeCNuMCPhV7Yndk9KU2Rbij2r5W4RzwDPkzC+2lzUqXYu9rFzTktCBnDjHuNQg==",
"dev": true
},
"@types/gif.js": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@types/gif.js/-/gif.js-0.2.2.tgz",
@@ -2917,6 +2939,11 @@
"resolved": "https://registry.npmjs.org/chess.js/-/chess.js-0.12.0.tgz",
"integrity": "sha512-eF1xf4j88r/AwIRxqV1FXZbVgMt4yUx05xrL+ZMqUrY+snFrUxGVHs0VgEd3AvngujDE9th7XZqqCwEKEQ7mWQ=="
},
"common-tags": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
"integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="
},
"esbuild": {
"version": "0.13.15",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.13.15.tgz",

View File

@@ -7,6 +7,7 @@
"preview": "vite preview"
},
"devDependencies": {
"@types/common-tags": "^1.8.1",
"@types/hammerjs": "^2.0.41",
"@types/node": "^17.0.8",
"@types/webfontloader": "^1.6.34",
@@ -18,6 +19,7 @@
"@types/chess.js": "^0.11.2",
"@types/gif.js": "^0.2.2",
"chess.js": "^0.12.0",
"common-tags": "^1.8.2",
"gif.js": "^0.2.0",
"h264-mp4-encoder": "^1.0.12",
"hammerjs": "^2.0.8",

View File

@@ -1,6 +1,5 @@
import { BoardConfig, PiecesStyle } from "./../types";
import { Material, MoveWithDetails } from "../game/Game_x";
import { Style, BoardData } from "../types";
import { BoardConfig, PiecesStyle, Position } from "./../types";
import { Style } from "../types";
import drawRectangle from "./layers/drawRectangle";
import drawCoords from "./layers/drawCoords";
import drawMoveIndicators from "./layers/drawMoveIndicators";
@@ -37,14 +36,10 @@ class Board {
private style: Style = boards.standard;
private flipped: boolean = false;
private header: { [key: string]: string | undefined } = {};
private boardData: BoardData | null = null;
private ctx: CanvasRenderingContext2D;
private tempCtx: CanvasRenderingContext2D;
// private boardData: BoardData | null = null;
private borderVisible: boolean = true;
private lastMove: MoveWithDetails | null = null;
private lastMaterial: Material | undefined = undefined;
public canvas: HTMLCanvasElement = document.createElement("canvas");
private tempCanvas: HTMLCanvasElement = document.createElement("canvas");
private lastPosition: Position | null = null;
// private lastMaterial: Material | undefined = undefined;
private background: HTMLCanvasElement | null = null;
private extraInfo: boolean = true;
private piecesStyle: PiecesStyle = "tatiana";
@@ -54,6 +49,12 @@ class Board {
private showChecks: boolean = true;
private currentScreen: "title" | "move" = "move";
private ctx: CanvasRenderingContext2D;
private tempCtx: CanvasRenderingContext2D;
private tempCanvas: HTMLCanvasElement = document.createElement("canvas");
public canvas: HTMLCanvasElement = document.createElement("canvas");
constructor(config: Partial<BoardConfig> = {}) {
const ctx = this.canvas.getContext("2d");
const tempCtx = this.tempCanvas.getContext("2d");
@@ -96,13 +97,8 @@ class Board {
if (this.currentScreen === "title") {
await this.titleFrame(this.header);
} else {
await this.frame(
this.boardData,
this.header,
this.lastMove,
this.lastMaterial
);
} else if (this.lastPosition !== null) {
await this.frame(this.lastPosition, this.header);
}
this.render();
@@ -174,30 +170,6 @@ class Board {
return this;
}
isCheck(move: MoveWithDetails | null) {
if (!move) {
return false;
}
return move.san.includes("+");
}
isMate(move: MoveWithDetails | null) {
if (!move) {
return false;
}
return move.san.includes("#");
}
getOppositeColor(color?: "w" | "b") {
if (!color) {
return;
}
return color === "w" ? "b" : "w";
}
async titleFrame(header: { [key: string]: string | undefined }) {
this.currentScreen = "title";
this.header = header;
@@ -265,22 +237,12 @@ class Board {
}
async frame(
boardData: BoardData | null,
header: { [key: string]: string | undefined },
move: MoveWithDetails | null = null,
material?: Material
position: Position | null,
header: { [key: string]: string | undefined }
) {
this.currentScreen = "move";
this.lastMove = move;
this.boardData = boardData;
this.lastMaterial = material;
const check = this.isCheck(move)
? this.getOppositeColor(move?.color)
: undefined;
const mate = this.isMate(move)
? this.getOppositeColor(move?.color)
: undefined;
this.lastPosition = position;
this.header = header;
this.tempCtx.clearRect(0, 0, this.size, this.size);
@@ -290,64 +252,59 @@ class Board {
this.tempCtx.drawImage((await this.background) as HTMLCanvasElement, 0, 0);
if (boardData !== null) {
if (this.lastMove && this.showMoveIndicator) {
await drawMoveIndicators(
this.tempCtx,
this.lastMove,
this.squareSize,
this.style,
this.borderWidth,
this.tiles,
this.flipped,
this.margin
);
}
if (!this.borderVisible && this.showCoords) {
drawCoords(
this.tempCtx,
this.style.coords,
this.squareSize,
this.tiles,
this.flipped,
this.borderWidth,
this.size,
this.borderVisible,
this.margin
);
}
const piecesShadow = false;
await drawPieces(
if (this.lastPosition?.move && this.showMoveIndicator) {
await drawMoveIndicators(
this.tempCtx,
boardData,
this.lastPosition.move,
this.squareSize,
this.style,
this.borderWidth,
this.tiles,
this.flipped,
check && this.showChecks ? check : undefined,
mate && this.showChecks ? mate : undefined,
piecesShadow,
this.margin,
this.piecesStyle
this.margin
);
}
if (this.extraInfo && header) {
await drawExtraInfo(
this.tempCtx,
this.width,
this.height,
this.scale,
this.margin,
this.style,
header,
this.flipped,
move?.end === 0,
material && this.showMaterial ? material : undefined
);
}
if (!this.borderVisible && this.showCoords) {
drawCoords(
this.tempCtx,
this.style.coords,
this.squareSize,
this.tiles,
this.flipped,
this.borderWidth,
this.size,
this.borderVisible,
this.margin
);
}
if (!this.lastPosition) {
return;
}
await drawPieces(
this.tempCtx,
this.lastPosition,
this.squareSize,
this.borderWidth,
this.flipped,
this.margin,
this.piecesStyle
);
if (this.extraInfo && header) {
await drawExtraInfo(
this.tempCtx,
this.width,
this.height,
this.scale,
this.margin,
this.style,
this.header,
this.flipped,
this.lastPosition
);
}
}

View File

@@ -1,5 +1,4 @@
import { Material } from "../../game/Game_x";
import { Style } from "./../../types";
import { Style, Position } from "./../../types";
import drawText from "./drawText";
const chessFontMapping: { [key: string]: string } = {
@@ -20,8 +19,7 @@ const drawExtraInfo = async (
style: Style,
data: { [key: string]: string | undefined },
flipped: boolean,
lastMove: boolean,
material?: Material
position: Position
) => {
const fontSize = Math.round(20 * scale);
let offsetX = (margin - fontSize) / 2;
@@ -88,7 +86,7 @@ const drawExtraInfo = async (
let rightMarginWhite = 0;
let rightMarginBlack = 0;
if (lastMove && data.Result) {
if (position.last && data.Result) {
const [resultWhite, resultBlack] = data.Result.split("-");
const textWhite =
@@ -125,76 +123,75 @@ const drawExtraInfo = async (
rightMarginBlack = w + 20 * scale;
}
if (material) {
const textWhite = material.diff > 0 ? `+${Math.abs(material.diff)}` : "";
const { diff, imbalance } = position.material;
rightMarginWhite += drawText(
ctx,
textWhite,
"Fira Mono",
fontSize,
500,
width - offsetX - rightMarginWhite,
flipped ? offsetY : height - offsetY,
"right"
);
const textWhite = diff > 0 ? `+${Math.abs(diff)}` : "";
const textBlack = material.diff < 0 ? `+${Math.abs(material.diff)}` : "";
rightMarginWhite += drawText(
ctx,
textWhite,
"Fira Mono",
fontSize,
500,
width - offsetX - rightMarginWhite,
flipped ? offsetY : height - offsetY,
"right"
);
rightMarginBlack += drawText(
ctx,
textBlack,
"Fira Mono",
fontSize,
500,
width - offsetX - rightMarginBlack,
flipped ? height - offsetY : offsetY,
"right"
);
const textBlack = diff < 0 ? `+${Math.abs(diff)}` : "";
for (const [piece, count] of Object.entries(material.imbalance.w)) {
for (let i = 0; i < count; i++) {
const textWidth = drawText(
ctx,
chessFontMapping[piece],
"Chess",
fontSize,
500,
width - offsetX - rightMarginWhite,
(flipped ? offsetY : height - offsetY) - 2 * scale,
"right"
);
rightMarginBlack += drawText(
ctx,
textBlack,
"Fira Mono",
fontSize,
500,
width - offsetX - rightMarginBlack,
flipped ? height - offsetY : offsetY,
"right"
);
rightMarginWhite +=
i === count - 1
? textWidth * 0.85
: piece === "p"
? textWidth * 0.4
: textWidth * 0.6;
}
for (const [piece, count] of Object.entries(imbalance.w)) {
for (let i = 0; i < count; i++) {
const textWidth = drawText(
ctx,
chessFontMapping[piece],
"Chess",
fontSize,
500,
width - offsetX - rightMarginWhite,
(flipped ? offsetY : height - offsetY) - 2 * scale,
"right"
);
rightMarginWhite +=
i === count - 1
? textWidth * 0.85
: piece === "p"
? textWidth * 0.4
: textWidth * 0.6;
}
}
for (const [piece, count] of Object.entries(material.imbalance.b)) {
for (let i = 0; i < count; i++) {
const textWidth = drawText(
ctx,
chessFontMapping[piece],
"Chess",
fontSize,
500,
width - offsetX - rightMarginBlack,
(flipped ? height - offsetY : offsetY) - 2 * scale,
"right"
);
for (const [piece, count] of Object.entries(imbalance.b)) {
for (let i = 0; i < count; i++) {
const textWidth = drawText(
ctx,
chessFontMapping[piece],
"Chess",
fontSize,
500,
width - offsetX - rightMarginBlack,
(flipped ? height - offsetY : offsetY) - 2 * scale,
"right"
);
rightMarginBlack +=
i === count - 1
? textWidth * 0.85
: piece === "p"
? textWidth * 0.4
: textWidth * 0.6;
// i === count - 1 || piece !== "p" ? textWidth * 0.8 : textWidth * 0.4;
}
rightMarginBlack +=
i === count - 1
? textWidth * 0.85
: piece === "p"
? textWidth * 0.4
: textWidth * 0.6;
}
}
};

View File

@@ -1,74 +1,56 @@
import { BoardData, PieceType, PieceColor, PiecesStyle } from "../../types";
import { Position, PieceType, PieceColor, PiecesStyle } from "../../types";
import ImagesCache from "../loaders/PiecesCache";
// import drawCircle from "./drawCircle";
const drawPieces = async (
ctx: CanvasRenderingContext2D,
board: BoardData,
position: Position,
squareSize: number,
borderWidth: number,
tiles: number,
flipped: boolean,
check: "b" | "w" | undefined,
mate: "b" | "w" | undefined,
shadow: boolean,
margin: number,
piecesStyle: PiecesStyle
) => {
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
if (board[y][x] !== null) {
const { type, color } = board[y][x] as {
type: PieceType;
color: PieceColor;
};
const img = await ImagesCache.get(piecesStyle, type, color);
const rank = flipped ? tiles - 1 - y : y;
const file = flipped ? tiles - 1 - x : x;
const { placement, check, mate, turn } = position;
const filters = [];
for (const { x, y, type, color } of placement) {
const img = await ImagesCache.get(piecesStyle, type, color);
const rank = flipped ? 8 - 1 - y : y;
const file = flipped ? 8 - 1 - x : x;
if (shadow) {
filters.push(
`drop-shadow(${squareSize * 0.05}px ${squareSize * 0.05}px ${
squareSize * 0.05
}px rgba(0, 0, 0, 0.6))`
);
}
const filters = [];
// if ((color === check || color === mate) && type === "k") {
// const hex = check ? "#ffa600" : "#ff002f";
// drawCircle(
// ctx,
// squareSize / 2,
// borderWidth + file * squareSize,
// borderWidth + rank * squareSize + margin,
// 0,
// hex,
// true
// );
// }
// if ((color === check || color === mate) && type === "k") {
// const hex = check ? "#ffa600" : "#ff002f";
// drawCircle(
// ctx,
// squareSize / 2,
// borderWidth + file * squareSize,
// borderWidth + rank * squareSize + margin,
// 0,
// hex,
// true
// );
// }
if ((color === check || color === mate) && type === "k") {
const hex = check ? "#ffa600" : "#ff002f";
filters.push(`drop-shadow(0 0 ${squareSize * 0.07}px ${hex})`);
filters.push(`drop-shadow(0 0 ${squareSize * 0.07}px ${hex})`);
}
ctx.filter = filters.length > 0 ? filters.join(" ") : "none";
ctx.drawImage(
img,
borderWidth + file * squareSize,
borderWidth + rank * squareSize + margin,
squareSize,
squareSize
);
}
if ((check || mate) && type === "k" && color === turn) {
const hex = mate ? "#ff002f" : "#ffa600";
filters.push(`drop-shadow(0 0 ${squareSize * 0.07}px ${hex})`);
filters.push(`drop-shadow(0 0 ${squareSize * 0.07}px ${hex})`);
}
}
ctx.filter = "none";
ctx.filter = filters.join(" ");
ctx.drawImage(
img,
borderWidth + file * squareSize,
borderWidth + rank * squareSize + margin,
squareSize,
squareSize
);
ctx.filter = "none";
}
};
export default drawPieces;

View File

@@ -28,7 +28,7 @@ const style: Style = {
border: {
type: "solid",
data: {
color: "#40522f",
color: "#312e2b",
},
},
coords: {

View File

@@ -27,7 +27,7 @@ const createAnimation = async (
? new MP4(board.width, board.height)
: new WebM();
const header = game.getHeader();
const header = game.header;
await board.titleFrame(header);
board.render();
@@ -35,25 +35,12 @@ const createAnimation = async (
// @ts-ignore
await encoder.add(getData(board, encoder), 4);
await board.frame(game.getBoardData(), header, null, game.materialInfo());
board.render();
// @ts-ignore
await encoder.add(getData(board, encoder), 1);
while (true) {
const move = game.next();
// console.log(move);
if (!move) {
break;
}
await board.frame(game.getBoardData(), header, move, game.materialInfo());
for (let ply = 0; ply < game.length; ply++) {
const position = game.getPosition(ply);
await board.frame(position, header);
board.render();
// @ts-ignore
await encoder.add(getData(board, encoder), move.end === 0 ? 5 : 1);
await encoder.add(getData(board, encoder), position.end === 0 ? 5 : 1);
}
return await encoder.render();

View File

@@ -1,9 +1,7 @@
import { PieceType } from "../types";
import { Chess, ChessInstance, Move } from "chess.js";
import { PieceType, PieceColor, BoardData, Position } from "../types";
import { Chess, ChessInstance } from "chess.js";
import { cleanPGN } from "./PGNHelpers";
export type MoveWithDetails = Move & { ply: number; end: number; fen: string };
const MATERIAL_VALUE: Map<PieceType, number> = new Map([
["q", 9],
["r", 5],
@@ -13,110 +11,96 @@ const MATERIAL_VALUE: Map<PieceType, number> = new Map([
]);
class Game {
private game: ChessInstance;
private replay: ChessInstance;
private moves: MoveWithDetails[];
private currentPly: number = 0;
private positions: Position[] = [];
private game: ChessInstance = new Chess();
constructor() {
this.game = new Chess();
this.replay = new Chess();
this.moves = [];
}
getMoves() {
return this.moves.map((move) => move.san);
}
getFEN() {
return this.replay.fen();
}
constructor() {}
loadPGN(pgn: string) {
this.game.load_pgn(cleanPGN(pgn));
this.game.delete_comments();
const game = new Chess();
const replay = new Chess();
const moves = this.game.history({ verbose: true });
const tempGame = new Chess();
const fen = this.game.header().FEN;
game.load_pgn(cleanPGN(pgn));
game.delete_comments();
const moves = game.history({ verbose: true });
const fen = game.header().FEN;
if (fen) {
tempGame.load(fen);
this.replay.load(fen);
replay.load(fen);
}
this.moves = moves.map((item, i) => {
tempGame.move(item);
this.positions = [
{
ply: 0,
move: null,
end: moves.length,
fen: replay.fen(),
check: replay.in_check(),
mate: replay.in_checkmate(),
turn: replay.turn(),
material: this.materialInfo(replay.board()),
placement: this.getPlacement(replay.fen()),
last: moves.length === 0,
},
];
return {
...item,
moves.forEach((item, i) => {
replay.move(item);
const currentFEN = replay.fen();
this.positions.push({
ply: i + 1,
move: item,
end: moves.length - 1 - i,
fen: tempGame.fen(),
};
fen: currentFEN,
check: replay.in_check(),
mate: replay.in_checkmate(),
turn: replay.turn(),
material: this.materialInfo(replay.board()),
placement: this.getPlacement(currentFEN),
last: i === moves.length - 1,
});
});
this.currentPly = 0;
this.game = game;
return this;
}
loadFEN(fen: string) {
this.game.load(fen);
return this;
this.game = new Chess(fen);
this.positions = [];
}
next() {
const move = this.moves[this.currentPly];
private getPlacement(fen: string) {
const board = new Chess(fen).board();
if (!move) {
return null;
const placement: {
x: number;
y: number;
type: PieceType;
color: PieceColor;
}[] = [];
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
if (board[y][x] !== null) {
const { type, color } = board[y][x] as {
type: PieceType;
color: PieceColor;
};
placement.push({ x, y, type, color });
}
}
}
this.currentPly++;
this.replay.move(move);
return move;
return placement;
}
prev() {
const undo = Boolean(this.replay.undo());
if (undo) {
this.currentPly--;
} else {
return null;
}
const move = this.moves[this.currentPly - 1];
return move;
}
last() {
while (this.next()) {}
return this.moves[this.moves.length - 1];
}
first() {
while (this.prev()) {}
return this.moves[0];
}
goto(ply: number) {
if (ply === this.currentPly || ply < 0 || ply > this.moves.length - 1) {
return null;
}
while (this.currentPly !== ply) {
ply > this.currentPly ? this.next() : this.prev();
}
return this.moves[ply - 1];
}
materialInfo() {
const pieces = this.replay.board().flat().filter(Boolean);
private materialInfo(boardData: BoardData) {
const pieces = boardData.flat().filter(Boolean);
const sum = { w: 0, b: 0 };
const imbalance = {
@@ -146,20 +130,23 @@ class Game {
return { sum, imbalance, count, diff: sum.w - sum.b };
}
getBoardData() {
// console.log(this.replay.ascii());
return this.replay.board();
get length() {
return this.positions.length;
}
getHeader() {
get header() {
return this.game.header();
}
pgn() {
return this.game.pgn();
getPosition(ply: number) {
const position = this.positions[ply];
return position ?? null;
}
getMoves() {
return this.positions.slice(1).map(({ move }) => move?.san) as string[];
}
}
export type Material = ReturnType<Game["materialInfo"]>;
export default Game;

View File

@@ -1,150 +0,0 @@
// import { PieceType, BoardData } from "../types";
// import { Chess, ChessInstance, Move } from "chess.js";
// import { cleanPGN } from "./PGNHelpers";
// export type MoveWithDetails = Move & { ply: number; end: number; fen: string };
// const MATERIAL_VALUE: Map<PieceType, number> = new Map([
// ["q", 9],
// ["r", 5],
// ["b", 3],
// ["n", 3],
// ["p", 1],
// ]);
// class Game {
// private moves: MoveWithDetails[];
// private currentPly: number = 0;
// constructor() {
// this.moves = [];
// }
// getMoves() {
// return this.moves.map((move) => move.san);
// }
// loadPGN(pgn: string) {
// const game = new Chess();
// const replay = new Chess();
// game.load_pgn(cleanPGN(pgn));
// game.delete_comments();
// const moves = game.history({ verbose: true });
// const fen = game.header().FEN;
// if (fen) {
// replay.load(fen);
// }
// this.moves = moves.map((item, i) => {
// replay.move(item);
// const currentFEN = replay.fen();
// return {
// ...item,
// ply: i + 1,
// end: moves.length - 1 - i,
// fen: currentFEN,
// material: this.materialInfo(replay.board()),
// };
// });
// this.currentPly = 0;
// return this;
// }
// next() {
// // const move = this.moves[this.currentPly];
// // if (!move) {
// // return null;
// // }
// // this.currentPly++;
// // this.replay.move(move);
// // return move;
// }
// prev() {
// // const undo = Boolean(this.replay.undo());
// // if (undo) {
// // this.currentPly--;
// // } else {
// // return null;
// // }
// // const move = this.moves[this.currentPly - 1];
// // return move;
// }
// last() {
// while (this.next()) {}
// return this.moves[this.moves.length - 1];
// }
// first() {
// while (this.prev()) {}
// return this.moves[0];
// }
// goto(ply: number) {
// if (ply === this.currentPly || ply < 0 || ply > this.moves.length - 1) {
// return null;
// }
// while (this.currentPly !== ply) {
// ply > this.currentPly ? this.next() : this.prev();
// }
// return this.moves[ply - 1];
// }
// materialInfo(boardData: BoardData) {
// const pieces = boardData.flat().filter(Boolean);
// const sum = { w: 0, b: 0 };
// const imbalance = {
// w: { p: 0, n: 0, b: 0, r: 0, q: 0 },
// b: { p: 0, n: 0, b: 0, r: 0, q: 0 },
// };
// const count = {
// w: { p: 0, n: 0, b: 0, r: 0, q: 0 },
// b: { p: 0, n: 0, b: 0, r: 0, q: 0 },
// };
// for (const piece of pieces) {
// if (piece !== null && piece.type !== "k") {
// sum[piece.color] += MATERIAL_VALUE.get(piece.type) ?? 0;
// count[piece.color][piece.type] += 1;
// const oppositeColor = piece.color === "b" ? "w" : "b";
// if (imbalance[oppositeColor][piece.type] === 0) {
// imbalance[piece.color][piece.type] += 1;
// } else {
// imbalance[oppositeColor][piece.type] -= 1;
// }
// }
// }
// return { sum, imbalance, count, diff: sum.w - sum.b };
// }
// getBoardData() {
// // return this.replay.board();
// return new Chess().board();
// }
// getHeader() {
// return this.game.header();
// }
// pgn() {
// return this.game.pgn();
// }
// }
// export type Material = ReturnType<Game["materialInfo"]>;
// export default Game;

View File

@@ -1,20 +1,22 @@
import { BoardConfig, GameConfig } from "./types";
import "./style.css";
import Board from "./board/Board";
import styles from "./board/styles-board";
import Game from "./game/Game";
import styles from "./board/styles-board";
import pgns from "./test-data/pgns";
import createAnimation from "./encoders/createAnimation";
// import { decompressPGN } from "./game/PGNHelpers";
import WebFont from "webfontloader";
import Player from "./player/Player";
import * as Hammer from "hammerjs";
import Moves from "./ui/moves/Moves";
const $app = document.querySelector<HTMLImageElement>("#app");
const $board = document.querySelector<HTMLImageElement>("#board");
const $moves = document.querySelector<HTMLImageElement>("#moves");
const boardConfig: BoardConfig = {
size: 1024,
boardStyle: styles.chesscom,
boardStyle: styles.lila,
piecesStyle: "tatiana",
showBorder: true,
showExtraInfo: true,
@@ -33,7 +35,7 @@ const gameConfig: GameConfig = {
};
const createDownloadLink = async (pgn: string, boardConfig: BoardConfig) => {
const file = await createAnimation(pgn, boardConfig, "GIF");
const file = await createAnimation(pgn, { ...boardConfig, size: 720 }, "GIF");
const link = document.createElement("a");
link.innerText = "DOWNLOAD";
link.setAttribute("href", URL.createObjectURL(file));
@@ -53,7 +55,7 @@ const main = async () => {
// const pgn = pgns[2];
const board = new Board(boardConfig);
$app?.appendChild(board.canvas);
$board?.appendChild(board.canvas);
// const interval = 1000;
// play(board, gameConfig, pgn, interval);
@@ -61,12 +63,15 @@ const main = async () => {
const player = new Player(board, gameConfig);
const game = new Game().loadPGN(pgn);
const moves = new Moves($moves as HTMLElement, player).load(game.getMoves());
// @ts-ignore
window.game = game;
await player.load(game);
// @ts-ignore
window.player = player;
// @ts-ignore
window.game = game;
// @ts-ignore
window.load = async (pgn: string) => {

BIN
src/pattern-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
src/pattern-light.png~ Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -7,15 +7,14 @@ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
class Player {
private interval = 1000;
private game: Game = new Game();
private header: { [key: string]: string | undefined } = {};
private start: boolean = true;
private end: boolean = false;
private ply: number = 0;
public playing: boolean = false;
private firstRender: Promise<void>;
constructor(private board: Board, private config: GameConfig) {
this.firstRender = this.board
.frame(this.game.getBoardData(), {}, null)
.frame(this.game.getPosition(0), this.game.header)
.then((_) => this.board.render());
}
@@ -28,9 +27,9 @@ class Player {
await this.firstRender;
this.game = game;
this.header = game.getHeader();
this.ply = -1;
await this.board.titleFrame(this.game.getHeader());
await this.board.titleFrame(this.game.header);
this.board.render();
}
@@ -52,103 +51,56 @@ class Player {
}
async prev() {
if (this.start) {
const ply = this.ply - 1;
if (ply < -1) {
return;
}
this.end = false;
this.ply = ply;
const move = this.game.prev();
if (!move) {
await this.board.titleFrame(this.header);
if (ply === -1) {
await this.board.titleFrame(this.game.header);
this.board.render();
this.start = true;
return;
}
await this.board.frame(
this.game.getBoardData(),
this.header,
move,
this.game.materialInfo()
);
await this.board.frame(this.game.getPosition(ply), this.game.header);
this.board.render();
}
async next() {
if (this.end) {
const ply = this.ply + 1;
if (ply >= this.game.length) {
return;
}
if (this.start) {
await this.board.frame(
this.game.getBoardData(),
this.header,
null,
this.game.materialInfo()
);
this.board.render();
this.start = false;
return;
}
this.ply = ply;
const move = this.game.next();
this.end = move?.end === 0;
if (!move) {
return;
}
await this.board.frame(
this.game.getBoardData(),
this.header,
move,
this.game.materialInfo()
);
await this.board.frame(this.game.getPosition(ply), this.game.header);
this.board.render();
}
async first() {
this.game.first();
this.ply = 0;
await this.board.titleFrame(this.header);
await this.board.frame(this.game.getPosition(this.ply), this.game.header);
this.board.render();
this.end = false;
this.start = true;
}
async last() {
const move = this.game.last();
this.ply = this.game.length - 1;
await this.board.frame(
this.game.getBoardData(),
this.header,
move,
this.game.materialInfo()
);
await this.board.frame(this.game.getPosition(this.ply), this.game.header);
this.board.render();
this.end = true;
this.start = false;
}
async goto(ply: number) {
const move = this.game.goto(ply);
this.ply = ply;
await this.board.frame(
this.game.getBoardData(),
this.header,
move,
this.game.materialInfo()
);
await this.board.frame(this.game.getPosition(this.ply), this.game.header);
this.board.render();
this.start = false;
this.end = move?.end === 0;
}
}

View File

@@ -10,12 +10,19 @@
}
body {
background-color: #191d24;
background-image: url(pattern.png);
background-repeat: repeat;
background-position: center;
text-align: center;
padding-top: 2.5vh;
}
.dark {
background-color: #191d24;
background-image: url(pattern.png);
}
.light {
background-color: #cfcfcf;
background-image: url(pattern-light.png);
}
.board {
@@ -30,3 +37,23 @@ body {
max-width: 100%;
max-height: 95vh;
}
.layout {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
}
.moves-box {
background: rgba(255, 192, 203, 0.5);
height: 100vh;
}
.board-box {
background: rgba(255, 166, 0, 0.5);
height: 100vh;
}
.setup-box {
background: rgba(135, 207, 235, 0.5);
height: 100vh;
}

View File

@@ -1,3 +1,5 @@
import { Move } from "chess.js";
export type GradientDir =
| "horizontal"
| "vertical"
@@ -112,3 +114,50 @@ export type GameConfig = {
toPly: number | null;
loop: boolean;
};
export type MaterialCount = {
w: {
p: number;
n: number;
b: number;
r: number;
q: number;
};
b: {
p: number;
n: number;
b: number;
r: number;
q: number;
};
};
export type Material = {
sum: {
w: number;
b: number;
};
imbalance: MaterialCount;
count: MaterialCount;
diff: number;
};
export type Placement = {
x: number;
y: number;
type: PieceType;
color: PieceColor;
}[];
export type Position = {
move: Move | null;
ply: number;
end: number;
fen: string;
check: boolean;
mate: boolean;
turn: PieceColor;
material: Material;
placement: Placement;
last: boolean;
};

36
src/ui/moves/Moves.ts Normal file
View File

@@ -0,0 +1,36 @@
import { html } from "common-tags";
import Player from "../../player/Player";
import chunk_ from "@arrows/array/chunk_";
class Moves {
constructor(private element: HTMLElement, private player: Player) {
this.element.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
if (target?.dataset?.type === "ply") {
const ply = Number(target?.dataset?.ply);
this.player.goto(ply);
}
});
}
load(moves: string[]) {
const items = chunk_(2, moves).map(
([white, black], i) =>
html`<p>
${i + 1}. <span data-type="ply" data-ply=${i * 2 + 1}>${white}</span>
<span data-type="ply" data-ply=${i * 2 + 2}>${black}</span>
</p>`
);
const content = html`<div class="moves">
${items.join("\n")}
</ul> `;
this.element.innerHTML = content;
return this;
}
}
export default Moves;