From edf0cdb3909971ed57462322b382cdad843de44b Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Mon, 9 Dec 2024 06:05:19 +0000 Subject: [PATCH 01/43] use destsToUcis from ui/chess --- ui/nvui/src/chess.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 575def341abb5..b75ecff56bc34 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -7,7 +7,7 @@ import { chessgroundDests } from 'chessops/compat'; import { type SquareName, RULES, type Rules } from 'chessops/types'; import { setupPosition } from 'chessops/variant'; import { charToRole, parseUci, roleToChar } from 'chessops/util'; -import { plyToTurn, type SanToUci, sanWriter } from 'chess'; +import { destsToUcis, plyToTurn, type SanToUci, sanWriter } from 'chess'; import { storage } from 'common/storage'; export type Style = 'uci' | 'san' | 'literate' | 'nato' | 'anna'; @@ -597,17 +597,6 @@ export function possibleMovesHandler( const promotionRegex = /^([a-h]x?)?[a-h](1|8)=\w$/; const uciPromotionRegex = /^([a-h][1-8])([a-h](1|8))[qrbn]$/; -function destsToUcis(dests: Dests) { - const ucis: string[] = []; - for (const [orig, d] of dests) { - if (d) - d.forEach(function (dest) { - ucis.push(orig + dest); - }); - } - return ucis; -} - function sanToUci(san: string, legalSans: SanToUci): Uci | undefined { if (san in legalSans) return legalSans[san]; const lowered = san.toLowerCase(); From 4b11790f09dde1ad4a1e28f55b941bfa47b47dd8 Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Mon, 9 Dec 2024 06:26:02 +0000 Subject: [PATCH 02/43] move sanToUci to ui/chess --- ui/chess/src/sanWriter.ts | 9 ++++++++- ui/keyboardMove/src/keyboardSubmit.ts | 9 +-------- ui/nvui/src/chess.ts | 9 +-------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/ui/chess/src/sanWriter.ts b/ui/chess/src/sanWriter.ts index 542db6f0a4380..af77b8cf7be80 100644 --- a/ui/chess/src/sanWriter.ts +++ b/ui/chess/src/sanWriter.ts @@ -1,7 +1,7 @@ import { charToRole, type Square } from 'chessops'; export type Board = { pieces: { [key: number]: string }; turn: boolean }; -export type SanToUci = { [key: string]: Uci }; +export type SanToUci = { [key: AlmostSan]: Uci }; function fixCrazySan(san: string) { return san[0] === 'P' ? san.slice(1) : san; @@ -151,6 +151,13 @@ export function sanWriter(fen: string, ucis: string[]): SanToUci { return sans; } +export function sanToUci(san: string, legalSans: SanToUci): Uci | undefined { + if (san in legalSans) return legalSans[san]; + const lowered = san.toLowerCase(); + for (const i in legalSans) if (i.toLowerCase() === lowered) return legalSans[i]; + return; +} + export function speakable(san?: San): string { const text = !san ? 'Game start' diff --git a/ui/keyboardMove/src/keyboardSubmit.ts b/ui/keyboardMove/src/keyboardSubmit.ts index 14d4c138ee9cc..4833ca0d3fd04 100644 --- a/ui/keyboardMove/src/keyboardSubmit.ts +++ b/ui/keyboardMove/src/keyboardSubmit.ts @@ -1,5 +1,5 @@ import { files } from 'chessground/types'; -import type { SanToUci } from 'chess'; +import { sanToUci, type SanToUci } from 'chess'; import type { Opts } from './exports'; const keyRegex = /^[a-h][1-8]$/; @@ -138,13 +138,6 @@ function iccfToUci(v: string) { return chars.join(''); } -function sanToUci(san: string, legalSans: SanToUci): Uci | undefined { - if (san in legalSans) return legalSans[san]; - const lowered = san.toLowerCase(); - for (const i in legalSans) if (i.toLowerCase() === lowered) return legalSans[i]; - return; -} - function sanCandidates(san: string, legalSans: SanToUci): San[] { // replace '=' in promotion moves (#7326) const lowered = san.replace('=', '').toLowerCase(); diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index b75ecff56bc34..ec090ca074fe1 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -7,7 +7,7 @@ import { chessgroundDests } from 'chessops/compat'; import { type SquareName, RULES, type Rules } from 'chessops/types'; import { setupPosition } from 'chessops/variant'; import { charToRole, parseUci, roleToChar } from 'chessops/util'; -import { destsToUcis, plyToTurn, type SanToUci, sanWriter } from 'chess'; +import { destsToUcis, plyToTurn, sanToUci, sanWriter } from 'chess'; import { storage } from 'common/storage'; export type Style = 'uci' | 'san' | 'literate' | 'nato' | 'anna'; @@ -597,13 +597,6 @@ export function possibleMovesHandler( const promotionRegex = /^([a-h]x?)?[a-h](1|8)=\w$/; const uciPromotionRegex = /^([a-h][1-8])([a-h](1|8))[qrbn]$/; -function sanToUci(san: string, legalSans: SanToUci): Uci | undefined { - if (san in legalSans) return legalSans[san]; - const lowered = san.toLowerCase(); - for (const i in legalSans) if (i.toLowerCase() === lowered) return legalSans[i]; - return; -} - export function inputToLegalUci(input: string, fen: string, chessground: CgApi): string | undefined { const legalUcis = destsToUcis(chessground.state.movable.dests!), legalSans = sanWriter(fen, legalUcis); From 60e0b6089dc3750b0a76eaaf85e2fcf735f28f9b Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Mon, 9 Dec 2024 09:35:10 +0000 Subject: [PATCH 03/43] change some types --- ui/analyse/src/plugins/analyse.nvui.ts | 4 ++-- ui/nvui/src/chess.ts | 30 ++++++++++++-------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/ui/analyse/src/plugins/analyse.nvui.ts b/ui/analyse/src/plugins/analyse.nvui.ts index 6268c89c1603b..6f327391efe58 100644 --- a/ui/analyse/src/plugins/analyse.nvui.ts +++ b/ui/analyse/src/plugins/analyse.nvui.ts @@ -1,4 +1,4 @@ -import { h, type VNode } from 'snabbdom'; +import { h, type VNode, type VNodeChildren } from 'snabbdom'; import { defined, prop, type Prop } from 'common'; import { text as xhrText } from 'common/xhr'; import type AnalyseController from '../ctrl'; @@ -352,7 +352,7 @@ function renderResult(ctrl: AnalyseController): VNode[] { return []; } -function renderCurrentLine(ctrl: AnalyseController, style: Style): (string | VNode)[] { +function renderCurrentLine(ctrl: AnalyseController, style: Style): VNodeChildren { if (ctrl.path.length === 0) { return renderMainline(ctrl.mainline, ctrl.path, style); } else { diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index ec090ca074fe1..266642f08a2f3 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -20,7 +20,7 @@ interface RoundStep { uci: Uci; } -const nato: { [letter: string]: string } = { +const nato: { [file in Files]: string } = { a: 'alpha', b: 'bravo', c: 'charlie', @@ -30,7 +30,7 @@ const nato: { [letter: string]: string } = { g: 'golf', h: 'hotel', }; -const anna: { [letter: string]: string } = { +const anna: { [file in Files]: string } = { a: 'anna', b: 'bella', c: 'cesar', @@ -40,7 +40,7 @@ const anna: { [letter: string]: string } = { g: 'gustav', h: 'hector', }; -const skipToFile: { [letter: string]: string } = { +const skipToFile: { [letter: string]: Files } = { '!': 'a', '@': 'b', '#': 'c', @@ -188,7 +188,7 @@ export function renderSan(san: San, uci: Uci | undefined, style: Style): string if (c === '=') return 'promotion'; const code = c.charCodeAt(0); if (code > 48 && code < 58) return c; // 1-8 - if (code > 96 && code < 105) return renderFile(c, style); // a-g + if (code > 96 && code < 105) return renderFile(c as Files, style); // a-h return charToRole(c) || c; }) .join(' '); @@ -319,11 +319,13 @@ export function renderBoard( return h(boardStyle === 'table' ? 'table.board-wrapper' : 'div.board-wrapper', ranks); } -export const renderFile = (f: string, style: Style): string => +export const renderFile = (f: Files, style: Style): string => style === 'nato' ? nato[f] : style === 'anna' ? anna[f] : f; export const renderKey = (key: string, style: Style): string => - style === 'nato' || style === 'anna' ? `${renderFile(key[0], style)} ${key[1]}` : `${key[0]}${key[1]}`; + style === 'nato' || style === 'anna' + ? `${renderFile(key[0] as Files, style)} ${key[1]}` + : `${key[0]}${key[1]}`; export function castlingFlavours(input: string): string { switch (input.toLowerCase().replace(/[-\s]+/g, '')) { @@ -532,8 +534,8 @@ export function possibleMovesHandler( turnColor: () => Color, startingFen: () => string, piecesFunc: () => Pieces, - variant: string, - moveable: () => Map> | undefined, + variant: VariantKey, + moveable: () => Dests | undefined, steps: () => RoundStep[], ) { return (ev: KeyboardEvent): boolean => { @@ -555,7 +557,7 @@ export function possibleMovesHandler( }; const rules: Rules = RULES[ruleTranslation[variant]]; - let rawMoves; + let rawMoves: Dests | undefined; // possible inefficient to reparse fen; but seems to work when it is AND when it is not the users' turn. Also note that this FEN is incomplete as it only contains the piece information. // if it is your turn @@ -577,7 +579,7 @@ export function possibleMovesHandler( const possibleMoves = rawMoves ?.get(pos) ?.map(i => { - const p = pieces.get(i as Key); + const p = pieces.get(i); // logic to prevent 'capture rook' on own piece in chess960 return p && p.color !== yourColor ? `${i} captures ${p.role}` : i; }) @@ -616,12 +618,8 @@ export function inputToLegalUci(input: string, fen: string, chessground: CgApi): else return; } -export function renderMainline( - nodes: Tree.Node[], - currentPath: Tree.Path, - style: Style, -): Array { - const res: Array = []; +export function renderMainline(nodes: Tree.Node[], currentPath: Tree.Path, style: Style): VNodeChildren { + const res: VNodeChildren = []; let path: Tree.Path = ''; nodes.forEach(node => { if (!node.san || !node.uci) return; From 686690ab4a2ce87ce0d13aadc2492120ae24f913 Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Mon, 9 Dec 2024 09:37:22 +0000 Subject: [PATCH 04/43] some code golf --- ui/nvui/src/chess.ts | 61 ++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 266642f08a2f3..8ace3d036c860 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -51,15 +51,10 @@ const skipToFile: { [letter: string]: Files } = { '*': 'h', }; -export function symbolToFile(char: string): string { - return skipToFile[char] ?? ''; -} +const symbolToFile = (char: string): string => skipToFile[char] ?? ''; -export function supportedVariant(key: string): boolean { - return ['standard', 'chess960', 'kingOfTheHill', 'threeCheck', 'fromPosition', 'atomic', 'horde'].includes( - key, - ); -} +export const supportedVariant = (key: string): boolean => + ['standard', 'chess960', 'kingOfTheHill', 'threeCheck', 'fromPosition', 'atomic', 'horde'].includes(key); export function boardSetting(): Setting { return makeSetting({ @@ -254,39 +249,35 @@ export function renderBoard( positionStyle: PositionStyle, boardStyle: BoardStyle, ): VNode { - const doRankHeader = (rank: Ranks): VNode => { - return h('th', { attrs: { scope: 'row' } }, rank); - }; + const doRankHeader = (rank: Ranks): VNode => h('th', { attrs: { scope: 'row' } }, rank); + const doFileHeaders = (): VNode => { const ths = files.map(file => h('th', { attrs: { scope: 'col' } }, file)); - if (pov === 'black') ths.reverse(); - return h('tr', [h('td'), ...ths, h('td')]); - }; - const renderPositionStyle = (rank: Ranks, file: Files, orig: string) => { - switch (positionStyle) { - case 'before': - return file.toUpperCase() + rank + ' ' + orig; - case 'after': - return orig + ' ' + file.toUpperCase() + rank; - case 'none': - return orig; - } + return h('tr', [h('td'), ...(pov === 'black' ? ths.reverse() : ths), h('td')]); }; + + const renderPositionStyle = (rank: Ranks, file: Files, orig: string) => + positionStyle === 'before' + ? file.toUpperCase() + rank + ' ' + orig + : positionStyle === 'after' + ? orig + ' ' + file.toUpperCase() + rank + : orig; + const doPieceButton = ( rank: Ranks, file: Files, letter: string, color: Color | 'none', text: string, - ): VNode => { - return h( + ): VNode => + h( 'button', { attrs: { rank: rank, file: file, piece: letter.toLowerCase(), color: color, 'trap-bypass': true }, }, text, ); - }; + const doPiece = (rank: Ranks, file: Files): VNode => { const key = (file + rank) as Key; const piece = pieces.get(key); @@ -303,6 +294,7 @@ export function renderBoard( return h(pieceWrapper, doPieceButton(rank, file, letter, 'none', text)); } }; + const doRank = (pov: Color, rank: Ranks): VNode => { const rankElements = []; if (boardStyle === 'table') rankElements.push(doRankHeader(rank)); @@ -311,6 +303,7 @@ export function renderBoard( if (pov === 'black') rankElements.reverse(); return h(boardStyle === 'table' ? 'tr' : 'div', rankElements); }; + const ranks: VNode[] = []; if (boardStyle === 'table') ranks.push(doFileHeaders()); ranks.push(...invRanks.map(rank => doRank(pov, rank))); @@ -538,8 +531,8 @@ export function possibleMovesHandler( moveable: () => Dests | undefined, steps: () => RoundStep[], ) { - return (ev: KeyboardEvent): boolean => { - if (ev.key !== 'm' && ev.key !== 'M') return true; + return (ev: KeyboardEvent): void => { + if (ev.key.toLowerCase() !== 'm') return; const $boardLive = $('.boardstatus'); const pieces: Pieces = piecesFunc(); @@ -592,7 +585,6 @@ export function possibleMovesHandler( } else { $boardLive.text(possibleMoves.join(', ')); } - return false; }; } @@ -636,16 +628,13 @@ export function renderMainline(nodes: Tree.Node[], currentPath: Tree.Path, style return res; } -export function renderComments(node: Tree.Node, style: Style): string { - if (!node.comments) return ''; - return (node.comments || []).map(c => renderComment(c, style)).join('. '); -} +export const renderComments = (node: Tree.Node, style: Style): string => + node.comments?.map(c => augmentLichessComment(c, style)).join('. ') ?? ''; -function renderComment(comment: Tree.Comment, style: Style): string { - return comment.by === 'lichess' +const augmentLichessComment = (comment: Tree.Comment, style: Style): string => + comment.by === 'lichess' ? comment.text.replace( /Best move was (.+)\./, (_, san) => 'Best move was ' + renderSan(san, undefined, style), ) : comment.text; -} From c26acd4a38afc20a8334de11db451baa9d408f96 Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Mon, 9 Dec 2024 09:37:52 +0000 Subject: [PATCH 05/43] use lichessrules chessops compat util --- ui/nvui/src/chess.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 8ace3d036c860..573db699e5e80 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -3,8 +3,8 @@ import { type Pieces, files } from 'chessground/types'; import { invRanks, allKeys } from 'chessground/util'; import { type Setting, makeSetting } from './setting'; import { parseFen } from 'chessops/fen'; -import { chessgroundDests } from 'chessops/compat'; -import { type SquareName, RULES, type Rules } from 'chessops/types'; +import { chessgroundDests, lichessRules } from 'chessops/compat'; +import { type SquareName } from 'chessops/types'; import { setupPosition } from 'chessops/variant'; import { charToRole, parseUci, roleToChar } from 'chessops/util'; import { destsToUcis, plyToTurn, sanToUci, sanWriter } from 'chess'; @@ -538,17 +538,6 @@ export function possibleMovesHandler( const $btn = $(ev.target as HTMLElement); const pos = (($btn.attr('file') ?? '') + $btn.attr('rank')) as SquareName; - const ruleTranslation: { [vari: string]: number } = { - standard: 0, - antichess: 1, - kingOfTheHill: 2, - threeCheck: 3, - atomic: 4, - horde: 5, - racingKings: 6, - crazyhouse: 7, - }; - const rules: Rules = RULES[ruleTranslation[variant]]; let rawMoves: Dests | undefined; @@ -557,7 +546,7 @@ export function possibleMovesHandler( if (turnColor() === yourColor) { rawMoves = moveable(); } else { - const fromSetup = setupPosition(rules, parseFen(startingFen()).unwrap()).unwrap(); + const fromSetup = setupPosition(lichessRules(variant), parseFen(startingFen()).unwrap()).unwrap(); steps().forEach(s => { if (s.uci) { const move = parseUci(s.uci); From 3dad0fb926ec753918f81fedbc43c34bc856f0bf Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Mon, 9 Dec 2024 10:50:06 +0000 Subject: [PATCH 06/43] golf + rename stuff to be more accurate --- ui/nvui/src/chess.ts | 61 +++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 573db699e5e80..1628855afd154 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -117,37 +117,27 @@ export function positionSetting(): Setting { storage: storage.make('nvui.positionStyle'), }); } -const renderPieceStyle = (piece: string, pieceStyle: PieceStyle) => { - switch (pieceStyle) { - case 'letter': - return piece.toLowerCase(); - case 'white uppercase letter': - return piece; - case 'name': - return charToRole(piece); - case 'white uppercase name': - return `${piece}${charToRole(piece)?.slice(1)}`; - } -}; -const renderPrefixStyle = (color: Color, prefixStyle: PrefixStyle) => { - switch (prefixStyle) { - case 'letter': - return color.charAt(0); - case 'name': - return color + ' '; - case 'none': - return ''; - } -}; + +const renderPieceStyle = (piece: string, pieceStyle: PieceStyle) => + pieceStyle === 'letter' + ? piece.toLowerCase() + : pieceStyle === 'white uppercase letter' + ? piece + : pieceStyle === 'name' + ? charToRole(piece) + : `${piece}${charToRole(piece)?.slice(1)}`; + +const renderPrefixStyle = (color: Color, prefixStyle: PrefixStyle): `${Color} ` | 'w' | 'b' | '' => + prefixStyle === 'letter' ? (color[0] as 'w' | 'b') : prefixStyle === 'name' ? `${color} ` : ''; export function lastCaptured( - movesGenerator: () => string[], + fensteps: () => string[], pieceStyle: PieceStyle, prefixStyle: PrefixStyle, ): string { - const moves = movesGenerator(); - const oldFen = moves[moves.length - 2]; - const newFen = moves[moves.length - 1]; + const fens = fensteps(); + const oldFen = fens[fens.length - 2]; + const newFen = fens[fens.length - 1]; if (!oldFen || !newFen) { return 'none'; } @@ -279,7 +269,7 @@ export function renderBoard( ); const doPiece = (rank: Ranks, file: Files): VNode => { - const key = (file + rank) as Key; + const key: Key = `${file}${rank}`; const piece = pieces.get(key); const pieceWrapper = boardStyle === 'table' ? 'td' : 'span'; if (piece) { @@ -507,7 +497,7 @@ export function boardCommandsHandler() { }; } export function lastCapturedCommandHandler( - steps: () => string[], + fensteps: () => string[], pieceStyle: PieceStyle, prefixStyle: PrefixStyle, ) { @@ -515,7 +505,7 @@ export function lastCapturedCommandHandler( const $boardLive = $('.boardstatus'); if (ev.key === 'c') { $boardLive.text(); - $boardLive.text(lastCaptured(steps, pieceStyle, prefixStyle)); + $boardLive.text(lastCaptured(fensteps, pieceStyle, prefixStyle)); return false; } return true; @@ -558,7 +548,7 @@ export function possibleMovesHandler( rawMoves = chessgroundDests(fromSetup); } - const possibleMoves = rawMoves + const possibleCaptures = rawMoves ?.get(pos) ?.map(i => { const p = pieces.get(i); @@ -566,14 +556,9 @@ export function possibleMovesHandler( return p && p.color !== yourColor ? `${i} captures ${p.role}` : i; }) ?.filter(i => ev.key === 'm' || i.includes('captures')); - if (!possibleMoves) { - $boardLive.text('None'); - // if filters out non-capturing moves - } else if (possibleMoves.length === 0) { - $boardLive.text('No captures'); - } else { - $boardLive.text(possibleMoves.join(', ')); - } + $boardLive.text( + !possibleCaptures ? 'None' : !possibleCaptures.length ? 'No captures' : possibleCaptures.join(', '), + ); }; } From 0e2b7cfd74c635874a93924a7e60e551ca9b806a Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Tue, 10 Dec 2024 05:01:50 +0000 Subject: [PATCH 07/43] remove unused blindmode variants scala set --- modules/game/src/main/Game.scala | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/game/src/main/Game.scala b/modules/game/src/main/Game.scala index 51dbf4d254f4a..0a3f860cf50a4 100644 --- a/modules/game/src/main/Game.scala +++ b/modules/game/src/main/Game.scala @@ -239,18 +239,6 @@ object Game: val unanalysableVariants: Set[Variant] = Variant.list.all.toSet -- analysableVariants - val blindModeVariants: Set[Variant] = Set( - chess.variant.Standard, - chess.variant.Chess960, - chess.variant.KingOfTheHill, - chess.variant.ThreeCheck, - chess.variant.FromPosition, - chess.variant.Antichess, - chess.variant.Atomic, - chess.variant.RacingKings, - chess.variant.Horde - ) - val hordeWhitePawnsSince = instantOf(2015, 4, 11, 10, 0) def isOldHorde(game: Game) = From cafb9aec639fb2199297a1a022f4a5dec8e26470 Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Tue, 10 Dec 2024 05:41:17 +0000 Subject: [PATCH 08/43] type tweak --- ui/nvui/src/chess.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 1628855afd154..2e5acf298a51e 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -53,8 +53,8 @@ const skipToFile: { [letter: string]: Files } = { const symbolToFile = (char: string): string => skipToFile[char] ?? ''; -export const supportedVariant = (key: string): boolean => - ['standard', 'chess960', 'kingOfTheHill', 'threeCheck', 'fromPosition', 'atomic', 'horde'].includes(key); +export const supportedVariant = (key: VariantKey): boolean => + !['antichess', 'racingKings', 'crazyhouse'].includes(key); export function boardSetting(): Setting { return makeSetting({ From 08dd1dbd119e72bf4b3b43a1b327c319a00e8a33 Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Tue, 10 Dec 2024 05:41:34 +0000 Subject: [PATCH 09/43] distinguish between cash and non cash variables --- ui/nvui/src/chess.ts | 112 +++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 2e5acf298a51e..01e9b35378481 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -326,22 +326,22 @@ export function castlingFlavours(input: string): string { export function positionJumpHandler() { return (ev: KeyboardEvent): boolean => { const $btn = $(ev.target as HTMLElement); - const $file = $btn.attr('file') ?? ''; - const $rank = $btn.attr('rank') ?? ''; - let $newRank = ''; - let $newFile = ''; + const file = $btn.attr('file') ?? ''; + const rank = $btn.attr('rank') ?? ''; + let newRank = ''; + let newFile = ''; if (ev.key.match(/^[1-8]$/)) { - $newRank = ev.key; - $newFile = $file; + newRank = ev.key; + newFile = file; } else if (ev.key.match(/^[!@#$%^&*]$/)) { - $newRank = $rank; - $newFile = symbolToFile(ev.key); + newRank = rank; + newFile = symbolToFile(ev.key); // if not a valid key for jumping } else { return true; } const newBtn = document.querySelector( - '.board-wrapper button[rank="' + $newRank + '"][file="' + $newFile + '"]', + '.board-wrapper button[rank="' + newRank + '"][file="' + newFile + '"]', ) as HTMLElement; if (newBtn) { newBtn.focus(); @@ -351,74 +351,74 @@ export function positionJumpHandler() { }; } -export function pieceJumpingHandler(wrapSound: () => void, errorSound: () => void) { - return (ev: KeyboardEvent): boolean => { - if (!ev.key.match(/^[kqrbnp]$/i)) return true; +export function pieceJumpingHandler(selectSound: () => void, errorSound: () => void) { + return (ev: KeyboardEvent): void => { + if (!ev.key.match(/^[kqrbnp]$/i)) return; const $currBtn = $(ev.target as HTMLElement); // TODO: decouple from promotion attribute setting in selectionHandler if ($currBtn.attr('promotion') === 'true') { const $moveBox = $('input.move'); const $boardLive = $('.boardstatus'); - const $promotionPiece = ev.key.toLowerCase(); + const promotionPiece = ev.key.toLowerCase(); const $form = $moveBox.parent().parent(); - if (!$promotionPiece.match(/^[qnrb]$/)) { + if (!promotionPiece.match(/^[qnrb]$/)) { $boardLive.text('Invalid promotion piece. q for queen, n for knight, r for rook, b for bishop'); - return false; + return; } - $moveBox.val($moveBox.val() + $promotionPiece); + $moveBox.val($moveBox.val() + promotionPiece); $currBtn.removeAttr('promotion'); - const $sendForm = new Event('submit', { + const sendForm = new Event('submit', { cancelable: true, bubbles: true, }); - $form.trigger($sendForm); - return false; + $form.trigger(sendForm); + return; } - const $myBtnAttrs = + const myBtnAttrs = '.board-wrapper [rank="' + $currBtn.attr('rank') + '"][file="' + $currBtn.attr('file') + '"]'; - const $allPieces = $('.board-wrapper [piece="' + ev.key.toLowerCase() + '"], ' + $myBtnAttrs); - const $myPieceIndex = $allPieces.index($myBtnAttrs); - const $next = ev.key.toLowerCase() === ev.key; - const $prevNextPieces = $next ? $allPieces.slice($myPieceIndex + 1) : $allPieces.slice(0, $myPieceIndex); - const $piece = $next ? $prevNextPieces.get(0) : $prevNextPieces.get($prevNextPieces.length - 1); - if ($piece) { - $piece.focus(); + const $allPieces = $('.board-wrapper [piece="' + ev.key.toLowerCase() + '"], ' + myBtnAttrs); + const myPieceIndex = $allPieces.index(myBtnAttrs); + const next = ev.key.toLowerCase() === ev.key; + const $prevNextPieces = next ? $allPieces.slice(myPieceIndex + 1) : $allPieces.slice(0, myPieceIndex); + const pieceEl = next ? $prevNextPieces.get(0) : $prevNextPieces.get($prevNextPieces.length - 1); + if (pieceEl) { + pieceEl.focus(); // if detected any matching piece; one is the piece being clicked on, } else if ($allPieces.length >= 2) { - const $wrapPiece = $next ? $allPieces.get(0) : $allPieces.get($allPieces.length - 1); - $wrapPiece?.focus(); - wrapSound(); + const wrapPieceEl = next ? $allPieces.get(0) : $allPieces.get($allPieces.length - 1); + wrapPieceEl?.focus(); + selectSound(); } else { errorSound(); } - return false; + return; }; } export function arrowKeyHandler(pov: Color, borderSound: () => void) { return (ev: KeyboardEvent): boolean => { const $currBtn = $(ev.target as HTMLElement); - const $isWhite = pov === 'white'; - let $file = $currBtn.attr('file') ?? ' '; - let $rank = Number($currBtn.attr('rank')); + const isWhite = pov === 'white'; + let file = $currBtn.attr('file') ?? ' '; + let rank = Number($currBtn.attr('rank')); if (ev.key === 'ArrowUp') { - $rank = $isWhite ? ($rank += 1) : ($rank -= 1); + rank = isWhite ? (rank += 1) : (rank -= 1); } else if (ev.key === 'ArrowDown') { - $rank = $isWhite ? ($rank -= 1) : ($rank += 1); + rank = isWhite ? (rank -= 1) : (rank += 1); } else if (ev.key === 'ArrowLeft') { - $file = String.fromCharCode($isWhite ? $file.charCodeAt(0) - 1 : $file.charCodeAt(0) + 1); + file = String.fromCharCode(isWhite ? file.charCodeAt(0) - 1 : file.charCodeAt(0) + 1); } else if (ev.key === 'ArrowRight') { - $file = String.fromCharCode($isWhite ? $file.charCodeAt(0) + 1 : $file.charCodeAt(0) - 1); + file = String.fromCharCode(isWhite ? file.charCodeAt(0) + 1 : file.charCodeAt(0) - 1); } else { return true; } - const $newSq = document.querySelector( - '.board-wrapper [file="' + $file + '"][rank="' + $rank + '"]', + const newSqEl = document.querySelector( + '.board-wrapper [file="' + file + '"][rank="' + rank + '"]', ) as HTMLElement; - if ($newSq) { - $newSq.focus(); + if (newSqEl) { + newSqEl.focus(); } else { borderSound(); } @@ -432,10 +432,10 @@ export function selectionHandler(getOpponentColor: () => Color, selectSound: () const opponentColor = getOpponentColor(); // this depends on the current document structure. This may not be advisable in case the structure wil change. const $evBtn = $(ev.target as HTMLElement); - const $rank = $evBtn.attr('rank'); - const $pos = ($evBtn.attr('file') ?? '') + $rank; + const rank = $evBtn.attr('rank'); + const pos = ($evBtn.attr('file') ?? '') + rank; const $boardLive = $('.boardstatus'); - const $promotionRank = opponentColor === 'black' ? '8' : '1'; + const promotionRank = opponentColor === 'black' ? '8' : '1'; const $moveBox = $(document.querySelector('input.move') as HTMLInputElement); if (!$moveBox) return false; @@ -445,30 +445,30 @@ export function selectionHandler(getOpponentColor: () => Color, selectSound: () if ($evBtn.attr('color') === opponentColor) return false; // as long as the user is selecting a piece and not a blank tile if ($evBtn.text().match(/^[^\-+]+/g)) { - $moveBox.val($pos); + $moveBox.val(pos); selectSound(); } } else { // if user selects their own piece second if ($evBtn.attr('color') === (opponentColor === 'black' ? 'white' : 'black')) return false; - const $first = $moveBox.val(); - const $firstPiece = $('.board-wrapper [file="' + $first[0] + '"][rank="' + $first[1] + '"]'); - $moveBox.val($moveBox.val() + $pos); + const first = $moveBox.val(); + const $firstPiece = $('.board-wrapper [file="' + first[0] + '"][rank="' + first[1] + '"]'); + $moveBox.val($moveBox.val() + pos); // this is coupled to pieceJumpingHandler() noticing that the attribute is set and acting differently. TODO: make cleaner // if pawn promotion - if ($rank === $promotionRank && $firstPiece.attr('piece')?.toLowerCase() === 'p') { + if (rank === promotionRank && $firstPiece.attr('piece')?.toLowerCase() === 'p') { $evBtn.attr('promotion', 'true'); $boardLive.text('Promote to? q for queen, n for knight, r for rook, b for bishop'); return false; } // this section depends on the form being the grandparent of the input.move box. const $form = $moveBox.parent().parent(); - const $event = new Event('submit', { + const event = new Event('submit', { cancelable: true, bubbles: true, }); - $form.trigger($event); + $form.trigger(event); } return false; }; @@ -478,15 +478,15 @@ export function boardCommandsHandler() { return (ev: KeyboardEvent): boolean => { const $currBtn = $(ev.target as HTMLElement); const $boardLive = $('.boardstatus'); - const $position = ($currBtn.attr('file') ?? '') + ($currBtn.attr('rank') ?? ''); + const position = ($currBtn.attr('file') ?? '') + ($currBtn.attr('rank') ?? ''); if (ev.key === 'o') { $boardLive.text(); - $boardLive.text($position); + $boardLive.text(position); return false; } else if (ev.key === 'l') { - const $lastMove = $('p.lastMove').text(); + const lastMove = $('p.lastMove').text(); $boardLive.text(); - $boardLive.text($lastMove); + $boardLive.text(lastMove); return false; } else if (ev.key === 't') { $boardLive.text(); From 7e6b26b7aad386cb23c35d28d32c4413053408eb Mon Sep 17 00:00:00 2001 From: Allan Joseph Date: Tue, 10 Dec 2024 05:57:47 +0000 Subject: [PATCH 10/43] rename `Style` to `MoveStyle` for clarity --- ui/analyse/src/plugins/analyse.nvui.ts | 14 +++++++------- ui/nvui/src/chess.ts | 25 +++++++++++++------------ ui/nvui/src/command.ts | 6 +++--- ui/puzzle/src/plugins/puzzle.nvui.ts | 8 ++++---- ui/round/src/plugins/round.nvui.ts | 8 ++++---- 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/ui/analyse/src/plugins/analyse.nvui.ts b/ui/analyse/src/plugins/analyse.nvui.ts index 6f327391efe58..f88dac437954a 100644 --- a/ui/analyse/src/plugins/analyse.nvui.ts +++ b/ui/analyse/src/plugins/analyse.nvui.ts @@ -7,7 +7,7 @@ import type { AnalyseData } from '../interfaces'; import type { Player } from 'game'; import viewStatus from 'game/view/status'; import { - type Style, + type MoveStyle, renderSan, renderPieces, renderBoard, @@ -303,7 +303,7 @@ function depthInfo(clientEv: Tree.ClientEval | undefined, isCloud: boolean): str return i18n.site.depthX(depth) + isCloud ? ' Cloud' : ''; } -function renderBestMove(ctrl: AnalyseController, style: Style): string { +function renderBestMove(ctrl: AnalyseController, style: MoveStyle): string { const instance = ctrl.getCeval(); if (!instance.allowed()) return NOT_ALLOWED; if (!instance.possible) return NOT_POSSIBLE; @@ -352,7 +352,7 @@ function renderResult(ctrl: AnalyseController): VNode[] { return []; } -function renderCurrentLine(ctrl: AnalyseController, style: Style): VNodeChildren { +function renderCurrentLine(ctrl: AnalyseController, style: MoveStyle): VNodeChildren { if (ctrl.path.length === 0) { return renderMainline(ctrl.mainline, ctrl.path, style); } else { @@ -361,7 +361,7 @@ function renderCurrentLine(ctrl: AnalyseController, style: Style): VNodeChildren } } -function onSubmit(ctrl: AnalyseController, notify: (txt: string) => void, style: () => Style, $input: Cash) { +function onSubmit(ctrl: AnalyseController, notify: (txt: string) => void, style: () => MoveStyle, $input: Cash) { return function () { let input = castlingFlavours(($input.val() as string).trim()); if (isShortCommand(input)) input = '/' + input; @@ -383,7 +383,7 @@ function isShortCommand(input: string): boolean { return shortCommands.includes(input.split(' ')[0].toLowerCase()); } -function onCommand(ctrl: AnalyseController, notify: (txt: string) => void, c: string, style: Style) { +function onCommand(ctrl: AnalyseController, notify: (txt: string) => void, c: string, style: MoveStyle) { const lowered = c.toLowerCase(); if (lowered === 'next') { next(ctrl); @@ -411,7 +411,7 @@ function onCommand(ctrl: AnalyseController, notify: (txt: string) => void, c: st const analysisGlyphs = ['?!', '?', '??']; -function renderAcpl(ctrl: AnalyseController, style: Style): MaybeVNodes | undefined { +function renderAcpl(ctrl: AnalyseController, style: MoveStyle): MaybeVNodes | undefined { const anal = ctrl.data.analysis; if (!anal) return undefined; const analysisNodes = ctrl.mainline.filter(n => @@ -486,7 +486,7 @@ function renderLineIndex(ctrl: AnalyseController): string { return of > 1 ? `, line ${i + 1} of ${of} ,` : ''; } -function renderCurrentNode(ctrl: AnalyseController, style: Style): string { +function renderCurrentNode(ctrl: AnalyseController, style: MoveStyle): string { const node = ctrl.node; if (!node.san || !node.uci) return 'Initial position'; return [ diff --git a/ui/nvui/src/chess.ts b/ui/nvui/src/chess.ts index 01e9b35378481..f21f59b3697bd 100644 --- a/ui/nvui/src/chess.ts +++ b/ui/nvui/src/chess.ts @@ -10,7 +10,7 @@ import { charToRole, parseUci, roleToChar } from 'chessops/util'; import { destsToUcis, plyToTurn, sanToUci, sanWriter } from 'chess'; import { storage } from 'common/storage'; -export type Style = 'uci' | 'san' | 'literate' | 'nato' | 'anna'; +export type MoveStyle = 'uci' | 'san' | 'literate' | 'nato' | 'anna'; export type PieceStyle = 'letter' | 'white uppercase letter' | 'name' | 'white uppercase name'; export type PrefixStyle = 'letter' | 'name' | 'none'; export type PositionStyle = 'before' | 'after' | 'none'; @@ -67,8 +67,9 @@ export function boardSetting(): Setting { }); } -export function styleSetting(): Setting