UNPKG

lichess-pgn-viewer

Version:

PGN viewer widget, designed to be embedded in content pages.

1,492 lines (1,481 loc) 194 kB
// node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/types.js var FILE_NAMES = ["a", "b", "c", "d", "e", "f", "g", "h"]; var RANK_NAMES = ["1", "2", "3", "4", "5", "6", "7", "8"]; var COLORS = ["white", "black"]; var ROLES = ["pawn", "knight", "bishop", "rook", "queen", "king"]; var CASTLING_SIDES = ["a", "h"]; var isDrop = (v) => "role" in v; // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/util.js var defined = (v) => v !== void 0; var opposite = (color) => color === "white" ? "black" : "white"; var squareRank = (square) => square >> 3; var squareFile = (square) => square & 7; var roleToChar = (role) => { switch (role) { case "pawn": return "p"; case "knight": return "n"; case "bishop": return "b"; case "rook": return "r"; case "queen": return "q"; case "king": return "k"; } }; function charToRole(ch) { switch (ch.toLowerCase()) { case "p": return "pawn"; case "n": return "knight"; case "b": return "bishop"; case "r": return "rook"; case "q": return "queen"; case "k": return "king"; default: return; } } function parseSquare(str) { if (str.length !== 2) return; const file = str.charCodeAt(0) - "a".charCodeAt(0); const rank = str.charCodeAt(1) - "1".charCodeAt(0); if (file < 0 || file >= 8 || rank < 0 || rank >= 8) return; return file + 8 * rank; } var makeSquare = (square) => FILE_NAMES[squareFile(square)] + RANK_NAMES[squareRank(square)]; var makeUci = (move3) => isDrop(move3) ? `${roleToChar(move3.role).toUpperCase()}@${makeSquare(move3.to)}` : makeSquare(move3.from) + makeSquare(move3.to) + (move3.promotion ? roleToChar(move3.promotion) : ""); var kingCastlesTo = (color, side) => color === "white" ? side === "a" ? 2 : 6 : side === "a" ? 58 : 62; var rookCastlesTo = (color, side) => color === "white" ? side === "a" ? 3 : 5 : side === "a" ? 59 : 61; // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/squareSet.js var popcnt32 = (n2) => { n2 = n2 - (n2 >>> 1 & 1431655765); n2 = (n2 & 858993459) + (n2 >>> 2 & 858993459); return Math.imul(n2 + (n2 >>> 4) & 252645135, 16843009) >> 24; }; var bswap32 = (n2) => { n2 = n2 >>> 8 & 16711935 | (n2 & 16711935) << 8; return n2 >>> 16 & 65535 | (n2 & 65535) << 16; }; var rbit32 = (n2) => { n2 = n2 >>> 1 & 1431655765 | (n2 & 1431655765) << 1; n2 = n2 >>> 2 & 858993459 | (n2 & 858993459) << 2; n2 = n2 >>> 4 & 252645135 | (n2 & 252645135) << 4; return bswap32(n2); }; var SquareSet = class _SquareSet { constructor(lo, hi) { this.lo = lo | 0; this.hi = hi | 0; } static fromSquare(square) { return square >= 32 ? new _SquareSet(0, 1 << square - 32) : new _SquareSet(1 << square, 0); } static fromRank(rank) { return new _SquareSet(255, 0).shl64(8 * rank); } static fromFile(file) { return new _SquareSet(16843009 << file, 16843009 << file); } static empty() { return new _SquareSet(0, 0); } static full() { return new _SquareSet(4294967295, 4294967295); } static corners() { return new _SquareSet(129, 2164260864); } static center() { return new _SquareSet(402653184, 24); } static backranks() { return new _SquareSet(255, 4278190080); } static backrank(color) { return color === "white" ? new _SquareSet(255, 0) : new _SquareSet(0, 4278190080); } static lightSquares() { return new _SquareSet(1437226410, 1437226410); } static darkSquares() { return new _SquareSet(2857740885, 2857740885); } complement() { return new _SquareSet(~this.lo, ~this.hi); } xor(other) { return new _SquareSet(this.lo ^ other.lo, this.hi ^ other.hi); } union(other) { return new _SquareSet(this.lo | other.lo, this.hi | other.hi); } intersect(other) { return new _SquareSet(this.lo & other.lo, this.hi & other.hi); } diff(other) { return new _SquareSet(this.lo & ~other.lo, this.hi & ~other.hi); } intersects(other) { return this.intersect(other).nonEmpty(); } isDisjoint(other) { return this.intersect(other).isEmpty(); } supersetOf(other) { return other.diff(this).isEmpty(); } subsetOf(other) { return this.diff(other).isEmpty(); } shr64(shift) { if (shift >= 64) return _SquareSet.empty(); if (shift >= 32) return new _SquareSet(this.hi >>> shift - 32, 0); if (shift > 0) return new _SquareSet(this.lo >>> shift ^ this.hi << 32 - shift, this.hi >>> shift); return this; } shl64(shift) { if (shift >= 64) return _SquareSet.empty(); if (shift >= 32) return new _SquareSet(0, this.lo << shift - 32); if (shift > 0) return new _SquareSet(this.lo << shift, this.hi << shift ^ this.lo >>> 32 - shift); return this; } bswap64() { return new _SquareSet(bswap32(this.hi), bswap32(this.lo)); } rbit64() { return new _SquareSet(rbit32(this.hi), rbit32(this.lo)); } minus64(other) { const lo = this.lo - other.lo; const c = (lo & other.lo & 1) + (other.lo >>> 1) + (lo >>> 1) >>> 31; return new _SquareSet(lo, this.hi - (other.hi + c)); } equals(other) { return this.lo === other.lo && this.hi === other.hi; } size() { return popcnt32(this.lo) + popcnt32(this.hi); } isEmpty() { return this.lo === 0 && this.hi === 0; } nonEmpty() { return this.lo !== 0 || this.hi !== 0; } has(square) { return (square >= 32 ? this.hi & 1 << square - 32 : this.lo & 1 << square) !== 0; } set(square, on) { return on ? this.with(square) : this.without(square); } with(square) { return square >= 32 ? new _SquareSet(this.lo, this.hi | 1 << square - 32) : new _SquareSet(this.lo | 1 << square, this.hi); } without(square) { return square >= 32 ? new _SquareSet(this.lo, this.hi & ~(1 << square - 32)) : new _SquareSet(this.lo & ~(1 << square), this.hi); } toggle(square) { return square >= 32 ? new _SquareSet(this.lo, this.hi ^ 1 << square - 32) : new _SquareSet(this.lo ^ 1 << square, this.hi); } last() { if (this.hi !== 0) return 63 - Math.clz32(this.hi); if (this.lo !== 0) return 31 - Math.clz32(this.lo); return; } first() { if (this.lo !== 0) return 31 - Math.clz32(this.lo & -this.lo); if (this.hi !== 0) return 63 - Math.clz32(this.hi & -this.hi); return; } withoutFirst() { if (this.lo !== 0) return new _SquareSet(this.lo & this.lo - 1, this.hi); return new _SquareSet(0, this.hi & this.hi - 1); } moreThanOne() { return this.hi !== 0 && this.lo !== 0 || (this.lo & this.lo - 1) !== 0 || (this.hi & this.hi - 1) !== 0; } singleSquare() { return this.moreThanOne() ? void 0 : this.last(); } *[Symbol.iterator]() { let lo = this.lo; let hi = this.hi; while (lo !== 0) { const idx = 31 - Math.clz32(lo & -lo); lo ^= 1 << idx; yield idx; } while (hi !== 0) { const idx = 31 - Math.clz32(hi & -hi); hi ^= 1 << idx; yield 32 + idx; } } *reversed() { let lo = this.lo; let hi = this.hi; while (hi !== 0) { const idx = 31 - Math.clz32(hi); hi ^= 1 << idx; yield 32 + idx; } while (lo !== 0) { const idx = 31 - Math.clz32(lo); lo ^= 1 << idx; yield idx; } } }; // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/attacks.js var computeRange = (square, deltas) => { let range = SquareSet.empty(); for (const delta of deltas) { const sq = square + delta; if (0 <= sq && sq < 64 && Math.abs(squareFile(square) - squareFile(sq)) <= 2) { range = range.with(sq); } } return range; }; var tabulate = (f) => { const table = []; for (let square = 0; square < 64; square++) table[square] = f(square); return table; }; var KING_ATTACKS = tabulate((sq) => computeRange(sq, [-9, -8, -7, -1, 1, 7, 8, 9])); var KNIGHT_ATTACKS = tabulate((sq) => computeRange(sq, [-17, -15, -10, -6, 6, 10, 15, 17])); var PAWN_ATTACKS = { white: tabulate((sq) => computeRange(sq, [7, 9])), black: tabulate((sq) => computeRange(sq, [-7, -9])) }; var kingAttacks = (square) => KING_ATTACKS[square]; var knightAttacks = (square) => KNIGHT_ATTACKS[square]; var pawnAttacks = (color, square) => PAWN_ATTACKS[color][square]; var FILE_RANGE = tabulate((sq) => SquareSet.fromFile(squareFile(sq)).without(sq)); var RANK_RANGE = tabulate((sq) => SquareSet.fromRank(squareRank(sq)).without(sq)); var DIAG_RANGE = tabulate((sq) => { const diag = new SquareSet(134480385, 2151686160); const shift = 8 * (squareRank(sq) - squareFile(sq)); return (shift >= 0 ? diag.shl64(shift) : diag.shr64(-shift)).without(sq); }); var ANTI_DIAG_RANGE = tabulate((sq) => { const diag = new SquareSet(270549120, 16909320); const shift = 8 * (squareRank(sq) + squareFile(sq) - 7); return (shift >= 0 ? diag.shl64(shift) : diag.shr64(-shift)).without(sq); }); var hyperbola = (bit, range, occupied) => { let forward = occupied.intersect(range); let reverse = forward.bswap64(); forward = forward.minus64(bit); reverse = reverse.minus64(bit.bswap64()); return forward.xor(reverse.bswap64()).intersect(range); }; var fileAttacks = (square, occupied) => hyperbola(SquareSet.fromSquare(square), FILE_RANGE[square], occupied); var rankAttacks = (square, occupied) => { const range = RANK_RANGE[square]; let forward = occupied.intersect(range); let reverse = forward.rbit64(); forward = forward.minus64(SquareSet.fromSquare(square)); reverse = reverse.minus64(SquareSet.fromSquare(63 - square)); return forward.xor(reverse.rbit64()).intersect(range); }; var bishopAttacks = (square, occupied) => { const bit = SquareSet.fromSquare(square); return hyperbola(bit, DIAG_RANGE[square], occupied).xor(hyperbola(bit, ANTI_DIAG_RANGE[square], occupied)); }; var rookAttacks = (square, occupied) => fileAttacks(square, occupied).xor(rankAttacks(square, occupied)); var queenAttacks = (square, occupied) => bishopAttacks(square, occupied).xor(rookAttacks(square, occupied)); var attacks = (piece, square, occupied) => { switch (piece.role) { case "pawn": return pawnAttacks(piece.color, square); case "knight": return knightAttacks(square); case "bishop": return bishopAttacks(square, occupied); case "rook": return rookAttacks(square, occupied); case "queen": return queenAttacks(square, occupied); case "king": return kingAttacks(square); } }; var ray = (a, b) => { const other = SquareSet.fromSquare(b); if (RANK_RANGE[a].intersects(other)) return RANK_RANGE[a].with(a); if (ANTI_DIAG_RANGE[a].intersects(other)) return ANTI_DIAG_RANGE[a].with(a); if (DIAG_RANGE[a].intersects(other)) return DIAG_RANGE[a].with(a); if (FILE_RANGE[a].intersects(other)) return FILE_RANGE[a].with(a); return SquareSet.empty(); }; var between = (a, b) => ray(a, b).intersect(SquareSet.full().shl64(a).xor(SquareSet.full().shl64(b))).withoutFirst(); // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/board.js var Board = class _Board { constructor() { } static default() { const board = new _Board(); board.reset(); return board; } /** * Resets all pieces to the default starting position for standard chess. */ reset() { this.occupied = new SquareSet(65535, 4294901760); this.promoted = SquareSet.empty(); this.white = new SquareSet(65535, 0); this.black = new SquareSet(0, 4294901760); this.pawn = new SquareSet(65280, 16711680); this.knight = new SquareSet(66, 1107296256); this.bishop = new SquareSet(36, 603979776); this.rook = new SquareSet(129, 2164260864); this.queen = new SquareSet(8, 134217728); this.king = new SquareSet(16, 268435456); } static empty() { const board = new _Board(); board.clear(); return board; } clear() { this.occupied = SquareSet.empty(); this.promoted = SquareSet.empty(); for (const color of COLORS) this[color] = SquareSet.empty(); for (const role of ROLES) this[role] = SquareSet.empty(); } clone() { const board = new _Board(); board.occupied = this.occupied; board.promoted = this.promoted; for (const color of COLORS) board[color] = this[color]; for (const role of ROLES) board[role] = this[role]; return board; } getColor(square) { if (this.white.has(square)) return "white"; if (this.black.has(square)) return "black"; return; } getRole(square) { for (const role of ROLES) { if (this[role].has(square)) return role; } return; } get(square) { const color = this.getColor(square); if (!color) return; const role = this.getRole(square); const promoted = this.promoted.has(square); return { color, role, promoted }; } /** * Removes and returns the piece from the given `square`, if any. */ take(square) { const piece = this.get(square); if (piece) { this.occupied = this.occupied.without(square); this[piece.color] = this[piece.color].without(square); this[piece.role] = this[piece.role].without(square); if (piece.promoted) this.promoted = this.promoted.without(square); } return piece; } /** * Put `piece` onto `square`, potentially replacing an existing piece. * Returns the existing piece, if any. */ set(square, piece) { const old = this.take(square); this.occupied = this.occupied.with(square); this[piece.color] = this[piece.color].with(square); this[piece.role] = this[piece.role].with(square); if (piece.promoted) this.promoted = this.promoted.with(square); return old; } has(square) { return this.occupied.has(square); } *[Symbol.iterator]() { for (const square of this.occupied) { yield [square, this.get(square)]; } } pieces(color, role) { return this[color].intersect(this[role]); } rooksAndQueens() { return this.rook.union(this.queen); } bishopsAndQueens() { return this.bishop.union(this.queen); } /** * Finds the unique king of the given `color`, if any. */ kingOf(color) { return this.pieces(color, "king").singleSquare(); } }; // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/setup.js var MaterialSide = class _MaterialSide { constructor() { } static empty() { const m = new _MaterialSide(); for (const role of ROLES) m[role] = 0; return m; } static fromBoard(board, color) { const m = new _MaterialSide(); for (const role of ROLES) m[role] = board.pieces(color, role).size(); return m; } clone() { const m = new _MaterialSide(); for (const role of ROLES) m[role] = this[role]; return m; } equals(other) { return ROLES.every((role) => this[role] === other[role]); } add(other) { const m = new _MaterialSide(); for (const role of ROLES) m[role] = this[role] + other[role]; return m; } nonEmpty() { return ROLES.some((role) => this[role] > 0); } isEmpty() { return !this.nonEmpty(); } hasPawns() { return this.pawn > 0; } hasNonPawns() { return this.knight > 0 || this.bishop > 0 || this.rook > 0 || this.queen > 0 || this.king > 0; } size() { return this.pawn + this.knight + this.bishop + this.rook + this.queen + this.king; } }; var Material = class _Material { constructor(white, black) { this.white = white; this.black = black; } static empty() { return new _Material(MaterialSide.empty(), MaterialSide.empty()); } static fromBoard(board) { return new _Material(MaterialSide.fromBoard(board, "white"), MaterialSide.fromBoard(board, "black")); } clone() { return new _Material(this.white.clone(), this.black.clone()); } equals(other) { return this.white.equals(other.white) && this.black.equals(other.black); } add(other) { return new _Material(this.white.add(other.white), this.black.add(other.black)); } count(role) { return this.white[role] + this.black[role]; } size() { return this.white.size() + this.black.size(); } isEmpty() { return this.white.isEmpty() && this.black.isEmpty(); } nonEmpty() { return !this.isEmpty(); } hasPawns() { return this.white.hasPawns() || this.black.hasPawns(); } hasNonPawns() { return this.white.hasNonPawns() || this.black.hasNonPawns(); } }; var RemainingChecks = class _RemainingChecks { constructor(white, black) { this.white = white; this.black = black; } static default() { return new _RemainingChecks(3, 3); } clone() { return new _RemainingChecks(this.white, this.black); } equals(other) { return this.white === other.white && this.black === other.black; } }; // node_modules/.pnpm/@badrap+result@0.2.13/node_modules/@badrap/result/dist/index.modern.mjs var r = class { unwrap(r2, t2) { const e2 = this._chain((t3) => n.ok(r2 ? r2(t3) : t3), (r3) => t2 ? n.ok(t2(r3)) : n.err(r3)); if (e2.isErr) throw e2.error; return e2.value; } map(r2, t2) { return this._chain((t3) => n.ok(r2(t3)), (r3) => n.err(t2 ? t2(r3) : r3)); } chain(r2, t2) { return this._chain(r2, t2 || ((r3) => n.err(r3))); } }; var t = class extends r { constructor(r2) { super(), this.value = void 0, this.isOk = true, this.isErr = false, this.value = r2; } _chain(r2, t2) { return r2(this.value); } }; var e = class extends r { constructor(r2) { super(), this.error = void 0, this.isOk = false, this.isErr = true, this.error = r2; } _chain(r2, t2) { return t2(this.error); } }; var n; !function(r2) { r2.ok = function(r3) { return new t(r3); }, r2.err = function(r3) { return new e(r3 || new Error()); }, r2.all = function(t2) { if (Array.isArray(t2)) { const e3 = []; for (let r3 = 0; r3 < t2.length; r3++) { const n3 = t2[r3]; if (n3.isErr) return n3; e3.push(n3.value); } return r2.ok(e3); } const e2 = {}, n2 = Object.keys(t2); for (let r3 = 0; r3 < n2.length; r3++) { const s = t2[n2[r3]]; if (s.isErr) return s; e2[n2[r3]] = s.value; } return r2.ok(e2); }; }(n || (n = {})); // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/chess.js var IllegalSetup; (function(IllegalSetup2) { IllegalSetup2["Empty"] = "ERR_EMPTY"; IllegalSetup2["OppositeCheck"] = "ERR_OPPOSITE_CHECK"; IllegalSetup2["ImpossibleCheck"] = "ERR_IMPOSSIBLE_CHECK"; IllegalSetup2["PawnsOnBackrank"] = "ERR_PAWNS_ON_BACKRANK"; IllegalSetup2["Kings"] = "ERR_KINGS"; IllegalSetup2["Variant"] = "ERR_VARIANT"; })(IllegalSetup || (IllegalSetup = {})); var PositionError = class extends Error { }; var attacksTo = (square, attacker, board, occupied) => board[attacker].intersect(rookAttacks(square, occupied).intersect(board.rooksAndQueens()).union(bishopAttacks(square, occupied).intersect(board.bishopsAndQueens())).union(knightAttacks(square).intersect(board.knight)).union(kingAttacks(square).intersect(board.king)).union(pawnAttacks(opposite(attacker), square).intersect(board.pawn))); var Castles = class _Castles { constructor() { } static default() { const castles = new _Castles(); castles.unmovedRooks = SquareSet.corners(); castles.rook = { white: { a: 0, h: 7 }, black: { a: 56, h: 63 } }; castles.path = { white: { a: new SquareSet(14, 0), h: new SquareSet(96, 0) }, black: { a: new SquareSet(0, 234881024), h: new SquareSet(0, 1610612736) } }; return castles; } static empty() { const castles = new _Castles(); castles.unmovedRooks = SquareSet.empty(); castles.rook = { white: { a: void 0, h: void 0 }, black: { a: void 0, h: void 0 } }; castles.path = { white: { a: SquareSet.empty(), h: SquareSet.empty() }, black: { a: SquareSet.empty(), h: SquareSet.empty() } }; return castles; } clone() { const castles = new _Castles(); castles.unmovedRooks = this.unmovedRooks; castles.rook = { white: { a: this.rook.white.a, h: this.rook.white.h }, black: { a: this.rook.black.a, h: this.rook.black.h } }; castles.path = { white: { a: this.path.white.a, h: this.path.white.h }, black: { a: this.path.black.a, h: this.path.black.h } }; return castles; } add(color, side, king2, rook2) { const kingTo = kingCastlesTo(color, side); const rookTo = rookCastlesTo(color, side); this.unmovedRooks = this.unmovedRooks.with(rook2); this.rook[color][side] = rook2; this.path[color][side] = between(rook2, rookTo).with(rookTo).union(between(king2, kingTo).with(kingTo)).without(king2).without(rook2); } static fromSetup(setup) { const castles = _Castles.empty(); const rooks = setup.unmovedRooks.intersect(setup.board.rook); for (const color of COLORS) { const backrank = SquareSet.backrank(color); const king2 = setup.board.kingOf(color); if (!defined(king2) || !backrank.has(king2)) continue; const side = rooks.intersect(setup.board[color]).intersect(backrank); const aSide = side.first(); if (defined(aSide) && aSide < king2) castles.add(color, "a", king2, aSide); const hSide = side.last(); if (defined(hSide) && king2 < hSide) castles.add(color, "h", king2, hSide); } return castles; } discardRook(square) { if (this.unmovedRooks.has(square)) { this.unmovedRooks = this.unmovedRooks.without(square); for (const color of COLORS) { for (const side of CASTLING_SIDES) { if (this.rook[color][side] === square) this.rook[color][side] = void 0; } } } } discardColor(color) { this.unmovedRooks = this.unmovedRooks.diff(SquareSet.backrank(color)); this.rook[color].a = void 0; this.rook[color].h = void 0; } }; var Position = class { constructor(rules) { this.rules = rules; } reset() { this.board = Board.default(); this.pockets = void 0; this.turn = "white"; this.castles = Castles.default(); this.epSquare = void 0; this.remainingChecks = void 0; this.halfmoves = 0; this.fullmoves = 1; } setupUnchecked(setup) { this.board = setup.board.clone(); this.board.promoted = SquareSet.empty(); this.pockets = void 0; this.turn = setup.turn; this.castles = Castles.fromSetup(setup); this.epSquare = validEpSquare(this, setup.epSquare); this.remainingChecks = void 0; this.halfmoves = setup.halfmoves; this.fullmoves = setup.fullmoves; } // When subclassing overwrite at least: // // - static default() // - static fromSetup() // - static clone() // // - dests() // - isVariantEnd() // - variantOutcome() // - hasInsufficientMaterial() // - isStandardMaterial() kingAttackers(square, attacker, occupied) { return attacksTo(square, attacker, this.board, occupied); } playCaptureAt(square, captured) { this.halfmoves = 0; if (captured.role === "rook") this.castles.discardRook(square); if (this.pockets) this.pockets[opposite(captured.color)][captured.promoted ? "pawn" : captured.role]++; } ctx() { const variantEnd = this.isVariantEnd(); const king2 = this.board.kingOf(this.turn); if (!defined(king2)) return { king: king2, blockers: SquareSet.empty(), checkers: SquareSet.empty(), variantEnd, mustCapture: false }; const snipers = rookAttacks(king2, SquareSet.empty()).intersect(this.board.rooksAndQueens()).union(bishopAttacks(king2, SquareSet.empty()).intersect(this.board.bishopsAndQueens())).intersect(this.board[opposite(this.turn)]); let blockers = SquareSet.empty(); for (const sniper of snipers) { const b = between(king2, sniper).intersect(this.board.occupied); if (!b.moreThanOne()) blockers = blockers.union(b); } const checkers = this.kingAttackers(king2, opposite(this.turn), this.board.occupied); return { king: king2, blockers, checkers, variantEnd, mustCapture: false }; } clone() { var _a, _b; const pos = new this.constructor(); pos.board = this.board.clone(); pos.pockets = (_a = this.pockets) === null || _a === void 0 ? void 0 : _a.clone(); pos.turn = this.turn; pos.castles = this.castles.clone(); pos.epSquare = this.epSquare; pos.remainingChecks = (_b = this.remainingChecks) === null || _b === void 0 ? void 0 : _b.clone(); pos.halfmoves = this.halfmoves; pos.fullmoves = this.fullmoves; return pos; } validate(opts) { if (this.board.occupied.isEmpty()) return n.err(new PositionError(IllegalSetup.Empty)); if (this.board.king.size() !== 2) return n.err(new PositionError(IllegalSetup.Kings)); if (!defined(this.board.kingOf(this.turn))) return n.err(new PositionError(IllegalSetup.Kings)); const otherKing = this.board.kingOf(opposite(this.turn)); if (!defined(otherKing)) return n.err(new PositionError(IllegalSetup.Kings)); if (this.kingAttackers(otherKing, this.turn, this.board.occupied).nonEmpty()) return n.err(new PositionError(IllegalSetup.OppositeCheck)); if (SquareSet.backranks().intersects(this.board.pawn)) return n.err(new PositionError(IllegalSetup.PawnsOnBackrank)); return (opts === null || opts === void 0 ? void 0 : opts.ignoreImpossibleCheck) ? n.ok(void 0) : this.validateCheckers(); } validateCheckers() { const ourKing = this.board.kingOf(this.turn); if (defined(ourKing)) { const checkers = this.kingAttackers(ourKing, opposite(this.turn), this.board.occupied); if (checkers.nonEmpty()) { if (defined(this.epSquare)) { const pushedTo = this.epSquare ^ 8; const pushedFrom = this.epSquare ^ 24; if (checkers.moreThanOne() || checkers.first() !== pushedTo && this.kingAttackers(ourKing, opposite(this.turn), this.board.occupied.without(pushedTo).with(pushedFrom)).nonEmpty()) return n.err(new PositionError(IllegalSetup.ImpossibleCheck)); } else { if (checkers.size() > 2 || checkers.size() === 2 && ray(checkers.first(), checkers.last()).has(ourKing)) return n.err(new PositionError(IllegalSetup.ImpossibleCheck)); } } } return n.ok(void 0); } dropDests(_ctx) { return SquareSet.empty(); } dests(square, ctx) { ctx = ctx || this.ctx(); if (ctx.variantEnd) return SquareSet.empty(); const piece = this.board.get(square); if (!piece || piece.color !== this.turn) return SquareSet.empty(); let pseudo, legal; if (piece.role === "pawn") { pseudo = pawnAttacks(this.turn, square).intersect(this.board[opposite(this.turn)]); const delta = this.turn === "white" ? 8 : -8; const step2 = square + delta; if (0 <= step2 && step2 < 64 && !this.board.occupied.has(step2)) { pseudo = pseudo.with(step2); const canDoubleStep = this.turn === "white" ? square < 16 : square >= 64 - 16; const doubleStep = step2 + delta; if (canDoubleStep && !this.board.occupied.has(doubleStep)) { pseudo = pseudo.with(doubleStep); } } if (defined(this.epSquare) && canCaptureEp(this, square, ctx)) { const pawn2 = this.epSquare - delta; if (ctx.checkers.isEmpty() || ctx.checkers.singleSquare() === pawn2) { legal = SquareSet.fromSquare(this.epSquare); } } } else if (piece.role === "bishop") pseudo = bishopAttacks(square, this.board.occupied); else if (piece.role === "knight") pseudo = knightAttacks(square); else if (piece.role === "rook") pseudo = rookAttacks(square, this.board.occupied); else if (piece.role === "queen") pseudo = queenAttacks(square, this.board.occupied); else pseudo = kingAttacks(square); pseudo = pseudo.diff(this.board[this.turn]); if (defined(ctx.king)) { if (piece.role === "king") { const occ = this.board.occupied.without(square); for (const to of pseudo) { if (this.kingAttackers(to, opposite(this.turn), occ).nonEmpty()) pseudo = pseudo.without(to); } return pseudo.union(castlingDest(this, "a", ctx)).union(castlingDest(this, "h", ctx)); } if (ctx.checkers.nonEmpty()) { const checker = ctx.checkers.singleSquare(); if (!defined(checker)) return SquareSet.empty(); pseudo = pseudo.intersect(between(checker, ctx.king).with(checker)); } if (ctx.blockers.has(square)) pseudo = pseudo.intersect(ray(square, ctx.king)); } if (legal) pseudo = pseudo.union(legal); return pseudo; } isVariantEnd() { return false; } variantOutcome(_ctx) { return; } hasInsufficientMaterial(color) { if (this.board[color].intersect(this.board.pawn.union(this.board.rooksAndQueens())).nonEmpty()) return false; if (this.board[color].intersects(this.board.knight)) { return this.board[color].size() <= 2 && this.board[opposite(color)].diff(this.board.king).diff(this.board.queen).isEmpty(); } if (this.board[color].intersects(this.board.bishop)) { const sameColor = !this.board.bishop.intersects(SquareSet.darkSquares()) || !this.board.bishop.intersects(SquareSet.lightSquares()); return sameColor && this.board.pawn.isEmpty() && this.board.knight.isEmpty(); } return true; } // The following should be identical in all subclasses toSetup() { var _a, _b; return { board: this.board.clone(), pockets: (_a = this.pockets) === null || _a === void 0 ? void 0 : _a.clone(), turn: this.turn, unmovedRooks: this.castles.unmovedRooks, epSquare: legalEpSquare(this), remainingChecks: (_b = this.remainingChecks) === null || _b === void 0 ? void 0 : _b.clone(), halfmoves: Math.min(this.halfmoves, 150), fullmoves: Math.min(Math.max(this.fullmoves, 1), 9999) }; } isInsufficientMaterial() { return COLORS.every((color) => this.hasInsufficientMaterial(color)); } hasDests(ctx) { ctx = ctx || this.ctx(); for (const square of this.board[this.turn]) { if (this.dests(square, ctx).nonEmpty()) return true; } return this.dropDests(ctx).nonEmpty(); } isLegal(move3, ctx) { if (isDrop(move3)) { if (!this.pockets || this.pockets[this.turn][move3.role] <= 0) return false; if (move3.role === "pawn" && SquareSet.backranks().has(move3.to)) return false; return this.dropDests(ctx).has(move3.to); } else { if (move3.promotion === "pawn") return false; if (move3.promotion === "king" && this.rules !== "antichess") return false; if (!!move3.promotion !== (this.board.pawn.has(move3.from) && SquareSet.backranks().has(move3.to))) return false; const dests = this.dests(move3.from, ctx); return dests.has(move3.to) || dests.has(normalizeMove(this, move3).to); } } isCheck() { const king2 = this.board.kingOf(this.turn); return defined(king2) && this.kingAttackers(king2, opposite(this.turn), this.board.occupied).nonEmpty(); } isEnd(ctx) { if (ctx ? ctx.variantEnd : this.isVariantEnd()) return true; return this.isInsufficientMaterial() || !this.hasDests(ctx); } isCheckmate(ctx) { ctx = ctx || this.ctx(); return !ctx.variantEnd && ctx.checkers.nonEmpty() && !this.hasDests(ctx); } isStalemate(ctx) { ctx = ctx || this.ctx(); return !ctx.variantEnd && ctx.checkers.isEmpty() && !this.hasDests(ctx); } outcome(ctx) { const variantOutcome = this.variantOutcome(ctx); if (variantOutcome) return variantOutcome; ctx = ctx || this.ctx(); if (this.isCheckmate(ctx)) return { winner: opposite(this.turn) }; else if (this.isInsufficientMaterial() || this.isStalemate(ctx)) return { winner: void 0 }; else return; } allDests(ctx) { ctx = ctx || this.ctx(); const d = /* @__PURE__ */ new Map(); if (ctx.variantEnd) return d; for (const square of this.board[this.turn]) { d.set(square, this.dests(square, ctx)); } return d; } play(move3) { const turn = this.turn; const epSquare = this.epSquare; const castling = castlingSide(this, move3); this.epSquare = void 0; this.halfmoves += 1; if (turn === "black") this.fullmoves += 1; this.turn = opposite(turn); if (isDrop(move3)) { this.board.set(move3.to, { role: move3.role, color: turn }); if (this.pockets) this.pockets[turn][move3.role]--; if (move3.role === "pawn") this.halfmoves = 0; } else { const piece = this.board.take(move3.from); if (!piece) return; let epCapture; if (piece.role === "pawn") { this.halfmoves = 0; if (move3.to === epSquare) { epCapture = this.board.take(move3.to + (turn === "white" ? -8 : 8)); } const delta = move3.from - move3.to; if (Math.abs(delta) === 16 && 8 <= move3.from && move3.from <= 55) { this.epSquare = move3.from + move3.to >> 1; } if (move3.promotion) { piece.role = move3.promotion; piece.promoted = !!this.pockets; } } else if (piece.role === "rook") { this.castles.discardRook(move3.from); } else if (piece.role === "king") { if (castling) { const rookFrom = this.castles.rook[turn][castling]; if (defined(rookFrom)) { const rook2 = this.board.take(rookFrom); this.board.set(kingCastlesTo(turn, castling), piece); if (rook2) this.board.set(rookCastlesTo(turn, castling), rook2); } } this.castles.discardColor(turn); } if (!castling) { const capture = this.board.set(move3.to, piece) || epCapture; if (capture) this.playCaptureAt(move3.to, capture); } } if (this.remainingChecks) { if (this.isCheck()) this.remainingChecks[turn] = Math.max(this.remainingChecks[turn] - 1, 0); } } }; var Chess = class extends Position { constructor() { super("chess"); } static default() { const pos = new this(); pos.reset(); return pos; } static fromSetup(setup, opts) { const pos = new this(); pos.setupUnchecked(setup); return pos.validate(opts).map((_) => pos); } clone() { return super.clone(); } }; var validEpSquare = (pos, square) => { if (!defined(square)) return; const epRank = pos.turn === "white" ? 5 : 2; const forward = pos.turn === "white" ? 8 : -8; if (squareRank(square) !== epRank) return; if (pos.board.occupied.has(square + forward)) return; const pawn2 = square - forward; if (!pos.board.pawn.has(pawn2) || !pos.board[opposite(pos.turn)].has(pawn2)) return; return square; }; var legalEpSquare = (pos) => { if (!defined(pos.epSquare)) return; const ctx = pos.ctx(); const ourPawns = pos.board.pieces(pos.turn, "pawn"); const candidates = ourPawns.intersect(pawnAttacks(opposite(pos.turn), pos.epSquare)); for (const candidate of candidates) { if (pos.dests(candidate, ctx).has(pos.epSquare)) return pos.epSquare; } return; }; var canCaptureEp = (pos, pawn2, ctx) => { if (!defined(pos.epSquare)) return false; if (!pawnAttacks(pos.turn, pawn2).has(pos.epSquare)) return false; if (!defined(ctx.king)) return true; const captured = pos.epSquare + (pos.turn === "white" ? -8 : 8); const occupied = pos.board.occupied.toggle(pawn2).toggle(pos.epSquare).toggle(captured); return !pos.kingAttackers(ctx.king, opposite(pos.turn), occupied).intersects(occupied); }; var castlingDest = (pos, side, ctx) => { if (!defined(ctx.king) || ctx.checkers.nonEmpty()) return SquareSet.empty(); const rook2 = pos.castles.rook[pos.turn][side]; if (!defined(rook2)) return SquareSet.empty(); if (pos.castles.path[pos.turn][side].intersects(pos.board.occupied)) return SquareSet.empty(); const kingTo = kingCastlesTo(pos.turn, side); const kingPath = between(ctx.king, kingTo); const occ = pos.board.occupied.without(ctx.king); for (const sq of kingPath) { if (pos.kingAttackers(sq, opposite(pos.turn), occ).nonEmpty()) return SquareSet.empty(); } const rookTo = rookCastlesTo(pos.turn, side); const after = pos.board.occupied.toggle(ctx.king).toggle(rook2).toggle(rookTo); if (pos.kingAttackers(kingTo, opposite(pos.turn), after).nonEmpty()) return SquareSet.empty(); return SquareSet.fromSquare(rook2); }; var pseudoDests = (pos, square, ctx) => { if (ctx.variantEnd) return SquareSet.empty(); const piece = pos.board.get(square); if (!piece || piece.color !== pos.turn) return SquareSet.empty(); let pseudo = attacks(piece, square, pos.board.occupied); if (piece.role === "pawn") { let captureTargets = pos.board[opposite(pos.turn)]; if (defined(pos.epSquare)) captureTargets = captureTargets.with(pos.epSquare); pseudo = pseudo.intersect(captureTargets); const delta = pos.turn === "white" ? 8 : -8; const step2 = square + delta; if (0 <= step2 && step2 < 64 && !pos.board.occupied.has(step2)) { pseudo = pseudo.with(step2); const canDoubleStep = pos.turn === "white" ? square < 16 : square >= 64 - 16; const doubleStep = step2 + delta; if (canDoubleStep && !pos.board.occupied.has(doubleStep)) { pseudo = pseudo.with(doubleStep); } } return pseudo; } else { pseudo = pseudo.diff(pos.board[pos.turn]); } if (square === ctx.king) return pseudo.union(castlingDest(pos, "a", ctx)).union(castlingDest(pos, "h", ctx)); else return pseudo; }; var castlingSide = (pos, move3) => { if (isDrop(move3)) return; const delta = move3.to - move3.from; if (Math.abs(delta) !== 2 && !pos.board[pos.turn].has(move3.to)) return; if (!pos.board.king.has(move3.from)) return; return delta > 0 ? "h" : "a"; }; var normalizeMove = (pos, move3) => { const side = castlingSide(pos, move3); if (!side) return move3; const rookFrom = pos.castles.rook[pos.turn][side]; return { from: move3.from, to: defined(rookFrom) ? rookFrom : move3.to }; }; // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/compat.js var scalachessCharPair = (move3) => isDrop(move3) ? String.fromCharCode(35 + move3.to, 35 + 64 + 8 * 5 + ["queen", "rook", "bishop", "knight", "pawn"].indexOf(move3.role)) : String.fromCharCode(35 + move3.from, move3.promotion ? 35 + 64 + 8 * ["queen", "rook", "bishop", "knight", "king"].indexOf(move3.promotion) + squareFile(move3.to) : 35 + move3.to); // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/fen.js var INITIAL_BOARD_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; var INITIAL_EPD = INITIAL_BOARD_FEN + " w KQkq -"; var INITIAL_FEN = INITIAL_EPD + " 0 1"; var EMPTY_BOARD_FEN = "8/8/8/8/8/8/8/8"; var EMPTY_EPD = EMPTY_BOARD_FEN + " w - -"; var EMPTY_FEN = EMPTY_EPD + " 0 1"; var InvalidFen; (function(InvalidFen2) { InvalidFen2["Fen"] = "ERR_FEN"; InvalidFen2["Board"] = "ERR_BOARD"; InvalidFen2["Pockets"] = "ERR_POCKETS"; InvalidFen2["Turn"] = "ERR_TURN"; InvalidFen2["Castling"] = "ERR_CASTLING"; InvalidFen2["EpSquare"] = "ERR_EP_SQUARE"; InvalidFen2["RemainingChecks"] = "ERR_REMAINING_CHECKS"; InvalidFen2["Halfmoves"] = "ERR_HALFMOVES"; InvalidFen2["Fullmoves"] = "ERR_FULLMOVES"; })(InvalidFen || (InvalidFen = {})); var FenError = class extends Error { }; var nthIndexOf = (haystack, needle, n2) => { let index = haystack.indexOf(needle); while (n2-- > 0) { if (index === -1) break; index = haystack.indexOf(needle, index + needle.length); } return index; }; var parseSmallUint = (str) => /^\d{1,4}$/.test(str) ? parseInt(str, 10) : void 0; var charToPiece = (ch) => { const role = charToRole(ch); return role && { role, color: ch.toLowerCase() === ch ? "black" : "white" }; }; var parseBoardFen = (boardPart) => { const board = Board.empty(); let rank = 7; let file = 0; for (let i = 0; i < boardPart.length; i++) { const c = boardPart[i]; if (c === "/" && file === 8) { file = 0; rank--; } else { const step2 = parseInt(c, 10); if (step2 > 0) file += step2; else { if (file >= 8 || rank < 0) return n.err(new FenError(InvalidFen.Board)); const square = file + rank * 8; const piece = charToPiece(c); if (!piece) return n.err(new FenError(InvalidFen.Board)); if (boardPart[i + 1] === "~") { piece.promoted = true; i++; } board.set(square, piece); file++; } } } if (rank !== 0 || file !== 8) return n.err(new FenError(InvalidFen.Board)); return n.ok(board); }; var parsePockets = (pocketPart) => { if (pocketPart.length > 64) return n.err(new FenError(InvalidFen.Pockets)); const pockets = Material.empty(); for (const c of pocketPart) { const piece = charToPiece(c); if (!piece) return n.err(new FenError(InvalidFen.Pockets)); pockets[piece.color][piece.role]++; } return n.ok(pockets); }; var parseCastlingFen = (board, castlingPart) => { let unmovedRooks = SquareSet.empty(); if (castlingPart === "-") return n.ok(unmovedRooks); for (const c of castlingPart) { const lower = c.toLowerCase(); const color = c === lower ? "black" : "white"; const backrank = SquareSet.backrank(color).intersect(board[color]); let candidates; if (lower === "q") candidates = backrank; else if (lower === "k") candidates = backrank.reversed(); else if ("a" <= lower && lower <= "h") candidates = SquareSet.fromFile(lower.charCodeAt(0) - "a".charCodeAt(0)).intersect(backrank); else return n.err(new FenError(InvalidFen.Castling)); for (const square of candidates) { if (board.king.has(square)) break; if (board.rook.has(square)) { unmovedRooks = unmovedRooks.with(square); break; } } } if (COLORS.some((color) => SquareSet.backrank(color).intersect(unmovedRooks).size() > 2)) return n.err(new FenError(InvalidFen.Castling)); return n.ok(unmovedRooks); }; var parseRemainingChecks = (part) => { const parts = part.split("+"); if (parts.length === 3 && parts[0] === "") { const white = parseSmallUint(parts[1]); const black = parseSmallUint(parts[2]); if (!defined(white) || white > 3 || !defined(black) || black > 3) return n.err(new FenError(InvalidFen.RemainingChecks)); return n.ok(new RemainingChecks(3 - white, 3 - black)); } else if (parts.length === 2) { const white = parseSmallUint(parts[0]); const black = parseSmallUint(parts[1]); if (!defined(white) || white > 3 || !defined(black) || black > 3) return n.err(new FenError(InvalidFen.RemainingChecks)); return n.ok(new RemainingChecks(white, black)); } else return n.err(new FenError(InvalidFen.RemainingChecks)); }; var parseFen = (fen) => { const parts = fen.split(/[\s_]+/); const boardPart = parts.shift(); let board; let pockets = n.ok(void 0); if (boardPart.endsWith("]")) { const pocketStart = boardPart.indexOf("["); if (pocketStart === -1) return n.err(new FenError(InvalidFen.Fen)); board = parseBoardFen(boardPart.slice(0, pocketStart)); pockets = parsePockets(boardPart.slice(pocketStart + 1, -1)); } else { const pocketStart = nthIndexOf(boardPart, "/", 7); if (pocketStart === -1) board = parseBoardFen(boardPart); else { board = parseBoardFen(boardPart.slice(0, pocketStart)); pockets = parsePockets(boardPart.slice(pocketStart + 1)); } } let turn; const turnPart = parts.shift(); if (!defined(turnPart) || turnPart === "w") turn = "white"; else if (turnPart === "b") turn = "black"; else return n.err(new FenError(InvalidFen.Turn)); return board.chain((board2) => { const castlingPart = parts.shift(); const unmovedRooks = defined(castlingPart) ? parseCastlingFen(board2, castlingPart) : n.ok(SquareSet.empty()); const epPart = parts.shift(); let epSquare; if (defined(epPart) && epPart !== "-") { epSquare = parseSquare(epPart); if (!defined(epSquare)) return n.err(new FenError(InvalidFen.EpSquare)); } let halfmovePart = parts.shift(); let earlyRemainingChecks; if (defined(halfmovePart) && halfmovePart.includes("+")) { earlyRemainingChecks = parseRemainingChecks(halfmovePart); halfmovePart = parts.shift(); } const halfmoves = defined(halfmovePart) ? parseSmallUint(halfmovePart) : 0; if (!defined(halfmoves)) return n.err(new FenError(InvalidFen.Halfmoves)); const fullmovesPart = parts.shift(); const fullmoves = defined(fullmovesPart) ? parseSmallUint(fullmovesPart) : 1; if (!defined(fullmoves)) return n.err(new FenError(InvalidFen.Fullmoves)); const remainingChecksPart = parts.shift(); let remainingChecks = n.ok(void 0); if (defined(remainingChecksPart)) { if (defined(earlyRemainingChecks)) return n.err(new FenError(InvalidFen.RemainingChecks)); remainingChecks = parseRemainingChecks(remainingChecksPart); } else if (defined(earlyRemainingChecks)) { remainingChecks = earlyRemainingChecks; } if (parts.length > 0) return n.err(new FenError(InvalidFen.Fen)); return pockets.chain((pockets2) => unmovedRooks.chain((unmovedRooks2) => remainingChecks.map((remainingChecks2) => { return { board: board2, pockets: pockets2, turn, unmovedRooks: unmovedRooks2, remainingChecks: remainingChecks2, epSquare, halfmoves, fullmoves: Math.max(1, fullmoves) }; }))); }); }; var makePiece = (piece) => { let r2 = roleToChar(piece.role); if (piece.color === "white") r2 = r2.toUpperCase(); if (piece.promoted) r2 += "~"; return r2; }; var makeBoardFen = (board) => { let fen = ""; let empty = 0; for (let rank = 7; rank >= 0; rank--) { for (let file = 0; file < 8; file++) { const square = file + rank * 8; const piece = board.get(square); if (!piece) empty++; else { if (empty > 0) { fen += empty; empty = 0; } fen += makePiece(piece); } if (file === 7) { if (empty > 0) { fen += empty; empty = 0; } if (rank !== 0) fen += "/"; } } } return fen; }; var makePocket = (material) => ROLES.map((role) => roleToChar(role).repeat(material[role])).join(""); var makePockets = (pocket) => makePocket(pocket.white).toUpperCase() + makePocket(pocket.black); var makeCastlingFen = (board, unmovedRooks) => { let fen = ""; for (const color of COLORS) { const backrank = SquareSet.backrank(color); let king2 = board.kingOf(color); if (defined(king2) && !backrank.has(king2)) king2 = void 0; const candidates = board.pieces(color, "rook").intersect(backrank); for (const rook2 of unmovedRooks.intersect(candidates).reversed()) { if (rook2 === candidates.first() && defined(king2) && rook2 < king2) { fen += color === "white" ? "Q" : "q"; } else if (rook2 === candidates.last() && defined(king2) && king2 < rook2) { fen += color === "white" ? "K" : "k"; } else { const file = FILE_NAMES[squareFile(rook2)]; fen += color === "white" ? file.toUpperCase() : file; } } } return fen || "-"; }; var makeRemainingChecks = (checks) => `${checks.white}+${checks.black}`; var makeFen = (setup, opts) => [ makeBoardFen(setup.board) + (setup.pockets ? `[${makePockets(setup.pockets)}]` : ""), setup.turn[0], makeCastlingFen(setup.board, setup.unmovedRooks), defined(setup.epSquare) ? makeSquare(setup.epSquare) : "-", ...setup.remainingChecks ? [makeRemainingChecks(setup.remainingChecks)] : [], ...(opts === null || opts === void 0 ? void 0 : opts.epd) ? [] : [Math.max(0, Math.min(setup.halfmoves, 9999)), Math.max(1, Math.min(setup.fullmoves, 9999))] ].join(" "); // node_modules/.pnpm/chessops@0.12.7/node_modules/chessops/san.js var makeSanWithoutSuffix = (pos, move3) => { let san = ""; if (isDrop(move3)) { if (move3.role !== "pawn") san = roleToChar(move3.role).toUpperCase(); san += "@" + makeSquare(move3.to); } else { const role = pos.board.getRole(move3.from); if (!role) return "--"; if (role === "king" && (pos.board[pos.turn].has(move3.to) || Math.abs(move3.to - move3.from) === 2)) { san = move3.to > move3.from ? "O-O" : "O-O-O"; } else { const capture = pos.board.occupied.has(move3.to) || role === "pawn" && squareFile(move3.from) !== squareFile(move3.to); if (role !== "pawn") { san = roleToChar(role).toUpperCase(); let others; if (role === "king") others = kingAttacks(move3.to).intersect(pos.board.king); else if (role === "queen") others = queenAttacks(move3.to, pos.board.occupied).intersect(pos.board.queen); else if (role === "rook") others = rookAttacks(move3.to, pos.board.occupied).intersect(pos.board.rook); else if (role === "bishop") others = bishopAttacks(move3.to, pos.board.occupied).intersect(pos.board.bishop); else others = knightAttacks(move3.to).intersect(pos.board.knight); others = others.intersect(pos.board[pos.turn]).without(move3.from); if (others.nonEmpty()) { const ctx = pos.ctx();