if (fenParsed.enpassant != "-")
{
const ep = V.SquareToCoords(fenParsed.enpassant);
- if (ep.y < 0 || ep.y > V.size.y || isNaN(ep.x) || ep.x < 0 || ep.x > V.size.x)
+ if (isNaN(ep.x) || !V.OnBoard(ep))
return false;
}
}
return !!flags.match(/^[01]{4,4}$/);
}
- // 3 --> d (column letter from number)
- static GetColumn(colnum)
+ // 3 --> d (column number to letter)
+ static CoordToColumn(colnum)
+ {
+ return String.fromCharCode(97 + colnum);
+ }
+
+ // d --> 3 (column letter to number)
+ static ColumnToCoord(colnum)
{
return String.fromCharCode(97 + colnum);
}
static SquareToCoords(sq)
{
return {
+ // NOTE: column is always one char => max 26 columns
+ // row is counted from black side => subtraction
x: V.size.x - parseInt(sq.substr(1)),
y: sq[0].charCodeAt() - 97
};
// {x:0,y:4} --> e8
static CoordsToSquare(coords)
{
- return V.GetColumn(coords.y) + (V.size.x - coords.x);
+ return V.CoordToColumn(coords.y) + (V.size.x - coords.x);
}
// Aggregates flags into one object
// Get random squares for bishops
let randIndex = 2 * _.random(3);
- let bishop1Pos = positions[randIndex];
+ const bishop1Pos = positions[randIndex];
// The second bishop must be on a square of different color
let randIndex_tmp = 2 * _.random(3) + 1;
- let bishop2Pos = positions[randIndex_tmp];
+ const bishop2Pos = positions[randIndex_tmp];
// Remove chosen squares
positions.splice(Math.max(randIndex,randIndex_tmp), 1);
positions.splice(Math.min(randIndex,randIndex_tmp), 1);
// Get random squares for knights
randIndex = _.random(5);
- let knight1Pos = positions[randIndex];
+ const knight1Pos = positions[randIndex];
positions.splice(randIndex, 1);
randIndex = _.random(4);
- let knight2Pos = positions[randIndex];
+ const knight2Pos = positions[randIndex];
positions.splice(randIndex, 1);
// Get random square for queen
randIndex = _.random(3);
- let queenPos = positions[randIndex];
+ const queenPos = positions[randIndex];
positions.splice(randIndex, 1);
- // Rooks and king positions are now fixed, because of the ordering rook-king-rook
- let rook1Pos = positions[0];
- let kingPos = positions[1];
- let rook2Pos = positions[2];
+ // Rooks and king positions are now fixed,
+ // because of the ordering rook-king-rook
+ const rook1Pos = positions[0];
+ const kingPos = positions[1];
+ const rook2Pos = positions[2];
// Finally put the shuffled pieces in the board array
pieces[c][rook1Pos] = 'r';
}
}
- // Build a regular move from its initial and destination squares; tr: transformation
+ // Build a regular move from its initial and destination squares.
+ // tr: transformation
getBasicMove([sx,sy], [ex,ey], tr)
{
let mv = new Move({
return mv;
}
- // Generic method to find possible moves of non-pawn pieces ("sliding or jumping")
+ // Generic method to find possible moves of non-pawn pieces:
+ // "sliding or jumping"
getSlideNJumpMoves([x,y], steps, oneStep)
{
const color = this.getColor(x,y);
// What are the queen moves from square x,y ?
getPotentialQueenMoves(sq)
{
- return this.getSlideNJumpMoves(sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+ return this.getSlideNJumpMoves(sq,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
}
// What are the king moves from square x,y ?
continue;
// If this code is reached, rooks and king are on initial position
- // Nothing on the path of the king (and no checks; OK also if y==finalSquare)?
+ // Nothing on the path of the king ?
+ // (And no checks; OK also if y==finalSquare)
let step = finalSquares[castleSide][0] < y ? -1 : 1;
for (i=y; i!=finalSquares[castleSide][0]; i+=step)
{
if (this.isAttacked([x,i], [oppCol]) || (this.board[x][i] != V.EMPTY &&
// NOTE: next check is enough, because of chessboard constraints
- (this.getColor(x,i) != c || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
+ (this.getColor(x,i) != c
+ || ![V.KING,V.ROOK].includes(this.getPiece(x,i)))))
{
continue castlingCheck;
}
});
}
- // Search for all valid moves considering current turn (for engine and game end)
+ // Search for all valid moves considering current turn
+ // (for engine and game end)
getAllValidMoves()
{
const color = this.turn;
{
for (let j=0; j<V.size.y; j++)
{
- // Next condition "!= oppCol" = harmless hack to work with checkered variant
+ // Next condition "!= oppCol" to work with checkered variant
if (this.board[i][j] != V.EMPTY && this.getColor(i,j) != oppCol)
- Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
+ {
+ Array.prototype.push.apply(potentialMoves,
+ this.getPotentialMovesFrom([i,j]));
+ }
}
}
- // NOTE: prefer lazy undercheck tests, letting the king being taken?
- // No: if happen on last 1/2 move, could lead to forbidden moves, wrong evals
return this.filterValid(potentialMoves);
}
return false;
}
- // Check if pieces of color in array 'colors' are attacking (king) on square x,y
+ // Check if pieces of color in 'colors' are attacking (king) on square x,y
isAttacked(sq, colors)
{
return (this.isAttackedByPawn(sq, colors)
play(move, ingame)
{
// DEBUG:
-// console.log("DO");
// if (!this.states) this.states = [];
// if (!ingame) this.states.push(this.getFen());
this.unupdateVariables(move);
// DEBUG:
-// console.log("UNDO "+this.getNotation(move));
// if (this.getFen() != this.states[this.states.length-1])
// debugger;
// this.states.pop();
// Rank moves using a min-max at depth 2
for (let i=0; i<moves1.length; i++)
{
- moves1[i].eval = (color=="w" ? -1 : 1) * maxeval; //very low, I'm checkmated
+ // Initial self evaluation is very low: "I'm checkmated"
+ moves1[i].eval = (color=="w" ? -1 : 1) * maxeval;
this.play(moves1[i]);
let eval2 = undefined;
if (this.atLeastOneMove())
{
- eval2 = (color=="w" ? 1 : -1) * maxeval; //initialized with checkmate value
+ // Initial enemy evaluation is very low too, for him
+ eval2 = (color=="w" ? 1 : -1) * maxeval;
// Second half-move:
let moves2 = this.getAllValidMoves("computer");
for (let j=0; j<moves2.length; j++)
const score = this.checkGameEnd();
evalPos = (score=="1/2" ? 0 : (score=="1-0" ? 1 : -1) * maxeval);
}
- if ((color == "w" && evalPos < eval2) || (color=="b" && evalPos > eval2))
+ if ((color == "w" && evalPos < eval2)
+ || (color=="b" && evalPos > eval2))
+ {
eval2 = evalPos;
+ }
this.undo(moves2[j]);
}
}
this.alphabeta(V.SEARCH_DEPTH-1, -maxeval, maxeval);
this.undo(moves1[i]);
}
- moves1.sort( (a,b) => { return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
+ moves1.sort( (a,b) => {
+ return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
}
else
return currentBest;
if (move.vanish.length > move.appear.length)
{
// Capture
- const startColumn = String.fromCharCode(97 + move.start.y);
+ const startColumn = V.CoordToColumn(move.start.y);
notation = startColumn + "x" + finalSquare;
}
else //no capture
notation = finalSquare;
- if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
+ if (move.appear.length > 0 && move.appear[0].p != V.PAWN) //promotion
notation += "=" + move.appear[0].p.toUpperCase();
return notation;
}
for (let i=0; i<squares.length; i++)
{
const res = /^([a-z]+)([0-9]+)$/i.exec(squares[i]);
- const x = sizeX - parseInt(res[2]); //white at bottom, so counting is reversed
- const y = res[1].charCodeAt(0)-97; //always one char: max 26, big enough
- markArray[x][y] = true;
+ const coords = V.SquareToCoords(res);
+ markArray[coords.x][coords.y] = true;
}
}
let boardDiv = "";
const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
let pawnMark = "";
if (["p","s"].includes(piece) && captureMark.length == 1)
- pawnMark = V.GetColumn(move.start.y); //start column
+ pawnMark = V.CoordToColumn(move.start.y); //start column
// Piece or pawn movement
let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
const square = moveOrSquare;
if (square == "-")
return undefined;
- return V.SquareToCoords(square);
+ // Enemy pawn initial column must be given too:
+ let res = [];
+ const epParts = square.split(",");
+ res.push(V.SquareToCoords(epParts[0]));
+ res.push(V.ColumnToCoord(epParts[1]));
+ return res;
}
// Argument is a move:
const move = moveOrSquare;
const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
{
- return {
- x: (ex + sx)/2,
- y: (move.end.y + sy)/2
- };
+ return
+ [
+ {
+ x: (ex + sx)/2,
+ y: (move.end.y + sy)/2
+ },
+ move.end.y
+ ];
}
return undefined; //default
}
if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
{
for (let piece of finalPieces)
- moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY], {c:color,p:piece}));
+ {
+ moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+ {c:color,p:piece}));
+ }
if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
&& this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
{
// En passant
const Lep = this.epSquares.length;
const epSquare = this.epSquares[Lep-1]; //always at least one element
- if (!!epSquare && epSquare.x == x+shiftX && epSquare.y == y)
+ if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
+ && Math.abs(epSquare[1] - y) == 1)
{
let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
enpassantMove.vanish.push({
- x: epSquare.x,
- y: epSquare.y,
+ x: x,
+ y: epSquare[1],
p: 'p',
- c: this.getColor(epSquare.x,epSquare.y)
+ c: this.getColor(x,epSquare[1])
});
moves.push(enpassantMove);
}
let pawnShift = (c=="w" ? 1 : -1);
if (x+pawnShift>=0 && x+pawnShift<V.size.x)
{
- if (this.getPiece(x+pawnShift,y)==V.PAWN && this.getColor(x+pawnShift,y)==c)
+ if (this.getPiece(x+pawnShift,y)==V.PAWN
+ && this.getColor(x+pawnShift,y)==c)
+ {
return true;
+ }
}
}
return false;
}
+
+ getNotation(move)
+ {
+ const piece = this.getPiece(move.start.x, move.start.y);
+ if (piece == V.PAWN)
+ {
+ // Pawn move
+ let notation = "";
+ if (move.vanish.length == 2) //capture
+ notation = finalSquare;
+ else
+ {
+ // No capture
+ const startColumn = V.CoordToColumn(move.start.y);
+ notation = startColumn + "x" + finalSquare;
+ }
+ if (move.appear[0].p != V.PAWN) //promotion
+ notation += "=" + move.appear[0].p.toUpperCase();
+ return notation;
+ }
+ return super.getNotation(move); //all other pieces are orthodox
+ }
}
const VariantRules = BerolinaRules;
if (move.vanish.length > 1)
{
// Capture
- const startColumn = V.GetColumn(move.start.y);
+ const startColumn = V.CoordToColumn(move.start.y);
notation = startColumn + "x" + finalSquare +
"=" + move.appear[0].p.toUpperCase();
}
-class LoserRules extends ChessRules
+class LosersRules extends ChessRules
{
static get HasFlags() { return false; }
}
}
-const VariantRules = LoserRules;
+const VariantRules = LosersRules;
if (move.appear[0].p == V.KING && move.appear[1].p == V.ROOK)
return (move.end.y < move.start.y ? "0-0-0" : "0-0");
// Switch:
- const startSquare =
- String.fromCharCode(97 + move.start.y) + (V.size.x-move.start.x);
- const finalSquare =
- String.fromCharCode(97 + move.end.y) + (V.size.x-move.end.x);
- return "S" + startSquare + finalSquare;
+ return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
}
}
{
static HasFlags() { return false; }
- // Forbid two knights moves in a row at moves 1 and 2
- getPotentialKnightMoves(sq)
- {
- // But this will also affect FEN for problems, and...
- // does it really solve the problem ?
- //if (this.moves. ...)
- }
+ static HasEnpassant() { return false; }
getPotentialKingMoves(sq)
{
{
let positions = _.range(8);
- let randIndex = 2 * _.random(3);
- let bishop1Pos = positions[randIndex];
- let randIndex_tmp = 2 * _.random(3) + 1;
- let bishop2Pos = positions[randIndex_tmp];
+ let randIndex = _.random(7);
+ const kingPos = positions[randIndex];
+ positions.splice(randIndex, 1);
+
+ // At least a knight must be next to the king:
+ let knight1Pos = undefined;
+ if (kingPos == 0)
+ knight1Pos = 1;
+ else if (kingPos == V.size.y-1)
+ knight1Pos = V.size.y-2;
+ else
+ knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
+ // Search for knight1Pos index in positions and remove it
+ const knight1Index = positions.indexOf(knight1Pos);
+ positions.splice(knight1Index, 1);
+
+ // King+knight1 are on two consecutive squares: one light, one dark
+ randIndex = 2 * _.random(2);
+ const bishop1Pos = positions[randIndex];
+ let randIndex_tmp = 2 * _.random(2) + 1;
+ const bishop2Pos = positions[randIndex_tmp];
positions.splice(Math.max(randIndex,randIndex_tmp), 1);
positions.splice(Math.min(randIndex,randIndex_tmp), 1);
- randIndex = _.random(5);
- let knight1Pos = positions[randIndex];
- positions.splice(randIndex, 1);
- randIndex = _.random(4);
- let knight2Pos = positions[randIndex];
+ randIndex = _.random(3);
+ const knight2Pos = positions[randIndex];
positions.splice(randIndex, 1);
- randIndex = _.random(3);
- let queenPos = positions[randIndex];
+ randIndex = _.random(2);
+ const queenPos = positions[randIndex];
positions.splice(randIndex, 1);
- let rook1Pos = positions[0];
- let kingPos = positions[1];
- let rook2Pos = positions[2];
+ const rook1Pos = positions[0];
+ const rook2Pos = positions[1];
pieces[c][rook1Pos] = 'r';
pieces[c][knight1Pos] = 'n';
.bigfont
font-size: 1.2em
+.bold
+ font-weight: bold
+
[type="checkbox"].modal+div .card
max-width: 767px
max-height: 100vh
| They generally move like an orthodox queen,
| but capturing rules are complex.
+p
+ | Note: 'Baroque' is the initial name thought by the author,
+ | but 'Ultima' is also largely adopted.
+ a(href="https://www.chessvariants.com/people.dir/abbott.html")
+ | He prefers 'Baroque'
+ | , and I think me too.
+
h3 Specifications
ul
| La plupart des pièces sont connues mais se déplacent différemment ;
| en général comme une dame orthodoxe, mais les captures sont complexes.
+p
+ | Note : le nom initialement choisit par l'auteur est 'Baroque',
+ | mais 'Ultima' est également largement utilisé.
+ a(href="https://www.chessvariants.com/people.dir/abbott.html")
+ | Il préfère 'Baroque'
+ | , et moi aussi je crois.
+
h3 Caractéristiques
ul
img(src="/images/tmp_checkered/no_ck.png")
figcaption Checkered pieces, born after captures.
+p Note: the initial French name for this variant is "l'Échiqueté".
+
h3 Specifications
ul
Checkered pawns cannot capture en passant, because while the pawn was "passing"
they were of the same color.
+p.bold.bigfont If you wanna play, you can stop reading here.
+
+h3 Stalemate or checkmate?
+
+p.
+ The following diagram seems to show a mating pattern, but the king if
+ "attacked" by a checkered pawn – which still belongs to white.
+ Therefore, it can be considered that it's not really a check because
+ white is unable to "give back the turn".
+ Without the black bishop on a7 it would be mate (without debate), because
+ the king could virtually go to g1 before being captured by the pawn-chamaleon.
+
+figure.diagram-container
+ .diagram
+ | fen:7k/b5pp/8/8/8/8/6ss/7K:
+ figcaption After 1...g2+(#?)
+
+p.
+ The interface considers that it's mate in both cases, following the idea
+ "it's checkmate if we cannot move, and the opponent on its turn could
+ take the king" (maybe after an impossible move).
+
h2.stageDelimiter Stage 2
p.warn This stage is not (and probably will never be) implemented.
img(src="/images/tmp_checkered/no_ck.png")
figcaption Pièces échiquetées, nées suite aux captures.
+p.
+ Note : le (vrai) nom initial de cette variante est "l'Échiqueté".
+ "Checkered" en est la traduction anglaise, et ce dernier terme me paraît
+ plus lisible pour des non francophones.
+
h3 Caractéristiques
ul
Les pions échiquetés ne peuvent capturer en passant, puisque pendant que
le pion adverse "passait" ils étaient dans le même camp.
+p.bold.bigfont Pour jouer, vous pouvez arrêter de lire ici.
+
+h3 Mat ou pat ?
+
+p.
+ La situation du diagramme suivant ressemble à un mat, mais le roi est
+ "attaqué" par un pion échiqueté : celui-ci appartient pour l'instant aux blancs.
+ On peut donc considérer qu'ils ne sont pas vraiment en échec
+ puisqu'incapables de "rendre le trait".
+ Sans le fou noir en a7 ce serait mat (indiscutable), car le roi pourrait
+ virtuellement aller en g1 avant de se faire capturer par le pion caméléon.
+
+figure.diagram-container
+ .diagram
+ | fen:7k/b5pp/8/8/8/8/6ss/7K:
+ figcaption Après 1...g2+(#?)
+
+p.
+ L'interface considère que c'est mat dans les deux cas, partant
+ du principe que "c'est mat si on ne peut pas bouger et que l'adversaire
+ au trait peut capturer le roi" (éventuellement après un coup interdit).
+
h2.stageDelimiter Phase 2
p.warn Cette étape n'est pas (et ne sera probablement jamais) implémentée ici.
Les règles de l'Échiqueté ont été déterminées par Patrick Bernier, puis
développées avec l'aide de Benjamin Auder.
li.
- Merci également à Olive Martin, Christian Poisson, Bevis Martin, Laurent Nouhaud
- et Frédéric Fradet.
+ Merci également à Olive Martin, Christian Poisson, Bevis Martin,
+ Laurent Nouhaud et Frédéric Fradet.