From 05a8e2bc0ee3b981f553f532e2d1ead6319d9677 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sun, 22 Mar 2020 18:41:37 +0100 Subject: [PATCH 1/2] puzzle: handle socket on client side --- ui/chess/src/main.ts | 1 + ui/chess/src/ucicharpair.ts | 34 +++++++++++++ ui/puzzle/package.json | 1 + ui/puzzle/src/ctrl.ts | 2 - ui/puzzle/src/interfaces.ts | 2 - ui/puzzle/src/main.ts | 6 +-- ui/puzzle/src/socket.ts | 96 +++++++++++++++++-------------------- ui/site/src/main.js | 9 +--- 8 files changed, 82 insertions(+), 69 deletions(-) create mode 100644 ui/chess/src/ucicharpair.ts diff --git a/ui/chess/src/main.ts b/ui/chess/src/main.ts index e140496aab..1ae130c31f 100644 --- a/ui/chess/src/main.ts +++ b/ui/chess/src/main.ts @@ -1,4 +1,5 @@ import { piotr } from './piotr'; +export { uciCharPair } from './ucicharpair'; export const initialFen: Fen = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; diff --git a/ui/chess/src/ucicharpair.ts b/ui/chess/src/ucicharpair.ts new file mode 100644 index 0000000000..3fb72b32c7 --- /dev/null +++ b/ui/chess/src/ucicharpair.ts @@ -0,0 +1,34 @@ +type Square = number; + +type Role = 'pawn' | 'knight' | 'bishop' | 'rook' | 'queen' | 'king'; + +interface UciMove { + from: Square; + to: Square; + promotion?: Role; +} + +interface UciDrop { + role: Role; + to: Square; +} + +type Uci = UciMove | UciDrop; + +function square(sq: Square): number { + return 35 + sq; +} + +function drop(role: Role): number { + return 35 + 64 + 8 * 5 + ['queen', 'rook', 'bishop', 'knight', 'pawn'].indexOf(role); +} + +function promotion(file: number, role: Role): number { + return 35 + 64 + 8 * ['queen', 'rook', 'bishop', 'knight', 'king'].indexOf(role) + file; +} + +export function uciCharPair(uci: Uci): string { + if ('role' in uci) return String.fromCharCode(square(uci.to), drop(uci.role)); + else if (!uci.promotion) return String.fromCharCode(square(uci.to), square(uci.from)); + else return String.fromCharCode(square(uci.from), promotion(uci.to & 7, uci.promotion)); +} diff --git a/ui/puzzle/package.json b/ui/puzzle/package.json index 465c195872..0553e2d3fc 100644 --- a/ui/puzzle/package.json +++ b/ui/puzzle/package.json @@ -21,6 +21,7 @@ "ceval": "2.0.0", "chess": "2.0.0", "chessground": "^7.6", + "chessops": "^0.3.4", "common": "2.0.0", "snabbdom": "ornicar/snabbdom#0.7.1-lichess", "tree": "2.0.0" diff --git a/ui/puzzle/src/ctrl.ts b/ui/puzzle/src/ctrl.ts index 116612ea03..50382f15ae 100644 --- a/ui/puzzle/src/ctrl.ts +++ b/ui/puzzle/src/ctrl.ts @@ -400,7 +400,6 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller { } const socket = socketBuild({ - send: opts.socketSend, addNode: addNode, addDests: addDests, reset: function() { @@ -482,7 +481,6 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller { getCeval, pref: opts.pref, trans: window.lichess.trans(opts.i18n), - socketReceive: socket.receive, gameOver, toggleCeval, toggleThreatMode, diff --git a/ui/puzzle/src/interfaces.ts b/ui/puzzle/src/interfaces.ts index 4ee57577d7..0e6a6bc365 100644 --- a/ui/puzzle/src/interfaces.ts +++ b/ui/puzzle/src/interfaces.ts @@ -45,7 +45,6 @@ export interface Controller extends KeyboardController { thanks(): boolean; vote(v: boolean): void; pref: PuzzlePrefs; - socketReceive: any; userMove(orig: Key, dest: Key): void; promotion: any; @@ -79,7 +78,6 @@ export interface PuzzleOpts { pref: PuzzlePrefs; data: PuzzleData; i18n: { [key: string]: string | undefined }; - socketSend: any; } export interface PuzzlePrefs { diff --git a/ui/puzzle/src/main.ts b/ui/puzzle/src/main.ts index 3b6e525788..80d9ffad11 100644 --- a/ui/puzzle/src/main.ts +++ b/ui/puzzle/src/main.ts @@ -14,7 +14,7 @@ menuHover(); const patch = init([klass, attributes]); -export default function(opts) { +export default function(opts): void { let vnode: VNode, ctrl: Controller; @@ -27,10 +27,6 @@ export default function(opts) { const blueprint = view(ctrl); opts.element.innerHTML = ''; vnode = patch(opts.element, blueprint); - - return { - socketReceive: ctrl.socketReceive - }; }; // that's for the rest of lichess to access chessground diff --git a/ui/puzzle/src/socket.ts b/ui/puzzle/src/socket.ts index 05dc6d85b7..ffefe12ce9 100644 --- a/ui/puzzle/src/socket.ts +++ b/ui/puzzle/src/socket.ts @@ -1,63 +1,55 @@ +import { Chess } from 'chessops/chess'; +import { parseFen, makeFen } from 'chessops/fen'; +import { makeSanAndPlay } from 'chessops/san'; +import { parseSquare, makeUci } from 'chessops/util'; +import { uciCharPair } from 'chess'; + export default function(opts) { - var anaMoveTimeout; - var anaDestsTimeout; + function piotr(sq: number): string { + if (sq < 26) return String.fromCharCode('a'.charCodeAt(0) + sq); + else if (sq < 52) return String.fromCharCode('A'.charCodeAt(0) + sq - 26); + else if (sq < 62) return String.fromCharCode('0'.charCodeAt(0) + sq - 52); + else if (sq == 62) return '!'; + else return '?'; + } - var anaDestsCache = {}; - - var handlers = { - node: function(data) { - clearTimeout(anaMoveTimeout); - opts.addNode(data.node, data.path); - }, - stepFailure: function() { - clearTimeout(anaMoveTimeout); - opts.reset(); - }, - dests: function(data) { - anaDestsCache[data.path] = data; - opts.addDests(data.dests, data.path); - clearTimeout(anaDestsTimeout); - }, - destsFailure: function(data) { - console.log(data); - clearTimeout(anaDestsTimeout); + function makeDests(pos: Chess): string { + const result: string[] = []; + for (const [from, squares] of pos.allDests()) { + if (squares.nonEmpty()) result.push([from, ...Array.from(squares)].map(piotr).join('')); } - }; + return result.join(' '); + } - var sendAnaMove = function(req) { - clearTimeout(anaMoveTimeout); - opts.send('anaMove', req); - anaMoveTimeout = setTimeout(function() { - sendAnaMove(req); - }, 3000); - }; + function sendAnaMove(req) { + const setup = parseFen(req.fen).unwrap(); + const pos = Chess.fromSetup(setup).unwrap(); + const uci = { + from: parseSquare(req.orig)!, + to: parseSquare(req.dest)!, + promotion: req.promotion, + }; + const san = makeSanAndPlay(pos, uci); + setTimeout(() => opts.addNode({ + ply: 2 * (pos.fullmoves - 1) + (pos.turn == 'white' ? 0 : 1), + fen: makeFen(pos.toSetup()), + id: uciCharPair(uci), + dests: makeDests(pos), + children: [], + san, + uci: makeUci(uci), + }, req.path), 10); + } - var sendAnaDests = function(req) { - clearTimeout(anaDestsTimeout); - if (anaDestsCache[req.path]) setTimeout(function() { - handlers.dests(anaDestsCache[req.path]); - }, 10); - else { - opts.send('anaDests', req); - anaDestsTimeout = setTimeout(function() { - sendAnaDests(req); - }, 3000); - } - }; + function sendAnaDests(req) { + const setup = parseFen(req.fen).unwrap(); + const pos = Chess.fromSetup(setup).unwrap(); + setTimeout(() => opts.addDests(makeDests(pos), req.path), 10); + } return { - send: opts.send, - receive: function(type, data) { - if (handlers[type]) { - handlers[type](data); - return true; - } - return false; - }, - sendAnaMove: sendAnaMove, - - sendAnaDests: sendAnaDests + sendAnaDests: sendAnaDests, }; } diff --git a/ui/site/src/main.js b/ui/site/src/main.js index b8d8621920..7a2ec4ceb5 100644 --- a/ui/site/src/main.js +++ b/ui/site/src/main.js @@ -955,15 +955,8 @@ //////////////// function startPuzzle(cfg) { - var puzzle; cfg.element = document.querySelector('main.puzzle'); - lichess.socket = lichess.StrongSocket('/socket/v4', false, { - receive: function(t, d) { - puzzle.socketReceive(t, d); - } - }); - cfg.socketSend = lichess.socket.send; - puzzle = LichessPuzzle.default(cfg); + LichessPuzzle.default(cfg); } //////////////////// From 73f1ecb7a3550bd7c48f6e0c03526507c1592b8f Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Wed, 25 Mar 2020 14:27:25 +0100 Subject: [PATCH 2/2] puzzle: allow alternative catsling moves --- ui/puzzle/src/socket.ts | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/ui/puzzle/src/socket.ts b/ui/puzzle/src/socket.ts index ffefe12ce9..3d8fe09e1e 100644 --- a/ui/puzzle/src/socket.ts +++ b/ui/puzzle/src/socket.ts @@ -1,8 +1,9 @@ import { Chess } from 'chessops/chess'; import { parseFen, makeFen } from 'chessops/fen'; import { makeSanAndPlay } from 'chessops/san'; -import { parseSquare, makeUci } from 'chessops/util'; -import { uciCharPair } from 'chess'; +import { parseSquare, makeUci, parseUci } from 'chessops/util'; +import { altCastles, uciCharPair } from 'chess'; +import { defined } from 'common'; export default function(opts) { @@ -15,8 +16,19 @@ export default function(opts) { } function makeDests(pos: Chess): string { + const dests = pos.allDests(); + + // add two step castling moves (standard chess) + const king = pos.board.kingOf(pos.turn); + if (defined(king) && king & 4 && dests.has(king)) { + if (dests.get(king)!.has(0)) dests.set(king, dests.get(king)!.with(2)); + if (dests.get(king)!.has(7)) dests.set(king, dests.get(king)!.with(6)); + if (dests.get(king)!.has(56)) dests.set(king, dests.get(king)!.with(58)); + if (dests.get(king)!.has(63)) dests.set(king, dests.get(king)!.with(62)); + } + const result: string[] = []; - for (const [from, squares] of pos.allDests()) { + for (const [from, squares] of dests) { if (squares.nonEmpty()) result.push([from, ...Array.from(squares)].map(piotr).join('')); } return result.join(' '); @@ -25,20 +37,21 @@ export default function(opts) { function sendAnaMove(req) { const setup = parseFen(req.fen).unwrap(); const pos = Chess.fromSetup(setup).unwrap(); - const uci = { + const move = { from: parseSquare(req.orig)!, to: parseSquare(req.dest)!, promotion: req.promotion, }; - const san = makeSanAndPlay(pos, uci); + const san = makeSanAndPlay(pos, move); + const uci = san.startsWith('O-O') && altCastles[makeUci(move)] || makeUci(move); setTimeout(() => opts.addNode({ ply: 2 * (pos.fullmoves - 1) + (pos.turn == 'white' ? 0 : 1), fen: makeFen(pos.toSetup()), - id: uciCharPair(uci), dests: makeDests(pos), children: [], san, - uci: makeUci(uci), + uci, + id: uciCharPair(parseUci(uci)!), }, req.path), 10); }