diff --git a/css/chess.css b/css/chess.css index caeae96..02f6f04 100644 --- a/css/chess.css +++ b/css/chess.css @@ -116,8 +116,8 @@ button:disabled .silhouette { overflow: auto; } -#cb_scrollable p { - margin-top: 0; +#cb_scrollable > *:last-child { + margin-bottom: 0.5em; } #cb_controls, #cb_theme, #cb_names, #cb_message, #cb_navigate { @@ -191,12 +191,18 @@ button:disabled .silhouette { #cb_history { max-width: 7.5in; + margin-bottom: 0.5em; } #cb_history_future { color: grey; } +#cb_explain_check { + color: red; + margin-top: 0.5em; +} + .cb-hbox { display: flex; flex-flow: row nowrap; diff --git a/index.html b/index.html index 00285b1..dd162ca 100644 --- a/index.html +++ b/index.html @@ -221,7 +221,10 @@
-

+
+ +
+
Rule Reference

The basic movement for each piece is the same as traditional chess.

diff --git a/js/pacosako.js b/js/pacosako.js index 113fb39..27afaf3 100644 --- a/js/pacosako.js +++ b/js/pacosako.js @@ -394,22 +394,6 @@ function addHistory(game) { result += '(' + move.promotion.toUpperCase() + ')'; } - if (game.status === CHECKMATE) { - result += '#'; - } else if (game.isInCheck()) { - result += '+'; - } - - let winner = game.winner; - - if (winner === LIGHT) { - result += ' 1-0'; - } else if (winner === DARK) { - result += ' 0-1'; - } else if (game.status !== PLAYING) { - result += ' \u00bd-\u00bd'; /* 1/2-1/2 */ - } - game._history += result; } @@ -420,20 +404,16 @@ class Game { throw { message: 'can only clone from another Game instance' }; } - this._board = new Board(original._board); - this._player = original._player; - this._status = original._status; - this._moves = original._moves.slice(); - this._redo = original._redo.slice(); - this._history = original._history; - this._turn = original._turn; - this._undo = original._undo; - this._checkCache = Object.assign({}, original._checkCache); - - this._castling = { - [LIGHT]: Object.assign({}, original._castling[LIGHT]), - [DARK]: Object.assign({}, original._castling[DARK]), - }; + Object.assign(this, original, { + _board: new Board(original._board), + _moves: original._moves.slice(), + _redo: original._redo.slice(), + _checkCache: Object.assign({}, original._checkCache), + _castling: { + [LIGHT]: Object.assign({}, original._castling[LIGHT]), + [DARK]: Object.assign({}, original._castling[DARK]), + }, + }); } else { this._board = new Board(); this._player = LIGHT; @@ -444,6 +424,7 @@ class Game { this._turn = 0; this._undo = null; this._checkCache = {}; + this._ignoreCheck = false; /* set to false when the king or rook moves */ this._castling = { @@ -473,6 +454,14 @@ class Game { return JSON.parse(JSON.stringify(this._redo)); } + get ignoreCheck() { + return this._ignoreCheck; + } + + set ignoreCheck(value) { + this._ignoreCheck = !!value; + } + canCapture(side, from) { if (from === PHANTOM) { return true; @@ -688,6 +677,7 @@ class Game { /* check is evaluated as if the current player passed without moving */ const sim = new Game(this); sim.dropHistory(); + sim.ignoreCheck = true; sim._player = other; /* start with the opponent's unpaired pieces, the ones that can capture */ @@ -698,7 +688,30 @@ class Game { if (sim._board.getPiece(other, from) !== KING) { if (sim.isLegalMove(other, from, king, true)) { /* this piece can directly capture the king */ - return recordCheck(this, true); + let check = true; + try { + if (this._history !== undefined) { + const hist = new Game(this); + hist.ignoreCheck = true; + if (hist._player !== other) { + if (hist._player === LIGHT) { + hist._turn += 1; + hist._history += ` ${hist._turn}.${NBSP}…`; + } else { + hist._history += ` …`; + } + hist._player = other; + } + const turn_before = hist._turn; + const history_before = hist._history; + hist.move(from, king); + check = hist.history.slice(history_before.length).trimStart(); + if (!check.match(/^\d+\./)) { + check = `${turn_before}.${NBSP}… ${check}`; + } + } + } catch(err) {} + return recordCheck(this, check); } queue.push({ game: sim, from }); @@ -747,7 +760,33 @@ class Game { if (game2.isLegalMove(other, PHANTOM, king, true)) { /* we have our answer */ - return recordCheck(this, true); + let check = true; + try { + if (this._history !== undefined) { + const hist = new Game(this); + hist.ignoreCheck = true; + if (hist._player !== other) { + if (hist._player === LIGHT) { + hist._turn += 1; + hist._history += ` ${hist._turn}.${NBSP}…`; + } else { + hist._history += ` …`; + } + hist._player = other; + } + const turn_before = hist._turn; + const history_before = hist._history; + for (const move of game2._moves.slice(hist._moves.length)) { + hist.replayMove(move); + } + hist.move(PHANTOM, king); + check = hist.history.slice(history_before.length).trimStart(); + if (!check.match(/^\d+\./)) { + check = `${turn_before}.${NBSP}… ${check}`; + } + } + } catch(err) {} + return recordCheck(this, check); } queue.push({ game: game2, from: PHANTOM }); @@ -762,6 +801,7 @@ class Game { const side = this._player; const sim = new Game(this); sim.dropHistory(); + sim.ignoreCheck = true; try { sim.move(from, to); @@ -936,14 +976,14 @@ class Game { this._moves.push(move); this._redo = []; + addHistory(this); + + let inCheck = false; + if (took === KING) { this._status = CHECKMATE; - } else if (this._history && this.isInCheck(this._player)) { - /* - * NOTE: Preemptive checkmate detection is not performed on - * simulated games (without history) to prevent recursion. - */ - + } else if (!this.ignoreCheck && this.isInCheck(this._player)) { + inCheck = true; let canEscape = false; for (const tryFrom of this._board.findPieces(this._player, true)) { @@ -964,7 +1004,23 @@ class Game { } } - addHistory(this); + if (this._history !== undefined) { + if (this._status === CHECKMATE) { + this._history += '#'; + } else if (inCheck) { + this._history += '+'; + } + + let winner = this.winner; + + if (winner === LIGHT) { + this._history += ' 1-0'; + } else if (winner === DARK) { + this._history += ' 0-1'; + } else if (this._status !== PLAYING) { + this._history += ' \u00bd-\u00bd'; /* 1/2-1/2 */ + } + } } resign(meta) { @@ -988,6 +1044,14 @@ class Game { this._status = RESIGNED; addHistory(this); + + if (this._history !== undefined) { + if (move.side === LIGHT) { + this._history += ' 0-1'; + } else { + this._history += ' 1-0'; + } + } } get lastMove() { diff --git a/js/pacosako_ui.js b/js/pacosako_ui.js index f0768ed..1b2bad0 100644 --- a/js/pacosako_ui.js +++ b/js/pacosako_ui.js @@ -224,6 +224,7 @@ $(function (){ $('#cb_board .cb-end').removeClass('cb-end'); $('#cb_board .cb-legal').removeClass('cb-legal'); $('#cb_phantom').appendTo('#cb_hidden'); + $('#cb_explain_check').text(''); const game = visibleGame; const board = game.board; @@ -294,6 +295,7 @@ $(function (){ const liveView = !game.canRedo; const playing = game.status === PS.PLAYING; + const clss = game.player === PS.LIGHT ? '.cb-lt-piece' : '.cb-dk-piece'; const phantom = board.phantom; if (phantom) { @@ -306,15 +308,20 @@ $(function (){ pieceStartMove(piece, 'click'); } } else if (liveView && playing) { - const clss = game.player === PS.LIGHT ? '.cb-lt-piece' : '.cb-dk-piece'; const pieces = $('#cb_board ' + clss) pieces.parent().on('click.select', squareClickSelect); pieces.draggable('enable'); } - if (game.isInCheck()) { - const clss = game.player === PS.LIGHT ? '.cb-lt-piece' : '.cb-dk-piece'; - $('#cb_board ' + clss + '.cb-king').addClass('cb-in-check'); + const check = game.isInCheck(); + const king = $('#cb_board ' + clss + '.cb-king'); + + if (check) { + king.addClass('cb-in-check'); + } + + if (typeof check === 'string') { + $('#cb_explain_check').text(`(Check: ${check})`); } let msg = '';