From 5430e348e28d24d40f6caa4e91f4790840fc6cac Mon Sep 17 00:00:00 2001 From: Gregor Zweig Date: Sun, 12 Nov 2023 01:48:18 +0100 Subject: [PATCH 1/6] remove unused import --- src/pages/settings/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/index.vue b/src/pages/settings/index.vue index f712cf8..ef6ecbc 100644 --- a/src/pages/settings/index.vue +++ b/src/pages/settings/index.vue @@ -45,7 +45,7 @@ import { z } from "zod"; import { useLichess } from "@stores/useLichess"; import BaseLink from "@components/base/BaseLink.vue"; import { ref } from "vue"; -import { AcademicCapIcon, CheckBadgeIcon, Cog8ToothIcon } from "@heroicons/vue/24/solid"; +import { CheckBadgeIcon, Cog8ToothIcon } from "@heroicons/vue/24/solid"; import BaseInputLabel from "@components/base/BaseInputLabel.vue"; import BaseInput from "@components/base/BaseInput.vue"; import { open } from "@tauri-apps/api/shell"; From eb0a7f1cef6d05f7b91f54c8079a71fa7ae304ec Mon Sep 17 00:00:00 2001 From: Gregor Zweig Date: Sun, 12 Nov 2023 20:23:18 +0100 Subject: [PATCH 2/6] friendship with chessjs ended --- src/components/sidebar/GameTreeItem.vue | 3 + src/composables/useGame.ts | 85 +++++++--------- src/composables/useGameTree.ts | 60 ++++++----- src/pages/studies/[studyId]/chapters/new.vue | 100 ++++++++++--------- src/utilities/comment.ts | 20 ++++ src/utilities/move.ts | 95 ++++++++---------- vitest.config.ts | 4 +- 7 files changed, 191 insertions(+), 176 deletions(-) create mode 100644 src/utilities/comment.ts diff --git a/src/components/sidebar/GameTreeItem.vue b/src/components/sidebar/GameTreeItem.vue index b8b9e01..bcd9f21 100644 --- a/src/components/sidebar/GameTreeItem.vue +++ b/src/components/sidebar/GameTreeItem.vue @@ -107,6 +107,9 @@ const commentHtml = computed(() => { // strip html tags including content assuming any html is malicious const htmlPattern = /<([^ ]+)[^<>]*?>[^<>]*?<\/\1>/g; let comment = props.node.comment.replace(htmlPattern, ""); + // strip custom pgn comments + const customCommentPattern = /\[[^\]]+?\]/g; + comment = comment.replace(customCommentPattern, ""); // unescape brackets comment = props.node.comment.replaceAll("@@StartBracket@@", "(").replaceAll("@@EndBracket@@", ")"); // remove fen information diff --git a/src/composables/useGame.ts b/src/composables/useGame.ts index 5745674..a2a099d 100644 --- a/src/composables/useGame.ts +++ b/src/composables/useGame.ts @@ -1,22 +1,23 @@ +import { getPossibleMoves, isPromotion } from "@/utilities/move"; import { Position, useChessground } from "@composables/useChessground"; import { ChessMove, PositionNode, useGameTree } from "@composables/useGameTree"; import { playAudio } from "@utilities/audio"; -import { isPromotion, toColor, toPiece, toPossibleMoves } from "@utilities/move"; -import { Chess } from "chess.js"; import { DrawShape } from "chessground/draw"; import { Color, Key, Piece } from "chessground/types"; import { key2pos } from "chessground/util"; +import { Chess } from "chessops/chess"; +import { makeFen, parseFen } from "chessops/fen"; +import { makeSanAndPlay } from "chessops/san"; +import { parseSquare } from "chessops/util"; import { CSSProperties, ref, shallowRef } from "vue"; export type PromotionPiece = "queen" | "rook" | "bishop" | "knight"; export function useGame() { - const chess = new Chess(); + let game = Chess.default(); let board: ReturnType | undefined; const tree = useGameTree(); - const turnColor = ref("white"); - - const fen = ref(chess.fen()); + const fen = ref(makeFen(game.toSetup())); // TODO: promotion to own composable const isPromoting = ref(false); @@ -32,26 +33,22 @@ export function useGame() { let persistentShapes: DrawShape[] = []; function initializeBoard(element: HTMLElement, options?: { orientation: "white" | "black" }) { - const move = chess.history({ verbose: true }).at(-1); - const position = { - fen: chess.fen(), - turnColor: toColor(chess.turn()), - possibleMoves: toPossibleMoves(chess.moves({ verbose: true })), - isCheck: chess.isCheck(), - lastMove: move && [move.from, move.to], + fen: fen.value, + turnColor: game.turn, + possibleMoves: getPossibleMoves(fen.value), + isCheck: game.isCheck(), }; board = useChessground(element, { orientation: options?.orientation, position, onMove: processMove }); } function createNewGame() { - chess.reset(); - fen.value = chess.fen(); - turnColor.value = toColor(chess.turn()); + game.reset(); + fen.value = makeFen(game.toSetup()); isPromoting.value = false; // reset board - board?.setPosition({ fen: fen.value, turnColor: turnColor.value }); + board?.setPosition({ fen: fen.value, turnColor: game.turn }); // reset game tree to starting position // TODO: check if tree reset is really necessary here @@ -64,12 +61,13 @@ export function useGame() { } function processMove(source: Key, destination: Key, options?: { promotionPiece?: PromotionPiece }) { - // fix lichess sending UCI for castling as `e1h1, `e1a1` if (source === "a0" || destination === "a0") return; - const sourcePiece = chess.get(source); + + // fix lichess sending UCI for castling as `e1h1, `e1a1` (adheres to chess960 option for chess engines) + const sourcePieceRole = game.board.getRole(parseSquare(source)); let fixedDestination = destination; const uci = `${source}${destination}`; - if (sourcePiece.type === "k" && (uci === "e1a1" || uci === "e1h1" || uci === "e8a8" || uci === "e8h8")) { + if (sourcePieceRole === "king" && (uci === "e1a1" || uci === "e1h1" || uci === "e8a8" || uci === "e8h8")) { switch (uci) { case "e1a1": fixedDestination = "c1"; @@ -85,28 +83,28 @@ export function useGame() { break; } } + const targetPieceRole = game.board.getRole(parseSquare(fixedDestination)); const piece = board?.getPiece(fixedDestination); if (!piece) return; if (isPromotion(fixedDestination, piece.role)) { const orientation = board?.getOrientation()!; - intentPromotion(fixedDestination, turnColor.value, orientation); + intentPromotion(fixedDestination, game.turn, orientation); return; } // handle promotion - const move = chess.move({ - from: source, - to: fixedDestination, - promotion: options?.promotionPiece && toPiece(options.promotionPiece), + const san = makeSanAndPlay(game, { + from: parseSquare(source), + to: parseSquare(fixedDestination), + promotion: options?.promotionPiece, }); - const isCapture = move.flags.includes("c") || move.flags.includes("e"); - const isEnPassant = move.flags.includes("e"); - const isCheck = chess.inCheck(); + const isEnPassant = sourcePieceRole === "pawn" && targetPieceRole === undefined && source[0] !== destination[0]; + const isCapture = san.includes("x"); + const isCheck = game.isCheck(); // remove en passanted pawn if (isEnPassant) board?.setPiece((fixedDestination[0] + source[1]) as Key, undefined); - if (isCheck) board?.setCheck(); // play move audio @@ -116,20 +114,16 @@ export function useGame() { playAudio("move", 0.5); } - // set up board for next move - fen.value = chess.fen(); - turnColor.value = toColor(chess.turn()); - - board?.setTurn(fen.value, turnColor.value); - + fen.value = makeFen(game.toSetup()); + board?.setTurn(fen.value, game.turn); // add recent move to game tree const nodeMove: ChessMove = { - source: move.from, - destination: move.to, - san: move.san, + source: source, + destination: fixedDestination, + san: san, isCapture, isCheck, - piece, + piece: piece, }; tree.addNode(fen.value, { move: nodeMove }); } @@ -151,7 +145,7 @@ export function useGame() { function cancelPromotion() { const lastPosition: Position = { fen: fen.value, - turnColor: turnColor.value, + turnColor: game.turn, // TODO last move is wrong lastMove: board?.getLastMove(), }; @@ -162,7 +156,7 @@ export function useGame() { function promote(promotionPiece: PromotionPiece) { const piece: Piece = { role: promotionPiece, - color: turnColor.value, + color: game.turn, promoted: true, }; const lastMove = board?.getLastMove(); @@ -175,14 +169,12 @@ export function useGame() { } function setActivePosition(node: PositionNode) { - chess.load(node.fen); - fen.value = node.fen; - turnColor.value = node.move?.piece.color === "white" ? "black" : "white"; + game = Chess.fromSetup(parseFen(node.fen).unwrap()).unwrap(); const lastMove = node.move && [node.move.source, node.move.destination]; const position: Position = { fen: node.fen, - turnColor: turnColor.value, + turnColor: game.turn, lastMove, }; @@ -212,12 +204,11 @@ export function useGame() { } return { + fen, tree, isPromoting, promotionColor, promotionStyles, - fen, - turnColor, initializeBoard, createNewGame, cancelPromotion, diff --git a/src/composables/useGameTree.ts b/src/composables/useGameTree.ts index 6488bf4..d1a4cae 100644 --- a/src/composables/useGameTree.ts +++ b/src/composables/useGameTree.ts @@ -1,8 +1,11 @@ -import { ParseTree, parse } from "@mliebelt/pgn-parser"; +import { formatComment } from "@/utilities/comment"; import { playAudio } from "@utilities/audio"; -import { toColor, toRole, toSAN } from "@utilities/move"; -import { Chess } from "chess.js"; +import { toSAN } from "@utilities/move"; import { Key, Piece } from "chessground/types"; +import { NormalMove, makeSquare } from "chessops"; +import { makeFen } from "chessops/fen"; +import { parsePgn, startingPosition } from "chessops/pgn"; +import { parseSan } from "chessops/san"; import { computed, ref } from "vue"; export interface ChessMove { @@ -126,44 +129,37 @@ export function useGameTree() { } function fromPgn(pgn: string) { + const games = parsePgn(pgn); + if (games.length === 0) return; reset(); - const parsedPgn = parse(pgn, { startRule: "game" }) as ParseTree; + const game = games[0]; + const pos = startingPosition(game.headers).unwrap(); + addNode(makeFen(pos.toSetup()), { comment: formatComment(game.comments?.join("") ?? "") }); - const chess = new Chess(); - chess.loadPgn(pgn); - const history = chess.history({ verbose: true }); - const parsedMoves = parsedPgn.moves; + for (const node of game.moves.mainline()) { + const move = parseSan(pos, node.san) as NormalMove; + if (!move) break; // Illegal move - if (history.length !== parsedMoves.length) throw new Error("Error while parsing PGN"); - - if (!history.length) return; - const firstMove = history[0]; - chess.load(firstMove.before); - - addNode(firstMove.before, { - comment: parsedPgn.gameComment?.comment, - }); - - history.forEach((move, i) => { - chess.load(move.after); - addNode(move.after, { + addNode(makeFen(pos.toSetup()), { move: { - source: move.from, - destination: move.to, - san: move.san, - isCapture: move.flags.includes("c"), - isCheck: chess.isCheck(), + source: makeSquare(move.from), + destination: makeSquare(move.to), + san: node.san, + isCapture: node.san.includes("x"), + isCheck: node.san.includes("+"), piece: { - role: toRole(move.piece), - color: toColor(move.color), - promoted: move.flags.includes("p"), + role: pos.board.getRole(move.from)!, + color: pos.turn === "white" ? "black" : "white", + promoted: move.promotion !== undefined, }, - annotations: parsedMoves[i].nag, + // TODO + // annotations: node.nags[0], }, - comment: parsedMoves[i].commentAfter, + comment: formatComment(node.comments?.join("") ?? ""), }); - }); + pos.play(move); + } // reset active node to root position after import if (root.value) setActiveNode(root.value); diff --git a/src/pages/studies/[studyId]/chapters/new.vue b/src/pages/studies/[studyId]/chapters/new.vue index f7e86ae..fe0ac28 100644 --- a/src/pages/studies/[studyId]/chapters/new.vue +++ b/src/pages/studies/[studyId]/chapters/new.vue @@ -41,7 +41,10 @@ import BaseButton from "@components/base/BaseButton.vue"; import BaseFileUpload from "@components/base/BaseFileUpload.vue"; import BaseInputLabel from "@components/base/BaseInputLabel.vue"; import { AcademicCapIcon } from "@heroicons/vue/24/solid"; -import { Chess } from "chess.js"; +import { NormalMove, makeSquare } from "chessops"; +import { makeBoardFen } from "chessops/fen"; +import { makePgn, parsePgn, startingPosition } from "chessops/pgn"; +import { parseSan } from "chessops/san"; import { ref } from "vue"; import { definePage, useRoute } from "vue-router/auto"; @@ -63,58 +66,65 @@ function onSubmit() { }); } +// TODO: will currently only work with mainline async function processPgn(pgn: string) { - const game = new Chess(); - game.loadPgn(pgn); + const games = parsePgn(pgn); + games.forEach(async (game) => { + const pos = startingPosition(game.headers).unwrap(); + const headers = game.headers; + const chapterName = headers.get(chapterHeader.value) ?? ""; + const lineName = headers.get(lineHeader.value) ?? ""; - const headers = game.header(); - const chapterName = headers[chapterHeader.value]; - const lineName = lineHeader.value ? headers[lineHeader.value] : ""; + const mainline = game.moves.mainline(); + let moves = ""; + for (const node of mainline) { + moves = `${moves} ${node.san}`; + } - // TODO same function in game index - const history = game.history({ verbose: true }); - const moves = history - .reduce((acc, move) => { - return `${acc} ${move.san}`; - }, "") - .trim(); + const chapterQuery = db + .insertInto("chapters") + .values({ + name: chapterName, + study: Number(route.params.studyId), + }) + .onConflict((oc) => oc.columns(["study", "name"]).doNothing()) + .compile(); - const chapterQuery = db - .insertInto("chapters") - .values({ - name: chapterName, - study: Number(route.params.studyId), - }) - .onConflict((oc) => oc.columns(["study", "name"]).doNothing()) - .compile(); + const chapterId = (await execute(chapterQuery)).lastInsertId; - const chapterId = (await execute(chapterQuery)).lastInsertId; + const lineQuery = db + .insertInto("lines") + .values({ study: Number(route.params.studyId), chapter: chapterId, name: lineName, pgn: makePgn(game), moves }) + .onConflict((oc) => oc.columns(["chapter", "moves"]).doNothing()) + .compile(); + const lineId = (await execute(lineQuery)).lastInsertId; - const lineQuery = db - .insertInto("lines") - .values({ study: Number(route.params.studyId), chapter: chapterId, name: lineName, pgn, moves }) - .onConflict((oc) => oc.columns(["chapter", "moves"]).doNothing()) - .compile(); - const lineId = (await execute(lineQuery)).lastInsertId; + let positions = []; + for (const node of mainline) { + const move = parseSan(pos, node.san) as NormalMove; + if (!move) { + console.error("mainline includes illegal moves"); + break; + } + pos.play(move); + positions.push({ + fen: makeBoardFen(pos.board), + source: makeSquare(move.from), + destination: makeSquare(move.to), + san: node.san, + study: Number(route.params.studyId), + chapter: chapterId, + line: lineId, + }); + } - const positions = history.map((move) => { - return { - fen: move.before, - source: move.from, - destination: move.to, - san: move.san, - study: Number(route.params.studyId), - chapter: chapterId, - line: lineId, - }; + const positionsQuery = db + .insertInto("positions") + .values(positions) + .onConflict((oc) => oc.columns(["line", "fen"]).doNothing()) + .compile(); + execute(positionsQuery); }); - - const positionsQuery = db - .insertInto("positions") - .values(positions) - .onConflict((oc) => oc.columns(["line", "fen"]).doNothing()) - .compile(); - execute(positionsQuery); } definePage({ diff --git a/src/utilities/comment.ts b/src/utilities/comment.ts new file mode 100644 index 0000000..bbc8046 --- /dev/null +++ b/src/utilities/comment.ts @@ -0,0 +1,20 @@ +export function formatComment(comment: string) { + // strip html tags including content assuming any html is malicious + const htmlPattern = /<([^ ]+)[^<>]*?>[^<>]*?<\/\1>/g; + let comment_ = comment.replace(htmlPattern, ""); + // strip custom pgn comments + const customCommentPattern = /\[[^\]]+?\]/g; + comment_ = comment_.replace(customCommentPattern, ""); + // unescape brackets + comment_ = comment_.replaceAll("@@StartBracket@@", "(").replaceAll("@@EndBracket@@", ")"); + // remove fen information + const fenPattern = /@@StartFEN@@[^@]+?@@EndFEN@@/g; + comment_ = comment_.replaceAll(fenPattern, ""); + // highlight chess moves + const chessMovePattern = /(([\d]{0,3}\.)?(\.{2,3})?[KQBNRP]?[a-h]?[1-8]?[x]?[a-h][1-8](=[NBRQK])?[+#]?)|0-0(-0)?/g; + comment_.replace(chessMovePattern, "$1"); + + comment_ = comment_.trim(); + + return comment_ === "" ? undefined : comment_; +} diff --git a/src/utilities/move.ts b/src/utilities/move.ts index 647cb11..0224380 100644 --- a/src/utilities/move.ts +++ b/src/utilities/move.ts @@ -1,6 +1,8 @@ import { ChessMove } from "@composables/useGameTree"; -import { Chess, Color, Move, PieceSymbol } from "chess.js"; -import { Color as CgColor, Dests, Key, Role } from "chessground/types"; +import { Key, Role } from "chessground/types"; +import { Chess } from "chessops/chess"; +import { chessgroundDests } from "chessops/compat"; +import { parseFen } from "chessops/fen"; export function isEnPassant(source: Key, destination: Key, pieceOnDestination: Role | undefined) { return source[0] !== destination[0] && pieceOnDestination === "pawn"; @@ -15,52 +17,43 @@ export function toSAN(move: ChessMove) { } export function getPossibleMoves(fen: string) { - const chess = new Chess(); - chess.load(fen); - - return toPossibleMoves(chess.moves({ verbose: true })); -} - -export function toPossibleMoves(moves: Move[]): Dests { - return moves.reduce((acc, move) => { - if (!acc.has(move.from)) return acc.set(move.from, [move.to]); - const dests = acc.get(move.from); - dests?.push(move.to); - return acc.set(move.from, dests!); - }, new Map()); -} - -export function toRole(piece: PieceSymbol): Role { - const roles: Record = { - q: "queen", - r: "rook", - b: "bishop", - n: "knight", - p: "pawn", - k: "king", - }; - - return roles[piece]; -} - -export function toPiece(role: Role): PieceSymbol { - const pieces: Record = { - queen: "q", - rook: "r", - bishop: "b", - knight: "n", - pawn: "p", - king: "k", - }; - - return pieces[role]; -} - -export function toColor(color: Color): CgColor { - const colors: Record = { - w: "white", - b: "black", - }; - - return colors[color]; -} + const setup = parseFen(fen).unwrap(); + const position = Chess.fromSetup(setup).unwrap(); + + return chessgroundDests(position); +} + +// export function toRole(piece: PieceSymbol): Role { +// const roles: Record = { +// q: "queen", +// r: "rook", +// b: "bishop", +// n: "knight", +// p: "pawn", +// k: "king", +// }; + +// return roles[piece]; +// } + +// export function toPiece(role: Role): PieceSymbol { +// const pieces: Record = { +// queen: "q", +// rook: "r", +// bishop: "b", +// knight: "n", +// pawn: "p", +// king: "k", +// }; + +// return pieces[role]; +// } + +// export function toColor(color: Color): CgColor { +// const colors: Record = { +// w: "white", +// b: "black", +// }; + +// return colors[color]; +// } diff --git a/vitest.config.ts b/vitest.config.ts index 77a73cf..0fe16ca 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ - test: {}, + test: { + include: ["src/**/*.spec.ts"], + }, }); From f777073fc8249884917610ba4b4a125db724511a Mon Sep 17 00:00:00 2001 From: Gregor Zweig Date: Sun, 12 Nov 2023 20:53:05 +0100 Subject: [PATCH 3/6] fix annotations and of-by-one pgn parsing --- src/components/sidebar/GameTreeItem.vue | 44 ++++++++++++------------- src/composables/useGame.ts | 36 ++++++++++---------- src/composables/useGameTree.ts | 16 ++++----- 3 files changed, 46 insertions(+), 50 deletions(-) diff --git a/src/components/sidebar/GameTreeItem.vue b/src/components/sidebar/GameTreeItem.vue index bcd9f21..f63a7da 100644 --- a/src/components/sidebar/GameTreeItem.vue +++ b/src/components/sidebar/GameTreeItem.vue @@ -120,28 +120,28 @@ const commentHtml = computed(() => { return comment.replace(chessMovePattern, "$1"); }); -const nags: Record = { - $1: "!", - $2: "?", - $3: "‼", - $4: "⁇", - $5: "⁉", - $6: "⁈", - $7: "□", - $10: "=", - $13: "∞", - $14: "⩲", - $15: "⩱", - $16: "±", - $17: "∓", - $18: "+-", - $19: "-+", - $22: "⨀", - $32: "⟳", - $36: "→", - $40: "↑", - $132: "⇆", - $220: "D", +const nags: Record = { + 1: "!", + 2: "?", + 3: "‼", + 4: "⁇", + 5: "⁉", + 6: "⁈", + 7: "□", + 10: "=", + 13: "∞", + 14: "⩲", + 15: "⩱", + 16: "±", + 17: "∓", + 18: "+-", + 19: "-+", + 22: "⨀", + 32: "⟳", + 36: "→", + 40: "↑", + 132: "⇆", + 220: "D", }; const resolvedAnnotation = computed(() => { diff --git a/src/composables/useGame.ts b/src/composables/useGame.ts index a2a099d..aec7fd8 100644 --- a/src/composables/useGame.ts +++ b/src/composables/useGame.ts @@ -14,10 +14,10 @@ import { CSSProperties, ref, shallowRef } from "vue"; export type PromotionPiece = "queen" | "rook" | "bishop" | "knight"; export function useGame() { - let game = Chess.default(); + let pos = Chess.default(); let board: ReturnType | undefined; const tree = useGameTree(); - const fen = ref(makeFen(game.toSetup())); + const fen = ref(makeFen(pos.toSetup())); // TODO: promotion to own composable const isPromoting = ref(false); @@ -35,20 +35,20 @@ export function useGame() { function initializeBoard(element: HTMLElement, options?: { orientation: "white" | "black" }) { const position = { fen: fen.value, - turnColor: game.turn, + turnColor: pos.turn, possibleMoves: getPossibleMoves(fen.value), - isCheck: game.isCheck(), + isCheck: pos.isCheck(), }; board = useChessground(element, { orientation: options?.orientation, position, onMove: processMove }); } function createNewGame() { - game.reset(); - fen.value = makeFen(game.toSetup()); + pos.reset(); + fen.value = makeFen(pos.toSetup()); isPromoting.value = false; // reset board - board?.setPosition({ fen: fen.value, turnColor: game.turn }); + board?.setPosition({ fen: fen.value, turnColor: pos.turn }); // reset game tree to starting position // TODO: check if tree reset is really necessary here @@ -64,7 +64,7 @@ export function useGame() { if (source === "a0" || destination === "a0") return; // fix lichess sending UCI for castling as `e1h1, `e1a1` (adheres to chess960 option for chess engines) - const sourcePieceRole = game.board.getRole(parseSquare(source)); + const sourcePieceRole = pos.board.getRole(parseSquare(source)); let fixedDestination = destination; const uci = `${source}${destination}`; if (sourcePieceRole === "king" && (uci === "e1a1" || uci === "e1h1" || uci === "e8a8" || uci === "e8h8")) { @@ -83,25 +83,25 @@ export function useGame() { break; } } - const targetPieceRole = game.board.getRole(parseSquare(fixedDestination)); + const targetPieceRole = pos.board.getRole(parseSquare(fixedDestination)); const piece = board?.getPiece(fixedDestination); if (!piece) return; if (isPromotion(fixedDestination, piece.role)) { const orientation = board?.getOrientation()!; - intentPromotion(fixedDestination, game.turn, orientation); + intentPromotion(fixedDestination, pos.turn, orientation); return; } // handle promotion - const san = makeSanAndPlay(game, { + const san = makeSanAndPlay(pos, { from: parseSquare(source), to: parseSquare(fixedDestination), promotion: options?.promotionPiece, }); const isEnPassant = sourcePieceRole === "pawn" && targetPieceRole === undefined && source[0] !== destination[0]; const isCapture = san.includes("x"); - const isCheck = game.isCheck(); + const isCheck = pos.isCheck(); // remove en passanted pawn if (isEnPassant) board?.setPiece((fixedDestination[0] + source[1]) as Key, undefined); @@ -114,8 +114,8 @@ export function useGame() { playAudio("move", 0.5); } - fen.value = makeFen(game.toSetup()); - board?.setTurn(fen.value, game.turn); + fen.value = makeFen(pos.toSetup()); + board?.setTurn(fen.value, pos.turn); // add recent move to game tree const nodeMove: ChessMove = { source: source, @@ -145,7 +145,7 @@ export function useGame() { function cancelPromotion() { const lastPosition: Position = { fen: fen.value, - turnColor: game.turn, + turnColor: pos.turn, // TODO last move is wrong lastMove: board?.getLastMove(), }; @@ -156,7 +156,7 @@ export function useGame() { function promote(promotionPiece: PromotionPiece) { const piece: Piece = { role: promotionPiece, - color: game.turn, + color: pos.turn, promoted: true, }; const lastMove = board?.getLastMove(); @@ -169,12 +169,12 @@ export function useGame() { } function setActivePosition(node: PositionNode) { - game = Chess.fromSetup(parseFen(node.fen).unwrap()).unwrap(); + pos = Chess.fromSetup(parseFen(node.fen).unwrap()).unwrap(); const lastMove = node.move && [node.move.source, node.move.destination]; const position: Position = { fen: node.fen, - turnColor: game.turn, + turnColor: pos.turn, lastMove, }; diff --git a/src/composables/useGameTree.ts b/src/composables/useGameTree.ts index d1a4cae..1888fb4 100644 --- a/src/composables/useGameTree.ts +++ b/src/composables/useGameTree.ts @@ -15,13 +15,9 @@ export interface ChessMove { piece: Piece; isCheck: boolean; isCapture: boolean; - annotations?: string[]; + annotations?: number[]; } -// TODO rename turncolor to movecolor -// TODO: consider evaluation and comments -// TODO can also cache possible moves, to avoid recalculation - // a node describes a position export interface PositionNode { // id generated from last move (not unique) @@ -139,8 +135,9 @@ export function useGameTree() { for (const node of game.moves.mainline()) { const move = parseSan(pos, node.san) as NormalMove; - if (!move) break; // Illegal move - + // TODO: main line null moves will be marked as illegal + if (!move) break; //illegal moves + pos.play(move); addNode(makeFen(pos.toSetup()), { move: { source: makeSquare(move.from), @@ -150,15 +147,14 @@ export function useGameTree() { isCheck: node.san.includes("+"), piece: { role: pos.board.getRole(move.from)!, - color: pos.turn === "white" ? "black" : "white", + color: pos.turn, promoted: move.promotion !== undefined, }, // TODO - // annotations: node.nags[0], + annotations: node.nags, }, comment: formatComment(node.comments?.join("") ?? ""), }); - pos.play(move); } // reset active node to root position after import From 5e2feeedff8911189da25883397fe4360155edaa Mon Sep 17 00:00:00 2001 From: Gregor Zweig Date: Wed, 15 Nov 2023 22:25:36 +0100 Subject: [PATCH 4/6] select orientation of line during chapter creation --- .../migrations/2023-10-06-205928_init/up.sql | 1 - src/pages/studies/[studyId]/chapters/new.vue | 61 ++++++++++++++++--- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src-tauri/migrations/2023-10-06-205928_init/up.sql b/src-tauri/migrations/2023-10-06-205928_init/up.sql index 67639d7..636b687 100644 --- a/src-tauri/migrations/2023-10-06-205928_init/up.sql +++ b/src-tauri/migrations/2023-10-06-205928_init/up.sql @@ -26,7 +26,6 @@ create table if not exists lines ( foreign key (chapter) references chapters(id) on delete cascade, foreign key (study) references studies(id) on delete cascade ); -create unique index idx_lines_chapter_moves on lines (chapter, moves); create table if not exists positions ( id integer primary key not null, diff --git a/src/pages/studies/[studyId]/chapters/new.vue b/src/pages/studies/[studyId]/chapters/new.vue index fe0ac28..0a01949 100644 --- a/src/pages/studies/[studyId]/chapters/new.vue +++ b/src/pages/studies/[studyId]/chapters/new.vue @@ -21,6 +21,15 @@ +
+ Line Orientation + +
PGNs([]); const chapterHeader = ref("White"); const lineHeader = ref("Black"); +const lineOrientation = ref("white"); const query = db.selectFrom("studies").select(["id", "name"]).where("id", "=", Number(route.params.studyId)).compile(); const study = await selectFirst(query); @@ -75,9 +85,8 @@ async function processPgn(pgn: string) { const chapterName = headers.get(chapterHeader.value) ?? ""; const lineName = headers.get(lineHeader.value) ?? ""; - const mainline = game.moves.mainline(); let moves = ""; - for (const node of mainline) { + for (const node of game.moves.mainline()) { moves = `${moves} ${node.san}`; } @@ -89,26 +98,56 @@ async function processPgn(pgn: string) { }) .onConflict((oc) => oc.columns(["study", "name"]).doNothing()) .compile(); + const chapterQueryResult = await execute(chapterQuery); - const chapterId = (await execute(chapterQuery)).lastInsertId; + let chapterId: number; + if (chapterQueryResult.rowsAffected === 0) { + const chapterQuery = db + .selectFrom("chapters") + .select("id") + .where("study", "=", Number(route.params.studyId)) + .where("name", "=", chapterName) + .compile(); + chapterId = (await selectFirst(chapterQuery)).id; + } else { + chapterId = chapterQueryResult.lastInsertId; + } const lineQuery = db .insertInto("lines") - .values({ study: Number(route.params.studyId), chapter: chapterId, name: lineName, pgn: makePgn(game), moves }) - .onConflict((oc) => oc.columns(["chapter", "moves"]).doNothing()) + .values({ + study: Number(route.params.studyId), + chapter: chapterId, + name: lineName, + pgn: makePgn(game), + moves, + orientation: lineOrientation.value, + }) .compile(); - const lineId = (await execute(lineQuery)).lastInsertId; + const lineQueryResult = await execute(lineQuery); + + let lineId: number; + if (lineQueryResult.rowsAffected === 0) { + const lineQuery = db + .selectFrom("lines") + .select("id") + .where("chapter", "=", chapterId) + .where("name", "=", lineName) + .compile(); + lineId = (await selectFirst(lineQuery)).id; + } else { + lineId = lineQueryResult.lastInsertId; + } let positions = []; - for (const node of mainline) { + for (const node of game.moves.mainline()) { const move = parseSan(pos, node.san) as NormalMove; if (!move) { console.error("mainline includes illegal moves"); break; } - pos.play(move); positions.push({ - fen: makeBoardFen(pos.board), + fen: makeFen(pos.toSetup()), source: makeSquare(move.from), destination: makeSquare(move.to), san: node.san, @@ -116,8 +155,10 @@ async function processPgn(pgn: string) { chapter: chapterId, line: lineId, }); + pos.play(move); } + if (positions.length === 0) return; const positionsQuery = db .insertInto("positions") .values(positions) From 177449722494908ec3eb6dbc8c41865161ebec36 Mon Sep 17 00:00:00 2001 From: Gregor Zweig Date: Wed, 15 Nov 2023 22:43:02 +0100 Subject: [PATCH 5/6] update fen on setting active position --- src/composables/useGame.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/composables/useGame.ts b/src/composables/useGame.ts index aec7fd8..17bdd88 100644 --- a/src/composables/useGame.ts +++ b/src/composables/useGame.ts @@ -169,7 +169,8 @@ export function useGame() { } function setActivePosition(node: PositionNode) { - pos = Chess.fromSetup(parseFen(node.fen).unwrap()).unwrap(); + const fen_ = parseFen(node.fen).unwrap(); + pos = Chess.fromSetup(fen_).unwrap(); const lastMove = node.move && [node.move.source, node.move.destination]; const position: Position = { @@ -180,7 +181,7 @@ export function useGame() { board?.setPosition(position); if (node.move?.isCheck) board?.setCheck(); - + fen.value = node.fen; tree.setActiveNode(node); } From 11d36da03cf0eb58d23c9cc32cd93f927a06a1ec Mon Sep 17 00:00:00 2001 From: Gregor Zweig Date: Sat, 18 Nov 2023 16:34:35 +0100 Subject: [PATCH 6/6] old dependency cleanup --- package.json | 4 -- pnpm-lock.yaml | 103 +++++++++++++++------------ src/components/games/LichessGame.vue | 50 ------------- src/pages/games/lichess/[gameId].vue | 8 +-- tailwind.config.cjs | 2 +- 5 files changed, 62 insertions(+), 105 deletions(-) delete mode 100644 src/components/games/LichessGame.vue diff --git a/package.json b/package.json index d5ebf04..d5fc68c 100644 --- a/package.json +++ b/package.json @@ -21,22 +21,18 @@ "@floating-ui/dom": "^1.5.3", "@headlessui/vue": "^1.7.16", "@heroicons/vue": "^2.0.18", - "@mliebelt/pgn-parser": "^1.4.12", "@tauri-apps/api": "^1.5.1", "@types/better-sqlite3": "^7.6.7", "@types/howler": "^2.2.11", "@vueuse/core": "^10.6.0", "better-sqlite3": "^9.1.1", - "chess.js": "1.0.0-beta.6", "chessground": "^9.0.4", "chessops": "^0.12.7", - "cm-pgn": "^3.3.0", "date-fns": "^2.30.0", "howler": "^2.2.4", "kysely": "^0.26.3", "pinia": "^2.1.7", "radix-vue": "^1.1.2", - "tailwind-scrollbar": "^3.0.5", "tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1", "uci-parser-ts": "^0.1.4", "vue": "^3.3.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b93c4e..deacbf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ dependencies: '@heroicons/vue': specifier: ^2.0.18 version: 2.0.18(vue@3.3.8) - '@mliebelt/pgn-parser': - specifier: ^1.4.12 - version: 1.4.12 '@tauri-apps/api': specifier: ^1.5.1 version: 1.5.1 @@ -32,18 +29,12 @@ dependencies: better-sqlite3: specifier: ^9.1.1 version: 9.1.1 - chess.js: - specifier: 1.0.0-beta.6 - version: 1.0.0-beta.6 chessground: specifier: ^9.0.4 version: 9.0.4 chessops: specifier: ^0.12.7 version: 0.12.7 - cm-pgn: - specifier: ^3.3.0 - version: 3.3.0 date-fns: specifier: ^2.30.0 version: 2.30.0 @@ -59,9 +50,6 @@ dependencies: radix-vue: specifier: ^1.1.2 version: 1.1.2(vue@3.3.8) - tailwind-scrollbar: - specifier: ^3.0.5 - version: 3.0.5(tailwindcss@3.3.5) tauri-plugin-sql-api: specifier: github:tauri-apps/tauri-plugin-sql#v1 version: github.com/tauri-apps/tauri-plugin-sql/3604825a4eded2be6cfa248aa0de9614a900cc2d @@ -177,6 +165,7 @@ packages: /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + dev: true /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} @@ -570,6 +559,7 @@ packages: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.20 + dev: true /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} @@ -578,6 +568,7 @@ packages: /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} + dev: true /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} @@ -588,27 +579,18 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - /@mliebelt/pgn-parser@1.4.12: - resolution: {integrity: sha512-z0FNbeGpaiWlFXjJe68oI6ctbjZh9hUWEOnAlYhiVhtzM9bgcdKdg6juX90+GSr8ZFLYMzgT4vxnIqENirnd7Q==} - hasBin: true - dependencies: - '@mliebelt/pgn-types': 1.0.4 - dev: false - - /@mliebelt/pgn-types@1.0.4: - resolution: {integrity: sha512-l1VUSp8pmdsr6HNTaZIQdmd70XqmtCp5jd0fOi4KpR/mq+NBbE1A178a6+rRy001A0/Li1rUxpssDzcYBR87GQ==} - dev: false - /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 + dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} + dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -616,6 +598,7 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + dev: true /@pkgr/utils@2.4.2: resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} @@ -1375,6 +1358,7 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -1382,9 +1366,11 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: true /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1476,6 +1462,7 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + dev: true /bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -1543,6 +1530,7 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 + dev: true /browserslist@4.22.1: resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} @@ -1630,6 +1618,7 @@ packages: /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + dev: true /caniuse-lite@1.0.30001561: resolution: {integrity: sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==} @@ -1669,14 +1658,6 @@ packages: dependencies: get-func-name: 2.0.2 - /chess.js@1.0.0-beta.6: - resolution: {integrity: sha512-sqBfX1VL3csSyqVM5ogbKA+aRlZyWDh276ruWXphwI0lDUMs7iYjZs29BOi49f7mXeunJE7cdfnIZhihsyLnsA==} - dev: false - - /chess.mjs@1.4.0: - resolution: {integrity: sha512-TxbpfE4BvRMteIWEy9j1wsR/UwYqGekmZFDcPrKjaqHbmeRwQOv5sribu6PE6nO/69uTxld0nfX7Ra0XA03j4Q==} - dev: false - /chessground@9.0.4: resolution: {integrity: sha512-KeZd/kcXSKQOGO6LOKu/3bAyVSMh7hgPPharMF15HMfNlHfcBpIVZIDAQRO2PNaNMzPWl0LKc05uFHt0D+lZGA==} dev: false @@ -1700,6 +1681,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: true /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -1718,12 +1700,6 @@ packages: mimic-response: 1.0.1 dev: true - /cm-pgn@3.3.0: - resolution: {integrity: sha512-vlOlUJFyaOF1KapSVpDGyf3rBdHNmeI9RjvxQCYdoioZJH20J3cjxi+WDf2wITXbaOlobrO6qGjGb2IwmvnZLA==} - dependencies: - chess.mjs: 1.4.0 - dev: false - /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -1746,6 +1722,7 @@ packages: /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + dev: true /computeds@0.0.1: resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} @@ -1816,6 +1793,7 @@ packages: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true + dev: true /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -1950,6 +1928,7 @@ packages: /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true /diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} @@ -1970,6 +1949,7 @@ packages: /dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} @@ -2602,6 +2582,7 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 + dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2615,6 +2596,7 @@ packages: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 + dev: true /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -2637,6 +2619,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -2788,12 +2771,14 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true /glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} @@ -2804,6 +2789,7 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -3057,6 +3043,7 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 + dev: true /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} @@ -3078,6 +3065,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -3089,6 +3077,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} @@ -3101,6 +3090,7 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -3152,6 +3142,7 @@ packages: /jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true + dev: true /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} @@ -3253,9 +3244,11 @@ packages: /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} + dev: true /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} @@ -3346,6 +3339,7 @@ packages: /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + dev: true /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -3358,6 +3352,7 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 + dev: true /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -3449,6 +3444,7 @@ packages: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: true /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} @@ -3485,6 +3481,7 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + dev: true /normalize-range@0.1.2: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} @@ -3529,10 +3526,12 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + dev: true /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + dev: true /object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} @@ -3683,10 +3682,12 @@ packages: /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + dev: true /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + dev: true /pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} @@ -3716,6 +3717,7 @@ packages: /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + dev: true /pkg-types@1.0.3: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} @@ -3751,6 +3753,7 @@ packages: postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 + dev: true /postcss-js@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -3760,6 +3763,7 @@ packages: dependencies: camelcase-css: 2.0.1 postcss: 8.4.31 + dev: true /postcss-load-config@4.0.1(postcss@8.4.31): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} @@ -3776,6 +3780,7 @@ packages: lilconfig: 2.1.0 postcss: 8.4.31 yaml: 2.3.4 + dev: true /postcss-nested@6.0.1(postcss@8.4.31): resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} @@ -3785,6 +3790,7 @@ packages: dependencies: postcss: 8.4.31 postcss-selector-parser: 6.0.13 + dev: true /postcss-selector-parser@6.0.13: resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} @@ -3792,9 +3798,11 @@ packages: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 + dev: true /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true /postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} @@ -3966,6 +3974,7 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true /radix-vue@1.1.2(vue@3.3.8): resolution: {integrity: sha512-nMKZPTHv3FHZA70XqWKBZ5Ih+xnwUgou7+oneu52IaNFj0eACHj9Ps4VBZbYvUrDbNXmsd//4gzDnS4/UmCDvQ==} @@ -4009,6 +4018,7 @@ packages: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: pify: 2.3.0 + dev: true /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -4023,6 +4033,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} @@ -4062,6 +4073,7 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -4110,6 +4122,7 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 + dev: true /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4385,6 +4398,7 @@ packages: mz: 2.7.0 pirates: 4.0.6 ts-interface-checker: 0.1.13 + dev: true /sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} @@ -4420,15 +4434,6 @@ packages: tslib: 2.6.2 dev: true - /tailwind-scrollbar@3.0.5(tailwindcss@3.3.5): - resolution: {integrity: sha512-0ZwxTivevqq9BY9fRP9zDjHl7Tu+J5giBGbln+0O1R/7nHtBUKnjQcA1aTIhK7Oyjp6Uc/Dj6/dn8Dq58k5Uww==} - engines: {node: '>=12.13.0'} - peerDependencies: - tailwindcss: 3.x - dependencies: - tailwindcss: 3.3.5 - dev: false - /tailwindcss@3.3.5: resolution: {integrity: sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==} engines: {node: '>=14.0.0'} @@ -4458,6 +4463,7 @@ packages: sucrase: 3.34.0 transitivePeerDependencies: - ts-node + dev: true /tar-fs@2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} @@ -4495,11 +4501,13 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: true /thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: true /tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} @@ -4544,6 +4552,7 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -4561,6 +4570,7 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true /tsconfck@2.1.2(typescript@5.2.2): resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} @@ -5154,6 +5164,7 @@ packages: /yaml@2.3.4: resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} engines: {node: '>= 14'} + dev: true /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} diff --git a/src/components/games/LichessGame.vue b/src/components/games/LichessGame.vue deleted file mode 100644 index 01a9dc8..0000000 --- a/src/components/games/LichessGame.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/src/pages/games/lichess/[gameId].vue b/src/pages/games/lichess/[gameId].vue index c8921aa..63822df 100644 --- a/src/pages/games/lichess/[gameId].vue +++ b/src/pages/games/lichess/[gameId].vue @@ -4,7 +4,7 @@
@@ -16,10 +16,10 @@ import { useRoute, definePage } from "vue-router/auto"; import GameChessboard from "@components/GameChessboard.vue"; import GameContextSidebar from "@components/sidebar/GameContextSidebar.vue"; import { useGame } from "@composables/useGame"; -import { ParseTree, parse } from "@mliebelt/pgn-parser"; import { AcademicCapIcon } from "@heroicons/vue/24/solid"; import { useBreadcrumbs } from "@stores/useBreadcrumbs"; import { useLichess } from "@stores/useLichess"; +import { parsePgn } from "chessops/pgn"; const route = useRoute("/games/lichess/[gameId]"); const lichess = useLichess(); @@ -30,7 +30,7 @@ game.createNewGame(); game.tree.fromPgn(lichessGamePgn); if (game.tree.root.value) game.setActivePosition(game.tree.root.value); -const parsedGame = parse(lichessGamePgn, { startRule: "game" }) as ParseTree; +const parsedGame = parsePgn(lichessGamePgn); definePage({ meta: { @@ -49,7 +49,7 @@ setBreadcrumbs([ to: "/games/lichess/", }, { - name: `${parsedGame.tags!["White"]} vs. ${parsedGame.tags!["Black"]}`, + name: `${parsedGame[0].headers.get("White")} vs. ${parsedGame[0].headers.get("Black")}`, to: `/games/lichess/${route.params.gameId}`, }, ]); diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 7ba8d96..289073e 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -17,6 +17,6 @@ module.exports = { }, }, }, - plugins: [require("@tailwindcss/forms"), require("tailwind-scrollbar")], + plugins: [require("@tailwindcss/forms")], darkMode: ["class", '[data-theme="dark"]'], };