explain why the player is in check using chess notation

This commit is contained in:
Jesse D. McDonald 2020-04-30 02:46:21 -05:00
parent 6e552cfa4e
commit 6d4923755e
4 changed files with 126 additions and 46 deletions

View File

@ -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;

View File

@ -221,7 +221,10 @@
<div id="cb_message"></div>
</form>
<div id="cb_scrollable">
<p id="cb_history"><span id="cb_history_past"></span><span id="cb_history_future"></span></p>
<div id="cb_history">
<span id="cb_history_past"></span><span id="cb_history_future"></span>
<div id="cb_explain_check"></div>
</div>
<details>
<summary>Rule Reference</summary>
<p>The basic movement for each piece is the same as traditional chess.</p>

View File

@ -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() {

View File

@ -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 = '';