From 013796a2edee455aa0ecbe0469a699b8a5183388 Mon Sep 17 00:00:00 2001 From: Maciej Caderek Date: Tue, 1 Feb 2022 00:08:15 +0100 Subject: [PATCH] WIP --- index.html | 8 +- package-lock.json | 27 ++++ package.json | 2 + src/board/Board.ts | 169 +++++++++-------------- src/board/layers/drawExtraInfo.ts | 131 +++++++++--------- src/board/layers/drawPieces.ts | 90 +++++-------- src/board/styles-board/mono/chesscom.ts | 2 +- src/encoders/createAnimation.ts | 23 +--- src/game/Game.ts | 171 +++++++++++------------- src/game/Game_x.ts | 150 --------------------- src/main.ts | 19 ++- src/pattern-light.png | Bin 0 -> 45406 bytes src/pattern-light.png~ | Bin 0 -> 26708 bytes src/player/Player.ts | 94 ++++--------- src/style.css | 33 ++++- src/types.ts | 49 +++++++ src/ui/moves/Moves.ts | 36 +++++ 17 files changed, 433 insertions(+), 571 deletions(-) delete mode 100644 src/game/Game_x.ts create mode 100644 src/pattern-light.png create mode 100644 src/pattern-light.png~ create mode 100644 src/ui/moves/Moves.ts 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 0000000000000000000000000000000000000000..6a5f0d7bc2624a169ab365e3e6205542b16b9734 GIT binary patch literal 45406 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 NNQlS^R|)C*|35&Bm0|z@ literal 0 HcmV?d00001 diff --git a/src/pattern-light.png~ b/src/pattern-light.png~ new file mode 100644 index 0000000000000000000000000000000000000000..3e47619a9d18965e8f7e8f9da3fbd0036df33958 GIT binary patch literal 26708 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 VoC9MVK@S0fQIu7csgg1a{a?22@s9ui literal 0 HcmV?d00001 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;