Skip to content

Commit

Permalink
OCS-27 highlight san moves in position comment (#10)
Browse files Browse the repository at this point in the history
Co-authored-by: Gregor Zweig <zwickzwackzwieback@gmail.com>
  • Loading branch information
flys1ck and Gregor Zweig authored Nov 12, 2023
1 parent 879d6b2 commit 7a0fb88
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 18 deletions.
5 changes: 4 additions & 1 deletion src/components/games/LichessGameList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
<template v-if="game.opening">{{ game.opening.name }}</template>
<template v-else-if="game.variant === 'fromPosition'">From position: {{ game.initialFen }}</template>
</div>
<p class="line-clamp-3 text-xs text-gray-500">{{ game.moves }}</p>
<p class="line-clamp-3 text-xs text-gray-500">
{{ getMoveStringFromSan(game.moves?.split(" ") ?? [], game.initialFen) }}
</p>
</div>
<div class="flex items-center border-l p-4">
<dl class="w-40 space-y-1 text-sm">
Expand Down Expand Up @@ -48,6 +50,7 @@ import { ref } from "vue";
import BaseCard from "@components/base/BaseCard.vue";
import { useLichess } from "@stores/useLichess";
import LichessPlayer from "@components/games/LichessPlayer.vue";
import { getMoveStringFromSan } from "@/utilities/moves";

defineExpose({ refresh });

Expand Down
4 changes: 2 additions & 2 deletions src/components/sidebar/GameEvaluation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<ol v-if="multiPvInfo.length" class="divide-y border-t">
<li v-for="info in multiPvInfo" :key="info.id" class="line-clamp-1 p-1 text-sm text-gray-700">
<span class="inline-block w-10 text-end font-medium">{{ info.evaluatedScore }}</span>
<span class="ml-2">{{ getMoveString(fen, info.principleVariation) }}</span>
<span class="ml-2">{{ getMovesStringFromUci(info.principleVariation, fen) }}</span>
</li>
</ol>
</div>
Expand All @@ -42,7 +42,7 @@ import BaseSwitch from "../base/BaseSwitch.vue";
import { useEvaluation } from "@composables/useEvaluation";
import { useSettings } from "@/stores/useSettings";
import { storeToRefs } from "pinia";
import { getMoveString } from "@/utilities/uci";
import { getMovesStringFromUci } from "@/utilities/moves";
import { Key } from "chessground/types";
const props = defineProps<{
Expand Down
28 changes: 19 additions & 9 deletions src/components/sidebar/GameTreeItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,25 @@
</button>
</template>
</template>
<template v-if="comment || node.previousPosition?.variations.length">
<template v-if="commentHtml || node.previousPosition?.variations.length">
<span
v-if="(comment || node.previousPosition?.variations.length) && node.ply % 2 === 1"
v-if="(commentHtml || node.previousPosition?.variations.length) && node.ply % 2 === 1"
class="col-span-7 pl-4 text-gray-500"
>...</span
>
<div class="relative col-span-full break-words border-y bg-gray-100 p-2 text-xs text-gray-700 shadow-inner">
<div class="relative col-span-full break-words bg-gray-100 p-2 text-xs text-gray-700 shadow-inner">
<span
class="absolute inset-0"
class="absolute inset-y-0"
:class="[
{
'border-l-4': node.move?.piece.color === 'white',
'border-r-4': node.move?.piece.color === 'black',
'left-0 border-l-4': node.move?.piece.color === 'white',
'right-0 border-r-4': node.move?.piece.color === 'black',
},
node.id === activeNodeId ? 'border-orange-300' : 'border-gray-300',
]"
aria-hidden
/>
<p>{{ comment }}</p>
<p v-html="commentHtml" />
<!-- variations -->
<div v-for="variation in node.previousPosition?.variations" :key="variation.id" class="space-x-0.5">
<GameTreeVariationItem
Expand Down Expand Up @@ -102,9 +102,19 @@ const moveNumber = computed(() => {
return Math.ceil(props.node.ply / 2);
});
const comment = computed(() => {
const commentHtml = computed(() => {
if (!props.node.comment) return;
return props.node.comment.replaceAll("@@StartBracket@@", "(").replaceAll("@@EndBracket@@", ")");
// strip html tags including content assuming any html is malicious
const htmlPattern = /<([^</> ]+)[^<>]*?>[^<>]*?<\/\1>/g;
let comment = props.node.comment.replace(htmlPattern, "");
// unescape brackets
comment = props.node.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;
return comment.replace(chessMovePattern, "<b>$1</b>");
});
const nags: Record<string, string> = {
Expand Down
8 changes: 4 additions & 4 deletions src/pages/studies/[studyId]/chapters/new.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ const query = db.selectFrom("studies").select(["id", "name"]).where("id", "=", N
const study = await selectFirst(query);
function onSubmit() {
const fileReader = new FileReader();
fileReader.addEventListener("load", (e) => {
processPgn(e.target?.result as string);
});
files.value.forEach((file) => {
const fileReader = new FileReader();
fileReader.addEventListener("load", (e) => {
processPgn(e.target?.result as string);
});
fileReader.readAsText(file);
});
}
Expand Down
24 changes: 22 additions & 2 deletions src/utilities/uci.ts → src/utilities/moves.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Chess } from "chessops/chess";
import { parseFen } from "chessops/fen";
import { INITIAL_BOARD_FEN, parseFen } from "chessops/fen";
import { makeSanAndPlay } from "chessops/san";
import { parseUci } from "chessops/util";
import { UciMove } from "uci-parser-ts";
Expand All @@ -8,7 +8,7 @@ import { MaybeRef, unref } from "vue";
/**
* Generates SAN move string from a given starting fen and list of UCI moves.
*/
export function getMoveString(fen: MaybeRef<string>, uciMoves: UciMove[]) {
export function getMovesStringFromUci(uciMoves: UciMove[], fen: MaybeRef<string>) {
const fen_ = unref(fen);
const setup = parseFen(fen_).unwrap();
const position = Chess.fromSetup(setup).unwrap();
Expand All @@ -31,3 +31,23 @@ export function getMoveString(fen: MaybeRef<string>, uciMoves: UciMove[]) {

return initialMoveColor === "white" ? moves : `... ${moves}`;
}

/**
* Generates SAN move string from a given starting fen and list of SAN moves.
*/
export function getMoveStringFromSan(sanMoves: string[], fen?: MaybeRef<string>) {
if (sanMoves.length === 0) return "";
const fen_ = unref(fen) ?? INITIAL_BOARD_FEN;
const parsedFen = parseFen(fen_).unwrap();

const turn = parsedFen.turn;
const moveCount = parsedFen.fullmoves;
const plyCount = parsedFen.halfmoves;
const moveString = sanMoves.reduce((acc, sanMove, i) => {
const currentPly = plyCount + i;
const currentMove = Math.floor(currentPly / 2) + 1;
return currentPly % 2 === 0 ? `${acc} ${currentMove}.${sanMove}` : `${acc} ${sanMove}`;
}, "");

return turn === "white" ? moveString : `${moveCount} ... ${moveString}`;
}

0 comments on commit 7a0fb88

Please sign in to comment.