├── js
├── gui
│ ├── pieces.js
│ └── controlls.js
├── searchWorker.js
├── sounds.js
├── bitBoard.js
├── pvTable.js
├── main.js
├── evaluation.js
├── pgnCompiler.js
├── search.js
├── board.js
├── moveStructure.js
├── perft.js
└── generateMove.js
├── desktop.ini
├── google37a82ba6b963c097.html
├── gm2600.bin
├── robots.txt
├── assets
├── icons
│ ├── copy.webp
│ ├── cross.webp
│ ├── share.webp
│ ├── tick.webp
│ ├── delete.webp
│ ├── favIcon.webp
│ ├── upload.webp
│ ├── blackKing.webp
│ ├── download.webp
│ ├── whiteKing.webp
│ ├── black-crown-alt.webp
│ ├── white-crown-alt.webp
│ ├── crown-shadow.svg
│ ├── book.svg
│ ├── setting.svg
│ ├── tournaments.svg
│ ├── computer.svg
│ ├── challengelink.svg
│ ├── playwhite.svg
│ └── handshake.svg
├── screenshot.webp
├── avatar
│ ├── devki.webp
│ └── acharya.webp
├── sounds
│ ├── capture.mp3
│ ├── castle.mp3
│ ├── illegal.mp3
│ ├── notify.mp3
│ ├── premove.mp3
│ ├── promote.mp3
│ ├── game-end.mp3
│ ├── game-start.mp3
│ ├── move-check.mp3
│ ├── move-self.mp3
│ ├── tenseconds.mp3
│ └── move-opponent.mp3
├── captured-pieces.webp
├── theme
│ ├── walnut
│ │ ├── board.webp
│ │ ├── preview.webp
│ │ └── pieces
│ │ │ ├── bb.webp
│ │ │ ├── bk.webp
│ │ │ ├── bn.webp
│ │ │ ├── bp.webp
│ │ │ ├── bq.webp
│ │ │ ├── br.webp
│ │ │ ├── wb.webp
│ │ │ ├── wk.webp
│ │ │ ├── wn.webp
│ │ │ ├── wp.webp
│ │ │ ├── wq.webp
│ │ │ └── wr.webp
│ ├── classic
│ │ ├── board.webp
│ │ ├── preview.webp
│ │ └── pieces
│ │ │ ├── bb.webp
│ │ │ ├── bk.webp
│ │ │ ├── bn.webp
│ │ │ ├── bp.webp
│ │ │ ├── bq.webp
│ │ │ ├── br.webp
│ │ │ ├── wb.webp
│ │ │ ├── wk.webp
│ │ │ ├── wn.webp
│ │ │ ├── wp.webp
│ │ │ ├── wq.webp
│ │ │ └── wr.webp
│ └── newspaper
│ │ ├── board.webp
│ │ ├── preview.webp
│ │ └── pieces
│ │ ├── bb.webp
│ │ ├── bk.webp
│ │ ├── bn.webp
│ │ ├── bp.webp
│ │ ├── bq.webp
│ │ ├── br.webp
│ │ ├── wb.webp
│ │ ├── wk.webp
│ │ ├── wn.webp
│ │ ├── wp.webp
│ │ ├── wq.webp
│ │ ├── wr.webp
│ │ └── Screenshot 2025-01-05 180521.webp
└── capture piece
│ ├── capture-bb-1.png
│ ├── capture-bb-2.png
│ ├── capture-bn-1.png
│ ├── capture-bn-2.png
│ ├── capture-bp-1.png
│ ├── capture-bp-2.png
│ ├── capture-bp-3.png
│ ├── capture-bp-4.png
│ ├── capture-bp-5.png
│ ├── capture-bp-6.png
│ ├── capture-bp-7.png
│ ├── capture-bp-8.png
│ ├── capture-bq-1.png
│ ├── capture-br-1.png
│ ├── capture-br-2.png
│ ├── capture-wb-1.png
│ ├── capture-wb-2.png
│ ├── capture-wn-1.png
│ ├── capture-wn-2.png
│ ├── capture-wp-1.png
│ ├── capture-wp-2.png
│ ├── capture-wp-3.png
│ ├── capture-wp-4.png
│ ├── capture-wp-5.png
│ ├── capture-wp-6.png
│ ├── capture-wp-7.png
│ ├── capture-wp-8.png
│ ├── capture-wq-1.png
│ ├── capture-wr-1.png
│ └── capture-wr-2.png
├── sitemap.xml
├── README.md
├── css
├── theme.css
├── util.css
└── style.css
└── index.html
/js/gui/pieces.js:
--------------------------------------------------------------------------------
1 | const pieces = document.querySelectorAll('.piece');
--------------------------------------------------------------------------------
/desktop.ini:
--------------------------------------------------------------------------------
1 | [ViewState]
2 | Mode=
3 | Vid=
4 | FolderType=Generic
5 |
--------------------------------------------------------------------------------
/google37a82ba6b963c097.html:
--------------------------------------------------------------------------------
1 | google-site-verification: google37a82ba6b963c097.html
--------------------------------------------------------------------------------
/gm2600.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/gm2600.bin
--------------------------------------------------------------------------------
/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
4 | Sitemap: https://chessleague.netlify.app/sitemap.xml
5 |
--------------------------------------------------------------------------------
/assets/icons/copy.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/copy.webp
--------------------------------------------------------------------------------
/assets/icons/cross.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/cross.webp
--------------------------------------------------------------------------------
/assets/icons/share.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/share.webp
--------------------------------------------------------------------------------
/assets/icons/tick.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/tick.webp
--------------------------------------------------------------------------------
/assets/screenshot.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/screenshot.webp
--------------------------------------------------------------------------------
/assets/avatar/devki.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/avatar/devki.webp
--------------------------------------------------------------------------------
/assets/icons/delete.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/delete.webp
--------------------------------------------------------------------------------
/assets/icons/favIcon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/favIcon.webp
--------------------------------------------------------------------------------
/assets/icons/upload.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/upload.webp
--------------------------------------------------------------------------------
/assets/sounds/capture.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/capture.mp3
--------------------------------------------------------------------------------
/assets/sounds/castle.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/castle.mp3
--------------------------------------------------------------------------------
/assets/sounds/illegal.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/illegal.mp3
--------------------------------------------------------------------------------
/assets/sounds/notify.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/notify.mp3
--------------------------------------------------------------------------------
/assets/sounds/premove.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/premove.mp3
--------------------------------------------------------------------------------
/assets/sounds/promote.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/promote.mp3
--------------------------------------------------------------------------------
/assets/avatar/acharya.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/avatar/acharya.webp
--------------------------------------------------------------------------------
/assets/captured-pieces.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/captured-pieces.webp
--------------------------------------------------------------------------------
/assets/icons/blackKing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/blackKing.webp
--------------------------------------------------------------------------------
/assets/icons/download.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/download.webp
--------------------------------------------------------------------------------
/assets/icons/whiteKing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/whiteKing.webp
--------------------------------------------------------------------------------
/assets/sounds/game-end.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/game-end.mp3
--------------------------------------------------------------------------------
/assets/sounds/game-start.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/game-start.mp3
--------------------------------------------------------------------------------
/assets/sounds/move-check.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/move-check.mp3
--------------------------------------------------------------------------------
/assets/sounds/move-self.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/move-self.mp3
--------------------------------------------------------------------------------
/assets/sounds/tenseconds.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/tenseconds.mp3
--------------------------------------------------------------------------------
/assets/theme/walnut/board.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/board.webp
--------------------------------------------------------------------------------
/assets/icons/black-crown-alt.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/black-crown-alt.webp
--------------------------------------------------------------------------------
/assets/icons/white-crown-alt.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/icons/white-crown-alt.webp
--------------------------------------------------------------------------------
/assets/sounds/move-opponent.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/sounds/move-opponent.mp3
--------------------------------------------------------------------------------
/assets/theme/classic/board.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/board.webp
--------------------------------------------------------------------------------
/assets/theme/classic/preview.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/preview.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/board.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/board.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/preview.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/preview.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/bb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/bb.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/bk.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/bk.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/bn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/bn.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/bp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/bp.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/bq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/bq.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/br.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/br.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/wb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/wb.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/wk.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/wk.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/wn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/wn.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/wp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/wp.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/wq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/wq.webp
--------------------------------------------------------------------------------
/assets/theme/classic/pieces/wr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/classic/pieces/wr.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/preview.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/preview.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/bb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/bb.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/bk.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/bk.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/bn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/bn.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/bp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/bp.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/bq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/bq.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/br.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/br.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/wb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/wb.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/wk.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/wk.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/wn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/wn.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/wp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/wp.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/wq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/wq.webp
--------------------------------------------------------------------------------
/assets/theme/walnut/pieces/wr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/walnut/pieces/wr.webp
--------------------------------------------------------------------------------
/assets/capture piece/capture-bb-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bb-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bb-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bb-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bn-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bn-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bn-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bn-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-3.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-4.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-5.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-6.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-7.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bp-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bp-8.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-bq-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-bq-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-br-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-br-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-br-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-br-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wb-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wb-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wb-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wb-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wn-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wn-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wn-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wn-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-2.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-3.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-4.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-5.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-6.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-7.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wp-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wp-8.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wq-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wq-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wr-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wr-1.png
--------------------------------------------------------------------------------
/assets/capture piece/capture-wr-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/capture piece/capture-wr-2.png
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/bb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/bb.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/bk.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/bk.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/bn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/bn.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/bp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/bp.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/bq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/bq.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/br.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/br.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/wb.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/wb.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/wk.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/wk.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/wn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/wn.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/wp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/wp.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/wq.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/wq.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/wr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/wr.webp
--------------------------------------------------------------------------------
/assets/theme/newspaper/pieces/Screenshot 2025-01-05 180521.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhijeetSinghRajput/ChessEngine/HEAD/assets/theme/newspaper/pieces/Screenshot 2025-01-05 180521.webp
--------------------------------------------------------------------------------
/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | https://chessleague.netlify.app/
6 | monthly
7 | 1.0
8 |
9 |
10 |
--------------------------------------------------------------------------------
/assets/icons/crown-shadow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/js/searchWorker.js:
--------------------------------------------------------------------------------
1 | importScripts(
2 | 'defs.js',
3 | 'bitBoard.js',
4 | 'board.js',
5 | 'moveStructure.js',
6 | 'generateMove.js',
7 | 'evaluation.js',
8 | 'pvTable.js',
9 | 'search.js',
10 | 'polyglot.js',
11 | 'main.js',
12 | );
13 |
14 | self.onmessage = function(e){
15 | const {command, searchTime, board} = e.data;
16 | gameBoard = board;
17 | if(command == 'search'){
18 | let depth = searchPosition(searchTime);
19 | self.postMessage({
20 | command: 'searchFinished',
21 | bestMove: searchController.bestMove,
22 | bestScore: searchController.bestScore,
23 | depth : searchController.depthReached,
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/js/sounds.js:
--------------------------------------------------------------------------------
1 | const CaptureSound = new Audio('./assets/sounds/capture.mp3');
2 | const CastleSound = new Audio('./assets/sounds/castle.mp3');
3 | const GameEndSound = new Audio('./assets/sounds/game-end.mp3');
4 | const GameStartSound = new Audio('./assets/sounds/game-start.mp3');
5 | const IllegalSound = new Audio('./assets/sounds/illegal.mp3');
6 | const CheckSound = new Audio('./assets/sounds/move-check.mp3');
7 | const EnemyMoveSound = new Audio('./assets/sounds/move-opponent.mp3');
8 | const SelfMoveSound = new Audio('./assets/sounds/move-self.mp3');
9 | const notifySound = new Audio('./assets/sounds/notify.mp3');
10 | const PremoveSound = new Audio('./assets/sounds/premove.mp3');
11 | const PromoteSound = new Audio('./assets/sounds/promote.mp3');
12 | const TenSecondsSound = new Audio('./assets/sounds/tenseconds.mp3');
13 |
--------------------------------------------------------------------------------
/assets/icons/book.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/setting.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/icons/tournaments.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/computer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Chess Engine (elo 2100)
2 |
3 | This is a lightweight and powerful chess engine written in JavaScript. It includes a bitboard-based architecture, move generation, evaluation, and a basic GUI. The project is modular and structured to make learning, debugging, and extending easier.
4 |
5 | ## Features
6 |
7 | - **Bitboard Representation** for efficient board handling (`bitBoard.js`)
8 | - **Move Generation** including legal move filtering (`generateMove.js`)
9 | - **Search Algorithm** with basic evaluation (`search.js`, `evaluation.js`)
10 | - **PGN Compiler** to parse/export games (`pgnCompiler.js`)
11 | - **Polyglot Opening Book Support** (`polyglot.js`)
12 | - **Perft Testing** for move generation accuracy (`perft.js`)
13 | - **Worker Support** for parallelized searching (`searchWorker.js`)
14 | - **Modular UI Components** in `gui/` folder
15 |
16 | ## File Structure
17 |
18 | - `bitBoard.js` - Core representation of the chessboard
19 | - `board.js` - Board setup, FEN parsing
20 | - `defs.js` - Constants and definitions
21 | - `evaluation.js` - Evaluation function to score positions
22 | - `generateMove.js` - Generates all legal and pseudo-legal moves
23 | - `main.js` - Entry point and initialization
24 | - `moveStructure.js` - Move representation and utilities
25 | - `perft.js` - Perft functionality for debugging
26 | - `pgnCompiler.js` - Handles PGN parsing and generation
27 | - `polyglot.js` - Integration for opening books
28 | - `pvTable.js` - Principal variation table
29 | - `search.js` - Minimax/Alpha-beta logic
30 | - `searchWorker.js` - Web worker for background search
31 | - `gui/` - Contains the graphical interface files
32 |
33 | ## Getting Started
34 |
35 | 1. Clone the repo:
36 | ```bash
37 | git clone https://github.com/abhijeetSinghRajput/chessEngine.git
38 | cd chessEngine
--------------------------------------------------------------------------------
/js/bitBoard.js:
--------------------------------------------------------------------------------
1 | //example:-
2 |
3 | // bitBoard file[0] nth Bit
4 | // 1 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7
5 | // 1 0 0 0 0 0 0 0 8 9 10 11 12 13 14 15
6 | // 1 0 0 0 0 0 0 0 16 17 18 19 20 21 22 23
7 | // 1 0 0 0 0 0 0 0 24 25 26 27 28 29 30 31
8 | // 1 0 0 0 0 0 0 0 32 33 34 35 36 37 38 39
9 | // 1 0 0 0 0 0 0 0 40 41 42 43 44 45 46 47
10 | // 1 0 0 0 0 0 0 0 48 49 50 51 52 53 54 55
11 | // 1 0 0 0 0 0 0 0 56 57 58 59 60 61 62 63
12 |
13 | const FileBitMask = new Array(8).fill(0n);
14 | const RankBitMask = new Array(8).fill(0n);
15 | const PassedPawnBitMask = new Array(2);
16 | const IsolatedBitMask = new Array(64).fill(0n);
17 |
18 |
19 |
20 | const PawnBitBoard = {
21 | 0: 0n,
22 | 1: 0n,
23 | setBit: function (color, sq) {
24 | this[color] |= (1n << BigInt(sq));
25 | },
26 | clearBit: function (color, sq) {
27 | this[color] &= ~(1n << BigInt(sq));
28 | },
29 | getBoth: function () {
30 | return this[Color.white] | this[Color.black];
31 | },
32 | init: function () {
33 | for (let sq = 0; sq < 64; sq++) {
34 | let piece = gameBoard.pieces[Sq64To120[sq]];
35 | if (piece == Pieces.empty) continue;
36 | if (piece == Pieces.wp) {
37 | this.setBit(Color.white, sq);
38 | }
39 | else if (piece == Pieces.bp) {
40 | this.setBit(Color.black, sq);
41 | }
42 | }
43 | }
44 | };
45 |
46 |
47 |
48 | function printBitBoard(num) {
49 | for (let row = 0; row < 8; row++) {
50 | let rowString = '';
51 | for (let col = 0; col < 8; col++) {
52 | const bit = (num >> BigInt(row * 8 + col)) & 1n;
53 | rowString += bit + ' ';
54 | }
55 | console.log(row + ". " + rowString);
56 | }
57 | console.log('');
58 | }
--------------------------------------------------------------------------------
/assets/icons/challengelink.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/icons/playwhite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/js/pvTable.js:
--------------------------------------------------------------------------------
1 | const PvTable = {
2 | entries: {},
3 | };
4 |
5 | PvTable.addMove = function (move) {
6 | this.entries[gameBoard.positionKey] = move;
7 | }
8 | PvTable.getMove = function () {
9 | let index = gameBoard.positionKey % transpositionTable.maxEntries;
10 | if(transpositionTable.entries[index]?.positionKey !== gameBoard.positionKey) return null;
11 | return transpositionTable.entries[index].move;
12 | }
13 | PvTable.clear = function () {
14 | this.entries = {};
15 | }
16 | PvTable.length = function () {
17 | return Object.keys(this.entries).length;
18 | }
19 | PvTable.getLine = function (depth) {
20 | let move = this.getMove();
21 | let movelist = [];
22 | let count = 0;
23 |
24 | while (move && count < depth) {
25 | if (isMoveExists(move)) {
26 | doMove(move);
27 | movelist.push(move);
28 | count++;
29 | }
30 | else {
31 | break;
32 | }
33 | move = this.getMove();
34 | }
35 | while (count--) undoMove();
36 | return movelist.map(move => moveStr(move)).join(' ');
37 | }
38 |
39 | function isMoveExists(arg) {
40 | for (const { move } of generateMoves()) {
41 | if (move == arg) {
42 | if (!doMove(move)) continue;
43 | undoMove();
44 | return true;
45 | }
46 | }
47 | return false;
48 | }
49 |
50 | const transpositionTable = {
51 | maxEntries: 0x100000 * 16,
52 | entries: new Array(this.maxEntries),
53 | }
54 | const AlphaFlag = 0;
55 | const BetaFlag = 1;
56 | const ExactFlag = 2;
57 |
58 | transpositionTable.clear = function(){
59 | this.entries = new Array(this.maxEntries);
60 | }
61 |
62 | transpositionTable.add = function (positionKey, move, score, flag, depth) {
63 | if (score > Mate) score += searchController.ply;
64 | if (score < -Mate) score -= searchController.ply;
65 | let index = positionKey % this.maxEntries;
66 |
67 | this.entries[index] = {
68 | positionKey,
69 | move,
70 | score,
71 | depth,
72 | flag
73 | }
74 | }
75 | transpositionTable.get = function (positionKey) {
76 | let index = positionKey % this.maxEntries;
77 | if(this.entries[index]?.positionKey !== positionKey) return null;
78 | return this.entries[index];
79 | }
80 | transpositionTable.length = function(){
81 | return Object.keys(this.entries).length;
82 | }
83 |
84 |
85 | // D:1 Best:d2d4 Score:30 nodes:21 Pv: d2d4
86 | // D:2 Best:d2d4 Score:0 nodes:207 Pv: d2d4 d7d5
87 | // D:3 Best:d2d4 Score:25 nodes:2680 Pv: d2d4 d7d5 c1e3
88 | // D:4 Best:d2d4 Score:0 nodes:17507 Pv: d2d4 d7d5 c1e3 c8e6
89 | // D:5 Best:e2e4 Score:25 nodes:217789 Pv: e2e4 e7e5 d2d4 d7d5 c1e3
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | (function main() {
2 | init();
3 | parseFen(StartingFen);
4 | if (typeof window !== 'undefined') {
5 | gui.renderPieces();
6 | }
7 | readPolyBook({path:'../gm2600.bin'});
8 | loadBooksFromIndexedDB();
9 | })();
10 |
11 | function init() {
12 | init_SquareSwitch();
13 | init_FileBitMask();
14 | init_RankBitMask();
15 | init_PassedPawnBitMask();
16 | init_IsolatedBitMask();
17 | }
18 |
19 |
20 | function init_FileBitMask() {
21 | for (let file = FileA; file <= FileH; ++file) {
22 | for (let rank = Rank1; rank <= Rank8; ++rank) {
23 | FileBitMask[file] |= (1n << BigInt(file)) << BigInt(8 * rank);
24 | }
25 | }
26 | }
27 |
28 | function init_RankBitMask() {
29 | for (let rank = Rank1; rank <= Rank8; ++rank) {
30 | RankBitMask[rank] |= 255n << BigInt(rank * 8);
31 | }
32 | }
33 |
34 | function init_IsolatedBitMask() {
35 | for (let sq = 0; sq < 64; sq++) {
36 | let file = fileOf(Sq64To120[sq]);
37 | if (file > FileA) {
38 | IsolatedBitMask[sq] |= FileBitMask[file - 1];
39 | }
40 | if (file < FileH) {
41 | IsolatedBitMask[sq] |= FileBitMask[file + 1];
42 | }
43 | }
44 | }
45 |
46 | function init_PassedPawnBitMask() {
47 | for (let i = 0; i < 2; ++i) {
48 | PassedPawnBitMask[i] = new Array(64).fill(0n);
49 |
50 | for (let sq = 0; sq < 64; sq++) {
51 | let file = fileOf(Sq64To120[sq]);
52 | let rank = rankOf(Sq64To120[sq]);
53 |
54 | PassedPawnBitMask[i][sq] |= FileBitMask[file];
55 | if (file > FileA) {
56 | PassedPawnBitMask[i][sq] |= FileBitMask[file - 1];
57 | }
58 | if (file < FileH) {
59 | PassedPawnBitMask[i][sq] |= FileBitMask[file + 1];
60 | }
61 |
62 | if (i == Color.white) {
63 | while (rank >= Rank1) {
64 | PassedPawnBitMask[i][sq] &= ~RankBitMask[rank--];
65 | }
66 | }
67 | else {
68 | while (rank <= Rank8) {
69 | PassedPawnBitMask[i][sq] &= ~RankBitMask[rank++];
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | function init_SquareSwitch() {
77 | for (let rank = Rank1; rank <= Rank8; ++rank) {
78 | for (let file = FileA; file <= FileH; ++file) {
79 | let sq120 = fileRank2Sq(file, rank);
80 | let sq64 = rank * 8 + file;
81 | Sq120To64[sq120] = sq64;
82 | Sq64To120[sq64] = sq120;
83 | }
84 | }
85 | }
86 |
87 |
88 | function printArr(arr, rows, cols) {
89 | for (let i = 0; i < rows; ++i) {
90 | let line = `${i} `;
91 | for (let j = 0; j < cols; ++j) {
92 | let val = arr[i * cols + j];
93 | if (val >= 0 && val <= 9) {
94 | line += '0' + val + ' ';
95 | }
96 | else if (!val) {
97 | line += '.. '
98 | }
99 | else {
100 | line += val + ' ';
101 | }
102 | }
103 | console.log(line);
104 | }
105 | }
--------------------------------------------------------------------------------
/assets/icons/handshake.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/css/theme.css:
--------------------------------------------------------------------------------
1 | /* ======================================= */
2 | /* =============== classic =============== */
3 | /* ======================================= */
4 | #board.classic {
5 | background: url('../assets/theme/classic/board.webp') no-repeat contain;
6 | }
7 | #board.classic .highlight {
8 | background-color: var(--highlight);
9 | }
10 | .classic .piece.wp {
11 | background-image: url(../assets/theme/classic/pieces/wp.webp);
12 | }
13 |
14 | .classic .piece.wr {
15 | background-image: url(../assets/theme/classic/pieces/wr.webp);
16 | }
17 |
18 | .classic .piece.wn {
19 | background-image: url(../assets/theme/classic/pieces/wn.webp);
20 | }
21 |
22 | .classic .piece.wb {
23 | background-image: url(../assets/theme/classic/pieces/wb.webp);
24 | }
25 |
26 | .classic .piece.wq {
27 | background-image: url(../assets/theme/classic/pieces/wq.webp);
28 | }
29 |
30 | .classic .piece.wk {
31 | background-image: url(../assets/theme/classic/pieces/wk.webp);
32 | }
33 |
34 | .classic .piece.bp {
35 | background-image: url(../assets/theme/classic/pieces/bp.webp);
36 | }
37 |
38 | .classic .piece.br {
39 | background-image: url(../assets/theme/classic/pieces/br.webp);
40 | }
41 |
42 | .classic .piece.bn {
43 | background-image: url(../assets/theme/classic/pieces/bn.webp);
44 | }
45 |
46 | .classic .piece.bb {
47 | background-image: url(../assets/theme/classic/pieces/bb.webp);
48 | }
49 |
50 | .classic .piece.bq {
51 | background-image: url(../assets/theme/classic/pieces/bq.webp);
52 | }
53 |
54 | .classic .piece.bk {
55 | background-image: url(../assets/theme/classic/pieces/bk.webp);
56 | }
57 |
58 |
59 | /* ======================================= */
60 | /* =============== walnut =============== */
61 | /* ======================================= */
62 | #board.walnut .coordinate-light {
63 | fill: #835f42;
64 | }
65 | #board.walnut .highlight {
66 | background-color: #d1a52d;
67 | }
68 | #board.walnut .coordinate-dark {
69 | fill: #c0a684;
70 | }
71 |
72 | #board.walnut {
73 | background: url('../assets/theme/walnut/board.webp') no-repeat;
74 | background-size: contain;
75 | }
76 |
77 |
78 | .walnut .piece.wp {
79 | background-image: url(../assets/theme/walnut/pieces/wp.webp);
80 | }
81 |
82 | .walnut .piece.wr {
83 | background-image: url(../assets/theme/walnut/pieces/wr.webp);
84 | }
85 |
86 | .walnut .piece.wn {
87 | background-image: url(../assets/theme/walnut/pieces/wn.webp);
88 | }
89 |
90 | .walnut .piece.wb {
91 | background-image: url(../assets/theme/walnut/pieces/wb.webp);
92 | }
93 |
94 | .walnut .piece.wq {
95 | background-image: url(../assets/theme/walnut/pieces/wq.webp);
96 | }
97 |
98 | .walnut .piece.wk {
99 | background-image: url(../assets/theme/walnut/pieces/wk.webp);
100 | }
101 |
102 | .walnut .piece.bp {
103 | background-image: url(../assets/theme/walnut/pieces/bp.webp);
104 | }
105 |
106 | .walnut .piece.br {
107 | background-image: url(../assets/theme/walnut/pieces/br.webp);
108 | }
109 |
110 | .walnut .piece.bn {
111 | background-image: url(../assets/theme/walnut/pieces/bn.webp);
112 | }
113 |
114 | .walnut .piece.bb {
115 | background-image: url(../assets/theme/walnut/pieces/bb.webp);
116 | }
117 |
118 | .walnut .piece.bq {
119 | background-image: url(../assets/theme/walnut/pieces/bq.webp);
120 | }
121 |
122 | .walnut .piece.bk {
123 | background-image: url(../assets/theme/walnut/pieces/bk.webp);
124 | }
125 |
126 |
127 | /* ======================================= */
128 | /* =============== newspaper =============== */
129 | /* ======================================= */
130 | #board.newspaper .coordinate-light {
131 | fill: #5a5956;
132 | }
133 | #board.newspaper .highlight {
134 | background-color: #99976e;
135 | }
136 | #board.newspaper .coordinate-dark {
137 | fill: #5a5956;
138 | }
139 | #board.newspaper{
140 | background: url('../assets/theme/newspaper/board.webp') no-repeat;
141 | background-size: contain;
142 | }
143 | .newspaper .piece.wp {
144 | background-image: url(../assets/theme/newspaper/pieces/wp.webp);
145 | }
146 |
147 | .newspaper .piece.wr {
148 | background-image: url(../assets/theme/newspaper/pieces/wr.webp);
149 | }
150 |
151 | .newspaper .piece.wn {
152 | background-image: url(../assets/theme/newspaper/pieces/wn.webp);
153 | }
154 |
155 | .newspaper .piece.wb {
156 | background-image: url(../assets/theme/newspaper/pieces/wb.webp);
157 | }
158 |
159 | .newspaper .piece.wq {
160 | background-image: url(../assets/theme/newspaper/pieces/wq.webp);
161 | }
162 |
163 | .newspaper .piece.wk {
164 | background-image: url(../assets/theme/newspaper/pieces/wk.webp);
165 | }
166 |
167 | .newspaper .piece.bp {
168 | background-image: url(../assets/theme/newspaper/pieces/bp.webp);
169 | }
170 |
171 | .newspaper .piece.br {
172 | background-image: url(../assets/theme/newspaper/pieces/br.webp);
173 | }
174 |
175 | .newspaper .piece.bn {
176 | background-image: url(../assets/theme/newspaper/pieces/bn.webp);
177 | }
178 |
179 | .newspaper .piece.bb {
180 | background-image: url(../assets/theme/newspaper/pieces/bb.webp);
181 | }
182 |
183 | .newspaper .piece.bq {
184 | background-image: url(../assets/theme/newspaper/pieces/bq.webp);
185 | }
186 |
187 | .newspaper .piece.bk {
188 | background-image: url(../assets/theme/newspaper/pieces/bk.webp);
189 | }
--------------------------------------------------------------------------------
/js/evaluation.js:
--------------------------------------------------------------------------------
1 | const PawnTable = [
2 | 0, 0, 0, 0, 0, 0, 0, 0,
3 | 10, 10, 0, -10, -10, 0, 10, 10,
4 | 5, 0, 0, 5, 5, 0, 0, 5,
5 | 0, 0, 10, 20, 20, 10, 0, 0,
6 | 5, 5, 5, 10, 10, 5, 5, 5,
7 | 10, 10, 10, 20, 20, 10, 10, 10,
8 | 20, 20, 20, 30, 30, 20, 20, 20,
9 | 0, 0, 0, 0, 0, 0, 0, 0
10 | ];
11 |
12 |
13 | const KnightTable = [
14 | 0, -10, 0, 0, 0, 0, -10, 0,
15 | 0, 0, 0, 5, 5, 0, 0, 0,
16 | 0, 0, 10, 10, 10, 10, 0, 0,
17 | 0, 0, 10, 20, 20, 10, 5, 0,
18 | 5, 10, 15, 20, 20, 15, 10, 5,
19 | 5, 10, 10, 20, 20, 10, 10, 5,
20 | 0, 0, 5, 10, 10, 5, 0, 0,
21 | 0, 0, 0, 0, 0, 0, 0, 0
22 | ];
23 |
24 | const BishopTable = [
25 | 0, 0, -10, 0, 0, -10, 0, 0,
26 | 0, 0, 0, 10, 10, 0, 0, 0,
27 | 0, 0, 10, 15, 15, 10, 0, 0,
28 | 0, 10, 15, 20, 20, 15, 10, 0,
29 | 0, 10, 15, 20, 20, 15, 10, 0,
30 | 0, 0, 10, 15, 15, 10, 0, 0,
31 | 0, 0, 0, 10, 10, 0, 0, 0,
32 | 0, 0, 0, 0, 0, 0, 0, 0
33 | ];
34 |
35 | const RookTable = [
36 | 0, 0, 5, 10, 10, 5, 0, 0,
37 | 0, 0, 5, 10, 10, 5, 0, 0,
38 | 0, 0, 5, 10, 10, 5, 0, 0,
39 | 0, 0, 5, 10, 10, 5, 0, 0,
40 | 0, 0, 5, 10, 10, 5, 0, 0,
41 | 0, 0, 5, 10, 10, 5, 0, 0,
42 | 25, 25, 25, 25, 25, 25, 25, 25,
43 | 0, 0, 5, 10, 10, 5, 0, 0
44 | ];
45 | const KingE = [
46 | -50, -10, 0, 0, 0, 0, -10, -50,
47 | -10, 0, 10, 10, 10, 10, 0, -10,
48 | 0, 10, 20, 20, 20, 20, 10, 0,
49 | 0, 10, 20, 40, 40, 20, 10, 0,
50 | 0, 10, 20, 40, 40, 20, 10, 0,
51 | 0, 10, 20, 20, 20, 20, 10, 0,
52 | -10, 0, 10, 10, 10, 10, 0, -10,
53 | -50, -10, 0, 0, 0, 0, -10, -50
54 | ];
55 |
56 | const KingO = [
57 | 0, 5, 5, -10, -10, 0, 10, 5,
58 | -30, -30, -30, -30, -30, -30, -30, -30,
59 | -50, -50, -50, -50, -50, -50, -50, -50,
60 | -70, -70, -70, -70, -70, -70, -70, -70,
61 | -70, -70, -70, -70, -70, -70, -70, -70,
62 | -70, -70, -70, -70, -70, -70, -70, -70,
63 | -70, -70, -70, -70, -70, -70, -70, -70,
64 | -70, -70, -70, -70, -70, -70, -70, -70
65 | ];
66 |
67 | const BishopPair = 40;
68 | const PawnIsolated = -10;
69 | const PawnPassed = [0, 5, 10, 20, 35, 60, 100, 200];
70 | const RookOpenFile = 10;
71 | const RookSemiOpenFile = 5;
72 | const QueenOpenFile = 5;
73 | const QueenSemiOpenFile = 3;
74 | const EndGame_Material = (1 * PieceValue[Pieces.wr]) + (2 * PieceValue[Pieces.wn]) + (2 * PieceValue[Pieces.wp]) + (PieceValue[Pieces.wk]);
75 |
76 |
77 | function evalPosition() {
78 |
79 | var score = gameBoard.material[Color.white] - gameBoard.material[Color.black];
80 | //pawn
81 | for (const sq of gameBoard.pieceList[Pieces.wp]) {
82 | score += PawnTable[Sq120To64[sq]];
83 |
84 | if ((IsolatedBitMask[Sq120To64[sq]] & PawnBitBoard[Color.white]) == 0) {
85 | score += PawnIsolated;
86 | }
87 |
88 | if ((PassedPawnBitMask[Color.white][Sq120To64[sq]] & PawnBitBoard[Color.black]) == 0) {
89 | score += PawnPassed[rankOf(sq)];
90 | }
91 | }
92 | for (const sq of gameBoard.pieceList[Pieces.bp]) {
93 | score -= PawnTable[Mirror64[Sq120To64[sq]]];
94 | if ((IsolatedBitMask[Sq120To64[sq]] & PawnBitBoard[Color.black]) == 0) {
95 | score -= PawnIsolated;
96 | }
97 |
98 | if ((PassedPawnBitMask[Color.black][Sq120To64[sq]] & PawnBitBoard[Color.white]) == 0) {
99 | score -= PawnPassed[7 - rankOf(sq)];
100 | }
101 | }
102 |
103 | //knight
104 | for (const sq of gameBoard.pieceList[Pieces.wn]) {
105 | score += KnightTable[Sq120To64[sq]];
106 | }
107 | for (const sq of gameBoard.pieceList[Pieces.bn]) {
108 | score -= KnightTable[Mirror64[Sq120To64[sq]]];
109 | }
110 |
111 | //bishop
112 | for (const sq of gameBoard.pieceList[Pieces.wb]) {
113 | score += BishopTable[Sq120To64[sq]];
114 | }
115 | for (const sq of gameBoard.pieceList[Pieces.bb]) {
116 | score -= BishopTable[Mirror64[Sq120To64[sq]]];
117 | }
118 |
119 | //rook
120 | for (const sq of gameBoard.pieceList[Pieces.wr]) {
121 | score += RookTable[Sq120To64[sq]];
122 | //open file bonus
123 | if ((PawnBitBoard.getBoth() & FileBitMask[fileOf(sq)]) == 0) {
124 | score += RookOpenFile;
125 | }
126 | else if ((PawnBitBoard[Color.white] & FileBitMask[fileOf(sq)]) == 0) {
127 | score += RookSemiOpenFile;
128 | }
129 | }
130 | for (const sq of gameBoard.pieceList[Pieces.br]) {
131 | score -= RookTable[Mirror64[Sq120To64[sq]]];
132 | //open file bonus
133 | if ((PawnBitBoard.getBoth() & FileBitMask[fileOf(sq)]) == 0) {
134 | score -= RookOpenFile;
135 | }
136 | else if ((PawnBitBoard[Color.black] & FileBitMask[fileOf(sq)]) == 0) {
137 | score -= RookSemiOpenFile;
138 | }
139 | }
140 |
141 | //queen
142 | for (const sq of gameBoard.pieceList[Pieces.wq]) {
143 | //open file bonus
144 | if ((PawnBitBoard.getBoth() & FileBitMask[fileOf(sq)]) == 0) {
145 | score += QueenOpenFile;
146 | }
147 | else if ((PawnBitBoard[Color.white] & FileBitMask[fileOf(sq)]) == 0) {
148 | score += QueenSemiOpenFile;
149 | }
150 | }
151 |
152 | for (const sq of gameBoard.pieceList[Pieces.bq]) {
153 | //open file bonus
154 | if ((PawnBitBoard.getBoth() & FileBitMask[fileOf(sq)]) == 0) {
155 | score -= QueenOpenFile;
156 | }
157 | else if ((PawnBitBoard[Color.black] & FileBitMask[fileOf(sq)]) == 0) {
158 | score -= QueenSemiOpenFile;
159 | }
160 | }
161 |
162 | //white king
163 | let kingSq = gameBoard.pieceList[Pieces.wk][0];
164 | if (gameBoard.material[Color.black] <= EndGame_Material) {
165 | score += KingE[Sq120To64[kingSq]];
166 | }
167 | else {
168 | score += KingO[Sq120To64[kingSq]];
169 | }
170 |
171 | //black king
172 | kingSq = gameBoard.pieceList[Pieces.bk][0];
173 | if (gameBoard.material[Color.white] <= EndGame_Material) {
174 | score -= KingE[Mirror64[Sq120To64[kingSq]]];
175 | }
176 | else {
177 | score -= KingO[Mirror64[Sq120To64[kingSq]]];
178 | }
179 |
180 |
181 | //bonus
182 | if (gameBoard.pieceCount[Pieces.wb] >= 2) {
183 | score += BishopPair;
184 | }
185 | if (gameBoard.pieceCount[Pieces.bb] >= 2) {
186 | score -= BishopPair;
187 | }
188 |
189 | return (gameBoard.side == Color.white) ? score : -score;
190 | }
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
--------------------------------------------------------------------------------
/js/pgnCompiler.js:
--------------------------------------------------------------------------------
1 |
2 | // Example usage
3 | // let pgn = [
4 | // '1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7 11. Nbd2 Bb7 12. Bc2 Re8 13. Nf1 Bf8 14. Ng3 g6 15. Bg5 h6 16. Bd2 Bg7 17. Qc1 Kh7 18. Nh2 c5 19. d5 c4 20. Ng4 Nxg4 21. hxg4 Nc5 22. f3 a5 23. Kf2 b4 24. Rh1 Qb6 25. Be3 Qc7 26. Bxh6 Bxh6 27. Qxh6+ Kg8 28. Qh8#',
5 | // '1. b3 d5 2. Nf3 Nf6 3. Bb2 Bf5 4. Nh4 Bd7 5. e3 c5 6. c4 e6 7. Be2 Be7 8. O-O O-O 9. Nf3 Nc6 10. d4 cxd4 11. exd4 Rc8 12. Nbd2 dxc4 13. bxc4 Re8 14. Nb3 Bf8 15. Qd2 Ne7 16. Ne5 g6 17. Bf3 b6 18. Rfe1 Bg7 19. Rad1 b5 20. Nxd7 Nxd7 21. cxb5 Nb6 22. Na5 Nc4 23. Nxc4 Rxc4 24. Qd3 Rc8 25. a4 Qa5 26. Qb3 Red8 27. g3 Nd5 28. Rc1 Nb6 29. d5 Bxb2 30. Qxb2 exd5 31. Qf6 Qxa4 32. Rc7 Rf8 33. Rxc8 Rxc8 34. Re7 Rf8 35. h4 h5 36. Kg2 Qc2 37. g4 hxg4 38. Bxg4 d4 39. Be6 Qe4+ 40. Kg3 Qxe6 41. Rxe6 fxe6 42. Qxg6+ Kh8 43. Qh6+ Kg8 44. Qxe6+ Kg7 45. Qe5+ Rf6 46. Qxd4 Kf7 47. h5 Nc8 48. Kg4 Ne7 49. f4 Rb6 50. Qc4+ Kg7 51. Qc5 Ng8 52. Kg5 Nh6 53. Qe5+ Kf8 54. Qh8+ Ng8 55. h6 Rxh6 56. Qxh6+ Nxh6 57. Kxh6 Ke7 58. Kg7 Ke6 59. Kg6 1-0',
6 | // ];
7 | // setTimeout(() => {
8 | // const result = parsePGN(pgn[1]);
9 | // }, 200);
10 |
11 | function parsePGN(pgn) {
12 | engine.stop();
13 |
14 | let result = [];
15 | const moves = extractMoves(pgn);
16 | console.log(moves);
17 | newGame();
18 | for (const move of moves) {
19 | const match = isValidPgnMove(move);
20 | if (!match) {
21 | throw new Error(`${move} is a invalid pgn move`);
22 | }
23 |
24 | let { piece, fromFile, fromRank, toSq, capture } = match;
25 | let fromSq;
26 |
27 | if (move[0].toUpperCase() == 'O') {
28 | ({ fromSq, toSq } = getCastleSq(move));
29 | }
30 | else if (piece == 'P') {
31 | fromSq = getPawnFromSq(match);
32 | }
33 | else {
34 | piece = Pieces[SideChar[gameBoard.side] + piece.toLowerCase()];
35 | toSq = Squares[toSq];
36 | if (fromFile !== '') fromFile = fromFile.charCodeAt(0) - 'a'.charCodeAt(0);
37 | if (fromRank !== '') fromRank -= 1;
38 |
39 | let moves = generateMoves().filter(({ move }) => {
40 | return (
41 | gameBoard.pieces[moveFrom(move)] == piece &&
42 | moveTo(move) == toSq
43 | )
44 | });
45 |
46 | //filtering illegal move
47 | moves = moves.filter(({ move }) => {
48 | if (!doMove(move)) return false;
49 | undoMove();
50 | return true;
51 | })
52 | if (moves.length == 1) {
53 | gui.doMove(moves[0].move, { audio: false });
54 | }
55 | else {
56 | moves = moves.filter(({ move }) => {
57 | return fileOf(moveFrom(move)) === fromFile || rankOf(moveFrom(move)) === fromRank
58 | })
59 | if (moves.length !== 1) {
60 | throw new Error(`${move} is a Illigal move`);
61 | }
62 | gui.doMove(moves[0].move, { audio: false });
63 | }
64 | continue;
65 | }
66 |
67 | const parsedMove = parseMove(fromSq, toSq);
68 | if (!parsedMove) {
69 | throw new Error(`${move} is a Illigal move`);
70 | }
71 | // console.log(fromSq, toSq, parsedMove);
72 | gui.doMove(parsedMove, { audio: false });
73 | }
74 | }
75 |
76 | function isValidPgnMove(move) {
77 | const movePattern = /^(O-O-O|O-O|([NBKRQ]?)([a-h]?)([1-8]?)(x?)([a-h][1-8])(=[NBKRQ])?)[+#]?$/;
78 | const match = move.match(movePattern);
79 | if (!match) return null;
80 | return {
81 | piece: match[2] || 'P',
82 | fromFile: match[3] || '',
83 | fromRank: match[4] || '',
84 | capture: match[5] === 'x',
85 | toSq: match[6]
86 | }
87 | }
88 |
89 | function getCastleSq(move) {
90 | let fromSq, toSq;
91 | if (gameBoard.side == Color.white) {
92 | fromSq = 'e1';
93 | toSq = (move.toLowerCase() == 'o-o') ? 'g1' : 'c1';
94 | }
95 | else {
96 | fromSq = 'e8';
97 | toSq = (move.toLowerCase() == 'o-o') ? 'g8' : 'c8';
98 | }
99 | return { fromSq, toSq };
100 | }
101 |
102 | function getPawnFromSq({ fromFile, toSq, capture }) {
103 | toSq = Squares[toSq];
104 |
105 | if (gameBoard.side == Color.white) {
106 | if (capture) return fromFile + (rankOf(toSq - 10) + 1);
107 | if (gameBoard.pieces[toSq - 10] != Pieces.empty) {
108 | return SquaresChar[toSq - 10];
109 | }
110 | return SquaresChar[toSq - 20];
111 | }
112 | else {
113 | if (capture) return fromFile + (rankOf(toSq + 10) + 1);
114 | if (gameBoard.pieces[toSq + 10] != Pieces.empty) {
115 | return SquaresChar[toSq + 10];
116 | }
117 | return SquaresChar[toSq + 20];
118 | }
119 | }
120 |
121 |
122 |
123 |
124 | function getPGN() {
125 | let pgn = '';
126 | let ply = 0;
127 | for(let i = 0; i<=currMoveNode; ++i){
128 | const node = nodeList[i];
129 | if (i % 2 == 0) {
130 | pgn += ++ply + '. ';
131 | }
132 | pgn += node.textContent;
133 | pgn += ' ';
134 | }
135 | if (isGameOver()) {
136 | let result = resultTitle.textContent;
137 | switch (result) {
138 | case 'Draw': pgn += '1/2-1/2'; break;
139 | case 'White Won': pgn += '1-0'; break;
140 | case 'Black Won': pgn += '0-1'; break;
141 | }
142 | }
143 | return pgn;
144 | }
145 |
146 | function extractMoves(pgn) {
147 | const lines = pgn.split('\n');
148 | let movesStart = false;
149 | let moves = [];
150 |
151 | for (let line of lines) {
152 | // If we encounter a line without square brackets, it indicates the start of moves
153 | if (!movesStart && !line.startsWith('[') && line.trim() !== '') {
154 | movesStart = true;
155 | }
156 |
157 | // If movesStart is true, we process the line for moves
158 | if (movesStart) {
159 | // Remove comments and metadata enclosed in {}, and also strip any evaluation comments
160 | line = line.replace(/\{[^}]*\}|\([^\)]*\)/g, '').trim();
161 |
162 | const parts = line.split(/\s+/);
163 |
164 | for (let part of parts) {
165 | // Remove invalid moves (like move numbers, result, and annotations)
166 | if (!part.match(/^(\d+\.$|\d+\.\.\.$|1-0|0-1|1\/2-1\/2|\*|[\(\)]|\?\?|\!\!|\?|\!)/)) {
167 | moves.push(part);
168 | }
169 | }
170 |
171 | // Stop processing when we encounter the result of the game
172 | if (line.includes('1-0') || line.includes('0-1') || line.includes('1/2-1/2') || line.includes('*')) {
173 | break;
174 | }
175 | }
176 | }
177 | moves = moves.map(move=>move.replace(/[?()\s]+/g, ''));
178 | return moves;
179 | }
180 |
181 |
--------------------------------------------------------------------------------
/js/search.js:
--------------------------------------------------------------------------------
1 | function getMoveFromBook() {
2 | const key = getPolyKey();
3 | // no move present for current position
4 | if (!openingBook[key]) return false;
5 |
6 | // pick a random move from movelist
7 | const moves = openingBook[key];
8 | let move = moves[Math.floor(Math.random() * moves.length)].move;
9 | move = extractPolyMove(move);
10 | if(!move) return false;
11 |
12 | searchController.thinking = false;
13 | searchController.bestMove = move | FromBookFlag;
14 | searchController.bestScore = 0;
15 | searchController.depthReached = 0;
16 | return true;
17 | }
18 |
19 | const searchController = {};
20 | searchController.depthReached;
21 | searchController.nodes;
22 | searchController.fh;
23 | searchController.fhf;
24 | searchController.depth;
25 | searchController.time;
26 | searchController.start;
27 | searchController.stop;
28 | searchController.bestMove;
29 | searchController.bestScore;
30 | searchController.thinking;
31 | searchController.ply;
32 | searchController.killers = new Array(MaxDepth); //killer[ply][0/1];
33 | searchController.history = new Array(13); //
34 |
35 |
36 | searchController.clear = function () {
37 | this.killers = Array.from({ length: MaxDepth }, () => [null, null]);
38 | this.history = Array.from({ length: 13 }, () => Array(120).fill(0));
39 | this.ply = 0;
40 | this.depthReached = 0;
41 | this.nodes = 0;
42 | this.fh = 0;
43 | this.fhf = 0;
44 | this.start = Date.now();
45 | this.stop = false;
46 | }
47 |
48 | searchController.clear();
49 |
50 | function searchPosition(thinkingTime = 2) {
51 | let bestMove = null;
52 | let bestScore = -Infinite;
53 | let depth = 1;
54 | let ordering = 0;
55 |
56 | searchController.clear();
57 | PvTable.clear();
58 |
59 | if (getMoveFromBook()) return;
60 |
61 | searchController.depth = MaxDepth;
62 | searchController.start = Date.now();
63 | searchController.time = thinkingTime * 1000;
64 |
65 | for (depth = 1; depth <= searchController.depth; ++depth) {
66 | bestScore = alphaBeta(-Infinite, Infinite, depth);
67 |
68 | if (searchController.stop) break;
69 |
70 | bestMove = PvTable.getMove();
71 |
72 | if (depth != 1 && searchController.fh) {
73 | ordering = ((searchController.fhf / searchController.fh) * 100).toFixed(2);
74 | }
75 | if(ordering == 'NaN'){
76 | console.log(searchController.fh, searchController.fhf);
77 | }
78 |
79 | self.postMessage({
80 | command: 'searching',
81 | depth,
82 | pvLine: PvTable.getLine(depth),
83 | bestMove,
84 | bestScore,
85 | nodes: searchController.nodes,
86 | ordering,
87 | })
88 | searchController.bestMove = bestMove;
89 | searchController.bestScore = bestScore;
90 | searchController.depthReached = depth;
91 | }
92 |
93 | searchController.thinking = false;
94 | }
95 |
96 |
97 |
98 |
99 | function alphaBeta(alpha, beta, depth, { doNull = true } = {}) {
100 | if (depth <= 0) {
101 | return quiescence(alpha, beta);
102 | }
103 |
104 | if ((searchController.nodes % 2048) == 0) {
105 | checkTimeUp();
106 | }
107 |
108 | searchController.nodes++;
109 |
110 | if ((isRepetition() || gameBoard.fiftyMove >= 100) && searchController.ply != 0) {
111 | return 0;
112 | }
113 |
114 | if (searchController.ply >= MaxDepth) {
115 | return evalPosition();
116 | }
117 |
118 | let inCheck = gameBoard.checkSq != Squares.noSq;
119 | if (inCheck) {
120 | ++depth;
121 | }
122 |
123 | let score = -Infinite;
124 |
125 | const ttEntry = transpositionTable.get(gameBoard.positionKey);
126 | let pvMove = ttEntry?.move;
127 | if (ttEntry) {
128 | if (ttEntry.depth >= depth) {
129 | score = ttEntry.score;
130 | if(score > Mate) score -= searchController.ply;
131 | else if(score < -Mate) score += searchController.ply;
132 | if(ttEntry.flag === AlphaFlag && score <= alpha) return alpha;
133 | if(ttEntry.flag === BetaFlag && score >= beta) return beta;
134 | if(ttEntry.flag === ExactFlag) return score;
135 | }
136 | }
137 |
138 | //NULL Move Pruning
139 | if (doNull && !inCheck && searchController.ply && depth >= 4) {
140 | doNullMove();
141 | score = -alphaBeta(-beta, -beta + 1, depth - 4, { doNull: false });
142 | undoNullMove();
143 | if (searchController.stop) return 0;
144 | if (score >= beta) {
145 | return beta;
146 | }
147 | }
148 |
149 | const moves = generateMoves();
150 | let legalMove = 0;
151 | const prevAlpha = alpha;
152 | let bestMove = null;
153 |
154 | if (pvMove) {
155 | for (const moveObj of moves) {
156 | if (moveObj.move == pvMove) {
157 | moveObj.score = 2000000;
158 | break;
159 | }
160 | }
161 | }
162 |
163 | for (let i = 0; i < moves.length; ++i) {
164 | swapWithBest(i, moves);
165 | const move = moves[i].move;
166 |
167 | if (!doMove(move)) continue;
168 | legalMove++;
169 | searchController.ply++;
170 | score = -alphaBeta(-beta, -alpha, depth - 1);
171 |
172 | undoMove();
173 | searchController.ply--;
174 |
175 | if (searchController.stop) return 0;
176 |
177 | if (score > alpha) {
178 | bestMove = move;;
179 |
180 | if (score >= beta) {
181 | if (legalMove === 1) searchController.fhf++;
182 | searchController.fh++;
183 | if (!(move & CaptureFlag)) {
184 | searchController.killers[searchController.ply][1] = searchController.killers[searchController.ply][0];
185 | searchController.killers[searchController.ply][0] = move;
186 | }
187 |
188 | transpositionTable.add(gameBoard.positionKey, move, beta, BetaFlag, depth);
189 | return beta;
190 | }
191 |
192 | alpha = score;
193 | if (!(move & CaptureFlag)) {
194 | let piece = gameBoard.pieces[moveFrom(move)];
195 | let toSq = moveTo(move);
196 | searchController.history[piece][toSq] += depth * depth;
197 | }
198 | }
199 |
200 | }
201 |
202 | if (legalMove == 0) {
203 | if (inCheck) {
204 | return -Infinite + searchController.ply;
205 | } else {
206 | return 0;
207 | }
208 | }
209 |
210 | if (alpha != prevAlpha) {
211 | transpositionTable.add(gameBoard.positionKey, bestMove, score, ExactFlag, depth);
212 | }
213 | else {
214 | transpositionTable.add(gameBoard.positionKey, bestMove, alpha, AlphaFlag, depth);
215 | }
216 |
217 | return alpha;
218 | }
219 |
220 |
221 | function quiescence(alpha, beta) {
222 |
223 | if ((searchController.nodes % 2048) == 0) {
224 | checkTimeUp();
225 | }
226 |
227 | searchController.nodes++;
228 |
229 | if ((isRepetition() || gameBoard.fiftyMove >= 100) && searchController.ply != 0) {
230 | return 0;
231 | }
232 |
233 | if (searchController.ply >= MaxDepth) {
234 | return evalPosition();
235 | }
236 |
237 | let score = evalPosition();
238 |
239 | if (score >= beta) return beta;
240 | if (score > alpha) alpha = score;
241 |
242 |
243 | let legalMove = 0;
244 | let moves = generateCaptureMoves();
245 |
246 |
247 | for (let i = 0; i < moves.length; ++i) {
248 | swapWithBest(i, moves);
249 | const move = moves[i].move;
250 |
251 | if (!doMove(move)) continue;
252 | legalMove++;
253 | searchController.ply++;
254 |
255 | score = -quiescence(-beta, -alpha);
256 |
257 | undoMove();
258 | searchController.ply--;
259 |
260 | if (searchController.stop) return 0;
261 |
262 | if (score > alpha) {
263 | if (score >= beta) {
264 | if (legalMove == 1) {
265 | searchController.fhf++;
266 | }
267 | searchController.fh++;
268 | return beta;
269 | }
270 | alpha = score;
271 | }
272 | }
273 |
274 |
275 | return alpha;
276 |
277 | }
278 |
279 | function swapWithBest(i, moves) {
280 | let bestIndex = i;
281 | for (let j = i + 1; j < moves.length; ++j) {
282 | if (moves[j].score > moves[bestIndex].score) {
283 | bestIndex = j;
284 | }
285 | }
286 | if (bestIndex !== i) {
287 | [moves[i], moves[bestIndex]] = [moves[bestIndex], moves[i]];
288 | }
289 | }
290 |
291 |
292 | function checkTimeUp() {
293 | if ((Date.now() - searchController.start) > searchController.time) {
294 | searchController.stop = true;
295 | }
296 | }
297 |
298 | function isRepetition() {
299 | let hisPly = gameBoard.history.length;
300 | for (let i = hisPly - gameBoard.fiftyMove; i < hisPly - 1; ++i) {
301 | if (gameBoard.positionKey == gameBoard.history[i].positionKey) {
302 | return true;
303 | }
304 | }
305 |
306 | return false;
307 | }
308 |
309 |
--------------------------------------------------------------------------------
/js/board.js:
--------------------------------------------------------------------------------
1 | let gameBoard = {};
2 |
3 | gameBoard.pieces = new Array(120);
4 | gameBoard.side = Color.white;
5 | gameBoard.castlePermission = 0;
6 | gameBoard.enPassantSq = Squares.noSq;
7 | gameBoard.fiftyMove = 0;
8 | gameBoard.checkSq = Squares.noSq;
9 | gameBoard.positionKey = 0;
10 |
11 | gameBoard.pieceList = new Array(13);
12 | gameBoard.pieceCount = new Array(13);
13 | gameBoard.material = new Array(2);
14 | gameBoard.history = [];
15 |
16 | function resetBoard() {
17 | for (let i = 0; i < 120; ++i) {
18 | gameBoard.pieces[i] = Squares.offBoard;
19 | }
20 | for (const sq of Sq64To120) {
21 | gameBoard.pieces[sq] = Pieces.empty;
22 | }
23 |
24 | for (let i = 0; i < 13; ++i) {
25 | gameBoard.pieceList[i] = [];
26 | gameBoard.pieceCount[i] = 0;
27 | }
28 |
29 | gameBoard.material[Color.white] = 0;
30 | gameBoard.material[Color.black] = 0;
31 |
32 | gameBoard.side = Color.white;
33 | gameBoard.castlePermission = 0;
34 | gameBoard.enPassantSq = Squares.noSq;
35 | gameBoard.fiftyMove = 0;
36 | gameBoard.checkSq = Squares.noSq;
37 | gameBoard.positionKey = 0;
38 | gameBoard.history = [];
39 | }
40 |
41 | function isValidFen(fen) {
42 | const [position, side, castling, enPassant] = fen.split(' ');
43 |
44 | const rows = position.split('/');
45 | if (rows.length !== 8) {
46 | throw new Error('FEN string should contain exactly 8 rows separated by slashes.');
47 | }
48 |
49 | const validPosition = rows.every(row => {
50 | let count = 0;
51 | for (let char of row) {
52 | if (char >= '1' && char <= '8') {
53 | count += parseInt(char);
54 | } else if ('pnbrqkPNBRQK'.includes(char)) {
55 | count++;
56 | } else {
57 | throw new Error(`Invalid character "${char}" found in FEN position.`);
58 | }
59 | }
60 | return count === 8;
61 | });
62 |
63 | if (!validPosition) {
64 | throw new Error('Each row in FEN position must sum to exactly 8 squares.');
65 | }
66 |
67 | if (!['w', 'b'].includes(side)) {
68 | throw new Error('Side to move must be either "w" (white) or "b" (black).');
69 | }
70 |
71 | // Validate castling availability
72 | if (!/^(-|[KQkq]{1,4})$/.test(castling)) {
73 | throw new Error('Castling availability must be "-" or a combination of "K", "Q", "k", and "q".');
74 | }
75 |
76 | // Validate en passant target square
77 | if (!/^(-|[a-h][36])$/.test(enPassant)) {
78 | throw new Error('En passant target square must be "-" or a valid square (e.g., "e3", "d6").');
79 | }
80 |
81 | return true;
82 | }
83 |
84 | function parseFen(fen) {
85 | try {
86 | isValidFen(fen)
87 | }
88 | catch (error) {
89 | console.error(error);
90 | alert(error.message);
91 | return;
92 | }
93 | resetBoard();
94 |
95 | const [position, side, castlePermission, enPassantSq] = fen.split(' ');
96 | const map = {
97 | P: Pieces.wp, R: Pieces.wr, N: Pieces.wn, B: Pieces.wb, Q: Pieces.wq, K: Pieces.wk,
98 | p: Pieces.bp, r: Pieces.br, n: Pieces.bn, b: Pieces.bb, q: Pieces.bq, k: Pieces.bk,
99 | };
100 |
101 | let rank = Rank8;
102 | let file = FileA;
103 |
104 | for (const char of position) {
105 | if (char === '/') {
106 | rank--;
107 | file = FileA;
108 | continue;
109 | }
110 |
111 | if (char >= '1' && char <= '8') {
112 | file += +char;
113 | }
114 | else {
115 | let sq = fileRank2Sq(file++, rank);
116 | gameBoard.pieces[sq] = map[char];
117 | }
118 | }
119 |
120 | gameBoard.side = (side === 'w') ? Color.white : Color.black;
121 |
122 | for (const char of castlePermission) {
123 | gameBoard.castlePermission |= CastleBit[char];
124 | }
125 |
126 | if (enPassantSq !== '-') {
127 | gameBoard.enPassantSq = Squares[enPassantSq];
128 | }
129 |
130 | //update value of pieces, pieceCount, and piece points
131 | updateMaterial();
132 |
133 | //is opponent king in check
134 | let opponentKing = gameBoard.pieceList[Kings[gameBoard.side]][0];
135 | if (isUnderAttack(opponentKing, gameBoard.side ^ 1)) {
136 | gameBoard.checkSq = gameBoard.pieceList[Kings[gameBoard.side]][0];
137 | }
138 |
139 | //assign a unique key for each different position
140 | gameBoard.positionKey = generatePositionKey();
141 | }
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | function generatePositionKey() {
153 | let hashKey = 0;
154 | for (const sq of Sq64To120) {
155 | let piece = gameBoard.pieces[sq];
156 | if (piece !== Pieces.empty) {
157 | hashKey ^= PieceKeys[piece][sq];
158 | }
159 | }
160 | hashKey ^= CastleKeys[gameBoard.castlePermission];
161 |
162 | if (gameBoard.side === Color.white) {
163 | hashKey ^= SideKey;
164 | }
165 | if (gameBoard.enPassantSq !== Squares.noSq) {
166 | hashKey ^= PieceKeys[Pieces.empty][gameBoard.enPassantSq];
167 | }
168 | return hashKey;
169 | }
170 |
171 | function updateMaterial() {
172 | for (const sq of Sq64To120) {
173 | let piece = gameBoard.pieces[sq];
174 | if (piece !== Pieces.empty) {
175 | gameBoard.pieceList[piece].push(sq);
176 | gameBoard.pieceCount[piece]++;
177 | gameBoard.material[PieceColor[piece]] += PieceValue[piece];
178 | }
179 | }
180 | PawnBitBoard.init();
181 | }
182 |
183 | function printBoard() {
184 | let line = '';
185 | for (let rank = Rank8; rank >= Rank1; --rank) {
186 | line = rank + ' ';
187 | for (let file = FileA; file <= FileH; ++file) {
188 | let sq = fileRank2Sq(file, rank);
189 | let piece = gameBoard.pieces[sq];
190 | line += ' ' + PieceChar[piece] + ' ';
191 | }
192 | console.log(line);
193 | }
194 |
195 | line = '\n ';
196 | for (let file = 0; file < 8; ++file) {
197 | line += FileChar[file] + ' ';
198 | }
199 | console.log(line);
200 |
201 | console.log('side :', SideChar[gameBoard.side]);
202 | console.log('castle :', gameBoard.castlePermission.toString(2));
203 | if (gameBoard.enPassantSq !== Squares.noSq) {
204 | console.log('enPassant :', SquaresChar[gameBoard.enPassantSq])
205 | }
206 | console.log('\x1b[33m' + 'hashKey : ' + gameBoard.positionKey.toString(16));
207 |
208 | if (gameBoard.checkSq !== Squares.noSq) {
209 | console.log('\x1b[31m' + 'check : ' + SquaresChar[gameBoard.checkSq])
210 | }
211 | }
212 |
213 | function getFen() {
214 | let fen = '';
215 | for (let rank = Rank8; rank >= Rank1; --rank) {
216 | let emptySq = 0;
217 | for (let file = FileA; file <= FileH; ++file) {
218 | let sq = fileRank2Sq(file, rank);
219 | let piece = gameBoard.pieces[sq];
220 |
221 | if (piece === Pieces.empty) {
222 | emptySq++;
223 | }
224 | else {
225 | if (emptySq) {
226 | fen += emptySq;
227 | emptySq = 0;
228 | }
229 | fen += PieceChar[piece];
230 | }
231 | }
232 | if (emptySq) {
233 | fen += emptySq;
234 | }
235 | if (rank !== Rank1) {
236 | fen += '/'
237 | }
238 | }
239 |
240 | fen += ' ' + SideChar[gameBoard.side] + ' ';
241 | if (gameBoard.castlePermission) {
242 | if (gameBoard.castlePermission & CastleBit.K) fen += 'K';
243 | if (gameBoard.castlePermission & CastleBit.Q) fen += 'Q';
244 | if (gameBoard.castlePermission & CastleBit.k) fen += 'k';
245 | if (gameBoard.castlePermission & CastleBit.q) fen += 'q';
246 | }
247 | else {
248 | fen += '-';
249 | }
250 |
251 | if (gameBoard.enPassantSq !== Squares.noSq) {
252 | fen += ' ' + SquaresChar[gameBoard.enPassantSq] + ' ';
253 | }
254 | else {
255 | fen += ' - '
256 | }
257 | fen += gameBoard.fiftyMove + ' ';
258 | //ply that visible on gui
259 | fen += ply ? ply : 1;
260 | return fen;
261 | }
262 |
263 | function isUnderAttack(sq, attakingSide) {
264 | if (attakingSide === Color.white) {
265 | if (gameBoard.pieces[sq - 9] === Pieces.wp || gameBoard.pieces[sq - 11] === Pieces.wp) {
266 | return true;
267 | }
268 | }
269 | else {
270 | if (gameBoard.pieces[sq + 9] === Pieces.bp || gameBoard.pieces[sq + 11] === Pieces.bp) {
271 | return true;
272 | }
273 | }
274 |
275 | //non sliding attack
276 | for (const attakingPiece of NonSlidingPieces[attakingSide]) {
277 | for (const direction of PieceDirections[attakingPiece]) {
278 | let targetSq = sq + direction;
279 |
280 | if (gameBoard.pieces[targetSq] === attakingPiece) {
281 | return true;
282 | }
283 | }
284 | }
285 |
286 | for (const attakingPiece of SlidingPieces[attakingSide]) {
287 | for (const direction of PieceDirections[attakingPiece]) {
288 | let targetSq = sq + direction;
289 | while (targetSq !== Squares.offBoard) {
290 | if (gameBoard.pieces[targetSq] !== Pieces.empty) {
291 | if (gameBoard.pieces[targetSq] === attakingPiece) {
292 | return true;
293 | }
294 | break;
295 | }
296 | targetSq += direction;
297 | }
298 | }
299 | }
300 |
301 | return false;
302 | }
303 |
--------------------------------------------------------------------------------
/js/moveStructure.js:
--------------------------------------------------------------------------------
1 | //move structure set Mask
2 | // 0000 0000 0000 0000 0000 0111 1111 0x7f from
3 | // 0000 0000 0000 0011 1111 1000 0000 0x7f to
4 | // 0000 0000 0011 1100 0000 0000 0000 0xf capture piece
5 | // 0000 0011 1100 0000 0000 0000 0000 0xf promoted piece
6 | // 0000 0100 0000 0000 0000 0000 0000 0x40000 enpassant
7 | // 0000 1000 0000 0000 0000 0000 0000 0x80000 castle
8 | // 0001 0000 0000 0000 0000 0000 0000 0x1000000 pawnstart
9 | // 0010 0000 0000 0000 0000 0000 0000 0x2000000 from Book
10 |
11 |
12 | //flags 🚩
13 | const EnPassantFlag = 0x400000;
14 | const CastleFlag = 0x800000;
15 | const PawnStartFlag = 0x1000000;
16 | const FromBookFlag = 0x2000000;
17 |
18 | const CaptureFlag = 0x3c000;
19 | const PromotionFlag = 0x3c0000;
20 |
21 | function buildMove(from, to, capturedPiece, promotedPiece, flag) {
22 | return from | (to << 7) | (capturedPiece << 14) | (promotedPiece << 18) | flag;
23 | }
24 |
25 |
26 | function moveFrom(move) {
27 | return (move & 0x7f);
28 | }
29 | function moveTo(move) {
30 | return (move >> 7) & 0x7f;
31 |
32 | }
33 | function moveCapturePiece(move) {
34 | return (move >> 14) & 0xf;
35 | }
36 | function movePromotionPiece(move) {
37 | return (move >> 18) & 0xf;
38 | }
39 |
40 | function moveDetail(move) {
41 | if (!move) return;
42 |
43 | console.log('from :', SquaresChar[moveFrom(move)]);
44 | console.log('to :', SquaresChar[moveTo(move)]);
45 | console.log('capture :', PieceChar[moveCapturePiece(move)]);
46 | console.log('promotion :', PieceChar[movePromotionPiece(move)]);
47 |
48 | if (move & EnPassantFlag) console.log('flag : enPassant');
49 | if (move & PawnStartFlag) console.log('flag : pawn start');
50 | if (move & CastleFlag) console.log('flag : castle');
51 | console.log('');
52 | }
53 |
54 | function movePiece(from, to) {
55 | let piece = gameBoard.pieces[from];
56 |
57 | hashPiece(from, piece);
58 | gameBoard.pieces[from] = Pieces.empty;
59 |
60 | hashPiece(to, piece);
61 | gameBoard.pieces[to] = piece;
62 |
63 | if(PieceType[piece] == 'p'){
64 | PawnBitBoard.clearBit(PieceColor[piece], Sq120To64[from]);
65 | PawnBitBoard.setBit(PieceColor[piece], Sq120To64[to]);
66 | }
67 |
68 | for (let i = 0; i < gameBoard.pieceCount[piece]; ++i) {
69 | if (gameBoard.pieceList[piece][i] === from) {
70 | gameBoard.pieceList[piece][i] = to
71 | return;
72 | }
73 | }
74 | }
75 | function addPiece(sq, piece) {
76 | if (gameBoard.pieces[sq] !== Pieces.empty) {
77 | console.error('add piece error');
78 | return;
79 | }
80 | hashPiece(sq, piece);
81 | gameBoard.pieces[sq] = piece;
82 | gameBoard.material[PieceColor[piece]] += PieceValue[piece];
83 | gameBoard.pieceList[piece].push(sq);
84 | gameBoard.pieceCount[piece]++;
85 | if (PieceType[piece] == 'p') {
86 | PawnBitBoard.setBit(PieceColor[piece], Sq120To64[sq])
87 | }
88 | }
89 |
90 | function removePiece(sq) {
91 | let piece = gameBoard.pieces[sq];
92 | hashPiece(sq, piece);
93 | gameBoard.pieces[sq] = Pieces.empty;
94 | gameBoard.material[PieceColor[piece]] -= PieceValue[piece];
95 |
96 | for (let i = 0; i < gameBoard.pieceCount[piece]; ++i) {
97 | if (gameBoard.pieceList[piece][i] === sq) {
98 | let lastIndex = gameBoard.pieceCount[piece] - 1;
99 | gameBoard.pieceList[piece][i] = gameBoard.pieceList[piece][lastIndex];
100 | gameBoard.pieceList[piece].pop();
101 | break;
102 | }
103 | }
104 | gameBoard.pieceCount[piece]--;
105 | if (PieceType[piece] == 'p') {
106 | PawnBitBoard.clearBit(PieceColor[piece], Sq120To64[sq])
107 | }
108 | }
109 |
110 |
111 | function doMove(move) {
112 | if (!move) {
113 | return;
114 | }
115 | const from = moveFrom(move);
116 | const to = moveTo(move);
117 | const side = gameBoard.side;
118 | const piece = gameBoard.pieces[from];
119 | gameBoard.history.push({
120 | fiftyMove: gameBoard.fiftyMove,
121 | positionKey: gameBoard.positionKey,
122 | enPassantSq: gameBoard.enPassantSq,
123 | castlePermission: gameBoard.castlePermission,
124 | checkSq: gameBoard.checkSq,
125 | move,
126 | });
127 |
128 | if (move & EnPassantFlag) {
129 | if (side === Color.white) {
130 | removePiece(to - 10);
131 | } else {
132 | removePiece(to + 10);
133 | }
134 | }
135 | else if (move & CastleFlag) {
136 | switch (to) {
137 | case Squares.g1: movePiece(Squares.h1, Squares.f1); break;
138 | case Squares.c1: movePiece(Squares.a1, Squares.d1); break;
139 |
140 | case Squares.g8: movePiece(Squares.h8, Squares.f8); break;
141 | case Squares.c8: movePiece(Squares.a8, Squares.d8); break;
142 |
143 | default: break;
144 | }
145 | }
146 | //hash out
147 | if (gameBoard.enPassantSq !== Squares.noSq) {
148 | hashEnPassant();
149 | }
150 | hashCastle();
151 |
152 | gameBoard.castlePermission &= CastlePermission[from];
153 | gameBoard.castlePermission &= CastlePermission[to];
154 | gameBoard.enPassantSq = Squares.noSq;
155 |
156 | //hash in
157 | hashCastle();
158 |
159 | gameBoard.fiftyMove++;
160 |
161 | if (PieceType[piece] === 'p') {
162 | gameBoard.fiftyMove = 0;
163 | if (move & PawnStartFlag) {
164 | if (side === Color.white) {
165 | gameBoard.enPassantSq = from + 10;
166 | }
167 | else {
168 | gameBoard.enPassantSq = from - 10;
169 | }
170 | hashEnPassant();
171 | }
172 | }
173 |
174 | if (move & CaptureFlag) {
175 | gameBoard.fiftyMove = 0;
176 | removePiece(to);
177 | }
178 | movePiece(from, to);
179 |
180 | if (move & PromotionFlag) {
181 | removePiece(to);
182 | addPiece(to, movePromotionPiece(move));
183 | }
184 |
185 | gameBoard.side ^= 1;
186 | hashSide();
187 |
188 | let kingOnSq = gameBoard.pieceList[Kings[side]][0];
189 | let enemyKingOnSq = gameBoard.pieceList[Kings[gameBoard.side]][0];
190 |
191 | if (isUnderAttack(kingOnSq, gameBoard.side)) {
192 | undoMove();
193 | return false;
194 | }
195 |
196 | if (isUnderAttack(enemyKingOnSq, side)) {
197 | gameBoard.checkSq = enemyKingOnSq;
198 | }
199 | else {
200 | gameBoard.checkSq = Squares.noSq;
201 | }
202 | return true;
203 | }
204 |
205 | function undoMove() {
206 | if (gameBoard.history.length === 0) {
207 | return null;
208 | }
209 |
210 | const {
211 | fiftyMove,
212 | enPassantSq,
213 | castlePermission,
214 | checkSq,
215 | move,
216 | } = gameBoard.history.pop();
217 |
218 | if (gameBoard.enPassantSq != Squares.noSq) {
219 | hashEnPassant();
220 | }
221 | hashCastle();
222 |
223 | gameBoard.fiftyMove = fiftyMove;
224 | gameBoard.enPassantSq = enPassantSq;
225 | gameBoard.castlePermission = castlePermission;
226 | gameBoard.checkSq = checkSq;
227 |
228 | if (gameBoard.enPassantSq != Squares.noSq) {
229 | hashEnPassant();
230 | }
231 | hashCastle();
232 |
233 | gameBoard.side ^= 1;
234 | hashSide();
235 |
236 | const from = moveFrom(move);
237 | const to = moveTo(move);
238 |
239 | if (move & EnPassantFlag) {
240 | if (gameBoard.side === Color.white) {
241 | addPiece(to - 10, Pieces.bp);
242 | }
243 | else {
244 | addPiece(to + 10, Pieces.wp);
245 | }
246 | }
247 | else if (move & CastleFlag) {
248 | switch (to) {
249 | case Squares.g1: movePiece(Squares.f1, Squares.h1); break;
250 | case Squares.c1: movePiece(Squares.d1, Squares.a1); break;
251 |
252 | case Squares.g8: movePiece(Squares.f8, Squares.h8); break;
253 | case Squares.c8: movePiece(Squares.d8, Squares.a8); break;
254 |
255 | default: break;
256 | }
257 | }
258 |
259 |
260 | movePiece(to, from);
261 | if (move & CaptureFlag) {
262 | addPiece(to, moveCapturePiece(move));
263 | }
264 | if (move & PromotionFlag) {
265 | removePiece(from);
266 | addPiece(from, PieceColor[movePromotionPiece(move)] === Color.white ? Pieces.wp : Pieces.bp);
267 | }
268 |
269 | return move;
270 | }
271 |
272 |
273 | // NULL MOVE
274 | function doNullMove() {
275 | if (gameBoard.checkSq != Squares.noSq) return;
276 |
277 | gameBoard.history.push({
278 | fiftyMove: gameBoard.fiftyMove,
279 | positionKey: gameBoard.positionKey,
280 | enPassantSq: gameBoard.enPassantSq,
281 | castlePermission: gameBoard.castlePermission,
282 | checkSq: gameBoard.checkSq,
283 | move: null,
284 | });
285 |
286 | gameBoard.enPassantSq = Squares.noSq;
287 |
288 | gameBoard.side ^= 1;
289 | hashSide();
290 | }
291 |
292 | function undoNullMove() {
293 | if (gameBoard.history.length === 0) {
294 | return null;
295 | }
296 |
297 | const {
298 | fiftyMove,
299 | enPassantSq,
300 | castlePermission,
301 | checkSq,
302 | } = gameBoard.history.pop();
303 |
304 | if (gameBoard.enPassantSq != Squares.noSq) {
305 | hashEnPassant();
306 | }
307 |
308 | gameBoard.castlePermission = castlePermission;
309 | gameBoard.fiftyMove = fiftyMove;
310 | gameBoard.enPassantSq = enPassantSq;
311 | gameBoard.checkSq = checkSq;
312 |
313 | if (gameBoard.enPassantSq != Squares.noSq) {
314 | hashEnPassant();
315 | }
316 | gameBoard.side ^= 1;
317 | hashSide();
318 |
319 | return null;
320 | }
321 |
322 | function parseMove(fromSq, toSq, movelist) {
323 | fromSq = Squares[fromSq];
324 | toSq = Squares[toSq];
325 | if (!movelist) {
326 | movelist = generateMoves();
327 | }
328 |
329 |
330 | let found = null;
331 | for (const { move } of movelist) {
332 | if (moveFrom(move) === fromSq && moveTo(move) === toSq) {
333 | found = move;
334 | break;
335 | }
336 | }
337 |
338 | if (found) {
339 | if (!doMove(found)) {
340 | IllegalSound.play();
341 | return null;
342 | }
343 | undoMove();
344 | }
345 |
346 | return found;
347 | }
348 |
--------------------------------------------------------------------------------
/js/perft.js:
--------------------------------------------------------------------------------
1 | function perft(depth) {
2 | if (depth <= 0) {
3 | return 1;
4 | }
5 | let count = 0;
6 | for (const { move } of generateMoves()) {
7 | if (doMove(move) == false) {
8 | continue;
9 | }
10 | count += perft(depth - 1);
11 | undoMove();
12 | }
13 | return count;
14 | }
15 |
16 | function perftTest(depth) {
17 | let moveCount = 0;
18 | let totalNodeSeared = 0;
19 | for (const { move } of generateMoves()) {
20 | if (doMove(move) == false) {
21 | continue;
22 | }
23 |
24 | let count = perft(depth - 1);
25 | undoMove();
26 |
27 | let test = TestCases[gameBoard.positionKey.toString(16)][depth][moveStr(move)] == count ? '✅' : '❌';
28 | if(!test) test = '';
29 |
30 | totalNodeSeared += count;
31 | console.log(++moveCount, moveStr(move), count, test);
32 | }
33 | console.log('Total Node Seared', totalNodeSeared);
34 | }
35 |
36 |
37 |
38 |
39 |
40 | const TestCases = {
41 | '5300d2e1': {
42 | 1: {
43 | 'a2a3': 1,
44 | 'a2a4': 1,
45 | 'b2b3': 1,
46 | 'b2b4': 1,
47 | 'c2c3': 1,
48 | 'c2c4': 1,
49 | 'd2d3': 1,
50 | 'd2d4': 1,
51 | 'e2e3': 1,
52 | 'e2e4': 1,
53 | 'f2f3': 1,
54 | 'f2f4': 1,
55 | 'g2g3': 1,
56 | 'g2g4': 1,
57 | 'h2h3': 1,
58 | 'h2h4': 1,
59 | 'b1a3': 1,
60 | 'b1c3': 1,
61 | 'g1f3': 1,
62 | 'g1h3': 1,
63 | },
64 | 2: {
65 | 'a2a3': 20,
66 | 'a2a4': 20,
67 | 'b2b3': 20,
68 | 'b2b4': 20,
69 | 'c2c3': 20,
70 | 'c2c4': 20,
71 | 'd2d3': 20,
72 | 'd2d4': 20,
73 | 'e2e3': 20,
74 | 'e2e4': 20,
75 | 'f2f3': 20,
76 | 'f2f4': 20,
77 | 'g2g3': 20,
78 | 'g2g4': 20,
79 | 'h2h3': 20,
80 | 'h2h4': 20,
81 | 'b1a3': 20,
82 | 'b1c3': 20,
83 | 'g1f3': 20,
84 | 'g1h3': 20,
85 | },
86 | 3: {
87 | 'a2a3': 380,
88 | 'a2a4': 420,
89 | 'b2b3': 420,
90 | 'b2b4': 421,
91 | 'c2c3': 420,
92 | 'c2c4': 441,
93 | 'd2d3': 539,
94 | 'd2d4': 560,
95 | 'e2e3': 599,
96 | 'e2e4': 600,
97 | 'f2f3': 380,
98 | 'f2f4': 401,
99 | 'g2g3': 420,
100 | 'g2g4': 421,
101 | 'h2h3': 380,
102 | 'h2h4': 420,
103 | 'b1a3': 400,
104 | 'b1c3': 440,
105 | 'g1f3': 440,
106 | 'g1h3': 400,
107 | },
108 | 4: {
109 | 'a2a3': 8457,
110 | 'a2a4': 9329,
111 | 'b2b3': 9345,
112 | 'b2b4': 9332,
113 | 'c2c3': 9272,
114 | 'c2c4': 9744,
115 | 'd2d3': 11959,
116 | 'd2d4': 12435,
117 | 'e2e3': 13134,
118 | 'e2e4': 13160,
119 | 'f2f3': 8457,
120 | 'f2f4': 8929,
121 | 'g2g3': 9345,
122 | 'g2g4': 9328,
123 | 'h2h3': 8457,
124 | 'h2h4': 9329,
125 | 'b1a3': 8885,
126 | 'b1c3': 9755,
127 | 'g1f3': 9748,
128 | 'g1h3': 8881,
129 |
130 | },
131 | 5: {
132 | 'a2a3': 181046,
133 | 'a2a4': 217832,
134 | 'b2b3': 215255,
135 | 'b2b4': 216145,
136 | 'c2c3': 222861,
137 | 'c2c4': 240082,
138 | 'd2d3': 328511,
139 | 'd2d4': 361790,
140 | 'e2e3': 402988,
141 | 'e2e4': 405385,
142 | 'f2f3': 178889,
143 | 'f2f4': 198473,
144 | 'g2g3': 217210,
145 | 'g2g4': 214048,
146 | 'h2h3': 181044,
147 | 'h2h4': 218829,
148 | 'b1a3': 198572,
149 | 'b1c3': 234656,
150 | 'g1f3': 233491,
151 | 'g1h3': 198502,
152 | },
153 | 6: {
154 | 'a2a3': 4463267,
155 | 'a2a4': 5363555,
156 | 'b2b3': 5310358,
157 | 'b2b4': 5293555,
158 | 'c2c3': 5417640,
159 | 'c2c4': 5866666,
160 | 'd2d3': 8073082,
161 | 'd2d4': 8879566,
162 | 'e2e3': 9726018,
163 | 'e2e4': 9771632,
164 | 'f2f3': 4404141,
165 | 'f2f4': 4890429,
166 | 'g2g3': 5346260,
167 | 'g2g4': 5239875,
168 | 'h2h3': 4463070,
169 | 'h2h4': 5385554,
170 | 'b1a3': 4856835,
171 | 'b1c3': 5708064,
172 | 'g1f3': 5723523,
173 | 'g1h3': 4877234,
174 | },
175 | },
176 |
177 | '6839c89f': {
178 | 1: {
179 | a2a3: 1,
180 | b2b3: 1,
181 | g2g3: 1,
182 | d5d6: 1,
183 | a2a4: 1,
184 | g2g4: 1,
185 | g2h3: 1,
186 | d5e6: 1,
187 | c3b1: 1,
188 | c3d1: 1,
189 | c3a4: 1,
190 | c3b5: 1,
191 | e5d3: 1,
192 | e5c4: 1,
193 | e5g4: 1,
194 | e5c6: 1,
195 | e5g6: 1,
196 | e5d7: 1,
197 | e5f7: 1,
198 | d2c1: 1,
199 | d2e3: 1,
200 | d2f4: 1,
201 | d2g5: 1,
202 | d2h6: 1,
203 | e2d1: 1,
204 | e2f1: 1,
205 | e2d3: 1,
206 | e2c4: 1,
207 | e2b5: 1,
208 | e2a6: 1,
209 | a1b1: 1,
210 | a1c1: 1,
211 | a1d1: 1,
212 | h1f1: 1,
213 | h1g1: 1,
214 | f3d3: 1,
215 | f3e3: 1,
216 | f3g3: 1,
217 | f3h3: 1,
218 | f3f4: 1,
219 | f3g4: 1,
220 | f3f5: 1,
221 | f3h5: 1,
222 | f3f6: 1,
223 | e1d1: 1,
224 | e1f1: 1,
225 | e1g1: 1,
226 | e1c1: 1,
227 | },
228 | 2: {
229 | a2a3: 44,
230 | b2b3: 42,
231 | g2g3: 42,
232 | d5d6: 41,
233 | a2a4: 44,
234 | g2g4: 42,
235 | g2h3: 43,
236 | d5e6: 46,
237 | c3b1: 42,
238 | c3d1: 42,
239 | c3a4: 42,
240 | c3b5: 39,
241 | e5d3: 43,
242 | e5c4: 42,
243 | e5g4: 44,
244 | e5c6: 41,
245 | e5g6: 42,
246 | e5d7: 45,
247 | e5f7: 44,
248 | d2c1: 43,
249 | d2e3: 43,
250 | d2f4: 43,
251 | d2g5: 42,
252 | d2h6: 41,
253 | e2d1: 44,
254 | e2f1: 44,
255 | e2d3: 42,
256 | e2c4: 41,
257 | e2b5: 39,
258 | e2a6: 36,
259 | a1b1: 43,
260 | a1c1: 43,
261 | a1d1: 43,
262 | h1f1: 43,
263 | h1g1: 43,
264 | f3d3: 42,
265 | f3e3: 43,
266 | f3g3: 43,
267 | f3h3: 43,
268 | f3f4: 43,
269 | f3g4: 43,
270 | f3f5: 45,
271 | f3h5: 43,
272 | f3f6: 39,
273 | e1d1: 43,
274 | e1f1: 43,
275 | e1g1: 43,
276 | e1c1: 43,
277 | },
278 | 3: {
279 | a2a3: 2186,
280 | b2b3: 1964,
281 | g2g3: 1882,
282 | d5d6: 1991,
283 | a2a4: 2149,
284 | g2g4: 1843,
285 | g2h3: 1970,
286 | d5e6: 2241,
287 | c3b1: 2038,
288 | c3d1: 2040,
289 | c3a4: 2203,
290 | c3b5: 2138,
291 | e5d3: 1803,
292 | e5c4: 1880,
293 | e5g4: 1878,
294 | e5c6: 2027,
295 | e5g6: 1997,
296 | e5d7: 2124,
297 | e5f7: 2080,
298 | d2c1: 1963,
299 | d2e3: 2136,
300 | d2f4: 2000,
301 | d2g5: 2134,
302 | d2h6: 2019,
303 | e2d1: 1733,
304 | e2f1: 2060,
305 | e2d3: 2050,
306 | e2c4: 2082,
307 | e2b5: 2057,
308 | e2a6: 1907,
309 | a1b1: 1969,
310 | a1c1: 1968,
311 | a1d1: 1885,
312 | h1f1: 1929,
313 | h1g1: 2013,
314 | f3d3: 2005,
315 | f3e3: 2174,
316 | f3g3: 2214,
317 | f3h3: 2360,
318 | f3f4: 2132,
319 | f3g4: 2169,
320 | f3f5: 2396,
321 | f3h5: 2267,
322 | f3f6: 2111,
323 | e1d1: 1894,
324 | e1f1: 1855,
325 | e1g1: 2059,
326 | e1c1: 1887,
327 | },
328 | 4: {
329 | a2a3: 94405,
330 | b2b3: 81066,
331 | g2g3: 77468,
332 | d5d6: 79551,
333 | a2a4: 90978,
334 | g2g4: 75677,
335 | g2h3: 82759,
336 | d5e6: 97464,
337 | c3b1: 84773,
338 | c3d1: 84782,
339 | c3a4: 91447,
340 | c3b5: 81498,
341 | e5d3: 77431,
342 | e5c4: 77752,
343 | e5g4: 79912,
344 | e5c6: 83885,
345 | e5g6: 83866,
346 | e5d7: 93913,
347 | e5f7: 88799,
348 | d2c1: 83037,
349 | d2e3: 90274,
350 | d2f4: 84869,
351 | d2g5: 87951,
352 | d2h6: 82323,
353 | e2d1: 74963,
354 | e2f1: 88728,
355 | e2d3: 85119,
356 | e2c4: 84835,
357 | e2b5: 79739,
358 | e2a6: 69334,
359 | a1b1: 83348,
360 | a1c1: 83263,
361 | a1d1: 79695,
362 | h1f1: 81563,
363 | h1g1: 84876,
364 | f3d3: 83727,
365 | f3e3: 92505,
366 | f3g3: 94461,
367 | f3h3: 98524,
368 | f3f4: 90488,
369 | f3g4: 92037,
370 | f3f5: 104992,
371 | f3h5: 95034,
372 | f3f6: 77838,
373 | e1d1: 79989,
374 | e1f1: 77887,
375 | e1g1: 86975,
376 | e1c1: 79803,
377 | },
378 | 5: {
379 | a2a3: 4627439,
380 | b2b3: 3768824,
381 | g2g3: 3472039,
382 | d5d6: 3835265,
383 | a2a4: 4387586,
384 | g2g4: 3338154,
385 | g2h3: 3819456,
386 | d5e6: 4727437,
387 | c3b1: 3996171,
388 | c3d1: 3995761,
389 | c3a4: 4628497,
390 | c3b5: 4317482,
391 | e5d3: 3288812,
392 | e5c4: 3494887,
393 | e5g4: 3415992,
394 | e5c6: 4083458,
395 | e5g6: 3949417,
396 | e5d7: 4404043,
397 | e5f7: 4164923,
398 | d2c1: 3793390,
399 | d2e3: 4407041,
400 | d2f4: 3941257,
401 | d2g5: 4370915,
402 | d2h6: 3967365,
403 | e2d1: 3074219,
404 | e2f1: 4095479,
405 | e2d3: 4066966,
406 | e2c4: 4182989,
407 | e2b5: 4032348,
408 | e2a6: 3553501,
409 | a1b1: 3827454,
410 | a1c1: 3814203,
411 | a1d1: 3568344,
412 | h1f1: 3685756,
413 | h1g1: 3989454,
414 | f3d3: 3949570,
415 | f3e3: 4477772,
416 | f3g3: 4669768,
417 | f3h3: 5067173,
418 | f3f4: 4327936,
419 | f3g4: 4514010,
420 | f3f5: 5271134,
421 | f3h5: 4743335,
422 | f3f6: 3975992,
423 | e1d1: 3559113,
424 | e1f1: 3377351,
425 | e1g1: 4119629,
426 | e1c1: 3551583,
427 | }
428 | }
429 | }
430 |
--------------------------------------------------------------------------------
/js/generateMove.js:
--------------------------------------------------------------------------------
1 | let moves;
2 | function generateMoves() {
3 | moves = [];
4 |
5 | if (gameBoard.side == Color.white) {
6 | for (const sq of gameBoard.pieceList[Pieces.wp]) {
7 | if (gameBoard.pieces[sq + 10] == Pieces.empty) {
8 | addWhitePawnQuietMove(sq, sq + 10);
9 | if (gameBoard.pieces[sq + 20] == Pieces.empty && rankOf(sq) == Rank2) {
10 | addQuiteMove(buildMove(sq, sq + 20, 0, 0, PawnStartFlag));
11 | }
12 | }
13 |
14 | //add capture move
15 | if (gameBoard.pieces[sq + 9] != Squares.offBoard && PieceColor[gameBoard.pieces[sq + 9]] == Color.black) {
16 | addWhiteCaptureMove(sq, sq + 9, gameBoard.pieces[sq + 9]);
17 | }
18 | if (gameBoard.pieces[sq + 11] != Squares.offBoard && PieceColor[gameBoard.pieces[sq + 11]] == Color.black) {
19 | addWhiteCaptureMove(sq, sq + 11, gameBoard.pieces[sq + 11]);
20 | }
21 |
22 | //add enpassant move
23 | if (sq + 9 == gameBoard.enPassantSq) {
24 | addEnPassantMove(buildMove(sq, sq + 9, 0, 0, EnPassantFlag));
25 | }
26 | if (sq + 11 == gameBoard.enPassantSq) {
27 | addEnPassantMove(buildMove(sq, sq + 11, 0, 0, EnPassantFlag));
28 | }
29 |
30 | }
31 |
32 |
33 | //castling
34 |
35 | if (gameBoard.castlePermission & CastleBit.K) {
36 | if (gameBoard.pieces[Squares.f1] == Pieces.empty && gameBoard.pieces[Squares.g1] == Pieces.empty) {
37 | if (gameBoard.checkSq == Squares.noSq && !isUnderAttack(Squares.f1, Color.black)) {
38 | addQuiteMove(buildMove(Squares.e1, Squares.g1, 0, 0, CastleFlag));
39 | }
40 | }
41 | }
42 | if (gameBoard.castlePermission & CastleBit.Q) {
43 | if (gameBoard.pieces[Squares.d1] == Pieces.empty && gameBoard.pieces[Squares.c1] == Pieces.empty && gameBoard.pieces[Squares.b1] == Pieces.empty) {
44 | if (gameBoard.checkSq == Squares.noSq && !isUnderAttack(Squares.d1, Color.black)) {
45 | addQuiteMove(buildMove(Squares.e1, Squares.c1, 0, 0, CastleFlag));
46 | }
47 | }
48 | }
49 |
50 | }
51 | else {
52 | for (const sq of gameBoard.pieceList[Pieces.bp]) {
53 | if (gameBoard.pieces[sq - 10] == Pieces.empty) {
54 | //add a white quite move
55 | addBlackPawnQuietMove(sq, sq - 10);
56 |
57 | if (gameBoard.pieces[sq - 20] == Pieces.empty && rankOf(sq) == Rank7) {
58 | //add pawn first move
59 | addQuiteMove(buildMove(sq, sq - 20, 0, 0, PawnStartFlag));
60 | }
61 | }
62 |
63 | //add capture move
64 | if (gameBoard.pieces[sq - 9] != Squares.offBoard && PieceColor[gameBoard.pieces[sq - 9]] == Color.white) {
65 | addBlackCaptureMove(sq, sq - 9, gameBoard.pieces[sq - 9]);
66 | }
67 | if (gameBoard.pieces[sq - 11] != Squares.offBoard && PieceColor[gameBoard.pieces[sq - 11]] == Color.white) {
68 | addBlackCaptureMove(sq, sq - 11, gameBoard.pieces[sq - 11]);
69 | }
70 |
71 | //add enpassant move
72 | if (sq - 9 == gameBoard.enPassantSq) {
73 | addEnPassantMove(buildMove(sq, sq - 9, 0, 0, EnPassantFlag));
74 | }
75 | if (sq - 11 == gameBoard.enPassantSq) {
76 | addEnPassantMove(buildMove(sq, sq - 11, 0, 0, EnPassantFlag));
77 | }
78 | }
79 |
80 |
81 | //castling
82 | if (gameBoard.castlePermission & CastleBit.k) {
83 | if (gameBoard.pieces[Squares.f8] == Pieces.empty && gameBoard.pieces[Squares.g8] == Pieces.empty) {
84 | if (gameBoard.checkSq == Squares.noSq && !isUnderAttack(Squares.f8, Color.white)) {
85 | addQuiteMove(buildMove(Squares.e8, Squares.g8, 0, 0, CastleFlag));
86 | }
87 | }
88 | }
89 | if (gameBoard.castlePermission & CastleBit.q) {
90 | if (gameBoard.pieces[Squares.d8] == Pieces.empty && gameBoard.pieces[Squares.c8] == Pieces.empty && gameBoard.pieces[Squares.b8] == Pieces.empty) {
91 | if (gameBoard.checkSq == Squares.noSq && !isUnderAttack(Squares.d8, Color.white)) {
92 | addQuiteMove(buildMove(Squares.e8, Squares.c8, 0, 0, CastleFlag));
93 | }
94 | }
95 | }
96 |
97 | }
98 |
99 | generateSlidingMoves();
100 | generateNonSlidingMoves();
101 |
102 | return moves;
103 | }
104 |
105 | function generateSlidingMoves({ captureOnly = false } = {}) {
106 | for (const piece of SlidingPieces[gameBoard.side]) {
107 | for (const sq of gameBoard.pieceList[piece]) {
108 | for (const direction of PieceDirections[piece]) {
109 | let targetSq = sq + direction;
110 |
111 | while (gameBoard.pieces[targetSq] != Squares.offBoard) {
112 | if (gameBoard.pieces[targetSq] != Pieces.empty) {
113 | if (PieceColor[gameBoard.pieces[targetSq]] != gameBoard.side) {
114 | addCaptureMove(buildMove(sq, targetSq, gameBoard.pieces[targetSq], 0, 0));
115 | }
116 | break;
117 | }
118 | else if (!captureOnly) {
119 | addQuiteMove(buildMove(sq, targetSq, 0, 0, 0));
120 | }
121 | targetSq += direction;
122 | }
123 |
124 | }
125 | }
126 | }
127 | }
128 |
129 | function generateNonSlidingMoves({ captureOnly = false } = {}) {
130 | for (const piece of NonSlidingPieces[gameBoard.side]) {
131 | for (const sq of gameBoard.pieceList[piece]) {
132 | for (const direction of PieceDirections[piece]) {
133 |
134 | let targetSq = sq + direction;
135 | if (gameBoard.pieces[targetSq] == Squares.offBoard) {
136 | continue;
137 | }
138 | if (gameBoard.pieces[targetSq] == Pieces.empty) {
139 | if (!captureOnly) {
140 | addQuiteMove(buildMove(sq, targetSq, 0, 0, 0));
141 | }
142 | }
143 | else if (PieceColor[gameBoard.pieces[targetSq]] != gameBoard.side) {
144 | addCaptureMove(buildMove(sq, targetSq, gameBoard.pieces[targetSq], 0, 0));
145 | }
146 | }
147 | }
148 | }
149 | }
150 |
151 |
152 | function generateCaptureMoves() {
153 | moves = [];
154 |
155 | if (gameBoard.side == Color.white) {
156 | for (const sq of gameBoard.pieceList[Pieces.wp]) {
157 |
158 | //add capture move
159 | if (sq + 9 != Squares.offBoard && PieceColor[gameBoard.pieces[sq + 9]] == Color.black) {
160 | addWhiteCaptureMove(sq, sq + 9, gameBoard.pieces[sq + 9]);
161 | }
162 | if (sq + 11 != Squares.offBoard && PieceColor[gameBoard.pieces[sq + 11]] == Color.black) {
163 | addWhiteCaptureMove(sq, sq + 11, gameBoard.pieces[sq + 11]);
164 | }
165 |
166 | //add enpassant move
167 | if (sq + 9 == gameBoard.enPassantSq) {
168 | addEnPassantMove(buildMove(sq, sq + 9, 0, 0, EnPassantFlag));
169 | }
170 | if (sq + 11 == gameBoard.enPassantSq) {
171 | addEnPassantMove(buildMove(sq, sq + 11, 0, 0, EnPassantFlag));
172 | }
173 |
174 | }
175 |
176 | }
177 | else {
178 | for (const sq of gameBoard.pieceList[Pieces.bp]) {
179 |
180 | //add capture move
181 | if (sq - 9 != Squares.offBoard && PieceColor[gameBoard.pieces[sq - 9]] == Color.white) {
182 | addBlackCaptureMove(sq, sq - 9, gameBoard.pieces[sq - 9]);
183 | }
184 | if (sq - 11 != Squares.offBoard && PieceColor[gameBoard.pieces[sq - 11]] == Color.white) {
185 | addBlackCaptureMove(sq, sq - 11, gameBoard.pieces[sq - 11]);
186 | }
187 |
188 | //add enpassant move
189 | if (sq - 9 == gameBoard.enPassantSq) {
190 | addEnPassantMove(buildMove(sq, sq - 9, 0, 0, EnPassantFlag));
191 | }
192 | if (sq - 11 == gameBoard.enPassantSq) {
193 | addEnPassantMove(buildMove(sq, sq - 11, 0, 0, EnPassantFlag));
194 | }
195 | }
196 | }
197 |
198 | generateSlidingMoves({ captureOnly: true });
199 | generateNonSlidingMoves({ captureOnly: true });
200 |
201 | return moves;
202 | }
203 |
204 | //MvvLva = [victim][attacker]
205 | let MvvLva = [
206 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
207 | [0, 105, 102, 104, 103, 101, 100, 105, 102, 104, 103, 101, 100],
208 | [0, 405, 402, 404, 403, 401, 400, 405, 402, 404, 403, 401, 400],
209 | [0, 205, 202, 204, 203, 201, 200, 205, 202, 204, 203, 201, 200],
210 | [0, 305, 302, 304, 303, 301, 300, 305, 302, 304, 303, 301, 300],
211 | [0, 505, 502, 504, 503, 501, 500, 505, 502, 504, 503, 501, 500],
212 | [0, 605, 602, 604, 603, 601, 600, 605, 602, 604, 603, 601, 600],
213 | [0, 105, 102, 104, 103, 101, 100, 105, 102, 104, 103, 101, 100],
214 | [0, 405, 402, 404, 403, 401, 400, 405, 402, 404, 403, 401, 400],
215 | [0, 205, 202, 204, 203, 201, 200, 205, 202, 204, 203, 201, 200],
216 | [0, 305, 302, 304, 303, 301, 300, 305, 302, 304, 303, 301, 300],
217 | [0, 505, 502, 504, 503, 501, 500, 505, 502, 504, 503, 501, 500],
218 | [0, 605, 602, 604, 603, 601, 600, 605, 602, 604, 603, 601, 600],
219 | ];
220 |
221 |
222 | function addCaptureMove(move) {
223 | let victim = moveCapturePiece(move);
224 | let attacker = gameBoard.pieces[moveFrom(move)];
225 | let score = MvvLva[victim][attacker] + 1000000;
226 | moves.push({ score, move });
227 | }
228 |
229 | function addQuiteMove(move) {
230 | let score = 0;
231 | if (searchController.killers[searchController.ply][0] == move) {
232 | score = 900000;
233 | }
234 | else if (searchController.killers[searchController.ply][1] == move) {
235 | score = 800000;
236 | }
237 | else {
238 | let piece = gameBoard.pieces[moveFrom(move)];
239 | let toSq = moveTo(move);
240 | score = searchController.history[piece][toSq];
241 | }
242 |
243 | moves.push({ score, move });
244 | }
245 |
246 | function addEnPassantMove(move) {
247 | //MvvLva[Pieces.wp][Pieces.bp] == MvvLva[Pieces.bp][Pieces.wp]
248 | let score = MvvLva[Pieces.wp][Pieces.bp] + 1000000;
249 | moves.push({ score, move });
250 | }
251 |
252 |
253 | function addWhitePawnQuietMove(from, to) {
254 | //handling promotion move
255 | if (rankOf(to) === Rank8) {
256 | addQuiteMove(buildMove(from, to, 0, Pieces.wq, 0));
257 | addQuiteMove(buildMove(from, to, 0, Pieces.wr, 0));
258 | addQuiteMove(buildMove(from, to, 0, Pieces.wb, 0));
259 | addQuiteMove(buildMove(from, to, 0, Pieces.wn, 0));
260 | }
261 | else {
262 | addQuiteMove(buildMove(from, to, 0, 0, 0));
263 | }
264 | }
265 | function addBlackPawnQuietMove(from, to) {
266 | //handling promotion move
267 | if (rankOf(to) === Rank1) {
268 | addQuiteMove(buildMove(from, to, 0, Pieces.bq, 0));
269 | addQuiteMove(buildMove(from, to, 0, Pieces.br, 0));
270 | addQuiteMove(buildMove(from, to, 0, Pieces.bb, 0));
271 | addQuiteMove(buildMove(from, to, 0, Pieces.bn, 0));
272 | }
273 | else {
274 | addQuiteMove(buildMove(from, to, 0, 0, 0));
275 | }
276 | }
277 |
278 |
279 |
280 | function addWhiteCaptureMove(from, to, capture) {
281 | //handling promotion move
282 | if (rankOf(to) === Rank8) {
283 | addCaptureMove(buildMove(from, to, capture, Pieces.wq, 0));
284 | addCaptureMove(buildMove(from, to, capture, Pieces.wr, 0));
285 | addCaptureMove(buildMove(from, to, capture, Pieces.wb, 0));
286 | addCaptureMove(buildMove(from, to, capture, Pieces.wn, 0));
287 | }
288 | else {
289 | addCaptureMove(buildMove(from, to, capture, 0, 0));
290 | }
291 | }
292 | function addBlackCaptureMove(from, to, capture) {
293 | //handling promotion move
294 | if (rankOf(to) === Rank1) {
295 | addCaptureMove(buildMove(from, to, capture, Pieces.bq, 0));
296 | addCaptureMove(buildMove(from, to, capture, Pieces.br, 0));
297 | addCaptureMove(buildMove(from, to, capture, Pieces.bb, 0));
298 | addCaptureMove(buildMove(from, to, capture, Pieces.bn, 0));
299 | }
300 | else {
301 | addCaptureMove(buildMove(from, to, capture, 0, 0));
302 | }
303 | }
--------------------------------------------------------------------------------
/js/gui/controlls.js:
--------------------------------------------------------------------------------
1 | const backdrop = document.querySelector('.backdrop');
2 | const downloadWindow = backdrop.querySelector('.download-window');
3 | const settingWindow = backdrop.querySelector('.setting-window');
4 | const confirmWindow = backdrop.querySelector('.confirm-popover');
5 |
6 | const downloadOptions = downloadWindow.querySelectorAll('.option>*');
7 | const downloadBtn = downloadWindow.querySelector('.btn.download');
8 |
9 | const confirmBtn = document.getElementById('confirm');
10 | const setupBtn = document.getElementById('setup-btn');
11 | const gameOver = document.querySelector('.game-over');
12 | const closeResult = gameOver.querySelector('.close');
13 |
14 | const pgnOutput = downloadWindow.querySelector('.pgn textarea');
15 | const fenOutput = downloadWindow.querySelector('.fen input');
16 |
17 | const uploadPgnBtn = document.getElementById('upload-pgn-btn');
18 | const uploadPgnInput = document.getElementById('upload-pgn-input');
19 | const uploadPgnContainer = document.querySelector('.upload');
20 | const uploadFenInput = document.getElementById('upload-fen-input');
21 | const uploadProgressBar = document.getElementById('upload-bar');
22 | const winAnimation = document.querySelector('dotlottie-player.win-animation');
23 | const uploadBookWindow = document.querySelector('.upload-book');
24 |
25 | const themes = document.querySelectorAll('.theme');
26 |
27 | const savedTheme = localStorage.getItem('selectedTheme');
28 | if (savedTheme) {
29 | board.className = savedTheme;
30 | themes.forEach(e => {
31 | e.classList.toggle('active', e.textContent === savedTheme);
32 | });
33 | }
34 |
35 | themes.forEach(theme => {
36 | theme.addEventListener('click', () => {
37 | themes.forEach(e => e.classList.remove('active'));
38 | theme.classList.add('active');
39 | const selectedTheme = theme.textContent;
40 | board.className = selectedTheme;
41 | localStorage.setItem('selectedTheme', selectedTheme);
42 | });
43 | });
44 |
45 | document.getElementById('clear-board').addEventListener('click', () => {
46 | parseFen('8/8/8/8/8/8/8/8 w KQkq -');
47 | gui.renderPieces();
48 | })
49 | document.getElementById('reset-board').addEventListener('click', () => {
50 | parseFen(StartingFen);
51 | gui.renderPieces();
52 | })
53 |
54 | document.addEventListener('mousedown', (e) => {
55 | if (!gameOver.contains(e.target)) {
56 | gameOver.classList.remove('active');
57 | }
58 | });
59 | gameOver.querySelectorAll('.btn').forEach(btn => {
60 | btn.addEventListener('click', () => {
61 | gameOver.classList.remove('active');
62 | })
63 | })
64 | closeResult.addEventListener('click', () => {
65 | gameOver.classList.remove('active');
66 | })
67 |
68 | const popup = document.querySelector('.popup');
69 | const popupMessage = popup.querySelector('.message');
70 | function showPopup(message, success = true) {
71 | popupMessage.textContent = message;
72 | popup.classList.remove('success', 'danger');
73 | popup.classList.add('active', success ? 'success' : 'danger')
74 | setTimeout(()=>{
75 | popup.classList.remove('active');
76 | },2000);
77 | }
78 |
79 | function removeAllWindows(){
80 | document.querySelectorAll('.window').forEach(window=>{
81 | window.classList.remove('active');
82 | })
83 | }
84 | function showSettingWindow() {
85 | removeAllWindows();
86 | backdrop.classList.add('active');
87 | settingWindow.classList.add('active');
88 | }
89 | function showConfirmWindow() {
90 | uploadPgnInput.value = '';
91 | confirmBtn.textContent = 'new game';
92 |
93 | removeAllWindows();
94 | backdrop.classList.add('active');
95 | confirmWindow.classList.add('active');
96 | uploadProgressBar.style.width = `0%`;
97 | }
98 |
99 |
100 | function showDownloadWindow() {
101 | pgnOutput.value = getPGN();
102 | fenOutput.value = getFen();
103 |
104 | removeAllWindows();
105 | backdrop.classList.add('active');
106 | downloadWindow.classList.add('active');
107 | }
108 |
109 | function showUploadBookWindow() {
110 | removeAllWindows();
111 | backdrop.classList.add('active');
112 | uploadBookWindow.classList.add('active');
113 | }
114 |
115 | backdrop.addEventListener('click', (e) => {
116 | if (e.target.contains(backdrop)) {
117 | removeBackdrop();
118 | }
119 | })
120 |
121 | function removeBackdrop() {
122 | backdrop.classList.remove('active');
123 | }
124 |
125 | setupBtn.addEventListener('click', () => {
126 | setup = true;
127 | setupPosition.classList.add('active');
128 | removeBackdrop();
129 | })
130 |
131 | confirmBtn.addEventListener('click', () => {
132 | if (uploadPgnInput.value) {
133 | try {
134 | parsePGN(uploadPgnInput.value);
135 | } catch (error) {
136 | console.error(error);
137 | alert(error);
138 | }
139 | }
140 | else {
141 | newGame(uploadFenInput.value);
142 | }
143 | removeBackdrop();
144 | })
145 |
146 | downloadOptions.forEach(option => {
147 | option.addEventListener('click', () => {
148 | downloadOptions.forEach(e => e.classList.remove('active'));
149 | option.classList.add('active');
150 | downloadFormate = option.textContent;
151 | })
152 | })
153 |
154 | let downloadFormate = 'PGN';
155 | downloadBtn.addEventListener('click', () => {
156 | if (downloadFormate == 'PGN') {
157 | downloadPgn();
158 | }
159 | else if (downloadFormate == 'Image') {
160 | downloadImage();
161 | }
162 | removeBackdrop();
163 | })
164 |
165 | function downloadPgn() {
166 | let pgn = pgnOutput.value;
167 | if (pgn === '') return;
168 |
169 | const a = document.createElement('a');
170 | const blob = new Blob([pgn], { type: 'application/x-chess-pgn' });
171 |
172 | a.href = URL.createObjectURL(blob);
173 | a.download = 'game.pgn';
174 |
175 | a.click();
176 | a.remove();
177 | }
178 |
179 | function downloadImage() {
180 | html2canvas(graphicalBoard)
181 | .then(canvas => {
182 | const a = document.createElement('a');
183 | a.href = canvas.toDataURL('image/jpeg');
184 | a.download = 'game.jpg';
185 |
186 | a.click();
187 | a.remove();
188 | });
189 | }
190 |
191 | const coordinates = document.querySelectorAll('.coordinates *');
192 | function flipBoard() {
193 | boardLayout.classList.toggle('flipped');
194 | flipCoordinates();
195 | }
196 | function flipCoordinates() {
197 | let texts = boardLayout.classList.contains('flipped') ? '12345678hgfedcba' : '87654321abcdefgh'
198 | for (let i = 0; i < coordinates.length; i++) {
199 | coordinates[i].textContent = texts[i];
200 | }
201 | }
202 |
203 |
204 |
205 |
206 |
207 | uploadPgnContainer.addEventListener('dragover', (e) => {
208 | uploadPgnContainer.classList.add('file-hover');
209 | e.preventDefault();
210 | });
211 |
212 | uploadPgnContainer.addEventListener('dragleave', () => {
213 | uploadPgnContainer.classList.remove('file-hover');
214 | });
215 | uploadPgnContainer.addEventListener('drop', (e) => {
216 | e.preventDefault();
217 | uploadPgnContainer.classList.remove('file-hover');
218 | const file = e.dataTransfer.files[0];
219 | resetProgressWidth();
220 | readPgnFile(file);
221 | });
222 |
223 | const fileInput = document.getElementById('file-input');
224 |
225 | uploadPgnBtn.addEventListener('click', () => {
226 | fileInput.click();
227 | })
228 |
229 | uploadPgnInput.addEventListener('input', () => {
230 | uploadFenInput.value = '';
231 | confirmBtn.textContent = (uploadPgnInput.value) ? 'load game' : 'new game';
232 | });
233 | uploadFenInput.addEventListener('input', () => {
234 | uploadPgnInput.value = '';
235 | confirmBtn.textContent = (uploadFenInput.value) ? 'load game' : 'new game';
236 | });
237 |
238 | fileInput.addEventListener('change', (e) => {
239 | const file = e.target.files[0];
240 | resetProgressWidth();
241 | readPgnFile(file);
242 | });
243 |
244 | function readPgnFile(file) {
245 | if (!file) return;
246 |
247 | confirmBtn.disabled = true;
248 |
249 | if (!file.name.endsWith('.pgn')) {
250 | alert('Please upload a valid PGN file.');
251 | return;
252 | }
253 |
254 | const reader = new FileReader();
255 | reader.addEventListener('load', (e) => {
256 | const pgn = e.target.result;
257 | uploadPgnInput.value = pgn;
258 | uploadFenInput.value = '';
259 |
260 | confirmBtn.textContent = (uploadPgnInput.value) ? 'load game' : 'new game';
261 | confirmBtn.disabled = false;
262 | });
263 |
264 | reader.addEventListener('progress', (e) => {
265 | if (e.lengthComputable) {
266 | const progress = (e.loaded / e.total) * 100;
267 | uploadProgressBar.style.width = `${progress}%`;
268 | }
269 | });
270 |
271 | reader.addEventListener('error', (e) => {
272 | console.error('File reading error:', e.target.error);
273 | alert(e.target.error);
274 | });
275 |
276 | reader.readAsText(file);
277 | }
278 |
279 | function resetProgressWidth() {
280 | uploadProgressBar.style.transition = 'none';
281 | uploadProgressBar.style.width = 0;
282 | void uploadProgressBar.offsetWidth;
283 | uploadProgressBar.style.transition = 'width .3s';
284 | }
285 |
286 | // ======================================================
287 | // ========== drop down search time controller ==========
288 | // ======================================================
289 | const selections = document.querySelectorAll('.selection');
290 | const options = document.querySelectorAll('.drop-menu .options');
291 |
292 | function removeSelectFrom(option) {
293 | [...option.children].forEach(child => {
294 | child.classList.remove('select');
295 | });
296 | }
297 |
298 | options.forEach(option => {
299 | [...option.children].forEach(child => {
300 | child.addEventListener('click', () => {
301 | removeSelectFrom(option);
302 | child.classList.add('select');
303 | if (option.id == 'white') {
304 | selections[1].textContent = child.textContent + 's';
305 | engine.searchTime[Color.white] = +child.textContent;
306 | }
307 | else {
308 | selections[0].textContent = child.textContent + 's';
309 | engine.searchTime[Color.black] = +child.textContent;
310 | }
311 | })
312 | })
313 | })
314 | selections.forEach(selection => {
315 | selection.addEventListener('click', () => {
316 | removeDropDown();
317 | selection.parentElement.classList.add('active');
318 | })
319 | })
320 |
321 | function removeDropDown() {
322 | selections.forEach(selection => {
323 | selection.parentElement.classList.remove('active');
324 | })
325 | }
326 | document.addEventListener('click', (e) => {
327 | if (!e.target.classList.contains('selection')) {
328 | removeDropDown();
329 | }
330 | })
331 |
332 | window.addEventListener('keydown', (e) => {
333 | if (e.keyCode === 37) {
334 | gui.undoMove();
335 | }
336 | else if (e.keyCode === 39) {
337 | moveForward();
338 | }
339 | })
340 |
341 | const uploadBookBtn = document.getElementById('upload-book-btn');
342 | const bookInput = document.getElementById('book-input');
343 | const bookUploadProgress = document.querySelector('.book .progress');
344 | const bookUploadPercent = document.querySelector('.book .percent');
345 | const library = document.querySelector('.library');
346 | const bookLoader = document.querySelector('.book');
347 | const uploadBookName = bookLoader.querySelector('.file-name');
348 | const defaultBook = library.querySelector('.book');
349 |
350 | defaultBook.addEventListener('click', selectBook(defaultBook));
351 | function selectBook(book) {
352 | return () => {
353 | if(book.classList.contains("selected")) return;
354 | document.querySelectorAll('.book').forEach(b => {
355 | b.classList.remove('selected');
356 | })
357 | book.classList.add('selected');
358 | if (book == defaultBook) {
359 | readPolyBook({ path: '../gm2600.bin' });
360 | }
361 | else {
362 | const fileName = book.querySelector('.file-name').textContent;
363 | readBookFromIndexedDB(fileName);
364 | }
365 | }
366 | }
367 | uploadBookBtn.addEventListener('click', () => {
368 | bookInput.click();
369 | })
370 |
371 | bookInput.addEventListener('change', uploadBook);
372 |
373 | function uploadBook() {
374 | const file = bookInput.files[0];
375 |
376 | if (!file) {
377 | alert('Please select a file!');
378 | return;
379 | }
380 | bookLoader.style.display = 'block';
381 | uploadBookName.textContent = file.name;
382 | const reader = new FileReader();
383 |
384 | reader.onprogress = function (event) {
385 | if (event.lengthComputable) {
386 | const percentLoaded = Math.round((event.loaded / event.total) * 100);
387 | console.log(percentLoaded);
388 | bookUploadProgress.style.width = percentLoaded + '%';
389 | bookUploadPercent.textContent = percentLoaded + '%';
390 | }
391 | };
392 | reader.onload = function (event) {
393 | const buffer = event.target.result; // This is the ArrayBuffer
394 |
395 | // Calculate file size
396 | const fileSize = file.size;
397 | let sizeText;
398 | if (fileSize >= 1024 * 1024) {
399 | sizeText = (fileSize / (1024 * 1024)).toFixed(2) + ' MB';
400 | } else if (fileSize >= 1024) {
401 | sizeText = (fileSize / 1024).toFixed(2) + ' KB';
402 | } else {
403 | sizeText = fileSize + ' B';
404 | }
405 | addBookToIndexedDB(buffer, file.name, sizeText);
406 | addBook(file.name, sizeText);
407 | bookLoader.style.display = 'none';
408 | }
409 | reader.readAsArrayBuffer(file);
410 | }
411 |
412 |
413 | function addBook(name, size, selected = false) {
414 | const book = document.createElement('div');
415 | const img = document.createElement('img');
416 | const detail = document.createElement('div');
417 | const fileName = document.createElement('div');
418 | const fileSize = document.createElement('div');
419 | const del = document.createElement('img');
420 | if (selected) {
421 | book.className.add('selected');
422 | }
423 | img.classList.add('icon');
424 | book.classList.add('book');
425 | detail.classList.add('detail');
426 | fileName.classList.add('file-name');
427 | fileSize.classList.add('file-size');
428 | del.classList.add('icon', 'delete');
429 | del.addEventListener('click', () => {
430 | removeBookFromIndexedDB(fileName.textContent);
431 | if (book.classList.contains('selected')) {
432 | setTimeout(() => {
433 | defaultBook.click();
434 | }, 0);
435 | }
436 | book.remove();
437 | })
438 | img.src = "./assets/icons/book.svg"
439 | del.src = "./assets/icons/delete.webp"
440 |
441 | fileSize.textContent = size;
442 | fileName.textContent = name;
443 |
444 |
445 | detail.appendChild(fileName);
446 | detail.appendChild(fileSize);
447 |
448 | book.appendChild(img);
449 | book.appendChild(detail);
450 | book.appendChild(del);
451 | book.addEventListener('click', selectBook(book));
452 | library.appendChild(book);
453 | }
454 |
455 |
--------------------------------------------------------------------------------
/css/util.css:
--------------------------------------------------------------------------------
1 | .btn {
2 | white-space: nowrap;
3 | border: none;
4 | outline: none;
5 | padding: 5px 20px;
6 | height: 40px;
7 | border-radius: 4px;
8 | color: #c5c5c4;
9 | cursor: pointer;
10 | text-transform: capitalize;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | border-bottom: 3px solid transparent;
15 | user-select: none;
16 | }
17 | body{
18 | /* background-color: white !important; */
19 | }
20 | .popup{
21 | position: fixed;
22 | bottom: 100px;
23 | padding: 6px 8px;
24 | border-radius: 5px;
25 | background-color: #e8e7e6;
26 | display: flex;
27 | gap: 10px;
28 | align-items: center;
29 | box-shadow: 2px 2px 5px 1px rgba(0, 0, 0, 0.3);
30 | z-index: 1000;
31 | transform: scale(0);
32 | opacity: 0;
33 | visibility: hidden;
34 | transition: .2s 0s cubic-bezier(0.27, 0.63, 0.51, 1.58);
35 | }
36 | .popup.active{
37 | transform: scale(1);
38 | opacity: 1;
39 | visibility: visible;
40 | }
41 | .popup .icon{
42 | background: url('../assets/icons/tick.webp') no-repeat;
43 | background-size: contain;
44 | width: 18px;
45 | }
46 |
47 | .popup.danger .icon{
48 | background: url('../assets/icons/cross.webp') no-repeat;
49 | background-size: contain;
50 | }
51 |
52 | .toggle-btn {
53 | background-color: #75757566;
54 | color: #ffffffb8;
55 | border: 1px solid #ffffff33;
56 | width: 30px;
57 | height: 16px;
58 | border-radius: 50px;
59 | position: relative;
60 | cursor: pointer;
61 | transition: all .3s ease; /* Unified transition */
62 | }
63 |
64 | .toggle-btn::before {
65 | content: '';
66 | position: absolute;
67 | background-color: #e8e7e6;
68 | border: 1px solid #dad7d5;
69 | border-radius: 50%;
70 | height: 120%;
71 | aspect-ratio: 1;
72 | top: 50%;
73 | transform: translateY(-50%);
74 | left: -1px;
75 | transition: all .3s ease;
76 | }
77 |
78 | .toggle-btn.active {
79 | background-color: #5d9948;
80 | }
81 |
82 | .toggle-btn.active::before {
83 | left: 100%;
84 | transform: translate(-90%, -50%);
85 | }
86 |
87 | .btn i::before {
88 | font-size: 22px;
89 | font-weight: 800;
90 | }
91 |
92 | .white.crown-alt {
93 | aspect-ratio: 1;
94 | width: 16px;
95 | background: url('../assets/icons/white-crown-alt.webp') no-repeat;
96 | background-size: 100%;
97 | }
98 |
99 | .black.crown-alt {
100 | aspect-ratio: 1;
101 | width: 16px;
102 | background: url('../assets/icons/black-crown-alt.webp') no-repeat;
103 | background-size: 100%;
104 | }
105 |
106 | .btn.primary {
107 | background-color: #81b64c;
108 | border-color: #45753c;
109 | color: white;
110 | }
111 |
112 | .btn.primary:hover {
113 | filter: brightness(120%);
114 | box-shadow: 0px 0px 16px 0px #aef46721;
115 | }
116 |
117 | .btn.bold {
118 | font-weight: 800;
119 | font-size: 16px;
120 | border-radius: 10px;
121 | border-width: 4px;
122 | height: 60px;
123 | grid-column: 1/-1;
124 | text-shadow: 0 0.1rem 0 rgba(0, 0, 0, .4);
125 | height: 48px;
126 | }
127 |
128 | .btn.secondary {
129 | background-color: #32312f;
130 | border-color: #2c2b28;
131 | }
132 |
133 | .highlight,
134 | .capture-hint,
135 | .hint,
136 | .check {
137 | width: 12.5%;
138 | height: 12.5%;
139 | position: absolute;
140 | z-index: 2;
141 | }
142 |
143 | .highlight {
144 | background-color: var(--highlight);
145 | opacity: 0.5;
146 | }
147 |
148 | .capture-hint {
149 | border: 6px solid var(--alpha-dark);
150 | border-radius: 50%;
151 | }
152 |
153 | .hint {
154 | padding: 4.2%;
155 | border-radius: 50%;
156 | background-color: var(--alpha-dark);
157 | background-clip: content-box;
158 | }
159 |
160 | .check {
161 | background-color: rgba(255, 0, 0, 0.5);
162 | }
163 |
164 | .btn.secondary:hover {
165 | color: white;
166 | background-color: #454441;
167 | }
168 |
169 |
170 |
171 | .capture.bp-1 {
172 | aspect-ratio: .8/1;
173 | background-image: url('../assets/capture piece/capture-bp-1.webp');
174 | }
175 |
176 | .capture.bp-2 {
177 | background-image: url('../assets/capture piece/capture-bp-2.webp');
178 | aspect-ratio: 1.3/1;
179 | }
180 |
181 | .capture.bp-3 {
182 | background-image: url('../assets/capture piece/capture-bp-3.webp');
183 | aspect-ratio: 1.7/1;
184 | }
185 |
186 | .capture.bp-4 {
187 | background-image: url('../assets/capture piece/capture-bp-4.webp');
188 | aspect-ratio: 2.1/1;
189 | }
190 |
191 | .capture.bp-5 {
192 | background-image: url('../assets/capture piece/capture-bp-5.webp');
193 | aspect-ratio: 2.5/1;
194 | }
195 |
196 | .capture.bp-6 {
197 | background-image: url('../assets/capture piece/capture-bp-6.webp');
198 | aspect-ratio: 3/1;
199 | }
200 |
201 | .capture.bp-7 {
202 | background-image: url('../assets/capture piece/capture-bp-7.webp');
203 | aspect-ratio: 3.4/1;
204 | }
205 |
206 | .capture.bp-8 {
207 | background-image: url('../assets/capture piece/capture-bp-8.webp');
208 | aspect-ratio: 4/1;
209 | }
210 |
211 | .capture.wp-1 {
212 | aspect-ratio: .8/1;
213 | background-image: url('../assets/capture piece/capture-wp-1.webp');
214 | }
215 |
216 | .capture.wp-2 {
217 | background-image: url('../assets/capture piece/capture-wp-2.webp');
218 | aspect-ratio: 1.3/1;
219 | }
220 |
221 | .capture.wp-3 {
222 | background-image: url('../assets/capture piece/capture-wp-3.webp');
223 | aspect-ratio: 1.8/1;
224 | }
225 |
226 | .capture.wp-4 {
227 | background-image: url('../assets/capture piece/capture-wp-4.webp');
228 | aspect-ratio: 2.3/1;
229 | }
230 |
231 | .capture.wp-5 {
232 | background-image: url('../assets/capture piece/capture-wp-5.webp');
233 | aspect-ratio: 2.6/1;
234 | }
235 |
236 | .capture.wp-6 {
237 | background-image: url('../assets/capture piece/capture-wp-6.webp');
238 | aspect-ratio: 3.1/1;
239 | }
240 |
241 | .capture.wp-7 {
242 | background-image: url('../assets/capture piece/capture-wp-7.webp');
243 | aspect-ratio: 3.5/1;
244 | }
245 |
246 | .capture.wp-8 {
247 | background-image: url('../assets/capture piece/capture-wp-8.webp');
248 | aspect-ratio: 4/1;
249 | }
250 |
251 | .capture.wb-1 {
252 | background-image: url('../assets/capture piece/capture-wb-1.webp');
253 | aspect-ratio: .8/1;
254 | }
255 |
256 | .capture.wb-2 {
257 | background-image: url('../assets/capture piece/capture-wb-2.webp');
258 | aspect-ratio: 1.2/1;
259 | }
260 |
261 | .capture.wn-1 {
262 | background-image: url('../assets/capture piece/capture-wn-1.webp');
263 | aspect-ratio: .8/1;
264 | }
265 |
266 | .capture.wn-2 {
267 | background-image: url('../assets/capture piece/capture-wn-2.webp');
268 | aspect-ratio: 1.2/1;
269 | }
270 |
271 | .capture.wr-1 {
272 | background-image: url('../assets/capture piece/capture-wr-1.webp');
273 | aspect-ratio: .8/1;
274 | }
275 |
276 | .capture.wr-2 {
277 | background-image: url('../assets/capture piece/capture-wr-2.webp');
278 | aspect-ratio: 1.3/1;
279 | }
280 |
281 | .capture.wq-1 {
282 | background-image: url('../assets/capture piece/capture-wq-1.webp');
283 | aspect-ratio: 1/1;
284 | }
285 |
286 |
287 | .capture.bb-1 {
288 | background-image: url('../assets/capture piece/capture-bb-1.webp');
289 | aspect-ratio: .8/1;
290 | }
291 |
292 | .capture.bb-2 {
293 | background-image: url('../assets/capture piece/capture-bb-2.webp');
294 | aspect-ratio: 1.2/1;
295 | }
296 |
297 | .capture.bn-1 {
298 | background-image: url('../assets/capture piece/capture-bn-1.webp');
299 | aspect-ratio: .8/1;
300 | }
301 |
302 | .capture.bn-2 {
303 | background-image: url('../assets/capture piece/capture-bn-2.webp');
304 | aspect-ratio: 1.2/1;
305 | }
306 |
307 | .capture.br-1 {
308 | background-image: url('../assets/capture piece/capture-br-1.webp');
309 | aspect-ratio: .8/1;
310 | }
311 |
312 | .capture.br-2 {
313 | background-image: url('../assets/capture piece/capture-br-2.webp');
314 | aspect-ratio: 1.3/1;
315 | }
316 |
317 | .capture.bq-1 {
318 | background-image: url('../assets/capture piece/capture-bq-1.webp');
319 | aspect-ratio: 1/1;
320 | }
321 |
322 |
323 |
324 |
325 |
326 | .piece.wp {
327 | background-image: url(../assets/theme/classic/pieces/wp.webp);
328 | }
329 |
330 | .piece.wr {
331 | background-image: url(../assets/theme/classic/pieces/wr.webp);
332 | }
333 |
334 | .piece.wn {
335 | background-image: url(../assets/theme/classic/pieces/wn.webp);
336 | }
337 |
338 | .piece.wb {
339 | background-image: url(../assets/theme/classic/pieces/wb.webp);
340 | }
341 |
342 | .piece.wq {
343 | background-image: url(../assets/theme/classic/pieces/wq.webp);
344 | }
345 |
346 | .piece.wk {
347 | background-image: url(../assets/theme/classic/pieces/wk.webp);
348 | }
349 |
350 | .piece.bp {
351 | background-image: url(../assets/theme/classic/pieces/bp.webp);
352 | }
353 |
354 | .piece.br {
355 | background-image: url(../assets/theme/classic/pieces/br.webp);
356 | }
357 |
358 | .piece.bn {
359 | background-image: url(../assets/theme/classic/pieces/bn.webp);
360 | }
361 |
362 | .piece.bb {
363 | background-image: url(../assets/theme/classic/pieces/bb.webp);
364 | }
365 |
366 | .piece.bq {
367 | background-image: url(../assets/theme/classic/pieces/bq.webp);
368 | }
369 |
370 | .piece.bk {
371 | background-image: url(../assets/theme/classic/pieces/bk.webp);
372 | }
373 |
374 |
375 | .promotion-window.file-a {
376 | transform: translateX(0%);
377 | }
378 |
379 | .promotion-window.file-b {
380 | transform: translateX(100%);
381 | }
382 |
383 | .promotion-window.file-c {
384 | transform: translateX(200%);
385 | }
386 |
387 | .promotion-window.file-d {
388 | transform: translateX(300%);
389 | }
390 |
391 | .promotion-window.file-e {
392 | transform: translateX(400%);
393 | }
394 |
395 | .promotion-window.file-f {
396 | transform: translateX(500%);
397 | }
398 |
399 | .promotion-window.file-g {
400 | transform: translateX(600%);
401 | }
402 |
403 | .promotion-window.file-h {
404 | transform: translateX(700%);
405 | }
406 |
407 | .flipped .promotion-window.file-a {
408 | transform: translateX(700%);
409 | }
410 |
411 | .flipped .promotion-window.file-b {
412 | transform: translateX(600%);
413 | }
414 |
415 | .flipped .promotion-window.file-c {
416 | transform: translateX(500%);
417 | }
418 |
419 | .flipped .promotion-window.file-d {
420 | transform: translateX(400%);
421 | }
422 |
423 | .flipped .promotion-window.file-e {
424 | transform: translateX(34px);
425 | }
426 |
427 | .flipped .promotion-window.file-f {
428 | transform: translateX(200%);
429 | }
430 |
431 | .flipped .promotion-window.file-g {
432 | transform: translateX(100%);
433 | }
434 |
435 | .flipped .promotion-window.file-h {
436 | transform: translateX(0%);
437 | }
438 |
439 | .flipped #a1 {
440 | transform: translate(700%, 0%);
441 | }
442 |
443 | .flipped #b1 {
444 | transform: translate(600%, 0%);
445 | }
446 |
447 | .flipped #c1 {
448 | transform: translate(500%, 0%);
449 | }
450 |
451 | .flipped #d1 {
452 | transform: translate(400%, 0%);
453 | }
454 |
455 | .flipped #e1 {
456 | transform: translate(300%, 0%);
457 | }
458 |
459 | .flipped #f1 {
460 | transform: translate(200%, 0%);
461 | }
462 |
463 | .flipped #g1 {
464 | transform: translate(100%, 0%);
465 | }
466 |
467 | .flipped #h1 {
468 | transform: translate(0%, 0%);
469 | }
470 |
471 |
472 |
473 |
474 | .flipped #a2 {
475 | transform: translate(700%, 100%);
476 | }
477 |
478 | .flipped #b2 {
479 | transform: translate(600%, 100%);
480 | }
481 |
482 | .flipped #c2 {
483 | transform: translate(500%, 100%);
484 | }
485 |
486 | .flipped #d2 {
487 | transform: translate(400%, 100%);
488 | }
489 |
490 | .flipped #e2 {
491 | transform: translate(300%, 100%);
492 | }
493 |
494 | .flipped #f2 {
495 | transform: translate(200%, 100%);
496 | }
497 |
498 | .flipped #g2 {
499 | transform: translate(100%, 100%);
500 | }
501 |
502 | .flipped #h2 {
503 | transform: translate(0%, 100%);
504 | }
505 |
506 |
507 |
508 |
509 | .flipped #a3 {
510 | transform: translate(700%, 200%);
511 | }
512 |
513 | .flipped #b3 {
514 | transform: translate(600%, 200%);
515 | }
516 |
517 | .flipped #c3 {
518 | transform: translate(500%, 200%);
519 | }
520 |
521 | .flipped #d3 {
522 | transform: translate(400%, 200%);
523 | }
524 |
525 | .flipped #e3 {
526 | transform: translate(300%, 200%);
527 | }
528 |
529 | .flipped #f3 {
530 | transform: translate(200%, 200%);
531 | }
532 |
533 | .flipped #g3 {
534 | transform: translate(100%, 200%);
535 | }
536 |
537 | .flipped #h3 {
538 | transform: translate(0%, 200%);
539 | }
540 |
541 |
542 |
543 |
544 | .flipped #a4 {
545 | transform: translate(700%, 300%);
546 | }
547 |
548 | .flipped #b4 {
549 | transform: translate(600%, 300%);
550 | }
551 |
552 | .flipped #c4 {
553 | transform: translate(500%, 300%);
554 | }
555 |
556 | .flipped #d4 {
557 | transform: translate(400%, 300%);
558 | }
559 |
560 | .flipped #e4 {
561 | transform: translate(300%, 300%);
562 | }
563 |
564 | .flipped #f4 {
565 | transform: translate(200%, 300%);
566 | }
567 |
568 | .flipped #g4 {
569 | transform: translate(100%, 300%);
570 | }
571 |
572 | .flipped #h4 {
573 | transform: translate(0%, 300%);
574 | }
575 |
576 |
577 |
578 |
579 | .flipped #a5 {
580 | transform: translate(700%, 400%);
581 | }
582 |
583 | .flipped #b5 {
584 | transform: translate(600%, 400%);
585 | }
586 |
587 | .flipped #c5 {
588 | transform: translate(500%, 400%);
589 | }
590 |
591 | .flipped #d5 {
592 | transform: translate(400%, 400%);
593 | }
594 |
595 | .flipped #e5 {
596 | transform: translate(300%, 400%);
597 | }
598 |
599 | .flipped #f5 {
600 | transform: translate(200%, 400%);
601 | }
602 |
603 | .flipped #g5 {
604 | transform: translate(100%, 400%);
605 | }
606 |
607 | .flipped #h5 {
608 | transform: translate(0%, 400%);
609 | }
610 |
611 |
612 |
613 |
614 | .flipped #a6 {
615 | transform: translate(700%, 500%);
616 | }
617 |
618 | .flipped #b6 {
619 | transform: translate(600%, 500%);
620 | }
621 |
622 | .flipped #c6 {
623 | transform: translate(500%, 500%);
624 | }
625 |
626 | .flipped #d6 {
627 | transform: translate(400%, 500%);
628 | }
629 |
630 | .flipped #e6 {
631 | transform: translate(300%, 500%);
632 | }
633 |
634 | .flipped #f6 {
635 | transform: translate(200%, 500%);
636 | }
637 |
638 | .flipped #g6 {
639 | transform: translate(100%, 500%);
640 | }
641 |
642 | .flipped #h6 {
643 | transform: translate(0%, 500%);
644 | }
645 |
646 |
647 |
648 |
649 | .flipped #a7 {
650 | transform: translate(700%, 600%);
651 | }
652 |
653 | .flipped #b7 {
654 | transform: translate(600%, 600%);
655 | }
656 |
657 | .flipped #c7 {
658 | transform: translate(500%, 600%);
659 | }
660 |
661 | .flipped #d7 {
662 | transform: translate(400%, 600%);
663 | }
664 |
665 | .flipped #e7 {
666 | transform: translate(300%, 600%);
667 | }
668 |
669 | .flipped #f7 {
670 | transform: translate(200%, 600%);
671 | }
672 |
673 | .flipped #g7 {
674 | transform: translate(100%, 600%);
675 | }
676 |
677 | .flipped #h7 {
678 | transform: translate(0%, 600%);
679 | }
680 |
681 |
682 |
683 |
684 | .flipped #a8 {
685 | transform: translate(700%, 700%);
686 | }
687 |
688 | .flipped #b8 {
689 | transform: translate(600%, 700%);
690 | }
691 |
692 | .flipped #c8 {
693 | transform: translate(500%, 700%);
694 | }
695 |
696 | .flipped #d8 {
697 | transform: translate(400%, 700%);
698 | }
699 |
700 | .flipped #e8 {
701 | transform: translate(300%, 700%);
702 | }
703 |
704 | .flipped #f8 {
705 | transform: translate(200%, 700%);
706 | }
707 |
708 | .flipped #g8 {
709 | transform: translate(100%, 700%);
710 | }
711 |
712 | .flipped #h8 {
713 | transform: translate(0%, 700%);
714 | }
715 |
716 |
717 | #a1 {
718 | transform: translate(0%, 700%);
719 | }
720 |
721 | #b1 {
722 | transform: translate(100%, 700%);
723 | }
724 |
725 | #c1 {
726 | transform: translate(200%, 700%);
727 | }
728 |
729 | #d1 {
730 | transform: translate(300%, 700%);
731 | }
732 |
733 | #e1 {
734 | transform: translate(400%, 700%);
735 | }
736 |
737 | #f1 {
738 | transform: translate(500%, 700%);
739 | }
740 |
741 | #g1 {
742 | transform: translate(600%, 700%);
743 | }
744 |
745 | #h1 {
746 | transform: translate(700%, 700%);
747 | }
748 |
749 |
750 | #a2 {
751 | transform: translate(0%, 600%);
752 | }
753 |
754 | #b2 {
755 | transform: translate(100%, 600%);
756 | }
757 |
758 | #c2 {
759 | transform: translate(200%, 600%);
760 | }
761 |
762 | #d2 {
763 | transform: translate(300%, 600%);
764 | }
765 |
766 | #e2 {
767 | transform: translate(400%, 600%);
768 | }
769 |
770 | #f2 {
771 | transform: translate(500%, 600%);
772 | }
773 |
774 | #g2 {
775 | transform: translate(600%, 600%);
776 | }
777 |
778 | #h2 {
779 | transform: translate(700%, 600%);
780 | }
781 |
782 |
783 | #a3 {
784 | transform: translate(0%, 500%);
785 | }
786 |
787 | #b3 {
788 | transform: translate(100%, 500%);
789 | }
790 |
791 | #c3 {
792 | transform: translate(200%, 500%);
793 | }
794 |
795 | #d3 {
796 | transform: translate(300%, 500%);
797 | }
798 |
799 | #e3 {
800 | transform: translate(400%, 500%);
801 | }
802 |
803 | #f3 {
804 | transform: translate(500%, 500%);
805 | }
806 |
807 | #g3 {
808 | transform: translate(600%, 500%);
809 | }
810 |
811 | #h3 {
812 | transform: translate(700%, 500%);
813 | }
814 |
815 |
816 | #a4 {
817 | transform: translate(0%, 400%);
818 | }
819 |
820 | #b4 {
821 | transform: translate(100%, 400%);
822 | }
823 |
824 | #c4 {
825 | transform: translate(200%, 400%);
826 | }
827 |
828 | #d4 {
829 | transform: translate(300%, 400%);
830 | }
831 |
832 | #e4 {
833 | transform: translate(400%, 400%);
834 | }
835 |
836 | #f4 {
837 | transform: translate(500%, 400%);
838 | }
839 |
840 | #g4 {
841 | transform: translate(600%, 400%);
842 | }
843 |
844 | #h4 {
845 | transform: translate(700%, 400%);
846 | }
847 |
848 |
849 | #a5 {
850 | transform: translate(0%, 300%);
851 | }
852 |
853 | #b5 {
854 | transform: translate(100%, 300%);
855 | }
856 |
857 | #c5 {
858 | transform: translate(200%, 300%);
859 | }
860 |
861 | #d5 {
862 | transform: translate(300%, 300%);
863 | }
864 |
865 | #e5 {
866 | transform: translate(400%, 300%);
867 | }
868 |
869 | #f5 {
870 | transform: translate(500%, 300%);
871 | }
872 |
873 | #g5 {
874 | transform: translate(600%, 300%);
875 | }
876 |
877 | #h5 {
878 | transform: translate(700%, 300%);
879 | }
880 |
881 |
882 | #a6 {
883 | transform: translate(0%, 200%);
884 | }
885 |
886 | #b6 {
887 | transform: translate(100%, 200%);
888 | }
889 |
890 | #c6 {
891 | transform: translate(200%, 200%);
892 | }
893 |
894 | #d6 {
895 | transform: translate(300%, 200%);
896 | }
897 |
898 | #e6 {
899 | transform: translate(400%, 200%);
900 | }
901 |
902 | #f6 {
903 | transform: translate(500%, 200%);
904 | }
905 |
906 | #g6 {
907 | transform: translate(600%, 200%);
908 | }
909 |
910 | #h6 {
911 | transform: translate(700%, 200%);
912 | }
913 |
914 | #a7 {
915 | transform: translate(0%, 100%);
916 | }
917 |
918 | #b7 {
919 | transform: translate(100%, 100%);
920 | }
921 |
922 | #c7 {
923 | transform: translate(200%, 100%);
924 | }
925 |
926 | #d7 {
927 | transform: translate(300%, 100%);
928 | }
929 |
930 | #e7 {
931 | transform: translate(400%, 100%);
932 | }
933 |
934 | #f7 {
935 | transform: translate(500%, 100%);
936 | }
937 |
938 | #g7 {
939 | transform: translate(600%, 100%);
940 | }
941 |
942 | #h7 {
943 | transform: translate(700%, 100%);
944 | }
945 |
946 | #a8 {
947 | transform: translate(0%, 0%);
948 | }
949 |
950 | #b8 {
951 | transform: translate(100%, 0%);
952 | }
953 |
954 | #c8 {
955 | transform: translate(200%, 0%);
956 | }
957 |
958 | #d8 {
959 | transform: translate(300%, 0%);
960 | }
961 |
962 | #e8 {
963 | transform: translate(400%, 0%);
964 | }
965 |
966 | #f8 {
967 | transform: translate(500%, 0%);
968 | }
969 |
970 | #g8 {
971 | transform: translate(600%, 0%);
972 | }
973 |
974 | #h8 {
975 | transform: translate(700%, 0%);
976 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | chess league
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |

60 |
61 |
62 |
63 |
devki
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
92 |

93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
120 |
121 |
127 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |

144 |
145 |
146 |
147 |
acharya
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
177 |

178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
202 |
207 |
208 |
209 |
210 |
211 |
214 |
215 |
218 |
219 |
222 |
223 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
235 |
236 |
237 |
238 |
239 |
245 |
246 |
247 |
248 |

249 |
250 |
251 |
acharya
252 |
253 |
vs
254 |
255 |
256 |

257 |
258 |
259 |
devki
260 |
261 |
262 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
284 |
285 |
290 |
294 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
Theme
307 |
308 |
309 |
classic
310 |
311 |

312 |
313 |
314 |
315 |
316 |
walnut
317 |
318 |

319 |
320 |
321 |
322 |
323 |
newspaper
324 |
325 |

326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
add new game
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
Books
358 |
359 |
360 |
361 |
362 |
363 |
book.bin
364 |
0%
365 |
366 |
369 |
370 |
371 |
372 |
373 |
374 |

375 |
376 |
gm2600.bin
377 |
338.61 KB
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 | Upload Book
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --bg-dark: #302e2b;
3 | --clr-dark: #262522;
4 | --clr-dark: #0e1514;
5 | --clr-dark: #e2e2e2;
6 | --clr-dark: #989795;
7 | --clr-dark: #ffffff14;
8 | --clr-dark: #ffffffb8;
9 | --sq-light: #ebecd0;
10 | --sq-dark: #739552;
11 | --highlight: #ffff33;
12 | --alpha-dark: rgba(0, 0, 0, 0.14);
13 | }
14 |
15 | .icon {
16 | aspect-ratio: 1;
17 | overflow: hidden;
18 | width: 20px;
19 | cursor: pointer;
20 | }
21 |
22 | .icon img {
23 | width: 100%;
24 | height: 100%;
25 | object-fit: cover;
26 | }
27 |
28 | *,
29 | *::before,
30 | *::after {
31 | font-family: 'Roboto', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
32 | list-style: none;
33 | margin: 0;
34 | padding: 0;
35 | box-sizing: border-box;
36 | text-decoration: none;
37 | }
38 |
39 | .win-animation{
40 | position: fixed;
41 | width: 100vw;
42 | height: 100vh;
43 | top: 0;
44 | left: 0;
45 | z-index: 100;
46 | display: none;
47 | }
48 |
49 | body {
50 | width: 100vw;
51 | min-height: 100svh;
52 | background-color: var(--bg-dark);
53 | display: flex;
54 | justify-content: center;
55 | overflow-x: hidden;
56 | }
57 |
58 | body::-webkit-scrollbar {
59 | display: none;
60 | }
61 |
62 | .container {
63 | display: grid;
64 | grid-template-columns: minmax(300px, 600px) 400px;
65 | grid-gap: 20px;
66 | max-height: 100svh;
67 | position: relative;
68 | }
69 |
70 | .board-layout {
71 | display: flex;
72 | flex-direction: column;
73 | gap: 10px;
74 | height: max-content;
75 | }
76 |
77 | .board-layout.flipped {
78 | flex-direction: column-reverse;
79 | }
80 |
81 | .board-layout .profile-photo {
82 | width: 50px;
83 | border-radius: 3px;
84 | }
85 |
86 | .board-layout .player {
87 | display: flex;
88 | align-items: center;
89 | justify-content: space-between;
90 | padding: 0 10px;
91 | color: #e2e2e2;
92 | text-transform: capitalize;
93 | font-size: 13px;
94 | font-weight: 600;
95 | }
96 | .board-layout .player .user{
97 | display: flex;
98 | align-items: center;
99 | gap: 10px;
100 | }
101 |
102 | .player.white .profile-photo {
103 | border: 5px solid white;
104 | }
105 |
106 | .player.black .profile-photo {
107 | border: 5px solid black;
108 | }
109 |
110 | .board-layout .player .profile-photo {
111 | border-width: 3px;
112 | }
113 |
114 | .board-layout .player .user .name{
115 | margin-bottom: 3px;
116 | }
117 | .board-layout .player .control{
118 | display: flex;
119 | align-items: center;
120 | gap: 10px;
121 | }
122 |
123 | .captures {
124 | display: flex;
125 | align-items: center;
126 | gap: 4px;
127 | }
128 |
129 | .capture {
130 | background-position: center;
131 | background-size: auto 100%;
132 | background-repeat: no-repeat;
133 | height: 12px;
134 | }
135 |
136 | .drop-menu{
137 | position: relative;
138 | min-width: 60px;
139 | height: max-content;
140 | color: #e2e2e2;
141 | z-index: 100;
142 | }
143 | .drop-menu .selection{
144 | cursor: pointer;
145 | background-color: #3c3a38;
146 | border: 1px solid #ffffff1a;
147 | padding: 4px 8px;
148 | display: flex;
149 | justify-content: space-between;
150 | align-items: center;
151 | border-radius: 3px;
152 | overflow: hidden;
153 | }
154 | .selection:hover,
155 | .drop-menu.active .selection{
156 | border-color: #ffffff4d;
157 | }
158 | .selection i{
159 | font-size: 22px;
160 | width: 16px;
161 | height: 16px;
162 | display: flex;
163 | align-items: center;
164 | justify-content: center;
165 | }
166 |
167 | .drop-menu.active .options{
168 | display: flex;
169 | }
170 | .drop-menu .options{
171 | width: 100px;
172 | right: 0;
173 | position: absolute;
174 | background-color: #21201d;
175 | border-radius: 3px;
176 | overflow: hidden;
177 | padding: 8px 0;
178 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
179 | display: flex;
180 | flex-direction: column;
181 | display: none;
182 | }
183 |
184 | .options .option{
185 | cursor: pointer;
186 | padding: 8px 8px;
187 | }
188 | .options .option.select,
189 | .options .option:hover{
190 | background-color: #0e1514;
191 | }
192 |
193 | .player.black .drop-menu .options,
194 | .flipped .player.white .drop-menu .options{
195 | top: calc(100% + 8px);
196 | bottom: auto;
197 | }
198 | .flipped .player.black .drop-menu .options,
199 | .player.white .drop-menu .options{
200 | bottom: calc(100% + 8px);
201 | flex-direction: column-reverse;
202 | top: auto;
203 | }
204 |
205 |
206 | #board {
207 | aspect-ratio: 1;
208 | background: url('../assets/theme/classic/board.webp') no-repeat;
209 | background-size: contain;
210 | border-radius: 3px;
211 | overflow: hidden;
212 | position: relative;
213 | }
214 |
215 | .coordinates {
216 | position: absolute;
217 | left: 0;
218 | top: 0;
219 | user-select: none;
220 | z-index: 3;
221 | }
222 |
223 | .coordinate-light {
224 | fill: var(--sq-dark);
225 | }
226 |
227 | .coordinate-dark {
228 | fill: var(--sq-light);
229 | }
230 |
231 | .coordinate-dark,
232 | .coordinate-light {
233 | font-weight: 600;
234 | }
235 |
236 | .flipped .promotion-window.black,
237 | .promotion-window {
238 | background-color: white;
239 | position: absolute;
240 | width: 12.5%;
241 | display: flex;
242 | flex-direction: column;
243 | top: 0;
244 | bottom: auto;
245 | border-radius: 3px;
246 | box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.65);
247 | z-index: 10;
248 | display: none;
249 | }
250 |
251 | .flipped .promotion-window,
252 | .promotion-window.black {
253 | top: auto;
254 | bottom: 0;
255 | flex-direction: column-reverse;
256 | }
257 |
258 | .promotion-window .piece {
259 | position: relative;
260 | width: 100%;
261 | }
262 |
263 | .promotion-window .piece:hover {
264 | background-color: var(--highlight);
265 | }
266 |
267 | .piece {
268 | background-size: contain;
269 | background-repeat: no-repeat;
270 | width: 12.5%;
271 | aspect-ratio: 1;
272 | position: absolute;
273 | cursor: grab;
274 | transition: transform .3s ease;
275 | z-index: 3;
276 | }
277 | .setting-window{
278 | padding: 30px 14px
279 | }
280 | .theme.active,
281 | .theme:hover{
282 | background-color: #302e2b;
283 | }
284 | .theme .name{
285 | text-transform: capitalize;
286 | }
287 | .theme{
288 | user-select: none;
289 | padding: 15px;
290 | display: flex;
291 | justify-content: space-between;
292 | align-items: flex-start;
293 | }
294 |
295 | .theme .preview img{
296 | width: 100%;
297 | height: 100%;
298 | object-fit: cover;
299 | }
300 |
301 | .theme .preview{
302 | aspect-ratio: 1;
303 | width: 100px;
304 | }
305 |
306 | .game-controller {
307 | background-color: #262522;
308 | display: flex;
309 | flex-direction: column;
310 | border-radius: 5px;
311 | overflow: hidden;
312 | height: 100svh;
313 | }
314 |
315 | .game-controller .header {
316 | background-color: #21201d;
317 | padding: 14px;
318 | color: #ffffffb8;
319 | display: flex;
320 | align-items: center;
321 | justify-content: space-between;
322 | font-weight: 600;
323 | font-size: 13px;
324 | }
325 |
326 | .game-controller .header .icons {
327 | display: flex;
328 | gap: 10px;
329 | align-items: center;
330 | }
331 |
332 | .game-controller .header .icons .icon {
333 | width: 20px;
334 | }
335 |
336 | .game-controller .header .icon:hover {
337 | filter: brightness(200%);
338 | }
339 |
340 | .lines{
341 | background-color: #1b1917;
342 | color: #e2e2e2;
343 | color: #81b64c;
344 | font-weight: 600;
345 | min-width: 100%;
346 | font-size: 12px;
347 | white-space: nowrap;
348 | margin-bottom: 5px;
349 | }
350 | .lines .line{
351 | margin: 0 8px;
352 | line-height: 25px;
353 | overflow-x: scroll;
354 | }
355 | .line::-webkit-scrollbar{
356 | display: none;
357 | }
358 |
359 | .game-controller .movelist {
360 | min-height: 150px;
361 | height: 100%;
362 | overflow-y: scroll;
363 | }
364 |
365 | .move {
366 | /* display: none !important; */
367 | }
368 |
369 | .movelist::-webkit-scrollbar {
370 | background-color: #262522;
371 | width: 6px;
372 | }
373 |
374 | .movelist::-webkit-scrollbar-thumb {
375 | background-color: #151413;
376 | }
377 |
378 | .movelist::-webkit-scrollbar-thumb:hover {
379 | background-color: #fff;
380 | }
381 |
382 | .movelist .move {
383 | display: grid;
384 | align-items: center;
385 | grid-template-columns: 10px 55px 55px;
386 | grid-gap: 10px;
387 | font-size: 11px;
388 | padding: 2px 5px 2px 15px;
389 | }
390 |
391 | .move .ply {
392 | color: #ffffff80;
393 | margin-right: 8px;
394 | }
395 |
396 | .move .node {
397 | min-width: 40px;
398 | width: max-content;
399 | color: #ffffffb8;
400 | font-weight: 700;
401 | padding: 4px;
402 | border-radius: 2px;
403 | border-bottom: 3px solid transparent;
404 | user-select: none;
405 | cursor: pointer;
406 | }
407 |
408 | .move:nth-child(odd) {
409 | background-color: #2a2926;
410 | }
411 |
412 | .node.selected {
413 | background-color: #454441;
414 | border-bottom: 3px solid #575754;
415 | color: white;
416 | }
417 |
418 | .controller {
419 | display: grid;
420 | grid-gap: 3px;
421 | grid-row-gap: 10px;
422 | grid-template-columns: repeat(4, 1fr);
423 | background-color: #21201d;
424 | padding: 16px 24px 20px;
425 | color: rgba(255, 255, 255, 0.72);
426 | }
427 |
428 |
429 | .backdrop {
430 | position: fixed;
431 | width: 100vw;
432 | height: 100vh;
433 | background-color: hsla(0, 0%, 0%, 0.7);
434 | z-index: 101;
435 | visibility: hidden;
436 | }
437 |
438 | .window{
439 | position: fixed;
440 | top: 50%;
441 | left: 50%;
442 | transform: translate(-50%, -50%) scale(0);
443 | background-color: #21201d;
444 | box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.65);
445 | border-radius: 10px;
446 | overflow: hidden;
447 | width: 100%;
448 | max-width: 450px;
449 | color: #ffffff80;
450 | font-size: 13px;
451 | font-weight: 600;
452 | transition: .4s 0s cubic-bezier(0.51, 0.01, 0.47, 1.3);
453 | visibility: hidden;
454 | opacity: 0;
455 | z-index: 100;
456 | }
457 |
458 |
459 | .download-window input,
460 | .download-window textarea {
461 | color: #ffffffA1;
462 | background-color: #3c3a38;
463 | border: 1px solid #777574;
464 | outline: none;
465 | padding: 4px 8px;
466 | font-size: 14px;
467 | border-radius: 2px;
468 | margin-bottom: 6px;
469 | display: block;
470 | width: 100%;
471 | resize: vertical;
472 | max-height: 300px;
473 | }
474 | .upload-book .icon{
475 | cursor: default;
476 | }
477 | .upload-book .icon.delete:hover{
478 | cursor: pointer;
479 | filter: brightness(200%);
480 | }
481 |
482 | .upload-book{
483 | padding: 30px 14px;
484 | font-weight: 400;
485 | }
486 |
487 | .upload-book > h3{
488 | text-align: center;
489 | color: #fff;
490 | font-size: 14px;
491 | margin-bottom: 20px;
492 | }
493 | .book{
494 | display: flex;
495 | align-items: center;
496 | gap: 10px;
497 | color: #ffffffA1;
498 | background-color: #151413;
499 | margin-bottom: 6px;
500 | padding: 16px 20px;
501 | border-radius: 5px;
502 | display: flex;
503 | gap: 10px;
504 | cursor: pointer;
505 | }
506 | .book.selected{
507 | background-color: #81b64c;
508 | }
509 | .book .content{
510 | /* border: 1px solid wheat; */
511 | width: 100%;
512 | }
513 | .book .content .detail{
514 | display: flex;
515 | justify-content: space-between;
516 | margin-bottom: 5px;
517 | }
518 | .progress-bar{
519 | width: 100%;
520 | height: 6px;
521 | background-color: #ebecd0;
522 | overflow: hidden;
523 | border-radius: 50px;
524 | }
525 | .progress-bar .progress{
526 | width: 0%;
527 | background-color: #81b64c;
528 | height: 100%;
529 | }
530 |
531 | .library{
532 | height: 312px;
533 | margin-bottom: 20px;
534 | overflow-y: scroll;
535 | }
536 | .library::-webkit-scrollbar{
537 | display: none;
538 | }
539 | .library .book .detail{
540 | width: 100%;
541 | }
542 |
543 | textarea {
544 | min-height: 200px;
545 | max-height: 400px;
546 | }
547 |
548 | textarea::-webkit-scrollbar-thumb {
549 | background-color: #21201f;
550 | }
551 |
552 | textarea::-webkit-scrollbar {
553 | width: 6px;
554 | background-color: #3c3a38;
555 | }
556 |
557 | .download-window .header {
558 | position: relative;
559 | background-color: #1b1917;
560 | padding-top: 16px;
561 | }
562 |
563 | .download-window .header .option {
564 | display: flex;
565 | }
566 |
567 | .download-window .header .option div {
568 | width: 100%;
569 | text-align: center;
570 | color: #81b64c;
571 | padding: 15px;
572 | border-bottom: 4px solid transparent;
573 | cursor: pointer;
574 | position: relative;
575 | }
576 |
577 | .download-window .header .option div::before {
578 | content: '';
579 | width: 0%;
580 | height: 4px;
581 | position: absolute;
582 | bottom: 0;
583 | left: 50%;
584 | transform: translateX(-50%);
585 | background-color: #81b64c;
586 | transition: width .3s;
587 | }
588 |
589 | .download-window .header .option div:hover::before,
590 | .download-window .header .option div.active::before {
591 | width: 100%
592 | }
593 |
594 | .download-window .content {
595 | padding: 10px 15px;
596 | display: flex;
597 | flex-direction: column;
598 | }
599 |
600 | .download-window .content p {
601 | padding: 5px 0;
602 | }
603 |
604 | .download-window .btn {
605 | font-size: 14px;
606 | font-weight: 600;
607 | padding: 5px 18px;
608 | text-shadow: none;
609 | height: 48px;
610 | align-self: center;
611 | margin-top: 8px;
612 | margin-bottom: 20px;
613 | }
614 |
615 | .download-window .btn .icon {
616 | filter: brightness(200%);
617 | width: 17px;
618 | padding: 0;
619 | margin-right: 10px;
620 | }
621 |
622 | .confirm-popover {
623 | text-align: center;
624 | width: 500px;
625 | padding: 30px 14px;
626 | }
627 | .backdrop.active{
628 | visibility: visible;
629 | }
630 | .backdrop.active>.active {
631 | transform: translate(-50%, -50%) scale(1);
632 | opacity: 1;
633 | visibility: visible;
634 | }
635 |
636 | .window .message {
637 | font-size: 14px;
638 | text-transform: capitalize;
639 | color: #e2e2e2;
640 | margin-bottom: 20px;
641 | }
642 |
643 | .confirm-popover .buttons {
644 | display: flex;
645 | gap: 10px;
646 | }
647 |
648 | .confirm-popover .buttons .btn {
649 | flex: 1;
650 | height: 48px;
651 | }
652 |
653 | .profile-photo {
654 | aspect-ratio: 1;
655 | width: 80px;
656 | border-radius: 10px;
657 | overflow: hidden;
658 | position: relative;
659 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.4);
660 | }
661 |
662 | .profile-photo .crown-alt {
663 | display: none;
664 | position: absolute;
665 | bottom: -5px;
666 | right: -5px;
667 | width: 30px;
668 | background-color: #81b64c;
669 | border: 7px solid #81b64c;
670 | border-top-left-radius: 5px;
671 | }
672 |
673 | .winner .profile-photo .crown-alt {
674 | display: block;
675 | }
676 |
677 |
678 | .profile-photo img {
679 | width: 100%;
680 | height: 100%;
681 | object-fit: cover;
682 | }
683 |
684 | .game-over {
685 | text-align: center;
686 | width: 350px;
687 | color: #e2e2e2;
688 | }
689 | .game-over::before{
690 | content: '';
691 | position: absolute;
692 | width: 200%;
693 | height: 60%;
694 | background-color: #0e1514;
695 | border-radius: 100%;
696 | left: 50%;
697 | top: 0;
698 | transform: translate(-50%, -50%);
699 | z-index: -2;
700 |
701 | }
702 | .close {
703 | position: absolute;
704 | z-index: 10;
705 | font-size: 24px;
706 | padding: 2px;
707 | top: 0;
708 | right: 0;
709 | width: 30px;
710 | aspect-ratio: 1;
711 | cursor: pointer;
712 | display: flex;
713 | align-items: center;
714 | justify-content: center;
715 | color: #989795;
716 | }
717 |
718 | .close:hover {
719 | color: #fff;
720 | background-color: #2a2926;
721 | }
722 |
723 | .game-over .header {
724 | margin-top: 20px;
725 | margin-bottom: 30px;
726 | }
727 |
728 | .game-over .header .title {
729 | font-size: 32px;
730 | font-weight: 800;
731 | }
732 |
733 | .game-over .header .subtitle {
734 | color: #989795;
735 | }
736 |
737 | .game-over .players {
738 | display: flex;
739 | align-items: center;
740 | justify-content: space-evenly;
741 | }
742 |
743 | .game-over .winner .profile-photo {
744 | border-color: #81b64c;
745 | box-shadow: 0px 0px 16px 0px #aef46721;
746 | }
747 |
748 | .players .name {
749 | margin-top: 5px;
750 | text-transform: capitalize;
751 | }
752 |
753 | .game-over .crowns {
754 | margin: 16px 0;
755 | display: flex;
756 | justify-content: center;
757 | gap: 10px;
758 | }
759 |
760 | .crown {
761 | width: 50px;
762 | }
763 |
764 | .crown.dark {
765 | filter: grayscale(1);
766 | }
767 |
768 | .game-over .buttons {
769 | display: grid;
770 | grid-template-columns: 1fr 1fr;
771 | grid-template-rows: 58px 45px;
772 | grid-gap: 8px;
773 | padding: 10px;
774 | }
775 |
776 | .game-over .buttons .btn {
777 | height: 100%;
778 | }
779 |
780 | .setup-position.active{
781 | visibility: visible;
782 | }
783 |
784 | .game-over.active {
785 | transition-delay: .7s;
786 | transform: translate(-50%, -50%) scale(1);
787 | opacity: 1;
788 | visibility: visible;
789 | }
790 |
791 |
792 | .upload {
793 | overflow: hidden;
794 | border-radius: 5px;
795 | margin-bottom: 20px;
796 | border: 2px dashed transparent;
797 | transition: .2s;
798 | }
799 |
800 | .upload.file-hover {
801 | border: 2px dashed #ffffffA1;
802 | }
803 |
804 | #upload-fen-input {
805 | color: #ffffffA1;
806 | background-color: #3c3a38;
807 | border: none;
808 | outline: none;
809 | padding: 4px 8px;
810 | font-size: 14px;
811 | border-radius: 2px;
812 | margin-bottom: 6px;
813 | width: 100%;
814 | }
815 |
816 | .upload textarea {
817 | color: #ffffffA1;
818 | background-color: #3c3a38;
819 | outline: none;
820 | padding: 4px 8px;
821 | font-size: 14px;
822 | display: block;
823 | width: 100%;
824 | resize: none;
825 | max-height: 300px;
826 | border: none;
827 | border-bottom: 1px solid #777574;
828 | }
829 |
830 | #upload-bar {
831 | width: 0%;
832 | height: 3px;
833 | margin-top: -3px;
834 | background-color: #777574;
835 | transition: width .3s;
836 | }
837 |
838 |
839 | .upload .btn {
840 | border-radius: 0;
841 | height: 48px;
842 | display: flex;
843 | align-items: center;
844 | justify-content: center;
845 | border: none;
846 | gap: 5px;
847 | }
848 |
849 | .upload .btn .icon {
850 | width: 20px;
851 | }
852 |
853 |
854 |
855 |
856 | .setup-position{
857 | background-color: #575754;
858 | display: grid;
859 | bottom: 0;
860 | right: 0;
861 | left: auto;
862 | }
863 | .setup-position.active{
864 | opacity: 1;
865 | transform: scale(1);
866 | }
867 |
868 | .setup-position .pieces{
869 | padding: 16px;
870 | display: grid;
871 | grid-template-columns: repeat(6, minmax(30px, 75px));
872 | }
873 | .setup-position .pieces .piece{
874 | position: relative;
875 | width: 100%;
876 | }
877 | .setup-position .piece.selected{
878 | background-color: var(--highlight);
879 | }
880 | .setup-position .buttons{
881 | display: grid;
882 | grid-template-columns: 1fr 1fr;
883 | grid-gap: 10px;
884 | padding: 16px;
885 | }
886 | .setup-position .buttons button{
887 | height: 50px;
888 | }
889 | .setup-position .close{
890 | background-color: transparent;
891 | }
892 |
893 | .castle-permission{
894 | font-weight: 600;
895 | text-transform: capitalize;
896 | grid-gap: 10px;
897 | padding: 16px;
898 | color: #e2e2e2;
899 | display: grid;
900 | grid-template-columns: 1fr 1fr;
901 | }
902 | .checkbox-grid{
903 | gap: 5px;
904 | grid-column: 1/3;
905 | display: grid;
906 | grid-template-columns: 1fr 1fr;
907 | }
908 | .checkbox-grid input{
909 | transform: scale(1.4);
910 | margin-right: 10px;
911 | cursor: pointer;
912 | }
913 |
914 |
915 |
916 |
917 | @media screen and (max-width: 900px) {
918 | .icon {
919 | width: 28px;
920 | }
921 |
922 | .close {
923 | width: 50px;
924 | font-size: 32px;
925 | }
926 |
927 | #board {
928 | justify-self: center;
929 | }
930 |
931 | .board-layout .player {
932 | padding: 0;
933 | }
934 |
935 |
936 | .container {
937 | grid-template-columns: minmax(200px, 600px);
938 | grid-template-rows: auto 1fr;
939 | margin-top: 10px;
940 | margin-bottom: 84px;
941 | }
942 |
943 | .movelist {
944 | padding-top: 10px;
945 | padding-bottom: 20px;
946 | overflow-y: auto;
947 | }
948 |
949 | .game-controller {
950 | min-height: 500px;
951 | max-height: 81svh;
952 | display: grid;
953 | grid-template-rows: repeat(3, max-content);
954 | }
955 | .game-controller .controller{
956 | grid-row: 1/2;
957 | border-bottom: 1px solid #2a2926;
958 | }
959 | .game-controller .controller .btn {
960 | height: 48px;
961 | }
962 | .game-controller .controller .btn {
963 | font-size: 18px;
964 | }
965 |
966 | .square {
967 | font-size: clamp(7px, 3vmin, 11px);
968 | }
969 | }
970 |
971 |
972 |
973 |
974 | @media screen and (max-width: 480px) {
975 | .toggle-btn {
976 | width: 40px;
977 | height: 20px;
978 | }
979 |
980 | .confirm-popover {
981 | padding: 40px 10px;
982 | }
983 | .confirm-popover,
984 | .download-window,
985 | .game-over {
986 | width: 92vw;
987 | }
988 |
989 | #upload-fen-input {
990 | padding: 8px;
991 | }
992 |
993 | .board-layout {
994 | margin: 0 16px;
995 | }
996 |
997 |
998 | .confirm-popover .message {
999 | font-size: 16px;
1000 | margin-bottom: 30px;
1001 | }
1002 |
1003 | .confirm-popover .buttons .btn {
1004 | height: 50px;
1005 | }
1006 |
1007 | .game-over .profile-photo {
1008 | width: 100px;
1009 | }
1010 |
1011 | .game-over .crown {
1012 | width: 60px;
1013 | }
1014 |
1015 | .game-controller {
1016 | flex-direction: column;
1017 | grid-template-rows: repeat(2, max-content);
1018 | }
1019 |
1020 | .game-over .buttons {
1021 | grid-template-rows: 64px 50px;
1022 | }
1023 |
1024 | .game-controller .controller {
1025 | position: fixed;
1026 | width: 100%;
1027 | left: 0;
1028 | bottom: 0;
1029 | padding: 16px 10px 20px;
1030 | z-index: 100;
1031 | }
1032 |
1033 | .controller .btn i::before {
1034 | font-size: 30px;
1035 | }
1036 |
1037 | .game-controller {
1038 | padding-bottom: 100px;
1039 | }
1040 |
1041 | .game-controller .controller .btn {
1042 | height: 48px;
1043 | }
1044 |
1045 | .movelist .move {
1046 | font-size: 12px;
1047 | grid-template-columns: 10px 80px 55px;
1048 | }
1049 | }
--------------------------------------------------------------------------------