Skip to content
JavaScript chess library running in a webworker, asynchronous and stateless, with multi variants support
Branch: master
Clone or download
Latest commit b6e23d3 Apr 4, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
build Keep build dir Mar 31, 2016
project Update, bump 1.10 Apr 4, 2019
src/main/scala Update, bump 1.10 Apr 4, 2019
submodules Update, bump 1.10 Apr 4, 2019
.gitignore Keep build dir Mar 31, 2016
.gitmodules Update scalachess Jun 23, 2016
LICENSE Initial commit Mar 25, 2016
README.md Update README.md Jun 28, 2018
build.sbt Update, bump 1.10 Apr 4, 2019
build.sh Tweak build Mar 31, 2016
norhino.sbt Link (hacked version of) scalachess Mar 25, 2016
scalachess.js Version 1.9 Oct 31, 2017
test.html Fix test.html Oct 27, 2017

README.md

scalachess.js

scalachess.js is a chess library that runs in a webworker, with multi-variants support.

It is based on the awesome scalachess library compiled to JavaScript, thanks to Scala.js.

It is currently used in production in lichess.org mobile application. So you can see a real world usage on the mobile app repo.

Features

  • Fully asynchronous and does not block the main thread: as it is executed in a web worker, you can safely compute chess logic while running screen animations
  • Completely stateless: you send the complete game position in each request, either with FEN or PGN
  • Built from extensively tested code: the scalachess library is by itself well tested and powers all the chess logic of the thousands of games being played every day on lichess.org.
  • Multi variants support: Chess 960, King Of The Hill, Three-check, Antichess, Atomic chess, Horde, Racing Kings, Crazyhouse!

Build

$ git submodule update --init
$ ./build.sh

Generated file will be in build dir.

API

Message Format

The same format is used for requests and responses:

interface ScalachessMessage {
  topic: string
  payload?: Object
  reqid?: string
}

The worker will always reply with the same topic field of the request. payload is either request or response data. The optional reqid can be used to differentiate responses if you are sending a lot of requests with the same topic at the same time.

Topics

  • init will initialize the board for a given variant
  • dests will get possible destinations for a given position
  • move will play a move for a given position set by FEN; you can optionally pass a pgnMoves array so you can accumulate moves. Useful for other requests like threefoldTest. Pass an uciMoves array if you want to accumulate moves in the UCI format
  • threefoldTest test if current situation falls under the threefold repetition rule. Warning: it can be slow since it has to replay the whole game from the PGN moves
  • pgnRead parse a PGN string a returns the whole game history
  • pgnDump takes an initial FEN, a list of moves and returns a formatted PGN string

An up-to-date and complete API documentation for all topics can be found in the typescript interfaces of lichess mobile client.

Init

Request:

chessWorker.postMessage({
  topic: 'init',
  payload: {
    variant: 'chess960'
  }
});

Response:

{
    "payload": {
        "setup": {
            "check": false,
            "dests": {
              "a2": [ "a3", "a4" ], "b2": [ "b3", "b4" ], "c1": [ "b3", "d3" ], "c2": [ "c3", "c4" ], "d2": [ "d3", "d4" ], "e2": [ "e3", "e4" ], "f2": [ "f3", "f4" ], "g2": [ "g3", "g4" ], "h1": [ "g3" ], "h2": [ "h3", "h4" ]
            },
            "fen": "rbnkbqrn/pppppppp/8/8/8/8/PPPPPPPP/RBNKBQRN w KQkq - 0 1",
            "pgnMoves": [],
            "playable": true,
            "player": "white",
            "ply": 0
        },
        "variant": {
            "key": "chess960",
            "name": "Chess960",
            "shortName": "960",
            "title": "Starting position of the home rank pieces is randomized."
        }
    },
    "topic": "init"
}

Dests

Request:

chessWorker.postMessage({
  topic: 'dests',
  payload: {
    fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
    variant: 'kingOfTheHill'
  }
});

Response:

{
    "payload": {
        "dests": {
            "a2": [ "a3", "a4" ], "b1": [ "a3", "c3" ], "b2": [ "b3", "b4" ], "c2": [ "c3", "c4" ], "d2": [ "d3", "d4" ], "e2": [ "e3", "e4" ], "f2": [ "f3", "f4" ], "g1": [ "f3", "h3" ], "g2": [ "g3", "g4" ], "h2": [ "h3", "h4" ]
        }
    },
    "topic": "dests"
}

Move

Request:

worker.postMessage({
  topic: 'move',
  payload: {
    fen: 'rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2 +0+0',
    variant: 'threeCheck',
    pgnMoves: ['e4', 'e5'],
    uciMoves: ['e2e4', 'e7e5'],
    orig: 'd2',
    dest: 'd4',
    path: '0'
  },
  reqid: '1'
});

Response:

{
    "reqid": "1",
    "path": "0",
    "situation": {
        "check": false,
        "checkCount": {
            "black": 0,
            "white": 0
        },
        "dests": {
            "a7": [ "a6", "a5" ], "b7": [ "b6", "b5" ], "b8": [ "a6", "c6" ], "c7": [ "c6", "c5" ], "d7": [ "d6", "d5" ], "d8": [ "e7", "f6", "g5", "h4" ], "e5": [ "d4" ], "e8": [ "e7" ], "f7": [ "f6", "f5" ], "f8": [ "e7", "d6", "c5", "b4", "a3" ], "g7": [ "g6", "g5" ], "g8": [ "e7", "f6", "h6" ], "h7": [ "h6", "h5" ]
        },
        "end": false,
        "fen": "rnbqkbnr/pppp1ppp/8/4p3/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2 +0+0",
        "pgnMoves": [
            "e4", "e5", "d4"
        ],
        "playable": true,
        "player": "black",
        "ply": 3,
        "uciMoves": [
            "e2e4", "e7e5", "d2d4"
        ],
        "variant": "threeCheck"
    }
}

pgnRead

Request

var pgn = '[Event "Fischer - Spassky World Championship Match"]\n' +
'[Site "Reykjavik ISL"]\n' +
'[Date "1972.08.22"]\n' +
'[EventDate "?"]\n' +
'[Round "17"]\n' +
'[Result "1/2-1/2"]\n' +
'[White "Boris Spassky"]\n' +
'[Black "Robert James Fischer"]\n' +
'[ECO "B09"]\n' +
'[WhiteElo "?"]\n' +
'[BlackElo "?"]\n' +
'[PlyCount "89"]\n' +
'\n' +
'1. e4 d6 2. d4 g6 3. Nc3 Nf6 4. f4 Bg7 5. Nf3 c5 6. dxc5 Qa5\n' +
'7. Bd3 Qxc5 8. Qe2 O-O 9. Be3 Qa5 10. O-O Bg4 11. Rad1 Nc6\n' +
'12. Bc4 Nh5 13. Bb3 Bxc3 14. bxc3 Qxc3 15. f5 Nf6 16. h3 Bxf3\n' +
'17. Qxf3 Na5 18. Rd3 Qc7 19. Bh6 Nxb3 20. cxb3 Qc5+ 21. Kh1\n' +
'Qe5 22. Bxf8 Rxf8 23. Re3 Rc8 24. fxg6 hxg6 25. Qf4 Qxf4\n' +
'26. Rxf4 Nd7 27. Rf2 Ne5 28. Kh2 Rc1 29. Ree2 Nc6 30. Rc2 Re1\n' +
'31. Rfe2 Ra1 32. Kg3 Kg7 33. Rcd2 Rf1 34. Rf2 Re1 35. Rfe2 Rf1\n' +
'36. Re3 a6 37. Rc3 Re1 38. Rc4 Rf1 39. Rdc2 Ra1 40. Rf2 Re1\n' +
'41. Rfc2 g5 42. Rc1 Re2 43. R1c2 Re1 44. Rc1 Re2 45. R1c2\n' +
'1/2-1/2';

worker.postMessage({
  topic: 'pgnRead',
  payload: {
    pgn: pgn
  }
});

Response

{
    "replay": [
        {
            "check": false,
            "checkCount": {
                "black": 0,
                "white": 0
            },
            "dests": {
                "a2": [ "a3", "a4" ], "b1": [ "a3", "c3" ], "b2": [ "b3", "b4" ], "c2": [ "c3", "c4" ], "d2": [ "d3", "d4" ], "e2": [ "e3", "e4" ], "f2": [ "f3", "f4" ], "g1": [ "f3", "h3" ], "g2": [ "g3", "g4" ], "h2": [ "h3", "h4" ]
            },
            "end": false,
            "fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
            "pgnMoves": [],
            "playable": true,
            "player": "white",
            "ply": 0,
            "uciMoves": [],
            "variant": "standard"
        },
        {
            "check": false,
            "checkCount": {
                "black": 0,
                "white": 0
            },
            "dests": {
                "a7": [ "a6", "a5" ], "b7": [ "b6", "b5" ], "b8": [ "a6", "c6" ], "c7": [ "c6", "c5" ], "d7": [ "d6", "d5" ], "e7": [ "e6", "e5" ], "f7": [ "f6", "f5" ], "g7": [ "g6", "g5" ], "g8": [ "f6", "h6" ], "h7": [ "h6", "h5" ]
            },
            "end": false,
            "fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1",
            "pgnMoves": [
                "e4"
            ],
            "playable": true,
            "player": "black",
            "ply": 1,
            "uciMoves": [
                "e2e4"
            ],
            "variant": "standard"
        },
        {
            "check": false,
            "checkCount": {
                "black": 0,
                "white": 0
            },
            "dests": {
                "a2": [ "a3", "a4" ], "b1": [ "a3", "c3" ], "b2": [ "b3", "b4" ], "c2": [ "c3", "c4" ], "d1": [ "e2", "f3", "g4", "h5" ], "d2": [ "d3", "d4" ], "e1": [ "e2" ], "e4": [ "e5" ], "f1": [ "e2", "d3", "c4", "b5", "a6" ], "f2": [ "f3", "f4" ], "g1": [ "f3", "h3", "e2" ], "g2": [ "g3", "g4" ], "h2": [ "h3", "h4" ]
            },
            "end": false,
            "fen": "rnbqkbnr/ppp1pppp/3p4/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2",
            "pgnMoves": [
                "e4",
                "d6"
            ],
            "playable": true,
            "player": "white",
            "ply": 2,
            "uciMoves": [
                "d7d6"
            ],
            "variant": "standard"
        },
        {
            "check": false,
            "checkCount": {
                "black": 0,
                "white": 0
            },
            "dests": {
                "a7": [ "a6", "a5" ], "b7": [ "b6", "b5" ], "b8": [ "d7", "a6", "c6" ], "c7": [ "c6", "c5" ], "c8": [ "d7", "e6", "f5", "g4", "h3" ], "d6": [ "d5" ], "d8": [ "d7" ], "e7": [ "e6", "e5" ], "e8": [ "d7" ], "f7": [ "f6", "f5" ], "g7": [ "g6", "g5" ], "g8": [ "f6", "h6" ], "h7": [ "h6", "h5" ]
            },
            "end": false,
            "fen": "rnbqkbnr/ppp1pppp/3p4/8/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 2",
            "pgnMoves": [
                "e4",
                "d6",
                "d4"
            ],
            "playable": true,
            "player": "black",
            "ply": 3,
            "uciMoves": [
                "d2d4"
            ],
            "variant": "standard"
        },
        ... // till up to ply 89
    ],
    "setup": {
        "check": false,
        "checkCount": {
            "black": 0,
            "white": 0
        },
        "dests": {
            "a2": [ "a3", "a4" ], "b1": [ "a3", "c3" ], "b2": [ "b3", "b4" ], "c2": [ "c3", "c4" ], "d2": [ "d3", "d4" ], "e2": [ "e3", "e4" ], "f2": [ "f3", "f4" ], "g1": [ "f3", "h3" ], "g2": [ "g3", "g4" ], "h2": [ "h3", "h4" ]
        },
        "end": false,
        "fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
        "pgnMoves": [],
        "playable": true,
        "player": "white",
        "ply": 0,
        "uciMoves": [],
        "variant": "standard"
    },
    "variant": {
        "key": "standard",
        "name": "Standard",
        "shortName": "Std",
        "title": "Standard rules of chess (FIDE)"
    }
}
You can’t perform that action at this time.