diff --git a/index.html b/index.html
index 2af8acc..7df951b 100644
--- a/index.html
+++ b/index.html
@@ -6,8 +6,12 @@
Vite App
-
-
+
+
diff --git a/package-lock.json b/package-lock.json
index d67cf04..33abd18 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 6e55bb4..2f39ac9 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/board/Board.ts b/src/board/Board.ts
index eb9eec3..6190fc1 100644
--- a/src/board/Board.ts
+++ b/src/board/Board.ts
@@ -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 = {}) {
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
+ );
}
}
diff --git a/src/board/layers/drawExtraInfo.ts b/src/board/layers/drawExtraInfo.ts
index da3e080..99de462 100644
--- a/src/board/layers/drawExtraInfo.ts
+++ b/src/board/layers/drawExtraInfo.ts
@@ -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;
}
}
};
diff --git a/src/board/layers/drawPieces.ts b/src/board/layers/drawPieces.ts
index e92d2f5..9647cb0 100644
--- a/src/board/layers/drawPieces.ts
+++ b/src/board/layers/drawPieces.ts
@@ -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;
diff --git a/src/board/styles-board/mono/chesscom.ts b/src/board/styles-board/mono/chesscom.ts
index c55ed74..4f868d9 100644
--- a/src/board/styles-board/mono/chesscom.ts
+++ b/src/board/styles-board/mono/chesscom.ts
@@ -28,7 +28,7 @@ const style: Style = {
border: {
type: "solid",
data: {
- color: "#40522f",
+ color: "#312e2b",
},
},
coords: {
diff --git a/src/encoders/createAnimation.ts b/src/encoders/createAnimation.ts
index a08ee3f..c58a6e5 100644
--- a/src/encoders/createAnimation.ts
+++ b/src/encoders/createAnimation.ts
@@ -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();
diff --git a/src/game/Game.ts b/src/game/Game.ts
index ae170af..b9aa100 100644
--- a/src/game/Game.ts
+++ b/src/game/Game.ts
@@ -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 = new Map([
["q", 9],
["r", 5],
@@ -13,110 +11,96 @@ const MATERIAL_VALUE: Map = 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;
-
export default Game;
diff --git a/src/game/Game_x.ts b/src/game/Game_x.ts
deleted file mode 100644
index 5135679..0000000
--- a/src/game/Game_x.ts
+++ /dev/null
@@ -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 = 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;
-
-// export default Game;
diff --git a/src/main.ts b/src/main.ts
index 64cd98d..b629df6 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -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("#app");
+const $board = document.querySelector("#board");
+const $moves = document.querySelector("#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) => {
diff --git a/src/pattern-light.png b/src/pattern-light.png
new file mode 100644
index 0000000..6a5f0d7
Binary files /dev/null and b/src/pattern-light.png differ
diff --git a/src/pattern-light.png~ b/src/pattern-light.png~
new file mode 100644
index 0000000..3e47619
Binary files /dev/null and b/src/pattern-light.png~ differ
diff --git a/src/player/Player.ts b/src/player/Player.ts
index 39fe8c6..21ccdd2 100644
--- a/src/player/Player.ts
+++ b/src/player/Player.ts
@@ -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;
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;
}
}
diff --git a/src/style.css b/src/style.css
index 292ec57..a03b01d 100644
--- a/src/style.css
+++ b/src/style.css
@@ -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;
+}
diff --git a/src/types.ts b/src/types.ts
index 6f51768..be2f7ea 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -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;
+};
diff --git a/src/ui/moves/Moves.ts b/src/ui/moves/Moves.ts
new file mode 100644
index 0000000..3e14f99
--- /dev/null
+++ b/src/ui/moves/Moves.ts
@@ -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`
+ ${i + 1}. ${white}
+ ${black}
+
`
+ );
+
+ const content = html`
+ ${items.join("\n")}
+ `;
+
+ this.element.innerHTML = content;
+
+ return this;
+ }
+}
+
+export default Moves;