Giter Club home page Giter Club logo

chessops's People

Contributors

dandaneel avatar dependabot-preview[bot] avatar dependabot[bot] avatar dignissimus avatar eronnen avatar fnogatz avatar franciscobsalgueiro avatar grantjenk avatar hyperbotauthor avatar hyperchessbot avatar kraktus avatar niklasf avatar ornicar avatar schlawg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chessops's Issues

Export everything from a single index.ts.

import ... from '.../something' is not valid in ES6 modules as it mixes package import syntax and relative file import syntax. All exports should be consolidated in a single file that re-exports things from other files. You can either do namespaced re-exports or singular re-exports, depending on what makes the most sense for the project.

// apple.ts
export const apple = 'apple' as const

// banana.ts
export const peel = 'peel' as const

// index.ts
export { apple } from './apple'
export * as banana from './banana'
console.log(apple)
console.log(banana.peel)

Incorrectly parsing variation past end of game as exported from Lichess study

This guy is parsed wrong

https://lichess.org/study/nk2t0m1n/NnOi2xkd

[Event "East Orange"]
[Site "https://lichess.org/study/nk2t0m1n/NnOi2xkd"]
[Date "1957.??.??"]
[White "Fischer, R."]
[Black "Sherwin, J."]
[Result "1-0"]
[Annotator "Fritz 5.00 (90s)"]
[Variant "Standard"]
[ECO "B40"]
[Opening "Sicilian Defense: French Variation"]

1. e4 c5 2. Nf3 e6 3. d3 Nc6 4. g3 Nf6 { out of book } 5. Bg2 Be7 6. O-O O-O 7. Nbd2 Rb8 8. Re1 d6 { Prevents intrusion on e5 } 9. c3 { Consolidates b4+d4 } 9... b6 10. d4 Qc7 11. e5 { This push gains space. White gets in control } 11... Nd5 $16 (11... dxe5!? { and Black can hope to survive } 12. Nxe5 Nxe5 13. dxe5 Nd5) 12. exd6 $14 (12. c4 Ndb4 13. exd6 Qxd6 $16) 12... Bxd6 13. Ne4 c4 14. Nxd6 Qxd6 15. Ng5 Nce7 16. Qc2 { Threatening mate... how? } 16... Ng6 17. h4 Nf6 (17... Rd8 18. Nxh7 Kxh7 19. h5 $18) 18. Nxh7 Nxh7 (18... Kxh7?? 19. Bf4 Qd7 20. Bxb8 $18) 19. h5 Nh4 20. Bf4! { the final nail in the coffin } (20. gxh4?! Bb7 21. Bxb7 Rxb7 $18) 20... Qd8 21. gxh4! { and the rest is history } (21. Bxb8?! Nxg2 22. Kxg2 Bb7+ 23. f3 Qd5) 21... Rb7 22. h6 (22. Bxb7 Bxb7 23. f3 Bxf3 $16) 22... Qxh4 23. hxg7 (23. Bxb7? { fails to } 23... Bxb7 24. f3 Qxf4 25. hxg7 Kxg7 26. Qg2+ Ng5 $19) 23... Kxg7 { White gets strong play along the open h-file } (23... Rd8 24. Bg3 (24. Bxb7? { doesn't work because of } 24... Bxb7 25. Re4 f5 $19) 24... Qh6 25. Re3 $16 (25. Bxb7?! Bxb7 26. f3 Ng5)) 24. Re4 (24. Bxb7? { doesn't work } 24... Bxb7 25. f3 Qxf4 26. Qg2+ Ng5 $19) 24... Qh5 25. Re3 f5? (25... Rh8 26. Rh3 Qb5 27. Rg3+ Kf8 $18) 26. Rh3! { doomsday } (26. Bxb7?! { is much
worse } 26... Bxb7 27. Rg3+ Kh8 $18) 26... Qe8 27. Be5+! { a mean check } 27... Nf6 28. Qd2 { The mate threat is Qh6 } 28... Kf7 29. Qg5 { Do you see the mate threat? } (29. Bxb7?! { is the weaker alternative } 29... Bxb7 30. Bxf6 Kxf6 31. Qh6+ Ke7 $18) 29... Qe7 30. Bxf6 { Threatening mate: Rh7 } 30... Qxf6 31. Rh7+ Ke8 32. Qxf6! { the end of the
story } 32... Rxh7 (32... Rxf6 { is one last hope } 33. Bxb7 Bxb7 34. Rxb7 Rf7 35. Rxf7 Kxf7 $18) 33. Bc6+ { 1-0 Black resigns. } (33... Bd7 34. Qxe6+ Kd8 35. Qd6 Rg8+ 36. Kf1 $18) 1-0

The problem is at the end like here

 33. Bc6+ { 1-0 Black resigns. } (33... Bd7 34. Qxe6+

Bc6+ and Bd7 are in the same children of the parent, instead of Bd7 should be the children of Bc6+

Add board.ascii()?

For debugging purposes in another project I implemented a method that returns a textual representation of a board:

const board = this.#position.board
let color = 0
let s = '  +------------------------+\n'
for (let rank = 8; rank >= 1; rank--) {
  s += `${rank} |`
  for (let file of ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']) {
    const square = parseSquare(file+rank)
    const piece = board.get(square)
    let symbol = ' '
    if (piece) {
      symbol = '\x1B[30m' + pieceSymbol(piece.role, piece.color) + '\x1B[39m'
    }

    if (color) {
      s += `\x1B[42m ${symbol} \x1B[49m`
    } else {
      s += `\x1B[107m ${symbol} \x1B[49m`
    }

    color ^= 1 // switch color
  }

  s += '|\n'
  color ^= 1
}

s += '  +------------------------+\n'
s += '    a  b  c  d  e  f  g  h'

Result:

Screenshot from 2020-07-27 09-57-10

By changing pieceSymbol, this could also use the symbols known from FEN instead. In addition, the colorised output could be optional.

Would you be interested in integrating this directly into chessops? If so, you could simply adapt the code snippet, or I would do so and open a PR. Is there a better way to iterate over the SquareSet.full() in the order a8...h8...h1?

Test for standard material

Add methods like Chess.isStandardMaterial(), Horde.isStandardMaterial(), etc. to check if the current material configuration is reachable from the starting position of the respective variant.

Useful, because Stockfish (and probably other engines) do not guarantee to correctly handle impossible material configurations.

Support for mate eval notation starting with M

It would be nice if the use of "M" followed by the number of moves to mate, was recognized as an alternative to the currently supported "#" notation. So things as [%eval M1] wouldn't be put into the text field.

Chess.hasInsufficientMaterial not entirely correct

I believe it should return true when a color has only the king & one bishop remaining. However, it returns false when both sides have a single bishop provided that the bishops occupy both square colors, even though neither side can mate.

Standard UCI for Castling is not being followed and its reflected on makeUci

Hello!!!

This is the problem, Lichess Board API returns moves in UCI format. I compare those moves with the ones made on the DGT Board which are on SAN. When castling they don't match because lichess returns e1g1 and makeUci returns e1h1

DGT SAN: O-O
lichess UCI: e1g1
chessops UCI: e1h1

The problem may be that parseSan calculates the from property correctly, but from my perspective, incorrectly the to property. Then the makeUci just takes the to and from properties and so, calculates a value for castling that does not follow the UCI standard.
UCI standard says that for castling the correct way is, for Normal Chess, e1g1 . For 960 its may be e1h1
This problem happens on all four castlings.

I don't know if its better to solve this on makeUci or on parseSan, but I do think its worth fixing.

Script to reproduce:

import { Chess, makeUci, NormalMove } from "chessops";
import { parseSan } from "chessops/san";
//Default board
const localBoard = Chess.default();
//Move Object
var moveObject: NormalMove | undefined;
//Ruy-Lopez kind of
var ruyLopez = ['e4','e5','Nf3','Nc6','Bb5','d6','O-O','Bg4','d3','Qd7','Qd2','O-O-O'];
//Conver to UCI
ruyLopez.forEach((sanMove) => {
    console.log(`SAN: ${sanMove}`);
    moveObject = <NormalMove>parseSan(localBoard, sanMove);
    if ( (moveObject) && localBoard.isLegal(moveObject))
    {
        var uciMove = makeUci(moveObject);
        console.log(`UCI: ${uciMove}`);
        console.log(`from: ${moveObject.from} to: ${moveObject.to}`);
        localBoard.play(moveObject);
    }
    console.log();
  });

Script Output:

SAN: e4
UCI: e2e4
from: 12 to: 28

SAN: e5
UCI: e7e5
from: 52 to: 36

SAN: Nf3
UCI: g1f3
from: 6 to: 21

SAN: Nc6
UCI: b8c6
from: 57 to: 42

SAN: Bb5
UCI: f1b5
from: 5 to: 33

SAN: d6
UCI: d7d6
from: 51 to: 43

SAN: O-O
UCI: e1h1
from: 4 to: 7

SAN: Bg4
UCI: c8g4
from: 58 to: 30

SAN: d3
UCI: d2d3
from: 11 to: 19

SAN: Qd7
UCI: d8d7
from: 59 to: 51

SAN: Qd2
UCI: d1d2
from: 3 to: 11

SAN: O-O-O
UCI: e8a8
from: 60 to: 56

Have a parseCastlingFen equivalent for enPassant

Currently in chessops, you have to setup a whole Position to know if a legalEpSquare exist. I think it may be logical to have a parseCastlingFen equivalent for enPassant; something like parseEnPassantFen(board, turnPart, epPart), returning a square if valid or undefined if the epPart isn't valid (e.g. 'd3' but no pawn or missing other pawn on the board).

I'm creating this issue as I'm doing lichess-org/lila#14139, and you need to wait for the "legalFen" [editor/src/ctrl.ts] to be created, which uses getSetup, which call parseFen & parseCastlingFen; so it is weird to not be able to valid enPassant there (while it is done with castling).

I also notice that in [email protected], only the fact that the square exist is validated, but not it's en-passant properties.

What do you think of it ?

edit : also possibly a way of getting all epSquares possibility.

Fen with repeated tokens in castling field accepted as valid

E.g. rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KKqq - 0 1

Considering that a field like qK is rejected even though it could easily be accepted, I think it would be consistent to reject other technically invalid fields unless it's needed to accommodate variants.

multiple FEN only PGNs are counted as one

Take a text like:

[FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/2BQKBNR w Kkq - 0 1"]

[FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/2BQKBNR w Kkq - 0 1"]

Is this two games, or just one?

In the current implementation, multiple PGN tags one after the other are considered part of the same game. However, multiple FEN tags should not be valid. There is the workaround to add a * as a "move" in which case parsePgn will see two games.

Add cjs target

This issue is just to gauge your interest in accepting a PR that would do two things:

  1. Create a cjs target in addition to esm
  2. Move built javascripts to dist/cjs, dist/esm and remap imports in package.json.

And if you're not interested in 1 (I don't blame you), we could still do 2.

PGN parsing and writing

Is it something that could be part of this library ?

Btw thank you for this awesome lib! :)

SAN and/or UCI move list parsing

I'm developing Lichess Statbot to allow Discord users easy access to Lichess APIs. I'm considering expanding use of one of its text-based commands:
/eval [fen] [play] using chessops-powered move validation (and maybe even SAN-to-UCI translation; fen optional; play optional)

Move validation and translation are challenging!

I guess users would prefer to simply type /eval d4 Nf6 rather than typing or copying a FEN; but sometimes users might copy a FEN and provide additional moves e.g. /eval rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2 Nf3 Nc6 f1d3 g8f6 O-O.

Background:
Currently /eval rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2 displays:

FEN parsing and validation: regression or refactoring?

Hi,

I'm using chessops and would like to share my experience, with the hope of better understanding this awesome library, but also to report on potential issues.

Let us consider this program:

import { parseFen } from 'chessops/fen';
import { Chess } from 'chessops/chess';

const setup = parseFen('1b1B4/1P6/2B5/3r2kN/2r4P/8/5K2/3B4 b').unwrap();
const pos = Chess.fromSetup(setup).unwrap();
console.log(pos);

The position is "illegal": https://lichess.org/analysis/standard/1b1B4/1P6/2B5/3r2kN/2r4P/8/5K2/3B4_b
since there are two checks, coming from a pawn and a bishop.
It's simply impossible in practice.

However, when you execute this piece of code with chessops >= 0.12.0 eg "chessops": "^0.12.0"
It's fine!

When you execute this code with chessops < 0.12.0 like "chessops": "^0.11.0", there is an error

You can play with code/versions here: https://replit.com/@acherm/FENChess

/home/runner/FENChess/node_modules/chessops/src/chess.ts:496
            return Result.err(new PositionError(IllegalSetup.ImpossibleCheck));
                              ^
PositionError: ERR_IMPOSSIBLE_CHECK
    at Chess.validateCheckers (/home/runner/FENChess/node_modules/chessops/src/chess.ts:496:31)
    at Chess.validate (/home/runner/FENChess/node_modules/chessops/src/chess.ts:470:70)
    at Function.fromSetup (/home/runner/FENChess/node_modules/chessops/src/chess.ts:449:16)
    at <anonymous> (/home/runner/FENChess/index.ts:5:19)
    at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
    at async loadESM (node:internal/process/esm_loader:91:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)

Node.js v18.12.1

I come to this conclusion through tries and errors, but also looking at @lichess-org
and specifically at https://github.com/lichess-org/lila/blob/master/ui/ceval/src/ctrl.ts#L64-L70
https://github.com/lichess-org/lila/blob/master/ui/ceval/src/view.ts#L29
that somehow explains why there is the message "Engine cannot analyze this position" when loading the FEN.

I also looked at the history of chessops and closed to 0.12.0 there is a huge refactoring that makes disappear the use of validateCheckers
77b905f

It has been moved in isImpossibleCheck but this function is never called... except in test cases!

I have spent a lot of time getting the source of error and my conclusion is that you should do something like:

const fenPosCase = parseFen("1b1B4/1P6/2B5/3r2kN/2r4P/8/5K2/3B4 b").chain(setup => setupPosition("chess", setup));
if (fenPosCase.isErr) {
    console.log("FEN position is non valid....");    
}
else {
    console.log("FEN position is valid");
    if (isImpossibleCheck(fenPosCase.unwrap())) {
        console.log("but impossible check!");
    }    
}

However, I highly suggest it should be documented somewhere.
An open and interested question (not a critic!) is to understand the rationale:

  • Is it a bug? (ie, isImpossibleCheck should be called in the validate function)
  • Is it a feature? (ie, you want to speed up the parsing, or you want to plug validation rules only when needs be).

Another related point: I don't understand why lichess.org does get it's an illegal position, despite using 0.12.7 https://github.com/lichess-org/lila/blob/master/ui/ceval/package.json#L19C1-L19C27 that is not supposed to catch the issue.
Or did I miss something?
Perhaps the deployed version of lichess.org is actually using an older version of chessops, cc @ornicar

Final point: I also come across this nice project https://github.com/tromp/ChessPositionRanking/blob/main/src/Legality.hs that apparently checks doubles checks https://github.com/tromp/ChessPositionRanking/blob/main/src/Legality.hs#L88-L90
and that found numerous illegal FEN positions

New issue on capture with SAN format

Hello!!!

I think there is still a bug when capturing pawns using SAN format.
So this happened on a real game played with the DGT Board. I tested also with this code on latest chessops (v.0.7.2)
Here is a sample code to reproduce the issue:

Basically on the position on the sample, when the move "bxc6" is played, it returns an undefined object.
If the same move is played using UCI then it works fine.

// https://lichess.org/editor/r4br1/pp1Npkp1/2P4p/5P2/6P1/5KnP/PP6/R1B5_b_-_-_0_1
import { parseFen } from 'chessops/fen';
import { Chess } from 'chessops/chess';
import { board } from 'chessops/debug';
import { NormalMove, parseUci } from 'chessops';
import { parseSan } from 'chessops/san';

const setup = parseFen('r4br1/pp1Npkp1/2P4p/5P2/6P1/5KnP/PP6/R1B5 b - - 0 1').unwrap();
const localBoard = Chess.fromSetup(setup).unwrap();
console.log(board(localBoard.board));
var nextMove = 'bxc6';
//var nextMove = 'b6';
var moveObject: NormalMove | undefined;
moveObject = <NormalMove>parseSan(localBoard, nextMove);

if (moveObject !== undefined) {
    //This never happens
    console.log('moveObj looks good');
}
else {
    //Now try the uci version
    console.log('moveObj is undefined');
    console.log('bxc6 was invalid lets try with b7c6 \n')
    moveObject = <NormalMove>parseUci('b7c6');    
}

localBoard.play(moveObject);
console.log(board(localBoard.board));

Output

r . . . . b r .
p p . N p k p .
. . P . . . . p
. . . . . P . .
. . . . . . P .
. . . . . K n P
P P . . . . . .
R . B . . . . .

moveObj is undefined
bxc6 was invalid lets try with b7c6

r . . . . b r .
p . . N p k p .
. . p . . . . p
. . . . . P . .
. . . . . . P .
. . . . . K n P
P P . . . . . .
R . B . . . . .

How to write PGN back to a file

I want functions to write the PGN back to file, possible a PGN that I constructed myself. Also one function to merge two PGN's together into a single PGN with variations.

Default initial setup includes short castling as destination

Chess.default().allDests()

includes short castling as destination for white:

image

I just printed out destinations:

const pos = { current: Chess.default() }
const dests = {};
  for (const [from, squares] of pos.current.allDests()) {
    const fromDests = [];
    for (const square of squares) {
      fromDests.push(makeSquare(square));
      console.table({
        from: { square: from, key: makeSquare(from) },
        to: { square: square, key: makeSquare(square) }
      });
    }
    dests[makeSquare(from)] = fromDests;
  }

En passant target squares not properly generated in FEN EPD

Hello,

Thanks for writing this library, it's extremely useful! The only thing I'm having trouble with is that it doesn't seem like en passant target squares are correctly handled by the FEN generation code. Here's an example:

import {Chess} from "chessops/chess";
import {makeFen} from "chessops/fen";
import {parseSan} from "chessops/san";

describe("When generating FEN", () => {
    test("en passant target square is in EPD", () => {
        const chess = Chess.default();
        chess.play(parseSan(chess, "a4"));
        expect(makeFen(chess.toSetup())).toBe("rnbqkbnr/pppppppp/8/8/P7/8/1PPPPPPP/RNBQKBNR b KQkq a3 0 1");
    });
}

which fails with:

Error: expect(received).toBe(expected) // Object.is equality

Expected: "rnbqkbnr/pppppppp/8/8/P7/8/1PPPPPPP/RNBQKBNR b KQkq a3 0 1"
Received: "rnbqkbnr/pppppppp/8/8/P7/8/1PPPPPPP/RNBQKBNR b KQkq - 0 1"

Problem parsing pawn captures in san format

I am unable to make parseSan work
I noticed that "xd5" will work bu "exd5" will throw an error. The problem is that when there is two pawns attacking one pawn I don't know to tell the parseSan function which column to use. Also I think that standard san is exd5 and should work without the need of removing any prefix.

This code will exemplify my point: (TypeScript)

import { parseFen, INITIAL_BOARD_FEN } from 'chessops/fen';
import { Chess } from 'chessops/chess';
import { board } from 'chessops/debug';
import { parseSan } from 'chessops/san';

const locaBoard = parseFen(INITIAL_BOARD_FEN).unwrap();
const currentPostiion = Chess.fromSetup(locaBoard).unwrap();
var sanMove = parseSan(currentPostiion, "e4");


//LiveChess return thing like hxg4 to signal pawn on h column takes on g4
if (sanMove) currentPostiion.play(sanMove);
sanMove = parseSan(currentPostiion, "d5");
if (sanMove) currentPostiion.play(sanMove);
sanMove = parseSan(currentPostiion, "c4");
if (sanMove) currentPostiion.play(sanMove);
sanMove = parseSan(currentPostiion, "nf6");
if (sanMove) currentPostiion.play(sanMove);

//Everything goes fine
console.log(sanMove);
console.log(board(currentPostiion.board));

//But this will fail
sanMove = parseSan(currentPostiion, "exd5");
if (sanMove) currentPostiion.play(sanMove);
console.log(sanMove);
console.log(board(currentPostiion.board));

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.