├── 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 | ``
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 |
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 |
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 |
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 |
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 |
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 |
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 |
16 |
Example: Template in page
17 |
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 |
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 |
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 |
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 |
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 | ""
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 | 
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 | `
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 | }
--------------------------------------------------------------------------------