├── artifacts
├── engine_selftest.txt
├── windows
│ └── wasabi.cmd
├── linux
│ └── wasabi
├── wavm_checksums.txt
└── release_notes.md
├── tools
├── tuning
│ ├── .gitignore
│ └── config_example.yml
├── bookgen
│ ├── Pipfile
│ ├── util.py
│ ├── zobrist.py
│ ├── Pipfile.lock
│ └── bookgen.py
└── gen_version_ts.js
├── public
├── robots.txt
├── favicon.png
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── screenshots
├── chess_mobile.png
└── chess_screenshot.png
├── version.ts
├── assembly
├── tsconfig.json
├── io
│ ├── browser
│ │ ├── clock.ts
│ │ └── stdio.ts
│ ├── wasi
│ │ ├── clock.ts
│ │ ├── abort.ts
│ │ └── stdio.ts
│ └── index.ts
├── __tests__
│ ├── opening-book.spec.ts
│ ├── perft.performance.ts
│ ├── random.spec.ts
│ ├── history.spec.ts
│ ├── move-notation.spec.ts
│ ├── history-heuristics.spec.ts
│ ├── transposition-table.spec.ts
│ ├── engine.performance.ts
│ ├── move-ordering.ts
│ ├── bitboard.spec.ts
│ ├── perft.spec.ts
│ ├── util.spec.ts
│ └── engine.spec.ts
├── random.ts
├── history.ts
├── opening-book.ts
├── perft.ts
├── move-ordering.ts
├── pieces.ts
├── util.ts
├── transposition-table.ts
├── history-heuristics.ts
├── uci-move-notation.ts
├── index.ts
├── fen.ts
└── bitboard.ts
├── as-pect-perft.config.js
├── as-pect.config.js
├── .github
└── workflows
│ ├── test.yml
│ └── release.yml
├── src
├── index.css
├── index.js
├── ui
│ ├── img
│ │ ├── white_rook.svg
│ │ ├── black_rook.svg
│ │ ├── black_pawn.svg
│ │ ├── white_pawn.svg
│ │ ├── white_knight.svg
│ │ ├── black_bishop.svg
│ │ ├── white_bishop.svg
│ │ ├── black_knight.svg
│ │ ├── black_king.svg
│ │ ├── white_king.svg
│ │ ├── white_queen.svg
│ │ └── black_queen.svg
│ ├── App.js
│ ├── Board.js
│ ├── PromotionPieceSelection.js
│ ├── AnimatedSpinner.js
│ ├── Piece.js
│ ├── Field.js
│ ├── GameMenu.js
│ └── Game.js
└── engine
│ ├── constants.js
│ ├── move.js
│ └── engine.worker.js
├── .gitignore
├── package.json
└── README.md
/artifacts/engine_selftest.txt:
--------------------------------------------------------------------------------
1 | test
2 | quit
3 |
--------------------------------------------------------------------------------
/tools/tuning/.gitignore:
--------------------------------------------------------------------------------
1 | config.yml
2 | tuning_result.yml
3 |
--------------------------------------------------------------------------------
/artifacts/windows/wasabi.cmd:
--------------------------------------------------------------------------------
1 | .\wavm\bin\wavm.exe run .\engine.wasm
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhonert/chess/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhonert/chess/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhonert/chess/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/screenshots/chess_mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhonert/chess/HEAD/screenshots/chess_mobile.png
--------------------------------------------------------------------------------
/screenshots/chess_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mhonert/chess/HEAD/screenshots/chess_screenshot.png
--------------------------------------------------------------------------------
/version.ts:
--------------------------------------------------------------------------------
1 | // Auto-generated file: to update the version use 'npm version ...'
2 | export const VERSION="1.5.1";
--------------------------------------------------------------------------------
/artifacts/linux/wasabi:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | SCRIPT_DIR=$(dirname $(readlink -f $0))
3 | $SCRIPT_DIR/wavm/bin/wavm run $SCRIPT_DIR/engine.wasm
4 |
--------------------------------------------------------------------------------
/assembly/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../node_modules/assemblyscript/std/assembly.json",
3 | "include": [
4 | "./**/*.ts",
5 | "../node_modules/@as-pect/assembly/types/as-pect.d.ts"
6 | ]
7 | }
--------------------------------------------------------------------------------
/artifacts/wavm_checksums.txt:
--------------------------------------------------------------------------------
1 | a0542e95d378a86ae73609acfe0208ef2ef70bd3d5645946b73b8b18a9be6e84 ./artifacts/wavm-windows.zip
2 | 10cec94139b2ebf18792ae8b0e8e8780be63f938f188332ef96367c20f9a74e6 ./artifacts/wavm-linux.tar.gz
3 |
--------------------------------------------------------------------------------
/tools/bookgen/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | numpy = "*"
8 | chess = "*"
9 |
10 | [dev-packages]
11 |
12 | [requires]
13 | python_version = "3.9"
14 |
--------------------------------------------------------------------------------
/tools/gen_version_ts.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const packageJson = require('../package.json');
3 | fs.writeFileSync('version.ts',
4 | `// Auto-generated file: to update the version use 'npm version ...'
5 | export const VERSION="${packageJson.version}";`);
6 |
--------------------------------------------------------------------------------
/as-pect-perft.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | include: ["assembly/__tests__/**/*.performance.ts"],
3 | flags: {
4 | "--runtime": ["incremental"],
5 | "--use": ["IS_WASI=0","ASC_RTRACE=1"],
6 | "--optimizeLevel": "2",
7 | "--shrinkLevel": "0",
8 | "--converge": [],
9 | },
10 | disclude: [/node_modules/],
11 | outputBinary: false,
12 | };
13 |
--------------------------------------------------------------------------------
/as-pect.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | include: ["assembly/__tests__/**/*.spec.ts"],
3 | add: ["assembly/__tests__/**/*.include.ts"],
4 | flags: {
5 | "--runtime": ["incremental"], // Acceptable values are: incremental, minimal and stub
6 | "--use": ["IS_WASI=0","ASC_RTRACE=1"]
7 | },
8 | disclude: [/node_modules/],
9 | imports: {},
10 | outputBinary: false,
11 | };
12 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - name: Checkout code
12 | uses: actions/checkout@v2
13 |
14 | - name: Setup Node
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: '14.x'
18 |
19 | - name: Run tests
20 | env:
21 | CI: true
22 | run: |
23 | npm ci
24 | npm test
25 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | background-color: white;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
16 | * {
17 | box-sizing: border-box;
18 | }
19 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Chess",
3 | "name": "Chess",
4 | "icons": [
5 | {
6 | "src": "favicon.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "sizes": "192x192",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "sizes": "512x512",
18 | "type": "image/png"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .idea
26 |
27 | # caches
28 | **/__pycache__
29 |
30 | # Generated as-pect type definition file
31 | as-pect.d.ts
32 |
33 | # Generated wasm binary and source map
34 | public/as-api.wasm
35 | public/as-api.wasm.map
36 |
37 | # PGN files
38 | **/pgn/*
39 |
40 | # FEN files
41 | **/fen/*
42 |
--------------------------------------------------------------------------------
/tools/tuning/config_example.yml:
--------------------------------------------------------------------------------
1 | engine:
2 | cmd: /absolutePath/to/chess-engine
3 |
4 | options:
5 | debug_log: false
6 | test_positions_file: fen/quiet.fen
7 | concurrency: 8
8 |
9 | tuning:
10 | - name: QueenValue
11 | value: 950
12 |
13 | - name: RookValue
14 | value: 500
15 |
16 | - name: BishopValue
17 | value: 350
18 |
19 | - name: KnightValue
20 | value: 350
21 |
22 | - name: PawnValue
23 | value: 100
24 |
25 | # Options with list values will be send to the engine as:
26 | # set option KnightMobBonus0 value 0
27 | # set option KnightMobBonus1 value 0
28 | # set option KnightMobBonus2 value 0
29 | # ...
30 | - name: KnightMobBonus
31 | value: [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
32 |
--------------------------------------------------------------------------------
/assembly/io/browser/clock.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | @inline
20 | export function currentMillis(): i64 {
21 | return Date.now();
22 | }
23 |
--------------------------------------------------------------------------------
/artifacts/release_notes.md:
--------------------------------------------------------------------------------
1 |
2 | This release focussed on tuning the evaluation function :balance_scale:
3 | > The Web App version of **Wasabi Chess** can be played [**here**](https://mhonert.github.io/chess).
4 |
5 | ## Changes
6 | - New tuning tool
7 | - Tuned all evaluation parameters
8 | - Replaced simple mobility score calculation with mobility score table
9 | - Replaced simple king safety calculation with king threat score table
10 | - Improved game phase calculation for tapered eval
11 | - Added endgame piece square tables
12 |
13 | ## Installation
14 | - Download and unpack the archive for your platform (Linux or Windows 8/10)
15 | - Configure your UCI client (e.g. PyChess, Arena, cutechess)
16 | - … on Windows ⇒ to run *wasabi.cmd*
17 | - … on Linux ⇒ to run *wasabi*
18 |
19 | > :warning: the bundled **WAVM** runtime is not compatible with Windows 7 - a workaround is described in the [Wiki](https://github.com/mhonert/chess/wiki/Windows-7)
20 |
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React from 'react';
20 | import ReactDOM from 'react-dom';
21 | import './index.css';
22 | import App from './ui/App';
23 |
24 | ReactDOM.render(, document.getElementById('root'));
25 |
--------------------------------------------------------------------------------
/src/ui/img/white_rook.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/engine/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | export const BLACK = -1;
20 | export const WHITE = 1;
21 |
22 | export const P = 1; // pawn
23 | export const N = 2; // knight
24 | export const B = 3; // bishop
25 | export const R = 4; // rook
26 | export const Q = 5; // queen
27 | export const K = 6; // king
28 |
--------------------------------------------------------------------------------
/assembly/io/browser/stdio.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | export function writeLine(message: string): void {
20 | trace(message);
21 | }
22 |
23 | export function writeError(message: string): void {
24 | trace(message);
25 | }
26 |
27 | export function readLine(maxLength: i32): string {
28 | throw new Error("readLine not supported in browser");
29 | }
--------------------------------------------------------------------------------
/assembly/io/wasi/clock.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { clock_time_get, clockid } from 'bindings/wasi';
20 |
21 | @inline
22 | export function currentMillis(): i64 {
23 | let time_ptr = changetype(new ArrayBuffer(8));
24 | clock_time_get(clockid.MONOTONIC, 1_000_000, time_ptr);
25 | return load(time_ptr) / 1_000_000;
26 | }
27 |
--------------------------------------------------------------------------------
/assembly/io/wasi/abort.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { writeError } from './stdio';
20 | import { proc_exit } from 'bindings/wasi';
21 |
22 | // Custom abort function
23 | @global
24 | export function _abort(message: string = "", file: string = "", line: u32 = 0, column: u32 = 0): void {
25 | writeError("Fatal error occured!");
26 | writeError(file + ": line " + line.toString() + "@" + column.toString() + " - error: " + message);
27 | proc_exit(128);
28 | }
29 |
--------------------------------------------------------------------------------
/src/engine/move.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | export class Move {
20 | constructor(piece, start, end) {
21 | this.start = start;
22 | this.end = end;
23 | this.encodedMove = Math.abs(piece) | (start << 3) | (end << 10);
24 | }
25 |
26 | static fromEncodedMove(encodedMove) {
27 | const piece = encodedMove & 0x7; // Bits 0-2
28 | const start = (encodedMove >> 3) & 0x7F; // Bits 3-10
29 | const end = (encodedMove >> 10) & 0x7F; // Bit 10-17
30 |
31 | return new Move(piece, start, end);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/assembly/io/wasi/stdio.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { Console } from "as-wasi";
20 |
21 | export function writeLine(str: string): void {
22 | Console.log(str);
23 | }
24 |
25 | export function writeError(str: string): void {
26 | Console.error(str);
27 | }
28 |
29 | // Reads characters one by one until a line feed character occurs or the stream ended
30 | export function readLine(): string {
31 | const line = Console.readLine();
32 | if (line === null) {
33 | return "";
34 | }
35 |
36 | return line as string;
37 | }
38 |
--------------------------------------------------------------------------------
/assembly/__tests__/opening-book.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { fromFEN, STARTPOS } from '../fen';
20 | import { isValidMove } from '../move-generation';
21 | import { findOpeningMove } from '../opening-book';
22 |
23 | describe("Find moves from opening book", () => {
24 | it("Finds moves for multiple plies", () => {
25 | const board = fromFEN(STARTPOS);
26 |
27 | for (let ply = 1; ply <= 4; ply++) {
28 | const openingMove = findOpeningMove(board);
29 | expect(isValidMove(board, board.getActivePlayer(), openingMove)).toBeTruthy("No move found for ply #" + ply.toString())
30 | board.performEncodedMove(openingMove);
31 | }
32 | })
33 | });
34 |
--------------------------------------------------------------------------------
/assembly/random.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | const multiplier: u64 = 6364136223846793005;
20 | const increment: u64 = 1442695040888963407;
21 |
22 | // Create pseudo random numbers using a "Permuted Congruential Generator" (see https://en.wikipedia.org/wiki/Permuted_congruential_generator)
23 | export class Random {
24 | state: u64 = 0x4d595df4d0f33173;
25 |
26 | rand32(): u32 {
27 | let x = this.state;
28 | const count: u32 = u32(x >> u64(59));
29 | this.state = x * multiplier + increment;
30 | x ^= x >> 18;
31 |
32 | return rotr(u32(x >> 27), count);
33 | }
34 |
35 | rand64(): u64 {
36 | return (u64(this.rand32()) << 32) | u64(this.rand32());
37 | }
38 |
39 | updateSeed(seed: u64): void {
40 | this.state = seed * multiplier + increment;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/assembly/io/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import {currentMillis as wasiCurrentMillis} from './wasi/clock';
20 | import {currentMillis as browserCurrentMillis} from './browser/clock';
21 |
22 | import {readLine as wasiReadLine, writeLine as wasiWriteLine, writeError as wasiWriteError} from './wasi/stdio';
23 | import {readLine as browserReadLine, writeLine as browserWriteLine, writeError as browserWriteError} from './browser/stdio';
24 |
25 | export namespace clock {
26 | export const currentMillis = (IS_WASI == 1 ? wasiCurrentMillis : browserCurrentMillis);
27 | }
28 |
29 | export namespace stdio {
30 | export const readLine = (IS_WASI == 1 ? wasiReadLine : browserReadLine);
31 | export const writeLine = (IS_WASI == 1 ? wasiWriteLine : browserWriteLine);
32 | export const writeError = (IS_WASI == 1 ? wasiWriteError : browserWriteError);
33 | }
--------------------------------------------------------------------------------
/src/ui/img/black_rook.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assembly/__tests__/perft.performance.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { fromFEN } from '../fen';
20 | import { perft } from '../perft';
21 |
22 |
23 | describe('Perft - Performance Test', () => {
24 | it('calculates correct moves for performance test', () => {
25 | expect(measurePerft("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 6)).toBe(119_060_324);
26 | expect(measurePerft("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", 5)).toBe(193_690_690);
27 | });
28 | });
29 |
30 | function measurePerft(fen: string, depth: i32): u64 {
31 | const board = fromFEN(fen);
32 |
33 | const start = Date.now();
34 | const result = perft(board, depth);
35 |
36 | const duration = Date.now() - start;
37 | const nodesPerSecond = result * 1000 / duration;
38 |
39 | trace(fen + ": Duration (ms) : " + duration.toString());
40 | trace(fen + ": Nodes per second: " + nodesPerSecond.toString());
41 |
42 | return result;
43 | }
44 |
45 |
46 |
--------------------------------------------------------------------------------
/assembly/__tests__/random.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | import { Random } from '../random';
21 |
22 | describe("Random number generated", () => {
23 |
24 | it("quickly calculates evenly distributed random numbers", () => {
25 | const rnd = new Random();
26 |
27 | const numberCounts = new Array(6);
28 | numberCounts.fill(0, 0, numberCounts.length);
29 |
30 | const iterations = 1_000_000;
31 |
32 | for (let i = 0; i < iterations; i++) {
33 | const number = u32(rnd.rand64() % 6);
34 | numberCounts[number]++;
35 | }
36 |
37 | const deviationTolerance = i32(iterations * 0.001); // accept a low deviation from the "ideal" distribution
38 |
39 | const idealDistribution = iterations / 6;
40 | for (let i = 0; i < numberCounts.length; i++) {
41 | const deviationFromIdeal = abs(idealDistribution - numberCounts[i]);
42 | expect(deviationFromIdeal).toBeLessThan(deviationTolerance);
43 | }
44 | });
45 |
46 | });
47 |
48 |
--------------------------------------------------------------------------------
/assembly/__tests__/history.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | import { PositionHistory } from '../history';
21 |
22 | describe("Position History", () => {
23 |
24 | it("Detects threefold repetition", () => {
25 | const history = new PositionHistory();
26 | history.push(1);
27 | history.push(1);
28 | expect(history.isThreefoldRepetion()).toBeFalsy("Position only occured twice");
29 |
30 | history.push(1);
31 | expect(history.isThreefoldRepetion()).toBeTruthy("Threefold repetion not detected");
32 | });
33 |
34 | it("Detects single repetition", () => {
35 | const history = new PositionHistory();
36 | history.push(1);
37 | expect(history.isSingleRepetition()).toBeFalsy("Position only occured once");
38 | history.push(2);
39 | expect(history.isSingleRepetition()).toBeFalsy("Position only occured once");
40 | history.push(1);
41 | expect(history.isSingleRepetition()).toBeTruthy("Single repetition not detected");
42 | });
43 |
44 | });
45 |
--------------------------------------------------------------------------------
/assembly/__tests__/move-notation.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { QUEEN } from '../pieces';
20 | import { UCIMove } from '../uci-move-notation';
21 |
22 | describe("UCIMove.fromUCINotation", () => {
23 | it("reads standard move", () => {
24 | const move = UCIMove.fromUCINotation("e2e4");
25 | expect(move.start).toBe(52, "start")
26 | expect(move.end).toBe(36, "end");
27 | expect(move.promotionPiece).toBe(0, "promotion piece");
28 | });
29 |
30 | it("reads promotion move", () => {
31 | const move = UCIMove.fromUCINotation("a7a8q");
32 | expect(move.start).toBe(8, "start")
33 | expect(move.end).toBe(0, "end");
34 | expect(move.promotionPiece).toBe(QUEEN, "promotion piece");
35 | });
36 | });
37 |
38 | describe("UCIMove.toUCINotation", () => {
39 | it("writes standard move", () => {
40 | const move = new UCIMove(52, 36);
41 | expect(move.toUCINotation()).toBe("e2e4");
42 | });
43 |
44 | it("writes promotion move", () => {
45 | const move = new UCIMove(8, 0, QUEEN);
46 | expect(move.toUCINotation()).toBe("a7a8q");
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/src/ui/App.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React from 'react';
20 | import Game from './Game';
21 | import { DndProvider } from 'react-dnd';
22 | import HTML5Backend from 'react-dnd-html5-backend';
23 | import TouchBackend from 'react-dnd-touch-backend';
24 |
25 | const multiBackends = (...backendFactories) =>
26 | function(manager) {
27 | const backends = backendFactories.map(b => b(manager));
28 | return {
29 | setup: (...args) =>
30 | backends.forEach(b => b.setup.apply(b, args)),
31 | teardown: (...args) =>
32 | backends.forEach(b => b.teardown.apply(b, args)),
33 | connectDropTarget: (...args) =>
34 | backends.forEach(b => b.connectDropTarget.apply(b, args)),
35 | connectDragPreview: (...args) =>
36 | backends.forEach(b => b.connectDragPreview.apply(b, args)),
37 | connectDragSource: (...args) =>
38 | backends.forEach(b => b.connectDragSource.apply(b, args)),
39 | };
40 | };
41 |
42 | function App() {
43 | return (
44 |
45 |
46 |
47 |
48 |
49 | );
50 | }
51 |
52 | export default App;
53 |
--------------------------------------------------------------------------------
/assembly/history.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | export class PositionHistory {
20 | private positions: StaticArray = new StaticArray(1024);
21 | private index: i32 = 0;
22 |
23 | push(hash: u64): void {
24 | unchecked(this.positions[this.index++] = hash);
25 | }
26 |
27 | pop(): void {
28 | this.index--;
29 | }
30 |
31 | isThreefoldRepetion(): bool {
32 | if (this.index <= 2) {
33 | return false;
34 | }
35 |
36 | const hash = unchecked(this.positions[this.index - 1]);
37 |
38 | let count = 0;
39 | for (let i = 0; i < this.index - 1; i++) {
40 | if (unchecked(this.positions[i]) == hash) {
41 | count++;
42 | if (count == 2) {
43 | return true;
44 | }
45 | }
46 | }
47 |
48 | return false;
49 | }
50 |
51 | isSingleRepetition(): bool {
52 | if (this.index <= 1) {
53 | return false;
54 | }
55 |
56 | const hash = unchecked(this.positions[this.index - 1]);
57 | for (let i = 0; i < this.index - 1; i++) {
58 | if (unchecked(this.positions[i]) == hash) {
59 | return true;
60 | }
61 | }
62 |
63 | return false;
64 | }
65 |
66 | clear(): void {
67 | this.index = 0;
68 | }
69 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wasabi-chess",
3 | "version": "1.5.1",
4 | "private": true,
5 | "homepage": "https://mhonert.github.io/chess",
6 | "license": "GPL-3.0-or-later",
7 | "dependencies": {
8 | "@fortawesome/fontawesome-svg-core": "^1.2.30",
9 | "@fortawesome/free-solid-svg-icons": "^5.14.0",
10 | "@fortawesome/react-fontawesome": "^0.1.11",
11 | "as-wasi": "^0.4.4",
12 | "react": "^16.13.1",
13 | "react-dnd": "^10.0.2",
14 | "react-dnd-html5-backend": "^10.0.2",
15 | "react-dnd-touch-backend": "^10.0.2",
16 | "react-dom": "^16.13.1",
17 | "react-scripts": "4.0.0",
18 | "styled-components": "^5.1.1"
19 | },
20 | "scripts": {
21 | "build:web": "npm run build:web-engine && react-scripts build",
22 | "build:web-engine": "asc assembly/index.ts -O2 --exportRuntime --use IS_WASI=0 -b public/as-api.wasm",
23 | "build:uci": "asc assembly/uci.ts -O2 --use IS_WASI=1 --use abort=_abort -b build/engine.wasm -t build/engine.wast",
24 | "build": "npm run build:web",
25 | "start": "npm run build:web-engine && react-scripts start",
26 | "test": "asp --verbose",
27 | "perft": "asp --verbose -c as-pect-perft.config.js --file=perft.performance.ts",
28 | "perft:engine": "asp --verbose -c as-pect-perft.config.js --file=engine.performance.ts",
29 | "predeploy": "npm run build:web",
30 | "deploy": "gh-pages -d build",
31 | "version": "node tools/gen_version_ts.js && git add version.ts"
32 | },
33 | "eslintConfig": {
34 | "extends": "react-app"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "devDependencies": {
49 | "@as-pect/cli": "^6.0.0",
50 | "assemblyscript": "0.18.15",
51 | "babel-plugin-macros": "^2.8.0",
52 | "gh-pages": "^2.2.0",
53 | "workerize-loader": "^1.3.0"
54 | },
55 | "babelMacros": {
56 | "styledComponents": {
57 | "pure": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tools/bookgen/util.py:
--------------------------------------------------------------------------------
1 | # A free and open source chess game using AssemblyScript and React
2 | # Copyright (C) 2020 mhonert (https://github.com/mhonert)
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | # Convert from python-chess board location to Wasabi chess engine location system
18 | import numpy as np
19 |
20 |
21 | # Convert from python-chess board position to Wasabi engine board position
22 | def square_to_bb(sq):
23 | return (7 - int(sq / 8)) * 8 + (sq & 7)
24 |
25 |
26 | # Encode piece ID, from and to board position of a move as a single 16 bit unsigned integer
27 | def encode_move(piece_id, move_from, move_to):
28 | bb_from = square_to_bb(move_from)
29 | bb_to = square_to_bb(move_to)
30 | return piece_id | (bb_from << 3) | (bb_to << 10)
31 |
32 |
33 | # Right-rotate the bits of a 32 Bit integer
34 | def rotr32(n, rotations):
35 | return np.uint32(n >> rotations) | (n << (32 - rotations))
36 |
37 |
38 | # Calculate pseudo-random numbers
39 | class Random:
40 |
41 | state = np.uint64(0x4d595df4d0f33173)
42 | multiplier = np.uint64(6364136223846793005)
43 | increment = np.uint64(1442695040888963407)
44 |
45 | def rand32(self):
46 | x = self.state
47 | count = np.uint32(x >> np.uint64(59))
48 | self.state = x * self.multiplier + self.increment
49 | x ^= x >> np.uint64(18)
50 |
51 | return np.uint32(rotr32(np.uint32(x >> np.uint64(27)), count))
52 |
53 | def rand64(self):
54 | return (np.uint64(self.rand32()) << np.uint64(32)) | np.uint64(self.rand32())
55 |
--------------------------------------------------------------------------------
/src/ui/img/black_pawn.svg:
--------------------------------------------------------------------------------
1 |
2 |
55 |
--------------------------------------------------------------------------------
/src/ui/img/white_pawn.svg:
--------------------------------------------------------------------------------
1 |
2 |
55 |
--------------------------------------------------------------------------------
/assembly/opening-book.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { Board } from './board';
20 | import { Random } from './random';
21 | import { clock } from './io';
22 | import { getOpeningBookI32, getOpeningBookU32 } from './opening-book-data';
23 |
24 | const rnd = new Random();
25 |
26 | // Returns an encoded opening move the current position or 0 if none exists
27 | export function findOpeningMove(board: Board): i32 {
28 | const ply = board.getHalfMoveCount();
29 | const maxPly = getOpeningBookPlyLimit();
30 | if (ply >= maxPly) {
31 | return 0;
32 | }
33 |
34 | const startIndex = getOpeningBookU32(ply + 1);
35 | let entriesLeft = getOpeningBookI32(startIndex);
36 |
37 | const boardLowHash = board.getHash() & 0xFFFFFFFF;
38 | const boardHighHash = board.getHash() >> 32;
39 |
40 | let index = startIndex + 1;
41 | do {
42 | const moveCount = getOpeningBookU32(index + 2);
43 |
44 | if (boardLowHash == getOpeningBookU32(index) && boardHighHash == getOpeningBookU32(index + 1)) {
45 | const selectedMoveNum = rnd.rand32() % moveCount;
46 | return getOpeningBookI32(index + 3 + selectedMoveNum);
47 | }
48 |
49 | index += moveCount + 3;
50 | entriesLeft--;
51 | } while (entriesLeft > 0);
52 |
53 | // No move found
54 | return 0;
55 | }
56 |
57 | export function getOpeningBookPlyLimit(): i32 {
58 | return getOpeningBookI32(0);
59 | }
60 |
61 | export function randomizeOpeningBookMoves(): void {
62 | rnd.updateSeed(u64(clock.currentMillis()));
63 | }
--------------------------------------------------------------------------------
/assembly/__tests__/history-heuristics.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { HistoryHeuristics } from '../history-heuristics';
20 | import { encodeMove } from '../move-generation';
21 | import { QUEEN, ROOK } from '../pieces';
22 | import { WHITE } from '../board';
23 |
24 | describe("Killer move table", () => {
25 |
26 | it("Sets primary and secondary killer move entries correctly", () => {
27 | const table = new HistoryHeuristics();
28 | const moveA = encodeMove(QUEEN, 1, 2);
29 | const moveB = encodeMove(ROOK, 4, 5);
30 | table.update(1, WHITE, 1, 2, moveA);
31 | table.update(1, WHITE, 4, 5, moveB);
32 |
33 | const primaryKiller = table.getPrimaryKiller(1);
34 | const secondaryKiller = table.getSecondaryKiller(1);
35 |
36 | expect(primaryKiller).toBe(moveB, "primary move set");
37 | expect(secondaryKiller).toBe(moveA, "secondary move set");
38 | })
39 |
40 | it("Same move is not stored in primary and secondary slot", () => {
41 | const table = new HistoryHeuristics();
42 | const moveA = encodeMove(QUEEN, 1, 2);
43 | const moveB = encodeMove(ROOK, 4, 5);
44 | table.update(1, WHITE, 4, 5, moveB);
45 | table.update(1, WHITE, 1, 2, moveA);
46 | table.update(1, WHITE, 1, 2, moveA);
47 |
48 | const primaryKiller = table.getPrimaryKiller(1);
49 | const secondaryKiller = table.getSecondaryKiller(1);
50 |
51 | expect(primaryKiller).toBe(moveA, "primary move set");
52 | expect(secondaryKiller).toBe(moveB, "secondary move set");
53 | })
54 |
55 | });
56 |
57 |
--------------------------------------------------------------------------------
/assembly/perft.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { Board } from './board';
20 | import {
21 | decodeEndIndex,
22 | decodePiece,
23 | decodeStartIndex,
24 | generateMoves,
25 | } from './move-generation';
26 |
27 |
28 | /* Perft (performance test, move path enumeration) test helper function to verify the move generator.
29 | It generates all possible moves up to the specified depth and counts the number of leaf nodes.
30 | This number can then be compared to precalculated numbers that are known to be correct
31 | (see __tests__/perft.spec.ts).
32 |
33 | Another use for this function is to test the performance of the move generator (see __tests__/perft.performance.ts).
34 | */
35 | export function perft(board: Board, depth: i32): u64 {
36 | if (depth == 0) {
37 | return 1
38 | }
39 |
40 | let nodes: u64 = 0;
41 |
42 | const activePlayer = board.getActivePlayer();
43 | const moves = generateMoves(board, activePlayer);
44 |
45 | for (let i: i32 = 0; i < moves.length; i++) {
46 | const move = unchecked(moves[i]);
47 |
48 | const targetPieceId = decodePiece(move);
49 | const moveStart = decodeStartIndex(move);
50 | const moveEnd = decodeEndIndex(move);
51 | const previousPiece = board.getItem(moveStart);
52 |
53 | const removedPiece = board.performMove(targetPieceId, moveStart, moveEnd);
54 |
55 | if (!board.isInCheck(activePlayer)) {
56 | nodes += perft(board, depth - 1);
57 | }
58 | board.undoMove(previousPiece, moveStart, moveEnd, removedPiece);
59 | }
60 |
61 | return nodes;
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## :sushi: Wasabi Chess Engine
2 |
3 | 
4 | 
5 | [](https://mhonert.github.io/chess)
6 | [](https://www.gnu.org/licenses/gpl-3.0)
7 |
8 | [](screenshots/chess_mobile.png?raw=true)
9 |
10 | **Wasabi Chess** is a web-based chess engine, written in AssemblyScript that runs directly in the Browser.
11 |
12 | > [**Play here!**](https://mhonert.github.io/chess)
13 |
14 | The React web application embeds the engine using [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)
15 | to compute the AI moves in the background without blocking the main thread for the UI.
16 |
17 | ### Features
18 |
19 | - Chess engine implemented in [AssemblyScript](https://github.com/AssemblyScript/assemblyscript)
20 | - Computer opponent with 6 difficulty levels
21 | - Opening Database for varied gameplay
22 | - Move history to undo player moves
23 | - Drag'n'Drop support to move chess pieces
24 | - Touch support for mobile devices
25 |
26 | ### Built With
27 | * [AssemblyScript](https://github.com/AssemblyScript/assemblyscript) - for the chess engine
28 | * [as-pect](https://github.com/jtenner/as-pect) - to test the engine
29 | * [react](https://reactjs.org/) - for the user interface
30 | * [react-dnd](https://github.com/react-dnd/react-dnd) - for Drag and Drop support
31 | * [styled-components](https://www.styled-components.com/) - to style React components in JS
32 | * [react-fontawesome](https://github.com/FortAwesome/react-fontawesome) - for some font icons
33 | * [workerize-loader](https://github.com/developit/workerize-loader) - to load modules as Web Workers
34 | * [as-wasi](https://github.com/jedisct1/as-wasi) - for WASI system calls (only for the standalone UCI engine)
35 |
36 | ### License
37 | This project is licensed under the GNU General Public License - see the [LICENSE](LICENSE) for details.
38 |
39 | ### Attributions
40 | * Images for the chess pieces come from [Wikimedia Commons](https://commons.wikimedia.org/wiki/Category:SVG_chess_pieces)
41 | * The opening book was generated from a selection of chess games from the [FICS Games Database](https://www.ficsgames.org)
42 | * A set of 725000 [test positions](https://bitbucket.org/zurichess/tuner/downloads/) collected by the author of Zurichess was used to tune all evaluation parameters
43 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
35 |
36 |
45 | Chess
46 |
47 |
48 |
49 |
50 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/assembly/__tests__/transposition-table.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import {
20 | getDepth,
21 | getScoredMove, getScoreType,
22 | ScoreType,
23 | TRANSPOSITION_MAX_DEPTH,
24 | TranspositionTable
25 | } from '../transposition-table';
26 | import { decodeScore, encodeMove, encodeScoredMove } from '../move-generation';
27 | import { MAX_SCORE, MIN_SCORE } from '../engine';
28 |
29 | describe("Transposition table", () => {
30 |
31 | it("writes entry correctly", () => {
32 | const hash: u64 = u64.MAX_VALUE;
33 | const depth = TRANSPOSITION_MAX_DEPTH;
34 | const move = encodeMove(5, 32, 33);
35 | const score: i32 = -10;
36 | const type = ScoreType.EXACT;
37 |
38 | const tt = new TranspositionTable();
39 | tt.writeEntry(hash, depth, encodeScoredMove(move, score), type);
40 |
41 | const entry: u64 = tt.getEntry(hash);
42 |
43 | expect(getScoredMove(entry)).toBe(encodeScoredMove(move, score), "move does not match");
44 | expect(getDepth(entry)).toBe(depth, "Depth does not match");
45 | expect(getScoreType(entry)).toBe(type, "Type does not match");
46 | });
47 |
48 | it("encodes negative score correctly", () => {
49 | const hash: u64 = u64.MAX_VALUE;
50 | const score: i32 = MIN_SCORE;
51 |
52 | const tt = new TranspositionTable();
53 | tt.writeEntry(hash, 1, encodeScoredMove(0, score), ScoreType.EXACT);
54 |
55 | const entry: u64 = tt.getEntry(hash);
56 | expect(decodeScore(getScoredMove(entry))).toBe(score, "Score does not match");
57 | });
58 |
59 | it("encodes positive score correctly", () => {
60 | const hash: u64 = u64.MAX_VALUE;
61 | const score: i32 = MAX_SCORE;
62 |
63 | const tt = new TranspositionTable();
64 | tt.writeEntry(hash, 1, encodeScoredMove(0, score), ScoreType.EXACT);
65 |
66 | const entry: u64 = tt.getEntry(hash);
67 | expect(decodeScore(getScoredMove(entry))).toBe(score, "Score does not match");
68 | });
69 |
70 | });
71 |
72 |
--------------------------------------------------------------------------------
/src/ui/img/white_knight.svg:
--------------------------------------------------------------------------------
1 |
2 |
72 |
--------------------------------------------------------------------------------
/src/ui/Board.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React from 'react';
20 | import Field from './Field';
21 | import Piece from './Piece';
22 | import styled from 'styled-components/macro';
23 | import { BLACK, K, WHITE } from '../engine/constants';
24 |
25 | const BoardGrid = styled.div`
26 | display: grid;
27 | grid-template-columns: repeat(8, 1fr);
28 | grid-template-rows: repeat(8, 1fr);
29 |
30 | @media (min-aspect-ratio: 99/100) {
31 | width: 100vh;
32 | height: 100vh;
33 | }
34 | @media (max-aspect-ratio: 100/99) {
35 | width: 100vw;
36 | height: 100vw;
37 | }
38 |
39 | box-shadow: 3px 3px 3px #586e75;
40 | `;
41 |
42 | const Board = ({board, isRotated, inCheck, lastMove, currentPieceMoves, handlePlayerMove, updatePossibleMoves, clearPossibleMoves}) => {
43 | return (
44 |
45 | {board.slice(0, 64).map((_, idx) => {
46 | const rotatedIndex = isRotated ? 63 - idx : idx;
47 | const item = board[rotatedIndex];
48 |
49 | return (
50 | > 3)) % 2 === 0}
55 | isStart={rotatedIndex === lastMove.start}
56 | isEnd={rotatedIndex === lastMove.end}
57 | isPossibleTarget={currentPieceMoves.has(rotatedIndex)}
58 | isInCheck={Math.abs(item) === K && Math.sign(item) === inCheck}
59 | >
60 | {item !== 0 && (
61 |
68 | )}
69 |
70 | );
71 | })}
72 |
73 | );
74 | };
75 |
76 | export default Board;
--------------------------------------------------------------------------------
/assembly/move-ordering.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { decodeScore } from './move-generation';
20 |
21 | const CAPTURE_ORDER_SCORES = createCaptureOrderScores();
22 |
23 | // Returns a higher score for capturing more valuable victims using less valuable attackers (MVV-LVA).
24 | @inline
25 | export function getCaptureOrderScore(attackerPieceId: i32, victimPieceId: i32): i32 {
26 | return unchecked(CAPTURE_ORDER_SCORES[((attackerPieceId - 1) * 8 + (victimPieceId - 1))]);
27 | }
28 |
29 | @inline
30 | export function sortByScoreDescending(moves: StaticArray): void {
31 | // Basic insertion sort
32 | for (let i = 1; i < moves.length; i++) {
33 | const x = unchecked(moves[i]);
34 | const xScore = decodeScore(x);
35 | let j = i - 1;
36 | while (j >= 0) {
37 | const y = unchecked(moves[j]);
38 | if (decodeScore(y) >= xScore) {
39 | break;
40 | }
41 | unchecked(moves[j + 1] = y);
42 | j--;
43 | }
44 | unchecked(moves[j + 1] = x);
45 | }
46 | }
47 |
48 | @inline
49 | export function sortByScoreAscending(moves: StaticArray): void {
50 | // Basic insertion sort
51 | for (let i = 1; i < moves.length; i++) {
52 | const x = unchecked(moves[i]);
53 | const xScore = decodeScore(x);
54 | let j = i - 1;
55 | while (j >= 0) {
56 | const y = unchecked(moves[j]);
57 | if (decodeScore(y) <= xScore) {
58 | break;
59 | }
60 | unchecked(moves[j + 1] = y);
61 | j--;
62 | }
63 | unchecked(moves[j + 1] = x);
64 | }
65 | }
66 |
67 |
68 | // Order capture moves first by most valuable victim and then by least valuable attacker (MVV-LVA)
69 | function createCaptureOrderScores(): StaticArray {
70 | const scores = new StaticArray(5 + 5 * 8 + 1);
71 |
72 | let orderScore: i32 = 0;
73 | for (let victim = 0; victim <= 5; victim++) {
74 | for (let attacker = 5; attacker >= 0; attacker--) {
75 | scores[victim + attacker * 8] = orderScore * 64;
76 | orderScore++;
77 | }
78 | }
79 |
80 | return scores;
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/src/ui/PromotionPieceSelection.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2019 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import styled from 'styled-components/macro';
20 | import { B, BLACK, N, Q, R, WHITE } from '../engine/constants';
21 | import { PieceImage } from './Piece';
22 | import React from 'react';
23 |
24 | const Overlay = styled.div`
25 | outline: 1px solid #586e75;
26 | box-shadow: 3px 3px 3px #586e75;
27 | margin: 0;
28 | padding: 0;
29 | position: absolute;
30 | z-index: 1;
31 |
32 | @media (min-aspect-ratio: 99/100) {
33 | top: ${props => props.row * 12.5}vh;
34 | left: ${props => props.column * 12.5}vh;
35 | width: 12.5vh;
36 | height: 50vh;
37 | }
38 | @media (max-aspect-ratio: 100/99) {
39 | top: ${props => props.row * 12.5}vw;
40 | left: ${props => props.column * 12.5}vw;
41 | width: 12.5vw;
42 | height: 50vw;
43 | }
44 | `;
45 |
46 | const FieldDiv = styled.div`
47 | position: absolute;
48 | background-color: ${props => props.isEven ? '#fdf6e3' : '#eee8d5'};
49 |
50 | @media (min-aspect-ratio: 99/100) {
51 | top: ${props => props.row * 12.5}vh;
52 | width: 12.5vh;
53 | height: 12.5vh;
54 | }
55 | @media (max-aspect-ratio: 100/99) {
56 | top: ${props => props.row * 12.5}vw;
57 | width: 12.5vw;
58 | height: 12.5vw;
59 | }
60 | `;
61 |
62 | const PromotionPieceSelection = ({ column, playerColor, onSelection, isRotated }) => {
63 | const rotatedColumn = isRotated ? 7 - column : column;
64 |
65 | const top = playerColor === WHITE
66 | ? isRotated ? 4 : 0
67 | : isRotated ? 0 : 4;
68 |
69 | let pieces = (isRotated && playerColor !== BLACK) || (!isRotated && playerColor === BLACK) ? [N, B, R, Q] : [Q, R, B, N];
70 |
71 | return (
72 |
73 | {
74 | pieces.map((piece, index) => (
75 | onSelection(piece)}>
76 |
77 |
78 | ))
79 | }
80 |
81 | );
82 |
83 | };
84 |
85 | export default PromotionPieceSelection;
86 |
--------------------------------------------------------------------------------
/src/ui/img/black_bishop.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
--------------------------------------------------------------------------------
/src/ui/img/white_bishop.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
--------------------------------------------------------------------------------
/assembly/__tests__/engine.performance.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { fromFEN } from '../fen';
20 | import EngineControl from '../engine';
21 | import { calculatePieceSquareTables } from '../board';
22 |
23 | describe('Engine performance', () => {
24 | it('plays against itself', () => {
25 | const start = Date.now();
26 | let nodeCount: u64 = 0;
27 | nodeCount += measureEnginePerformance("2r2rk1/1b3p1p/p5p1/q2pb3/Pp6/3BP2P/1P1NQPP1/3R1RK1 w - - 0 1", 12); // bm Nf3
28 | nodeCount += measureEnginePerformance("2rq1rk1/p2nbppp/b1p1p3/8/2pPP3/2B3P1/P2N1PBP/R2QR1K1 w - - 0 1", 12); // bm Qa4
29 | nodeCount += measureEnginePerformance("r1qr2k1/p3bppp/1p1Bpn2/1P6/2PQb3/5NP1/P3PPBP/R2R2K1 w - - 0 1", 12); // bm c5
30 | nodeCount += measureEnginePerformance("r1r2k2/pp2ppbp/3pb1p1/3N4/2P1P1n1/1P6/P2BBPPP/1R3RK1 w - - 0 1", 12); // bm Bg5
31 | nodeCount += measureEnginePerformance("r1r3k1/1p1nppbp/p2pb1p1/8/N1P1P3/1P2BP2/P2KB1PP/2R4R w - - 0 1", 12); // bm g4
32 | nodeCount += measureEnginePerformance("r1r3k1/2q1bppp/pn1p1n2/4pP2/2b1P3/1NN1B3/1PP1B1PP/R2Q1R1K w - - 0 1", 12); // bm Na5
33 | nodeCount += measureEnginePerformance("r2q1k1r/pb3pp1/4p2p/1BnnP3/1p6/5N2/PP3PPP/R1BQ1RK1 w - - 0 1", 12); // bm a3
34 | nodeCount += measureEnginePerformance("r2q1nk1/pp2r1pp/1np1bp2/3p4/3P1P2/2NBPN2/PPQ3PP/4RRK1 w - - 0 1", 12); // bm Kh1
35 | nodeCount += measureEnginePerformance("r2q1r1k/pb1nbppB/2p1p2p/4P3/1p1P4/5N2/PPQ2PPP/R1B2RK1 w - - 0 1", 12); // bm Be4
36 |
37 | const duration = Date.now() - start;
38 | trace("-------------------------------------------------------");
39 | trace("Total nodes : " + nodeCount.toString());
40 | trace("Duration (ms) : " + duration.toString());
41 | const nodesPerSecond = duration > 0 ? nodeCount * 1000 / duration : 0;
42 | trace("Nodes per second: " + nodesPerSecond.toString());
43 |
44 | });
45 |
46 | });
47 |
48 |
49 | function measureEnginePerformance(fen: string, depth: i32): u64 {
50 | calculatePieceSquareTables();
51 | EngineControl.reset();
52 | EngineControl.setBoard(fromFEN(fen));
53 | const move = EngineControl.findBestMove(depth, 0, true);
54 |
55 | return EngineControl.getNodeCount();
56 | }
57 |
58 |
59 |
--------------------------------------------------------------------------------
/tools/bookgen/zobrist.py:
--------------------------------------------------------------------------------
1 | # A free and open source chess game using AssemblyScript and React
2 | # Copyright (C) 2020 mhonert (https://github.com/mhonert)
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | import chess
18 | import numpy as np
19 |
20 | from util import square_to_bb, Random
21 |
22 | WHITE_KING_SIDE_CASTLING = 1 << 0
23 | BLACK_KING_SIDE_CASTLING = 1 << 1
24 | WHITE_QUEEN_SIDE_CASTLING = 1 << 2
25 | BLACK_QUEEN_SIDE_CASTLING = 1 << 3
26 |
27 | BIT_POS = [square_to_bb(sq) for sq in range(64)]
28 |
29 |
30 | # Calculates the zobrist hash for the current board position
31 | def calc_hash(board):
32 | hash = np.uint64(0)
33 | for idx, bitPos in enumerate(BIT_POS):
34 | piece = board.piece_at(bitPos)
35 | if piece:
36 | if piece.color:
37 | hash ^= (PIECE_RNG_NUMBERS[(piece.piece_type + 6) * 64 + idx])
38 |
39 | else:
40 | hash ^= (PIECE_RNG_NUMBERS[(-piece.piece_type + 6) * 64 + idx])
41 |
42 | if not board.turn:
43 | hash ^= PLAYER_RNG_NUMBER
44 |
45 | castling_bits = 0
46 | if board.castling_rights & chess.BB_A8:
47 | castling_bits |= BLACK_QUEEN_SIDE_CASTLING
48 |
49 | if board.castling_rights & chess.BB_H8:
50 | castling_bits |= BLACK_KING_SIDE_CASTLING
51 |
52 | if board.castling_rights & chess.BB_H1:
53 | castling_bits |= WHITE_KING_SIDE_CASTLING
54 |
55 | if board.castling_rights & chess.BB_A1:
56 | castling_bits |= WHITE_QUEEN_SIDE_CASTLING
57 |
58 | hash ^= CASTLING_RNG_NUMBERS[castling_bits]
59 |
60 | if board.ep_square is not None:
61 | ep_bit = 0
62 | if chess.A3 <= board.ep_square <= chess.H3:
63 | ep_bit |= ((board.ep_square - 16) + 8)
64 |
65 | if chess.A6 <= board.ep_square <= chess.H6:
66 | ep_bit |= (board.ep_square - 40)
67 |
68 | hash ^= EN_PASSANT_RNG_NUMBERS[ep_bit]
69 |
70 | return hash
71 |
72 |
73 | rnd = Random()
74 |
75 |
76 | def rand_array(count):
77 | numbers = []
78 | for i in range(count):
79 | numbers.append(rnd.rand64())
80 | return numbers
81 |
82 |
83 | def last_element_zero(elements):
84 | elements[len(elements) - 1] = np.uint64(0)
85 | return elements
86 |
87 |
88 | PIECE_RNG_NUMBERS = rand_array(13 * 64)
89 | PLAYER_RNG_NUMBER = rnd.rand64()
90 | EN_PASSANT_RNG_NUMBERS = rand_array(16)
91 |
92 | CASTLING_RNG_NUMBERS = last_element_zero(rand_array(16))
93 |
--------------------------------------------------------------------------------
/src/ui/img/black_knight.svg:
--------------------------------------------------------------------------------
1 |
2 |
76 |
--------------------------------------------------------------------------------
/tools/bookgen/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "7e30dd3518ae2677056069bc5f19f3b91a23312d8e875012cefc8cb96e22caa9"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.9"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "chess": {
20 | "hashes": [
21 | "sha256:590c979f25a26b8c621c6c312278fb5ba6cd6bdf39af9bd2553a7f1732295120",
22 | "sha256:823ab1bc8b1674e37e77e8d4ff8e758f37551dcdee4d106438fa22f12d3a7d9b"
23 | ],
24 | "index": "pypi",
25 | "version": "==1.4.0"
26 | },
27 | "numpy": {
28 | "hashes": [
29 | "sha256:032be656d89bbf786d743fee11d01ef318b0781281241997558fa7950028dd29",
30 | "sha256:104f5e90b143dbf298361a99ac1af4cf59131218a045ebf4ee5990b83cff5fab",
31 | "sha256:125a0e10ddd99a874fd357bfa1b636cd58deb78ba4a30b5ddb09f645c3512e04",
32 | "sha256:12e4ba5c6420917571f1a5becc9338abbde71dd811ce40b37ba62dec7b39af6d",
33 | "sha256:13adf545732bb23a796914fe5f891a12bd74cf3d2986eed7b7eba2941eea1590",
34 | "sha256:2d7e27442599104ee08f4faed56bb87c55f8b10a5494ac2ead5c98a4b289e61f",
35 | "sha256:3bc63486a870294683980d76ec1e3efc786295ae00128f9ea38e2c6e74d5a60a",
36 | "sha256:3d3087e24e354c18fb35c454026af3ed8997cfd4997765266897c68d724e4845",
37 | "sha256:4ed8e96dc146e12c1c5cdd6fb9fd0757f2ba66048bf94c5126b7efebd12d0090",
38 | "sha256:60759ab15c94dd0e1ed88241fd4fa3312db4e91d2c8f5a2d4cf3863fad83d65b",
39 | "sha256:65410c7f4398a0047eea5cca9b74009ea61178efd78d1be9847fac1d6716ec1e",
40 | "sha256:66b467adfcf628f66ea4ac6430ded0614f5cc06ba530d09571ea404789064adc",
41 | "sha256:7199109fa46277be503393be9250b983f325880766f847885607d9b13848f257",
42 | "sha256:72251e43ac426ff98ea802a931922c79b8d7596480300eb9f1b1e45e0543571e",
43 | "sha256:89e5336f2bec0c726ac7e7cdae181b325a9c0ee24e604704ed830d241c5e47ff",
44 | "sha256:89f937b13b8dd17b0099c7c2e22066883c86ca1575a975f754babc8fbf8d69a9",
45 | "sha256:9c94cab5054bad82a70b2e77741271790304651d584e2cdfe2041488e753863b",
46 | "sha256:9eb551d122fadca7774b97db8a112b77231dcccda8e91a5bc99e79890797175e",
47 | "sha256:a1d7995d1023335e67fb070b2fae6f5968f5be3802b15ad6d79d81ecaa014fe0",
48 | "sha256:ae61f02b84a0211abb56462a3b6cd1e7ec39d466d3160eb4e1da8bf6717cdbeb",
49 | "sha256:b9410c0b6fed4a22554f072a86c361e417f0258838957b78bd063bde2c7f841f",
50 | "sha256:c26287dfc888cf1e65181f39ea75e11f42ffc4f4529e5bd19add57ad458996e2",
51 | "sha256:c91ec9569facd4757ade0888371eced2ecf49e7982ce5634cc2cf4e7331a4b14",
52 | "sha256:ecb5b74c702358cdc21268ff4c37f7466357871f53a30e6f84c686952bef16a9"
53 | ],
54 | "index": "pypi",
55 | "version": "==1.20.1"
56 | }
57 | },
58 | "develop": {}
59 | }
60 |
--------------------------------------------------------------------------------
/src/ui/img/black_king.svg:
--------------------------------------------------------------------------------
1 |
2 |
85 |
--------------------------------------------------------------------------------
/src/ui/img/white_king.svg:
--------------------------------------------------------------------------------
1 |
2 |
90 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags: "v*"
6 |
7 | jobs:
8 | release:
9 | if: github.repository == 'mhonert/chess'
10 | name: Publish release
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v2
15 |
16 | - name: Setup node
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: '14.x'
20 |
21 | - name: Build UCI engine
22 | env:
23 | CI: true
24 | run: |
25 | npm ci
26 | npm test
27 | npm run build:uci
28 | cp build/engine.wasm artifacts/linux
29 | cp build/engine.wasm artifacts/windows
30 | cp LICENSE artifacts/linux
31 | cp LICENSE artifacts/windows
32 |
33 | - name: Get WAVM
34 | env:
35 | WAVM_VERSION: "2020-05-13"
36 | run: |
37 | mkdir -p artifacts/linux/wavm
38 | curl -L -o artifacts/wavm-linux.tar.gz https://github.com/WAVM/WAVM/releases/download/nightly%2F${WAVM_VERSION}/wavm-0.0.0-prerelease-linux.tar.gz
39 |
40 | mkdir -p artifacts/windows/wavm
41 | curl -L -o artifacts/wavm-windows.zip https://github.com/WAVM/WAVM/releases/download/nightly%2F${WAVM_VERSION}/wavm-0.0.0-prerelease-windows.zip
42 |
43 | sha256sum -c artifacts/wavm_checksums.txt
44 |
45 | tar xf artifacts/wavm-linux.tar.gz -C artifacts/linux/wavm
46 | rm -rf artifacts/linux/wavm/examples
47 | rm -rf artifacts/linux/wavm/include
48 | rm -rf artifacts/linux/wavm/lib
49 | rm artifacts/wavm-linux.tar.gz
50 |
51 | unzip artifacts/wavm-windows.zip -d artifacts/windows/wavm
52 | rm -rf artifacts/windows/wavm/examples
53 | rm -rf artifacts/windows/wavm/include
54 | rm -rf artifacts/windows/wavm/lib
55 | rm artifacts/wavm-windows.zip
56 | cp artifacts/windows/wavm/LICENSE.txt artifacts/linux/wavm
57 |
58 | - name: Package artifacts
59 | run: |
60 | tag_name="${GITHUB_REF##*/}"
61 |
62 | pushd artifacts/linux
63 | tar czvf ../wasabi-${tag_name}-linux.tar.gz *
64 | popd
65 |
66 | pushd artifacts/windows
67 | zip -r ../wasabi-${tag_name}-windows.zip *
68 | popd
69 |
70 | cp artifacts/linux/engine.wasm artifacts/wasabi-${tag_name}-engine.wasm
71 |
72 | pushd artifacts
73 | sha256sum wasabi* > checksums.txt
74 | popd
75 |
76 | - name: Test linux executable
77 | timeout-minutes: 1
78 | run: |
79 | ./artifacts/linux/wasabi < ./artifacts/engine_selftest.txt
80 |
81 | - name: Create Release
82 | env:
83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
84 | run: |
85 | pushd artifacts
86 | tag_name="${GITHUB_REF##*/}"
87 | echo "$tag_name" > release_description.txt
88 | cat release_notes.md >> release_description.txt
89 | hub release create -a "checksums.txt#Checksums" -a "wasabi-${tag_name}-linux.tar.gz#Wasabi Chess for Linux 64-bit" \
90 | -a "wasabi-${tag_name}-windows.zip#Wasabi Chess for Windows 64-bit" -F release_description.txt "$tag_name" \
91 | -a "wasabi-${tag_name}-engine.wasm#Standalone engine.wasm" -F release_description.txt "$tag_name"
92 |
--------------------------------------------------------------------------------
/assembly/__tests__/move-ordering.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { decodeScore, encodeScoredMove } from '../move-generation';
20 | import { sortByScoreAscending, sortByScoreDescending } from '../move-ordering';
21 |
22 | describe("Move list sorting", () => {
23 |
24 | it("sorts moves descending by score", () => {
25 | const moves: StaticArray = StaticArray.fromArray([encodeScoredMove(0, 12), encodeScoredMove(1, 5), encodeScoredMove(2, 27), encodeScoredMove(3, 15)]);
26 |
27 | sortByScoreDescending(moves);
28 |
29 | expect(decodeScore(moves[0])).toBe(27);
30 | expect(decodeScore(moves[1])).toBe(15);
31 | expect(decodeScore(moves[2])).toBe(12);
32 | expect(decodeScore(moves[3])).toBe(5);
33 | });
34 |
35 | it("sorts empty move list descending", () => {
36 | sortByScoreDescending(new StaticArray(0));
37 | });
38 |
39 | it("sorts moves with 1 element descending", () => {
40 | const moves: StaticArray = StaticArray.fromArray([encodeScoredMove(0, 12)]);
41 | sortByScoreDescending(moves);
42 |
43 | expect(decodeScore(moves[0])).toBe(12);
44 | });
45 |
46 | it("sorts moves with 2 elements descending", () => {
47 | const moves: StaticArray = StaticArray.fromArray([encodeScoredMove(0, 5), encodeScoredMove(1, 12)]);
48 | sortByScoreDescending(moves);
49 |
50 | expect(decodeScore(moves[0])).toBe(12);
51 | expect(decodeScore(moves[1])).toBe(5);
52 | });
53 |
54 | it("sorts moves ascending by score for black player", () => {
55 | const moves: StaticArray = StaticArray.fromArray([encodeScoredMove(0, 12), encodeScoredMove(1, 5), encodeScoredMove(2, 27), encodeScoredMove(3, 15)]);
56 |
57 | sortByScoreAscending(moves);
58 |
59 | expect(decodeScore(moves[0])).toBe(5);
60 | expect(decodeScore(moves[1])).toBe(12);
61 | expect(decodeScore(moves[2])).toBe(15);
62 | expect(decodeScore(moves[3])).toBe(27);
63 | });
64 |
65 | it("sorts empty move list ascending", () => {
66 | sortByScoreAscending(new StaticArray(0));
67 | });
68 |
69 | it("sorts moves with 1 element ascending", () => {
70 | const moves: StaticArray = StaticArray.fromArray([encodeScoredMove(0, 12)]);
71 | sortByScoreAscending(moves);
72 |
73 | expect(decodeScore(moves[0])).toBe(12);
74 | });
75 |
76 | it("sorts moves with 2 elements ascending", () => {
77 | const moves: StaticArray = StaticArray.fromArray([encodeScoredMove(0, 12), encodeScoredMove(1, 5)]);
78 | sortByScoreAscending(moves);
79 |
80 | expect(decodeScore(moves[0])).toBe(5);
81 | expect(decodeScore(moves[1])).toBe(12);
82 | });
83 | });
84 |
85 |
--------------------------------------------------------------------------------
/src/ui/AnimatedSpinner.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import styled from 'styled-components/macro';
20 | import React from 'react';
21 |
22 | /* Animated spinner adapted from https://loading.io/css/ */
23 | const Spinner = styled.div`
24 | color: #073642;
25 | display: inline-block;
26 | position: relative;
27 | width: 80px;
28 | height: 80px;
29 |
30 | margin-top: 1rem;
31 | margin-right: 0.5rem;
32 | margin-left: 1.5rem;
33 |
34 | div {
35 | transform-origin: 40px 40px;
36 | animation: lds-spinner 1.2s linear infinite;
37 | }
38 | div:after {
39 | content: " ";
40 | display: block;
41 | position: absolute;
42 | top: 3px;
43 | left: 37px;
44 | width: 6px;
45 | height: 18px;
46 | border-radius: 20%;
47 | background: #073642;
48 | }
49 | div:nth-child(1) {
50 | transform: rotate(0deg);
51 | animation-delay: -1.1s;
52 | }
53 | div:nth-child(2) {
54 | transform: rotate(30deg);
55 | animation-delay: -1s;
56 | }
57 | div:nth-child(3) {
58 | transform: rotate(60deg);
59 | animation-delay: -0.9s;
60 | }
61 | div:nth-child(4) {
62 | transform: rotate(90deg);
63 | animation-delay: -0.8s;
64 | }
65 | div:nth-child(5) {
66 | transform: rotate(120deg);
67 | animation-delay: -0.7s;
68 | }
69 | div:nth-child(6) {
70 | transform: rotate(150deg);
71 | animation-delay: -0.6s;
72 | }
73 | div:nth-child(7) {
74 | transform: rotate(180deg);
75 | animation-delay: -0.5s;
76 | }
77 | div:nth-child(8) {
78 | transform: rotate(210deg);
79 | animation-delay: -0.4s;
80 | }
81 | div:nth-child(9) {
82 | transform: rotate(240deg);
83 | animation-delay: -0.3s;
84 | }
85 | div:nth-child(10) {
86 | transform: rotate(270deg);
87 | animation-delay: -0.2s;
88 | }
89 | div:nth-child(11) {
90 | transform: rotate(300deg);
91 | animation-delay: -0.1s;
92 | }
93 | div:nth-child(12) {
94 | transform: rotate(330deg);
95 | animation-delay: 0s;
96 | }
97 | @keyframes lds-spinner {
98 | 0% {
99 | opacity: 1;
100 | }
101 | 100% {
102 | opacity: 0;
103 | }
104 | }
105 | `
106 |
107 | export const AnimatedSpinner = () => (
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | );
123 |
124 | export default AnimatedSpinner;
125 |
--------------------------------------------------------------------------------
/assembly/pieces.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | export const PAWN: i32 = 1;
20 | export const KNIGHT: i32 = 2;
21 | export const BISHOP: i32 = 3;
22 | export const ROOK: i32 = 4;
23 | export const QUEEN: i32 = 5;
24 | export const KING: i32 = 6;
25 |
26 | const KING_VALUE = 1500;
27 | export let EG_QUEEN_VALUE = 991;
28 | export let QUEEN_VALUE = 1376;
29 | export let EG_ROOK_VALUE = 568;
30 | export let ROOK_VALUE = 659;
31 | export let EG_BISHOP_VALUE = 335;
32 | export let BISHOP_VALUE = 489;
33 | export let EG_KNIGHT_VALUE = 267;
34 | export let KNIGHT_VALUE = 456;
35 | export let EG_PAWN_VALUE = 107;
36 | export let PAWN_VALUE = 102;
37 |
38 | export const PIECE_VALUES: StaticArray = StaticArray.fromArray([0, PAWN_VALUE, KNIGHT_VALUE, BISHOP_VALUE, ROOK_VALUE, QUEEN_VALUE, KING_VALUE]);
39 | export const EG_PIECE_VALUES: StaticArray = StaticArray.fromArray([0, EG_PAWN_VALUE, EG_KNIGHT_VALUE, EG_BISHOP_VALUE, EG_ROOK_VALUE, EG_QUEEN_VALUE, KING_VALUE]);
40 |
41 | export function resetPieceValues(): void {
42 | unchecked(PIECE_VALUES[PAWN] = PAWN_VALUE);
43 | unchecked(PIECE_VALUES[KNIGHT] = KNIGHT_VALUE);
44 | unchecked(PIECE_VALUES[BISHOP] = BISHOP_VALUE);
45 | unchecked(PIECE_VALUES[ROOK] = ROOK_VALUE);
46 | unchecked(PIECE_VALUES[QUEEN] = QUEEN_VALUE);
47 | unchecked(PIECE_VALUES[KING] = KING_VALUE);
48 |
49 | unchecked(EG_PIECE_VALUES[PAWN] = EG_PAWN_VALUE);
50 | unchecked(EG_PIECE_VALUES[KNIGHT] = EG_KNIGHT_VALUE);
51 | unchecked(EG_PIECE_VALUES[BISHOP] = EG_BISHOP_VALUE);
52 | unchecked(EG_PIECE_VALUES[ROOK] = EG_ROOK_VALUE);
53 | unchecked(EG_PIECE_VALUES[QUEEN] = EG_QUEEN_VALUE);
54 | unchecked(EG_PIECE_VALUES[KING] = KING_VALUE);
55 | }
56 |
57 | export const P = PAWN;
58 | export const N = KNIGHT;
59 | export const B = BISHOP;
60 | export const R = ROOK;
61 | export const Q = QUEEN;
62 | export const K = KING;
63 |
64 | export const KNIGHT_DIRECTIONS: StaticArray = StaticArray.fromArray([17, 15, 10, 6, -10, -6, -15, -17]);
65 | export const KING_DIRECTIONS: StaticArray = StaticArray.fromArray([1, 8, -1, -8, 7, 9, -7, -9]);
66 |
67 | export const BISHOP_DIRECTIONS: StaticArray = StaticArray.fromArray([-7, -9, 7, 9]);
68 |
69 | export const WHITE_QUEEN_SIDE_ROOK_START = 56;
70 | export const WHITE_KING_SIDE_ROOK_START = 63;
71 | export const BLACK_QUEEN_SIDE_ROOK_START = 0;
72 | export const BLACK_KING_SIDE_ROOK_START = 7;
73 |
74 | export const WHITE_PAWNS_BASELINE_START = 48;
75 | export const WHITE_PAWNS_BASELINE_END = 55;
76 | export const BLACK_PAWNS_BASELINE_START = 8;
77 | export const BLACK_PAWNS_BASELINE_END = 15;
78 |
79 | export const WHITE_ENPASSANT_LINE_START = 16;
80 | export const WHITE_ENPASSANT_LINE_END = 23
81 | export const BLACK_ENPASSANT_LINE_START = 40;
82 | export const BLACK_ENPASSANT_LINE_END = 47
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/ui/Piece.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React from 'react';
20 | import { useDrag } from 'react-dnd';
21 | import styled from 'styled-components/macro';
22 |
23 | import whitePawn from './img/white_pawn.svg';
24 | import blackPawn from './img/black_pawn.svg';
25 | import whiteKnight from './img/white_knight.svg';
26 | import blackKnight from './img/black_knight.svg';
27 | import whiteBishop from './img/white_bishop.svg';
28 | import blackBishop from './img/black_bishop.svg';
29 | import whiteRook from './img/white_rook.svg';
30 | import blackRook from './img/black_rook.svg';
31 | import whiteQueen from './img/white_queen.svg';
32 | import blackQueen from './img/black_queen.svg';
33 | import whiteKing from './img/white_king.svg';
34 | import blackKing from './img/black_king.svg';
35 | import { BLACK } from '../engine/constants';
36 |
37 | const whiteImages = [
38 | whitePawn,
39 | whiteKnight,
40 | whiteBishop,
41 | whiteRook,
42 | whiteQueen,
43 | whiteKing
44 | ];
45 |
46 | const blackImages = [
47 | blackPawn,
48 | blackKnight,
49 | blackBishop,
50 | blackRook,
51 | blackQueen,
52 | blackKing
53 | ];
54 |
55 | const pieceNames = [ 'Pawn', 'Knight', 'Bishop', 'Rook', 'Queen', 'King' ]
56 |
57 | const Image = styled.img`
58 | display: block;
59 | margin: 13%;
60 | height: 74%;
61 | width: 74%;
62 |
63 | &.dragging {
64 | visibility: hidden;
65 | }
66 |
67 | // Workaround for wrong Drag'n'Drop preview image rendering in Chrome (see https://github.com/react-dnd/react-dnd/issues/832)
68 | -webkit-transform: rotateZ(0deg);
69 | `;
70 |
71 | const isFirefox = typeof InstallTrigger !== 'undefined';
72 |
73 | export const PieceImage = ({color, pieceId, ...props}) => {
74 | const img = color === BLACK ? blackImages[pieceId - 1] : whiteImages[pieceId - 1];
75 | return ;
76 | }
77 |
78 | const Piece = ({ boardIndex, color, piece, onPickup, onDrop }) => {
79 | const pieceId = Math.abs(piece);
80 | const img = color === BLACK ? blackImages[pieceId - 1] : whiteImages[pieceId - 1];
81 |
82 | const [{ isDragging }, drag] = useDrag({
83 | item: {
84 | type: 'PIECE',
85 | pieceId,
86 | boardIndex
87 | },
88 | begin: monitor => onPickup(boardIndex),
89 | end: dropResult => onDrop(boardIndex),
90 | collect: monitor => ({
91 | isDragging: !!monitor.isDragging()
92 | })
93 | });
94 |
95 | const pieceImage =
96 | ;
101 |
102 | // Workaround for wrong Drag'n'Drop preview image rendering in Firefox
103 | return isFirefox
104 | ?
{pieceImage}
105 | : pieceImage;
106 | };
107 |
108 | export default Piece;
109 |
--------------------------------------------------------------------------------
/src/engine/engine.worker.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import {instantiate} from "assemblyscript/lib/loader";
20 | import { BLACK, WHITE } from './constants';
21 | import { Move } from './move';
22 |
23 | let engine;
24 |
25 | // Initializes the wasm engine
26 | export async function init() {
27 | if (engine) {
28 | return; // already initialized
29 | }
30 | const result = await instantiate(fetch("./as-api.wasm"));
31 | engine = result.exports;
32 | console.log("Engine initialized");
33 | };
34 |
35 | export function newGame() {
36 | engine.newGame();
37 | }
38 |
39 | export function calculateMove(difficultyLevel) {
40 | console.log('Start calculation of move ...');
41 |
42 | const moveEncoded = engine.calculateMove(difficultyLevel);
43 | const move = Move.fromEncodedMove(moveEncoded);
44 |
45 | console.log('Calculation finished');
46 | return move;
47 | }
48 |
49 | export function performMove(move) {
50 | const gameStatePtr = engine.__pin(engine.performMove(move.encodedMove));
51 | const gameState = engine.__getArray(gameStatePtr);
52 | engine.__unpin(gameStatePtr);
53 |
54 | return decodeGameState(gameState);
55 | }
56 |
57 | export function setPosition(fen, moves) {
58 | const fenStr = engine.__pin(engine.__newString(fen));
59 | const movesArray = engine.__pin(engine.__newArray(engine.INT32ARRAY_ID, moves.map(move => move.encodedMove)));
60 |
61 | const gameStatePtr = engine.__pin(engine.setPosition(fenStr, movesArray));
62 |
63 | engine.__unpin(movesArray);
64 | engine.__unpin(fenStr);
65 |
66 | const gameState = engine.__getArray(gameStatePtr);
67 | engine.__unpin(gameStatePtr);
68 |
69 | return decodeGameState(gameState);
70 | }
71 |
72 | const GAME_ENDED = 1
73 | const CHECK_MATE = 2;
74 | const STALE_MATE = 4;
75 | const THREEFOLD_REPETITION_DRAW = 8;
76 | const FIFTYMOVE_DRAW = 16;
77 | const INSUFFICIENT_MATERIAL_DRAW = 32;
78 | const ACTIVE_PLAYER = 64; // 0 - White, 1 - Black
79 | const WHITE_IN_CHECK = 128;
80 | const BLACK_IN_CHECK = 256;
81 |
82 | function decodeGameState(gameState) {
83 | const board = gameState.slice(0, 64);
84 | const state = gameState[64];
85 | const moves = gameState.length > 65 ? gameState.slice(65).map(Move.fromEncodedMove) : [];
86 |
87 | return {
88 | board,
89 | moves,
90 | gameEnded: (state & GAME_ENDED) !== 0,
91 | checkMate: (state & CHECK_MATE) !== 0,
92 | staleMate: (state & STALE_MATE) !== 0,
93 | whiteInCheck: (state & WHITE_IN_CHECK) !== 0,
94 | blackInCheck: (state & BLACK_IN_CHECK) !== 0,
95 | threefoldRepetition: (state & THREEFOLD_REPETITION_DRAW) !== 0,
96 | fiftyMoveDraw: (state & FIFTYMOVE_DRAW) !== 0,
97 | insufficientMaterial: (state & INSUFFICIENT_MATERIAL_DRAW) !== 0,
98 | activePlayer: (state & ACTIVE_PLAYER) !== 0 ? BLACK : WHITE
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/src/ui/img/white_queen.svg:
--------------------------------------------------------------------------------
1 |
2 |
100 |
--------------------------------------------------------------------------------
/src/ui/Field.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React from 'react';
20 | import { useDrop } from 'react-dnd';
21 | import styled from 'styled-components/macro';
22 |
23 | export const FieldDiv = styled.div`
24 | position: relative;
25 | background-color: ${props => props.isEven ? "#fdf6e3" : "#eee8d5"};
26 |
27 | &.mark:before {
28 | position: absolute;
29 | content: '';
30 | display: block;
31 | border-radius: 50%;
32 | }
33 |
34 | // always keep board size rectangular and maximized to the smaller axis
35 | @media (min-aspect-ratio: 99/100) {
36 | width: 12.5vh;
37 | height: 12.5vh;
38 |
39 | &.mark:before {
40 | top: 0.7vh;
41 | left: 0.7vh;
42 | bottom: 0.7vh;
43 | right: 0.7vh;
44 | }
45 |
46 | &.move:before {
47 | border: 0.6vh solid ${props => props.markColor};
48 | box-shadow: 0 0 0.2vh ${props => props.markColor};
49 | }
50 |
51 | &.check:before {
52 | top: 6vh;
53 | left: 6vh;
54 | bottom: 6vh;
55 | right: 6vh;
56 | background-color: ${props => props.markColor};
57 | box-shadow: 0 0 2.8vh 4vh ${props => props.markColor};
58 | }
59 | }
60 |
61 | @media (max-aspect-ratio: 100/99) {
62 | width: 12.5vw;
63 | height: 12.5vw;
64 |
65 | &.mark:before {
66 | top: 0.7vw;
67 | left: 0.7vw;
68 | bottom: 0.7vw;
69 | right: 0.7vw;
70 | }
71 |
72 | &.move:before {
73 | border: 0.6vw solid ${props => props.markColor};
74 | box-shadow: 0 0 0.2vw ${props => props.markColor};
75 | }
76 |
77 | &.check:before {
78 | top: 6vw;
79 | left: 6vw;
80 | bottom: 6vw;
81 | right: 6vw;
82 | background-color: ${props => props.markColor};
83 | box-shadow: 0 0 2.8vw 4vw ${props => props.markColor};
84 | }
85 | }
86 |
87 | `;
88 |
89 | const Field = ({
90 | boardIndex,
91 | children,
92 | movePiece,
93 | isEven,
94 | isStart,
95 | isEnd,
96 | isPossibleTarget,
97 | isInCheck
98 | }) => {
99 | const [, dropRef] = useDrop({
100 | accept: 'PIECE',
101 | drop: (item, monitor) => {
102 | movePiece(item.pieceId, item.boardIndex, boardIndex);
103 | }
104 | });
105 |
106 | const fieldMarkStyle = isStart || isEnd || isPossibleTarget ? ' mark move'
107 | : isInCheck ? 'mark check'
108 | : '';
109 |
110 | const markColor = isPossibleTarget ? 'rgba(169, 189, 0, 0.69)'
111 | : isStart ? '#dc322f9f'
112 | : isEnd ? '#dc322faf'
113 | : isInCheck ? '#ff322f'
114 | : 'white';
115 |
116 | return (
117 |
123 | {children}
124 |
125 | );
126 | };
127 |
128 | export default Field;
129 |
--------------------------------------------------------------------------------
/assembly/__tests__/bitboard.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | import { antiDiagonalAttacks, diagonalAttacks, horizontalAttacks, verticalAttacks } from '../bitboard';
21 | import { fromBitBoardString, toBitBoardString } from '../util';
22 |
23 | describe("Bitboard ray attacks", () => {
24 | it("finds diagonal attacks", () => {
25 | expect(toBitBoardString(diagonalAttacks(0, 7))).toBe("00000000/00000010/00000100/00001000/00010000/00100000/01000000/10000000");
26 | expect(toBitBoardString(diagonalAttacks(0, 56))).toBe("00000001/00000010/00000100/00001000/00010000/00100000/01000000/00000000");
27 | })
28 |
29 | it("finds diagonal attacks with blockers", () => {
30 | const blockers = fromBitBoardString("00000000/00000000/00000100/00000000/00000000/00000000/00000000/00000000")
31 | expect(toBitBoardString(diagonalAttacks(blockers, 7))).toBe("00000000/00000010/00000100/00000000/00000000/00000000/00000000/00000000");
32 | expect(toBitBoardString(diagonalAttacks(blockers, 56))).toBe("00000000/00000000/00000100/00001000/00010000/00100000/01000000/00000000");
33 | })
34 |
35 | it("finds anti-diagonal attacks", () => {
36 | expect(toBitBoardString(antiDiagonalAttacks(0, 0))).toBe("00000000/01000000/00100000/00010000/00001000/00000100/00000010/00000001");
37 | expect(toBitBoardString(antiDiagonalAttacks(0, 63))).toBe("10000000/01000000/00100000/00010000/00001000/00000100/00000010/00000000");
38 | })
39 |
40 | it("finds anti-diagonal attacks with blockers", () => {
41 | const blockers = fromBitBoardString("00000000/00000000/00000100/00000000/00000000/00000000/00000010/00000000")
42 | expect(toBitBoardString(antiDiagonalAttacks(blockers, 0))).toBe("00000000/01000000/00100000/00010000/00001000/00000100/00000010/00000000");
43 | expect(toBitBoardString(antiDiagonalAttacks(blockers, 63))).toBe("00000000/00000000/00000000/00000000/00000000/00000000/00000010/00000000");
44 |
45 | expect(toBitBoardString(antiDiagonalAttacks(blockers, 63))).toBe("00000000/00000000/00000000/00000000/00000000/00000000/00000010/00000000");
46 | })
47 |
48 | it("finds anti-diagonal attacks with multiple blockers", () => {
49 | const blockers = fromBitBoardString("10000110/01101111/10110100/00101010/00101010/10110100/01101111/10000110")
50 | expect(toBitBoardString(antiDiagonalAttacks(blockers, 30))).toBe("00000000/00000000/00000100/00000000/00000001/00000000/00000000/00000000");
51 | });
52 |
53 | it("finds horizontal attacks", () => {
54 | expect(toBitBoardString(horizontalAttacks(0, 0))).toBe("01111111/00000000/00000000/00000000/00000000/00000000/00000000/00000000");
55 | expect(toBitBoardString(horizontalAttacks(0, 7))).toBe("11111110/00000000/00000000/00000000/00000000/00000000/00000000/00000000");
56 | })
57 |
58 | it("finds vertical attacks", () => {
59 | expect(toBitBoardString(verticalAttacks(0, 0))).toBe("00000000/10000000/10000000/10000000/10000000/10000000/10000000/10000000");
60 | expect(toBitBoardString(verticalAttacks(0, 63))).toBe("00000001/00000001/00000001/00000001/00000001/00000001/00000001/00000000");
61 | })
62 | });
63 |
--------------------------------------------------------------------------------
/assembly/__tests__/perft.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { fromFEN } from '../fen';
20 | import { perft } from '../perft';
21 |
22 |
23 | // Compare number of computed nodes for various positions and depths with results listed here: https://www.chessprogramming.org/Perft_Results
24 | describe('Perft - move generation validation', () => {
25 | it('generates correct number of nodes for initial position', () => {
26 | const fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
27 | expect(perft(fromFEN(fen), 0)).toBe(1, "depth 0");
28 | expect(perft(fromFEN(fen), 1)).toBe(20, "depth 1");
29 | expect(perft(fromFEN(fen), 2)).toBe(400, "depth 2");
30 | expect(perft(fromFEN(fen), 3)).toBe(8902, "depth 3");
31 | expect(perft(fromFEN(fen), 4)).toBe(197281, "depth 4");
32 | });
33 |
34 | it('generates correct number of nodes for "Test Position 2"', () => {
35 | const fen = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1";
36 | expect(perft(fromFEN(fen), 1)).toBe(48, "depth 1");
37 | expect(perft(fromFEN(fen), 2)).toBe(2039, "depth 2");
38 | expect(perft(fromFEN(fen), 3)).toBe(97862, "depth 3");
39 | });
40 |
41 | it('generates correct number of nodes for "Test Position 3"', () => {
42 | const fen = "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1";
43 | expect(perft(fromFEN(fen), 1)).toBe(14, "depth 1");
44 | expect(perft(fromFEN(fen), 2)).toBe(191, "depth 2");
45 | expect(perft(fromFEN(fen), 3)).toBe(2812, "depth 3");
46 | expect(perft(fromFEN(fen), 4)).toBe(43238, "depth 4");
47 | expect(perft(fromFEN(fen), 5)).toBe(674624, "depth 5");
48 | });
49 |
50 | it('generates correct number of nodes for "Test Position 4"', () => {
51 | const fen = "r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1";
52 | expect(perft(fromFEN(fen), 1)).toBe(6, "depth 1");
53 | expect(perft(fromFEN(fen), 2)).toBe(264, "depth 2");
54 | expect(perft(fromFEN(fen), 3)).toBe(9467, "depth 3");
55 | expect(perft(fromFEN(fen), 4)).toBe(422333, "depth 4");
56 | });
57 |
58 | it('generates correct number of nodes for "Test Position 5"', () => {
59 | expect(perft(fromFEN("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"), 1)).toBe(44, "depth 1");
60 | expect(perft(fromFEN("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"), 2)).toBe(1486, "depth 2");
61 | expect(perft(fromFEN("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"), 3)).toBe(62379, "depth 3");
62 | expect(perft(fromFEN("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"), 4)).toBe(2103487, "depth 4");
63 | });
64 |
65 | it('generates correct number of nodes for "Test Position 6"', () => {
66 | expect(perft(fromFEN("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"), 1)).toBe(46, "depth 1");
67 | expect(perft(fromFEN("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"), 2)).toBe(2079, "depth 2");
68 | expect(perft(fromFEN("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"), 3)).toBe(89890, "depth 3");
69 | });
70 |
71 | });
72 |
73 |
74 |
--------------------------------------------------------------------------------
/assembly/util.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { BLACK, Board, WHITE } from './board';
20 |
21 | @inline
22 | export function sign(value: i32): i32 {
23 | if (value == 0) {
24 | return 0;
25 | }
26 |
27 | return value < 0 ? -1 : 1;
28 | }
29 |
30 | // Checks whether the given values have the same color, by comparing the sign (+/-)
31 | @inline
32 | export function sameColor(a: i32, b: i32): bool {
33 | return (a ^ b) >= 0;
34 | }
35 |
36 | // Checks whether the given values have different colors, by comparing the sign (+/-)
37 | @inline
38 | export function differentColor(a: i32, b: i32): bool {
39 | return (a ^ b) < 0;
40 | }
41 |
42 | // Only parses 0s and 1s. All other characters are ignored and can for example be used as separators (10001000/00110011/...)
43 | // Note: the printing order starts with the least significant bit (index 0) up to the most significant bit (index 63), so
44 | // the string representation is reversed (i.e. for value 1: "10000000/00000000/[...]" instead of "[...]/00000000/00000001")
45 | export function fromBitBoardString(bits: string): u64 {
46 | let result: u64 = 0;
47 | let bitCount = 0;
48 | for (let i = bits.length - 1; i >= 0; i--) {
49 | const char = bits.charAt(i);
50 |
51 | if (char == '1') {
52 | bitCount++;
53 | if (bitCount > 64) {
54 | throw new Error("Can not parse bit string with more than 64 bits");
55 | }
56 | result <<= 1;
57 | result |= 1;
58 | } else if (char == '0') {
59 | bitCount++;
60 | if (bitCount > 64) {
61 | throw new Error("Can not parse bit string with more than 64 bits");
62 | }
63 | result <<= 1;
64 | }
65 | }
66 |
67 | return result;
68 | }
69 |
70 | export function toBitBoardString(value: u64, separator: string = '/'): string {
71 | let result = "";
72 | for (let i = 0; i < 64; i++) {
73 | if (i != 0 && i % 8 == 0) {
74 | result += separator
75 | }
76 | if ((value & (1 << i)) != 0) {
77 | result += "1";
78 | } else {
79 | result += "0";
80 | }
81 | }
82 | return result;
83 | }
84 |
85 | export function moveKing(board: Board, piece: i32, location: i32): void {
86 | const color = piece < 0 ? BLACK : WHITE;
87 |
88 | const kingPos = board.findKingPosition(color);
89 | board.removePiece(kingPos);
90 |
91 | board.addPiece(color, abs(piece), location);
92 | const state = board.getState();
93 | board.updateKingPosition(color, location);
94 | board.setState(state);
95 | }
96 |
97 | // Packs two scores (i16) into a single value (u32)
98 | @inline
99 | export function packScores(a: i16, b: i16): u32 {
100 | return (u32(b) << 16) | (u32(a) & 0xFFFF);
101 | }
102 |
103 | // Unpacks the first score from a packed value (see packScore)
104 | @inline
105 | export function unpackFirstScore(packed: u32): i16 {
106 | return i16(packed & 0xFFFF);
107 | }
108 |
109 | // Unpacks the second score from a packed value (see packScore)
110 | @inline
111 | export function unpackSecondScore(packed: u32): i16 {
112 | return i16(packed >> 16);
113 | }
114 |
--------------------------------------------------------------------------------
/src/ui/img/black_queen.svg:
--------------------------------------------------------------------------------
1 |
2 |
119 |
--------------------------------------------------------------------------------
/assembly/transposition-table.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | export const MAX_HASH_SIZE_MB = 768;
20 |
21 | // Transposition table entry
22 | // Bits 63 - 23: 41 highest bits of the hash
23 | const HASHCHECK_BITSIZE = 41;
24 | const HASHCHECK_MASK: u64 = 0b1111111111111111111111111111111111111111100000000000000000000000;
25 |
26 | // Bits 22 - 17: Depth
27 | export const TRANSPOSITION_MAX_DEPTH = 63;
28 | const DEPTH_BITSHIFT = 17;
29 | const DEPTH_MASK: u64 = 0b111111;
30 |
31 | // Bits 16 - 15: Score Type
32 | export enum ScoreType {
33 | EXACT = 0,
34 | UPPER_BOUND,
35 | LOWER_BOUND
36 | }
37 |
38 | const SCORE_TYPE_BITSHIFT = 15;
39 | const SCORE_TYPE_MASK: u64 = 0b11;
40 |
41 | // Bits 14 - 0: Age
42 | const AGE_MASK: u64 = 0b111111111111111;
43 |
44 |
45 | export const DEFAULT_SIZE_MB = 32;
46 | const perEntryByteSize = 8 + 4;
47 |
48 | export class TranspositionTable {
49 | private indexMask: u64;
50 | private entries: StaticArray = new StaticArray(0);
51 | private moves: StaticArray = new StaticArray(0);
52 | private age: i32;
53 |
54 | constructor() {
55 | this.resize(DEFAULT_SIZE_MB, true);
56 | }
57 |
58 | resize(sizeInMB: u32, initialize: bool = false): void {
59 | // Calculate table size as close to the desired sizeInMB as possible, but never above it
60 | const sizeInBytes = sizeInMB * 1_048_576;
61 | const entryCount = sizeInBytes / perEntryByteSize;
62 | const indexBitCount = 31 - clz(entryCount | 1);
63 |
64 | const size = (1 << indexBitCount);
65 | if (initialize || size != this.entries.length) {
66 | this.indexMask = size - 1;
67 | this.entries = new StaticArray(size);
68 | this.moves = new StaticArray(size);
69 | }
70 | }
71 |
72 | increaseAge(): void {
73 | this.age = (this.age + 1) & i32(AGE_MASK);
74 | }
75 |
76 | writeEntry(hash: u64, depth: i32, scoredMove: i32, type: ScoreType): void {
77 | const index = this.calculateIndex(hash);
78 |
79 | const entry = unchecked(this.entries[index]);
80 | if (entry != 0 && i32(entry & AGE_MASK) == this.age && depth < i32((entry >> DEPTH_BITSHIFT) & DEPTH_MASK)) {
81 | return;
82 | }
83 |
84 | let newEntry: u64 = hash & HASHCHECK_MASK;
85 | newEntry |= (depth << DEPTH_BITSHIFT);
86 | newEntry |= (type << SCORE_TYPE_BITSHIFT);
87 | newEntry |= this.age;
88 |
89 | unchecked(this.entries[index] = newEntry);
90 | unchecked(this.moves[index] = scoredMove);
91 | }
92 |
93 | getEntry(hash: u64): u64 {
94 | const index = this.calculateIndex(hash);
95 |
96 | const entry = unchecked(this.entries[index]);
97 | const ageDiff = this.age - i32(entry & AGE_MASK);
98 |
99 | if (entry == 0 || ageDiff < 0 || ageDiff > 1 || (entry & HASHCHECK_MASK) != (hash & HASHCHECK_MASK)) {
100 | return 0;
101 | }
102 |
103 | return u64(unchecked(this.moves[index])) << 32 | (entry & ~HASHCHECK_MASK);
104 | }
105 |
106 | private calculateIndex(hash: u64): i32 {
107 | return i32(hash & this.indexMask);
108 | }
109 |
110 | clear(): void {
111 | for (let i = 0; i < this.entries.length; i++) {
112 | unchecked(this.entries[i] = 0);
113 | unchecked(this.moves[i] = 0);
114 | }
115 | this.age = 0;
116 | }
117 |
118 | }
119 |
120 |
121 | export function getScoredMove(entry: u64): i32 {
122 | return i32(entry >> 32);
123 | }
124 |
125 | export function getDepth(entry: u64): i32 {
126 | return i32((entry >> DEPTH_BITSHIFT) & DEPTH_MASK);
127 | }
128 |
129 | export function getScoreType(entry: u64): ScoreType {
130 | return i32((entry >> SCORE_TYPE_BITSHIFT) & SCORE_TYPE_MASK);
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/assembly/history-heuristics.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { TRANSPOSITION_MAX_DEPTH } from './transposition-table';
20 | import { WHITE } from './board';
21 |
22 | const PLAYED_MOVE_THRESHOLDS = calculateMoveThresholds();
23 |
24 | /* Stores information about non-capture moves, which caused a cut-off during search */
25 | export class HistoryHeuristics {
26 | private primaryKillers: StaticArray = new StaticArray(TRANSPOSITION_MAX_DEPTH);
27 | private secondaryKillers: StaticArray = new StaticArray(TRANSPOSITION_MAX_DEPTH);
28 | private cutOffHistory: StaticArray = new StaticArray(2 * 64 * 64);
29 | private playedMoveHistory: StaticArray = new StaticArray(2 * 64 * 64);
30 |
31 | clear(): void {
32 | for (let i = 0; i < this.primaryKillers.length; i++) {
33 | unchecked(this.primaryKillers[i] = 0);
34 | unchecked(this.secondaryKillers[i] = 0);
35 | }
36 | this.clearHistory();
37 | }
38 |
39 | clearHistory(): void {
40 | for (let i = 0; i < this.cutOffHistory.length; i++) {
41 | unchecked(this.cutOffHistory[i] = 0);
42 | unchecked(this.playedMoveHistory[i] = 0);
43 | }
44 | }
45 |
46 | @inline
47 | getPrimaryKiller(ply: i32): i32 {
48 | return unchecked(this.primaryKillers[ply]);
49 | }
50 |
51 | @inline
52 | getSecondaryKiller(ply: i32): i32 {
53 | return unchecked(this.secondaryKillers[ply]);
54 | }
55 |
56 | @inline
57 | update(ply: i32, color: i32, moveStart: i32, moveEnd: i32, move: i32): void {
58 | const colOffset = color == WHITE ? 0 : 64 * 64;
59 | unchecked(this.cutOffHistory[colOffset + moveStart + moveEnd * 64]++);
60 |
61 | this.updateKillerMoveHistory(ply, move);
62 | }
63 |
64 | @inline
65 | private updateKillerMoveHistory(ply: i32, move: i32): void {
66 | const currentPrimary = unchecked(this.primaryKillers[ply]);
67 | if (currentPrimary != move) {
68 | unchecked(this.primaryKillers[ply] = move);
69 | unchecked(this.secondaryKillers[ply] = currentPrimary);
70 | }
71 | }
72 |
73 | @inline
74 | updatePlayedMoves(color: i32, moveStart: i32, moveEnd: i32): void {
75 | const colOffset = color == WHITE ? 0 : 64 * 64;
76 | unchecked(this.playedMoveHistory[colOffset + moveStart + moveEnd * 64]++);
77 | }
78 |
79 | // Returns a score between 0 and 512, which indicates how likely it is to cause a cut-off during search (higher scores = more likely).
80 | @inline
81 | getHistoryScore(color: i32, moveStart: i32, moveEnd: i32): i32 {
82 | const colOffset = color == WHITE ? 0 : 64 * 64;
83 | const index = colOffset + moveStart + moveEnd * 64;
84 | const playedMoveCount = unchecked(this.playedMoveHistory[index]);
85 | if (playedMoveCount == 0) {
86 | return 0;
87 | }
88 |
89 | return i32(unchecked(this.cutOffHistory[index]) * 512 / playedMoveCount);
90 | }
91 |
92 | // Returns true, if the history contains sufficient information about the given move, to indicate
93 | // that it is very unlikely to cause a cut-off during search
94 | @inline
95 | hasNegativeHistory(color: i32, depth: i32, moveStart: i32, moveEnd: i32): bool {
96 | const colOffset = color == WHITE ? 0 : 64 * 64;
97 | const index = colOffset + moveStart + moveEnd * 64;
98 |
99 | const playedMoveCount = unchecked(this.playedMoveHistory[index]);
100 | if (playedMoveCount < unchecked(PLAYED_MOVE_THRESHOLDS[depth])) {
101 | return false;
102 | }
103 |
104 | return (unchecked(this.cutOffHistory[index]) * 512 / playedMoveCount) == 0;
105 | }
106 | }
107 |
108 | function calculateMoveThresholds(): StaticArray {
109 | const thresholds = new StaticArray(TRANSPOSITION_MAX_DEPTH);
110 | let threshold: f32 = 2;
111 | for (let depth = 0; depth < TRANSPOSITION_MAX_DEPTH; depth++) {
112 | unchecked(thresholds[depth] = u64(threshold));
113 | threshold *= 1.6;
114 | }
115 |
116 | return thresholds;
117 | }
118 |
119 |
120 |
--------------------------------------------------------------------------------
/assembly/uci-move-notation.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import { Board, EMPTY } from './board';
20 | import { BISHOP, KNIGHT, QUEEN, ROOK } from './pieces';
21 | import { decodeEndIndex, decodePiece, decodeStartIndex, encodeMove } from './move-generation';
22 |
23 | const CHARCODE_A: i32 = 'a'.charCodeAt(0);
24 | const CHARCODE_H: i32 = 'h'.charCodeAt(0);
25 |
26 | const CHARCODE_1: i32 = '1'.charCodeAt(0);
27 | const CHARCODE_8: i32 = '8'.charCodeAt(0);
28 |
29 | // Returns column 0 to 7 for chars 'a' to 'h'
30 | function toNumericColumn(colChar: i32): i32 {
31 | if (colChar < CHARCODE_A || colChar > CHARCODE_H) {
32 | throw new Error("Invalid uci move colum char: " + String.fromCharCode(colChar));
33 | }
34 |
35 | return colChar - CHARCODE_A;
36 | }
37 |
38 | // Returns row 7 to 0 for chars '1' to '8'
39 | // Note: bitboard representation for a8 is 0, while h1 is 63
40 | function toNumericRow(rowChar: i32): i32 {
41 | if (rowChar < CHARCODE_1 || rowChar > CHARCODE_8) {
42 | throw new Error("Invalid uci move row char: " + String.fromCharCode(rowChar));
43 | }
44 |
45 | return CHARCODE_8 - rowChar;
46 | }
47 |
48 | function toNumericPiece(pieceChar: string): i32 {
49 | if (pieceChar == 'n') {
50 | return KNIGHT;
51 | } else if (pieceChar == 'b') {
52 | return BISHOP;
53 | } else if (pieceChar == 'r') {
54 | return ROOK;
55 | } else if (pieceChar == 'q') {
56 | return QUEEN;
57 | }
58 |
59 | throw new Error("Invalid uci move promotion piece: " + pieceChar);
60 | }
61 |
62 | function toUCISquare(row: i32, column: i32): string {
63 | return String.fromCharCode(CHARCODE_A + column) + String.fromCharCode(CHARCODE_8 - row);
64 | }
65 |
66 | function toUCIPromotionPiece(piece: i32): string {
67 | switch (piece) {
68 | case EMPTY: return "";
69 | case KNIGHT: return 'n';
70 | case BISHOP: return 'b';
71 | case ROOK: return 'r';
72 | case QUEEN: return 'q';
73 | default: throw new Error("Unexpected promotion piece id: " + piece.toString());
74 | }
75 | }
76 |
77 | export class UCIMove {
78 | start: i32;
79 | end: i32;
80 | promotionPiece: i32;
81 |
82 | constructor(start: i32, end: i32, promotionPiece: i32 = EMPTY) {
83 | this.start = start;
84 | this.end = end;
85 | this.promotionPiece = promotionPiece;
86 | }
87 |
88 | toEncodedMove(board: Board): i32 {
89 | const piece = this.promotionPiece != EMPTY ? this.promotionPiece : abs(board.getItem(this.start));
90 | return encodeMove(piece, this.start, this.end);
91 | }
92 |
93 | toUCINotation(): string {
94 | const startColumn = this.start & 7;
95 | const startRow = this.start / 8;
96 |
97 | const endColumn = this.end & 7;
98 | const endRow = this.end / 8 ;
99 |
100 | return toUCISquare(startRow, startColumn) + toUCISquare(endRow, endColumn) + toUCIPromotionPiece(this.promotionPiece);
101 | }
102 |
103 | static fromUCINotation(str: string): UCIMove {
104 | if (str.length < 4) {
105 | throw new Error("Invalid uci move notation: " + str);
106 | }
107 |
108 | const startColumn = toNumericColumn(str.charCodeAt(0));
109 | const startRow = toNumericRow(str.charCodeAt(1));
110 |
111 | const endColumn = toNumericColumn(str.charCodeAt(2));
112 | const endRow = toNumericRow(str.charCodeAt(3));
113 |
114 | const promotionPiece = str.length == 5 ? toNumericPiece(str.charAt(4)) : EMPTY;
115 |
116 | return new UCIMove((startRow * 8) + startColumn, endRow * 8 + endColumn, promotionPiece);
117 | }
118 |
119 | static fromEncodedMove(board: Board, move: i32): UCIMove {
120 | const targetPiece = decodePiece(move);
121 | const startIndex = decodeStartIndex(move);
122 | const endIndex = decodeEndIndex(move);
123 |
124 | const currentPiece = abs(board.getItem(startIndex));
125 | const promotionPiece = targetPiece != currentPiece ? targetPiece : EMPTY;
126 |
127 | return new UCIMove(startIndex, endIndex, promotionPiece);
128 | }
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/assembly/__tests__/util.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import {
20 | differentColor,
21 | fromBitBoardString,
22 | packScores,
23 | sameColor,
24 | toBitBoardString,
25 | unpackFirstScore,
26 | unpackSecondScore
27 | } from '../util';
28 |
29 |
30 | describe("fromBitBoardString", () => {
31 |
32 | it("parses bit board string correctly", () => {
33 | expect(fromBitBoardString("11000000/00000000/00000000/00000000/00000000/00000000/00000000/00000000")).toBe(0x3);
34 | expect(fromBitBoardString("11110000/00000000/00000000/00000000/00000000/00000000/00000000/00000000")).toBe(0xF);
35 | expect(fromBitBoardString("01010101/00000000/00000000/00000000/00000000/00000000/00000000/00000000")).toBe(0xAA);
36 | expect(fromBitBoardString("00000000/00000000/00000000/00000000/00000000/00000000/00000000/00000000")).toBe(0);
37 | expect(fromBitBoardString("11111111/11111111/11111111/11111111/11111111/11111111/11111111/11111111")).toBe(0xFFFFFFFFFFFFFFFF);
38 | expect(fromBitBoardString("00000000/00000000/00000000/00000000/00000000/00000000/00000000/00000001")).toBe(0x8000000000000000)
39 | });
40 |
41 | });
42 |
43 |
44 | describe("toBitBoardString", () => {
45 |
46 | it("writes correct bit board string", () => {
47 | expect(toBitBoardString(0x3)).toBe(("11000000/00000000/00000000/00000000/00000000/00000000/00000000/00000000"));
48 | expect(toBitBoardString(0xF)).toBe(("11110000/00000000/00000000/00000000/00000000/00000000/00000000/00000000"));
49 | expect(toBitBoardString(0xAA)).toBe(("01010101/00000000/00000000/00000000/00000000/00000000/00000000/00000000"));
50 | expect(toBitBoardString(0)).toBe(("00000000/00000000/00000000/00000000/00000000/00000000/00000000/00000000"));
51 | expect(toBitBoardString(0xFFFFFFFFFFFFFFFF)).toBe(("11111111/11111111/11111111/11111111/11111111/11111111/11111111/11111111"));
52 | expect(toBitBoardString(0x8000000000000000)).toBe("00000000/00000000/00000000/00000000/00000000/00000000/00000000/00000001");
53 | });
54 |
55 | });
56 |
57 |
58 | describe("sameColor", () => {
59 |
60 | it("returns true for the same color (black)", () => {
61 | expect(sameColor(-2, -3)).toBeTruthy();
62 | expect(sameColor(-4, -1)).toBeTruthy();
63 | });
64 |
65 | it("returns true for the same color (white)", () => {
66 | expect(sameColor(2, 3)).toBeTruthy();
67 | expect(sameColor(4, 1)).toBeTruthy();
68 | });
69 |
70 | it("returns false for different colors", () => {
71 | expect(sameColor(-2, 3)).toBeFalsy();
72 | expect(sameColor(4, -1)).toBeFalsy();
73 | });
74 |
75 | });
76 |
77 |
78 | describe("differentColor", () => {
79 |
80 | it("returns false for the same color (black)", () => {
81 | expect(differentColor(-2, -3)).toBeFalsy();
82 | expect(differentColor(-4, -1)).toBeFalsy();
83 | });
84 |
85 | it("returns false for the same color (white)", () => {
86 | expect(differentColor(2, 3)).toBeFalsy();
87 | expect(differentColor(4, 1)).toBeFalsy();
88 | });
89 |
90 | it("returns true for different colors", () => {
91 | expect(differentColor(-2, 3)).toBeTruthy();
92 | expect(differentColor(4, -1)).toBeTruthy();
93 | });
94 |
95 | });
96 |
97 |
98 | describe("packScores", () => {
99 | it("packs two i16 values into one u32 value", () => {
100 | const a: i16 = 1;
101 | const b: i16 = 2;
102 | const packed = packScores(a, b);
103 | expect(unpackFirstScore(packed)).toBe(a, "first");
104 | expect(unpackSecondScore(packed)).toBe(b, "second");
105 | });
106 |
107 | it("packs two maximum i16 values into one u32 value", () => {
108 | const a = i16.MAX_VALUE;
109 | const b = i16.MAX_VALUE;
110 | const packed = packScores(a, b);
111 | expect(unpackFirstScore(packed)).toBe(a, "first");
112 | expect(unpackSecondScore(packed)).toBe(b, "second");
113 | });
114 |
115 | it("packs two minimum i16 values into one u32 value", () => {
116 | const a = i16.MIN_VALUE;
117 | const b = i16.MIN_VALUE;
118 | const packed = packScores(a, b);
119 | expect(unpackFirstScore(packed)).toBe(a, "first");
120 | expect(unpackSecondScore(packed)).toBe(b, "second");
121 | });
122 | });
123 |
124 |
--------------------------------------------------------------------------------
/assembly/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | // The entry file of your WebAssembly module.
20 | ///
21 | ///
22 |
23 | import EngineControl from './engine';
24 | import { isCheckMate as isCheckMateFn } from './move-generation';
25 | import { BLACK, calculatePieceSquareTables, WHITE } from './board';
26 | import { randomizeOpeningBookMoves } from './opening-book';
27 | import { stdio } from './io';
28 | import { VERSION } from '../version';
29 |
30 | const DIFFICULTY_LEVELS: Array> = [
31 | [1, 0],
32 | [3, 0],
33 | [5, 0],
34 | [7, 0],
35 | [9, 0],
36 | [11, 1250]
37 | ]
38 |
39 | export const INT32ARRAY_ID = idof();
40 |
41 | const GAME_ENDED = 1;
42 | const CHECK_MATE = 2;
43 | const STALE_MATE = 4;
44 | const THREEFOLD_REPETITION_DRAW = 8;
45 | const FIFTYMOVE_DRAW = 16;
46 | const INSUFFICIENT_MATERIAL_DRAW = 32;
47 | const ACTIVE_PLAYER = 64; // 0 - White, 1 - Black
48 | const WHITE_IN_CHECK = 128;
49 | const BLACK_IN_CHECK = 256;
50 |
51 | let isInitialized = false;
52 |
53 | // Resets the engine state for a new game
54 | export function newGame(): void {
55 | EngineControl.reset();
56 |
57 | if (!isInitialized) {
58 | stdio.writeLine("Wasabi " + VERSION);
59 | calculatePieceSquareTables();
60 |
61 | EngineControl.resizeTranspositionTable(64);
62 | EngineControl.setUseOpeningBook(true);
63 | randomizeOpeningBookMoves();
64 | isInitialized = true;
65 | }
66 | }
67 |
68 | // Sets the current board to the given position and returns the encoded game state
69 | export function setPosition(fen: string, moves: Int32Array): Int32Array {
70 | EngineControl.setPosition(fen);
71 |
72 | for (let i = 0; i < moves.length; i++) {
73 | EngineControl.performMove(moves[i]);
74 | }
75 |
76 | return encodeChessState(false);
77 | }
78 |
79 | // Calculates the best move for the current player using the given difficulty level
80 | export function calculateMove(difficultyLevel: i32): i32 {
81 | const levelSettings = DIFFICULTY_LEVELS[difficultyLevel - 1];
82 |
83 | const maxTime = levelSettings[1];
84 | const minimumSearchDepth = levelSettings[0];
85 |
86 | return EngineControl.findBestMove(minimumSearchDepth, maxTime, maxTime === 0);
87 | }
88 |
89 | // Applies the given move to the current board and returns the encoded game state
90 | export function performMove(encodedMove: i32): Int32Array {
91 | EngineControl.performMove(encodedMove);
92 |
93 | return encodeChessState(true);
94 | }
95 |
96 |
97 | // Encodes the board (index 0-63), the game state (index 64) and all possible moves for the current player (index 65+)
98 | function encodeChessState(checkThreefoldRepetition: bool): Int32Array {
99 | const board = EngineControl.getBoard();
100 | const moves = EngineControl.generateAvailableMoves();
101 |
102 | const stateArray = new Int32Array(64 + 1 + moves.length);
103 |
104 | for (let i = 0; i < 64; i++) {
105 | stateArray[i] = board.getItem(i);
106 | }
107 |
108 | const isCheckMate: bool = isCheckMateFn(board, board.getActivePlayer());
109 | const isStaleMate: bool = moves.length == 0;
110 | const isThreefoldRepetition: bool = checkThreefoldRepetition && board.isThreefoldRepetion();
111 | const isFiftyMoveDraw: bool = board.isFiftyMoveDraw();
112 | const isInsufficientMaterialDraw: bool = board.isInsufficientMaterialDraw();
113 |
114 | const hasGameEnded: bool = isCheckMate || isStaleMate || isThreefoldRepetition || isFiftyMoveDraw || isInsufficientMaterialDraw;
115 |
116 | stateArray[64] = (hasGameEnded ? GAME_ENDED : 0)
117 | | (isCheckMate ? CHECK_MATE : 0)
118 | | (isStaleMate ? STALE_MATE : 0)
119 | | (isThreefoldRepetition ? THREEFOLD_REPETITION_DRAW : 0)
120 | | (isFiftyMoveDraw ? FIFTYMOVE_DRAW : 0)
121 | | (isInsufficientMaterialDraw ? INSUFFICIENT_MATERIAL_DRAW : 0)
122 | | ((board.getActivePlayer() == BLACK) ? ACTIVE_PLAYER : 0)
123 | | (board.isInCheck(WHITE) ? WHITE_IN_CHECK : 0)
124 | | (board.isInCheck(BLACK) ? BLACK_IN_CHECK : 0)
125 |
126 | for (let i = 0; i < moves.length; i++) {
127 | stateArray[i + 65] = moves[i];
128 | }
129 |
130 | return stateArray;
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/src/ui/GameMenu.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React from 'react';
20 | import styled from 'styled-components/macro';
21 | import { WHITE } from '../engine/constants';
22 | import AnimatedSpinner from './AnimatedSpinner';
23 | import {
24 | faBalanceScale,
25 | faDiceFive,
26 | faDiceFour,
27 | faDiceOne,
28 | faDiceSix,
29 | faDiceThree,
30 | faDiceTwo,
31 | faExchangeAlt,
32 | faMedal,
33 | faPlus,
34 | faRetweet,
35 | faRobot,
36 | faUndo
37 | } from '@fortawesome/free-solid-svg-icons';
38 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
39 |
40 | const MenuBar = styled.div`
41 | display: flex;
42 | flex-direction: column;
43 | margin-top: 0.7rem;
44 | margin-left: 1rem;
45 | text-align: center;
46 | align-items: center;
47 |
48 | // center menubar below board, if window width is < window height
49 | @media (max-aspect-ratio: 100/99) {
50 | margin-left: auto;
51 | margin-right: auto;
52 | flex-flow: column-reverse;
53 | }
54 | `;
55 |
56 | const MenuItem = styled.div`
57 | position: relative;
58 | display: ${props => (props.hidden ? 'none' : 'flex')};
59 | padding-bottom: 0.2rem;
60 | flex-direction: column;
61 |
62 | @media (max-aspect-ratio: 100/99) {
63 | flex-direction: row;
64 | align-self: center;
65 | }
66 | `;
67 |
68 | const GameButton = styled.button`
69 | background: white;
70 | color: #073642;
71 | border: 1px solid #073642;
72 | border-radius: 0.3rem;
73 | font-size: 1rem;
74 | font-weight: bold;
75 | padding: 0.5rem 0.3rem;
76 | width: 2.5rem;
77 | margin: 0.2rem;
78 | box-shadow: 1px 1px 1px #073642;
79 |
80 | &[disabled] {
81 | display: none;
82 | }
83 |
84 | & :hover {
85 | background: #073642;
86 | color: white;
87 | cursor: pointer;
88 | }
89 | `;
90 |
91 | const GameResult = styled(MenuItem)`
92 | margin-top: 0.3rem;
93 | font-weight: bold;
94 | font-size: 1.5rem;
95 | color: #073642;
96 | width: 100%;
97 | align-items: center;
98 |
99 | svg {
100 | margin-left: 1rem;
101 | margin-right: 1rem;
102 | }
103 | `;
104 |
105 | const IconRadioInput = styled.input`
106 | display: none;
107 |
108 | &:checked + label {
109 | opacity: 1;
110 | }
111 | `;
112 |
113 | const IconRadioLabel = styled.label`
114 | color: #073642;
115 | margin: 0.06rem 0.2rem;
116 | opacity: 0.2;
117 |
118 | & :hover {
119 | opacity: 0.5;
120 | cursor: pointer;
121 | }
122 | `
123 |
124 | const colorName = color => (color === WHITE ? 'White' : 'Black');
125 |
126 | const getGameResultIcon = (winningPlayerColor, humanPlayerColor) => {
127 | if (!winningPlayerColor) {
128 | return faBalanceScale;
129 | }
130 |
131 | return winningPlayerColor === humanPlayerColor
132 | ? faMedal
133 | : faRobot;
134 | }
135 |
136 | const GameMenu = ({
137 | isAiThinking,
138 | firstMovePlayed,
139 | humanPlayerColor,
140 | gameEnded,
141 | winningPlayerColor,
142 | startNewGame,
143 | switchSides,
144 | rotateBoard,
145 | difficultyLevel,
146 | setDifficultyLevel,
147 | canUndoMove,
148 | undoMove,
149 | }) => (
150 |
151 | {gameEnded &&
152 |
153 |
154 | {winningPlayerColor ? colorName(winningPlayerColor) + ' wins!' : 'Draw!'}
155 |
156 | }
157 |
158 |
177 |
178 |
189 |
190 | {isAiThinking && }
191 |
192 |
193 | );
194 |
195 | const IconRadioButtons = ({currentValue, name, options, onChange}) => (
196 | <>
197 | {options.map(({value, description, icon}) => (
198 |
199 | onChange(e.target.value)} />
200 |
201 |
202 |
203 |
204 | ))}
205 | >
206 | );
207 |
208 | export default GameMenu;
209 |
--------------------------------------------------------------------------------
/assembly/__tests__/engine.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | import { B, K, N, P, Q, QUEEN, R } from '../pieces';
21 | import EngineControl from '../engine';
22 | import {
23 | BLACK,
24 | Board, calculatePieceSquareTables, NO_CASTLING_RIGHTS,
25 | WHITE
26 | } from '../board';
27 | import { decodeMove, decodeScore, encodeMove, encodeScoredMove, isCheckMate } from '../move-generation';
28 |
29 | beforeAll(() => {
30 | calculatePieceSquareTables();
31 | });
32 |
33 | describe('Encode and decode scored moves', () => {
34 | it('Zero score', () => {
35 | const score = 0;
36 | const move = encodeMove(QUEEN, 2, 63);
37 |
38 | const scoredMove = encodeScoredMove(move, score);
39 |
40 | expect(decodeMove(scoredMove)).toBe(move, "Correctly extracted move");
41 | expect(decodeScore(scoredMove)).toBe(score, "Correctly extracted score");
42 | });
43 |
44 | it('Positive scores', () => {
45 | const score = 16383;
46 | const move = encodeMove(QUEEN, 2, 63);
47 |
48 | const scoredMove = encodeScoredMove(move, score);
49 |
50 | expect(decodeMove(scoredMove)).toBe(move, "Correctly extracted move");
51 | expect(decodeScore(scoredMove)).toBe(score, "Correctly extracted score");
52 | });
53 |
54 | it('Negative score', () => {
55 | const score = -16383;
56 | const move = encodeMove(QUEEN, 2, 63);
57 |
58 | const scoredMove = encodeScoredMove(move, score);
59 |
60 | expect(decodeMove(scoredMove)).toBe(move, "Correctly extracted move");
61 | expect(decodeScore(scoredMove)).toBe(score, "Correctly extracted score");
62 | });
63 |
64 | });
65 |
66 | describe('Finds moves', () => {
67 | it('Finds mate in 1 move', () => {
68 | // prettier-ignore
69 | const board: Board = new Board([
70 | 0, 0, 0, 0, +K, 0, 0, 0,
71 | -R, 0, 0, 0, 0, 0, 0, 0,
72 | 0, 0, 0, 0, 0, 0, 0, 0,
73 | 0, 0, 0, 0, 0, 0, 0, 0,
74 | 0, 0, 0, 0, 0, 0, 0, 0,
75 | 0, 0, 0, 0, 0, 0, 0, 0,
76 | 0, 0, 0, -K, 0, 0, 0, 0,
77 | 0, 0, 0, 0, 0, 0, 0, -R,
78 | 0, 0, NO_CASTLING_RIGHTS
79 | ]);
80 |
81 | board.performEncodedMove(findBestMove(board, BLACK, 2));
82 |
83 | expect(isCheckMate(board, WHITE)).toBe(true);
84 | });
85 |
86 | it('Finds mate in two moves', () => {
87 | // prettier-ignore
88 | const board: Board = new Board([
89 | 0, 0, 0, 0, -K, 0, 0, 0,
90 | 0, 0, 0, 0, 0, 0, 0, 0,
91 | 0, 0, 0, 0, 0, 0, 0, 0,
92 | 0, 0, 0, 0, 0, 0, 0, 0,
93 | 0, 0, 0, 0, 0, 0, 0, 0,
94 | 0, 0, 0, 0, 0, 0, 0, 0,
95 | 0, 0, 0, 0, 0, 0, 0, 0,
96 | +R, 0, 0, 0, +K, 0, 0, +R,
97 | 0, 0, NO_CASTLING_RIGHTS
98 | ]);
99 |
100 | board.performEncodedMove(findBestMove(board, WHITE, 3));
101 | board.performEncodedMove(findBestMove(board, BLACK, 2));
102 | board.performEncodedMove(findBestMove(board, WHITE, 1));
103 |
104 | expect(isCheckMate(board, BLACK)).toBe(true);
105 | });
106 |
107 | it('Finds another mate in two moves', () => {
108 | // prettier-ignore
109 | const board: Board = new Board([
110 | 0, 0, 0, -B, -R, -R, -B, 0,
111 | 0, 0, +N, 0, 0, 0, 0, +B,
112 | 0, 0, 0, 0, 0, 0, 0, 0,
113 | 0, 0, -P, 0, 0, 0, 0, +Q,
114 | 0, 0, -P, 0, 0, -K, 0, 0,
115 | 0, 0, 0, 0, 0, +P, 0, 0,
116 | 0, 0, 0, 0, +P, 0, +K, +R,
117 | 0, 0, +N, 0, 0, +R, +B, 0,
118 | 0, 0, NO_CASTLING_RIGHTS
119 | ]);
120 |
121 | board.performEncodedMove(findBestMove(board, WHITE, 5));
122 | board.performEncodedMove(findBestMove(board, BLACK, 2));
123 | board.performEncodedMove(findBestMove(board, WHITE, 1));
124 |
125 | expect(isCheckMate(board, BLACK)).toBe(true);
126 | });
127 |
128 | it('Finds opening move', () => {
129 | // prettier-ignore
130 | const board: Board = new Board([
131 | -R, -N, -B, -Q, -K, -B, -N, -R,
132 | -P, -P, -P, -P, -P, -P, -P, -P,
133 | 0, 0, 0, 0, 0, 0, 0, 0,
134 | 0, 0, 0, 0, 0, 0, 0, 0,
135 | 0, 0, 0, 0, 0, 0, 0, 0,
136 | 0, 0, 0, 0, 0, 0, 0, 0,
137 | +P, +P, +P, +P, +P, +P, +P, +P,
138 | +R, +N, +B, +Q, +K, +B, +N, +R,
139 | 0, 0, 0
140 | ]);
141 |
142 | const move = findBestMove(board, WHITE, 4);
143 | expect(move).toBeGreaterThan(0, "An encoded move");
144 | });
145 |
146 | it('Does not sacrifice queen', () => {
147 | // prettier-ignore
148 | const board: Board = new Board([
149 | -R, 0, -B, -Q, 0, -R, 0, -K,
150 | 0, -P, -P, 0, -N, -P, 0, 0,
151 | -P, 0, 0, -P, -P, 0, 0, -P,
152 | 0, 0, 0, 0, 0, -P, 0, 0,
153 | +B, 0, +P, 0, +P, 0, 0, 0,
154 | 0, 0, 0, 0, 0, 0, +Q, 0,
155 | +P, +P, +P, +N, 0, +P, +P, +P,
156 | +R, 0, 0, 0, 0, +R, +K, 0,
157 | 0, 0, 0
158 | ]);
159 |
160 | const move = findBestMove(board, WHITE, 2);
161 | board.performEncodedMove(move);
162 | expect(move).not.toBe(encodeMove(5, 46, 14), "Must not sacrifice queen @14");
163 | expect(move).not.toBe(encodeMove(5, 46, 19), "Must not sacrifice queen @19");
164 | });
165 |
166 |
167 | it('Avoids stalemate when it is ahead of the opponent', () => {
168 | // prettier-ignore
169 | const board: Board = new Board([
170 | 0, 0, 0, 0, 0, 0, 0, 0,
171 | 0, 0, 0, 0, 0, 0, 0, R,
172 | 0, 0, 0, 0, 0, B, 0, 0,
173 | 0, 0, 0, N, 0, P, 0, 0,
174 | 0, 0, 0, 0, P, 0, -K, 0,
175 | 0, 0, 0, 0, 0, 0, 0, -R,
176 | 0, 0, 0, 0, 0, 0, P, K,
177 | 0, 0, 0, 0, 0, 0, 0, 0,
178 | 0, 0, NO_CASTLING_RIGHTS
179 | ]);
180 |
181 | const move = findBestMove(board, WHITE, 2);
182 | expect(move).not.toBe(encodeMove(4, 15, 47), "Using the rook to capture the black rook causes a stalemate");
183 | expect(move).toBe(encodeMove(1, 54, 47), "Using the pawn for the capture lets the game proceed");
184 | });
185 |
186 | it('Avoids threefold repetition', () => {
187 | // prettier-ignore
188 | const board: Board = new Board([
189 | 0, -K, 0, 0, 0, 0, 0, 0,
190 | 0, 0, 0, 0, 0, 0, R, 0,
191 | 0, 0, 0, 0, 0, 0, 0, 0,
192 | 0, 0, 0, 0, 0, 0, 0, 0,
193 | 0, 0, 0, 0, 0, 0, 0, 0,
194 | 0, 0, 0, 0, 0, 0, 0, 0,
195 | 0, 0, 0, 0, 0, 0, 0, 0,
196 | 0, 0, 0, 0, 0, 0, K, 0,
197 | 0, 0, NO_CASTLING_RIGHTS
198 | ]);
199 |
200 | board.performEncodedMove(findBestMove(board, WHITE, 1));
201 | const boardState1 = board.getHash();
202 |
203 | board.performEncodedMove(findBestMove(board, BLACK, 1));
204 | board.performEncodedMove(findBestMove(board, WHITE, 1));
205 | const boardState2 = board.getHash();
206 |
207 | board.performEncodedMove(findBestMove(board, BLACK, 1));
208 | board.performEncodedMove(findBestMove(board, WHITE, 1));
209 | const boardState3 = board.getHash();
210 |
211 | board.performEncodedMove(findBestMove(board, BLACK, 1));
212 | board.performEncodedMove(findBestMove(board, WHITE, 1));
213 | const boardState4 = board.getHash();
214 |
215 | board.performEncodedMove(findBestMove(board, BLACK, 1));
216 | board.performEncodedMove(findBestMove(board, WHITE, 1));
217 | const boardState5 = board.getHash();
218 |
219 | expect(boardState5 != boardState1).toBeTruthy("Threefold repetion!");
220 |
221 | });
222 | });
223 |
224 |
225 | // Test helper functions
226 | function findBestMove(board: Board, playerColor: i32, exactDepth: i32): i32 {
227 | EngineControl.setBoard(board);
228 | if (board.getActivePlayer() != playerColor) {
229 | board.performNullMove();
230 | }
231 | return EngineControl.findBestMove(exactDepth, 0, true);
232 | }
233 |
234 | function findBestMoveIncrementally(board: Board, playerColor: i32, minimumDepth: i32, timeLimitMillis: i32): i32 {
235 | EngineControl.setBoard(board);
236 | if (board.getActivePlayer() != playerColor) {
237 | board.performNullMove();
238 | }
239 | return EngineControl.findBestMove(minimumDepth, timeLimitMillis, true);
240 | }
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/src/ui/Game.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import React, { useCallback, useEffect, useState } from 'react';
20 | import styled from 'styled-components/macro';
21 |
22 | import engineWorkerLoader from 'workerize-loader!../engine/engine.worker'; // eslint-disable-line import/no-webpack-loader-syntax
23 | import { BLACK, P, WHITE } from '../engine/constants';
24 | import AnimatedSpinner from './AnimatedSpinner';
25 | import { Move } from '../engine/move';
26 | import Board from './Board';
27 | import GameMenu from './GameMenu';
28 | import PromotionPieceSelection from './PromotionPieceSelection';
29 |
30 | const engine = engineWorkerLoader();
31 |
32 | const GameArea = styled.div`
33 | display: flex;
34 | flex-wrap: wrap;
35 | `;
36 |
37 | const startPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
38 |
39 | const nextPlayer = playerColor => -playerColor;
40 |
41 | const Game = () => {
42 | const [rotateBoard, setRotateBoard] = useState(false);
43 | const [activePlayer, setActivePlayer] = useState(WHITE);
44 | const [humanPlayerColor, setHumanPlayerColor] = useState(WHITE);
45 | const [isAiTurn, setAiTurn] = useState(false);
46 | const [board, setBoard] = useState();
47 | const [gameEnded, setGameEnded] = useState(false);
48 | const [availableMoves, setAvailableMoves] = useState([]);
49 | const [currentPieceMoves, setCurrentPieceMoves] = useState(new Set());
50 | const [winningPlayer, setWinningPlayer] = useState();
51 | const [difficultyLevel, setDifficultyLevel] = useState(6);
52 | const [moveHistory, setMoveHistory] = useState([]);
53 | const [promotion, setPromotion] = useState(undefined);
54 | const [inCheck, setInCheck] = useState(0);
55 |
56 | const lastMove = moveHistory.length > 0 ? moveHistory[moveHistory.length - 1] : { start: -1, end: -1 };
57 |
58 | const clearAvailableMoves = () => setAvailableMoves([]);
59 |
60 | const addMove = useCallback(move => {
61 | setMoveHistory([...moveHistory, move])
62 | }, [setMoveHistory, moveHistory]);
63 |
64 | const updateGame = useCallback(async state => {
65 | setBoard(state.board);
66 | setAvailableMoves(state.moves);
67 |
68 | if (state.whiteInCheck) {
69 | setInCheck(WHITE);
70 | } else if (state.blackInCheck) {
71 | setInCheck(BLACK);
72 | } else {
73 | setInCheck(0);
74 | }
75 |
76 | if (state.gameEnded) {
77 | setGameEnded(true);
78 |
79 | if (state.checkMate) {
80 | setWinningPlayer(nextPlayer(state.activePlayer));
81 | }
82 | } else {
83 | setActivePlayer(state.activePlayer);
84 | }
85 |
86 | }, []);
87 |
88 | // Initialize chess engine and game state
89 | useEffect(() => {
90 | (async () => {
91 | await engine.init();
92 | await engine.newGame();
93 | const gameState = await engine.setPosition(startPosition, []);
94 | await updateGame(gameState);
95 | })();
96 | }, [updateGame]);
97 |
98 | const canMove = useCallback((start, end) => {
99 | return availableMoves.some(
100 | move => move.start === start && move.end === end
101 | );
102 | }, [availableMoves]);
103 |
104 | const asyncDelay = (millis) => new Promise(resolve => setTimeout(resolve, millis));
105 |
106 | const calculateAIMove = useCallback(async () => {
107 | clearAvailableMoves();
108 | setAiTurn(true);
109 |
110 | const [move] = await Promise.all([engine.calculateMove(difficultyLevel), asyncDelay(150)]);
111 |
112 | const gameState = await engine.performMove(move);
113 | setAiTurn(false);
114 | await updateGame(gameState);
115 | addMove(move);
116 | }, [difficultyLevel, addMove, updateGame]);
117 |
118 | const switchSides = async () => {
119 | setRotateBoard(true);
120 | setHumanPlayerColor(-humanPlayerColor);
121 | setAiTurn(true);
122 | };
123 |
124 | // Calculate next AI move whenever isAiTurn is set to true
125 | useEffect( () => {
126 | if (isAiTurn) {
127 | (async () => {
128 | await calculateAIMove();
129 | })();
130 | }
131 | }, [isAiTurn, calculateAIMove]);
132 |
133 | const startNewGame = async () => {
134 | setGameEnded(false);
135 | setWinningPlayer(undefined);
136 | setActivePlayer(WHITE);
137 | setHumanPlayerColor(WHITE);
138 | setMoveHistory([]);
139 | setCurrentPieceMoves(new Set());
140 |
141 | await engine.newGame();
142 | const gameState = await engine.setPosition(startPosition, []);
143 | await updateGame(gameState);
144 |
145 | setRotateBoard(false);
146 | };
147 |
148 | const undoMove = useCallback(async () => {
149 | const previousMoveHistory = moveHistory.slice(0, moveHistory.length - 2);
150 | const gameState = await engine.setPosition(startPosition, previousMoveHistory);
151 | setMoveHistory(previousMoveHistory);
152 | await updateGame(gameState);
153 | }, [moveHistory, setMoveHistory, updateGame]);
154 |
155 | const handlePlayerMove = useCallback(async (piece, start, end) => {
156 | let pieceId = Math.abs(piece);
157 | if (gameEnded || isAiTurn) {
158 | return;
159 | }
160 |
161 | if (!canMove(start, end)) {
162 | return;
163 | }
164 |
165 | setCurrentPieceMoves(new Set());
166 |
167 | if (pieceId === P && ((activePlayer === WHITE && end < 8) || (activePlayer === BLACK && end >= 56))) {
168 | // Promotion
169 |
170 | clearAvailableMoves();
171 | setPromotion({
172 | start: start,
173 | end: end,
174 | column: end & 7,
175 | color: activePlayer
176 | });
177 | } else {
178 |
179 | // Standard move
180 | const interimBoard = board.slice();
181 | interimBoard[start] = 0;
182 | setBoard(interimBoard);
183 |
184 | const move = new Move(pieceId, start, end);
185 | const gameState = await engine.performMove(move);
186 | await updateGame(gameState);
187 | addMove(move);
188 |
189 | if (!gameState.gameEnded) {
190 | setAiTurn(true);
191 | }
192 | }
193 |
194 | }, [activePlayer, addMove, board, canMove, gameEnded, isAiTurn, updateGame]);
195 |
196 | const handlePromotion = useCallback(async (pieceId) => {
197 | const { start, end } = promotion;
198 | setPromotion(undefined);
199 | const interimBoard = board.slice();
200 | interimBoard[start] = 0;
201 | setBoard(interimBoard);
202 |
203 | const move = new Move(pieceId, start, end);
204 | const gameState = await engine.performMove(move);
205 | await updateGame(gameState);
206 | addMove(move);
207 |
208 | if (!gameState.gameEnded) {
209 | setAiTurn(true);
210 | }
211 | }, [addMove, board, promotion, setPromotion, updateGame]);
212 |
213 | const updatePossibleMoves = start => {
214 | const possibleMoves = availableMoves
215 | .filter(move => move.start === start)
216 | .map(move => move.end);
217 | setCurrentPieceMoves(new Set(possibleMoves));
218 | };
219 |
220 | const clearPossibleMoves = () => {
221 | setCurrentPieceMoves(new Set());
222 | };
223 |
224 | return (
225 | board
226 | ? (
227 |
228 | {promotion &&
229 | }
231 |
241 | setRotateBoard(!rotateBoard)}
250 | difficultyLevel={difficultyLevel}
251 | setDifficultyLevel={setDifficultyLevel}
252 | canUndoMove={!gameEnded && moveHistory.length > 1}
253 | undoMove={undoMove}
254 | />
255 |
256 | )
257 | : (
258 |
259 | )
260 | );
261 | };
262 |
263 | const Centered = styled.div`
264 | position: absolute;
265 | left: calc(50% - 60px);
266 | top: calc(50% - 60px);
267 | `;
268 |
269 | export default Game;
270 |
--------------------------------------------------------------------------------
/assembly/fen.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | import {
20 | BLACK, BLACK_KING_SIDE_CASTLING, BLACK_QUEEN_SIDE_CASTLING,
21 | Board,
22 | EMPTY, NO_CASTLING_RIGHTS,
23 | WHITE, WHITE_KING_SIDE_CASTLING, WHITE_QUEEN_SIDE_CASTLING
24 | } from './board';
25 | import {
26 | BLACK_ENPASSANT_LINE_END,
27 | BLACK_ENPASSANT_LINE_START, BLACK_PAWNS_BASELINE_START,
28 | WHITE_ENPASSANT_LINE_END,
29 | WHITE_ENPASSANT_LINE_START, WHITE_PAWNS_BASELINE_START
30 | } from './pieces';
31 |
32 | export const STARTPOS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
33 |
34 | /* Transforms the given board to a string representation of FEN (see https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)
35 | */
36 | export function toFEN(board: Board): string {
37 | return piecePlacement(board) + ' ' + activeColor(board) + ' ' + castlingAvailability(board) +
38 | ' ' + enPassantTargetSquare(board) + ' ' + halfMoveClock(board) + ' ' + fullMoveNumber(board);
39 | }
40 |
41 | // Black pieces go from -6 to -1, white pieces from 1 to 6
42 | // add 6 to get the index to the FEN character for the piece:
43 | const PIECE_FEN_CHARS: string = "kqrbnp/PNBRQK";
44 |
45 | const COLUMN_LETTERS: string = "abcdefgh";
46 |
47 | function piecePlacement(board: Board): string {
48 | let result: string = "";
49 | let emptyFieldCount = 0;
50 |
51 | for (let pos = 0; pos < 64; pos++) {
52 | const piece = board.getItem(pos);
53 |
54 | if (piece == EMPTY) {
55 | emptyFieldCount++;
56 | if (pos % 8 == 7) {
57 | result += emptyFieldCount.toString();
58 | if (pos != 63) {
59 | result += "/";
60 | }
61 | emptyFieldCount = 0;
62 | }
63 | continue;
64 | }
65 |
66 | if (emptyFieldCount > 0) {
67 | result += emptyFieldCount.toString();
68 | emptyFieldCount = 0;
69 | }
70 |
71 | const pieceFenCode = PIECE_FEN_CHARS.charAt(piece + 6);
72 | result += pieceFenCode;
73 |
74 | if (pos != 63 && pos % 8 == 7 ) {
75 | result += "/";
76 | }
77 |
78 | }
79 |
80 | return result;
81 | }
82 |
83 | function activeColor(board: Board): string {
84 | return board.getActivePlayer() == WHITE ? "w" : "b";
85 | }
86 |
87 | function castlingAvailability(board: Board): string {
88 | let result: string = "";
89 | if (board.canWhiteCastleKingSide()) {
90 | result += "K";
91 | }
92 |
93 | if (board.canWhiteCastleQueenSide()) {
94 | result += "Q";
95 | }
96 |
97 | if (board.canBlackCastleKingSide()) {
98 | result += "k";
99 | }
100 |
101 | if (board.canBlackCastleQueenSide()) {
102 | result += "q";
103 | }
104 |
105 | return result.length == 0 ? "-" : result;
106 | }
107 |
108 | function enPassantTargetSquare(board: Board): string {
109 | for (let i = WHITE_ENPASSANT_LINE_START; i <= WHITE_ENPASSANT_LINE_END; i++) {
110 | if (board.isEnPassentPossible(WHITE, i)) {
111 | const columnNum = i % 8;
112 | return COLUMN_LETTERS.charAt(columnNum) + "6";
113 | }
114 | }
115 |
116 | for (let i = BLACK_ENPASSANT_LINE_START; i <= BLACK_ENPASSANT_LINE_END; i++) {
117 | if (board.isEnPassentPossible(BLACK, i)) {
118 | const columnNum = i % 8;
119 | return COLUMN_LETTERS.charAt(columnNum) + "3";
120 | }
121 | }
122 |
123 | return "-";
124 | }
125 |
126 | function halfMoveClock(board: Board): string {
127 | return board.getHalfMoveClock().toString();
128 | }
129 |
130 | function fullMoveNumber(board: Board): string {
131 | return board.getFullMoveCount().toString();
132 | }
133 |
134 |
135 | /* Creates a Board instance from a FEN string (see https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation)
136 | */
137 | export function fromFEN(fen: string): Board {
138 | const boardItems = new Array(67);
139 | boardItems[64] = 0;
140 | boardItems[65] = 0;
141 | boardItems[66] = 0;
142 |
143 | const fenParts = fen.split(" ");
144 | if (fenParts.length != 6) {
145 | throw new Error("Invalid FEN string: expected 6 parts, but got " + fenParts.length.toString());
146 | }
147 |
148 | readPiecePlacement(boardItems, fenParts[0]);
149 | const board = new Board(boardItems);
150 |
151 | const activeColor = readActiveColor(fenParts[1]);
152 | readCastlingAvailability(board, fenParts[2]);
153 | readEnPassantTargetSquare(board, fenParts[3]);
154 | readHalfMoveClock(board,fenParts[4]);
155 | readFullMoveNumber(board, activeColor, fenParts[5]);
156 |
157 | board.recalculateHash();
158 |
159 | return board;
160 | }
161 |
162 | const DIGIT_ONE_CHARCODE = "1".charCodeAt(0);
163 | const DIGIT_EIGHT_CHARCODE = "8".charCodeAt(0);
164 |
165 | function readPiecePlacement(boardItems: Array, fenPart: string): void {
166 | const piecePlacements = fenPart.split("/");
167 | if (piecePlacements.length != 8) {
168 | throw new Error("Invalid FEN string: invalid piece placement part");
169 | }
170 |
171 | let boardPos = 0;
172 | for (let i = 0; i < piecePlacements.length; i++) {
173 | const rowChars = piecePlacements[i];
174 | for (let j = 0; j < rowChars.length; j++) {
175 | const pieceCharCode = rowChars.charCodeAt(j);
176 | if (pieceCharCode >= DIGIT_ONE_CHARCODE && pieceCharCode <= DIGIT_EIGHT_CHARCODE) {
177 | // it's a digit indicating the number of empty fields
178 | const numberOfEmptyFields = pieceCharCode - DIGIT_ONE_CHARCODE + 1;
179 | boardItems.fill(EMPTY, boardPos, boardPos + numberOfEmptyFields);
180 | boardPos += numberOfEmptyFields;
181 | continue;
182 | }
183 |
184 | const pieceIndex = PIECE_FEN_CHARS.indexOf(String.fromCharCode(pieceCharCode));
185 | if (pieceIndex == -1) {
186 | throw new Error("Invalid FEN string: unknown piece character: " + String.fromCharCode(pieceCharCode));
187 | }
188 |
189 | const piece = pieceIndex - 6;
190 | boardItems[boardPos] = piece;
191 | boardPos++;
192 | }
193 | }
194 | }
195 |
196 | function readActiveColor(fenPart: string): i32 {
197 | if (fenPart == "w") {
198 | return WHITE;
199 | } else if (fenPart == "b") {
200 | return BLACK;
201 | }
202 |
203 | throw new Error("Invalid FEN string: unexpected character in color part: " + fenPart);
204 | }
205 |
206 | function readCastlingAvailability(board: Board, fenPart: string): void {
207 | let state = NO_CASTLING_RIGHTS;
208 |
209 | if (fenPart == "-") {
210 | board.setState(state);
211 | return;
212 | }
213 |
214 | for (let i = 0; i < fenPart.length; i++) {
215 | const castlingChar = fenPart.charAt(i);
216 | if (castlingChar == "K") {
217 | state |= WHITE_KING_SIDE_CASTLING;
218 | } else if (castlingChar == "Q") {
219 | state |= WHITE_QUEEN_SIDE_CASTLING;
220 | } else if (castlingChar == "k") {
221 | state |= BLACK_KING_SIDE_CASTLING;
222 | } else if (castlingChar == "q") {
223 | state |= BLACK_QUEEN_SIDE_CASTLING;
224 | } else {
225 | throw new Error("Invalid FEN string: unexpected character in castling availability string: " + castlingChar);
226 | }
227 | }
228 |
229 | board.setState(state);
230 | }
231 |
232 |
233 | const LETTER_A_CHARCODE = "a".charCodeAt(0);
234 | const LETTER_H_CHARCODE = "h".charCodeAt(0);
235 |
236 | function readEnPassantTargetSquare(board: Board, fenPart: string): void {
237 | if (fenPart == "-") {
238 | return;
239 | }
240 |
241 | if (fenPart.length != 2) {
242 | throw new Error("Invalid FEN string: unexpected en passant part: " + fenPart);
243 | }
244 |
245 | const colChar = fenPart.charCodeAt(0);
246 | if (colChar < LETTER_A_CHARCODE || colChar > LETTER_H_CHARCODE) {
247 | throw new Error("Invalid FEN string: unexpected en passant part: " + fenPart);
248 | }
249 |
250 | const rowChar = fenPart.charAt(1);
251 |
252 | const colOffset = (colChar - LETTER_A_CHARCODE); // 0-7
253 |
254 | if (rowChar == "3") {
255 | board.setEnPassantPossible(WHITE_PAWNS_BASELINE_START + colOffset);
256 | } else if (rowChar == "6") {
257 | board.setEnPassantPossible(BLACK_PAWNS_BASELINE_START + colOffset);
258 | } else {
259 | throw new Error("Invalid FEN string: unexpected en passant part: " + fenPart);
260 | }
261 |
262 | board.updateHashForEnPassent(0);
263 | }
264 |
265 | function readHalfMoveClock(board: Board, fenPart: string): void {
266 | const halfMoveClock = i16(parseInt(fenPart));
267 | if (halfMoveClock < 0) {
268 | throw new Error("Invalid FEN string: unexpected halfmove clock part: " + fenPart);
269 | }
270 |
271 | board.setHalfMoveClock(halfMoveClock);
272 | }
273 |
274 | function readFullMoveNumber(board: Board, activeColor: i32, fenPart: string): void {
275 | const fullMoveNumber = i16(parseInt(fenPart));
276 | if (fullMoveNumber < 1) {
277 | throw new Error("Invalid FEN string: unexpected fullmove number part: " + fenPart);
278 | }
279 |
280 | const halfMoveCount = (fullMoveNumber - 1) * 2 + (activeColor == WHITE ? 0 : 1);
281 | board.initializeHalfMoveCount(halfMoveCount);
282 | }
283 |
284 |
--------------------------------------------------------------------------------
/tools/bookgen/bookgen.py:
--------------------------------------------------------------------------------
1 | # A free and open source chess game using AssemblyScript and React
2 | # Copyright (C) 2020 mhonert (https://github.com/mhonert)
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | import chess.pgn
18 | import zobrist
19 | import numpy as np
20 | import statistics
21 |
22 | from util import encode_move
23 | from pathlib import Path
24 |
25 | # Generates an opening book from chess games stored in PGN format.
26 | # Each game in the PGN files must contain the following header information
27 | # - Result (e.g. "1-0" if the white player won)
28 | # - WhiteElo
29 | # - BlackElo
30 | #
31 | # The generator will only take games from players with at least 2000 ELO into account.
32 | # The generation result is an AssemblyScript source file.
33 |
34 | PLY_BOOK_LIMIT = 16
35 | ply_moves = [{} for _ in range(PLY_BOOK_LIMIT + 1)]
36 |
37 | move_occurences = [{} for _ in range(PLY_BOOK_LIMIT + 1)]
38 |
39 | max_ply = 0
40 |
41 | print("Parsing chess games from pgn file ...")
42 | game_num = 0
43 |
44 | books = [
45 | "pgn/fics2400.pgn",
46 | "pgn/fics2600.pgn",
47 | "pgn/fics2600ur.pgn",
48 | "pgn/fics2400ur.pgn"
49 | ]
50 |
51 | move_thresholds = [2 for i in range(PLY_BOOK_LIMIT + 1)]
52 | move_thresholds[0] = 10
53 | move_thresholds[1] = 10
54 |
55 | # First pass: count occurrences of moves to filter out unusual or rarely played openings
56 | for book in books:
57 | pgn = open(book)
58 | print("Analyzing games from ", book)
59 | while True:
60 | game = chess.pgn.read_game(pgn)
61 | if not game:
62 | break
63 |
64 | result = game.headers['Result']
65 | whiteElo = int(game.headers['WhiteElo'])
66 | blackElo = int(game.headers['BlackElo'])
67 | skipWhite = result == '0-1'
68 | skipBlack = result == '1-0'
69 |
70 | if whiteElo < 2000 or blackElo < 2000 or abs(whiteElo - blackElo) > 50:
71 | continue
72 |
73 | game_num += 1
74 | print("- analyzing game #", game_num)
75 | board = game.board()
76 | ply = 0
77 | for move in game.mainline_moves():
78 | zobrist_hash = zobrist.calc_hash(board)
79 | moveFrom = move.from_square
80 | moveTo = move.to_square
81 | encoded_move = encode_move(board.piece_at(moveFrom).piece_type, moveFrom, moveTo)
82 |
83 | isWhiteTurn = board.turn == chess.WHITE
84 | isBlackTurn = not isWhiteTurn
85 |
86 | board.push(move)
87 |
88 | if (isWhiteTurn and not skipWhite) or (isBlackTurn and not skipBlack):
89 | # Only include moves that were played in multiple games
90 | if encoded_move in move_occurences[ply]:
91 | move_occurences[ply][encoded_move] += 1
92 |
93 | if move_occurences[ply][encoded_move] >= move_thresholds[ply]:
94 | if ply > max_ply:
95 | max_ply = ply
96 |
97 | else:
98 | move_occurences[ply][encoded_move] = 1
99 |
100 | ply += 1
101 |
102 | if ply > PLY_BOOK_LIMIT:
103 | break
104 | pgn.close()
105 |
106 | # Second pass: extract opening lines
107 | for book in books:
108 | pgn = open(book)
109 | print("Reading games from ", book)
110 | while True:
111 | game = chess.pgn.read_game(pgn)
112 | if not game:
113 | break
114 |
115 | result = game.headers['Result']
116 | whiteElo = int(game.headers['WhiteElo'])
117 | blackElo = int(game.headers['BlackElo'])
118 | skipWhite = result == '0-1'
119 | skipBlack = result == '1-0'
120 |
121 | if whiteElo < 2000 or blackElo < 2000 or abs(whiteElo - blackElo) > 50:
122 | continue
123 |
124 | game_num += 1
125 | print("- extracting moves from game #", game_num)
126 | board = game.board()
127 | ply = 0
128 | for move in game.mainline_moves():
129 | zobrist_hash = zobrist.calc_hash(board)
130 | moveFrom = move.from_square
131 | moveTo = move.to_square
132 | encoded_move = encode_move(board.piece_at(moveFrom).piece_type, moveFrom, moveTo)
133 |
134 | isWhiteTurn = board.turn == chess.WHITE
135 | isBlackTurn = not isWhiteTurn
136 |
137 | board.push(move)
138 |
139 | if (isWhiteTurn and not skipWhite) or (isBlackTurn and not skipBlack):
140 | # Only include moves that were played in multiple games
141 | if encoded_move in move_occurences[ply] and move_occurences[ply][encoded_move] >= move_thresholds[ply]:
142 | if ply > max_ply:
143 | max_ply = ply
144 |
145 | if zobrist_hash in ply_moves[ply]:
146 | ply_moves[ply][zobrist_hash].add(encoded_move)
147 | else:
148 | ply_moves[ply][zobrist_hash] = {encoded_move}
149 |
150 | else:
151 | break
152 |
153 | ply += 1
154 |
155 | if ply > PLY_BOOK_LIMIT:
156 | break
157 | pgn.close()
158 |
159 | print("Preparing opening book list...")
160 |
161 | book = [0 for _ in range(max_ply + 1)]
162 | book[0] = max_ply
163 |
164 | for idx in range(max_ply):
165 | if len(ply_moves[idx]) == 0:
166 | break
167 | book[idx + 1] = len(book) # Start index for the move list of the current ply
168 | book.append(len(ply_moves[idx])) # Number of entries (positions)
169 | for zobrist_hash, moves in ply_moves[idx].items():
170 | # Split 64 bit hash into 2 32-bit entries
171 | for i in range(2):
172 | book.append(zobrist_hash & np.uint64(0xFFFFFFFF))
173 | zobrist_hash = np.uint64(zobrist_hash >> np.uint64(32))
174 |
175 | book.append(len(moves))
176 | book.extend(moves)
177 |
178 | print("Writing opening book list...")
179 |
180 | out = open("../../assembly/opening-book-data.ts", "w")
181 | out.write("/*\n")
182 | out.write(" * A free and open source chess game using AssemblyScript and React\n")
183 | out.write(" * Copyright (C) 2020 mhonert (https://github.com/mhonert)\n")
184 | out.write(" *\n")
185 | out.write(" * This program is free software: you can redistribute it and/or modify\n")
186 | out.write(" * it under the terms of the GNU General Public License as published by\n")
187 | out.write(" * the Free Software Foundation, either version 3 of the License, or\n")
188 | out.write(" * (at your option) any later version.\n")
189 | out.write(" *\n")
190 | out.write(" * This program is distributed in the hope that it will be useful,\n")
191 | out.write(" * but WITHOUT ANY WARRANTY; without even the implied warranty of\n")
192 | out.write(" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n")
193 | out.write(" * GNU General Public License for more details.\n")
194 | out.write(" *\n")
195 | out.write(" * You should have received a copy of the GNU General Public License\n")
196 | out.write(" * along with this program. If not, see .\n*/\n\n")
197 |
198 | out.write("/* _________________________________________________________________________\n\n")
199 | out.write(" * Auto-generated opening book data file\n")
200 | out.write(" * Format:\n")
201 | out.write(" * Index\n")
202 | out.write(" * 0: Number of plies in this book (BOOK_PLIES)\n")
203 | out.write(" * 1 - BOOK_PLIES: Start index for the moves for this ply\n\n")
204 | out.write(" * For each ply:\n")
205 | out.write(" * - Number of entries for this ply\n")
206 | out.write(" * For each entry:\n")
207 | out.write(" * - Zobrist hash\n")
208 | out.write(" * - Number of moves for this position\n")
209 | out.write(" * For each move:\n")
210 | out.write(" * - Encoded move\n*/\n\n")
211 |
212 |
213 | out.write("@inline\n")
214 | out.write("export function getOpeningBookU32(index: u32): u32 {\n")
215 | out.write(" return load(openingBookData + index * 4);\n")
216 | out.write("}\n\n")
217 |
218 | out.write("@inline\n")
219 | out.write("export function getOpeningBookI32(index: u32): i32 {\n")
220 | out.write(" return load(openingBookData + index * 4);\n")
221 | out.write("}\n\n")
222 |
223 | out.write("const openingBookData = memory.data([ ")
224 |
225 | charsWritten = 0
226 | for idx, entry in enumerate(book):
227 | if idx > 0:
228 | charsWritten += 2
229 | out.write(", ")
230 |
231 | if idx == 1:
232 | out.write("\n ")
233 |
234 | if idx > max_ply and (charsWritten >= 100 or entry > 65535 and charsWritten >= 88):
235 | charsWritten = 0
236 | out.write("\n ")
237 |
238 | text_entry = hex(entry) if entry > 65535 else str(entry)
239 |
240 | out.write(text_entry)
241 | charsWritten += len(text_entry)
242 |
243 | out.write("\n]);\n")
244 | out.close()
245 | print("Success!")
246 |
247 | for ply in range(max_ply):
248 | occurences = move_occurences[ply]
249 | if len(occurences) > 0:
250 | values = move_occurences[ply].values()
251 | print(ply, max(values), int(statistics.median(values)), int(statistics.mean(values)))
252 |
253 | print(move_thresholds)
254 |
255 | print("Memory usage for book data: ", (len(book) * 4) / 1024, "KB")
256 |
--------------------------------------------------------------------------------
/assembly/bitboard.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * A free and open source chess game using AssemblyScript and React
3 | * Copyright (C) 2020 mhonert (https://github.com/mhonert)
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 |
20 | import { BLACK, indexFromColor, MAX_FIELD_DISTANCE, WHITE } from './board';
21 |
22 |
23 | // Patterns to check, whether the fields between king and rook are empty
24 | export const WHITE_KING_SIDE_CASTLING_BIT_PATTERN: u64 = 0b01100000_00000000_00000000_00000000_00000000_00000000_00000000_00000000;
25 | export const WHITE_QUEEN_SIDE_CASTLING_BIT_PATTERN: u64 = 0b00001110_00000000_00000000_00000000_00000000_00000000_00000000_00000000;
26 |
27 | export const BLACK_KING_SIDE_CASTLING_BIT_PATTERN: u64 = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_01100000;
28 | export const BLACK_QUEEN_SIDE_CASTLING_BIT_PATTERN: u64 = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00001110;
29 |
30 | // Patterns to check, whether a piece is on a light or dark field
31 | export const LIGHT_COLORED_FIELD_PATTERN: u64 = 0b01010101_01010101_01010101_01010101_01010101_01010101_01010101_01010101;
32 | export const DARK_COLORED_FIELD_PATTERN: u64 = 0b10101010_10101010_10101010_10101010_10101010_10101010_10101010_10101010;
33 |
34 | export const KING_DANGER_ZONE_SIZE: i32 = 2;
35 |
36 | function isBorder(boardPos: i32): bool {
37 | if (boardPos < 21 || boardPos > 98) {
38 | return true;
39 | }
40 |
41 | return boardPos % 10 == 0 || boardPos % 10 == 9;
42 | }
43 |
44 |
45 | function calculateSingleMovePatterns(directions: StaticArray): StaticArray {
46 | const patterns = new StaticArray(64);
47 | let index = 0;
48 | for (let boardPos = 21; boardPos <= 98; boardPos++) {
49 | if (isBorder(boardPos)) {
50 | continue;
51 | }
52 |
53 | let pattern: u64 = 0;
54 | for (let i = 0; i < directions.length; i++) {
55 | const dir = directions[i];
56 | const targetPos = boardPos + dir;
57 | if (!isBorder(targetPos)) {
58 | let row = (targetPos - 21) / 10;
59 | let col = (targetPos - 21) % 10;
60 | const bitIndex = col + (row * 8);
61 | pattern |= 1 << bitIndex;
62 | }
63 | }
64 |
65 | patterns[index++] = pattern;
66 | }
67 |
68 | return patterns;
69 | }
70 |
71 | // Use letterbox board (10 columns * 12 rows) for simpler border detection during pattern calculation:
72 | const LETTERBOX_KNIGHT_DIRECTIONS: StaticArray = StaticArray.fromArray([21, 19, 12, 8, -12, -21, -19, -8]);
73 | export const KNIGHT_PATTERNS: StaticArray = calculateSingleMovePatterns(LETTERBOX_KNIGHT_DIRECTIONS);
74 |
75 | const LETTERBOX_KING_DIRECTIONS: StaticArray = StaticArray.fromArray([1, 10, -1, -10, 9, 11, -9, -11]);
76 | export const KING_PATTERNS: StaticArray = calculateSingleMovePatterns(LETTERBOX_KING_DIRECTIONS);
77 |
78 |
79 | export const PAWN_DOUBLE_MOVE_LINE: StaticArray = createDoubleMoveLine();
80 |
81 | function createDoubleMoveLine(): StaticArray {
82 | const lines = new StaticArray(2);
83 | lines[indexFromColor(BLACK)] = 0b0000000000000000000000000000000000000000111111110000000000000000;
84 | lines[indexFromColor(WHITE)] = 0b0000000000000000111111110000000000000000000000000000000000000000;
85 |
86 | return lines;
87 | }
88 |
89 |
90 | enum Direction {
91 | NORTH_WEST, NORTH, NORTH_EAST,
92 | EAST,
93 | SOUTH_EAST, SOUTH, SOUTH_WEST,
94 | WEST
95 | }
96 |
97 | const DIRECTION_COL_OFFSET: Array = [-1, 0, +1, +1, +1, 0, -1, -1];
98 | const DIRECTION_ROW_OFFSET: Array = [-1, -1, -1, 0, +1, +1, +1, 0];
99 |
100 | function computeRayAttackBitboards(): StaticArray {
101 | const rayAttacks = new StaticArray(65 * 8); // (64 squares + 1 for empty attack bitboard) * 8 directions
102 | let index = 0;
103 | for (let dir = Direction.NORTH_WEST; dir <= Direction.WEST; dir++) {
104 | for (let pos = 0; pos < 64; pos++) {
105 | let col = pos % 8;
106 | let row = pos / 8;
107 |
108 | let attackBitboard: u64 = 0;
109 |
110 | for (let distance = 1; distance <= MAX_FIELD_DISTANCE; distance++) {
111 | col += DIRECTION_COL_OFFSET[dir];
112 | row += DIRECTION_ROW_OFFSET[dir];
113 | if (col < 0 || col > 7 || row < 0 || row > 7) {
114 | break; // border
115 | }
116 |
117 | const patternIndex = row * 8 + col;
118 | attackBitboard |= (1 << patternIndex);
119 | }
120 |
121 | rayAttacks[index++] = attackBitboard;
122 | }
123 | rayAttacks[index++] = 0; // empty attack bitboard
124 | }
125 | return rayAttacks;
126 | }
127 |
128 | const RAY_ATTACKS: StaticArray = computeRayAttackBitboards();
129 |
130 | @inline
131 | function getPositiveRayAttacks(occupied: u64, dir: Direction, pos: i32): u64 {
132 | const dirOffset = dir * 65;
133 | let attacks = unchecked(RAY_ATTACKS[dirOffset + pos]);
134 | const blocker = attacks & occupied;
135 | if (blocker == 0) {
136 | return attacks;
137 | }
138 | const firstBlockerPos = 63 - i32(clz(blocker));
139 | attacks ^= unchecked(RAY_ATTACKS[dirOffset + firstBlockerPos]);
140 | return attacks;
141 | }
142 |
143 | @inline
144 | function getNegativeRayAttacks(occupied: u64, dir: Direction, pos: i32): u64 {
145 | const dirOffset = dir * 65;
146 | let attacks = unchecked(RAY_ATTACKS[dirOffset + pos]);
147 | const blocker = attacks & occupied;
148 | const firstBlockerPos = i32(ctz(blocker));
149 | attacks ^= unchecked(RAY_ATTACKS[dirOffset + firstBlockerPos]);
150 | return attacks;
151 | }
152 |
153 | @inline
154 | export function diagonalAttacks(occupied: u64, pos: i32): u64 {
155 | return getPositiveRayAttacks(occupied, Direction.NORTH_EAST, pos) | getNegativeRayAttacks(occupied, Direction.SOUTH_WEST, pos);
156 | }
157 |
158 | @inline
159 | export function antiDiagonalAttacks(occupied: u64, pos: i32): u64 {
160 | return getPositiveRayAttacks(occupied, Direction.NORTH_WEST, pos) | getNegativeRayAttacks(occupied, Direction.SOUTH_EAST, pos);
161 | }
162 |
163 | @inline
164 | export function horizontalAttacks(occupied: u64, pos: i32): u64 {
165 | return getPositiveRayAttacks(occupied, Direction.WEST, pos) | getNegativeRayAttacks(occupied, Direction.EAST, pos);
166 | }
167 |
168 | @inline
169 | export function verticalAttacks(occupied: u64, pos: i32): u64 {
170 | return getPositiveRayAttacks(occupied, Direction.NORTH, pos) | getNegativeRayAttacks(occupied, Direction.SOUTH, pos);
171 | }
172 |
173 |
174 | @inline
175 | export function blackPawnAttacks(pawns: u64): u64 {
176 | return blackLeftPawnAttacks(pawns) | blackRightPawnAttacks(pawns);
177 | }
178 |
179 | @inline
180 | export function whitePawnAttacks(pawns: u64): u64 {
181 | return whiteLeftPawnAttacks(pawns) | whiteRightPawnAttacks(pawns);
182 | }
183 |
184 | @inline
185 | export function whiteLeftPawnAttacks(pawns: u64): u64 {
186 | return (pawns & 0xfefefefefefefefe) >> 9 // mask right column
187 | }
188 |
189 | @inline
190 | export function whiteRightPawnAttacks(pawns: u64): u64 {
191 | return (pawns & 0x7f7f7f7f7f7f7f7f) >> 7 // mask right column
192 | }
193 |
194 | @inline
195 | export function blackLeftPawnAttacks(pawns: u64): u64 {
196 | return (pawns & 0xfefefefefefefefe) << 7 // mask right column
197 | }
198 |
199 | @inline
200 | export function blackRightPawnAttacks(pawns: u64): u64 {
201 | return (pawns & 0x7f7f7f7f7f7f7f7f) << 9 // mask right column
202 | }
203 |
204 | export const WHITE_KING_SHIELD_PATTERNS = createKingShieldPatterns(-1);
205 | export const BLACK_KING_SHIELD_PATTERNS = createKingShieldPatterns(1);
206 |
207 | function createKingShieldPatterns(direction: i32): StaticArray {
208 | const patterns = new StaticArray(64);
209 |
210 | for (let pos: u32 = 0; pos < 64; pos++) {
211 | const row = pos / 8;
212 | const col = pos & 7;
213 |
214 | let pattern: u64 = 0;
215 | for (let distance = 1; distance <= 2; distance++) {
216 | const shieldRow = row + (direction * distance);
217 | if (shieldRow < 0 || shieldRow > 7) { // Outside the board
218 | continue;
219 | }
220 |
221 | let frontPawnPos = shieldRow * 8 + col;
222 | pattern |= (1 << frontPawnPos);
223 | if (col > 0) {
224 | let frontWestPawnPos = shieldRow * 8 + col - 1;
225 | pattern |= (1 << frontWestPawnPos);
226 | }
227 | if (col < 7) {
228 | let frontEastPawnPos = shieldRow * 8 + col + 1;
229 | pattern |= (1 << frontEastPawnPos);
230 | }
231 | }
232 |
233 | unchecked(patterns[pos] = pattern);
234 | }
235 |
236 | return patterns;
237 | }
238 |
239 |
240 | export const KING_DANGER_ZONE_PATTERNS = createKingDangerZonePatterns();
241 |
242 | function createKingDangerZonePatterns(): StaticArray {
243 | const patterns = new StaticArray(64);
244 |
245 | for (let pos: u32 = 0; pos < 64; pos++) {
246 | const row = pos / 8;
247 | const col = pos & 7;
248 |
249 | let pattern: u64 = 0;
250 | for (let rowOffset = -KING_DANGER_ZONE_SIZE; rowOffset <= KING_DANGER_ZONE_SIZE; rowOffset++) {
251 | const zoneRow = row + rowOffset;
252 | if (zoneRow < 0 || zoneRow > 7) { // Outside the board
253 | continue;
254 | }
255 | for (let colOffset = -KING_DANGER_ZONE_SIZE; colOffset <= KING_DANGER_ZONE_SIZE; colOffset++) {
256 | const zoneCol = col + colOffset;
257 | if (zoneCol < 0 || zoneCol > 7) { // Outside the board
258 | continue;
259 | }
260 |
261 | const patternPos = zoneRow * 8 + zoneCol;
262 | pattern |= (1 << patternPos);
263 | }
264 | }
265 |
266 | unchecked(patterns[pos] = pattern);
267 | }
268 |
269 | return patterns;
270 | }
271 |
272 | // Patterns to check, whether the path in front of the pawn is free (i.e. not blocked by opponent pieces)
273 | export const WHITE_PAWN_FREEPATH_PATTERNS = createPawnFreePathPatterns(-1);
274 | export const BLACK_PAWN_FREEPATH_PATTERNS = createPawnFreePathPatterns(1);
275 |
276 | function createPawnFreePathPatterns(direction: i32): StaticArray {
277 | const patterns = new StaticArray(64);
278 |
279 | for (let pos: u32 = 0; pos < 64; pos++) {
280 | let row = pos / 8;
281 | const col = pos & 7;
282 |
283 | let pattern: u64 = 0;
284 | while (row >= 1 && row <= 6) {
285 | row += direction;
286 |
287 | pattern |= u64(1) << (row * 8 + col);
288 | }
289 |
290 | unchecked(patterns[pos] = pattern);
291 | }
292 |
293 | return patterns;
294 | }
295 |
--------------------------------------------------------------------------------