├── doc └── data flow moving.md ├── .gitignore ├── favicon.ico ├── assets ├── sounds │ └── chess_console_sounds.mp3 └── styles │ ├── screen.scss │ ├── _config.scss │ ├── _markers.scss │ ├── _chess-console.scss │ ├── screen.css.map │ └── screen.css ├── .gitattributes ├── src ├── ChessConsolePlayer.js ├── ChessConsoleState.js ├── players │ ├── RandomPlayer.js │ └── LocalPlayer.js ├── components │ ├── GameControl │ │ ├── GameControl.js │ │ └── NewGameDialog.js │ ├── Persistence.js │ ├── GameStateOutput.js │ ├── Sound.js │ ├── History.js │ ├── CapturedPieces.js │ ├── HistoryControl.js │ └── Board.js ├── tools │ ├── ChessRender.js │ └── Openings.js └── ChessConsole.js ├── package.json ├── LICENSE ├── index.html ├── examples ├── minimal.html ├── sandbox │ ├── minimal.html │ └── template-in-page.html ├── play-both-local.html ├── advanced-accessibility.html ├── game-with-random.html ├── load-pgn-with-setup.html ├── chess960-game.html ├── load-pgn.html ├── test-promotion.html └── different-style.html ├── CLAUDE.md └── README.md /doc/data flow moving.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.idea 3 | /lib -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaack/chess-console/HEAD/favicon.ico -------------------------------------------------------------------------------- /assets/sounds/chess_console_sounds.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaack/chess-console/HEAD/assets/sounds/chess_console_sounds.mp3 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # https://github.com/github/linguist#overrides 2 | assets/styles/screen.css linguist-generated 3 | index.html linguist-documentation 4 | examples/* linguist-documentation -------------------------------------------------------------------------------- /assets/styles/screen.scss: -------------------------------------------------------------------------------- 1 | @import "config"; 2 | @import "chess-console"; 3 | @import "../../node_modules/cm-chessboard/assets/chessboard.scss"; 4 | @import "../../node_modules/cm-chessboard/assets/extensions/promotion-dialog/promotion-dialog.scss"; 5 | @import "markers"; 6 | 7 | html { 8 | body { 9 | .container-fluid { 10 | max-width: 1200px; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /assets/styles/_config.scss: -------------------------------------------------------------------------------- 1 | $spacer: 1rem; 2 | 3 | $white: #fff; 4 | $gray-100: #f8f9fa; 5 | $gray-200: #e9ecef; 6 | $gray-300: #dee2e6; 7 | $gray-400: #ced4da; 8 | $gray-500: #adb5bd; 9 | $gray-600: #6c757d; 10 | $gray-700: #495057; 11 | $gray-800: #343a40; 12 | $gray-900: #212529; 13 | $black: #000; 14 | 15 | $blue: #0d6efd; 16 | $indigo: #6610f2; 17 | $purple: #6f42c1; 18 | $pink: #d63384; 19 | $red: #dc3545; 20 | $orange: #fd7e14; 21 | $yellow: #ffc107; 22 | $green: #198754; 23 | $teal: #20c997; 24 | $cyan: #0dcaf0; 25 | 26 | $primary: $blue; 27 | $secondary: $gray-600; 28 | $success: $green; 29 | $info: $cyan; 30 | $warning: $yellow; 31 | $danger: $red; 32 | $light: $gray-100; 33 | $dark: $gray-900; 34 | -------------------------------------------------------------------------------- /src/ChessConsolePlayer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | export class ChessConsolePlayer { 8 | 9 | constructor(chessConsole, name) { 10 | this.chessConsole = chessConsole 11 | this.name = name 12 | } 13 | 14 | /** 15 | * Called, when the Console requests the next Move from a Player. 16 | * The Player should answer the moveRequest with a moveResponse. 17 | * The moveResponse then returns the move result, if no move result was returned, the move was not legal. 18 | * @param fen current position 19 | * @param moveResponse a callback function to call as the moveResponse. Parameter is an object, 20 | * containing 'from' and `to`. Example: `moveResult = moveResponse({from: "e2", to: "e4", promotion: null})`. 21 | */ 22 | moveRequest(fen, moveResponse) { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chess-console", 3 | "version": "6.12.4", 4 | "description": "ES6 Module for playing chess", 5 | "browser": "index.html", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/shaack/chess-console.git" 13 | }, 14 | "keywords": [ 15 | "ES6", 16 | "chess", 17 | "stockfish", 18 | "chessboard", 19 | "bootstrap", 20 | "jquery" 21 | ], 22 | "author": "shaack (https://shaack.com)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/shaack/chess-console/issues" 26 | }, 27 | "homepage": "https://github.com/shaack/chess-console#readme", 28 | "dependencies": { 29 | "bootstrap-show-modal": "^6.0.10", 30 | "chess.mjs": "^2.2.5", 31 | "cm-chess": "^3.6.1", 32 | "cm-chessboard": "^8.11.0", 33 | "cm-pgn": "^4.0.6", 34 | "cm-web-modules": "^2.5.0", 35 | "patch": "^0.0.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ChessConsoleState.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Observe} from "cm-web-modules/src/observe/Observe.js" 8 | import {COLOR} from "cm-chessboard/src/Chessboard.js" 9 | import {Chess} from "cm-chess/src/Chess.js" 10 | 11 | export class ChessConsoleState { 12 | 13 | constructor(props) { 14 | this.chess = new Chess() // used to validate moves and keep the history 15 | this.orientation = props.playerColor || COLOR.white 16 | this.plyViewed = undefined // the play viewed on the board 17 | } 18 | 19 | observeChess(callback) { 20 | const chessManipulationMethods = [ 21 | 'move', 'clear', 'load', 'loadPgn', 'put', 'remove', 'reset', 'undo' 22 | ] 23 | chessManipulationMethods.forEach((methodName) => { 24 | Observe.postFunction(this.chess, methodName, (params) => { 25 | callback(params) 26 | }) 27 | }) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/players/RandomPlayer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Chess} from "chess.mjs/src/Chess.js" 8 | import {ChessConsolePlayer} from "../ChessConsolePlayer.js" 9 | 10 | export class RandomPlayer extends ChessConsolePlayer { 11 | 12 | constructor(chessConsole, name, props = {}) { 13 | super(chessConsole, name) 14 | this.chess = new Chess() 15 | this.props = {delay: 1000} 16 | Object.assign(this.props, props) 17 | } 18 | 19 | random(min, max) { 20 | return Math.floor(Math.random() * (max - min + 1)) + min 21 | } 22 | 23 | moveRequest(fen, moveResponse) { 24 | setTimeout(() => { 25 | this.chess.load(fen) 26 | const possibleMoves = this.chess.moves({verbose: true}) 27 | if (possibleMoves.length > 0) { 28 | const randomMove = possibleMoves[this.random(0, possibleMoves.length - 1)] 29 | moveResponse({from: randomMove.from, to: randomMove.to, promotion: randomMove.promotion}) 30 | } 31 | }, this.props.delay) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (for the source code) 2 | 3 | Copyright (c) 2018 Stefan Haack (https://shaack.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | Creative Commons License BY 4.0 (for the sounds 'chess_console_sounds.mp3') 25 | 26 | https://creativecommons.org/licenses/by/4.0/ -------------------------------------------------------------------------------- /assets/styles/_markers.scss: -------------------------------------------------------------------------------- 1 | $marker-color: #000000; 2 | $marker-color-primary: #0009bd; 3 | $marker-color-danger: #aa0000; 4 | 5 | .cm-chessboard { 6 | .markers { 7 | pointer-events: none; 8 | 9 | .marker { 10 | &.marker-frame { 11 | stroke: $marker-color; 12 | stroke-width: 1.8px; 13 | opacity: 0.4; 14 | } 15 | 16 | &.marker-frame-primary { 17 | stroke: $marker-color-primary; 18 | stroke-width: 1.8px; 19 | opacity: 0.4; 20 | } 21 | 22 | &.marker-frame-danger { 23 | stroke: $marker-color-danger; 24 | stroke-width: 1.8px; 25 | opacity: 0.4; 26 | } 27 | 28 | &.marker-circle { 29 | stroke: $marker-color; 30 | stroke-width: 2.5px; 31 | opacity: 0.2; 32 | } 33 | 34 | &.marker-circle-primary { 35 | stroke: $marker-color-primary; 36 | stroke-width: 2.5px; 37 | opacity: 0.4; 38 | } 39 | 40 | &.marker-circle-danger { 41 | stroke: $marker-color-danger; 42 | stroke-width: 2.5px; 43 | opacity: 0.5; 44 | } 45 | 46 | &.marker-square { 47 | fill: yellow; 48 | opacity: 0.3; 49 | } 50 | 51 | &.marker-square-danger { 52 | fill: $marker-color-danger; 53 | opacity: 0.2; 54 | } 55 | 56 | &.marker-square-primary { 57 | fill: $marker-color-primary; 58 | opacity: 0.11; 59 | } 60 | 61 | &.marker-dot { 62 | fill: black; 63 | opacity: 0.25; 64 | } 65 | 66 | &.marker-bevel { 67 | fill: black; 68 | opacity: 0.25; 69 | } 70 | 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | chess-console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

chess-console

14 |

A component based JavaScript chess client GUI, based on cm-chessboard and 15 | Bootstrap 5

16 |

Examples

17 | 27 |

References

28 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/components/GameControl/GameControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {NewGameDialog} from "./NewGameDialog.js" 8 | 9 | export class GameControl { 10 | 11 | constructor(chessConsole, props) { 12 | this.context = chessConsole.componentContainers.controlButtons 13 | this.chessConsole = chessConsole 14 | this.props = props 15 | 16 | const i18n = chessConsole.i18n 17 | i18n.load({ 18 | de: { 19 | "start_game": "Ein neues Spiel starten", 20 | "undo_move": "Zug zurück nehmen" 21 | }, 22 | en: { 23 | "start_game": "Start a new game", 24 | "undo_move": "Undo move" 25 | } 26 | }).then(() => { 27 | 28 | this.$btnUndoMove = $(``) 29 | this.$btnStartNewGame = $(`\`)`) 30 | 31 | this.context.appendChild(this.$btnUndoMove[0]) 32 | this.context.appendChild(this.$btnStartNewGame[0]) 33 | 34 | this.$btnUndoMove.click(() => { 35 | this.chessConsole.undoMove() 36 | }) 37 | this.$btnStartNewGame.click(() => { 38 | this.showNewGameDialog() 39 | }) 40 | 41 | this.chessConsole.state.observeChess(() => { 42 | this.setButtonStates() 43 | }) 44 | this.setButtonStates() 45 | }) 46 | } 47 | 48 | showNewGameDialog() { 49 | new NewGameDialog(this.chessConsole, { 50 | title: this.chessConsole.i18n.t('start_game') 51 | }) 52 | } 53 | 54 | setButtonStates() { 55 | if (this.chessConsole.state.chess.plyCount() < 2) { 56 | this.$btnUndoMove.prop("disabled", true) 57 | } else { 58 | this.$btnUndoMove.prop("disabled", false) 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/components/Persistence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {COLOR} from "cm-chess/src/Chess.js" 7 | import {Component} from "cm-web-modules/src/app/Component.js" 8 | import {CONSOLE_MESSAGE_TOPICS} from "../ChessConsole.js" 9 | 10 | export class Persistence extends Component { 11 | 12 | constructor(chessConsole, props) { 13 | super(props) 14 | this.chessConsole = chessConsole 15 | if(!this.props.savePrefix) { 16 | this.props.savePrefix = "ChessConsole" 17 | } 18 | this.chessConsole.state.observeChess(() => { 19 | this.save() 20 | }) 21 | this.chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.newGame, () => { 22 | this.save() 23 | }) 24 | this.chessConsole.persistence = this 25 | } 26 | 27 | load(prefix = this.props.savePrefix) { 28 | const props = {} 29 | try { 30 | if (this.loadValue("PlayerColor") !== null) { 31 | props.playerColor = this.loadValue("PlayerColor") 32 | } else { 33 | props.playerColor = COLOR.white 34 | } 35 | if (localStorage.getItem(prefix + "Pgn") !== null) { 36 | props.pgn = localStorage.getItem(prefix + "Pgn") 37 | } 38 | this.chessConsole.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.load) 39 | this.chessConsole.initGame(props) 40 | } catch (e) { 41 | localStorage.clear() 42 | console.warn(e) 43 | this.chessConsole.initGame({playerColor: COLOR.white}) 44 | } 45 | } 46 | 47 | loadValue(valueName, prefix = this.props.savePrefix) { 48 | let item = null 49 | try { 50 | item = localStorage.getItem(prefix + valueName) 51 | return JSON.parse(item) 52 | } catch (e) { 53 | console.error("error loading ", prefix + valueName) 54 | console.error("item:" + item) 55 | console.error(e) 56 | } 57 | } 58 | 59 | save(prefix = this.props.savePrefix) { 60 | localStorage.setItem(prefix + "PlayerColor", JSON.stringify(this.chessConsole.props.playerColor)) 61 | localStorage.setItem(prefix + "Pgn", this.chessConsole.state.chess.renderPgn()) 62 | } 63 | 64 | saveValue(valueName, value, prefix = this.props.savePrefix) { 65 | localStorage.setItem(prefix + valueName, JSON.stringify(value)) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/components/GameStateOutput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | export class GameStateOutput { 8 | 9 | constructor(chessConsole) { 10 | this.context = chessConsole.componentContainers.notifications 11 | this.chessConsole = chessConsole 12 | this.i18n = chessConsole.i18n 13 | this.i18n.load( 14 | { 15 | de: { 16 | game_over: "Das Spiel ist beendet", 17 | check: "Schach!", 18 | checkmate: "Schachmatt", 19 | draw: "Remis", 20 | stalemate: "Patt", 21 | threefold_repetition: "Remis durch dreifache Wiederholung" 22 | }, 23 | en: { 24 | game_over: "The game is over", 25 | check: "Check!", 26 | checkmate: "Checkmate", 27 | draw: "Draw", 28 | stalemate: "Stalemate", 29 | threefold_repetition: "Draw by threefold repetition" 30 | } 31 | } 32 | ) 33 | this.element = document.createElement("div") 34 | this.element.setAttribute("class", "gameState alert alert-primary mb-2") 35 | this.context.appendChild(this.element) 36 | 37 | this.chessConsole.state.observeChess(() => { 38 | this.redraw() 39 | }) 40 | this.redraw() 41 | } 42 | 43 | redraw() { 44 | const chess = this.chessConsole.state.chess 45 | let html = '' 46 | if (chess.gameOver()) { 47 | html += `${this.i18n.t("game_over")}
` 48 | if (chess.inCheckmate()) { 49 | html += `${this.i18n.t("checkmate")}` 50 | } else if (chess.inStalemate()) { 51 | html += `${this.i18n.t("stalemate")}` 52 | } else if (chess.inThreefoldRepetition()) { 53 | html += `${this.i18n.t("threefold_repetition")}` 54 | } else if (chess.inDraw()) { 55 | html += `${this.i18n.t("draw")}` 56 | } 57 | } else if (chess.inCheck()) { 58 | html = `${this.i18n.t("check")}` 59 | } else { 60 | html = "" 61 | } 62 | if (html) { 63 | this.chessConsole.componentContainers.notifications.style.display = "block" 64 | this.element.innerHTML = `${html}` 65 | } else { 66 | this.chessConsole.componentContainers.notifications.style.display = "none" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/GameControl/NewGameDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {COLOR} from "cm-chess/src/Chess.js" 8 | import "bootstrap-show-modal/src/ShowModal.js" 9 | 10 | export class NewGameDialog { 11 | 12 | constructor(module, props) { 13 | const i18n = module.i18n 14 | i18n.load({ 15 | de: { 16 | color: "Farbe", 17 | white: "Weiss", 18 | black: "Schwarz", 19 | auto: "automatisch" 20 | }, 21 | en: { 22 | color: "Color", 23 | white: "White", 24 | black: "Black", 25 | auto: "automatically" 26 | } 27 | }).then(() => { 28 | const newGameColor = module.persistence.loadValue("newGameColor") 29 | props.modalClass = "fade" 30 | props.body = 31 | `
32 |
33 |
38 |
` 39 | props.footer = ` 40 | ` 41 | props.onCreate = (modal) => { 42 | modal.element.querySelector("button[type='submit']").addEventListener("click", function (event) { 43 | event.preventDefault() 44 | const formElement = modal.element.querySelector(".form") 45 | let color = formElement.querySelector("#color").value 46 | module.persistence.saveValue("newGameColor", color) 47 | if (color !== COLOR.white && color !== COLOR.black) { 48 | color = (module.props.playerColor === COLOR.white) ? COLOR.black : COLOR.white 49 | } 50 | modal.hide() 51 | module.newGame({playerColor: color}) 52 | }) 53 | } 54 | bootstrap.showModal(props) 55 | }) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /examples/minimal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Example: minimal setup, without persistence, sound and most components

16 |
17 |
18 | 19 | 20 | 23 | 24 | 25 | 36 | 37 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/tools/ChessRender.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chess 4 | * License: MIT, see file 'LICENSE' 5 | * 6 | * @deprecated 7 | */ 8 | 9 | import {COLOR} from "cm-chess/src/Chess.js" 10 | 11 | export const PIECES = { 12 | notation: { 13 | de: { 14 | R: "T", N: "S", B: "L", Q: "D", K: "K", P: "" 15 | } 16 | }, 17 | figures: { 18 | utf8: { 19 | Rw: "♖", Nw: "♘", Bw: "♗", Qw: "♕", Kw: "♔", Pw: "♙", 20 | Rb: "♜", Nb: "♞", Bb: "♝", Qb: "♛", Kb: "♚", Pb: "♟" 21 | }, 22 | fontAwesomePro: { 23 | Rw: '', 24 | Nw: '', 25 | Bw: '', 26 | Qw: '', 27 | Kw: '', 28 | Pw: '', 29 | Rb: '', 30 | Nb: '', 31 | Bb: '', 32 | Qb: '', 33 | Kb: '', 34 | Pb: '' 35 | } 36 | } 37 | } 38 | 39 | // noinspection JSUnusedGlobalSymbols 40 | export class ChessRender { 41 | static san(san, color = COLOR.white, lang = "en", mode = "text", pieces = PIECES.figures.utf8) { 42 | // console.warn("ChessRender is deprecated and will be removed in future") 43 | if(mode === "figures") { 44 | if (color === COLOR.white) { 45 | return this.replaceAll(san, { 46 | "R": pieces.Rw, 47 | "N": pieces.Nw, 48 | "B": pieces.Bw, 49 | "Q": pieces.Qw, 50 | "K": pieces.Kw 51 | }) 52 | } else { 53 | return this.replaceAll(san, { 54 | "R": pieces.Rb, 55 | "N": pieces.Nb, 56 | "B": pieces.Bb, 57 | "Q": pieces.Qb, 58 | "K": pieces.Kb 59 | }) 60 | } 61 | } else if(mode === "text") { 62 | return this.replaceAll(san, PIECES.notation[lang]) 63 | } else { 64 | console.error("mode must be 'text' or 'figures'") 65 | } 66 | } 67 | static replaceAll(str, replacementsObj, ignoreCase = false) { 68 | let retStr = str 69 | const flags = ignoreCase ? "gi" : "g" 70 | for (let needle in replacementsObj) { 71 | // noinspection JSUnfilteredForInLoop 72 | retStr = retStr.replace(new RegExp(needle, flags), replacementsObj[needle]) 73 | } 74 | return retStr 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/sandbox/minimal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Example: minimal setup, without persistence, sound and most components

16 |
17 |
18 | 19 | 20 | 23 | 24 | 25 | 37 | 38 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/play-both-local.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Example: Both players local

16 |
17 |
18 | 19 | 20 | 23 | 24 | 25 | 37 | 38 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/components/Sound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {AudioSprite} from "cm-web-modules/src/audio/AudioSprite.js" 8 | import {Component} from "cm-web-modules/src/app/Component.js" 9 | import {CONSOLE_MESSAGE_TOPICS} from "../ChessConsole.js" 10 | import {createAudioContext} from "cm-web-modules/src/audio/Audio.js" 11 | 12 | export class Sound extends Component { 13 | 14 | constructor(chessConsole, props) { 15 | super(props) 16 | createAudioContext() 17 | this.chessConsole = chessConsole 18 | this.audioSprite = new AudioSprite(this.props.soundSpriteFile, 19 | { 20 | gain: 1, 21 | slices: { 22 | "game_start": {offset: 0, duration: 0.9}, 23 | "game_won": {offset: 0.9, duration: 1.8}, 24 | "game_lost": {offset: 2.7, duration: 0.9}, 25 | "game_draw": {offset: 9.45, duration: 1.35}, 26 | "check": {offset: 3.6, duration: 0.45}, 27 | "wrong_move": {offset: 4.05, duration: 0.45}, 28 | "move": {offset: 4.5, duration: 0.2}, 29 | "capture": {offset: 6.3, duration: 0.2}, 30 | "castle": {offset: 7.65, duration: 0.2}, 31 | "take_back": {offset: 8.1, duration: 0.12}, 32 | "promotion": {offset: 9.0, duration: 0.45}, 33 | "dialog": {offset: 10.8, duration: 0.45} 34 | } 35 | }) 36 | chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.initGame, () => { 37 | // this.play("game_start") 38 | }) 39 | chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.legalMove, (data) => { 40 | const chess = this.chessConsole.state.chess 41 | const flags = data.moveResult.flags 42 | if (flags.indexOf("p") !== -1) { 43 | this.play("promotion") 44 | } else if (flags.indexOf("c") !== -1) { 45 | this.play("capture") 46 | } else if (flags.indexOf("k") !== -1 || flags.indexOf("q") !== -1) { 47 | this.play("castle") 48 | } else { 49 | clearInterval(this.moveDebounced) 50 | this.moveDebounced = setTimeout(() => { 51 | this.play("move") 52 | }, 10) 53 | } 54 | if (chess.inCheck() || chess.inCheckmate()) { 55 | this.play("check") 56 | } 57 | }) 58 | chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.illegalMove, () => { 59 | this.play("wrong_move") 60 | }) 61 | chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.moveUndone, () => { 62 | this.play("take_back") 63 | }) 64 | chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.gameOver, (data) => { 65 | setTimeout(() => { 66 | if(!data.wonColor) { 67 | this.play("game_lost") 68 | } else { 69 | if(data.wonColor === this.chessConsole.props.playerColor) { 70 | this.play("game_won") 71 | } else { 72 | this.play("game_lost") 73 | } 74 | } 75 | }, 500) 76 | }) 77 | chessConsole.sound = this 78 | } 79 | 80 | play(soundName) { 81 | this.audioSprite.play(soundName) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /examples/advanced-accessibility.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Local vs Random with advanced accessibility

16 |
17 |
18 | 19 | 20 | 23 | 24 | 25 | 37 | 38 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /assets/styles/_chess-console.scss: -------------------------------------------------------------------------------- 1 | .buttons-grid { 2 | display: grid; 3 | justify-content: left; 4 | grid-template-columns: repeat(auto-fill, 3rem); 5 | gap: 0.5rem; 6 | 7 | .btn-link.text-black:active, .btn-link.text-black:focus { 8 | box-shadow: none !important; 9 | // background-color: $gray-100 !important; 10 | } 11 | 12 | .btn[disabled] { 13 | opacity: 0.4; 14 | } 15 | 16 | } 17 | 18 | .chess-console { 19 | 20 | .fa-figure-white { 21 | color: white; 22 | text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black, -1px -1px 1px black; 23 | } 24 | 25 | .fa-figure-black { 26 | color: black; 27 | text-shadow: 1px 1px 1px white, 1px -1px 1px white, -1px 1px 1px white, -1px -1px 1px white; 28 | transform: scale(1.14); 29 | } 30 | 31 | .chess-console-right, .chess-console-left { 32 | margin-top: 20px; 33 | } 34 | 35 | .chess-console-board { 36 | // margin-top: -1.4em; 37 | .chessboard { 38 | width: 100%; 39 | } 40 | 41 | .player { 42 | // text-align: center; 43 | font-weight: bold; 44 | font-size: 120%; 45 | 46 | &.to-move:after { 47 | color: #333; 48 | margin-left: 8px; 49 | content: "■"; 50 | } 51 | } 52 | 53 | .clock { 54 | font-family: 'DSDIGI', sans-serif; 55 | font-size: 150%; 56 | line-height: 0.99; 57 | } 58 | } 59 | 60 | .chess-console-left { 61 | .history { 62 | max-height: 284px; 63 | max-width: 210px; 64 | overflow-y: auto; 65 | overflow-x: visible; 66 | margin-bottom: 2 * $spacer; 67 | width: calc(100% + 8px); 68 | 69 | table { 70 | width: 100%; 71 | 72 | .num { 73 | width: 18%; 74 | } 75 | 76 | .ply { 77 | width: 41%; 78 | 79 | i { 80 | pointer-events: none; 81 | } 82 | 83 | &.active { 84 | font-weight: bold; 85 | } 86 | } 87 | } 88 | 89 | &.clickable { 90 | .ply { 91 | cursor: pointer; 92 | } 93 | } 94 | } 95 | 96 | .captured-pieces { 97 | margin-bottom: $spacer; 98 | font-size: 120%; 99 | > div:first-of-type { 100 | margin-bottom: 0.5rem; 101 | } 102 | .piece { 103 | * { 104 | pointer-events: none; 105 | } 106 | } 107 | } 108 | } 109 | 110 | .chess-console-right { 111 | .buttons-grid { 112 | .autoplay { 113 | .fa-play { 114 | color: $green; 115 | } 116 | 117 | .fa-stop { 118 | color: $orange; 119 | } 120 | } 121 | 122 | .btn-active { 123 | color: $green; 124 | } 125 | } 126 | 127 | .game-status { 128 | font-weight: bold; 129 | margin-bottom: $spacer; 130 | } 131 | 132 | .last-error { 133 | color: $danger; 134 | margin-bottom: $spacer; 135 | } 136 | 137 | .move-input { 138 | margin-bottom: $spacer; 139 | 140 | .first-move { 141 | color: $gray-800; 142 | font-size: 280%; 143 | width: 100%; 144 | overflow: hidden; 145 | } 146 | 147 | .move-input-controls { 148 | width: 100%; 149 | 150 | button { 151 | float: left; 152 | margin-right: $spacer / 2; 153 | margin-bottom: $spacer / 2; 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | .modal svg.piece { 161 | width: 40px; 162 | height: 40px; 163 | cursor: pointer; 164 | 165 | &:hover { 166 | background-color: #ccc; 167 | } 168 | } 169 | 170 | button.close { 171 | cursor: pointer; 172 | } 173 | -------------------------------------------------------------------------------- /examples/game-with-random.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Local vs Random with premoves 🆕

16 | 17 |
18 |
19 | 20 | 21 | 24 | 25 | 26 | 38 | 39 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/load-pgn-with-setup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Load a game from a PGN with [SetUp "1"] and FEN

16 |
17 |

The loaded PGN:

18 |

19 | 
20 | 21 | 22 | 25 | 26 | 27 | 39 | 40 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/sandbox/template-in-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 |
15 |

chess-console

16 |

Example: Template in page

17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 43 | 46 | 47 | 48 | 60 | 61 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/chess960-game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Load a game from a PGN with [SetUp "1"] and FEN

16 |
17 |

The loaded PGN:

18 |

19 | 
20 | 21 | 22 | 25 | 26 | 27 | 39 | 40 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/load-pgn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Load a game from a PGN

16 |
17 |

The loaded PGN:

18 |

19 | 
20 | 21 | 22 | 25 | 26 | 27 | 39 | 40 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/test-promotion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Test the PromotionDialog extension of cm-chessboard

16 |
17 |

The loaded PGN:

18 |

19 | 
20 | 21 | 22 | 25 | 26 | 27 | 39 | 40 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /examples/different-style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chess Console 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

chess-console

15 |

Local vs Random in a different style

16 |
17 |
18 | 19 | 22 | 23 | 24 | 36 | 37 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/components/History.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Observe} from "cm-web-modules/src/observe/Observe.js" 8 | import {COLOR} from "cm-chess/src/Chess.js" 9 | import {DomUtils} from "cm-web-modules/src/utils/DomUtils.js" 10 | import {ChessRender} from "../tools/ChessRender.js" 11 | 12 | export class History { 13 | 14 | constructor(chessConsole, props) { 15 | this.context = chessConsole.componentContainers.left.querySelector(".chess-console-history") 16 | this.chessConsole = chessConsole 17 | this.element = document.createElement("div") 18 | this.element.setAttribute("class", "history") 19 | this.context.appendChild(this.element) 20 | this.props = { 21 | notationType: "figures", 22 | makeClickable: true 23 | } 24 | Object.assign(this.props, props) 25 | this.chessConsole.state.observeChess(() => { 26 | this.redraw() 27 | }) 28 | Observe.property(chessConsole.state, "plyViewed", () => { 29 | this.redraw() 30 | }) 31 | if(this.props.makeClickable) { 32 | this.addClickEvents() 33 | } 34 | this.i18n = chessConsole.i18n 35 | this.i18n.load({ 36 | "de": { 37 | "game_history": "Spielnotation" 38 | }, 39 | "en": { 40 | "game_history": "Game notation" 41 | } 42 | }).then(() => { 43 | this.redraw() 44 | }) 45 | } 46 | 47 | addClickEvents() { 48 | this.clickHandler = DomUtils.delegate(this.element, "click", ".ply", (event) => { 49 | const ply = parseInt(event.target.getAttribute("data-ply"), 10) 50 | if(ply <= this.chessConsole.state.chess.history().length) { 51 | this.chessConsole.state.plyViewed = ply 52 | } 53 | }) 54 | this.element.classList.add("clickable") 55 | } 56 | 57 | removeClickEvents() { 58 | this.clickHandler.remove() 59 | this.element.classList.remove("clickable") 60 | } 61 | 62 | redraw() { 63 | window.clearTimeout(this.redrawDebounce) 64 | this.redrawDebounce = setTimeout(() => { 65 | const history = this.chessConsole.state.chess.history() 66 | let sanWhite 67 | let sanBlack 68 | let output = "" 69 | let i 70 | let rowClass = "" 71 | let whiteClass = "" 72 | let blackClass = "" 73 | for (i = 0; i < history.length; i += 2) { 74 | const moveWhite = history[i] 75 | if (moveWhite) { 76 | sanWhite = ChessRender.san(moveWhite.san, COLOR.white, this.chessConsole.i18n.lang, this.props.notationType, this.chessConsole.props.figures) 77 | } 78 | const moveBlack = history[i + 1] 79 | if (moveBlack) { 80 | sanBlack = ChessRender.san(moveBlack.san, COLOR.black, this.chessConsole.i18n.lang, this.props.notationType, this.chessConsole.props.figures) 81 | } else { 82 | sanBlack = "" 83 | } 84 | if (this.chessConsole.state.plyViewed < i + 1) { 85 | whiteClass = "text-muted" 86 | } 87 | if(this.chessConsole.state.plyViewed === i + 1) { 88 | whiteClass = "active" 89 | } 90 | if (this.chessConsole.state.plyViewed < i + 2) { 91 | blackClass = "text-muted" 92 | } 93 | if(this.chessConsole.state.plyViewed === i + 2) { 94 | blackClass = "active" 95 | } 96 | output += "" + (i / 2 + 1) + "." + sanWhite + "" + sanBlack + "" 97 | } 98 | this.element.innerHTML = "

" + this.i18n.t("game_history") + "

" + 99 | "" + output + "
" 100 | if (this.chessConsole.state.plyViewed > 0) { 101 | const $ply = $(this.element).find('.ply' + this.chessConsole.state.plyViewed) 102 | if ($ply.position()) { 103 | this.element.scrollTop = 0 104 | this.element.scrollTop = ($ply.position().top - 68) 105 | } 106 | } 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/components/CapturedPieces.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Observe} from "cm-web-modules/src/observe/Observe.js" 8 | import {PIECES} from "cm-chess/src/Chess.js" 9 | import {DomUtils} from "cm-web-modules/src/utils/DomUtils.js" 10 | 11 | const zeroWithSpace = "​" 12 | 13 | export class CapturedPieces { 14 | 15 | constructor(chessConsole) { 16 | this.chessConsole = chessConsole 17 | this.element = document.createElement("div") 18 | this.element.setAttribute("class", "captured-pieces") 19 | this.chessConsole.componentContainers.left.querySelector(".chess-console-captured").appendChild(this.element) 20 | this.chessConsole.state.observeChess(() => { 21 | this.redraw() 22 | }) 23 | Observe.property(this.chessConsole.state, "plyViewed", () => { 24 | this.redraw() 25 | }) 26 | Observe.property(this.chessConsole.state, "orientation", () => { 27 | this.redraw() 28 | }) 29 | this.i18n = chessConsole.i18n 30 | this.i18n.load({ 31 | "de": { 32 | "captured_pieces": "Geschlagene Figuren" 33 | }, 34 | "en": { 35 | "captured_pieces": "Captured pieces" 36 | } 37 | }).then(() => { 38 | this.redraw() 39 | }) 40 | DomUtils.delegate(this.element, "click", ".piece", (event) => { 41 | const ply = event.target.getAttribute("data-ply") 42 | this.chessConsole.state.plyViewed = parseInt(ply, 10) 43 | }) 44 | } 45 | 46 | redraw() { 47 | window.clearTimeout(this.redrawDebounce) 48 | this.redrawDebounce = setTimeout(() => { 49 | const capturedPiecesWhite = [] 50 | const capturedPiecesWhiteAfterPlyViewed = [] 51 | const capturedPiecesBlack = [] 52 | const capturedPiecesBlackAfterPlyViewed = [] 53 | 54 | const history = this.chessConsole.state.chess.history({verbose: true}) 55 | let pointsWhite = 0 56 | let pointsBlack = 0 57 | history.forEach((move, index) => { 58 | if (move.flags.indexOf("c") !== -1 || move.flags.indexOf("e") !== -1) { 59 | const pieceCaptured = move.captured.toUpperCase() 60 | if (move.color === "b") { 61 | const pieceHtml = `` + this.chessConsole.props.figures[pieceCaptured + "w"] + "" 62 | if (index < this.chessConsole.state.plyViewed) { 63 | capturedPiecesWhite.push(pieceHtml) 64 | } else { 65 | capturedPiecesWhiteAfterPlyViewed.push(pieceHtml) 66 | } 67 | pointsWhite += PIECES[pieceCaptured.toLowerCase()].value 68 | } else if (move.color === "w") { 69 | const pieceHtml = `` + this.chessConsole.props.figures[pieceCaptured + "b"] + "" 70 | if (index < this.chessConsole.state.plyViewed) { 71 | capturedPiecesBlack.push(pieceHtml) 72 | } else { 73 | capturedPiecesBlackAfterPlyViewed.push(pieceHtml) 74 | } 75 | pointsBlack += PIECES[pieceCaptured.toLowerCase()].value 76 | } 77 | } 78 | }) 79 | const outputWhite = this.renderPieces(capturedPiecesWhite, capturedPiecesWhiteAfterPlyViewed, pointsWhite) 80 | const outputBlack = this.renderPieces(capturedPiecesBlack, capturedPiecesBlackAfterPlyViewed, pointsBlack) 81 | this.element.innerHTML = "

" + this.i18n.t("captured_pieces") + "

" + 82 | (this.chessConsole.state.orientation === "w" ? outputWhite + outputBlack : outputBlack + outputWhite) 83 | }) 84 | } 85 | 86 | renderPieces(capturedPieces, capturedPiecesAfterPlyViewed, points) { 87 | let output = "
" 88 | if (capturedPieces.length > 0) { 89 | output += capturedPieces.join(zeroWithSpace) 90 | } 91 | if (capturedPiecesAfterPlyViewed.length > 0) { 92 | output += "" + capturedPiecesAfterPlyViewed.join(zeroWithSpace) + "" 93 | } 94 | output += " " + (points > 0 ? points : "") + "
" 95 | return output 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | chess-console is a JavaScript-based chess game client framework that creates browser-based, mobile-friendly, responsive chess GUIs using ES6 modules, cm-chessboard, and Bootstrap 5. 8 | 9 | ## Development Commands 10 | 11 | ### Running Examples 12 | Open any HTML file in the `examples/` directory directly in a browser (requires a local web server due to ES6 modules): 13 | - `examples/minimal.html` - Minimal setup without persistence 14 | - `examples/play-both-local.html` - Two local players 15 | - `examples/game-with-random.html` - Play against random moves engine 16 | - `examples/load-pgn.html` - Load games from PGN notation 17 | 18 | ### Testing 19 | No automated tests are currently configured (`npm test` is not implemented). 20 | 21 | ## Architecture 22 | 23 | ### Core Classes 24 | 25 | **ChessConsole** (`src/ChessConsole.js`) 26 | - Main orchestrator class that manages the entire game 27 | - Initializes with: context (DOM element), player, opponent, and optional props 28 | - Creates a MessageBroker for pub/sub communication between components 29 | - Manages game flow via `initGame()`, `newGame()`, and `nextMove()` 30 | - Delegates to players via `moveRequest()` → player responds via callback → `handleMoveResponse()` validates and processes 31 | - Key message topics defined in `CONSOLE_MESSAGE_TOPICS`: newGame, initGame, gameOver, moveRequest, legalMove, illegalMove, moveUndone, load 32 | 33 | **ChessConsoleState** (`src/ChessConsoleState.js`) 34 | - Holds game state: Chess instance (cm-chess), orientation, plyViewed 35 | - Provides `observeChess()` to watch for chess manipulation methods (move, load, undo, etc.) 36 | - Uses cm-web-modules Observe utility for reactive state tracking 37 | 38 | **ChessConsolePlayer** (`src/ChessConsolePlayer.js`) 39 | - Abstract base class for all player types 40 | - Subclasses must implement `moveRequest(fen, moveResponse)` method 41 | - moveResponse is a callback that receives {from, to, promotion} move object 42 | 43 | ### Player Types 44 | 45 | **LocalPlayer** (`src/players/LocalPlayer.js`) 46 | - Human player controlled via chessboard drag-and-drop 47 | - Handles move input events from cm-chessboard 48 | - Supports promotion dialogs via `validateMoveAndPromote()` 49 | - Implements premove support (optional via props.allowPremoves) 50 | - Enables/disables chessboard move input based on turn 51 | 52 | **RandomPlayer** (`src/players/RandomPlayer.js`) 53 | - Computer player that makes random legal moves 54 | - Uses chess.mjs to validate moves independently 55 | - Configurable delay (default 1000ms) via props.delay 56 | 57 | ### Component Architecture 58 | 59 | Components are instantiated after ChessConsole initialization and subscribe to message broker topics: 60 | 61 | **Board** (`src/components/Board.js`) 62 | - Wraps cm-chessboard with player name labels and markers 63 | - Manages visual state: position updates, legal move markers, check indicators, last move highlights 64 | - Subscribes to state changes via Observe.property 65 | - Configures extensions: PromotionDialog, Markers, Accessibility, AutoBorderNone 66 | - Marker types defined in `CONSOLE_MARKER_TYPE` 67 | 68 | **GameStateOutput** (`src/components/GameStateOutput.js`) 69 | - Displays current game state text (check, checkmate, stalemate, etc.) 70 | 71 | **History** (`src/components/History.js`) 72 | - Displays move history in algebraic notation 73 | 74 | **HistoryControl** (`src/components/HistoryControl.js`) 75 | - Navigation controls for viewing previous positions 76 | 77 | **CapturedPieces** (`src/components/CapturedPieces.js`) 78 | - Shows captured pieces for both sides 79 | 80 | **Sound** (`src/components/Sound.js`) 81 | - Plays sound effects for moves, captures, check, etc. 82 | 83 | **GameControl** (`src/components/GameControl/GameControl.js`) 84 | - Buttons for game actions (new game, undo, flip board, etc.) 85 | 86 | **Persistence** (`src/components/Persistence.js`) 87 | - Saves/loads game state to localStorage 88 | 89 | ### Data Flow 90 | 91 | Game initialization → `ChessConsole.initGame()` → publishes initGame message → components update 92 | Player turn → `ChessConsole.nextMove()` → `player.moveRequest(fen, moveResponse)` → player makes move → `moveResponse(move)` → `ChessConsole.handleMoveResponse()` → validates → publishes legalMove/illegalMove → updates state → triggers next move or gameOver 93 | 94 | State changes propagate via: 95 | 1. MessageBroker pub/sub for game events 96 | 2. Observe.property for reactive state tracking 97 | 3. ChessConsoleState.observeChess() for chess manipulation detection 98 | 99 | ### Key Dependencies 100 | 101 | - **cm-chessboard** - Interactive chessboard UI component 102 | - **cm-chess** - Chess logic and validation 103 | - **chess.mjs** - Alternative chess library used by players 104 | - **cm-pgn** - PGN parsing 105 | - **cm-web-modules** - Utilities (I18n, MessageBroker, Observe, DomUtils) 106 | - **bootstrap-show-modal** - Modal dialogs 107 | 108 | ### Component Registration 109 | 110 | Components can register themselves in `chessConsole.components` for cross-component access (e.g., Board registers itself so other components can access the promotion dialog). 111 | 112 | ### Initialization Pattern 113 | 114 | Both ChessConsole and Board expose an `initialized` Promise that resolves when async initialization (i18n loading, etc.) completes. Always await these before calling methods. 115 | 116 | Example: 117 | ```javascript 118 | const chessConsole = new ChessConsole(context, player, opponent, props) 119 | chessConsole.initialized.then((chessConsole) => { 120 | new Board(chessConsole, boardProps) 121 | chessConsole.newGame() 122 | }) 123 | ``` 124 | -------------------------------------------------------------------------------- /src/players/LocalPlayer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {COLOR, INPUT_EVENT_TYPE} from "cm-chessboard/src/Chessboard.js" 7 | import {Chess} from "chess.mjs/src/Chess.js" 8 | import {ChessConsolePlayer} from "../ChessConsolePlayer.js" 9 | import {CONSOLE_MESSAGE_TOPICS} from "../ChessConsole.js" 10 | 11 | export class LocalPlayer extends ChessConsolePlayer { 12 | 13 | constructor(chessConsole, name, props) { 14 | super(chessConsole, name) 15 | this.props = { 16 | allowPremoves: false 17 | } 18 | Object.assign(this.props, props) 19 | this.premoves = [] 20 | } 21 | 22 | /** 23 | * The return value returns, if valid or if is promotion. 24 | * The callback returns the move. 25 | */ 26 | validateMoveAndPromote(fen, squareFrom, squareTo, callback) { 27 | const tmpChess = new Chess(fen) 28 | let move = {from: squareFrom, to: squareTo} 29 | const moveResult = tmpChess.move(move) 30 | if (moveResult) { 31 | callback(moveResult) 32 | return true 33 | } else { // is a promotion? 34 | if (tmpChess.get(squareFrom) && tmpChess.get(squareFrom).type === "p") { 35 | const possibleMoves = tmpChess.moves({square: squareFrom, verbose: true}) 36 | for (let possibleMove of possibleMoves) { 37 | if (possibleMove.to === squareTo && possibleMove.promotion) { 38 | const chessboard = this.chessConsole.components.board.chessboard 39 | chessboard.showPromotionDialog(squareTo, tmpChess.turn(), (event) => { 40 | console.log(event) 41 | if (event.piece) { 42 | move.promotion = event.piece.charAt(1) 43 | console.log(move) 44 | callback(tmpChess.move(move)) 45 | } else { 46 | callback(null) 47 | } 48 | }) 49 | return true 50 | } 51 | } 52 | } 53 | } 54 | callback(null) 55 | return false 56 | } 57 | 58 | /** 59 | * Handles the events from cm-chessboard 60 | * 61 | * INPUT_EVENT_TYPE.moveDone 62 | * - validates Move, returns false, if not valid 63 | * - does promotion 64 | * - calls moveResponse() 65 | * 66 | * INPUT_EVENT_TYPE.moveStart 67 | * - allowed only the right color to move 68 | */ 69 | chessboardMoveInputCallback(event, moveResponse) { 70 | // if player can make move, make, if not store as premove 71 | // const boardFen = this.chessConsole.components.board.chessboard.getPosition() 72 | const gameFen = this.chessConsole.state.chess.fen() 73 | if (this.chessConsole.playerToMove() === this) { 74 | console.log("chessboardMoveInputCallback",event) 75 | if (event.type === INPUT_EVENT_TYPE.validateMoveInput) { 76 | return this.validateMoveAndPromote(gameFen, event.squareFrom, event.squareTo, (moveResult) => { 77 | let result 78 | if (moveResult) { // valid 79 | result = moveResponse(moveResult) 80 | } else { // not valid 81 | result = moveResponse({from: event.squareFrom, to: event.squareTo}) 82 | this.premoves = [] 83 | this.updatePremoveMarkers() 84 | } 85 | if (result) { 86 | if(!this.props.allowPremoves) { 87 | this.chessConsole.components.board.chessboard.disableMoveInput() 88 | } 89 | } 90 | }) 91 | } else if (event.type === INPUT_EVENT_TYPE.moveInputStarted) { 92 | if (this.chessConsole.state.plyViewed !== this.chessConsole.state.chess.plyCount()) { 93 | this.chessConsole.state.plyViewed = this.chessConsole.state.chess.plyCount() 94 | return false 95 | } else { 96 | const possibleMoves = this.chessConsole.state.chess.moves({square: event.square}) 97 | if(possibleMoves.length > 0) { 98 | return true 99 | } else { 100 | this.chessConsole.components.board.chessConsole.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.illegalMove, { 101 | move: { 102 | from: event.squareFrom 103 | } 104 | }) 105 | return false 106 | } 107 | } 108 | } 109 | } else { 110 | // premoves 111 | if (event.type === INPUT_EVENT_TYPE.validateMoveInput) { 112 | this.premoves.push(event) 113 | this.updatePremoveMarkers() 114 | } 115 | return true 116 | } 117 | } 118 | 119 | moveRequest(fen, moveResponse) { 120 | if(!this.contextMenuEvent) { 121 | this.chessConsole.components.board.chessboard.context.addEventListener("contextmenu", (event) => { 122 | event.preventDefault() 123 | if(this.premoves.length > 0) { 124 | this.resetBoardPosition() 125 | this.premoves = [] 126 | this.updatePremoveMarkers() 127 | } 128 | }) 129 | this.contextMenuEvent = true 130 | } 131 | const color = this.chessConsole.state.chess.turn() === 'w' ? COLOR.white : COLOR.black 132 | if (!this.chessConsole.state.chess.gameOver()) { 133 | if (this.premoves.length > 0) { 134 | // premove 135 | const eventFromPremovesQueue = this.premoves.shift() 136 | this.updatePremoveMarkers() 137 | setTimeout(() => { 138 | this.chessboardMoveInputCallback(eventFromPremovesQueue, moveResponse) 139 | }, 20) 140 | return true 141 | } 142 | // normal move 143 | if(!this.chessConsole.components.board.chessboard.isMoveInputEnabled()) { 144 | this.chessConsole.components.board.chessboard.enableMoveInput( 145 | (event) => { 146 | return this.chessboardMoveInputCallback(event, moveResponse) 147 | }, color 148 | ) 149 | } 150 | } 151 | } 152 | 153 | updatePremoveMarkers() { 154 | this.chessConsole.components.board.chessboard.removeMarkers(this.chessConsole.components.board.props.markers.premove) 155 | for (const premove of this.premoves) { 156 | this.chessConsole.components.board.chessboard.addMarker(this.chessConsole.components.board.props.markers.premove, premove.squareTo) 157 | } 158 | } 159 | 160 | resetBoardPosition() { 161 | this.chessConsole.components.board.chessboard.setPosition(this.chessConsole.state.chess.fen(), true) 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chess-console 2 | 3 | ChessConsole is a JavaScript-based chess game client framework that uses [cm-chessboard](https://github.com/shaack/cm-chessboard) and [Bootstrap](https://getbootstrap.com/) to create a browser-based, mobile-friendly, responsive chess game GUI. 4 | 5 | - **[Repository on GitHub](https://github.com/shaack/chess-console)** 6 | - **[Demo pages](https://shaack.com/projekte/chess-console)** 7 | 8 | ### chess-console is used in Production 9 | 10 | **[Used by chessmail as a framework for an online chess computer.](https://www.chessmail.eu/pages/chess-computer.html)** 11 | 12 | ## Component structure 13 | 14 | Because of its component architecture chess-console is expandable for all kind of chess pages. You may check out the [Stockfish Player](https://github.com/shaack/chess-console-stockfish) for chess-console, a fully functional online chess computer. 15 | 16 | ## Screenshot 17 | 18 | ![Example chess-console](https://shaack.com/projekte/assets/img/example_chess_console_checkmate.png) 19 | 20 | ## Installation 21 | 22 | ### Option 1: Download from GitHub 23 | 24 | 1. Clone the repository: 25 | ```sh 26 | git clone https://github.com/shaack/chess-console.git 27 | ``` 28 | 2. Navigate to the project directory and install dependencies: 29 | ```sh 30 | cd chess-console 31 | npm install 32 | ``` 33 | 34 | ### Option 2: Install via npm 35 | 36 | 1. Install the npm package: 37 | ```sh 38 | npm install chess-console 39 | ``` 40 | 41 | ## Usage 42 | 43 | ### Initialization 44 | 45 | To initialize a new ChessConsole instance, you need to provide the context, player, opponent, and optional properties. 46 | 47 | ```javascript 48 | import { ChessConsole } from 'chess-console'; 49 | 50 | const context = document.getElementById('chess-console'); 51 | // a LocalPlayer, that can be controlled by the user 52 | const player = { type: LocalPlayer, name: 'Player 1', props: {} }; 53 | // an engine player, that playes random moves 54 | const opponent = { type: RandomPlayer, name: 'Player 2', props: {} }; 55 | 56 | const chessConsole = new ChessConsole(context, player, opponent, { 57 | locale: 'en', 58 | playerColor: 'w', 59 | pgn: undefined, 60 | accessible: false 61 | }); 62 | ``` 63 | 64 | ## Running Examples 65 | 66 | Open any HTML file in the `examples/` directory directly in a browser (requires a local web server due to ES6 modules): 67 | - `examples/minimal.html` - Minimal setup without persistence 68 | - `examples/play-both-local.html` - Two local players 69 | - `examples/game-with-random.html` - Play against random moves engine 70 | - `examples/load-pgn.html` - Load games from PGN notation 71 | 72 | ## Architecture 73 | 74 | ### Core Classes 75 | 76 | **ChessConsole** (`src/ChessConsole.js`) 77 | - Main orchestrator class that manages the entire game 78 | - Initializes with: context (DOM element), player, opponent, and optional props 79 | - Creates a MessageBroker for pub/sub communication between components 80 | - Manages game flow via `initGame()`, `newGame()`, and `nextMove()` 81 | - Delegates to players via `moveRequest()` → player responds via callback → `handleMoveResponse()` validates and processes 82 | - Key message topics defined in `CONSOLE_MESSAGE_TOPICS`: newGame, initGame, gameOver, moveRequest, legalMove, illegalMove, moveUndone, load 83 | 84 | **ChessConsoleState** (`src/ChessConsoleState.js`) 85 | - Holds game state: Chess instance (cm-chess), orientation, plyViewed 86 | - Provides `observeChess()` to watch for chess manipulation methods (move, load, undo, etc.) 87 | - Uses cm-web-modules Observe utility for reactive state tracking 88 | 89 | **ChessConsolePlayer** (`src/ChessConsolePlayer.js`) 90 | - Abstract base class for all player types 91 | - Subclasses must implement `moveRequest(fen, moveResponse)` method 92 | - moveResponse is a callback that receives {from, to, promotion} move object 93 | 94 | ### Player Types 95 | 96 | **LocalPlayer** (`src/players/LocalPlayer.js`) 97 | - Human player controlled via chessboard drag-and-drop 98 | - Handles move input events from cm-chessboard 99 | - Supports promotion dialogs via `validateMoveAndPromote()` 100 | - Implements premove support (optional via props.allowPremoves) 101 | - Enables/disables chessboard move input based on turn 102 | 103 | **RandomPlayer** (`src/players/RandomPlayer.js`) 104 | - Computer player that makes random legal moves 105 | - Uses chess.mjs to validate moves independently 106 | - Configurable delay (default 1000ms) via props.delay 107 | 108 | ### Component Architecture 109 | 110 | Components are instantiated after ChessConsole initialization and subscribe to message broker topics: 111 | 112 | **Board** (`src/components/Board.js`) 113 | - Wraps cm-chessboard with player name labels and markers 114 | - Manages visual state: position updates, legal move markers, check indicators, last move highlights 115 | - Subscribes to state changes via Observe.property 116 | - Configures extensions: PromotionDialog, Markers, Accessibility, AutoBorderNone 117 | - Marker types defined in `CONSOLE_MARKER_TYPE` 118 | 119 | **GameStateOutput** (`src/components/GameStateOutput.js`) 120 | - Displays current game state text (check, checkmate, stalemate, etc.) 121 | 122 | **History** (`src/components/History.js`) 123 | - Displays move history in algebraic notation 124 | 125 | **HistoryControl** (`src/components/HistoryControl.js`) 126 | - Navigation controls for viewing previous positions 127 | 128 | **CapturedPieces** (`src/components/CapturedPieces.js`) 129 | - Shows captured pieces for both sides 130 | 131 | **Sound** (`src/components/Sound.js`) 132 | - Plays sound effects for moves, captures, check, etc. 133 | 134 | **GameControl** (`src/components/GameControl/GameControl.js`) 135 | - Buttons for game actions (new game, undo, flip board, etc.) 136 | 137 | **Persistence** (`src/components/Persistence.js`) 138 | - Saves/loads game state to localStorage 139 | 140 | ### Data Flow 141 | 142 | Game initialization → `ChessConsole.initGame()` → publishes initGame message → components update 143 | Player turn → `ChessConsole.nextMove()` → `player.moveRequest(fen, moveResponse)` → player makes move → `moveResponse(move)` → `ChessConsole.handleMoveResponse()` → validates → publishes legalMove/illegalMove → updates state → triggers next move or gameOver 144 | 145 | State changes propagate via: 146 | 1. MessageBroker pub/sub for game events 147 | 2. Observe.property for reactive state tracking 148 | 3. ChessConsoleState.observeChess() for chess manipulation detection 149 | 150 | ### Key Dependencies 151 | 152 | - **cm-chessboard** - Interactive chessboard UI component 153 | - **cm-chess** - Chess logic and validation 154 | - **chess.mjs** - Alternative chess library used by players 155 | - **cm-pgn** - PGN parsing 156 | - **cm-web-modules** - Utilities (I18n, MessageBroker, Observe, DomUtils) 157 | - **bootstrap-show-modal** - Modal dialogs 158 | 159 | ### Component Registration 160 | 161 | Components can register themselves in `chessConsole.components` for cross-component access (e.g., Board registers itself so other components can access the promotion dialog). 162 | 163 | ### Initialization Pattern 164 | 165 | Both ChessConsole and Board expose an `initialized` Promise that resolves when async initialization (i18n loading, etc.) completes. Always await these before calling methods. 166 | 167 | Example: 168 | ```javascript 169 | const chessConsole = new ChessConsole(context, player, opponent, props) 170 | chessConsole.initialized.then((chessConsole) => { 171 | new Board(chessConsole, boardProps) 172 | chessConsole.newGame() 173 | }) 174 | ``` 175 | 176 | 177 | ## Licenses 178 | 179 | Source code license: MIT,
180 | License for the Sounds: CC BY 4.0,
181 | License of the SVG pieces CC BY-SA 3.0. 182 | 183 | Copyright © [shaack.com](https://shaack.com). 184 | -------------------------------------------------------------------------------- /src/components/HistoryControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Observe} from "cm-web-modules/src/observe/Observe.js" 8 | import {COLOR} from "cm-chessboard/src/Chessboard.js" 9 | 10 | export class HistoryControl { 11 | constructor(chessConsole, props = {}) { 12 | this.context = chessConsole.componentContainers.controlButtons 13 | this.chessConsole = chessConsole 14 | const i18n = chessConsole.i18n 15 | this.props = { 16 | autoPlayDelay: 1500 17 | } 18 | Object.assign(this.props, props) 19 | i18n.load({ 20 | de: { 21 | "to_game_start": "Zum Spielstart", 22 | "one_move_back": "Ein Zug zurück", 23 | "one_move_forward": "Ein Zug weiter", 24 | "to_last_move": "Zum letzen Zug", 25 | "auto_run": "Automatisch abspielen", 26 | "turn_board": "Brett drehen" 27 | }, 28 | en: { 29 | "to_game_start": "To game start", 30 | "one_move_back": "One move back", 31 | "one_move_forward": "One move forward", 32 | "to_last_move": "To last move", 33 | "auto_run": "Auto play", 34 | "turn_board": "Turn board" 35 | } 36 | }).then(() => { 37 | 38 | this.$btnFirst = $(``) 39 | this.$btnBack = $(``) 40 | this.$btnForward = $(``) 41 | this.$btnLast = $(``) 42 | this.$btnAutoplay = $(``) 43 | this.$btnOrientation = $(``) 44 | 45 | this.context.appendChild(this.$btnFirst[0]) 46 | this.context.appendChild(this.$btnBack[0]) 47 | this.context.appendChild(this.$btnForward[0]) 48 | this.context.appendChild(this.$btnLast[0]) 49 | this.context.appendChild(this.$btnAutoplay[0]) 50 | this.context.appendChild(this.$btnOrientation[0]) 51 | 52 | this.chessConsole.state.observeChess(() => { 53 | this.setButtonStates() 54 | }) 55 | Observe.property(this.chessConsole.state, "plyViewed", () => { 56 | this.setButtonStates() 57 | }) 58 | Observe.property(this.chessConsole.state, "orientation", () => { 59 | if (this.chessConsole.state.orientation !== this.chessConsole.props.playerColor) { 60 | this.$btnOrientation.addClass("btn-active") // todo 61 | } else { 62 | this.$btnOrientation.removeClass("btn-active") // todo 63 | } 64 | }) 65 | this.$btnFirst.click(() => { 66 | this.chessConsole.state.plyViewed = 0 67 | this.resetAutoPlay() 68 | }) 69 | this.$btnBack.click(() => { 70 | this.chessConsole.state.plyViewed-- 71 | this.resetAutoPlay() 72 | }) 73 | this.$btnForward.click(() => { 74 | this.chessConsole.state.plyViewed++ 75 | this.resetAutoPlay() 76 | }) 77 | this.$btnLast.click(() => { 78 | this.chessConsole.state.plyViewed = this.chessConsole.state.chess.plyCount() 79 | this.resetAutoPlay() 80 | }) 81 | this.$btnOrientation.click(() => { 82 | this.chessConsole.state.orientation = this.chessConsole.state.orientation === COLOR.white ? COLOR.black : COLOR.white 83 | }) 84 | this.$btnAutoplay.click(() => { 85 | if (this.autoplay) { 86 | clearInterval(this.autoplay) 87 | this.autoplay = null 88 | } else { 89 | this.chessConsole.state.plyViewed++ 90 | this.autoplay = setInterval(this.autoPlayMove.bind(this), this.props.autoPlayDelay) 91 | } 92 | this.updatePlayIcon() 93 | }) 94 | document.addEventListener('keydown', (e) => { 95 | if (e.metaKey || e.ctrlKey || e.altKey) { 96 | return 97 | } 98 | if (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA") { 99 | return 100 | } 101 | if (e.key === "ArrowLeft" || e.key === "j") { 102 | if (this.chessConsole.state.plyViewed > 0) { 103 | this.chessConsole.state.plyViewed-- 104 | this.resetAutoPlay() 105 | e.preventDefault() 106 | } 107 | } else if (e.key === "ArrowRight" || e.key === "k") { 108 | if (this.chessConsole.state.plyViewed < this.chessConsole.state.chess.plyCount()) { 109 | this.chessConsole.state.plyViewed++ 110 | this.resetAutoPlay() 111 | e.preventDefault() 112 | } 113 | } else if (e.key === "ArrowUp") { 114 | this.chessConsole.state.plyViewed = 0 115 | this.resetAutoPlay() 116 | e.preventDefault() 117 | } else if (e.key === "ArrowDown") { 118 | this.chessConsole.state.plyViewed = this.chessConsole.state.chess.plyCount() 119 | this.resetAutoPlay() 120 | e.preventDefault() 121 | } else if (e.key === "f") { 122 | this.chessConsole.state.orientation = this.chessConsole.state.orientation === COLOR.white ? COLOR.black : COLOR.white 123 | e.preventDefault() 124 | } else if (e.key === " ") { 125 | if (this.autoplay) { 126 | clearInterval(this.autoplay) 127 | this.autoplay = null 128 | } else { 129 | if (this.chessConsole.state.plyViewed < this.chessConsole.state.chess.plyCount()) { 130 | this.chessConsole.state.plyViewed++ 131 | this.autoplay = setInterval(this.autoPlayMove.bind(this), this.props.autoPlayDelay) 132 | } 133 | } 134 | this.updatePlayIcon() 135 | e.preventDefault() 136 | } 137 | }) 138 | this.setButtonStates() 139 | }) 140 | } 141 | 142 | resetAutoPlay() { 143 | if (this.autoplay) { 144 | clearInterval(this.autoplay) 145 | this.autoplay = setInterval(this.autoPlayMove.bind(this), this.props.autoPlayDelay) 146 | } 147 | } 148 | 149 | autoPlayMove() { 150 | if (this.chessConsole.state.plyViewed >= this.chessConsole.state.chess.plyCount()) { 151 | clearInterval(this.autoplay) 152 | this.autoplay = null 153 | this.updatePlayIcon() 154 | } else { 155 | this.chessConsole.state.plyViewed++ 156 | if (this.chessConsole.state.plyViewed >= this.chessConsole.state.chess.plyCount()) { 157 | clearInterval(this.autoplay) 158 | this.autoplay = null 159 | this.updatePlayIcon() 160 | } 161 | } 162 | } 163 | 164 | updatePlayIcon() { 165 | const $playIcon = this.$btnAutoplay.find(".fa-play") 166 | const $stopIcon = this.$btnAutoplay.find(".fa-stop") 167 | if (this.autoplay) { 168 | $playIcon.hide() 169 | $stopIcon.show() 170 | } else { 171 | $playIcon.show() 172 | $stopIcon.hide() 173 | } 174 | } 175 | 176 | setButtonStates() { 177 | window.clearTimeout(this.redrawDebounce) 178 | this.redrawDebounce = setTimeout(() => { 179 | if (this.chessConsole.state.plyViewed > 0) { 180 | this.$btnFirst.prop('disabled', false) 181 | this.$btnBack.prop('disabled', false) 182 | } else { 183 | this.$btnFirst.prop('disabled', true) 184 | this.$btnBack.prop('disabled', true) 185 | } 186 | if (this.chessConsole.state.plyViewed < this.chessConsole.state.chess.plyCount()) { 187 | this.$btnLast.prop('disabled', false) 188 | this.$btnForward.prop('disabled', false) 189 | this.$btnAutoplay.prop('disabled', false) 190 | } else { 191 | this.$btnLast.prop('disabled', true) 192 | this.$btnForward.prop('disabled', true) 193 | this.$btnAutoplay.prop('disabled', true) 194 | } 195 | }) 196 | this.updatePlayIcon() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/ChessConsole.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {FEN, GAME_VARIANT} from "cm-chess/src/Chess.js" 8 | import {COLOR} from "cm-chessboard/src/Chessboard.js" 9 | import {I18n} from "cm-web-modules/src/i18n/I18n.js" 10 | import {MessageBroker} from "cm-web-modules/src/message-broker/MessageBroker.js" 11 | import {piecesTranslations} from "cm-chessboard/src/extensions/accessibility/I18n.js" 12 | import {ChessConsoleState} from "./ChessConsoleState.js" 13 | import {DomUtils} from "cm-web-modules/src/utils/DomUtils.js" 14 | 15 | export const CONSOLE_MESSAGE_TOPICS = { 16 | newGame: "game/new", // if a new game was startet 17 | initGame: "game/init", // after a page reload and when a new game was started 18 | gameOver: "game/over", 19 | moveRequest: "game/moveRequest", 20 | legalMove: "game/move/legal", 21 | illegalMove: "game/move/illegal", 22 | moveUndone: "game/move/undone", // mainly for sound 23 | load: "game/load" 24 | } 25 | 26 | export class ChessConsole { 27 | 28 | constructor(context, player, opponent, props = {}, 29 | state = new ChessConsoleState(props)) { 30 | this.context = context 31 | this.state = state 32 | this.props = { 33 | locale: navigator.language, // locale for i18n 34 | playerColor: COLOR.white, // the players color (color at bottom) 35 | gameVariant: GAME_VARIANT.standard, 36 | pgn: undefined, // initial pgn, can contain header and history 37 | accessible: false // render additional information to improve the usage for persons using screen readers 38 | } 39 | if (!this.props.figures) { 40 | this.props.figures = { 41 | Rw: '', 42 | Nw: '', 43 | Bw: '', 44 | Qw: '', 45 | Kw: '', 46 | Pw: '', 47 | Rb: '', 48 | Nb: '', 49 | Bb: '', 50 | Qb: '', 51 | Kb: '', 52 | Pb: '' 53 | } 54 | } 55 | const colSets = { 56 | consoleGame: "col-xl-7 order-xl-2 col-lg-8 order-lg-1 order-md-1 col-md-12", 57 | consoleRight: "col-xl-3 order-xl-3 col-lg-4 order-lg-2 col-md-8 order-md-3", 58 | consoleLeft: "col-xl-2 order-xl-1 order-lg-3 col-lg-12 col-md-4 order-md-2" 59 | } 60 | this.initialized = new Promise((resolve => { 61 | this.i18n = new I18n({locale: props.locale}) 62 | this.i18n.load({ 63 | de: { 64 | ok: "OK", 65 | cancel: "Abbrechen", 66 | }, 67 | en: { 68 | ok: "OK", 69 | cancel: "Cancel", 70 | } 71 | }).then(() => { 72 | this.i18n.load(piecesTranslations).then(() => { 73 | resolve(this) 74 | }) 75 | }) 76 | })) 77 | /** 78 | * @deprecated 2023-04-11 use this.initialized instead 79 | */ 80 | this.initialization = this.initialized 81 | if (!this.props.template) { 82 | this.props.template = ` 83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
` 102 | } 103 | Object.assign(this.props, props) 104 | this.messageBroker = new MessageBroker() 105 | const innerHTMLElement = DomUtils.createElement(this.context.innerHTML) 106 | if (!(innerHTMLElement instanceof Element) || 107 | (!innerHTMLElement.querySelector(".chess-console") && 108 | !innerHTMLElement.classList.contains("chess-console"))) { 109 | this.context.innerHTML = this.props.template 110 | } 111 | this.componentContainers = { 112 | center: this.context.querySelector(".chess-console-center"), 113 | left: this.context.querySelector(".chess-console-left"), 114 | right: this.context.querySelector(".chess-console-right"), 115 | board: this.context.querySelector(".chess-console-board"), 116 | controlButtons: this.context.querySelector(".control-buttons"), 117 | notifications: this.context.querySelector(".chess-console-notifications") 118 | } 119 | this.components = { 120 | // put here components, which want to be accessible from other components 121 | } 122 | this.player = new player.type(this, player.name, player.props) 123 | this.opponent = new opponent.type(this, opponent.name, opponent.props) 124 | 125 | /** @var this.persistence Persistence */ 126 | this.persistence = undefined 127 | } 128 | 129 | initGame(props = {}, requestNextMove = true) { 130 | Object.assign(this.props, props) 131 | this.state.orientation = this.props.playerColor 132 | if (props.pgn) { 133 | this.state.chess.loadPgn(props.pgn, true) 134 | this.props.gameVariant = this.state.chess.props.gameVariant 135 | this.state.plyViewed = this.state.chess.plyCount() 136 | } else { 137 | this.state.chess.load(FEN.start) 138 | this.state.plyViewed = 0 139 | } 140 | if (requestNextMove) { 141 | this.nextMove() 142 | } 143 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.initGame, {props: props}) 144 | } 145 | 146 | newGame(props = {}) { 147 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.newGame, {props: props}) 148 | this.initGame(props) 149 | if (this.components.board.chessboard) { 150 | this.components.board.chessboard.disableMoveInput() 151 | } 152 | } 153 | 154 | playerWhite() { 155 | return this.props.playerColor === COLOR.white ? this.player : this.opponent 156 | } 157 | 158 | playerBlack() { 159 | return this.props.playerColor === COLOR.white ? this.opponent : this.player 160 | } 161 | 162 | playerToMove() { 163 | if (this.state.chess.gameOver()) { 164 | return null 165 | } else { 166 | if (this.state.chess.turn() === "w") { 167 | return this.playerWhite() 168 | } else { 169 | return this.playerBlack() 170 | } 171 | } 172 | } 173 | 174 | /* 175 | * - calls `moveRequest()` in next player 176 | */ 177 | nextMove() { 178 | const playerToMove = this.playerToMove() 179 | if (playerToMove) { 180 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.moveRequest, {playerToMove: playerToMove}) 181 | setTimeout(() => { 182 | playerToMove.moveRequest(this.state.chess.fen(), (move) => { 183 | return this.handleMoveResponse(move) 184 | }) 185 | }) 186 | } 187 | } 188 | 189 | /* 190 | * - validates move 191 | * - requests nextMove 192 | */ 193 | handleMoveResponse(move) { 194 | const playerMoved = this.playerToMove() 195 | const moveResult = this.state.chess.move(move) 196 | if (!moveResult) { 197 | if (this.props.debug) { 198 | console.warn("illegalMove", this.state.chess, move) 199 | } 200 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.illegalMove, { 201 | playerMoved: playerMoved, 202 | move: move 203 | }) 204 | return moveResult 205 | } 206 | if (this.state.plyViewed === this.state.chess.plyCount() - 1) { 207 | this.state.plyViewed++ 208 | } 209 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.legalMove, { 210 | playerMoved: playerMoved, 211 | move: move, 212 | moveResult: moveResult 213 | }) 214 | if (!this.state.chess.gameOver()) { 215 | this.nextMove() 216 | } else { 217 | let wonColor = null 218 | if (this.state.chess.inCheckmate()) { 219 | wonColor = (this.state.chess.turn() === COLOR.white) ? COLOR.black : COLOR.white 220 | } 221 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.gameOver, {wonColor: wonColor}) 222 | } 223 | return moveResult 224 | } 225 | 226 | undoMove() { 227 | this.components.board.chessboard.disableMoveInput() 228 | this.state.chess.undo() 229 | if (this.playerToMove() !== this.player) { 230 | this.state.chess.undo() 231 | } 232 | if (this.state.plyViewed > this.state.chess.plyCount()) { 233 | this.state.plyViewed = this.state.chess.plyCount() 234 | } 235 | this.messageBroker.publish(CONSOLE_MESSAGE_TOPICS.moveUndone) 236 | this.nextMove() 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /assets/styles/screen.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "screen.css", 4 | "sources": [ 5 | "screen.scss", 6 | "_config.scss", 7 | "_chess-console.scss", 8 | "../../node_modules/cm-chessboard/assets/chessboard.scss", 9 | "../../node_modules/cm-chessboard/assets/_chessboard-theme.scss", 10 | "../../node_modules/cm-chessboard/assets/extensions/promotion-dialog/promotion-dialog.scss", 11 | "_markers.scss" 12 | ], 13 | "names": [], 14 | "mappings": "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" 15 | } -------------------------------------------------------------------------------- /src/components/Board.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/chess-console 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Chessboard, COLOR, INPUT_EVENT_TYPE} from "cm-chessboard/src/Chessboard.js" 8 | import {FEN} from "cm-chessboard/src/model/Position.js" 9 | import {Markers} from "cm-chessboard/src/extensions/markers/Markers.js" 10 | import {Observe} from "cm-web-modules/src/observe/Observe.js" 11 | import {CoreUtils} from "cm-web-modules/src/utils/CoreUtils.js" 12 | import {DomUtils} from "cm-web-modules/src/utils/DomUtils.js" 13 | import {PromotionDialog} from "cm-chessboard/src/extensions/promotion-dialog/PromotionDialog.js" 14 | import {Accessibility} from "cm-chessboard/src/extensions/accessibility/Accessibility.js" 15 | import {CONSOLE_MESSAGE_TOPICS} from "../ChessConsole.js" 16 | import {AutoBorderNone} from "cm-chessboard/src/extensions/auto-border-none/AutoBorderNone.js" 17 | 18 | export const CONSOLE_MARKER_TYPE = { 19 | moveInput: {class: "marker-frame", slice: "markerFrame"}, 20 | check: {class: "marker-circle-danger", slice: "markerCircle"}, 21 | wrongMove: {class: "marker-frame-danger", slice: "markerFrame"}, 22 | premove: {class: "marker-frame-primary", slice: "markerFrame"}, 23 | legalMove: {class: "marker-dot", slice: "markerDot"}, 24 | legalMoveCapture: {class: "marker-bevel", slice: "markerBevel"} 25 | } 26 | 27 | export class Board { 28 | 29 | constructor(chessConsole, props = {}) { 30 | this.context = chessConsole.componentContainers.board 31 | chessConsole.components.board = this // register board component, to allow access to the promotion dialog 32 | this.initialized = new Promise((resolve) => { 33 | this.i18n = chessConsole.i18n 34 | this.i18n.load({ 35 | de: { 36 | chessBoard: "Schachbrett" 37 | }, 38 | en: { 39 | chessBoard: "Chess Board" 40 | } 41 | }).then(() => { 42 | this.chessConsole = chessConsole 43 | this.elements = { 44 | playerTop: document.createElement("div"), 45 | playerBottom: document.createElement("div"), 46 | chessboard: document.createElement("div") 47 | } 48 | this.elements.playerTop.setAttribute("class", "player top") 49 | this.elements.playerTop.innerHTML = " " 50 | this.elements.playerBottom.setAttribute("class", "player bottom") 51 | this.elements.playerBottom.innerHTML = " " 52 | this.elements.chessboard.setAttribute("class", "chessboard") 53 | this.context.appendChild(DomUtils.createElement("

" + this.i18n.t("chessBoard") + "

")) 54 | this.context.appendChild(this.elements.playerTop) 55 | this.context.appendChild(this.elements.chessboard) 56 | this.context.appendChild(this.elements.playerBottom) 57 | this.chessConsole.state.observeChess((params) => { 58 | let animated = true 59 | if (params.functionName === "load_pgn") { 60 | animated = false 61 | } 62 | this.setPositionOfPlyViewed(animated) 63 | this.markLastMove() 64 | }) 65 | Observe.property(this.chessConsole.state, "plyViewed", (props) => { 66 | this.setPositionOfPlyViewed(props.oldValue !== undefined) 67 | this.markLastMove() 68 | }) 69 | this.props = { 70 | position: FEN.empty, 71 | orientation: chessConsole.state.orientation, 72 | assetsUrl: undefined, 73 | markLegalMoves: true, 74 | style: { 75 | aspectRatio: 0.98 76 | }, 77 | accessibility: { 78 | active: true, // turn accessibility on or off 79 | brailleNotationInAlt: true, // show the braille notation of the position in the alt attribute of the SVG image 80 | movePieceForm: true, // display a form to move a piece (from, to, move) 81 | boardAsTable: true, // display the board additionally as HTML table 82 | piecesAsList: true, // display the pieces additionally as List 83 | visuallyHidden: true // hide all those extra outputs visually but keep them accessible for screen readers and braille displays 84 | }, 85 | markers: { 86 | moveInput: CONSOLE_MARKER_TYPE.moveInput, 87 | check: CONSOLE_MARKER_TYPE.check, 88 | wrongMove: CONSOLE_MARKER_TYPE.wrongMove, 89 | premove: CONSOLE_MARKER_TYPE.premove, 90 | legalMove: CONSOLE_MARKER_TYPE.legalMove, 91 | legalMoveCapture: CONSOLE_MARKER_TYPE.legalMoveCapture 92 | }, 93 | extensions: [{class: PromotionDialog}, { 94 | class: ChessConsoleMarkers, props: { 95 | board: this, 96 | autoMarkers: props.markers && props.markers.moveInput ? {...props.markers.moveInput} : {...CONSOLE_MARKER_TYPE.moveInput} 97 | } 98 | }, {class: AutoBorderNone, props: { borderNoneBelow: 580 }}] 99 | } 100 | CoreUtils.mergeObjects(this.props, props) 101 | if (this.props.accessibility.active) { 102 | this.props.extensions.push({ 103 | class: Accessibility, props: this.props.accessibility 104 | }) 105 | } 106 | this.chessboard = new Chessboard(this.elements.chessboard, this.props) 107 | Observe.property(chessConsole.state, "orientation", () => { 108 | this.setPlayerNames() 109 | this.chessboard.setOrientation(chessConsole.state.orientation).then(() => { 110 | this.markPlayerToMove() 111 | }) 112 | }) 113 | Observe.property(chessConsole.player, "name", () => { 114 | this.setPlayerNames() 115 | }) 116 | Observe.property(chessConsole.opponent, "name", () => { 117 | this.setPlayerNames() 118 | }) 119 | chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.moveRequest, () => { 120 | this.markPlayerToMove() 121 | }) 122 | this.chessConsole.messageBroker.subscribe(CONSOLE_MESSAGE_TOPICS.illegalMove, (message) => { 123 | this.chessboard.removeMarkers(this.props.markers.wrongMove) 124 | clearTimeout(this.removeMarkersTimeout) 125 | if (message.move.from) { 126 | this.chessboard.addMarker(this.props.markers.wrongMove, message.move.from) 127 | } else { 128 | console.warn("illegalMove without `message.move.from`") 129 | } 130 | if (message.move.to) { 131 | this.chessboard.addMarker(this.props.markers.wrongMove, message.move.to) 132 | } 133 | this.removeMarkersTimeout = setTimeout(() => { 134 | this.chessboard.removeMarkers(this.props.markers.wrongMove) 135 | }, 500) 136 | }) 137 | this.setPositionOfPlyViewed(false) 138 | this.setPlayerNames() 139 | this.markPlayerToMove() 140 | this.markLastMove() 141 | 142 | resolve(this) 143 | }) 144 | }) 145 | /** 146 | * @deprecated 2023-04-11 use `this.initialized` instead 147 | */ 148 | this.initialization = this.initialized 149 | } 150 | 151 | setPositionOfPlyViewed(animated = true) { 152 | clearTimeout(this.setPositionOfPlyViewedDebounced) 153 | this.setPositionOfPlyViewedDebounced = setTimeout(() => { 154 | const to = this.chessConsole.state.chess.fenOfPly(this.chessConsole.state.plyViewed) 155 | this.chessboard.setPosition(to, animated) 156 | }) 157 | } 158 | 159 | markLastMove() { 160 | window.clearTimeout(this.markLastMoveDebounce) 161 | this.markLastMoveDebounce = setTimeout(() => { 162 | this.chessboard.removeMarkers(this.props.markers.moveInput) 163 | this.chessboard.removeMarkers(this.props.markers.check) 164 | if (this.chessConsole.state.plyViewed > 0) { 165 | const lastMove = this.chessConsole.state.chess.history()[this.chessConsole.state.plyViewed - 1] 166 | if (lastMove) { 167 | this.chessboard.addMarker(this.props.markers.moveInput, lastMove.from) 168 | this.chessboard.addMarker(this.props.markers.moveInput, lastMove.to) 169 | if (this.chessConsole.state.chess.inCheck(lastMove) || this.chessConsole.state.chess.inCheckmate(lastMove)) { 170 | const kingSquare = this.chessConsole.state.chess.pieces("k", this.chessConsole.state.chess.turn(lastMove), lastMove)[0] 171 | this.chessboard.addMarker(this.props.markers.check, kingSquare.square) 172 | } 173 | } 174 | } 175 | }) 176 | } 177 | 178 | setPlayerNames() { 179 | window.clearTimeout(this.setPlayerNamesDebounce) 180 | this.setPlayerNamesDebounce = setTimeout(() => { 181 | if (this.chessConsole.props.playerColor === this.chessConsole.state.orientation) { 182 | this.elements.playerBottom.innerHTML = this.chessConsole.player.name 183 | this.elements.playerTop.innerHTML = this.chessConsole.opponent.name 184 | } else { 185 | this.elements.playerBottom.innerHTML = this.chessConsole.opponent.name 186 | this.elements.playerTop.innerHTML = this.chessConsole.player.name 187 | } 188 | }) 189 | } 190 | 191 | markPlayerToMove() { 192 | clearTimeout(this.markPlayerToMoveDebounce) 193 | this.markPlayerToMoveDebounce = setTimeout(() => { 194 | this.elements.playerTop.classList.remove("to-move") 195 | this.elements.playerBottom.classList.remove("to-move") 196 | this.elements.playerTop.classList.remove("not-to-move") 197 | this.elements.playerBottom.classList.remove("not-to-move") 198 | const playerMove = this.chessConsole.playerToMove() 199 | if ( 200 | this.chessConsole.state.orientation === COLOR.white && 201 | playerMove === this.chessConsole.playerWhite() || 202 | this.chessConsole.state.orientation === COLOR.black && 203 | playerMove === this.chessConsole.playerBlack()) { 204 | this.elements.playerBottom.classList.add("to-move") 205 | this.elements.playerTop.classList.add("not-to-move") 206 | } else { 207 | this.elements.playerTop.classList.add("to-move") 208 | this.elements.playerBottom.classList.add("not-to-move") 209 | } 210 | }, 10) 211 | } 212 | 213 | } 214 | 215 | class ChessConsoleMarkers extends Markers { 216 | drawAutoMarkers(event) { 217 | clearTimeout(this.drawAutoMarkersDebounced) 218 | this.drawAutoMarkersDebounced = setTimeout(() => { 219 | this.removeMarkers(this.props.autoMarkers) 220 | const board = this.props.board 221 | const moves = this.props.board.chessConsole.state.chess.moves({square: event.square, verbose: true}) 222 | if (board.props.markLegalMoves) { 223 | if (event.type === INPUT_EVENT_TYPE.moveInputStarted || 224 | event.type === INPUT_EVENT_TYPE.validateMoveInput || 225 | event.type === INPUT_EVENT_TYPE.moveInputCanceled || 226 | event.type === INPUT_EVENT_TYPE.moveInputFinished) { 227 | event.chessboard.removeMarkers(board.props.markers.legalMove) 228 | event.chessboard.removeMarkers(board.props.markers.legalMoveCapture) 229 | } 230 | if (event.type === INPUT_EVENT_TYPE.moveInputStarted) { 231 | for (const move of moves) { // draw dots on possible squares 232 | if (move.promotion && move.promotion !== "q") { 233 | continue 234 | } 235 | if (event.chessboard.getPiece(move.to)) { 236 | event.chessboard.addMarker(board.props.markers.legalMoveCapture, move.to) 237 | } else { 238 | event.chessboard.addMarker(board.props.markers.legalMove, move.to) 239 | } 240 | } 241 | } 242 | } 243 | if (event.type === INPUT_EVENT_TYPE.moveInputStarted) { 244 | if (event.moveInputCallbackResult) { 245 | this.addMarker(this.props.autoMarkers, event.squareFrom) 246 | } 247 | } else if (event.type === INPUT_EVENT_TYPE.movingOverSquare) { 248 | this.addMarker(this.props.autoMarkers, event.squareFrom) 249 | if (event.squareTo) { 250 | this.addMarker(this.props.autoMarkers, event.squareTo) 251 | } 252 | } 253 | } 254 | ) 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /assets/styles/screen.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .buttons-grid { 3 | display: grid; 4 | justify-content: left; 5 | grid-template-columns: repeat(auto-fill, 3rem); 6 | gap: 0.5rem; } 7 | .buttons-grid .btn-link.text-black:active, .buttons-grid .btn-link.text-black:focus { 8 | box-shadow: none !important; } 9 | .buttons-grid .btn[disabled] { 10 | opacity: 0.4; } 11 | 12 | .chess-console .fa-figure-white { 13 | color: white; 14 | text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black, -1px -1px 1px black; } 15 | 16 | .chess-console .fa-figure-black { 17 | color: black; 18 | text-shadow: 1px 1px 1px white, 1px -1px 1px white, -1px 1px 1px white, -1px -1px 1px white; 19 | transform: scale(1.14); } 20 | 21 | .chess-console .chess-console-right, .chess-console .chess-console-left { 22 | margin-top: 20px; } 23 | 24 | .chess-console .chess-console-board .chessboard { 25 | width: 100%; } 26 | 27 | .chess-console .chess-console-board .player { 28 | font-weight: bold; 29 | font-size: 120%; } 30 | .chess-console .chess-console-board .player.to-move:after { 31 | color: #333; 32 | margin-left: 8px; 33 | content: "■"; } 34 | 35 | .chess-console .chess-console-board .clock { 36 | font-family: 'DSDIGI', sans-serif; 37 | font-size: 150%; 38 | line-height: 0.99; } 39 | 40 | .chess-console .chess-console-left .history { 41 | max-height: 284px; 42 | max-width: 210px; 43 | overflow-y: auto; 44 | overflow-x: visible; 45 | margin-bottom: 2rem; 46 | width: calc(100% + 8px); } 47 | .chess-console .chess-console-left .history table { 48 | width: 100%; } 49 | .chess-console .chess-console-left .history table .num { 50 | width: 18%; } 51 | .chess-console .chess-console-left .history table .ply { 52 | width: 41%; } 53 | .chess-console .chess-console-left .history table .ply i { 54 | pointer-events: none; } 55 | .chess-console .chess-console-left .history table .ply.active { 56 | font-weight: bold; } 57 | .chess-console .chess-console-left .history.clickable .ply { 58 | cursor: pointer; } 59 | 60 | .chess-console .chess-console-left .captured-pieces { 61 | margin-bottom: 1rem; 62 | font-size: 120%; } 63 | .chess-console .chess-console-left .captured-pieces > div:first-of-type { 64 | margin-bottom: 0.5rem; } 65 | .chess-console .chess-console-left .captured-pieces .piece * { 66 | pointer-events: none; } 67 | 68 | .chess-console .chess-console-right .buttons-grid .autoplay .fa-play { 69 | color: #198754; } 70 | 71 | .chess-console .chess-console-right .buttons-grid .autoplay .fa-stop { 72 | color: #fd7e14; } 73 | 74 | .chess-console .chess-console-right .buttons-grid .btn-active { 75 | color: #198754; } 76 | 77 | .chess-console .chess-console-right .game-status { 78 | font-weight: bold; 79 | margin-bottom: 1rem; } 80 | 81 | .chess-console .chess-console-right .last-error { 82 | color: #dc3545; 83 | margin-bottom: 1rem; } 84 | 85 | .chess-console .chess-console-right .move-input { 86 | margin-bottom: 1rem; } 87 | .chess-console .chess-console-right .move-input .first-move { 88 | color: #343a40; 89 | font-size: 280%; 90 | width: 100%; 91 | overflow: hidden; } 92 | .chess-console .chess-console-right .move-input .move-input-controls { 93 | width: 100%; } 94 | .chess-console .chess-console-right .move-input .move-input-controls button { 95 | float: left; 96 | margin-right: 0.5rem; 97 | margin-bottom: 0.5rem; } 98 | 99 | .modal svg.piece { 100 | width: 40px; 101 | height: 40px; 102 | cursor: pointer; } 103 | .modal svg.piece:hover { 104 | background-color: #ccc; } 105 | 106 | button.close { 107 | cursor: pointer; } 108 | 109 | .cm-chessboard .board.input-enabled .square { 110 | cursor: pointer; } 111 | 112 | .cm-chessboard .coordinates, .cm-chessboard .markers-layer, .cm-chessboard .pieces-layer, .cm-chessboard .markers-top-layer { 113 | pointer-events: none; } 114 | 115 | .cm-chessboard-content .list-inline { 116 | padding-left: 0; 117 | list-style: none; } 118 | 119 | .cm-chessboard-content .list-inline-item { 120 | display: inline-block; } 121 | .cm-chessboard-content .list-inline-item:not(:last-child) { 122 | margin-right: 1rem; } 123 | 124 | .cm-chessboard-content .list-inline { 125 | padding-left: 0; 126 | list-style: none; } 127 | 128 | .cm-chessboard-content .list-inline-item { 129 | display: inline-block; } 130 | .cm-chessboard-content .list-inline-item:not(:last-child) { 131 | margin-right: 1rem; } 132 | 133 | .cm-chessboard-accessibility.visually-hidden { 134 | width: 1px; 135 | height: 1px; 136 | padding: 0; 137 | margin: -1px; 138 | overflow: hidden; 139 | clip: rect(0, 0, 0, 0); 140 | white-space: nowrap; 141 | border: 0; } 142 | 143 | .cm-chessboard.default .board .square.white { 144 | fill: #ecdab9; } 145 | 146 | .cm-chessboard.default .board .square.black { 147 | fill: #c5a076; } 148 | 149 | .cm-chessboard.default.border-type-thin .board .border { 150 | stroke: #c5a076; 151 | stroke-width: 0.7%; 152 | fill: #c5a076; } 153 | 154 | .cm-chessboard.default.border-type-none .board .border { 155 | stroke: #c5a076; 156 | stroke-width: 0; 157 | fill: #c5a076; } 158 | 159 | .cm-chessboard.default.border-type-frame .board .border { 160 | fill: #ecdab9; 161 | stroke: none; } 162 | 163 | .cm-chessboard.default.border-type-frame .board .border-inner { 164 | fill: #c5a076; 165 | stroke: #c5a076; 166 | stroke-width: 0.7%; } 167 | 168 | .cm-chessboard.default .coordinates { 169 | pointer-events: none; 170 | user-select: none; } 171 | .cm-chessboard.default .coordinates .coordinate { 172 | fill: #b5936d; 173 | font-size: 7px; 174 | cursor: default; } 175 | .cm-chessboard.default .coordinates .coordinate.black { 176 | fill: #eeddbf; } 177 | .cm-chessboard.default .coordinates .coordinate.white { 178 | fill: #b5936d; } 179 | 180 | .cm-chessboard.default-contrast .board .square.white { 181 | fill: #ecdab9; } 182 | 183 | .cm-chessboard.default-contrast .board .square.black { 184 | fill: #c5a076; } 185 | 186 | .cm-chessboard.default-contrast.border-type-thin .board .border { 187 | stroke: #c5a076; 188 | stroke-width: 0.7%; 189 | fill: #c5a076; } 190 | 191 | .cm-chessboard.default-contrast.border-type-none .board .border { 192 | stroke: #c5a076; 193 | stroke-width: 0; 194 | fill: #c5a076; } 195 | 196 | .cm-chessboard.default-contrast.border-type-frame .board .border { 197 | fill: #ecdab9; 198 | stroke: none; } 199 | 200 | .cm-chessboard.default-contrast.border-type-frame .board .border-inner { 201 | fill: #c5a076; 202 | stroke: #c5a076; 203 | stroke-width: 0.7%; } 204 | 205 | .cm-chessboard.default-contrast .coordinates { 206 | pointer-events: none; 207 | user-select: none; } 208 | .cm-chessboard.default-contrast .coordinates .coordinate { 209 | fill: #b5936d; 210 | font-size: 7px; 211 | cursor: default; } 212 | .cm-chessboard.default-contrast .coordinates .coordinate.black { 213 | fill: #333; } 214 | .cm-chessboard.default-contrast .coordinates .coordinate.white { 215 | fill: #333; } 216 | 217 | .cm-chessboard.green .board .square.white { 218 | fill: #E0DDCC; } 219 | 220 | .cm-chessboard.green .board .square.black { 221 | fill: #4c946a; } 222 | 223 | .cm-chessboard.green.border-type-thin .board .border { 224 | stroke: #4c946a; 225 | stroke-width: 0.7%; 226 | fill: #4c946a; } 227 | 228 | .cm-chessboard.green.border-type-none .board .border { 229 | stroke: #4c946a; 230 | stroke-width: 0; 231 | fill: #4c946a; } 232 | 233 | .cm-chessboard.green.border-type-frame .board .border { 234 | fill: #E0DDCC; 235 | stroke: none; } 236 | 237 | .cm-chessboard.green.border-type-frame .board .border-inner { 238 | fill: #4c946a; 239 | stroke: #4c946a; 240 | stroke-width: 0.7%; } 241 | 242 | .cm-chessboard.green .coordinates { 243 | pointer-events: none; 244 | user-select: none; } 245 | .cm-chessboard.green .coordinates .coordinate { 246 | fill: #468862; 247 | font-size: 7px; 248 | cursor: default; } 249 | .cm-chessboard.green .coordinates .coordinate.black { 250 | fill: #e2e0d0; } 251 | .cm-chessboard.green .coordinates .coordinate.white { 252 | fill: #468862; } 253 | 254 | .cm-chessboard.blue .board .square.white { 255 | fill: #d8ecfb; } 256 | 257 | .cm-chessboard.blue .board .square.black { 258 | fill: #86afcf; } 259 | 260 | .cm-chessboard.blue.border-type-thin .board .border { 261 | stroke: #86afcf; 262 | stroke-width: 0.7%; 263 | fill: #86afcf; } 264 | 265 | .cm-chessboard.blue.border-type-none .board .border { 266 | stroke: #86afcf; 267 | stroke-width: 0; 268 | fill: #86afcf; } 269 | 270 | .cm-chessboard.blue.border-type-frame .board .border { 271 | fill: #d8ecfb; 272 | stroke: none; } 273 | 274 | .cm-chessboard.blue.border-type-frame .board .border-inner { 275 | fill: #86afcf; 276 | stroke: #86afcf; 277 | stroke-width: 0.7%; } 278 | 279 | .cm-chessboard.blue .coordinates { 280 | pointer-events: none; 281 | user-select: none; } 282 | .cm-chessboard.blue .coordinates .coordinate { 283 | fill: #7ba1be; 284 | font-size: 7px; 285 | cursor: default; } 286 | .cm-chessboard.blue .coordinates .coordinate.black { 287 | fill: #dbeefb; } 288 | .cm-chessboard.blue .coordinates .coordinate.white { 289 | fill: #7ba1be; } 290 | 291 | .cm-chessboard.chess-club .board .square.white { 292 | fill: #E6D3B1; } 293 | 294 | .cm-chessboard.chess-club .board .square.black { 295 | fill: #AF6B3F; } 296 | 297 | .cm-chessboard.chess-club.border-type-thin .board .border { 298 | stroke: #692e2b; 299 | stroke-width: 0.7%; 300 | fill: #AF6B3F; } 301 | 302 | .cm-chessboard.chess-club.border-type-none .board .border { 303 | stroke: #692e2b; 304 | stroke-width: 0; 305 | fill: #AF6B3F; } 306 | 307 | .cm-chessboard.chess-club.border-type-frame .board .border { 308 | fill: #692e2b; 309 | stroke: none; } 310 | 311 | .cm-chessboard.chess-club.border-type-frame .board .border-inner { 312 | fill: #AF6B3F; 313 | stroke: #692e2b; 314 | stroke-width: 0.7%; } 315 | 316 | .cm-chessboard.chess-club .coordinates { 317 | pointer-events: none; 318 | user-select: none; } 319 | .cm-chessboard.chess-club .coordinates .coordinate { 320 | fill: #E6D3B1; 321 | font-size: 7px; 322 | cursor: default; } 323 | .cm-chessboard.chess-club .coordinates .coordinate.black { 324 | fill: #E6D3B1; } 325 | .cm-chessboard.chess-club .coordinates .coordinate.white { 326 | fill: #AF6B3F; } 327 | 328 | .cm-chessboard.chessboard-js .board .square.white { 329 | fill: #f0d9b5; } 330 | 331 | .cm-chessboard.chessboard-js .board .square.black { 332 | fill: #b58863; } 333 | 334 | .cm-chessboard.chessboard-js.border-type-thin .board .border { 335 | stroke: #404040; 336 | stroke-width: 0.7%; 337 | fill: #b58863; } 338 | 339 | .cm-chessboard.chessboard-js.border-type-none .board .border { 340 | stroke: #404040; 341 | stroke-width: 0; 342 | fill: #b58863; } 343 | 344 | .cm-chessboard.chessboard-js.border-type-frame .board .border { 345 | fill: #f0d9b5; 346 | stroke: none; } 347 | 348 | .cm-chessboard.chessboard-js.border-type-frame .board .border-inner { 349 | fill: #b58863; 350 | stroke: #404040; 351 | stroke-width: 0.7%; } 352 | 353 | .cm-chessboard.chessboard-js .coordinates { 354 | pointer-events: none; 355 | user-select: none; } 356 | .cm-chessboard.chessboard-js .coordinates .coordinate { 357 | fill: #404040; 358 | font-size: 7px; 359 | cursor: default; } 360 | .cm-chessboard.chessboard-js .coordinates .coordinate.black { 361 | fill: #f0d9b5; } 362 | .cm-chessboard.chessboard-js .coordinates .coordinate.white { 363 | fill: #b58863; } 364 | 365 | .cm-chessboard.black-and-white .board .square.white { 366 | fill: #ffffff; } 367 | 368 | .cm-chessboard.black-and-white .board .square.black { 369 | fill: #9c9c9c; } 370 | 371 | .cm-chessboard.black-and-white.border-type-thin .board .border { 372 | stroke: #9c9c9c; 373 | stroke-width: 0.7%; 374 | fill: #9c9c9c; } 375 | 376 | .cm-chessboard.black-and-white.border-type-none .board .border { 377 | stroke: #9c9c9c; 378 | stroke-width: 0; 379 | fill: #9c9c9c; } 380 | 381 | .cm-chessboard.black-and-white.border-type-frame .board .border { 382 | fill: #ffffff; 383 | stroke: none; } 384 | 385 | .cm-chessboard.black-and-white.border-type-frame .board .border-inner { 386 | fill: #9c9c9c; 387 | stroke: #9c9c9c; 388 | stroke-width: 0.7%; } 389 | 390 | .cm-chessboard.black-and-white .coordinates { 391 | pointer-events: none; 392 | user-select: none; } 393 | .cm-chessboard.black-and-white .coordinates .coordinate { 394 | fill: #909090; 395 | font-size: 7px; 396 | cursor: default; } 397 | .cm-chessboard.black-and-white .coordinates .coordinate.black { 398 | fill: white; } 399 | .cm-chessboard.black-and-white .coordinates .coordinate.white { 400 | fill: #909090; } 401 | 402 | svg.cm-chessboard .promotion-dialog-group rect, svg.cm-chessboard .promotion-dialog-group g[data-piece] { 403 | animation: fade-in 0.25s ease-in; } 404 | 405 | svg.cm-chessboard .promotion-dialog-group .promotion-dialog { 406 | fill: white; 407 | fill-opacity: 0.7; 408 | stroke: rgba(0, 0, 0, 0.4); } 409 | 410 | svg.cm-chessboard .promotion-dialog-group .promotion-dialog-button { 411 | fill: transparent; 412 | cursor: pointer; } 413 | svg.cm-chessboard .promotion-dialog-group .promotion-dialog-button:hover { 414 | fill: rgba(0, 0, 0, 0.2); } 415 | 416 | svg.cm-chessboard .promotion-dialog-group .piece { 417 | pointer-events: none; } 418 | 419 | @keyframes fade-in { 420 | 0% { 421 | opacity: 0; } 422 | 100% { 423 | opacity: 1; } } 424 | 425 | .cm-chessboard .markers { 426 | pointer-events: none; } 427 | .cm-chessboard .markers .marker.marker-frame { 428 | stroke: #000000; 429 | stroke-width: 1.8px; 430 | opacity: 0.4; } 431 | .cm-chessboard .markers .marker.marker-frame-primary { 432 | stroke: #0009bd; 433 | stroke-width: 1.8px; 434 | opacity: 0.4; } 435 | .cm-chessboard .markers .marker.marker-frame-danger { 436 | stroke: #aa0000; 437 | stroke-width: 1.8px; 438 | opacity: 0.4; } 439 | .cm-chessboard .markers .marker.marker-circle { 440 | stroke: #000000; 441 | stroke-width: 2.5px; 442 | opacity: 0.2; } 443 | .cm-chessboard .markers .marker.marker-circle-primary { 444 | stroke: #0009bd; 445 | stroke-width: 2.5px; 446 | opacity: 0.4; } 447 | .cm-chessboard .markers .marker.marker-circle-danger { 448 | stroke: #aa0000; 449 | stroke-width: 2.5px; 450 | opacity: 0.5; } 451 | .cm-chessboard .markers .marker.marker-square { 452 | fill: yellow; 453 | opacity: 0.3; } 454 | .cm-chessboard .markers .marker.marker-square-danger { 455 | fill: #aa0000; 456 | opacity: 0.2; } 457 | .cm-chessboard .markers .marker.marker-square-primary { 458 | fill: #0009bd; 459 | opacity: 0.11; } 460 | .cm-chessboard .markers .marker.marker-dot { 461 | fill: black; 462 | opacity: 0.25; } 463 | .cm-chessboard .markers .marker.marker-bevel { 464 | fill: black; 465 | opacity: 0.25; } 466 | 467 | html body .container-fluid { 468 | max-width: 1200px; } 469 | 470 | /*# sourceMappingURL=screen.css.map */ -------------------------------------------------------------------------------- /src/tools/Openings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chess 4 | * License: MIT, see file 'LICENSE' 5 | * 6 | * @deprecated 7 | */ 8 | 9 | const openings = [ 10 | { 11 | "moves": "d2d4d7d5e2e4d5e4b1c3g8f6f2f3e4f3", 12 | "name_de": "BDG", 13 | "name_en": "BDG", 14 | "descriptionLink_de": "Blackmar-Diemer-Gambit", 15 | "descriptionLink_en": "Blackmar–Diemer_Gambit" 16 | }, 17 | { 18 | "moves": "d2d4g8f6c2c4e7e6g1f3c7c5d4d5b7b5", 19 | "name_de": "Blumfeld-Gambit", 20 | "name_en": "Blumenfeld Gambit", 21 | "descriptionLink_de": "Blumenfeld-Gambit", 22 | "descriptionLink_en": "Blumenfeld_Gambit" 23 | }, 24 | { 25 | "moves": "c2c4g8f6d2d4e7e6g1f3c7c5d4d5b7b5", 26 | "name_de": "Blumfeld-Gambit", 27 | "name_en": "Blumenfeld Gambit", 28 | "descriptionLink_de": "Blumenfeld-Gambit", 29 | "descriptionLink_en": "Blumenfeld_Gambit" 30 | }, 31 | { 32 | "moves": "d2d4g8f6c2c4g7g6b1c3f8g7e2e4d7d6", 33 | "name_de": "Königsindisch", 34 | "name_en": "King's Indian Def.", 35 | "descriptionLink_de": "Königsindische_Verteidigung", 36 | "descriptionLink_en": "King%27s_Indian_Defence" 37 | }, 38 | { 39 | "moves": "c2c4g8f6d2d4g7g6b1c3f8g7e2e4d7d6", 40 | "name_de": "Königsindisch", 41 | "name_en": "King's Indian Def.", 42 | "descriptionLink_de": "Königsindische_Verteidigung", 43 | "descriptionLink_en": "King%27s_Indian_Defence" 44 | }, 45 | { 46 | "moves": "e2e4e7e5d2d4e5d4c2c3d4c3f1c4", 47 | "name_de": "Nordisches Gambit", 48 | "name_en": "Danish Gambit", 49 | "descriptionLink_de": "Nordisches_Gambit", 50 | "descriptionLink_en": "Danish_Gambit" 51 | }, 52 | { 53 | "moves": "d2d4g8f6c2c4e7e6g1f3f8b4", 54 | "name_de": "Bogoindisch", 55 | "name_en": "Bogo-Indian", 56 | "descriptionLink_de": "Bogoljubow-Indische_Verteidigung", 57 | "descriptionLink_en": "Bogo-Indian_Defence" 58 | }, 59 | { 60 | "moves": "c2c4g8f6d2d4e7e6g1f3f8b4", 61 | "name_de": "Bogoindisch", 62 | "name_en": "Bogo-Indian", 63 | "descriptionLink_de": "Bogoljubow-Indische_Verteidigung", 64 | "descriptionLink_en": "Bogo-Indian_Defence" 65 | }, 66 | { 67 | "moves": "d2d4g8f6c2c4e7e5d4e5f6g4", 68 | "name_de": "Budapester Gambit", 69 | "name_en": "Budapest Gambit", 70 | "descriptionLink_de": "Budapester_Gambit", 71 | "descriptionLink_en": "Budapest_Gambit" 72 | }, 73 | { 74 | "moves": "c2c4g8f6d2d4e7e5d4e5f6g4", 75 | "name_de": "Budapester Gambit", 76 | "name_en": "Budapest Gambit", 77 | "descriptionLink_de": "Budapester_Gambit", 78 | "descriptionLink_en": "Budapest_Gambit" 79 | }, 80 | { 81 | "moves": "d2d4g8f6c2c4e7e6g1f3b7b6", 82 | "name_de": "Damenindisch", 83 | "name_en": "Queen's Indian", 84 | "descriptionLink_de": "Damenindische_Verteidigung", 85 | "descriptionLink_en": "Queen%27s_Indian_Defense" 86 | }, 87 | { 88 | "moves": "c2c4g8f6d2d4e7e6g1f3b7b6", 89 | "name_de": "Damenindisch", 90 | "name_en": "Queen's Indian", 91 | "descriptionLink_de": "Damenindische_Verteidigung", 92 | "descriptionLink_en": "Queen%27s_Indian_Defense" 93 | }, 94 | { 95 | "moves": "d2d4g8f6c2c4g7g6b1c3d7d5", 96 | "name_de": "Grünfeld-Indisch", 97 | "name_en": "Grünfeld Defence", 98 | "descriptionLink_de": "Grünfeld-Indische_Verteidigung", 99 | "descriptionLink_en": "Grünfeld_Defence" 100 | }, 101 | { 102 | "moves": "c2c4g8f6d2d4g7g6b1c3d7d5", 103 | "name_de": "Grünfeld-Indisch", 104 | "name_en": "Grünfeld Defence", 105 | "descriptionLink_de": "Grünfeld-Indische_Verteidigung", 106 | "descriptionLink_en": "Grünfeld_Defence" 107 | }, 108 | { 109 | "moves": "e2e4e7e5g1f3b8c6f1c4f8c5", 110 | "name_de": "Italienisch", 111 | "name_en": "Giuoco Piano", 112 | "descriptionLink_de": "Italienische_Partie", 113 | "descriptionLink_en": "Giuoco_Piano" 114 | }, 115 | { 116 | "moves": "d2d4g8f6c2c4e7e6b1c3f8b4", 117 | "name_de": "Nimzo-Indisch", 118 | "name_en": "Nimzo-Indian Def.", 119 | "descriptionLink_de": "Nimzowitsch-Indische_Verteidigung", 120 | "descriptionLink_en": "Nimzo-Indian_Defence" 121 | }, 122 | { 123 | "moves": "d2d4e7e6c2c4g8f6b1c3f8b4", 124 | "name_de": "Nimzo-Indisch", 125 | "name_en": "Nimzo-Indian Def.", 126 | "descriptionLink_de": "Nimzowitsch-Indische_Verteidigung", 127 | "descriptionLink_en": "Nimzo-Indian_Defence" 128 | }, 129 | { 130 | "moves": "c2c4g8f6d2d4e7e6b1c3f8b4", 131 | "name_de": "Nimzo-Indisch", 132 | "name_en": "Nimzo-Indian Def.", 133 | "descriptionLink_de": "Nimzowitsch-Indische_Verteidigung", 134 | "descriptionLink_en": "Nimzo-Indian_Defence" 135 | }, 136 | { 137 | "moves": "e2e4e7e5g1f3b8c6d2d4e5d4", 138 | "name_de": "Schottisch", 139 | "name_en": "Scotch Game", 140 | "descriptionLink_de": "Schottische_Partie", 141 | "descriptionLink_en": "Scotch_Game" 142 | }, 143 | { 144 | "moves": "e2e4e7e5g1f3b8c6f1c4f8e7", 145 | "name_de": "Ungarisch", 146 | "name_en": "Hungarian Defence", 147 | "descriptionLink_de": "Ungarische_Verteidigung", 148 | "descriptionLink_en": "Hungarian_Defense" 149 | }, 150 | { 151 | "moves": "e2e4e7e5g1f3b8c6b1c3g8f6", 152 | "name_de": "Vierspringerspiel", 153 | "name_en": "Four Knights Game", 154 | "descriptionLink_de": "Vierspringerspiel", 155 | "descriptionLink_en": "Four_Knights_Game" 156 | }, 157 | { 158 | "moves": "d2d4g8f6c2c4c7c5d4d5b7b5", 159 | "name_de": "Wolga-Gambit", 160 | "name_en": "Benko Gambit", 161 | "descriptionLink_de": "Wolga-Gambit", 162 | "descriptionLink_en": "Benko_Gambit" 163 | }, 164 | { 165 | "moves": "c2c4g8f6d2d4c7c5d4d5b7b5", 166 | "name_de": "Wolga-Gambit", 167 | "name_en": "Benko Gambit", 168 | "descriptionLink_de": "Wolga-Gambit", 169 | "descriptionLink_en": "Benko_Gambit" 170 | }, 171 | { 172 | "moves": "e2e4e7e5g1f3b8c6f1c4g8f6", 173 | "name_de": "Zweispringerspiel", 174 | "name_en": "Two Knights Def.", 175 | "descriptionLink_de": "Zweispringerspiel_im_Nachzuge", 176 | "descriptionLink_en": "Two_Knights_Defense" 177 | }, 178 | { 179 | "moves": "d2d4g8f6c2c4e7e6g2g3", 180 | "name_de": "Katalanisch", 181 | "name_en": "Catalan Opening", 182 | "descriptionLink_de": "Katalanische_Eröffnung", 183 | "descriptionLink_en": "Catalan_Opening" 184 | }, 185 | { 186 | "moves": "c2c4g8f6d2d4e7e6g2g3", 187 | "name_de": "Katalanisch", 188 | "name_en": "Catalan Opening", 189 | "descriptionLink_de": "Katalanische_Eröffnung", 190 | "descriptionLink_en": "Catalan_Opening" 191 | }, 192 | { 193 | "moves": "e2e4e7e5g1f3b8c6c2c3", 194 | "name_de": "Ponziani", 195 | "name_en": "Ponziani Opening", 196 | "descriptionLink_de": "Ponziani-Eröffnung", 197 | "descriptionLink_en": "Ponziani_Opening" 198 | }, 199 | { 200 | "moves": "e2e4e7e5g1f3b8c6f1b5", 201 | "name_de": "Spanisch", 202 | "name_en": "Ruy Lopez", 203 | "descriptionLink_de": "Spanische_Partie", 204 | "descriptionLink_en": "Ruy_Lopez" 205 | }, 206 | { 207 | "moves": "d2d4g8f6c2c4d7d6", 208 | "name_de": "Altindisch", 209 | "name_en": "Old Indian Defence", 210 | "descriptionLink_de": "Altindische_Verteidigung", 211 | "descriptionLink_en": "Old_Indian_Defense" 212 | }, 213 | { 214 | "moves": "c2c4g8f6d2d4d7d6", 215 | "name_de": "Altindisch", 216 | "name_en": "Old Indian Defence", 217 | "descriptionLink_de": "Altindische_Verteidigung", 218 | "descriptionLink_en": "Old_Indian_Defense" 219 | }, 220 | { 221 | "moves": "d2d4g8f6c2c4c7c5", 222 | "name_de": "Benoni", 223 | "name_en": "Benoni Defence", 224 | "descriptionLink_de": "Benoni-Verteidigung", 225 | "descriptionLink_en": "Benoni_Defense" 226 | }, 227 | { 228 | "moves": "c2c4g8f6d2d4c7c5", 229 | "name_de": "Benoni", 230 | "name_en": "Benoni Defence", 231 | "descriptionLink_de": "Benoni-Verteidigung", 232 | "descriptionLink_en": "Benoni_Defense" 233 | }, 234 | { 235 | "moves": "d2d4g8f6c2c4b8c6", 236 | "name_de": "Mexikanisch", 237 | "name_en": "King's Indian Def.", 238 | "descriptionLink_de": "Mexikanische_Verteidigung", 239 | "descriptionLink_en": "Black_Knights%27_Tango" 240 | }, 241 | { 242 | "moves": "c2c4g8f6d2d4b8c6", 243 | "name_de": "Mexikanisch", 244 | "name_en": "Black Knights' Tango", 245 | "descriptionLink_de": "Mexikanische_Verteidigung", 246 | "descriptionLink_en": "Black_Knights%27_Tango" 247 | }, 248 | { 249 | "moves": "e2e4e7e5d2d4e5d4", 250 | "name_de": "Mittelgambit", 251 | "name_en": "Center Game", 252 | "descriptionLink_de": "Mittelgambit", 253 | "descriptionLink_en": "Center_Game" 254 | }, 255 | { 256 | "moves": "e2e4e7e5g1f3d7d6", 257 | "name_de": "Philidor", 258 | "name_en": "Philidor Defence", 259 | "descriptionLink_de": "Philidor-Verteidigung", 260 | "descriptionLink_en": "Philidor_Defence" 261 | }, 262 | { 263 | "moves": "e2e4e7e5g1f3g8f6", 264 | "name_de": "Russisch", 265 | "name_en": "Russian", 266 | "descriptionLink_de": "Russische_Verteidigung", 267 | "descriptionLink_en": "Petrov%27s_Defence" 268 | }, 269 | { 270 | "moves": "e2e4e7e5g1e2", 271 | "name_de": "Alapin", 272 | "name_en": "Alapin's Opening", 273 | "descriptionLink_de": "Alapin-Eröffnung", 274 | "descriptionLink_en": "Alapin%27s_Opening" 275 | }, 276 | { 277 | "moves": "d2d4d7d5c2c4", 278 | "name_de": "Damengambit", 279 | "name_en": "Queen's Gambit", 280 | "descriptionLink_de": "Damengambit", 281 | "descriptionLink_en": "Queen%27s_Gambit" 282 | }, 283 | { 284 | "moves": "d2d4g8f6c2c4", 285 | "name_de": "Indisch", 286 | "name_en": "Indian Defence", 287 | "descriptionLink_de": "Indische_Verteidigung", 288 | "descriptionLink_en": "Indian_Defence" 289 | }, 290 | { 291 | "moves": "c2c4g8f6d2d4", 292 | "name_de": "Indisch", 293 | "name_en": "Indian Defence", 294 | "descriptionLink_de": "Indische_Verteidigung", 295 | "descriptionLink_en": "Indian_Defence" 296 | }, 297 | { 298 | "moves": "e2e4e7e5f2f4", 299 | "name_de": "Königsgambit", 300 | "name_en": "King's Gambit", 301 | "descriptionLink_de": "Königsgambit", 302 | "descriptionLink_en": "King%27s_Gambit" 303 | }, 304 | { 305 | "moves": "e2e4e7e5f1c4", 306 | "name_de": "Läuferspiel", 307 | "name_en": "Bishop's Opening", 308 | "descriptionLink_de": "Läuferspiel", 309 | "descriptionLink_en": "Bishop%27s_Opening" 310 | }, 311 | { 312 | "moves": "e2e4d7d5e4d5", 313 | "name_de": "Skandinavisch", 314 | "name_en": "Scandinavian Def.", 315 | "descriptionLink_de": "Skandinavische_Verteidigung", 316 | "descriptionLink_en": "Scandinavian_Defense" 317 | }, 318 | { 319 | "moves": "e2e4g8f6", 320 | "name_de": "Aljechin", 321 | "name_en": "Alekhine's Defence", 322 | "descriptionLink_de": "Aljechin-Verteidigung", 323 | "descriptionLink_en": "Alekhine%27s_Defence" 324 | }, 325 | { 326 | "moves": "e2e4c7c6", 327 | "name_de": "Caro-Kann", 328 | "name_en": "Caro–Kann Defence", 329 | "descriptionLink_de": "Caro-Kann", 330 | "descriptionLink_en": "Caro–Kann_Defence" 331 | }, 332 | { 333 | "moves": "d2d4d7d5", 334 | "name_de": "Damenbauernspiel", 335 | "name_en": "Queen's Pawn Game", 336 | "descriptionLink_de": "Damenbauernspiel", 337 | "descriptionLink_en": "Queen%27s_Pawn_Game" 338 | }, 339 | { 340 | "moves": "e2e4e7e6", 341 | "name_de": "Französisch", 342 | "name_en": "French Defence", 343 | "descriptionLink_de": "Französische_Verteidigung", 344 | "descriptionLink_en": "French_Defence" 345 | }, 346 | { 347 | "moves": "d2d4f7f5", 348 | "name_de": "Holländisch", 349 | "name_en": "Dutch Defence", 350 | "descriptionLink_de": "Holländische_Verteidigung", 351 | "descriptionLink_en": "Dutch_Defence" 352 | }, 353 | { 354 | "moves": "e2e4d7d6", 355 | "name_de": "Jugoslawisch", 356 | "name_en": "Pirc Defence", 357 | "descriptionLink_de": "Pirc-Ufimzew-Verteidigung", 358 | "descriptionLink_en": "Pirc_Defence" 359 | }, 360 | { 361 | "moves": "e2e4b8c6", 362 | "name_de": "Nimzowitsch", 363 | "name_en": "Nimzowitsch Def.", 364 | "descriptionLink_de": "Nimzowitsch-Verteidigung", 365 | "descriptionLink_en": "Nimzowitsch_Defence" 366 | }, 367 | { 368 | "moves": "e2e4b7b5", 369 | "name_de": "Owen", 370 | "name_en": "Owen's Defence", 371 | "descriptionLink_de": "Owen-Verteidigung", 372 | "descriptionLink_en": "Owen%27s_Defence" 373 | }, 374 | { 375 | "moves": "e2e4g7g6", 376 | "name_de": "Robatsch", 377 | "name_en": "Robatsch Defence", 378 | "descriptionLink_de": "Moderne_Verteidigung", 379 | "descriptionLink_en": "Modern_Defense" 380 | }, 381 | { 382 | "moves": "e2e4c7c5", 383 | "name_de": "Sizilianisch", 384 | "name_en": "Sicilian Defence", 385 | "descriptionLink_de": "Sizilianische_Verteidigung", 386 | "descriptionLink_en": "Sicilian_Defence" 387 | }, 388 | { 389 | "moves": "c2c4", 390 | "name_de": "Englisch", 391 | "name_en": "English Opening", 392 | "descriptionLink_de": "Englische_Eröffnung", 393 | "descriptionLink_en": "English_Opening" 394 | }, 395 | { 396 | "moves": "b2b4", 397 | "name_de": "Orang Utan", 398 | "name_en": "Orangutan", 399 | "descriptionLink_de": "Orang-Utan_(Schach)", 400 | "descriptionLink_en": "Sokolsky_Opening" 401 | } 402 | ]; 403 | 404 | export function detect(moves) { 405 | console.warn("Openings is deprecated") 406 | let i; 407 | for (i in openings) { 408 | const opening = openings[i]; 409 | if (opening.moves.length <= moves.length) { 410 | const movesTruncated = moves.substring(0, opening.moves.length); 411 | if (movesTruncated === opening.moves) { 412 | return opening; 413 | } 414 | } 415 | } 416 | } --------------------------------------------------------------------------------